Skip to content

Commit 1c3f53d

Browse files
authored
Validate that the target component is a parent of the scope (#52)
1 parent c6bd7b5 commit 1c3f53d

File tree

13 files changed

+269
-7
lines changed

13 files changed

+269
-7
lines changed

compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/Errors.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ object Errors {
3131
fun invalidComponent(component: String): String =
3232
"The specified component $component isn't a Dagger component"
3333

34+
fun parentComponent(installIn: String, inferredComponent: String): String =
35+
"The installIn component $installIn is a parent component of $inferredComponent and cannot be used as installIn for @AutoBind"
36+
3437
fun nonStandardScope(scope: String): String =
3538
"Scope $scope is not a standard scope so you must specify a component explicitly using the `inComponent` parameter."
3639

compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/processors/AutoBindProcessor.kt

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
212212
private fun ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>.guessComponent(): ClassName {
213213
val scope = annotations.find { it.isAnnotatedWith(Scope::class) }
214214
?: return environment.renderEngine.className(SingletonComponent::class)
215-
return scopeToComponent(scope) ?: run {
215+
return scope.guessComponent() ?: run {
216216
logger?.error(Errors.AutoBind.nonStandardScope(scope.qualifiedName), this)
217217
throw AbortProcessingError()
218218
}
@@ -222,9 +222,40 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
222222
resolver: AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>,
223223
component: ClassName,
224224
) {
225-
if (!resolver.lookupType(component).isAnnotatedWith(DefineComponent::class)) {
225+
val defineComponent = resolver.lookupType(component)
226+
.getAnnotation(DefineComponent::class)
227+
if (defineComponent == null) {
226228
logger?.error(Errors.AutoBind.invalidComponent(component.toString()), this)
229+
return
230+
}
231+
val scope = annotations.find { it.isAnnotatedWith(Scope::class) }
232+
?: return
233+
val inferredComponent = scope.guessComponent() ?: return
234+
if (isComponentChildComponent(resolver, component, inferredComponent)) {
235+
logger?.error(
236+
message = Errors.AutoBind.parentComponent(
237+
installIn = resolver.environment.renderEngine.simpleName(component),
238+
inferredComponent = resolver.environment.renderEngine.simpleName(inferredComponent)
239+
),
240+
node = this
241+
)
242+
}
243+
}
244+
245+
private fun isComponentChildComponent(
246+
resolver: AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>,
247+
installInComponent: ClassName,
248+
inferredComponent: ClassName,
249+
): Boolean {
250+
var c = inferredComponent
251+
while (c != installInComponent) {
252+
c = resolver.lookupType(c)
253+
.getAnnotation(DefineComponent::class)
254+
?.getValue<ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>>("parent")
255+
?.className
256+
?: return false
227257
}
258+
return true
228259
}
229260

230261
fun getBoundSupertypes(
@@ -249,7 +280,7 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
249280

250281
else -> logger?.error(Errors.AutoBind.multipleSuperTypes, type)
251282
}
252-
}
283+
}
253284

254285
val supertypes = type.supertypes.associateByTo(mutableMapOf()) {
255286
environment.renderEngine.rawType(it.toTypeName())
@@ -267,8 +298,8 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
267298
return supertypes.values
268299
}
269300

270-
private fun scopeToComponent(scope: AnnotationModel<*, *>): ClassName? =
271-
when (scope.qualifiedName) {
301+
private fun AnnotationModel<*, *>.guessComponent(): ClassName? =
302+
when (qualifiedName) {
272303
Singleton::class.java.name,
273304
Reusable::class.java.name ->
274305
environment.renderEngine.className(SingletonComponent::class)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (C) 2019 The Dagger Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dagger.hilt.android.components;
18+
19+
import dagger.hilt.DefineComponent;
20+
import dagger.hilt.android.scopes.FragmentScoped;
21+
import dagger.hilt.components.SingletonComponent;
22+
23+
@FragmentScoped
24+
@DefineComponent(parent = SingletonComponent.class)
25+
public interface ActivityComponent {}

compiler/src/test/java/dagger/hilt/android/components/FragmentComponent.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818

1919
import dagger.hilt.DefineComponent;
2020
import dagger.hilt.android.scopes.FragmentScoped;
21-
import dagger.hilt.components.SingletonComponent;
2221

2322
@FragmentScoped
24-
@DefineComponent(parent = SingletonComponent.class)
23+
@DefineComponent(parent = ActivityComponent.class)
2524
public interface FragmentComponent {}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (C) 2020 The Dagger Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dagger.hilt.android.scopes;
18+
19+
import static java.lang.annotation.RetentionPolicy.CLASS;
20+
21+
import java.lang.annotation.Retention;
22+
23+
import javax.inject.Scope;
24+
25+
@Scope
26+
@Retention(CLASS)
27+
public @interface ActivityScoped {}

compiler/src/test/kotlin/se/ansman/dagger/auto/compiler/BaseTest.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,49 @@ abstract class BaseTest(
285285
)
286286
.assertFailedWithMessage(Errors.AutoBind.multipleSuperTypes)
287287
}
288+
289+
@Test
290+
fun `prevents binding into parent or unrelated components`(@TempDir tempDirectory: File) {
291+
compilation(tempDirectory)
292+
.compile(
293+
"""
294+
package se.ansman
295+
296+
interface Repository
297+
298+
@se.ansman.dagger.auto.AutoBind(inComponent = dagger.hilt.components.SingletonComponent::class)
299+
@dagger.hilt.android.scopes.ActivityScoped
300+
class ActivityRepository @javax.inject.Inject constructor() : Repository
301+
"""
302+
)
303+
.assertFailedWithMessage(Errors.AutoBind.parentComponent("SingletonComponent", "ActivityComponent"))
304+
compilation(tempDirectory)
305+
.compile(
306+
"""
307+
package se.ansman
308+
309+
interface Repository
310+
311+
@se.ansman.dagger.auto.AutoBind(inComponent = dagger.hilt.android.components.ActivityComponent::class)
312+
@dagger.hilt.android.scopes.FragmentScoped
313+
class FragmentRepository @javax.inject.Inject constructor() : Repository
314+
"""
315+
)
316+
.assertFailedWithMessage(Errors.AutoBind.parentComponent("ActivityComponent", "FragmentComponent"))
317+
compilation(tempDirectory)
318+
.compile(
319+
"""
320+
package se.ansman
321+
322+
interface Repository
323+
324+
@se.ansman.dagger.auto.AutoBind(inComponent = dagger.hilt.components.SingletonComponent::class)
325+
@dagger.hilt.android.scopes.FragmentScoped
326+
class FragmentRepository @javax.inject.Inject constructor() : Repository
327+
"""
328+
)
329+
.assertFailedWithMessage(Errors.AutoBind.parentComponent("SingletonComponent", "FragmentComponent"))
330+
}
288331
}
289332

290333
@Nested
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package tests.auto_bind.child_component
2+
3+
import dagger.hilt.android.components.ActivityComponent
4+
import dagger.hilt.android.components.FragmentComponent
5+
import se.ansman.dagger.auto.AutoBind
6+
import javax.inject.Inject
7+
import javax.inject.Singleton
8+
9+
interface Repository
10+
11+
@AutoBind
12+
@Singleton
13+
class GlobalRepository @Inject constructor() : Repository
14+
15+
@AutoBind(inComponent = ActivityComponent::class)
16+
@Singleton
17+
class ActivityRepository @Inject constructor() : Repository
18+
19+
@AutoBind(inComponent = FragmentComponent::class)
20+
@Singleton
21+
class FragmentRepository @Inject constructor() : Repository
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Code generated by Auto Dagger. Do not edit.
2+
package tests.auto_bind.child_component;
3+
4+
import dagger.Binds;
5+
import dagger.Module;
6+
import dagger.hilt.InstallIn;
7+
import dagger.hilt.android.components.ActivityComponent;
8+
import dagger.hilt.codegen.OriginatingElement;
9+
10+
@Module
11+
@InstallIn(ActivityComponent.class)
12+
@OriginatingElement(
13+
topLevelClass = ActivityRepository.class
14+
)
15+
public abstract class AutoBindActivityRepositoryActivityModule {
16+
private AutoBindActivityRepositoryActivityModule() {
17+
}
18+
19+
@Binds
20+
public abstract Repository bindActivityRepositoryAsRepository(
21+
ActivityRepository activityRepository);
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Code generated by Auto Dagger. Do not edit.
2+
package tests.auto_bind.child_component;
3+
4+
import dagger.Binds;
5+
import dagger.Module;
6+
import dagger.hilt.InstallIn;
7+
import dagger.hilt.android.components.FragmentComponent;
8+
import dagger.hilt.codegen.OriginatingElement;
9+
10+
@Module
11+
@InstallIn(FragmentComponent.class)
12+
@OriginatingElement(
13+
topLevelClass = FragmentRepository.class
14+
)
15+
public abstract class AutoBindFragmentRepositoryFragmentModule {
16+
private AutoBindFragmentRepositoryFragmentModule() {
17+
}
18+
19+
@Binds
20+
public abstract Repository bindFragmentRepositoryAsRepository(
21+
FragmentRepository fragmentRepository);
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Code generated by Auto Dagger. Do not edit.
2+
package tests.auto_bind.child_component;
3+
4+
import dagger.Binds;
5+
import dagger.Module;
6+
import dagger.hilt.InstallIn;
7+
import dagger.hilt.codegen.OriginatingElement;
8+
import dagger.hilt.components.SingletonComponent;
9+
10+
@Module
11+
@InstallIn(SingletonComponent.class)
12+
@OriginatingElement(
13+
topLevelClass = GlobalRepository.class
14+
)
15+
public abstract class AutoBindGlobalRepositorySingletonModule {
16+
private AutoBindGlobalRepositorySingletonModule() {
17+
}
18+
19+
@Binds
20+
public abstract Repository bindGlobalRepositoryAsRepository(GlobalRepository globalRepository);
21+
}

0 commit comments

Comments
 (0)