Enhance visibility check when rebinding fields. Instead of simple check of the target field/holder's publcity, we should take into account the access context: where such field access occurs. Enqueuer collects field access with context, which are in turn used by member rebinding analysis when building field rebinding. Bug: 76025099, 76447395, 76191597 Change-Id: I9b4d1dcad8888aa8f6119d5fc8b84c07d4c43437
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 a5ac28c..d5ac39d 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
@@ -202,7 +202,7 @@ } return NEVER; } else { - /* package-private */ + /* package-private */ return targetHolder.isSamePackage(contextHolder) ? PACKAGE : NEVER; } }
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 de171a3..3372a20 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -11,8 +11,9 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.ir.optimize.Inliner.Constraint; import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; -import com.google.common.collect.Sets; +import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; @@ -124,7 +125,7 @@ // Rebind to the lowest library class or program class. if (target != null && target.method != method) { DexClass targetClass = appInfo.definitionFor(target.method.holder); - // If the targetclass is not public but the targeted method is, we might run into + // If the target class is not public but the targeted method is, we might run into // visibility problems when rebinding. if (!targetClass.accessFlags.isPublic() && target.accessFlags.isPublic()) { // If the original class is public and this method is public, it might have been called @@ -178,27 +179,37 @@ return null; } - private void computeFieldRebinding(Set<DexField> fields, + private void computeFieldRebinding(Map<DexField, Set<DexEncodedMethod>> fields, BiFunction<DexType, DexField, DexEncodedField> lookup, BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) { - for (DexField field : fields) { + for (Map.Entry<DexField, Set<DexEncodedMethod>> entry : fields.entrySet()) { + DexField field = entry.getKey(); field = lense.lookupField(field, null); DexEncodedField target = lookup.apply(field.getHolder(), field); // Rebind to the lowest library class or program class. Do not rebind accesses to fields that - // are not public, as this might lead to access violation errors. - if (target != null && target.field != field && isVisibleFromOtherClasses(target)) { + // are not visible from the access context. + Set<DexEncodedMethod> contexts = entry.getValue(); + if (target != null && target.field != field && contexts.stream().allMatch(context -> + isVisibleFromOriginalContext(context.method.getHolder(), target))) { builder.map(field, validTargetFor(target.field, field, lookupTargetOnClass)); } } } - private boolean isVisibleFromOtherClasses(DexEncodedField field) { - // If the field is not public, the visibility on the class can not be a further constraint. - if (!field.accessFlags.isPublic()) { - return true; + private boolean isVisibleFromOriginalContext(DexType context, DexEncodedField field) { + DexType holderType = field.field.getHolder(); + DexClass holder = appInfo.definitionFor(holderType); + if (holder == null) { + return false; } - // If the field is public, then a non-public holder class will further constrain visibility. - return appInfo.definitionFor(field.field.getHolder()).accessFlags.isPublic(); + Constraint classVisibility = + Constraint.deriveConstraint(context, holderType, holder.accessFlags, appInfo); + if (classVisibility == Constraint.NEVER) { + return false; + } + Constraint fieldVisibility = + Constraint.deriveConstraint(context, holderType, field.accessFlags, appInfo); + return fieldVisibility != Constraint.NEVER; } public GraphLense run() { @@ -213,10 +224,15 @@ // Likewise static invokes. computeMethodRebinding(appInfo.staticInvokes, this::anyLookup); - computeFieldRebinding(Sets.union(appInfo.staticFieldReads, appInfo.staticFieldWrites), + computeFieldRebinding(appInfo.staticFieldReads, appInfo::resolveFieldOn, DexClass::lookupField); - computeFieldRebinding(Sets.union(appInfo.instanceFieldReads, appInfo.instanceFieldWrites), + computeFieldRebinding(appInfo.staticFieldWrites, appInfo::resolveFieldOn, DexClass::lookupField); + computeFieldRebinding(appInfo.instanceFieldReads, + appInfo::resolveFieldOn, DexClass::lookupField); + computeFieldRebinding(appInfo.instanceFieldWrites, + appInfo::resolveFieldOn, DexClass::lookupField); + return builder.build(appInfo.dexItemFactory, lense); } }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index a726391..2774709 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -99,10 +99,14 @@ Maps.newIdentityHashMap(); private final Map<DexType, Set<DexMethod>> directInvokes = Maps.newIdentityHashMap(); private final Map<DexType, Set<DexMethod>> staticInvokes = Maps.newIdentityHashMap(); - private final Map<DexType, Set<DexField>> instanceFieldsWritten = Maps.newIdentityHashMap(); - private final Map<DexType, Set<DexField>> instanceFieldsRead = Maps.newIdentityHashMap(); - private final Map<DexType, Set<DexField>> staticFieldsRead = Maps.newIdentityHashMap(); - private final Map<DexType, Set<DexField>> staticFieldsWritten = Maps.newIdentityHashMap(); + private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsWritten = + Maps.newIdentityHashMap(); + private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsRead = + Maps.newIdentityHashMap(); + private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsRead = + Maps.newIdentityHashMap(); + private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsWritten = + Maps.newIdentityHashMap(); private final ProtoLiteExtension protoLiteExtension; private final Set<DexField> protoLiteFields = Sets.newIdentityHashSet(); @@ -361,7 +365,7 @@ @Override public boolean registerInstanceFieldWrite(DexField field) { - if (!registerItemWithTarget(instanceFieldsWritten, field)) { + if (!registerItemWithTargetAndContext(instanceFieldsWritten, field, currentMethod)) { return false; } if (Log.ENABLED) { @@ -374,7 +378,7 @@ @Override public boolean registerInstanceFieldRead(DexField field) { - if (!registerItemWithTarget(instanceFieldsRead, field)) { + if (!registerItemWithTargetAndContext(instanceFieldsRead, field, currentMethod)) { return false; } if (Log.ENABLED) { @@ -392,7 +396,7 @@ @Override public boolean registerStaticFieldRead(DexField field) { - if (!registerItemWithTarget(staticFieldsRead, field)) { + if (!registerItemWithTargetAndContext(staticFieldsRead, field, currentMethod)) { return false; } if (Log.ENABLED) { @@ -404,7 +408,7 @@ @Override public boolean registerStaticFieldWrite(DexField field) { - if (!registerItemWithTarget(staticFieldsWritten, field)) { + if (!registerItemWithTargetAndContext(staticFieldsWritten, field, currentMethod)) { return false; } if (Log.ENABLED) { @@ -1208,34 +1212,41 @@ } } - private Set<DexField> collectFields(Map<DexType, Set<DexField>> map) { - return map.values().stream().flatMap(Collection::stream) - .collect(Collectors.toCollection(Sets::newIdentityHashSet)); + private Map<DexField, Set<DexEncodedMethod>> collectFields( + Map<DexType, Set<TargetWithContext<DexField>>> map) { + Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>(); + for (Map.Entry<DexType, Set<TargetWithContext<DexField>>> entry : map.entrySet()) { + for (TargetWithContext<DexField> fieldWithContext : entry.getValue()) { + DexField field = fieldWithContext.getTarget(); + DexEncodedMethod context = fieldWithContext.getContext(); + result.computeIfAbsent(field, k -> Sets.newIdentityHashSet()) + .add(context); + } + } + return result; } - SortedSet<DexField> collectInstanceFieldsRead() { - return ImmutableSortedSet.copyOf( - PresortedComparable<DexField>::slowCompareTo, collectFields(instanceFieldsRead)); + Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsRead() { + return Collections.unmodifiableMap(collectFields(instanceFieldsRead)); } - SortedSet<DexField> collectInstanceFieldsWritten() { - return ImmutableSortedSet.copyOf( - PresortedComparable<DexField>::slowCompareTo, collectFields(instanceFieldsWritten)); + Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsWritten() { + return Collections.unmodifiableMap(collectFields(instanceFieldsWritten)); } - SortedSet<DexField> collectStaticFieldsRead() { - return ImmutableSortedSet.copyOf( - PresortedComparable<DexField>::slowCompareTo, collectFields(staticFieldsRead)); + Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsRead() { + return Collections.unmodifiableMap(collectFields(staticFieldsRead)); } - SortedSet<DexField> collectStaticFieldsWritten() { - return ImmutableSortedSet.copyOf( - PresortedComparable<DexField>::slowCompareTo, collectFields(staticFieldsWritten)); + Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsWritten() { + return Collections.unmodifiableMap(collectFields(staticFieldsWritten)); } - private Set<DexField> collectReachedFields(Map<DexType, Set<DexField>> map, - Function<DexField, DexField> lookup) { - return map.values().stream().flatMap(set -> set.stream().map(lookup).filter(Objects::nonNull)) + private Set<DexField> collectReachedFields( + Set<DexField> set, Function<DexField, DexField> lookup) { + return set.stream() + .map(lookup) + .filter(Objects::nonNull) .collect(Collectors.toCollection(Sets::newIdentityHashSet)); } @@ -1249,16 +1260,11 @@ return target == null ? null : target.field; } - SortedSet<DexField> collectFieldsRead() { + SortedSet<DexField> mergeFieldAccesses(Set<DexField> instanceFields, Set<DexField> staticFields) { return ImmutableSortedSet.copyOf(PresortedComparable<DexField>::slowCompareTo, - Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField), - collectReachedFields(staticFieldsRead, this::tryLookupStaticField))); - } - - SortedSet<DexField> collectFieldsWritten() { - return ImmutableSortedSet.copyOf(PresortedComparable<DexField>::slowCompareTo, - Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField), - collectReachedFields(staticFieldsWritten, this::tryLookupStaticField))); + Sets.union( + collectReachedFields(instanceFields, this::tryLookupInstanceField), + collectReachedFields(staticFields, this::tryLookupStaticField))); } private void markClassAsInstantiatedWithCompatRule(DexClass clazz) { @@ -1429,21 +1435,21 @@ */ public final SortedSet<DexField> fieldsWritten; /** - * Set of all field ids used in instance field reads. + * Set of all field ids used in instance field reads, along with access context. */ - public final SortedSet<DexField> instanceFieldReads; + public final Map<DexField, Set<DexEncodedMethod>> instanceFieldReads; /** - * Set of all field ids used in instance field writes. + * Set of all field ids used in instance field writes, along with access context. */ - public final SortedSet<DexField> instanceFieldWrites; + public final Map<DexField, Set<DexEncodedMethod>> instanceFieldWrites; /** - * Set of all field ids used in static static field reads. + * Set of all field ids used in static static field reads, along with access context. */ - public final SortedSet<DexField> staticFieldReads; + public final Map<DexField, Set<DexEncodedMethod>> staticFieldReads; /** - * Set of all field ids used in static field writes. + * Set of all field ids used in static field writes, along with access context. */ - public final SortedSet<DexField> staticFieldWrites; + public final Map<DexField, Set<DexEncodedMethod>> staticFieldWrites; /** * Set of all methods referenced in virtual invokes; */ @@ -1515,8 +1521,10 @@ this.instanceFieldWrites = enqueuer.collectInstanceFieldsWritten(); this.staticFieldReads = enqueuer.collectStaticFieldsRead(); this.staticFieldWrites = enqueuer.collectStaticFieldsWritten(); - this.fieldsRead = enqueuer.collectFieldsRead(); - this.fieldsWritten = enqueuer.collectFieldsWritten(); + this.fieldsRead = enqueuer.mergeFieldAccesses( + instanceFieldReads.keySet(), staticFieldReads.keySet()); + this.fieldsWritten = enqueuer.mergeFieldAccesses( + instanceFieldWrites.keySet(), staticFieldWrites.keySet()); this.pinnedItems = rewritePinnedItemsToDescriptors(enqueuer.pinnedItems); this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes); this.interfaceInvokes = joinInvokedMethods(enqueuer.interfaceInvokes); @@ -1532,8 +1540,8 @@ this.prunedTypes = Collections.emptySet(); this.switchMaps = Collections.emptyMap(); this.ordinalsMaps = Collections.emptyMap(); - assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0; - assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0; + assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty(); + assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty(); } private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application, @@ -1566,8 +1574,8 @@ this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses); this.switchMaps = previous.switchMaps; this.ordinalsMaps = previous.ordinalsMaps; - assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0; - assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0; + assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty(); + assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty(); } private AppInfoWithLiveness(AppInfoWithLiveness previous, @@ -1579,10 +1587,14 @@ this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod); this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod); this.liveFields = rewriteItems(previous.liveFields, lense::lookupField); - this.instanceFieldReads = rewriteItems(previous.instanceFieldReads, lense::lookupField); - this.instanceFieldWrites = rewriteItems(previous.instanceFieldWrites, lense::lookupField); - this.staticFieldReads = rewriteItems(previous.staticFieldReads, lense::lookupField); - this.staticFieldWrites = rewriteItems(previous.staticFieldWrites, lense::lookupField); + this.instanceFieldReads = + rewriteKeysWhileMergingValues(previous.instanceFieldReads, lense::lookupField); + this.instanceFieldWrites = + rewriteKeysWhileMergingValues(previous.instanceFieldWrites, lense::lookupField); + this.staticFieldReads = + rewriteKeysWhileMergingValues(previous.staticFieldReads, lense::lookupField); + this.staticFieldWrites = + rewriteKeysWhileMergingValues(previous.staticFieldWrites, lense::lookupField); this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField); this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField); this.pinnedItems = rewriteMixedItems(previous.pinnedItems, lense); @@ -1608,8 +1620,8 @@ this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType); this.protoLiteFields = previous.protoLiteFields; // Sanity check sets after rewriting. - assert Sets.intersection(instanceFieldReads, staticFieldReads).isEmpty(); - assert Sets.intersection(instanceFieldWrites, staticFieldWrites).isEmpty(); + assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty(); + assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty(); } public AppInfoWithLiveness(AppInfoWithLiveness previous, @@ -1744,6 +1756,18 @@ return builder.build(); } + private static <T extends PresortedComparable<T>, S> Map<T, Set<S>> + rewriteKeysWhileMergingValues( + Map<T, Set<S>> original, BiFunction<T, DexEncodedMethod, T> rewrite) { + Map<T, Set<S>> result = new IdentityHashMap<>(); + for (T item : original.keySet()) { + T rewrittenKey = rewrite.apply(item, null); + result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet()) + .addAll(original.get(item)); + } + return Collections.unmodifiableMap(result); + } + private static ImmutableSet<DexItem> rewriteMixedItems( Set<DexItem> original, GraphLense lense) { ImmutableSet.Builder<DexItem> builder = ImmutableSet.builder(); @@ -2095,6 +2119,10 @@ return target; } + public DexEncodedMethod getContext() { + return context; + } + @Override public int hashCode() { return target.hashCode() * 31 + context.hashCode();
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java index 4d7240e..923398d 100644 --- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java +++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -550,24 +550,6 @@ // Tests where the output of R8 fails when run with Art. private static final Multimap<String, TestCondition> failingRunWithArt = new ImmutableListMultimap.Builder<String, TestCondition>() - // This test relies on specific field access patterns, which we rewrite. - .put("064-field-access", - TestCondition.match( - TestCondition.R8DEX_NOT_AFTER_D8_COMPILER, - TestCondition.runtimesUpTo(DexVm.Version.V4_4_4))) - .put("064-field-access", - TestCondition.match( - TestCondition.R8DEX_COMPILER, - TestCondition.runtimes( - DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, - DexVm.Version.V5_1_1))) - .put("064-field-access", - TestCondition.match( - TestCondition.tools(DexTool.NONE), - TestCondition.D8_AFTER_R8CF_COMPILER, - TestCondition.runtimes( - DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, - DexVm.Version.V5_1_1))) // The growth limit test fails after processing by R8 because R8 will eliminate an // "unneeded" const store. The following reflective call to the VM's GC will then see the // large array as still live and the subsequent allocations will fail to reach the desired
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java index 9fa694d..767420b 100644 --- a/src/test/java/com/android/tools/r8/regress/B76025099.java +++ b/src/test/java/com/android/tools/r8/regress/B76025099.java
@@ -3,7 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.regress; +import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import com.android.tools.r8.OutputMode; import com.android.tools.r8.R8Command; @@ -11,50 +13,113 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.VmTestRunner; -import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.code.InvokeDirect; +import com.android.tools.r8.code.IputObject; +import com.android.tools.r8.code.ReturnVoid; +import com.android.tools.r8.graph.DexCode; +import com.android.tools.r8.graph.DexField; import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DexInspector; +import com.android.tools.r8.utils.DexInspector.ClassSubject; +import com.android.tools.r8.utils.DexInspector.MethodSubject; import com.android.tools.r8.utils.FileUtils; import com.google.common.collect.ImmutableList; +import java.io.File; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import org.junit.Ignore; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import regress_76025099.Main; +import regress_76025099.impl.Impl; @RunWith(VmTestRunner.class) public class B76025099 extends TestBase { - private static final String PRG = ToolHelper.EXAMPLES_BUILD_DIR + "regress_76025099" + FileUtils.JAR_EXTENSION; - private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception { + private AndroidApp runR8(AndroidApp app) throws Exception { R8Command command = ToolHelper.addProguardConfigurationConsumer( ToolHelper.prepareR8CommandBuilder(app), pgConfig -> { pgConfig.setPrintMapping(true); - pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE)); + pgConfig.setPrintMappingFile(map); }) - .addProguardConfiguration( - ImmutableList.of(keepMainProguardConfiguration(main)), - Origin.unknown()) - .setOutput(out, OutputMode.DexIndexed) + .addProguardConfigurationFiles(pgConfig) + .setOutput(tempRoot.toPath(), OutputMode.DexIndexed) .build(); return ToolHelper.runR8(command, o -> { o.enableMinification = false; }); } - @Ignore("b/76025099") + private File tempRoot; + private Path jarPath; + private AndroidApp originalApp; + private String mainName; + private Path pgConfig; + private Path map; + + @Before + public void setUp() throws Exception { + tempRoot = temp.getRoot(); + jarPath = Paths.get(PRG); + originalApp = readJar(jarPath); + mainName = Main.class.getCanonicalName(); + pgConfig = File.createTempFile("keep-rules", ".config", tempRoot).toPath(); + String config = keepMainProguardConfiguration(Main.class); + config += System.lineSeparator() + "-dontobfuscate"; + Files.write(pgConfig, config.getBytes()); + map = File.createTempFile("proguard", ".map", tempRoot).toPath(); + } + @Test - public void test() throws Exception { - Path out = temp.getRoot().toPath(); - Path jarPath = Paths.get(PRG); - String mainName = Main.class.getCanonicalName(); + public void testProguardAndD8() throws Exception { + if (!isRunProguard()) { + return; + } + ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName); assertEquals(0, jvmOutput.exitCode); - AndroidApp processedApp = runR8(readJar(jarPath), Main.class, out); + + Path proguarded = + File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, tempRoot).toPath(); + ProcessResult proguardResult = ToolHelper.runProguardRaw(jarPath, proguarded, pgConfig, map); + assertEquals(0, proguardResult.exitCode); + + AndroidApp processedApp = ToolHelper.runD8(readJar(proguarded)); + verifyFieldAccess(processedApp, jvmOutput); + } + + @Test + public void testR8() throws Exception { + ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName); + assertEquals(0, jvmOutput.exitCode); + + AndroidApp processedApp = runR8(originalApp); + verifyFieldAccess(processedApp, jvmOutput); + } + + private void verifyFieldAccess(AndroidApp processedApp, ProcessResult jvmOutput) + throws Exception { + DexInspector inspector = new DexInspector(processedApp); + ClassSubject impl = inspector.clazz(Impl.class); + assertThat(impl, isPresent()); + MethodSubject init = impl.init(ImmutableList.of("java.lang.String")); + assertThat(init, isPresent()); + DexCode dexCode = init.getMethod().getCode().asDexCode(); + checkInstructions(dexCode, ImmutableList.of( + InvokeDirect.class, + IputObject.class, + ReturnVoid.class + )); + IputObject iput = (IputObject) dexCode.instructions[1]; + DexField fld = iput.getField(); + assertEquals("name", fld.name.toString()); + assertEquals("regress_76025099.impl.Impl", fld.getHolder().toSourceString()); + ProcessResult artOutput = runOnArtRaw(processedApp, mainName); assertEquals(0, artOutput.exitCode); assertEquals(jvmOutput.stdout, artOutput.stdout);
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java index b13a08e..6642352 100644 --- a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java +++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.resolution; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import com.android.tools.r8.ClassFileConsumer; import com.android.tools.r8.CompilationMode; @@ -46,12 +45,11 @@ assertEquals(0, runInput.exitCode); Path outDex = temp.getRoot().toPath().resolve("dex.zip"); build(new DexIndexedConsumer.ArchiveConsumer(outDex)); - // TODO(b/76191597): Change to runArtNoVerificationErrors + assertEquals when bug is fixed - ProcessResult runDex = ToolHelper.runArtRaw( + ProcessResult runDex = ToolHelper.runArtNoVerificationErrorsRaw( outDex.toString(), CLASS.getCanonicalName()); - assertNotEquals(runInput.stdout, runDex.stdout); - assertNotEquals(runInput.exitCode, runDex.exitCode); - assertNotEquals( + assertEquals(runInput.stdout, runDex.stdout); + assertEquals(runInput.exitCode, runDex.exitCode); + assertEquals( -1, runDex.stderr.indexOf("java.lang.NoSuchFieldError")); }