Skip to content

Commit db25237

Browse files
Chang-EricDagger Team
authored andcommitted
Correctly handle cases where base classes have a package private constructor that isn't visible to the subclass.
RELNOTES=Fixed an issue where base classes with a package private constructor would cause the generated code to fail PiperOrigin-RevId: 6236625
1 parent 18ce1b5 commit db25237

File tree

11 files changed

+275
-56
lines changed

11 files changed

+275
-56
lines changed

java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public void generate() throws IOException {
9595
Generators.copyConstructors(
9696
metadata.baseElement(),
9797
CodeBlock.builder().addStatement("_initHiltInternal()").build(),
98-
builder);
98+
builder,
99+
metadata.element());
99100
builder.addMethod(init());
100101
if (!metadata.overridesAndroidEntryPointClass()) {
101102
builder

java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void generate() throws IOException {
7878
JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
7979
Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
8080
Processors.addGeneratedAnnotation(builder, env, getClass());
81-
Generators.copyConstructors(metadata.baseElement(), builder);
81+
Generators.copyConstructors(metadata.baseElement(), builder, metadata.element());
8282

8383
metadata.baseElement().getTypeParameters().stream()
8484
.map(XTypeParameterElement::getTypeVariableName)

java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ TypeSpec createTypeSpec() {
8686
Processors.addGeneratedAnnotation(builder, env, getClass());
8787
Generators.copyLintAnnotations(metadata.element(), builder);
8888
Generators.copySuppressAnnotations(metadata.element(), builder);
89-
Generators.copyConstructors(metadata.baseElement(), builder);
89+
Generators.copyConstructors(metadata.baseElement(), builder, metadata.element());
9090

9191
metadata.baseElement().getTypeParameters().stream()
9292
.map(XTypeParameterElement::getTypeVariableName)

java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import dagger.hilt.android.processor.internal.AndroidClassNames;
4646
import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata.AndroidType;
4747
import dagger.hilt.processor.internal.ClassNames;
48+
import dagger.hilt.processor.internal.Processors;
4849
import java.util.List;
4950
import java.util.Optional;
5051
import java.util.stream.Collectors;
@@ -65,15 +66,20 @@ static void addGeneratedBaseClassJavadoc(TypeSpec.Builder builder, ClassName ann
6566
}
6667

6768
/** Copies all constructors with arguments to the builder. */
68-
static void copyConstructors(XTypeElement baseClass, TypeSpec.Builder builder) {
69-
copyConstructors(baseClass, CodeBlock.builder().build(), builder);
69+
static void copyConstructors(
70+
XTypeElement baseClass, TypeSpec.Builder builder, XTypeElement subclassReference) {
71+
copyConstructors(baseClass, CodeBlock.builder().build(), builder, subclassReference);
7072
}
7173

7274
/** Copies all constructors with arguments along with an appended body to the builder. */
73-
static void copyConstructors(XTypeElement baseClass, CodeBlock body, TypeSpec.Builder builder) {
75+
static void copyConstructors(
76+
XTypeElement baseClass,
77+
CodeBlock body,
78+
TypeSpec.Builder builder,
79+
XTypeElement subclassReference) {
7480
ImmutableList<XConstructorElement> constructors =
7581
baseClass.getConstructors().stream()
76-
.filter(constructor -> !constructor.isPrivate())
82+
.filter(constructor -> isConstructorVisibleToSubclass(constructor, subclassReference))
7783
.collect(toImmutableList());
7884

7985
if (constructors.size() == 1
@@ -86,6 +92,30 @@ && getOnlyElement(constructors).getParameters().isEmpty()
8692
constructors.forEach(constructor -> builder.addMethod(copyConstructor(constructor, body)));
8793
}
8894

95+
/**
96+
* Returns true if the constructor is visibile to a subclass in the same package as the reference.
97+
* A reference is used because usually for generators the subclass is being generated and so
98+
* doesn't actually exist.
99+
*/
100+
static boolean isConstructorVisibleToSubclass(
101+
XConstructorElement constructor, XTypeElement subclassReference) {
102+
// Check if the constructor has package private visibility and we're outside the package
103+
if (Processors.hasJavaPackagePrivateVisibility(constructor)
104+
&& !constructor
105+
.getEnclosingElement()
106+
.getPackageName()
107+
.contentEquals(subclassReference.getPackageName())) {
108+
return false;
109+
// Or if it is private, we know generated code can't be in the same file
110+
} else if (constructor.isPrivate()) {
111+
return false;
112+
}
113+
114+
// Assume this is for a subclass per the name, so both protected and public methods are always
115+
// accessible.
116+
return true;
117+
}
118+
89119
/** Returns Optional with AnnotationSpec for Nullable if found on element, empty otherwise. */
90120
private static Optional<AnnotationSpec> getNullableAnnotationSpec(XElement element) {
91121
for (XAnnotation annotation : element.getAllAnnotations()) {

java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,14 @@
1616

1717
package dagger.hilt.android.processor.internal.androidentrypoint;
1818

19-
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
20-
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
21-
import static java.util.stream.Collectors.joining;
2219

2320
import androidx.room.compiler.processing.JavaPoetExtKt;
24-
import androidx.room.compiler.processing.XConstructorElement;
25-
import androidx.room.compiler.processing.XExecutableParameterElement;
2621
import androidx.room.compiler.processing.XFiler;
2722
import androidx.room.compiler.processing.XProcessingEnv;
2823
import androidx.room.compiler.processing.XTypeParameterElement;
29-
import com.google.common.collect.ImmutableList;
3024
import com.squareup.javapoet.ClassName;
3125
import com.squareup.javapoet.JavaFile;
3226
import com.squareup.javapoet.MethodSpec;
33-
import com.squareup.javapoet.ParameterSpec;
3427
import com.squareup.javapoet.TypeSpec;
3528
import dagger.hilt.android.processor.internal.AndroidClassNames;
3629
import dagger.hilt.processor.internal.Processors;
@@ -58,7 +51,6 @@ public void generate() throws IOException {
5851
TypeSpec.classBuilder(generatedClassName.simpleName())
5952
.superclass(metadata.baseClassName())
6053
.addModifiers(metadata.generatedClassModifiers())
61-
.addMethods(baseClassConstructors())
6254
.addMethod(onCreateMethod());
6355

6456
JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
@@ -74,36 +66,14 @@ public void generate() throws IOException {
7466
Generators.addInjectionMethods(metadata, builder);
7567

7668
Generators.addComponentOverride(metadata, builder);
69+
Generators.copyConstructors(metadata.baseElement(), builder, metadata.element());
7770

7871
env.getFiler()
7972
.write(
8073
JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
8174
XFiler.Mode.Isolating);
8275
}
8376

84-
private ImmutableList<MethodSpec> baseClassConstructors() {
85-
return metadata.baseElement().getConstructors().stream()
86-
.map(ServiceGenerator::toMethodSpec)
87-
.collect(toImmutableList());
88-
}
89-
90-
private static MethodSpec toMethodSpec(XConstructorElement constructor) {
91-
ImmutableList<ParameterSpec> params =
92-
constructor.getParameters().stream()
93-
.map(ServiceGenerator::toParameterSpec)
94-
.collect(toImmutableList());
95-
96-
return MethodSpec.constructorBuilder()
97-
.addParameters(params)
98-
.addStatement("super($L)", params.stream().map(p -> p.name).collect(joining(",")))
99-
.build();
100-
}
101-
102-
private static ParameterSpec toParameterSpec(XExecutableParameterElement parameter) {
103-
return ParameterSpec.builder(parameter.getType().getTypeName(), getSimpleName(parameter))
104-
.build();
105-
}
106-
10777
// @CallSuper
10878
// @Override
10979
// protected void onCreate() {

java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ public void generate() {
7878
Generators.addInjectionMethods(metadata, builder);
7979

8080
metadata.baseElement().getConstructors().stream()
81-
.filter(this::isConstructorVisibleToGeneratedClass)
81+
.filter(constructor -> Generators.isConstructorVisibleToSubclass(
82+
constructor, metadata.element()))
8283
.map(this::constructorMethod)
8384
.forEach(builder::addMethod);
8485

@@ -88,16 +89,6 @@ public void generate() {
8889
XFiler.Mode.Isolating);
8990
}
9091

91-
private boolean isConstructorVisibleToGeneratedClass(XConstructorElement constructor) {
92-
if (Processors.hasJavaPackagePrivateVisibility(constructor) && !isInOurPackage(constructor)) {
93-
return false;
94-
} else if (constructor.isPrivate()) {
95-
return false;
96-
}
97-
98-
// We extend the base class, so both protected and public methods are always accessible.
99-
return true;
100-
}
10192

10293
/**
10394
* Returns a pass-through constructor matching the base class's provided constructorElement. The
@@ -185,11 +176,4 @@ private static boolean isFirstRestrictedParameter(XType type) {
185176
return isDeclared(type)
186177
&& Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.CONTEXT);
187178
}
188-
189-
private boolean isInOurPackage(XConstructorElement constructor) {
190-
return constructor
191-
.getEnclosingElement()
192-
.getPackageName()
193-
.contentEquals(metadata.element().getPackageName());
194-
}
195179
}

javatests/dagger/hilt/android/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
android:name=".OptionalInjectTestClasses$OptionalSubclassActivity"
4646
android:exported="false"
4747
tools:ignore="MissingClass"/>
48+
<activity
49+
android:name=".PackagePrivateConstructorTest$TestActivity"
50+
android:exported="false"
51+
tools:ignore="MissingClass"/>
4852
<activity
4953
android:name=".QualifierInKotlinFieldsTest$TestActivity"
5054
android:exported="false"

javatests/dagger/hilt/android/BUILD

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,22 @@ android_local_test(
359359
],
360360
)
361361

362+
android_local_test(
363+
name = "PackagePrivateConstructorTest",
364+
srcs = ["PackagePrivateConstructorTest.java"],
365+
manifest = "AndroidManifest.xml",
366+
manifest_values = {
367+
"minSdkVersion": "14",
368+
},
369+
deps = [
370+
"//:android_local_test_exports",
371+
"//java/dagger/hilt/android:android_entry_point",
372+
"//java/dagger/hilt/android:package_info",
373+
"//java/dagger/hilt/android/testing:hilt_android_test",
374+
"//javatests/dagger/hilt/android/testsubpackage:PackagePrivateConstructorTestClasses",
375+
],
376+
)
377+
362378
android_local_test(
363379
name = "QualifierInKotlinFieldsTest",
364380
srcs = ["QualifierInKotlinFieldsTest.java"],
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright (C) 2024 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;
18+
19+
import android.content.Context;
20+
import android.content.Intent;
21+
import android.os.Build;
22+
import android.os.IBinder;
23+
import androidx.test.core.app.ActivityScenario;
24+
import androidx.test.core.app.ApplicationProvider;
25+
import androidx.test.ext.junit.runners.AndroidJUnit4;
26+
import dagger.hilt.android.testing.HiltAndroidRule;
27+
import dagger.hilt.android.testing.HiltAndroidTest;
28+
import dagger.hilt.android.testing.HiltTestApplication;
29+
import dagger.hilt.android.testsubpackage.PackagePrivateConstructorTestClasses.BaseActivity;
30+
import dagger.hilt.android.testsubpackage.PackagePrivateConstructorTestClasses.BaseBroadcastReceiver;
31+
import dagger.hilt.android.testsubpackage.PackagePrivateConstructorTestClasses.BaseFragment;
32+
import dagger.hilt.android.testsubpackage.PackagePrivateConstructorTestClasses.BaseIntentService;
33+
import dagger.hilt.android.testsubpackage.PackagePrivateConstructorTestClasses.BaseService;
34+
import dagger.hilt.android.testsubpackage.PackagePrivateConstructorTestClasses.BaseView;
35+
import org.junit.Before;
36+
import org.junit.Rule;
37+
import org.junit.Test;
38+
import org.junit.runner.RunWith;
39+
import org.robolectric.Robolectric;
40+
import org.robolectric.annotation.Config;
41+
42+
/** Regression test for b/331280240. */
43+
@HiltAndroidTest
44+
@RunWith(AndroidJUnit4.class)
45+
// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
46+
@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
47+
public final class PackagePrivateConstructorTest {
48+
@Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
49+
50+
@AndroidEntryPoint(BaseActivity.class)
51+
public static final class TestActivity extends Hilt_PackagePrivateConstructorTest_TestActivity {
52+
}
53+
54+
@AndroidEntryPoint(BaseFragment.class)
55+
public static final class TestFragment extends Hilt_PackagePrivateConstructorTest_TestFragment {
56+
}
57+
58+
@AndroidEntryPoint(BaseView.class)
59+
public static final class TestView extends Hilt_PackagePrivateConstructorTest_TestView {
60+
TestView(Context context) {
61+
super(context);
62+
}
63+
}
64+
65+
@AndroidEntryPoint(BaseService.class)
66+
public static final class TestService extends Hilt_PackagePrivateConstructorTest_TestService {
67+
@Override
68+
public IBinder onBind(Intent intent) {
69+
return null;
70+
}
71+
}
72+
73+
@AndroidEntryPoint(BaseIntentService.class)
74+
public static final class TestIntentService
75+
extends Hilt_PackagePrivateConstructorTest_TestIntentService {
76+
public TestIntentService() {
77+
super("TestIntentServiceName");
78+
}
79+
80+
@Override
81+
public void onHandleIntent(Intent intent) {}
82+
}
83+
84+
@AndroidEntryPoint(BaseBroadcastReceiver.class)
85+
public static final class TestBroadcastReceiver
86+
extends Hilt_PackagePrivateConstructorTest_TestBroadcastReceiver {
87+
}
88+
89+
@Before
90+
public void setup() {
91+
rule.inject();
92+
}
93+
94+
// Technically all the tests need to do is check for compilation, but might as well make sure the
95+
// classes are usable
96+
@Test
97+
public void testActivityFragmentView() throws Exception {
98+
try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
99+
scenario.onActivity(
100+
activity -> {
101+
TestFragment fragment = new TestFragment();
102+
activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow();
103+
TestView unused = new TestView(fragment.getContext());
104+
});
105+
}
106+
}
107+
108+
@Test
109+
public void testServices() throws Exception {
110+
Robolectric.setupService(TestService.class);
111+
Robolectric.setupService(TestIntentService.class);
112+
}
113+
114+
@Test
115+
public void testBroadcastReceiver() throws Exception {
116+
TestBroadcastReceiver testBroadcastReceiver = new TestBroadcastReceiver();
117+
Intent intent = new Intent();
118+
testBroadcastReceiver.onReceive(ApplicationProvider.getApplicationContext(), intent);
119+
}
120+
}

javatests/dagger/hilt/android/testsubpackage/BUILD

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ android_local_test(
3434
],
3535
)
3636

37+
android_library(
38+
name = "PackagePrivateConstructorTestClasses",
39+
srcs = ["PackagePrivateConstructorTestClasses.java"],
40+
deps = [
41+
"@maven//:androidx_activity_activity",
42+
"@maven//:androidx_fragment_fragment",
43+
"@maven//:androidx_lifecycle_lifecycle_common",
44+
"@maven//:androidx_lifecycle_lifecycle_viewmodel",
45+
"@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
46+
],
47+
)
48+
3749
exports_files(srcs = [
3850
"UsesLocalComponentTestBindingsTest.java",
3951
"UsesSharedComponent1Test.java",

0 commit comments

Comments
 (0)