Version 2.1.69
Partial cherry pick: Check accessibility to fields and members based on the resolution holder
CL: https://r8-review.googlesource.com/c/r8/+/54092
This CL cherry picks the tests from https://r8-review.googlesource.com/c/r8/+/54092.
The non-test changes does not cherry pick cleanly. Therefore, this CL manually implements the fixes from https://r8-review.googlesource.com/c/r8/+/54092 in 2.1.
Unlike the change in https://r8-review.googlesource.com/c/r8/+/54092, this fix does not attempt to clean up the AccessControl API.
Bug: 169045091
Change-Id: Ic7e94d309c6ac6b19c2cafc83f71260c28e3e457
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 944cc82..a496472 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.1.68";
+ public static final String LABEL = "2.1.69";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 788b358..1a36378 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -37,43 +37,53 @@
public static OptionalBool isMethodAccessible(
DexEncodedMethod method,
- DexClass holder,
+ DexClass resolvedHolder,
+ DexClass initialResolutionHolder,
ProgramMethod context,
AppView<? extends AppInfoWithClassHierarchy> appView) {
- return isMethodAccessible(method, holder, context.getHolder(), appView.appInfo());
+ return isMethodAccessible(
+ method, resolvedHolder, initialResolutionHolder, context.getHolder(), appView.appInfo());
}
public static OptionalBool isMethodAccessible(
DexEncodedMethod method,
- DexClass holder,
+ DexClass resolvedHolder,
+ DexClass initialResolutionHolder,
DexProgramClass context,
AppInfoWithClassHierarchy appInfo) {
- return isMemberAccessible(method.accessFlags, holder, context, appInfo);
+ return isMemberAccessible(
+ method.accessFlags, resolvedHolder, initialResolutionHolder, context, appInfo);
}
public static OptionalBool isFieldAccessible(
DexEncodedField field,
- DexClass holder,
+ DexClass resolvedHolder,
+ DexClass initialResolutionHolder,
ProgramMethod context,
AppView<? extends AppInfoWithClassHierarchy> appView) {
- return isFieldAccessible(field, holder, context.getHolder(), appView.appInfo());
+ return isFieldAccessible(
+ field, resolvedHolder, initialResolutionHolder, context.getHolder(), appView.appInfo());
}
public static OptionalBool isFieldAccessible(
DexEncodedField field,
- DexClass holder,
+ DexClass resolvedHolder,
+ DexClass initialResolutionHolder,
DexProgramClass context,
AppInfoWithClassHierarchy appInfo) {
- return isMemberAccessible(field.accessFlags, holder, context, appInfo);
+ return isMemberAccessible(
+ field.accessFlags, resolvedHolder, initialResolutionHolder, context, appInfo);
}
private static OptionalBool isMemberAccessible(
AccessFlags<?> memberFlags,
- DexClass holder,
+ DexClass resolvedHolder,
+ DexClass initialResolutionHolder,
DexProgramClass context,
AppInfoWithClassHierarchy appInfo) {
OptionalBool classAccessibility =
- isClassAccessible(holder, context, appInfo.options().featureSplitConfiguration);
+ isClassAccessible(
+ initialResolutionHolder, context, appInfo.options().featureSplitConfiguration);
if (classAccessibility.isFalse()) {
return OptionalBool.FALSE;
}
@@ -81,15 +91,16 @@
return classAccessibility;
}
if (memberFlags.isPrivate()) {
- if (!isNestMate(holder, context)) {
+ if (!isNestMate(resolvedHolder, context)) {
return OptionalBool.FALSE;
}
return classAccessibility;
}
- if (holder.getType().isSamePackage(context.getType())) {
+ if (resolvedHolder.getType().isSamePackage(context.getType())) {
return classAccessibility;
}
- if (memberFlags.isProtected() && appInfo.isSubtype(context.getType(), holder.getType())) {
+ if (memberFlags.isProtected()
+ && appInfo.isSubtype(context.getType(), resolvedHolder.getType())) {
return classAccessibility;
}
return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 0d2390b..b49e712 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -20,8 +20,13 @@
return null;
}
+ public final OptionalBool isAccessibleFrom(
+ ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+ return isAccessibleFrom(context.getHolder(), appInfo);
+ }
+
public abstract OptionalBool isAccessibleFrom(
- ProgramMethod context, AppInfoWithClassHierarchy appInfo);
+ DexProgramClass context, AppInfoWithClassHierarchy appInfo);
public boolean isSuccessfulResolution() {
return false;
@@ -67,9 +72,10 @@
}
@Override
- public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+ public OptionalBool isAccessibleFrom(
+ DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
return AccessControl.isFieldAccessible(
- resolvedField, initialResolutionHolder, context.getHolder(), appInfo);
+ resolvedField, resolvedHolder, initialResolutionHolder, context, appInfo);
}
@Override
@@ -88,7 +94,8 @@
private static final FailedFieldResolutionResult INSTANCE = new FailedFieldResolutionResult();
@Override
- public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+ public OptionalBool isAccessibleFrom(
+ DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
return OptionalBool.FALSE;
}
@@ -107,7 +114,8 @@
private static final UnknownFieldResolutionResult INSTANCE = new UnknownFieldResolutionResult();
@Override
- public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+ public OptionalBool isAccessibleFrom(
+ DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
return OptionalBool.FALSE;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index fab752df..a3cd8bb 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -136,6 +136,10 @@
|| initialResolutionHolder.type == resolvedMethod.holder();
}
+ public DexClass getInitialResolutionHolder() {
+ return initialResolutionHolder;
+ }
+
public DexClass getResolvedHolder() {
return resolvedHolder;
}
@@ -162,7 +166,7 @@
public OptionalBool isAccessibleFrom(
DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
return AccessControl.isMethodAccessible(
- resolvedMethod, initialResolutionHolder, context, appInfo);
+ resolvedMethod, resolvedHolder, initialResolutionHolder, context, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 7bdcc84..798b246 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -14,6 +13,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -80,12 +80,10 @@
@Override
public boolean isMaterializableInContext(
AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
- return AccessControl.isFieldAccessible(
- appView.appInfo().resolveField(field).getResolvedField(),
- appView.definitionForHolder(field),
- context.getHolder(),
- appView.appInfo())
- .isTrue();
+ SuccessfulFieldResolutionResult resolutionResult =
+ appView.appInfo().resolveField(field).asSuccessfulResolution();
+ return resolutionResult != null
+ && resolutionResult.isAccessibleFrom(context, appView.appInfo()).isTrue();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 40942d4..050886e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -65,7 +65,6 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -966,7 +965,8 @@
.appInfo()
.resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
.asSingleResolution();
- if (resolutionResult == null) {
+ if (resolutionResult == null
+ || resolutionResult.isAccessibleFrom(context, appView.appInfo()).isPossiblyFalse()) {
continue;
}
@@ -977,16 +977,6 @@
continue;
}
- OptionalBool methodAccessible =
- AccessControl.isMethodAccessible(
- singleTarget.getDefinition(),
- singleTarget.getHolder().asDexClass(),
- context.getHolder(),
- appView.withClassHierarchy().appInfo());
- if (!methodAccessible.isTrue()) {
- continue;
- }
-
DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
oracle.isForcedInliningOracle()
@@ -1005,6 +995,16 @@
continue;
}
+ DexType downcastTypeOrNull = getDowncastTypeIfNeeded(strategy, invoke, singleTarget);
+ if (downcastTypeOrNull != null) {
+ DexClass downcastClass = appView.definitionFor(downcastTypeOrNull);
+ if (downcastClass == null
+ || AccessControl.isClassAccessible(downcastClass, context, appView)
+ .isPossiblyFalse()) {
+ continue;
+ }
+ }
+
if (!inlineeStack.isEmpty()
&& !strategy.allowInliningOfInvokeInInlinee(
action, inlineeStack.size(), whyAreYouNotInliningReporter)) {
@@ -1052,12 +1052,7 @@
iterator.previous();
strategy.markInlined(inlinee);
iterator.inlineInvoke(
- appView,
- code,
- inlinee.code,
- blockIterator,
- blocksToRemove,
- getDowncastTypeIfNeeded(strategy, invoke, singleTarget));
+ appView, code, inlinee.code, blockIterator, blocksToRemove, downcastTypeOrNull);
if (inlinee.reason == Reason.SINGLE_CALLER) {
feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index abd24cb..d51a639 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -60,7 +60,6 @@
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
@@ -292,7 +291,8 @@
.appInfo()
.resolveMethod(invokeMethod.getInvokedMethod(), invokeMethod.getInterfaceBit())
.asSingleResolution();
- if (resolutionResult == null) {
+ if (resolutionResult == null
+ || resolutionResult.isAccessibleFrom(method, appView.appInfo()).isPossiblyFalse()) {
return user; // Not eligible.
}
@@ -311,13 +311,11 @@
return user; // Not eligible.
}
- OptionalBool methodAccessible =
- AccessControl.isMethodAccessible(
- singleTargetMethod, singleTarget.getHolder().asDexClass(), method, appView);
-
- if (!methodAccessible.isTrue()) {
+ if (AccessControl.isClassAccessible(singleTarget.getHolder(), method, appView)
+ .isPossiblyFalse()) {
return user; // Not eligible.
}
+
// Eligible constructor call (for new instance roots only).
if (user.isInvokeDirect()) {
InvokeDirect invoke = user.asInvokeDirect();
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 1c1de57..76086cc 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -320,7 +320,11 @@
for (ProgramMethod context : contexts) {
boolean inaccessibleInContext =
AccessControl.isFieldAccessible(
- resolvedField, resolutionResult.getResolvedHolder(), context, appView)
+ resolvedField,
+ resolutionResult.getResolvedHolder(),
+ resolutionResult.getResolvedHolder(),
+ context,
+ appView)
.isPossiblyFalse();
if (inaccessibleInContext) {
accessibleInAllContexts = false;
diff --git a/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
new file mode 100644
index 0000000..5c1453e
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+public class NestHost {
+ /*private*/ int f;
+
+ public static class NestMember extends NestHost {}
+}
diff --git a/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
new file mode 100644
index 0000000..02fd5e7
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+public class NonNestMember extends NestHost.NestMember {}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index fbe8b07..bf37f3a 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -245,6 +245,11 @@
return ClassFileTransformer.create(clazz);
}
+ public static ClassFileTransformer transformer(Path path, ClassReference classReference)
+ throws IOException {
+ return ClassFileTransformer.create(Files.readAllBytes(path), classReference);
+ }
+
public static ClassFileTransformer transformer(byte[] bytes, ClassReference classReference) {
return ClassFileTransformer.create(bytes, classReference);
}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java b/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
new file mode 100644
index 0000000..dfed133
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b158429654;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.regress.b158429654.innerpackage.InnerClass;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InliningNonAccessible extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public InliningNonAccessible(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCompileToInvalidFileD8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(OuterAbstract.class, OuterImpl.class, InnerClass.class)
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .noMinification()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ OuterImpl.register(args);
+ new InnerClass().foobar();
+ System.out.println("42");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/OuterAbstract.java b/src/test/java/com/android/tools/r8/regress/b158429654/OuterAbstract.java
new file mode 100644
index 0000000..0c5a078
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/OuterAbstract.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b158429654;
+
+public abstract class OuterAbstract {
+ private static OuterAbstract sInstance;
+
+ public static OuterAbstract getInstance() {
+ return sInstance;
+ }
+
+ public static void setsInstance(OuterAbstract sInstance) {
+ OuterAbstract.sInstance = sInstance;
+ }
+
+ public abstract void theMethod();
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/OuterImpl.java b/src/test/java/com/android/tools/r8/regress/b158429654/OuterImpl.java
new file mode 100644
index 0000000..edca5c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/OuterImpl.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b158429654;
+
+class OuterImpl extends OuterAbstract {
+
+ public static void register(String[] args) {
+ OuterAbstract.setsInstance(new OuterImpl());
+ }
+
+ private OuterImpl() {}
+
+ @Override
+ public void theMethod() {}
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/innerpackage/InnerClass.java b/src/test/java/com/android/tools/r8/regress/b158429654/innerpackage/InnerClass.java
new file mode 100644
index 0000000..b2f8582
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/innerpackage/InnerClass.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b158429654.innerpackage;
+
+import com.android.tools.r8.regress.b158429654.OuterAbstract;
+
+public class InnerClass {
+
+ public void foobar() {
+ OuterAbstract.getInstance().theMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
index ed64145..6773040 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -5,9 +5,7 @@
package com.android.tools.r8.resolution.packageprivate;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
@@ -29,10 +27,7 @@
import com.android.tools.r8.resolution.packageprivate.a.NonAbstractExtendingA;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassTransformer;
-import com.google.common.collect.ImmutableSet;
import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,13 +70,7 @@
DexProgramClass context =
appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
- assertTrue(lookupResult.isLookupResultSuccess());
- Set<String> targets = new HashSet<>();
- lookupResult.forEach(
- target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
- // TODO(b/148591377): The set should be empty.
- ImmutableSet<String> expected = ImmutableSet.of(Abstract.class.getTypeName() + ".foo");
- assertEquals(expected, targets);
+ assertTrue(lookupResult.isLookupResultFailure());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
new file mode 100644
index 0000000..bf08534
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.b169045091.testclasses.HelloGreeter;
+import com.android.tools.r8.shaking.b169045091.testclasses.WorldGreeterBase;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B169045091 extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B169045091(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getProgramClasses())
+ .addProgramClassFileData(getWorldGreeterClassFileData())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getProgramClasses())
+ .addProgramClassFileData(getWorldGreeterClassFileData())
+ .addKeepMainRule(TestClass.class)
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private List<Class<?>> getProgramClasses() {
+ return ImmutableList.of(
+ TestClass.class, HelloGreeter.class, HelloGreeterBase.class, WorldGreeterBase.class);
+ }
+
+ private byte[] getWorldGreeterClassFileData() throws Exception {
+ return transformer(WorldGreeter.class)
+ .removeMethods(
+ (int access, String name, String descriptor, String signature, String[] exceptions) ->
+ name.equals("world"))
+ .transform();
+ }
+
+ @Test
+ public void testAccessibility() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(getProgramClasses())
+ .addClassProgramData(getWorldGreeterClassFileData())
+ .build(),
+ TestClass.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+ DexProgramClass context =
+ appView.definitionFor(buildType(TestClass.class, dexItemFactory)).asProgramClass();
+
+ // Test that HelloGreeter.greet() is accessible to TestClass.
+ DexMethod helloReference = buildNullaryVoidMethod(HelloGreeter.class, "hello", dexItemFactory);
+ assertTrue(
+ appInfo.resolveMethodOnClass(helloReference).isAccessibleFrom(context, appInfo).isTrue());
+
+ // Test that WorldGreeter.greet() is inaccessible to TestClass.
+ DexMethod worldReference = buildNullaryVoidMethod(WorldGreeter.class, "world", dexItemFactory);
+ assertTrue(
+ appInfo.resolveMethodOnClass(worldReference).isAccessibleFrom(context, appInfo).isFalse());
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ // HelloGreeterBase.greet() is accessible to TestClass because they are in the same package.
+ new HelloGreeter().hello();
+
+ try {
+ // WorldGreeterBase.world() is inaccessible to TestClass.
+ new WorldGreeter().world();
+ throw new RuntimeException();
+ } catch (IllegalAccessError e) {
+ System.out.println(" world!");
+ }
+ }
+ }
+
+ @NeverMerge
+ public static class HelloGreeterBase {
+ @NeverInline
+ protected void hello() {
+ System.out.print("Hello");
+ }
+ }
+
+ @NeverClassInline
+ public static class WorldGreeter extends WorldGreeterBase {
+
+ // Removed by a transformer.
+ @Override
+ public void world() {
+ super.world();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java b/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java
new file mode 100644
index 0000000..a2dfead
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091;
+
+import static com.android.tools.r8.references.Reference.INT;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.b169045091.B169045091.TestClass;
+import com.android.tools.r8.shaking.b169045091.examples.NestHost;
+import com.android.tools.r8.shaking.b169045091.examples.NestHost.NestMember;
+import com.android.tools.r8.shaking.b169045091.examples.NonNestMember;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NestMemberAccessibilityTest extends TestBase {
+
+ private final Path TEST_DIRECTORY =
+ Paths.get(ToolHelper.EXAMPLES_JAVA11_BUILD_DIR)
+ .resolve(
+ DescriptorUtils.getBinaryNameFromJavaType(NestHost.class.getPackage().getName()));
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return TestBase.getTestParameters().withNoneRuntime().build();
+ }
+
+ public NestMemberAccessibilityTest(TestParameters parameters) {}
+
+ @Test
+ public void testAccessibility() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses()
+ .addProgramFiles(getProgramFiles())
+ .addClassProgramData(getNestHostClassFileData())
+ .build(),
+ TestClass.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+ DexProgramClass hostContext =
+ appView
+ .definitionForProgramType(buildType(NestHost.class, dexItemFactory))
+ .asProgramClass();
+
+ DexProgramClass memberContext =
+ appView
+ .definitionForProgramType(buildType(NestMember.class, dexItemFactory))
+ .asProgramClass();
+
+ DexProgramClass nonMemberContext =
+ appView
+ .definitionForProgramType(buildType(NonNestMember.class, dexItemFactory))
+ .asProgramClass();
+
+ // Test that NestHost.f is accessible to NestHost and NestMember but not NonNestMember.
+ DexField hostFieldReference =
+ buildField(
+ Reference.field(Reference.classFromClass(NestHost.class), "f", INT), dexItemFactory);
+ assertTrue(
+ appInfo.resolveField(hostFieldReference).isAccessibleFrom(hostContext, appInfo).isTrue());
+ assertTrue(
+ appInfo.resolveField(hostFieldReference).isAccessibleFrom(memberContext, appInfo).isTrue());
+ assertTrue(
+ appInfo
+ .resolveField(hostFieldReference)
+ .isAccessibleFrom(nonMemberContext, appInfo)
+ .isFalse());
+
+ // Test that NestMember.f is accessible to NestMember but not NonNestMember.
+ DexField memberFieldReference =
+ buildField(
+ Reference.field(Reference.classFromClass(NestMember.class), "f", INT), dexItemFactory);
+ assertTrue(
+ appInfo
+ .resolveField(memberFieldReference)
+ .isAccessibleFrom(memberContext, appInfo)
+ .isTrue());
+ assertTrue(
+ appInfo
+ .resolveField(memberFieldReference)
+ .isAccessibleFrom(nonMemberContext, appInfo)
+ .isFalse());
+
+ // Test that NonNestMember.f is inaccessible to NonNestMember.
+ DexField nonMemberFieldReference =
+ buildField(
+ Reference.field(Reference.classFromClass(NonNestMember.class), "f", INT),
+ dexItemFactory);
+ assertTrue(
+ appInfo
+ .resolveField(nonMemberFieldReference)
+ .isAccessibleFrom(nonMemberContext, appInfo)
+ .isFalse());
+ }
+
+ private List<Path> getProgramFiles() {
+ return ImmutableList.of(
+ TEST_DIRECTORY.resolve("NestHost$NestMember.class"),
+ TEST_DIRECTORY.resolve("NonNestMember.class"));
+ }
+
+ private byte[] getNestHostClassFileData() throws Exception {
+ return transformer(
+ TEST_DIRECTORY.resolve("NestHost.class"), Reference.classFromClass(NestHost.class))
+ .setPrivate(NestHost.class.getDeclaredField("f"))
+ .transform();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
new file mode 100644
index 0000000..872e37b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+/**
+ * Mirror of src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
+ */
+public class NestHost {
+
+ public int f;
+
+ public static class NestMember {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
new file mode 100644
index 0000000..26de699
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+/**
+ * Mirror of
+ * src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java.
+ */
+public class NonNestMember {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java
new file mode 100644
index 0000000..edc59a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.shaking.b169045091.B169045091.HelloGreeterBase;
+
+@NeverClassInline
+public class HelloGreeter extends HelloGreeterBase {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java
new file mode 100644
index 0000000..8f30083
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.testclasses;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class WorldGreeterBase {
+ @NeverInline
+ protected void world() {
+ System.out.println(" world!");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 1f1d744..804fe5c 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -13,15 +13,18 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
import com.android.tools.r8.utils.DescriptorUtils;
import java.io.IOException;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
@@ -354,6 +357,16 @@
return setAccessFlags(method, MethodAccessFlags::setBridge);
}
+ public ClassFileTransformer setPrivate(Field field) {
+ return setAccessFlags(
+ field,
+ accessFlags -> {
+ accessFlags.setPrivate();
+ accessFlags.unsetProtected();
+ accessFlags.unsetPublic();
+ });
+ }
+
public ClassFileTransformer setPublic(Method method) {
return setAccessFlags(
method,
@@ -383,11 +396,34 @@
return setAccessFlags(Reference.methodFromMethod(constructor), setter);
}
+ public ClassFileTransformer setAccessFlags(Field field, Consumer<FieldAccessFlags> setter) {
+ return setAccessFlags(Reference.fieldFromField(field), setter);
+ }
+
public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
return setAccessFlags(Reference.methodFromMethod(method), setter);
}
private ClassFileTransformer setAccessFlags(
+ FieldReference fieldReference, Consumer<FieldAccessFlags> setter) {
+ return addClassTransformer(
+ new ClassTransformer() {
+
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String descriptor, String signature, Object value) {
+ FieldAccessFlags accessFlags = FieldAccessFlags.fromCfAccessFlags(access);
+ if (name.equals(fieldReference.getFieldName())
+ && descriptor.equals(fieldReference.getFieldType().getDescriptor())) {
+ setter.accept(accessFlags);
+ }
+ return super.visitField(
+ accessFlags.getAsCfAccessFlags(), name, descriptor, signature, value);
+ }
+ });
+ }
+
+ private ClassFileTransformer setAccessFlags(
MethodReference methodReference, Consumer<MethodAccessFlags> setter) {
return addClassTransformer(
new ClassTransformer() {