Merge commit '4f920edd9ded4a9c3436423ef3427ad7a5a40d0e' into dev-release Change-Id: I2996101098642d5f04cc809ff797ac45fcc0e6fb
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index c994367..74c5484 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -439,10 +439,8 @@ /** Add proguard rules for automatic main-dex-list calculation. */ public Builder addMainDexRules(List<String> lines, Origin origin) { - guard( - () -> - mainDexRules.add( - new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin))); + String config = String.join(System.lineSeparator(), lines); + mainDexRules.add(new ProguardConfigurationSourceStrings(config, Paths.get("."), origin)); return self(); }
diff --git a/src/main/java/com/android/tools/r8/ExtractKeepAnnoRules.java b/src/main/java/com/android/tools/r8/ExtractKeepAnnoRules.java index 90ffd66..a8c18f5 100644 --- a/src/main/java/com/android/tools/r8/ExtractKeepAnnoRules.java +++ b/src/main/java/com/android/tools/r8/ExtractKeepAnnoRules.java
@@ -11,13 +11,11 @@ import com.android.tools.r8.keepanno.ast.KeepDeclaration; import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor; import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions; -import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; -import java.util.Collections; import java.util.List; /** Experimental API to extract keep rules from keep annotations. */ @@ -50,14 +48,7 @@ List<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(resource.getBytes()); if (!declarations.isEmpty()) { KeepRuleExtractor extractor = - new KeepRuleExtractor( - rule -> { - ProguardConfigurationSourceStrings source = - new ProguardConfigurationSourceStrings( - Collections.singletonList(rule), null, resource.getOrigin()); - consumer.accept(source.get(), reporter); - }, - extractorOptions); + new KeepRuleExtractor(rule -> consumer.accept(rule, reporter), extractorOptions); declarations.forEach(extractor::extract); } }
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java index 4e1e118..ce770ed 100644 --- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java +++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -82,8 +82,8 @@ * Add proguard configuration for automatic main dex list calculation. */ public GenerateMainDexListCommand.Builder addMainDexRules(List<String> lines, Origin origin) { - guard(() -> mainDexRules.add( - new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin))); + String config = String.join(System.lineSeparator(), lines); + mainDexRules.add(new ProguardConfigurationSourceStrings(config, Paths.get("."), origin)); return self(); }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index fa8a50c..9f1f42d 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -74,7 +74,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -247,10 +246,8 @@ /** Add proguard rules for automatic main-dex-list calculation. */ public Builder addMainDexRules(List<String> lines, Origin origin) { - guard( - () -> - mainDexRules.add( - new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin))); + String config = String.join(System.lineSeparator(), lines); + mainDexRules.add(new ProguardConfigurationSourceStrings(config, Paths.get("."), origin)); return self(); } @@ -320,8 +317,8 @@ /** Add proguard configuration. */ public Builder addProguardConfiguration(List<String> lines, Origin origin) { - guard(() -> proguardConfigs.add( - new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin))); + String config = String.join(System.lineSeparator(), lines); + proguardConfigs.add(new ProguardConfigurationSourceStrings(config, Paths.get("."), origin)); return self(); } @@ -1051,7 +1048,7 @@ rule -> { ProguardConfigurationSourceStrings source = new ProguardConfigurationSourceStrings( - Collections.singletonList(rule), null, resource.getOrigin()); + rule, null, resource.getOrigin()); parser.parse(source); }); declarations.forEach(extractor::extract);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java index 6d4a086..73aeeee 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -67,7 +67,7 @@ public class ApplicationReader { private final InternalOptions options; - private final DexItemFactory itemFactory; + private final DexItemFactory factory; private final Timing timing; private final AndroidApp inputApp; @@ -79,7 +79,7 @@ public ApplicationReader(AndroidApp inputApp, InternalOptions options, Timing timing) { this.options = options; - itemFactory = options.itemFactory; + factory = options.itemFactory; this.timing = timing; this.inputApp = inputApp; } @@ -127,7 +127,7 @@ timing.begin("DexApplication.read"); LazyLoadedDexApplication.Builder builder = DexApplication.builder(options, timing); - TaskCollection<?> tasks = new TaskCollection<>(options, executorService); + TaskCollection<?> tasks = createReaderTaskCollection(executorService); try { // Still preload some of the classes, primarily for two reasons: // (a) class lazy loading is not supported for DEX files @@ -140,7 +140,7 @@ ClassReader classReader = new ClassReader(tasks); classReader.initializeLazyClassCollection(builder); classReader.readSources(); - timing.time("Await read", () -> tasks.await()); + awaitReaderTaskCollection(tasks); flags = classReader.getDexApplicationReadFlags(); return builder .addDataResourceProviders(inputApp.getProgramResourceProviders()) @@ -178,7 +178,7 @@ timing.begin("DexApplication.readDirect"); DirectMappedDexApplication.Builder builder = DirectMappedDexApplication.directBuilder(options, timing); - TaskCollection<?> tasks = new TaskCollection<>(options, executorService); + TaskCollection<?> tasks = createReaderTaskCollection(executorService); try { readProguardMap(inputApp.getProguardMapInputData(), builder, tasks); ClassReader classReader = new ClassReader(tasks); @@ -187,7 +187,7 @@ allClassesBuilder::setClasspathClasses, allClassesBuilder::setLibraryClasses); allClassesBuilder.forceLoadNonProgramClassCollections(options, tasks, timing); classReader.readSources(); - timing.time("Await read", () -> tasks.await()); + awaitReaderTaskCollection(tasks); allClassesBuilder.setProgramClasses( ProgramClassCollection.resolveConflicts(classReader.programClasses, options)); AllClasses allClasses = allClassesBuilder.build(options, timing); @@ -209,6 +209,18 @@ } } + private TaskCollection<?> createReaderTaskCollection(ExecutorService executorService) { + // Commit all statically known types before starting to read (e.g., java.lang.Object). + factory.commitPendingItems(); + return new TaskCollection<>(options, executorService); + } + + private void awaitReaderTaskCollection(TaskCollection<?> tasks) throws ExecutionException { + timing.time("Await read", () -> tasks.await()); + // Commit all references corresponding to program definitions. + factory.commitPendingItems(); + } + public final void dump(DumpInputFlags dumpInputFlags) { assert verifyMainDexOptionsCompatible(inputApp, options); dumpApplication(dumpInputFlags); @@ -250,7 +262,7 @@ if (emitDeprecatedDiagnostics) { options.reporter.error(new UnsupportedMainDexListUsageDiagnostic(resource.getOrigin())); } - addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, itemFactory)); + addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, factory)); } if (!inputApp.getMainDexClasses().isEmpty()) { if (emitDeprecatedDiagnostics) { @@ -260,7 +272,7 @@ app, builder, inputApp.getMainDexClasses().stream() - .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz))) + .map(clazz -> factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz))) .collect(Collectors.toList())); } } @@ -435,7 +447,7 @@ return true; } DexAnnotation retentionAnnotation = - clazz.annotations().getFirstMatching(itemFactory.retentionType); + clazz.annotations().getFirstMatching(factory.retentionType); // Default is CLASS retention, read if retained. if (retentionAnnotation == null) { return DexAnnotation.retainCompileTimeAnnotation(clazz.getType(), application.options);
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index ce75e87..5b17a26 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -3360,28 +3360,6 @@ return markers; } - // Non-synchronized internal create. - private DexType internalCreateType(DexString descriptor) { - assert descriptor != null; - DexType result = types.get(descriptor); - if (result == null) { - if (descriptor.getFirstByteAsChar() != '[') { - result = new DexType(descriptor); - } else { - DexType elementType = createType(descriptor.toArrayElementDescriptor(this)); - result = new DexArrayType(descriptor, elementType); - } - assert result.isArrayType() - || result.isClassType() - || result.isPrimitiveType() - || result.isVoidType() - : descriptor.toString(); - assert !isInternalSentinel(result); - types.put(descriptor, result); - } - return result; - } - private DexType createStaticallyKnownType(String descriptor) { return createStaticallyKnownType(createString(descriptor)); } @@ -3394,7 +3372,7 @@ } private DexType createStaticallyKnownType(DexString descriptor) { - DexType type = internalCreateType(descriptor); + DexType type = createType(descriptor); // Conservatively add all statically known types to "compiler synthesized types set". addPossiblySynthesizedType(type); return type; @@ -3403,7 +3381,7 @@ // Safe synchronized external create. May be used for statically known types in synthetic code. // See the generated BackportedMethods.java for reference. public synchronized DexType createSynthesizedType(String descriptor) { - DexType type = internalCreateType(createString(descriptor)); + DexType type = createType(createString(descriptor)); addPossiblySynthesizedType(type); return type; } @@ -3431,9 +3409,21 @@ possibleCompilerSynthesizedTypes.forEach(fn); } - // Safe synchronized external create. Should never be used to create a statically known type! - public synchronized DexType createType(DexString descriptor) { - return internalCreateType(descriptor); + public DexType createType(DexString descriptor) { + assert descriptor != null; + DexType committed = committedTypes.get(descriptor); + if (committed != null) { + return committed; + } + if (descriptor.getFirstByteAsChar() != '[') { + return types.computeIfAbsent(descriptor, DexType::new); + } + DexType pending = types.get(descriptor); + if (pending != null) { + return pending; + } + DexType elementType = createType(descriptor.toArrayElementDescriptor(this)); + return types.computeIfAbsent(descriptor, d -> new DexArrayType(d, elementType)); } public DexType createType(String descriptor) { @@ -3686,7 +3676,7 @@ public void commitPendingItems() { commitPendingItems(committedMethodHandles, methodHandles); commitPendingItems(committedStrings, strings); - // commitPendingItems(committedTypes, types); + commitPendingItems(committedTypes, types); commitPendingItems(committedFields, fields); commitPendingItems(committedProtos, protos); commitPendingItems(committedMethods, methods);
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java index 5a42ddc..4cd24cc 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -11,7 +11,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.TypeElement; -public interface FieldGet { +public interface FieldGet extends InstructionOrValue { DexField getField();
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java index 91e6d2d..37b1cf5 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
@@ -10,9 +10,7 @@ import com.android.tools.r8.graph.FieldResolutionResult; import com.android.tools.r8.graph.ProgramMethod; -public interface FieldPut { - - BasicBlock getBlock(); +public interface FieldPut extends InstructionOrValue { DexField getField();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java index 1de0af6..003e17f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -374,12 +374,9 @@ } } - /** - * Returns the basic block containing this instruction. - */ + /** Returns the basic block containing this instruction. */ @Override - public BasicBlock getBlock() { - assert block != null; + public BasicBlock getBlockOrNull() { return block; } @@ -423,13 +420,6 @@ block.getInstructions().replace(this, newInstruction, affectedValues); } - /** - * Returns true if the instruction is in the IR and therefore has a block. - */ - public boolean hasBlock() { - return block != null; - } - public String getInstructionName() { return getClass().getSimpleName(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java b/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java index b45fd92..65c63f4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java
@@ -29,5 +29,14 @@ return null; } - BasicBlock getBlock(); + default boolean hasBlock() { + return getBlockOrNull() != null; + } + + default BasicBlock getBlock() { + assert hasBlock(); + return getBlockOrNull(); + } + + BasicBlock getBlockOrNull(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java index e49e987..83dd50b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Phi.java +++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -84,12 +84,7 @@ } @Override - public boolean hasBlock() { - return block != null; - } - - @Override - public BasicBlock getBlock() { + public BasicBlock getBlockOrNull() { return block; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java index fff0036..6546ce4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Value.java +++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -1087,13 +1087,9 @@ && !appView.enableWholeProgramOptimizations(); } - public boolean hasBlock() { - return definition.hasBlock(); - } - @Override - public BasicBlock getBlock() { - return definition.getBlock(); + public BasicBlock getBlockOrNull() { + return definition.getBlockOrNull(); } public TypeElement getType() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java index 54fa781..133faa0 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.ir.optimize.ListIterationRewriter; import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination; import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter; +import com.android.tools.r8.ir.optimize.ShareInstanceGetInstructions; import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer; import com.android.tools.r8.ir.optimize.string.StringBuilderAppendOptimizer; import com.android.tools.r8.utils.InternalOptions; @@ -63,6 +64,7 @@ passes.add(new SplitReturnRewriter(appView)); passes.add(new ReturnBlockCanonicalizerRewriter(appView)); } + passes.add(new ShareInstanceGetInstructions(appView)); return new CodeRewriterPassCollection(passes); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index e459b66..f34dd58 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -3,6 +3,17 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.optimize; +import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_MULTI_NEW_ARRAY; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_POLYMORPHIC; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER; +import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL; +import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_FILLED; +import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; @@ -25,13 +36,12 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.IterableUtils; +import com.android.tools.r8.utils.WorkList; import com.android.tools.r8.utils.timing.Timing; import com.google.common.collect.ImmutableList; -import java.util.ArrayDeque; import java.util.Collection; -import java.util.Deque; import java.util.Iterator; -import java.util.Queue; +import java.util.List; public class DeadCodeRemover { @@ -51,13 +61,15 @@ // We may encounter unneeded catch handlers after each iteration, e.g., if a dead instruction // is the only throwing instruction in a block. Removing unneeded catch handlers can lead to // more dead instructions. - Deque<BasicBlock> worklist = new ArrayDeque<>(); + List<BasicBlock> topologicallySortedBlocks = code.topologicallySortedBlocks(); + WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(); do { AffectedValues affectedValues = new AffectedValues(); ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code); - worklist.addAll(code.topologicallySortedBlocks()); + worklist.addIfNotSeen(topologicallySortedBlocks); while (!worklist.isEmpty()) { BasicBlock block = worklist.removeLast(); + worklist.removeSeen(block); removeDeadInstructions(worklist, code, block, affectedValues, valueIsDeadAnalysis); removeDeadAndTrivialPhis(worklist, block, valueIsDeadAnalysis); } @@ -95,30 +107,31 @@ } // Add the block from where the value originates to the worklist. - private static void updateWorklist(Queue<BasicBlock> worklist, Value value) { - BasicBlock block = null; - if (value.isPhi()) { - block = value.asPhi().getBlock(); - } else if (value.definition.hasBlock()) { - block = value.definition.getBlock(); - } + private static void updateWorklist(WorkList<BasicBlock> worklist, Value value) { + BasicBlock block = value.getBlockOrNull(); if (block != null) { - worklist.add(block); + worklist.addIfNotSeen(block); } } // Add all blocks from where the in/debug-values to the instruction originates. - private static void updateWorklist(Queue<BasicBlock> worklist, Instruction instruction) { + private static void updateWorklist(WorkList<BasicBlock> worklist, Instruction instruction) { for (Value inValue : instruction.inValues()) { - updateWorklist(worklist, inValue); + BasicBlock block = inValue.getBlockOrNull(); + if (block != null && block != instruction.getBlock()) { + updateWorklist(worklist, inValue); + } } for (Value debugValue : instruction.getDebugValues()) { - updateWorklist(worklist, debugValue); + BasicBlock block = debugValue.getBlockOrNull(); + if (block != null && block != instruction.getBlock()) { + updateWorklist(worklist, debugValue); + } } } private void removeDeadAndTrivialPhis( - Queue<BasicBlock> worklist, BasicBlock block, ValueIsDeadAnalysis valueIsDeadAnalysis) { + WorkList<BasicBlock> worklist, BasicBlock block, ValueIsDeadAnalysis valueIsDeadAnalysis) { Iterator<Phi> phiIt = block.getPhis().iterator(); while (phiIt.hasNext()) { Phi phi = phiIt.next(); @@ -137,7 +150,7 @@ @SuppressWarnings("ReferenceEquality") private void removeDeadInstructions( - Queue<BasicBlock> worklist, + WorkList<BasicBlock> worklist, IRCode code, BasicBlock block, AffectedValues affectedValues, @@ -146,36 +159,55 @@ while (iterator.hasPrevious()) { Instruction current = iterator.previous(); if (current.hasOutValue()) { - // Replace unnecessary cast values. - if (current.isCheckCast()) { - CheckCast checkCast = current.asCheckCast(); - if (!checkCast.isRefiningStaticType(appView.options()) - && checkCast.outValue().getLocalInfo() == checkCast.object().getLocalInfo()) { - updateWorklistWithNonDebugUses(worklist, checkCast); - checkCast.outValue().replaceUsers(checkCast.object(), affectedValues); - checkCast.object().uniquePhiUsers().forEach(Phi::removeTrivialPhi); - } - } - // Remove unused invoke results. - if (current.isInvoke() && !current.outValue().isUsed()) { - current.setOutValue(null); - } - if (current.isStaticGet() && !current.outValue().isUsed() && appView.hasLiveness()) { - Box<InitClass> initClass = new Box<>(); - if (iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible( - appView.withLiveness(), - code, - current.asStaticGet().getField().getHolderType(), - initClass::set)) { - if (initClass.isSet()) { - // Apply dead code remover to the new init-class instruction. - current = iterator.previous(); - assert current == initClass.get(); - } else { - // Instruction removed. - continue; + switch (current.opcode()) { + case CHECK_CAST: + { + // Replace unnecessary cast values. + CheckCast checkCast = current.asCheckCast(); + if (!checkCast.isRefiningStaticType(appView.options()) + && checkCast.outValue().getLocalInfo() == checkCast.object().getLocalInfo()) { + updateWorklistWithNonDebugUses(worklist, checkCast); + checkCast.outValue().replaceUsers(checkCast.object(), affectedValues); + checkCast.object().uniquePhiUsers().forEach(Phi::removeTrivialPhi); } + break; } + case INVOKE_CUSTOM: + case INVOKE_DIRECT: + case INVOKE_INTERFACE: + case INVOKE_MULTI_NEW_ARRAY: + case INVOKE_STATIC: + case INVOKE_SUPER: + case INVOKE_POLYMORPHIC: + case INVOKE_VIRTUAL: + case NEW_ARRAY_FILLED: + // Remove unused invoke results. + if (!current.outValue().isUsed()) { + current.clearOutValue(); + } + break; + case STATIC_GET: + if (!current.outValue().isUsed() && appView.hasLiveness()) { + Box<InitClass> initClass = new Box<>(); + if (iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible( + appView.withLiveness(), + code, + current.asStaticGet().getField().getHolderType(), + initClass::set)) { + if (initClass.isSet()) { + // Apply dead code remover to the new init-class instruction. + current = iterator.previous(); + assert current == initClass.get(); + } else { + // Instruction removed. + continue; + } + } + } + break; + default: + assert !current.isInvoke(); + break; } } DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code); @@ -209,12 +241,12 @@ } private static void updateWorklistWithNonDebugUses( - Queue<BasicBlock> worklist, CheckCast checkCast) { + WorkList<BasicBlock> worklist, CheckCast checkCast) { for (Instruction user : checkCast.outValue().uniqueUsers()) { - worklist.add(user.getBlock()); + worklist.addIfNotSeen(user.getBlock()); } for (Phi user : checkCast.outValue().uniquePhiUsers()) { - worklist.add(user.getBlock()); + worklist.addIfNotSeen(user.getBlock()); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ShareInstanceGetInstructions.java b/src/main/java/com/android/tools/r8/ir/optimize/ShareInstanceGetInstructions.java new file mode 100644 index 0000000..0fe2c54 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/ShareInstanceGetInstructions.java
@@ -0,0 +1,193 @@ +// Copyright (c) 2025, 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.ir.optimize; + +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.InstanceGet; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; +import com.google.common.collect.Sets; +import java.util.List; +import java.util.Set; + +public class ShareInstanceGetInstructions extends CodeRewriterPass<AppInfo> { + + public ShareInstanceGetInstructions(AppView<?> appView) { + super(appView); + } + + @Override + protected String getRewriterId() { + return "ShareInstanceGetInstructions"; + } + + @Override + protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) { + return code.metadata().mayHaveInstanceGet(); + } + + @Override + protected CodeRewriterResult rewriteCode(IRCode code) { + boolean changed = false; + for (BasicBlock block : code.getBlocks()) { + // Try to hoist identical InstanceGets in two successors to this block: + List<BasicBlock> successors = block.getSuccessors(); + // TODO(b/448586591: We should also be able to handle catch handlers by splitting the block we + // hoist into. + if (successors.size() == 2 && !block.hasCatchHandlers()) { + InstanceGet firstInstanceGet = findFirstInstanceGetInstruction(code, successors.get(0)); + InstanceGet secondInstanceGet = findFirstInstanceGetInstruction(code, successors.get(1)); + if (firstInstanceGet == null || secondInstanceGet == null) { + continue; + } + Value firstReceiver = firstInstanceGet.object(); + Value firstReceiverRoot = firstReceiver.getAliasedValue(); + + Value secondReceiver = secondInstanceGet.object(); + DexField field = firstInstanceGet.getField(); + if (firstReceiverRoot != secondReceiver.getAliasedValue() + || firstReceiver.isMaybeNull() + || secondReceiver.isMaybeNull() + || field.isNotIdenticalTo(secondInstanceGet.getField())) { + continue; + } + Value firstOutValue = firstInstanceGet.outValue(); + Value secondOutValue = secondInstanceGet.outValue(); + if (firstOutValue.hasLocalInfo() || secondOutValue.hasLocalInfo()) { + continue; + } + Value outValue = code.createValue(firstOutValue.getType()); + Value newReceiver = + firstReceiver.getBlock() == firstInstanceGet.getBlock() + ? firstReceiverRoot + : firstReceiver; + InstanceGet hoistedInstanceGet = new InstanceGet(outValue, newReceiver, field); + hoistedInstanceGet.setPosition(firstInstanceGet.getPosition()); + block.getInstructions().addBefore(hoistedInstanceGet, block.getLastInstruction()); + removeOldInstructions(outValue, firstInstanceGet, secondInstanceGet); + changed = true; + } + // Try to sink shareable InstanceGets from two predecessors into this block: + List<BasicBlock> predecessors = block.getPredecessors(); + if (predecessors.size() == 2 && !block.hasCatchHandlers()) { + BasicBlock firstPredecessor = predecessors.get(0); + BasicBlock secondPredecessor = predecessors.get(1); + // TODO(b/448586591: We should also be able to handle catch handlers by splitting the block + // we hoist into. + if (firstPredecessor.hasCatchHandlers() || secondPredecessor.hasCatchHandlers()) { + continue; + } + InstanceGet firstInstanceGet = getLastInstanceGet(firstPredecessor); + InstanceGet secondInstanceGet = getLastInstanceGet(secondPredecessor); + if (firstInstanceGet == null || secondInstanceGet == null) { + continue; + } + Value firstOutValue = firstInstanceGet.outValue(); + Value secondOutValue = secondInstanceGet.outValue(); + if (firstOutValue.hasLocalInfo() + || secondOutValue.hasLocalInfo() + || hasPhisThatWillBecomeInvalid(block, firstOutValue, secondOutValue)) { + continue; + } + Value firstReceiver = firstInstanceGet.object(); + Value secondReceiver = secondInstanceGet.object(); + if (firstReceiver.isMaybeNull() || secondReceiver.isMaybeNull()) { + continue; + } + DexField field = firstInstanceGet.getField(); + if (field.isNotIdenticalTo(secondInstanceGet.getField())) { + continue; + } + Value receiver; + if (firstReceiver == secondReceiver) { + receiver = firstReceiver; + } else { + Value firstReceiverRoot = firstReceiver.getAliasedValue(); + if (firstReceiverRoot == secondReceiver.getAliasedValue()) { + receiver = firstReceiverRoot; + } else { + TypeElement type = firstReceiver.getType().join(secondReceiver.getType(), appView); + Phi phi = code.createPhi(block, type); + phi.appendOperand(firstReceiver); + phi.appendOperand(secondReceiver); + receiver = phi; + } + } + Value outValue = code.createValue(firstOutValue.getType()); + Instruction hoistedInstanceGet = new InstanceGet(outValue, receiver, field); + hoistedInstanceGet.setPosition(firstInstanceGet.getPosition()); + block.getInstructions().addFirst(hoistedInstanceGet); + removeOldInstructions(outValue, firstInstanceGet, secondInstanceGet); + changed = true; + } + } + if (changed) { + code.removeRedundantBlocks(); + } + return CodeRewriterResult.hasChanged(changed); + } + + private static void removeOldInstructions( + Value outValue, InstanceGet firstInstanceGet, InstanceGet secondInstanceGet) { + firstInstanceGet.outValue().replaceUsers(outValue); + secondInstanceGet.outValue().replaceUsers(outValue); + outValue.uniquePhiUsers().forEach(Phi::removeTrivialPhi); + firstInstanceGet.removeOrReplaceByDebugLocalRead(); + secondInstanceGet.removeOrReplaceByDebugLocalRead(); + } + + private boolean hasPhisThatWillBecomeInvalid( + BasicBlock block, Value firstOutValue, Value secondOutValue) { + for (Phi phi : block.getPhis()) { + if (phi.getOperands().contains(firstOutValue)) { + if (phi.getOperands().size() != 2 || !phi.getOperands().contains(secondOutValue)) { + return true; + } + } else if (phi.getOperands().contains(secondOutValue)) { + if (phi.getOperands().size() != 2 || !phi.getOperands().contains(firstOutValue)) { + return true; + } + } + } + return false; + } + + private InstanceGet getLastInstanceGet(BasicBlock block) { + Set<Value> seenValues = Sets.newIdentityHashSet(); + for (Instruction instruction = block.getLastInstruction(); + instruction != null; + instruction = instruction.getPrev()) { + if (instruction.isInstanceGet() && !seenValues.contains(instruction.outValue())) { + return instruction.asInstanceGet(); + } else { + seenValues.addAll(instruction.inValues()); + } + } + return null; + } + + private InstanceGet findFirstInstanceGetInstruction(IRCode code, BasicBlock block) { + for (Instruction instruction = block.entry(); + instruction != null; + instruction = instruction.getNext()) { + if (instruction.instructionMayHaveSideEffects(appView, code.context())) { + break; + } + if (instruction.isInstanceGet()) { + return instruction.asInstanceGet(); + } + } + return null; + } +}
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ConsumerRuleDiagnostic.java b/src/main/java/com/android/tools/r8/processkeeprules/ConsumerRuleDiagnostic.java new file mode 100644 index 0000000..1a119a4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/processkeeprules/ConsumerRuleDiagnostic.java
@@ -0,0 +1,31 @@ +// Copyright (c) 2025, 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.processkeeprules; + +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.keepanno.annotations.KeepForApi; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; + +@KeepForApi +public abstract class ConsumerRuleDiagnostic implements Diagnostic { + + private final Origin origin; + private final Position position; + + public ConsumerRuleDiagnostic(Origin origin, Position position) { + this.origin = origin; + this.position = position; + } + + @Override + public Origin getOrigin() { + return origin; + } + + @Override + public Position getPosition() { + return position; + } +}
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java new file mode 100644 index 0000000..fa4d3ab --- /dev/null +++ b/src/main/java/com/android/tools/r8/processkeeprules/FilteredKeepRulesBuilder.java
@@ -0,0 +1,227 @@ +// Copyright (c) 2025, 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.processkeeprules; + +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; +import com.android.tools.r8.position.TextPosition; +import com.android.tools.r8.shaking.FilteredClassPath; +import com.android.tools.r8.shaking.ProguardClassNameList; +import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser; +import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer; +import com.android.tools.r8.shaking.ProguardConfigurationRule; +import com.android.tools.r8.shaking.ProguardPathList; +import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode; +import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Path; +import java.util.List; + +// TODO(b/437139566): This is not yet feature complete. Add support for writing all directives. +// TODO(b/437139566): Implement filtering by commenting out rules. +public class FilteredKeepRulesBuilder implements ProguardConfigurationParserConsumer { + + private final StringConsumer consumer; + private final Reporter reporter; + + private boolean isInComment; + + FilteredKeepRulesBuilder(StringConsumer consumer, Reporter reporter) { + this.consumer = consumer; + this.reporter = reporter; + } + + private void appendToCurrentLine(String string) { + assert string.indexOf('\n') < 0; + consumer.accept(string, reporter); + if (!isInComment && string.indexOf('#') >= 0) { + isInComment = true; + } + } + + private void ensureComment() { + if (!isInComment) { + appendToCurrentLine("#"); + isInComment = true; + } + } + + private void ensureNewlineAfterComment() { + if (isInComment) { + exitComment(); + consumer.accept(StringUtils.UNIX_LINE_SEPARATOR, reporter); + } + } + + private void exitComment() { + isInComment = false; + } + + private void write(ProguardConfigurationSourceParser parser, TextPosition positionStart) { + write(parser.getContentSince(positionStart)); + } + + private void write(String string) { + int lastNewlineIndex = string.lastIndexOf('\n'); + if (lastNewlineIndex < 0) { + appendToCurrentLine(string); + } else { + // Write the lines leading up to the last line. + String untilNewlineInclusive = string.substring(0, lastNewlineIndex + 1); + consumer.accept(untilNewlineInclusive, reporter); + // Due to the newline character we are no longer inside a comment. + exitComment(); + // Emit everything after the newline character. + String fromNewlineExclusive = string.substring(lastNewlineIndex + 1); + appendToCurrentLine(fromNewlineExclusive); + } + } + + @Override + public void addKeepAttributePatterns( + List<String> attributesPatterns, + Origin origin, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart) { + ensureNewlineAfterComment(); + write(parser, positionStart); + } + + @Override + public void addRule(ProguardConfigurationRule rule) { + ensureNewlineAfterComment(); + write(rule.getSource()); + } + + @Override + public void addWhitespace(ProguardConfigurationSourceParser parser, TextPosition positionStart) { + write(parser, positionStart); + } + + @Override + public void disableObfuscation(Origin origin, Position position) { + ensureComment(); + write("-dontobfuscate"); + } + + @Override + public void disableOptimization(Origin origin, Position position) { + ensureComment(); + write("-dontoptimize"); + } + + @Override + public void disableShrinking(Origin origin, Position position) { + ensureComment(); + write("-dontshrink"); + } + + @Override + public void setRenameSourceFileAttribute(String s, Origin origin, Position position) {} + + @Override + public void addKeepPackageNamesPattern(ProguardClassNameList proguardClassNameList) {} + + @Override + public void setKeepParameterNames(boolean b, Origin origin, Position position) {} + + @Override + public void enableKeepDirectories() {} + + @Override + public void addKeepDirectories(ProguardPathList proguardPathList) {} + + @Override + public void addParsedConfiguration(String s) {} + + @Override + public void setPrintUsage(boolean b) {} + + @Override + public void setPrintUsageFile(Path path) {} + + @Override + public void enableProtoShrinking() {} + + @Override + public void setIgnoreWarnings(boolean b) {} + + @Override + public void addDontWarnPattern(ProguardClassNameList pattern) {} + + @Override + public void addDontNotePattern(ProguardClassNameList pattern) {} + + @Override + public void enableAllowAccessModification(Origin origin, Position position) {} + + @Override + public void enablePrintConfiguration(Origin origin, Position position) {} + + @Override + public void setPrintConfigurationFile(Path path) {} + + @Override + public void enablePrintMapping(Origin origin, Position position) {} + + @Override + public void setPrintMappingFile(Path path) {} + + @Override + public void setApplyMappingFile(Path path, Origin origin, Position position) {} + + @Override + public void addInjars( + List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) {} + + @Override + public void addLibraryJars( + List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) {} + + @Override + public void setPrintSeeds(boolean b, Origin origin, Position position) {} + + @Override + public void setSeedFile(Path path) {} + + @Override + public void setObfuscationDictionary(Path path, Origin origin, Position position) {} + + @Override + public void setClassObfuscationDictionary(Path path, Origin origin, Position position) {} + + @Override + public void setPackageObfuscationDictionary(Path path, Origin origin, Position position) {} + + @Override + public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {} + + @Override + public void addAdaptResourceFileContents(ProguardPathList pattern) {} + + @Override + public void addAdaptResourceFilenames(ProguardPathList pattern) {} + + @Override + public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {} + + @Override + public PackageObfuscationMode getPackageObfuscationMode() { + return null; + } + + @Override + public void setPackagePrefix(String s) {} + + @Override + public void setFlattenPackagePrefix(String s) {} + + @Override + public void enableRepackageClasses(Origin origin, Position position) {} + + @Override + public void enableFlattenPackageHierarchy(Origin origin, Position position) {} +}
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java b/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java index 3921207..bc1a21d 100644 --- a/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java +++ b/src/main/java/com/android/tools/r8/processkeeprules/GlobalLibraryConsumerRuleDiagnostic.java
@@ -3,34 +3,20 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.processkeeprules; -import com.android.tools.r8.Diagnostic; import com.android.tools.r8.keepanno.annotations.KeepForApi; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; @KeepForApi -public class GlobalLibraryConsumerRuleDiagnostic implements Diagnostic { - private final Origin origin; - private final Position position; +public class GlobalLibraryConsumerRuleDiagnostic extends ConsumerRuleDiagnostic { private final String rule; public GlobalLibraryConsumerRuleDiagnostic(Origin origin, Position position, String rule) { - this.origin = origin; - this.position = position; + super(origin, position); this.rule = rule; } @Override - public Origin getOrigin() { - return origin; - } - - @Override - public Position getPosition() { - return position; - } - - @Override public String getDiagnosticMessage() { return rule + " not allowed in library consumer rules."; }
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java b/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java index c7c1c1a..c1f3d5a 100644 --- a/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java +++ b/src/main/java/com/android/tools/r8/processkeeprules/KeepAttributeLibraryConsumerRuleDiagnostic.java
@@ -3,36 +3,22 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.processkeeprules; -import com.android.tools.r8.Diagnostic; import com.android.tools.r8.keepanno.annotations.KeepForApi; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; @KeepForApi -public class KeepAttributeLibraryConsumerRuleDiagnostic implements Diagnostic { +public class KeepAttributeLibraryConsumerRuleDiagnostic extends ConsumerRuleDiagnostic { - private final Origin origin; - private final Position position; private final String attribute; public KeepAttributeLibraryConsumerRuleDiagnostic( Origin origin, Position position, String attribute) { - this.origin = origin; - this.position = position; + super(origin, position); this.attribute = attribute; } @Override - public Origin getOrigin() { - return origin; - } - - @Override - public Position getPosition() { - return position; - } - - @Override public String getDiagnosticMessage() { return "Illegal attempt to keep the attribute '" + attribute + "' in library consumer rules."; }
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRules.java b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRules.java index 158894d..9f1cf1e 100644 --- a/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRules.java +++ b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRules.java
@@ -4,28 +4,62 @@ package com.android.tools.r8.processkeeprules; import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.StringConsumer; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.keepanno.annotations.KeepForApi; import com.android.tools.r8.shaking.ProguardConfigurationParser; import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer; +import com.android.tools.r8.shaking.ProguardConfigurationSource; import com.android.tools.r8.utils.ExceptionUtils; +import com.android.tools.r8.utils.Reporter; +import java.util.List; @KeepForApi public class ProcessKeepRules { + + private final DexItemFactory factory; + private final Reporter reporter; + + private final List<ProguardConfigurationSource> keepRules; + private final StringConsumer filteredKeepRulesConsumer; + private final boolean validateLibraryConsumerRules; + + private ProcessKeepRules(DexItemFactory factory, ProcessKeepRulesCommand command) { + this.factory = factory; + this.reporter = command.getReporter(); + this.keepRules = command.getKeepRules(); + this.filteredKeepRulesConsumer = command.getFilteredKeepRulesConsumer(); + this.validateLibraryConsumerRules = command.isValidateLibraryConsumerRules(); + } + public static void run(ProcessKeepRulesCommand command) throws CompilationFailedException { ExceptionUtils.withCompilationHandler( command.getReporter(), () -> { - // This is the only valid form of keep rule processing for now. - assert command.getValidateLibraryConsumerRules(); - DexItemFactory dexItemFactory = new DexItemFactory(); - ProguardConfigurationParserConsumer configurationConsumer = - new ValidateLibraryConsumerRulesKeepRuleProcessor(command.getReporter()); - ProguardConfigurationParser parser = - new ProguardConfigurationParser( - dexItemFactory, command.getReporter(), configurationConsumer); - parser.parse(command.getKeepRules()); - command.getReporter().failIfPendingErrors(); + DexItemFactory factory = new DexItemFactory(); + new ProcessKeepRules(factory, command).internalRun(); }); } + + private void internalRun() { + FilteredKeepRulesBuilder filteredKeepRulesBuilder = + filteredKeepRulesConsumer != null + ? new FilteredKeepRulesBuilder(filteredKeepRulesConsumer, reporter) + : null; + ProguardConfigurationParserConsumer parserConsumer = + validateLibraryConsumerRules + ? new ValidateLibraryConsumerRulesKeepRuleProcessor(reporter) + : filteredKeepRulesBuilder; + ProguardConfigurationParser parser = + new ProguardConfigurationParser(factory, reporter, parserConsumer); + parser.parse(keepRules); + supplyConsumers(); + reporter.failIfPendingErrors(); + } + + private void supplyConsumers() { + if (filteredKeepRulesConsumer != null) { + filteredKeepRulesConsumer.finished(reporter); + } + } }
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommand.java b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommand.java index 81f18f2..0c429f3 100644 --- a/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommand.java +++ b/src/main/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommand.java
@@ -4,27 +4,36 @@ package com.android.tools.r8.processkeeprules; import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; import com.android.tools.r8.keepanno.annotations.KeepForApi; +import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.ProguardConfigurationSource; import com.android.tools.r8.shaking.ProguardConfigurationSourceFile; +import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings; import com.android.tools.r8.utils.Reporter; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @KeepForApi public class ProcessKeepRulesCommand { + private final Reporter reporter; private final List<ProguardConfigurationSource> keepRules; + private final StringConsumer filteredKeepRulesConsumer; private final boolean validateLibraryConsumerRules; private ProcessKeepRulesCommand( Reporter reporter, List<ProguardConfigurationSource> keepRules, + StringConsumer filteredKeepRulesConsumer, boolean validateLibraryConsumerRules) { this.reporter = reporter; this.keepRules = keepRules; + this.filteredKeepRulesConsumer = filteredKeepRulesConsumer; this.validateLibraryConsumerRules = validateLibraryConsumerRules; } @@ -49,10 +58,14 @@ return reporter; } - boolean getValidateLibraryConsumerRules() { + boolean isValidateLibraryConsumerRules() { return validateLibraryConsumerRules; } + StringConsumer getFilteredKeepRulesConsumer() { + return filteredKeepRulesConsumer; + } + List<ProguardConfigurationSource> getKeepRules() { return keepRules; } @@ -61,7 +74,9 @@ public static class Builder { private final Reporter reporter; - private List<ProguardConfigurationSource> keepRuleFiles = new ArrayList<>(); + private final List<ProguardConfigurationSource> keepRules = new ArrayList<>(); + + private StringConsumer filteredKeepRulesConsumer = null; private boolean validateLibraryConsumerRules = false; // TODO(b/447161121) introduce a DefaultDiagnosticHandler instead. @@ -73,14 +88,28 @@ this.reporter = new Reporter(diagnosticsHandler); } + public Builder addKeepRules(String data, Origin origin) { + keepRules.add(new ProguardConfigurationSourceStrings(data, Paths.get("."), origin)); + return this; + } + /** Add proguard configuration-file resources. */ public Builder addKeepRuleFiles(Collection<Path> paths) { for (Path path : paths) { - keepRuleFiles.add(new ProguardConfigurationSourceFile(path)); + keepRules.add(new ProguardConfigurationSourceFile(path)); } return this; } + public Builder addKeepRuleFiles(Path... paths) { + return addKeepRuleFiles(Arrays.asList(paths)); + } + + public Builder setFilteredKeepRulesConsumer(StringConsumer filteredKeepRulesConsumer) { + this.filteredKeepRulesConsumer = filteredKeepRulesConsumer; + return this; + } + public Builder setLibraryConsumerRuleValidation(boolean enable) { validateLibraryConsumerRules = enable; return this; @@ -88,15 +117,20 @@ public ProcessKeepRulesCommand build() { validate(); - return new ProcessKeepRulesCommand(reporter, keepRuleFiles, validateLibraryConsumerRules); + return new ProcessKeepRulesCommand( + reporter, keepRules, filteredKeepRulesConsumer, validateLibraryConsumerRules); } private void validate() { - if (keepRuleFiles.isEmpty()) { - reporter.error("No keep rule files provided."); + if (keepRules.isEmpty()) { + reporter.error("No keep rules provided."); } - if (!validateLibraryConsumerRules) { - reporter.error("No rule validation enabled."); + if (filteredKeepRulesConsumer == null) { + if (!validateLibraryConsumerRules) { + reporter.error("Filtering or validation not enabled."); + } + } else if (validateLibraryConsumerRules) { + reporter.error("Filtering and validation not supported simultaneously."); } reporter.failIfPendingErrors(); }
diff --git a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java index 371e1cb..c2ced3e 100644 --- a/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java +++ b/src/main/java/com/android/tools/r8/processkeeprules/ValidateLibraryConsumerRulesKeepRuleProcessor.java
@@ -5,8 +5,10 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; +import com.android.tools.r8.position.TextPosition; import com.android.tools.r8.shaking.FilteredClassPath; import com.android.tools.r8.shaking.ProguardClassNameList; +import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser; import com.android.tools.r8.shaking.ProguardConfigurationParserConsumer; import com.android.tools.r8.shaking.ProguardConfigurationRule; import com.android.tools.r8.shaking.ProguardKeepAttributes; @@ -75,7 +77,11 @@ @Override public void addKeepAttributePatterns( - List<String> attributesPatterns, Origin origin, Position position) { + List<String> attributesPatterns, + Origin origin, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart) { // TODO(b/270289387): Add support for more attributes. ProguardKeepAttributes keepAttributes = ProguardKeepAttributes.fromPatterns(attributesPatterns); if (keepAttributes.lineNumberTable) { @@ -128,40 +134,60 @@ public void addDontNotePattern(ProguardClassNameList pattern) {} @Override - public void setPrintConfiguration(boolean b) {} + public void enablePrintConfiguration(Origin origin, Position position) { + handleGlobalRule(origin, position, "-printconfiguration"); + } @Override public void setPrintConfigurationFile(Path path) {} @Override - public void setPrintMapping(boolean b) {} + public void enablePrintMapping(Origin origin, Position position) { + handleGlobalRule(origin, position, "-printmapping"); + } @Override public void setPrintMappingFile(Path path) {} @Override - public void setApplyMappingFile(Path path) {} + public void setApplyMappingFile(Path path, Origin origin, Position position) { + handleGlobalRule(origin, position, "-applymapping"); + } @Override - public void addInjars(List<FilteredClassPath> filteredClassPaths) {} + public void addInjars( + List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) { + handleGlobalRule(origin, position, "-injars"); + } @Override - public void addLibraryJars(List<FilteredClassPath> filteredClassPaths) {} + public void addLibraryJars( + List<FilteredClassPath> filteredClassPaths, Origin origin, Position position) { + handleGlobalRule(origin, position, "-libraryjars"); + } @Override - public void setPrintSeeds(boolean b) {} + public void setPrintSeeds(boolean b, Origin origin, Position position) { + handleGlobalRule(origin, position, "-printseeds"); + } @Override public void setSeedFile(Path path) {} @Override - public void setObfuscationDictionary(Path path) {} + public void setObfuscationDictionary(Path path, Origin origin, Position position) { + handleGlobalRule(origin, position, "-obfuscationdictionary"); + } @Override - public void setClassObfuscationDictionary(Path path) {} + public void setClassObfuscationDictionary(Path path, Origin origin, Position position) { + handleGlobalRule(origin, position, "-classobfuscationdictionary"); + } @Override - public void setPackageObfuscationDictionary(Path path) {} + public void setPackageObfuscationDictionary(Path path, Origin origin, Position position) { + handleGlobalRule(origin, position, "-packageobfuscationdictionary"); + } @Override public void addAdaptClassStringsPattern(ProguardClassNameList pattern) {}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java index 2d4e353..5b0c3f1 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -8,6 +8,8 @@ import com.android.tools.r8.naming.DictionaryReader; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; +import com.android.tools.r8.position.TextPosition; +import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser; import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringUtils; @@ -88,12 +90,13 @@ } @Override - public void addInjars(List<FilteredClassPath> injars) { + public void addInjars(List<FilteredClassPath> injars, Origin origin, Position position) { this.injars.addAll(injars); } @Override - public void addLibraryJars(List<FilteredClassPath> libraryJars) { + public void addLibraryJars( + List<FilteredClassPath> libraryJars, Origin origin, Position position) { this.libraryJars.addAll(libraryJars); } @@ -154,8 +157,8 @@ } @Override - public void setPrintConfiguration(boolean printConfiguration) { - this.printConfiguration = printConfiguration; + public void enablePrintConfiguration(Origin origin, Position position) { + this.printConfiguration = true; } @Override @@ -175,8 +178,8 @@ } @Override - public void setPrintMapping(boolean printMapping) { - this.printMapping = printMapping; + public void enablePrintMapping(Origin origin, Position position) { + this.printMapping = true; } @Override @@ -186,7 +189,7 @@ } @Override - public void setApplyMappingFile(Path file) { + public void setApplyMappingFile(Path file, Origin origin, Position position) { this.applyMappingFile = file; } @@ -202,7 +205,11 @@ @Override public void addKeepAttributePatterns( - List<String> keepAttributePatterns, Origin origin, Position position) { + List<String> keepAttributePatterns, + Origin origin, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart) { this.keepAttributePatterns.addAll(keepAttributePatterns); } @@ -279,22 +286,25 @@ } @Override - public void setPrintSeeds(boolean printSeeds) { + public void setPrintSeeds(boolean printSeeds, Origin origin, Position position) { this.printSeeds = printSeeds; } @Override - public void setObfuscationDictionary(Path obfuscationDictionary) { + public void setObfuscationDictionary( + Path obfuscationDictionary, Origin origin, Position position) { this.obfuscationDictionary = obfuscationDictionary; } @Override - public void setClassObfuscationDictionary(Path classObfuscationDictionary) { + public void setClassObfuscationDictionary( + Path classObfuscationDictionary, Origin origin, Position position) { this.classObfuscationDictionary = classObfuscationDictionary; } @Override - public void setPackageObfuscationDictionary(Path packageObfuscationDictionary) { + public void setPackageObfuscationDictionary( + Path packageObfuscationDictionary, Origin origin, Position position) { this.packageObfuscationDictionary = packageObfuscationDictionary; }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java index 0f505fd..3c01885 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -222,7 +222,7 @@ ANY } - private class ProguardConfigurationSourceParser { + public class ProguardConfigurationSourceParser { private final String name; private final String contents; private int position = 0; @@ -240,9 +240,16 @@ this.origin = source.getOrigin(); } + public String getContentSince(TextPosition start) { + return contents.substring(start.getOffsetAsInt(), position); + } + public void parse() throws ProguardRuleParserException { do { - skipWhitespace(); + TextPosition whitespaceStart = getPosition(); + if (skipWhitespace()) { + configurationConsumer.addWhitespace(this, whitespaceStart); + } } while (parseOption()); // This may be unknown, but we want to always ensure that we don't attribute lines to the // wrong configuration. @@ -287,11 +294,15 @@ configurationConsumer.addKeepAttributePatterns( Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS), origin, - getPosition(optionStart)); + this, + getPosition(optionStart), + optionStart); configurationConsumer.addKeepAttributePatterns( Collections.singletonList(RUNTIME_INVISIBLE_ANNOTATIONS), origin, - getPosition(optionStart)); + this, + getPosition(optionStart), + optionStart); } else if (acceptString("renamesourcefileattribute")) { skipWhitespace(); String renameSourceFileAttribute = @@ -299,7 +310,7 @@ configurationConsumer.setRenameSourceFileAttribute( renameSourceFileAttribute, origin, getPosition(optionStart)); } else if (acceptString("keepattributes")) { - parseKeepAttributes(getPosition(optionStart)); + parseKeepAttributes(optionStart); } else if (acceptString("keeppackagenames")) { parseClassFilter(configurationConsumer::addKeepPackageNamesPattern); } else if (acceptString("keepparameternames")) { @@ -391,20 +402,22 @@ } else if (acceptString("allowaccessmodification")) { configurationConsumer.enableAllowAccessModification(origin, getPosition(optionStart)); } else if (acceptString("printconfiguration")) { - configurationConsumer.setPrintConfiguration(true); + configurationConsumer.enablePrintConfiguration(origin, getPosition(optionStart)); skipWhitespace(); if (isOptionalArgumentGiven()) { configurationConsumer.setPrintConfigurationFile(parseFileName(false)); } } else if (acceptString("printmapping")) { - configurationConsumer.setPrintMapping(true); + configurationConsumer.enablePrintMapping(origin, getPosition(optionStart)); skipWhitespace(); if (isOptionalArgumentGiven()) { configurationConsumer.setPrintMappingFile(parseFileName(false)); } } else if (acceptString("applymapping")) { configurationConsumer.setApplyMappingFile( - parseFileInputDependency(inputDependencyConsumer::acceptProguardApplyMapping)); + parseFileInputDependency(inputDependencyConsumer::acceptProguardApplyMapping), + origin, + getPosition(optionStart)); } else if (acceptString("assumenosideeffects")) { ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(optionStart); configurationConsumer.addRule(rule); @@ -423,27 +436,37 @@ baseDirectory = parseFileName(false); } else if (acceptString("injars")) { configurationConsumer.addInjars( - parseClassPath(inputDependencyConsumer::acceptProguardInJars)); + parseClassPath(inputDependencyConsumer::acceptProguardInJars), + origin, + getPosition(optionStart)); } else if (acceptString("libraryjars")) { configurationConsumer.addLibraryJars( - parseClassPath(inputDependencyConsumer::acceptProguardLibraryJars)); + parseClassPath(inputDependencyConsumer::acceptProguardLibraryJars), + origin, + getPosition(optionStart)); } else if (acceptString("printseeds")) { - configurationConsumer.setPrintSeeds(true); + configurationConsumer.setPrintSeeds(true, origin, getPosition(optionStart)); skipWhitespace(); if (isOptionalArgumentGiven()) { configurationConsumer.setSeedFile(parseFileName(false)); } } else if (acceptString("obfuscationdictionary")) { configurationConsumer.setObfuscationDictionary( - parseFileInputDependency(inputDependencyConsumer::acceptProguardObfuscationDictionary)); + parseFileInputDependency(inputDependencyConsumer::acceptProguardObfuscationDictionary), + origin, + getPosition(optionStart)); } else if (acceptString("classobfuscationdictionary")) { configurationConsumer.setClassObfuscationDictionary( parseFileInputDependency( - inputDependencyConsumer::acceptProguardClassObfuscationDictionary)); + inputDependencyConsumer::acceptProguardClassObfuscationDictionary), + origin, + getPosition(optionStart)); } else if (acceptString("packageobfuscationdictionary")) { configurationConsumer.setPackageObfuscationDictionary( parseFileInputDependency( - inputDependencyConsumer::acceptProguardPackageObfuscationDictionary)); + inputDependencyConsumer::acceptProguardPackageObfuscationDictionary), + origin, + getPosition(optionStart)); } else if (acceptString("alwaysinline")) { InlineRule rule = parseRuleWithClassSpec( @@ -735,7 +758,7 @@ return true; } - private void parseKeepAttributes(Position position) throws ProguardRuleParserException { + private void parseKeepAttributes(TextPosition start) throws ProguardRuleParserException { List<String> attributesPatterns = acceptKeepAttributesPatternList(); if (attributesPatterns.isEmpty()) { throw parseError("Expected attribute pattern list"); @@ -753,7 +776,8 @@ + ")")); } } - configurationConsumer.addKeepAttributePatterns(attributesPatterns, origin, position); + configurationConsumer.addKeepAttributePatterns( + attributesPatterns, origin, this, getPosition(start), start); } private boolean skipFlag(String name) { @@ -1686,27 +1710,31 @@ return builder.build(); } - private void skipWhitespace() { + private boolean skipWhitespace() { + boolean skipped = false; while (!eof() && StringUtils.isWhitespace(peekChar())) { if (peekChar() == '\n') { line++; lineStartPosition = position + 1; } position++; + skipped = true; } - skipComment(); + if (skipComment()) { + skipped = true; + } + return skipped; } - private void skipComment() { - if (eof()) { - return; + private boolean skipComment() { + if (eof() || peekChar() != '#') { + return false; } - if (peekChar() == '#') { - while (!eof() && peekChar() != '\n') { - position++; - } - skipWhitespace(); + while (!eof() && peekChar() != '\n') { + position++; } + skipWhitespace(); + return true; } private boolean eof() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java index f0fbdd6..ea658c5 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserConsumer.java
@@ -5,6 +5,8 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; +import com.android.tools.r8.position.TextPosition; +import com.android.tools.r8.shaking.ProguardConfigurationParser.ProguardConfigurationSourceParser; import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode; import java.nio.file.Path; import java.util.List; @@ -15,7 +17,12 @@ void addRule(ProguardConfigurationRule rule); - void addKeepAttributePatterns(List<String> attributesPatterns, Origin origin, Position position); + void addKeepAttributePatterns( + List<String> attributesPatterns, + Origin origin, + ProguardConfigurationSourceParser parser, + Position position, + TextPosition positionStart); void setRenameSourceFileAttribute(String s, Origin origin, Position position); @@ -47,29 +54,29 @@ void enableAllowAccessModification(Origin origin, Position position); - void setPrintConfiguration(boolean b); + void enablePrintConfiguration(Origin origin, Position position); void setPrintConfigurationFile(Path path); - void setPrintMapping(boolean b); + void enablePrintMapping(Origin origin, Position position); void setPrintMappingFile(Path path); - void setApplyMappingFile(Path path); + void setApplyMappingFile(Path path, Origin origin, Position position); - void addInjars(List<FilteredClassPath> filteredClassPaths); + void addInjars(List<FilteredClassPath> filteredClassPaths, Origin origin, Position position); - void addLibraryJars(List<FilteredClassPath> filteredClassPaths); + void addLibraryJars(List<FilteredClassPath> filteredClassPaths, Origin origin, Position position); - void setPrintSeeds(boolean b); + void setPrintSeeds(boolean b, Origin origin, Position position); void setSeedFile(Path path); - void setObfuscationDictionary(Path path); + void setObfuscationDictionary(Path path, Origin origin, Position position); - void setClassObfuscationDictionary(Path path); + void setClassObfuscationDictionary(Path path, Origin origin, Position position); - void setPackageObfuscationDictionary(Path path); + void setPackageObfuscationDictionary(Path path, Origin origin, Position position); void addAdaptClassStringsPattern(ProguardClassNameList pattern); @@ -88,4 +95,6 @@ void enableRepackageClasses(Origin origin, Position position); void enableFlattenPackageHierarchy(Origin origin, Position position); + + default void addWhitespace(ProguardConfigurationSourceParser parser, TextPosition position) {} }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java index cc9fafa..b826231 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
@@ -8,33 +8,27 @@ import com.google.common.annotations.VisibleForTesting; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; public class ProguardConfigurationSourceStrings implements ProguardConfigurationSource { private final Path basePath; - private final List<String> config; + private final String config; private final Origin origin; /** - * Creates {@link ProguardConfigurationSource} with raw {@param config}, along with - * {@param basePath}, which allows all other options that use a relative path to reach out - * to desired paths appropriately. + * Creates {@link ProguardConfigurationSource} with raw {@param config}, along with {@param + * basePath}, which allows all other options that use a relative path to reach out to desired + * paths appropriately. */ - public ProguardConfigurationSourceStrings(List<String> config, Path basePath, Origin origin) { + public ProguardConfigurationSourceStrings(String config, Path basePath, Origin origin) { this.basePath = basePath; this.config = config; this.origin = origin; } - private ProguardConfigurationSourceStrings(List<String> config) { - this(config, Paths.get(""), Origin.unknown()); - } - @VisibleForTesting - public static ProguardConfigurationSourceStrings createConfigurationForTesting( - List<String> config) { - return new ProguardConfigurationSourceStrings(config); + public static ProguardConfigurationSourceStrings createConfigurationForTesting(String config) { + return new ProguardConfigurationSourceStrings(config, Paths.get(""), Origin.unknown()); } @Override
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java index 9d0ef51..ade3e8b 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -24,11 +24,13 @@ import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.timing.Timing; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ExecutorService; import java.util.function.Consumer; @KeepForApi @@ -38,6 +40,11 @@ runForTesting(command, command.getInternalOptions()); } + public static void run(TraceReferencesCommand command, ExecutorService executorService) + throws CompilationFailedException { + runForTesting(command, command.getInternalOptions(), executorService); + } + private static void forEachDescriptor(ProgramResourceProvider provider, Consumer<String> consumer) throws ResourceException, IOException { for (ProgramResource programResource : provider.getProgramResources()) { @@ -63,11 +70,18 @@ static void runForTesting(TraceReferencesCommand command, InternalOptions options) throws CompilationFailedException { - ExceptionUtils.withCompilationHandler( - command.getReporter(), () -> runInternal(command, options)); + runForTesting(command, options, ThreadUtils.getExecutorService(options)); } - static void runInternal(TraceReferencesCommand command, InternalOptions options) + static void runForTesting( + TraceReferencesCommand command, InternalOptions options, ExecutorService executorService) + throws CompilationFailedException { + ExceptionUtils.withCompilationHandler( + command.getReporter(), () -> runInternal(command, options, executorService)); + } + + static void runInternal( + TraceReferencesCommand command, InternalOptions options, ExecutorService executorService) throws IOException, ResourceException { AndroidApp.Builder builder = AndroidApp.builder(); command.getLibrary().forEach(builder::addLibraryResourceProvider); @@ -84,7 +98,7 @@ AppView.createForTracer( AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy( new ApplicationReader(builder.build(), options, Timing.empty()) - .readDirectSingleThreaded(), + .readDirect(executorService), ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(), MainDexInfo.none(), GlobalSyntheticsStrategy.forSingleOutputMode()));
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java index 429b912..462e396 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -39,6 +39,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -214,15 +215,20 @@ public ProgramResource getProgramResource(String descriptor) { return descriptor.equals(this.descriptor) ? programResource : null; } + + @Override + public void getProgramResources(Consumer<ProgramResource> consumer) { + consumer.accept(programResource); + } } - private ClassFileResourceProvider singleClassFileClassFileResourceProvider(Path file) + private static ClassFileResourceProvider singleClassFileClassFileResourceProvider(Path file) throws IOException { return new SingleClassClassFileResourceProvider( new PathOrigin(file), Files.readAllBytes(file)); } - private ProgramResourceProvider singleClassFileProgramResourceProvider(Path file) + private static ProgramResourceProvider singleClassFileProgramResourceProvider(Path file) throws IOException { byte[] bytes = Files.readAllBytes(file); String descriptor = extractClassDescriptor(bytes); @@ -230,9 +236,33 @@ @Override public Collection<ProgramResource> getProgramResources() { - return ImmutableList.of( - ProgramResource.fromBytes( - new PathOrigin(file), Kind.CF, bytes, ImmutableSet.of(descriptor))); + return Collections.singletonList(createProgramResource()); + } + + @Override + public void getProgramResources(Consumer<ProgramResource> consumer) { + consumer.accept(createProgramResource()); + } + + private ProgramResource createProgramResource() { + return ProgramResource.fromBytes( + new PathOrigin(file), Kind.CF, bytes, ImmutableSet.of(descriptor)); + } + }; + } + + private static ProgramResourceProvider singleDexFileProgramResourceProvider(Path file) { + ProgramResource programResource = ProgramResource.fromFile(Kind.DEX, file); + return new ProgramResourceProvider() { + + @Override + public Collection<ProgramResource> getProgramResources() { + return Collections.singletonList(programResource); + } + + @Override + public void getProgramResources(Consumer<ProgramResource> consumer) { + consumer.accept(programResource); } }; } @@ -277,15 +307,7 @@ error(new ExceptionDiagnostic(e)); } } else if (isDexFile(file)) { - traceSourceBuilder.add( - new ProgramResourceProvider() { - ProgramResource dexResource = ProgramResource.fromFile(Kind.DEX, file); - - @Override - public Collection<ProgramResource> getProgramResources() { - return Collections.singletonList(dexResource); - } - }); + traceSourceBuilder.add(singleDexFileProgramResourceProvider(file)); } else { error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file))); }
diff --git a/src/main/java/com/android/tools/r8/utils/SourceFileTemplateProvider.java b/src/main/java/com/android/tools/r8/utils/SourceFileTemplateProvider.java index b1c3cda..6216687 100644 --- a/src/main/java/com/android/tools/r8/utils/SourceFileTemplateProvider.java +++ b/src/main/java/com/android/tools/r8/utils/SourceFileTemplateProvider.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.SourceFileProvider; import com.google.common.collect.ImmutableMap; import java.util.Map; +import java.util.Map.Entry; public class SourceFileTemplateProvider implements SourceFileProvider { @@ -53,7 +54,7 @@ } private final String template; - private String cachedValue = null; + private volatile String cachedValue = null; private SourceFileTemplateProvider(String template) { this.template = template; @@ -62,11 +63,15 @@ @Override public String get(SourceFileEnvironment environment) { if (cachedValue == null) { - cachedValue = template; - HANDLERS.forEach( - (variable, getter) -> { - cachedValue = cachedValue.replace(variable, getter.get(environment)); - }); + synchronized (this) { + if (cachedValue == null) { + String temp = template; + for (Entry<String, SourceFileProvider> entry : HANDLERS.entrySet()) { + temp = temp.replace(entry.getKey(), entry.getValue().get(environment)); + } + cachedValue = temp; + } + } } return cachedValue; }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java index aac9d19..c9a17a0 100644 --- a/src/main/java/com/android/tools/r8/utils/WorkList.java +++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -167,6 +167,11 @@ return next; } + public void removeSeen(T element) { + boolean removed = seen.remove(element); + assert removed; + } + public void clearSeen() { seen.clear(); }
diff --git a/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java b/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java index 3047ca7..b10f50b 100644 --- a/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java +++ b/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java
@@ -4,8 +4,12 @@ package com.android.tools.r8.bootstrap; +import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR; +import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -16,19 +20,19 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.utils.BooleanBox; +import com.android.tools.r8.utils.CfUtils; import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.StreamUtils; import com.android.tools.r8.utils.ZipUtils; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.Enumeration; import java.util.Set; import java.util.function.Predicate; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -53,11 +57,74 @@ parameters.assertNoneRuntime(); } - private void checkJarContent(Path jar, boolean allowDirectories, Predicate<String> entryTester) + private void checkJarContent( + Path jar, boolean allowDirectories, ClassNameMapper mapping, Predicate<String> entryTester) throws Exception { - ZipFile zipFile; + BooleanBox licenseSeen = new BooleanBox(); + Set<String> apiDatabaseFiles = Sets.newHashSet("resources/new_api_database.ser"); + Set<String> r8AssistantRuntime = + ImmutableSet.of( + "ReflectiveEventType.java", + "ReflectiveOperationJsonLogger.java", + "ReflectiveOracle.java", + "EmptyReflectiveOperationReceiver.java", + "ReflectiveOperationReceiver.java", + "ReflectiveOperationLogger.java"); try { - zipFile = new ZipFile(jar.toFile(), StandardCharsets.UTF_8); + ZipUtils.iter( + jar, + (entry, input) -> { + String name = entry.getName(); + if (ZipUtils.isClassFile(name) || FileUtils.isKotlinBuiltinsFile(name)) { + assertThat(name, startsWith("com/android/tools/r8/")); + if (ZipUtils.isClassFile(name)) { + byte[] classFileBytes = StreamUtils.streamToByteArrayClose(input); + String sourceFile = extractSourceFile(classFileBytes); + if (!r8AssistantRuntime.contains(sourceFile)) { + if (mapping != null) { + assertNotNull(sourceFile); + assertTrue(sourceFile, sourceFile.startsWith("R8_")); + assertEquals(sourceFile.contains("+excldeps") ? 117 : 108, sourceFile.length()); + } else { + // Some class files from third party libraries does not have a SourceFile + // attribute. + if (sourceFile != null) { + assertTrue( + sourceFile, sourceFile.endsWith(".java") || sourceFile.endsWith(".kt")); + } + } + } else { + String className = CfUtils.extractClassName(classFileBytes); + int lastPackageSeparator = className.lastIndexOf(JAVA_PACKAGE_SEPARATOR); + String expectedSourceFile = className.substring(lastPackageSeparator + 1); + int firstInnerclassSeparator = expectedSourceFile.indexOf('$'); + if (firstInnerclassSeparator >= 0) { + expectedSourceFile = expectedSourceFile.substring(0, firstInnerclassSeparator); + } + expectedSourceFile += JAVA_EXTENSION; + assertEquals(expectedSourceFile, sourceFile); + } + } + } else if (name.equals("META-INF/MANIFEST.MF")) { + // Allow. + } else if (name.equals("LICENSE")) { + licenseSeen.set(); + } else if (name.equals(THREADING_MODULE_SERVICE_FILE)) { + // Allow. + } else if (entryTester.test(name)) { + // Allow. + } else if (apiDatabaseFiles.contains(name)) { + // Allow all api database files. + apiDatabaseFiles.remove(name); + } else if (name.endsWith("/")) { + assertTrue("Unexpected directory entry in" + jar, allowDirectories); + } else { + fail("Unexpected entry '" + name + "' in " + jar); + } + }); + assertTrue(apiDatabaseFiles.isEmpty()); + assertTrue( + "No LICENSE entry found in " + jar, licenseSeen.isAssigned() && licenseSeen.isTrue()); } catch (IOException e) { if (!Files.exists(jar)) { throw new NoSuchFileException(jar.toString()); @@ -65,33 +132,6 @@ throw e; } } - boolean licenseSeen = false; - final Enumeration<? extends ZipEntry> entries = zipFile.entries(); - Set<String> apiDatabaseFiles = Sets.newHashSet("resources/new_api_database.ser"); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String name = entry.getName(); - if (ZipUtils.isClassFile(name) || FileUtils.isKotlinBuiltinsFile(name)) { - assertThat(name, startsWith("com/android/tools/r8/")); - } else if (name.equals("META-INF/MANIFEST.MF")) { - // Allow. - } else if (name.equals("LICENSE")) { - licenseSeen = true; - } else if (name.equals(THREADING_MODULE_SERVICE_FILE)) { - // Allow. - } else if (entryTester.test(name)) { - // Allow. - } else if (apiDatabaseFiles.contains(name)) { - // Allow all api database files. - apiDatabaseFiles.remove(name); - } else if (name.endsWith("/")) { - assertTrue("Unexpected directory entry in" + jar, allowDirectories); - } else { - fail("Unexpected entry '" + name + "' in " + jar); - } - } - assertTrue(apiDatabaseFiles.isEmpty()); - assertTrue("No LICENSE entry found in " + jar, licenseSeen); } private void checkLibJarContent(Path jar, Path map) throws Exception { @@ -100,14 +140,14 @@ } assertTrue(Files.exists(map)); ClassNameMapper mapping = ClassNameMapper.mapperFromFile(map); - checkJarContent(jar, false, name -> metadataExtensionTester(name, mapping)); + checkJarContent(jar, false, mapping, name -> metadataExtensionTester(name, mapping)); } private void checkJarContent(Path jar) throws Exception { if (!Files.exists(jar)) { return; } - checkJarContent(jar, true, name -> metadataExtensionTester(name, null)); + checkJarContent(jar, true, null, name -> metadataExtensionTester(name, null)); } private boolean metadataExtensionTester(String name, ClassNameMapper mapping) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InvokeInterfaceNoDevirtualizationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InvokeInterfaceNoDevirtualizationEnumUnboxingTest.java new file mode 100644 index 0000000..0d04b20 --- /dev/null +++ b/src/test/java/com/android/tools/r8/enumunboxing/InvokeInterfaceNoDevirtualizationEnumUnboxingTest.java
@@ -0,0 +1,83 @@ +// Copyright (c) 2025, 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.enumunboxing; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.codeinspector.AssertUtils; +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 InvokeInterfaceNoDevirtualizationEnumUnboxingTest extends EnumUnboxingTestBase { + + private final TestParameters parameters; + private final boolean enumValueOptimization; + private final EnumKeepRules enumKeepRules; + + @Parameters(name = "{0} valueOpt: {1} keep: {2}") + public static List<Object[]> data() { + return enumUnboxingTestParameters(); + } + + public InvokeInterfaceNoDevirtualizationEnumUnboxingTest( + TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) { + this.parameters = parameters; + this.enumValueOptimization = enumValueOptimization; + this.enumKeepRules = enumKeepRules; + } + + @Test + public void test() throws Exception { + AssertUtils.assertFailsCompilation( + () -> + testForR8(parameters) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addKeepRules(enumKeepRules.getKeepRules()) + // Disable devirtualization so that the enum unboxing rewriter sees the call to + // I#greet instead of MyEnum#greet. + .addOptionsModification(options -> options.enableDevirtualization = false) + .addOptionsModification( + options -> enableEnumOptions(options, enumValueOptimization)) + .enableInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("Hello, world!")); + } + + static class Main { + + public static void main(String[] args) { + MyEnum e = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B; + greet(e); + } + + static void greet(I i) { + i.greet(); + } + } + + @NoVerticalClassMerging + interface I { + + void greet(); + } + + enum MyEnum implements I { + A, + B; + + @NeverInline + @Override + public void greet() { + System.out.println("Hello, world!"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java index 0990e64..e290f65 100644 --- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java +++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -120,7 +120,7 @@ ToolHelper.addProguardConfigurationConsumer( builder, pgConfig -> { - pgConfig.setPrintSeeds(false); + pgConfig.setPrintSeeds(false, null, null); pgConfig.setIgnoreWarnings(true); }); outputApp = new AndroidAppConsumers(builder);
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java index 69e016e..f63abf2 100644 --- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java +++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -28,7 +28,6 @@ import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.android.tools.r8.utils.timing.Timing; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -54,8 +53,7 @@ ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter, configurationBuilder); parser.parse( - createConfigurationForTesting( - ImmutableList.of("-keep class ** { void m1(); void m5(); }"))); + createConfigurationForTesting("-keep class ** { void m1(); void m5(); }")); return configurationBuilder.build(); }); this.options = appView.options();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/InstanceGetSharingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/InstanceGetSharingTest.java new file mode 100644 index 0000000..eb2e295 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/InstanceGetSharingTest.java
@@ -0,0 +1,117 @@ +// Copyright (c) 2025, 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.ir.optimize; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.NoMethodStaticizing; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ir.code.Instruction; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InstanceGetSharingTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, A.class, B.class, C.class) + .addDontObfuscate() + .addKeepMainRule(TestClass.class) + .enableInliningAnnotations() + .enableNeverClassInliningAnnotations() + .enableNoMethodStaticizingAnnotations() + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + assertEquals( + 1, + inspector + .clazz(A.class) + .uniqueMethodWithOriginalName("foo") + .buildIR() + .streamInstructions() + .filter(Instruction::isInstanceGet) + .count()); + assertEquals( + 1, + inspector + .clazz(C.class) + .uniqueMethodWithOriginalName("bar") + .buildIR() + .streamInstructions() + .filter(Instruction::isInstanceGet) + .count()); + }); + } + + static class TestClass { + public static void main(String[] args) { + System.out.println(new A().foo()); + System.out.println(new C().bar()); + } + } + + @NeverClassInline + static class A { + private B b = new B(); + + @NeverInline + public long foo() { + if (System.currentTimeMillis() > 0) { + return b.getNum() + 1; + } else { + return b.getNum() + 2; + } + } + } + + @NoHorizontalClassMerging + @NeverClassInline + static class C { + + @NeverInline + public long bar() { + long num; + if (System.currentTimeMillis() > 0) { + B b1 = new B(); + num = b1.num; + } else { + B b2 = new B(); + num = b2.num; + } + return num + 1; + } + } + + @NeverClassInline + static class B { + public long num = System.currentTimeMillis(); + + @NeverInline + @NoMethodStaticizing + long getNum() { + return System.currentTimeMillis(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/CanonicalizeInstanceGetBeforeInstancePutTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/CanonicalizeInstanceGetBeforeInstancePutTest.java index 93fa047..7ff0eeb 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/CanonicalizeInstanceGetBeforeInstancePutTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/CanonicalizeInstanceGetBeforeInstancePutTest.java
@@ -49,10 +49,8 @@ MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod(); assertThat(mainMethodSubject, isPresent()); - // We expect two instance-get instructions when compiling to CF since we only run - // canonicalization when compiling to DEX. assertEquals( - parameters.isCfRuntime() ? 2 : 1, + 1, mainMethodSubject.streamInstructions().filter(InstructionSubject::isInstanceGet).count()); }
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java index 489b670..42243c0 100644 --- a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java +++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -78,7 +78,7 @@ ToolHelper.addProguardConfigurationConsumer( R8Command.builder(), pgConfig -> { - pgConfig.setPrintMapping(true); + pgConfig.enablePrintMapping(null, null); if (!minify.isMinify()) { pgConfig.disableObfuscation(); }
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java index 56eae04..2f684c0 100644 --- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java +++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -48,7 +48,7 @@ ToolHelper.addProguardConfigurationConsumer( ToolHelper.prepareR8CommandBuilder(app), pgConfig -> { - pgConfig.setPrintMapping(true); + pgConfig.enablePrintMapping(null, null); pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE)); }) .addProguardConfiguration(
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java index 13ec056..1807513 100644 --- a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java +++ b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesCommandTest.java
@@ -37,6 +37,21 @@ .put("-dontobfuscate", "-dontobfuscate not allowed in library consumer rules.") .put("-dontshrink", "-dontshrink not allowed in library consumer rules.") .put("-repackageclasses", "-repackageclasses not allowed in library consumer rules.") + .put("-printconfiguration", "-printconfiguration not allowed in library consumer rules.") + .put("-printmapping", "-printmapping not allowed in library consumer rules.") + .put("-applymapping foo", "-applymapping not allowed in library consumer rules.") + .put("-injars foo", "-injars not allowed in library consumer rules.") + .put("-libraryjars foo", "-libraryjars not allowed in library consumer rules.") + .put("-printseeds", "-printseeds not allowed in library consumer rules.") + .put( + "-obfuscationdictionary foo", + "-obfuscationdictionary not allowed in library consumer rules.") + .put( + "-classobfuscationdictionary foo", + "-classobfuscationdictionary not allowed in library consumer rules.") + .put( + "-packageobfuscationdictionary foo", + "-packageobfuscationdictionary not allowed in library consumer rules.") .put( "-flattenpackagehierarchy", "-flattenpackagehierarchy not allowed in library consumer rules.") @@ -45,20 +60,22 @@ "-allowaccessmodification not allowed in library consumer rules.") .put( "-keepattributes LineNumberTable", - "Illegal attempt to keep LineNumberTable in library consumer rules.") + "Illegal attempt to keep the attribute 'LineNumberTable' in library consumer rules.") .put( "-keepattributes RuntimeInvisibleAnnotations", - "Illegal attempt to keep RuntimeInvisibleAnnotations in library consumer rules.") + "Illegal attempt to keep the attribute 'RuntimeInvisibleAnnotations' in library" + + " consumer rules.") .put( "-keepattributes RuntimeInvisibleTypeAnnotations", - "Illegal attempt to keep RuntimeInvisibleTypeAnnotations in library consumer rules.") + "Illegal attempt to keep the attribute 'RuntimeInvisibleTypeAnnotations' in library" + + " consumer rules.") .put( "-keepattributes RuntimeInvisibleParameterAnnotations", - "Illegal attempt to keep RuntimeInvisibleParameterAnnotations in library consumer" - + " rules.") + "Illegal attempt to keep the attribute 'RuntimeInvisibleParameterAnnotations' in" + + " library consumer rules.") .put( "-keepattributes SourceFile", - "Illegal attempt to keep SourceFile in library consumer rules.") + "Illegal attempt to keep the attribute 'SourceFile' in library consumer rules.") .put( "-renamesourcefileattribute", "-renamesourcefileattribute not allowed in library consumer rules.")
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesFilteringTest.java b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesFilteringTest.java new file mode 100644 index 0000000..16cb049 --- /dev/null +++ b/src/test/java/com/android/tools/r8/processkeeprules/ProcessKeepRulesFilteringTest.java
@@ -0,0 +1,84 @@ +// Copyright (c) 2025, 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.processkeeprules; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ProcessKeepRulesFilteringTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + @Test + public void test() throws Exception { + TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl(); + String keepRules = + StringUtils.lines( + " -dontobfuscate -dontoptimize -dontshrink -keep class com.example.MainActivity # keep", + "# Keep all attributes", + "-keepattributes *", + "# Keep all", + "-keep class **", + "# End"); + FilteredKeepRules filteredKeepRules = new FilteredKeepRules(); + ProcessKeepRulesCommand command = + ProcessKeepRulesCommand.builder(diagnostics) + .addKeepRules(keepRules, Origin.unknown()) + .setFilteredKeepRulesConsumer(filteredKeepRules) + .build(); + ProcessKeepRules.run(command); + assertEquals( + StringUtils.lines( + " #-dontobfuscate -dontoptimize -dontshrink ", + "-keep class com.example.MainActivity # keep", + "# Keep all attributes", + "-keepattributes *", + "# Keep all", + "-keep class **", + "# End"), + filteredKeepRules.get()); + diagnostics.assertNoMessages(); + } + + private static class FilteredKeepRules implements StringConsumer { + + private StringBuilder sb = new StringBuilder(); + private String result; + + @Override + public void accept(String string, DiagnosticsHandler handler) { + sb.append(string); + } + + @Override + public void finished(DiagnosticsHandler handler) { + result = sb.toString(); + sb = null; + } + + public String get() { + return result; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java index 907e566..f9f7c65 100644 --- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -46,7 +46,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -225,7 +224,7 @@ // Parse from strings. - List<String> lines = FileUtils.readAllLines(Paths.get(PROGUARD_SPEC_FILE)); + String lines = FileUtils.readTextFile(Paths.get(PROGUARD_SPEC_FILE)); parser.parse(createConfigurationForTesting(lines)); rules = builder.build().getRules(); assertEquals(24, rules.size()); @@ -261,7 +260,7 @@ " -NameWithDash- -field-;", " p-.-OtherNameWithDash- -method-(-p.-WithDash-, -package-.-ClassNameWithDash-[]); ", "}")); - parser.parse(createConfigurationForTesting(ImmutableList.of(nonJavaIdentifiers))); + parser.parse(createConfigurationForTesting(nonJavaIdentifiers)); verifyParserEndsCleanly(); List<ProguardConfigurationRule> rules = builder.build().getRules(); assertEquals(1, rules.size()); @@ -296,7 +295,7 @@ private void testDontXXX( String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) { String configuration = "-dont" + xxx + " !foobar,*bar"; - parser.parse(createConfigurationForTesting(ImmutableList.of(configuration))); + parser.parse(createConfigurationForTesting(configuration)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); Predicate<DexType> matcher = matcherFactory.apply(config); @@ -314,9 +313,9 @@ private void testDontXXXMultiple( String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) { DexItemFactory dexItemFactory = new DexItemFactory(); - List<String> configuration1 = ImmutableList.of("-dont" + xxx + " foo.**, bar.**"); - List<String> configuration2 = ImmutableList.of("-dont" + xxx + " foo.**", "-dontwarn bar.**"); - for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) { + String configuration1 = StringUtils.lines("-dont" + xxx + " foo.**, bar.**"); + String configuration2 = StringUtils.lines("-dont" + xxx + " foo.**", "-dontwarn bar.**"); + for (String configuration : ImmutableList.of(configuration1, configuration2)) { parser.parse(createConfigurationForTesting(configuration)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); @@ -337,7 +336,7 @@ String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) { DexItemFactory dexItemFactory = new DexItemFactory(); String dontwarnAll = "-dont" + xxx + " *"; - parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll))); + parser.parse(createConfigurationForTesting(dontwarnAll)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); Predicate<DexType> matcher = matcherFactory.apply(config); @@ -357,7 +356,7 @@ DexItemFactory dexItemFactory = new DexItemFactory(); String dontwarnAll = "-dont" + xxx; String otherOption = "-keep class *"; - parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll, otherOption))); + parser.parse(createConfigurationForTesting(StringUtils.lines(dontwarnAll, otherOption))); ProguardConfiguration config = builder.build(); Predicate<DexType> matcher = matcherFactory.apply(config); assertTrue(matcher.test(dexItemFactory.createType("Lboobaz;"))); @@ -612,7 +611,7 @@ @Test public void testAdaptClassStrings() { String adaptClassStrings = "-adaptclassstrings !foobar,*bar"; - parser.parse(createConfigurationForTesting(ImmutableList.of(adaptClassStrings))); + parser.parse(createConfigurationForTesting(adaptClassStrings)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertFalse( @@ -625,10 +624,10 @@ @Test public void testAdaptClassStringsMultiple() { - List<String> configuration1 = ImmutableList.of("-adaptclassstrings foo.**, bar.**"); - List<String> configuration2 = - ImmutableList.of("-adaptclassstrings foo.**", "-adaptclassstrings bar.**"); - for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) { + String configuration1 = "-adaptclassstrings foo.**, bar.**"; + String configuration2 = + StringUtils.lines("-adaptclassstrings foo.**", "-adaptclassstrings bar.**"); + for (String configuration : ImmutableList.of(configuration1, configuration2)) { parser.parse(createConfigurationForTesting(configuration)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); @@ -644,7 +643,7 @@ @Test public void testAdaptClassStringsAllExplicitly() { String adaptAll = "-adaptclassstrings *"; - parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll))); + parser.parse(createConfigurationForTesting(adaptAll)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertTrue( @@ -658,7 +657,7 @@ @Test public void testAdaptClassStringsAllImplicitly() { String adaptAll = "-adaptclassstrings"; - parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll))); + parser.parse(createConfigurationForTesting(adaptAll)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertTrue( @@ -683,7 +682,7 @@ "-identifiernamestring class * {\n" + " @my.annotations.IdentifierNameString *;\n" + "}"; - parser.parse(createConfigurationForTesting(ImmutableList.of(config1, config2, config3))); + parser.parse(createConfigurationForTesting(StringUtils.lines(config1, config2, config3))); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); List<ProguardConfigurationRule> identifierNameStrings = config.getRules(); @@ -736,7 +735,7 @@ null, builder); String rule = "-convertchecknotnull class C { ** m(**, ...); }"; - parser.parse(createConfigurationForTesting(ImmutableList.of(rule))); + parser.parse(createConfigurationForTesting(rule)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertEquals(1, config.getRules().size()); @@ -758,7 +757,7 @@ null, builder); String rule = "-convertchecknotnull class C { void m(**, ...); }"; - parser.parse(createConfigurationForTesting(ImmutableList.of(rule))); + parser.parse(createConfigurationForTesting(rule)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertEquals(1, config.getRules().size()); @@ -918,9 +917,7 @@ @Test public void parseInvalidFilePattern() { try { - parser.parse( - createConfigurationForTesting( - Collections.singletonList("-injars abc.jar(*.zip;*.class)"))); + parser.parse(createConfigurationForTesting("-injars abc.jar(*.zip;*.class)")); fail(); } catch (RuntimeException e) { assertEquals(1, handler.errors.size()); @@ -933,29 +930,28 @@ for (String after : whiteSpace) { reset(); parseAndVerifyParserEndsCleanly( - ImmutableList.of( - "-keep" - + before - + "," - + after - + "includedescriptorclasses" - + before - + "," - + after - + "allowaccessmodification" - + before - + "," - + after - + "allowshrinking" - + before - + "," - + after - + "allowobfuscation" - + before - + "," - + after - + "allowoptimization " - + "class A { *; }")); + "-keep" + + before + + "," + + after + + "includedescriptorclasses" + + before + + "," + + after + + "allowaccessmodification" + + before + + "," + + after + + "allowshrinking" + + before + + "," + + after + + "allowobfuscation" + + before + + "," + + after + + "allowoptimization " + + "class A { *; }"); } } } @@ -964,18 +960,16 @@ public void parseKeepAnnotation() { for (String space : whiteSpace) { reset(); - parseAndVerifyParserEndsCleanly(ImmutableList.of( - "-keep @" + space + "interface A" - )); + parseAndVerifyParserEndsCleanly("-keep @" + space + "interface A"); } } @Test public void regress78442725() { - parseAndVerifyParserEndsCleanly(ImmutableList.of( - "-keep, includedescriptorclasses class in.uncod.android.bypass.Document { *; }", - "-keep, includedescriptorclasses class in.uncod.android.bypass.Element { *; }" - )); + parseAndVerifyParserEndsCleanly( + StringUtils.lines( + "-keep, includedescriptorclasses class in.uncod.android.bypass.Document { *; }", + "-keep, includedescriptorclasses class in.uncod.android.bypass.Element { *; }")); } @Test @@ -1086,7 +1080,7 @@ private void testUnsupportedOption(String option) { try { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(option))); + parser.parse(createConfigurationForTesting(option)); fail("Expect to fail due to unsupported option."); } catch (RuntimeException e) { checkDiagnostics(handler.errors, null, 1, 1, "Unsupported option", option); @@ -1101,7 +1095,7 @@ @Test public void parse_printconfiguration_noArguments() { - parser.parse(createConfigurationForTesting(ImmutableList.of("-printconfiguration"))); + parser.parse(createConfigurationForTesting("-printconfiguration")); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertTrue(config.isPrintConfiguration()); @@ -1110,7 +1104,7 @@ @Test public void parse_printconfiguration_argument() { - parser.parse(createConfigurationForTesting(ImmutableList.of("-printconfiguration file_name"))); + parser.parse(createConfigurationForTesting("-printconfiguration file_name")); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertTrue(config.isPrintConfiguration()); @@ -1359,7 +1353,7 @@ } private void testKeepattributes(List<String> expected, String config) { - parser.parse(createConfigurationForTesting(ImmutableList.of(config))); + parser.parse(createConfigurationForTesting(config)); verifyParserEndsCleanly(); assertEquals( ProguardKeepAttributes.fromPatterns(expected), builder.buildRaw().getKeepAttributes()); @@ -1402,7 +1396,7 @@ } private void testKeeppackagenames(String config) { - parser.parse(createConfigurationForTesting(ImmutableList.of(config))); + parser.parse(createConfigurationForTesting(config)); verifyParserEndsCleanly(); } @@ -1429,7 +1423,7 @@ @Test public void parseInvalidKeepattributes_brokenList() { try { - parser.parse(createConfigurationForTesting(ImmutableList.of("-keepattributes xxx,"))); + parser.parse(createConfigurationForTesting("-keepattributes xxx,")); fail(); } catch (RuntimeException e) { assertTrue( @@ -1448,14 +1442,14 @@ @Test public void parseKeepParameterNamesWithoutMinification() { parser.parse( - createConfigurationForTesting(ImmutableList.of("-keepparameternames", "-dontobfuscate"))); + createConfigurationForTesting(StringUtils.lines("-keepparameternames", "-dontobfuscate"))); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); assertTrue(config.isKeepParameterNames()); - parser.parse(createConfigurationForTesting(ImmutableList.of("-keepparameternames"))); + parser.parse(createConfigurationForTesting("-keepparameternames")); verifyParserEndsCleanly(); - parser.parse(createConfigurationForTesting(ImmutableList.of("-dontobfuscate"))); + parser.parse(createConfigurationForTesting("-dontobfuscate")); verifyParserEndsCleanly(); config = builder.build(); assertTrue(config.isKeepParameterNames()); @@ -1464,7 +1458,7 @@ @Test public void parseShortLine() { try { - parser.parse(createConfigurationForTesting(Collections.singletonList("-"))); + parser.parse(createConfigurationForTesting("-")); fail(); } catch (AbortException e) { assertEquals(1, handler.errors.size()); @@ -1475,7 +1469,7 @@ @Test public void parseNoLocals() { try { - parser.parse(createConfigurationForTesting(Collections.singletonList("--no-locals"))); + parser.parse(createConfigurationForTesting("--no-locals")); fail(); } catch (AbortException e) { assertEquals(1, handler.errors.size()); @@ -1491,11 +1485,10 @@ @Test public void parse_adaptresourcexxx_keepdirectories_noArguments1() { - ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of( - "-adaptresourcefilenames", - "-adaptresourcefilecontents", - "-keepdirectories" - )); + ProguardConfiguration config = + parseAndVerifyParserEndsCleanly( + StringUtils.lines( + "-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories")); checkFileFilterMatchAnything(config.getAdaptResourceFilenames()); checkFileFilterMatchAnything(config.getAdaptResourceFileContents()); checkFileFilterMatchAnything(config.getKeepDirectories()); @@ -1503,11 +1496,10 @@ @Test public void parse_adaptresourcexxx_keepdirectories_noArguments2() { - ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of( - "-keepdirectories", - "-adaptresourcefilenames", - "-adaptresourcefilecontents" - )); + ProguardConfiguration config = + parseAndVerifyParserEndsCleanly( + StringUtils.lines( + "-keepdirectories", "-adaptresourcefilenames", "-adaptresourcefilecontents")); checkFileFilterMatchAnything(config.getAdaptResourceFilenames()); checkFileFilterMatchAnything(config.getAdaptResourceFileContents()); checkFileFilterMatchAnything(config.getKeepDirectories()); @@ -1515,11 +1507,10 @@ @Test public void parse_adaptresourcexxx_keepdirectories_noArguments3() { - ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of( - "-adaptresourcefilecontents", - "-keepdirectories", - "-adaptresourcefilenames" - )); + ProguardConfiguration config = + parseAndVerifyParserEndsCleanly( + StringUtils.lines( + "-adaptresourcefilecontents", "-keepdirectories", "-adaptresourcefilenames")); checkFileFilterMatchAnything(config.getAdaptResourceFilenames()); checkFileFilterMatchAnything(config.getAdaptResourceFileContents()); checkFileFilterMatchAnything(config.getKeepDirectories()); @@ -1536,19 +1527,17 @@ @Test public void parse_adaptresourcexxx_keepdirectories_singleArgument() { - ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of( - "-adaptresourcefilenames " + FILE_FILTER_SINGLE, - "-adaptresourcefilecontents " + FILE_FILTER_SINGLE, - "-keepdirectories " + FILE_FILTER_SINGLE - )); + ProguardConfiguration config = + parseAndVerifyParserEndsCleanly( + StringUtils.lines( + "-adaptresourcefilenames " + FILE_FILTER_SINGLE, + "-adaptresourcefilecontents " + FILE_FILTER_SINGLE, + "-keepdirectories " + FILE_FILTER_SINGLE)); checkFileFilterSingle(config.getAdaptResourceFilenames()); checkFileFilterSingle(config.getAdaptResourceFileContents()); checkFileFilterSingle(config.getKeepDirectories()); } - private String FILE_FILTER_MULTIPLE = - "xxx/*, !**.gif ,images/** , com/myapp/**/*.xml,com/mylib/*/*.xml"; - private void checkFileFilterMultiple(ProguardPathFilter filter) { assertTrue(filter.matches("xxx/x")); assertTrue(filter.matches("xxx/x.gif")); @@ -1568,11 +1557,14 @@ @Test public void parse_adaptresourcexxx_keepdirectories_multipleArgument() { - ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of( - "-adaptresourcefilenames " + FILE_FILTER_MULTIPLE, - "-adaptresourcefilecontents " + FILE_FILTER_MULTIPLE, - "-keepdirectories " + FILE_FILTER_MULTIPLE - )); + String FILE_FILTER_MULTIPLE = + "xxx/*, !**.gif ,images/** , com/myapp/**/*.xml,com/mylib/*/*.xml"; + ProguardConfiguration config = + parseAndVerifyParserEndsCleanly( + StringUtils.lines( + "-adaptresourcefilenames " + FILE_FILTER_MULTIPLE, + "-adaptresourcefilecontents " + FILE_FILTER_MULTIPLE, + "-keepdirectories " + FILE_FILTER_MULTIPLE)); checkFileFilterMultiple(config.getAdaptResourceFilenames()); checkFileFilterMultiple(config.getAdaptResourceFileContents()); checkFileFilterMultiple(config.getKeepDirectories()); @@ -1585,7 +1577,7 @@ for (String option : options) { try { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,"))); + parser.parse(createConfigurationForTesting(option + " ,")); fail("Expect to fail due to the lack of path filter."); } catch (RuntimeException e) { checkDiagnostics(handler.errors, null, 1, option.length() + 2, "Path filter expected"); @@ -1600,7 +1592,7 @@ for (String option : options) { try { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy"))); + parser.parse(createConfigurationForTesting(option + " xxx,,yyy")); fail("Expect to fail due to the lack of path filter."); } catch (RuntimeException e) { checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected"); @@ -1615,7 +1607,7 @@ for (String option : options) { try { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,"))); + parser.parse(createConfigurationForTesting(option + " xxx,")); fail("Expect to fail due to the lack of path filter."); } catch (RuntimeException e) { checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected"); @@ -1629,7 +1621,7 @@ for (String option : options) { try { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(option + " class A { *; }"))); + parser.parse(createConfigurationForTesting(option + " class A { *; }")); fail("Expect to fail due to testing option being turned off."); } catch (AbortException e) { assertEquals(1, handler.errors.size()); @@ -2193,12 +2185,11 @@ assertEquals(0, handler.warnings.size()); } - private void checkRulesSourceSnippet(List<String> sourceRules) { - checkRulesSourceSnippet(sourceRules, sourceRules, false); + private void checkRulesSourceSnippet(String sourceRule) { + checkRulesSourceSnippet(sourceRule, ImmutableList.of(sourceRule), false); } - private void checkRulesSourceSnippet( - List<String> sourceRules, List<String> expected, boolean trim) { + private void checkRulesSourceSnippet(String sourceRules, List<String> expected, boolean trim) { reset(); parser.parse(createConfigurationForTesting(sourceRules)); verifyParserEndsCleanly(); @@ -2211,18 +2202,17 @@ @Test public void accurateSourceSnippet() { - String rule1 = String.join(System.lineSeparator(), ImmutableList.of("-keep class A { *; }")); + String rule1 = "-keep class A { *; }"; String rule2 = String.join(System.lineSeparator(), ImmutableList.of("-keep class A ", "{ *; ", "}")); String rule3 = String.join( System.lineSeparator(), ImmutableList.of("-checkdiscard class A ", "{ *; ", "}")); - - checkRulesSourceSnippet(ImmutableList.of(rule1)); - checkRulesSourceSnippet(ImmutableList.of(rule2)); - checkRulesSourceSnippet(ImmutableList.of(rule3)); + checkRulesSourceSnippet(rule1); + checkRulesSourceSnippet(rule2); + checkRulesSourceSnippet(rule3); checkRulesSourceSnippet( - ImmutableList.of(rule1, rule2, rule3), ImmutableList.of(rule1, rule3), false); + StringUtils.lines(rule1, rule2, rule3), ImmutableList.of(rule1, rule3), false); } @Test @@ -2238,17 +2228,17 @@ System.lineSeparator(), ImmutableList.of("-keep class A ", "{ *; ", "}", "")) .replaceAll(" {2}", space); - checkRulesSourceSnippet(ImmutableList.of(rule1), ImmutableList.of(rule1), true); + checkRulesSourceSnippet(rule1, ImmutableList.of(rule1), true); checkRulesSourceSnippet( - ImmutableList.of("#Test comment ", "", rule1), ImmutableList.of(rule1), true); + StringUtils.lines("#Test comment ", "", rule1), ImmutableList.of(rule1), true); checkRulesSourceSnippet( - ImmutableList.of("#Test comment ", "", rule1, "", "#Test comment ", ""), + StringUtils.lines("#Test comment ", "", rule1, "", "#Test comment ", ""), ImmutableList.of(rule1), true); - checkRulesSourceSnippet(ImmutableList.of(rule2), ImmutableList.of(rule2), true); - checkRulesSourceSnippet(ImmutableList.of(rule1, rule2), ImmutableList.of(rule1), true); + checkRulesSourceSnippet(rule2, ImmutableList.of(rule2), true); + checkRulesSourceSnippet(StringUtils.lines(rule1, rule2), ImmutableList.of(rule1), true); checkRulesSourceSnippet( - ImmutableList.of( + StringUtils.lines( "#Test comment ", "", rule1, " ", "#Test comment ", "", rule2, "#Test comment ", ""), ImmutableList.of(rule1), true); @@ -2280,7 +2270,7 @@ BiConsumer<String, String> check) { for (String value : values) { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value))); + parser.parse(createConfigurationForTesting(flag + " " + value)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); check.accept(value, extractPath.apply(config).toString()); @@ -2354,7 +2344,7 @@ BiConsumer<String, String> check) { for (String value : values) { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value))); + parser.parse(createConfigurationForTesting(flag + " " + value)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); Path path = extractPath.apply(config); @@ -2413,7 +2403,7 @@ @Test public void pasteFlagWithFilenamesWithSystemProperty_empty() { try { - parser.parse(createConfigurationForTesting(ImmutableList.of("-printusage <>"))); + parser.parse(createConfigurationForTesting("-printusage <>")); fail("Expect to fail due to the lack of file name."); } catch (RuntimeException e) { checkDiagnostics(handler.errors, null, 1, 15, "Value of system property '' not found"); @@ -2429,8 +2419,7 @@ } try { - parser.parse( - createConfigurationForTesting(ImmutableList.of("-printusage <" + property + ">"))); + parser.parse(createConfigurationForTesting("-printusage <" + property + ">")); fail("Expect to fail due to the lack of file name."); } catch (RuntimeException e) { checkDiagnostics( @@ -2441,7 +2430,7 @@ private void testFlagWithQuotedValue( String flag, String value, BiConsumer<PackageObfuscationMode, String> checker) { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value))); + parser.parse(createConfigurationForTesting(flag + " " + value)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); checker.accept(config.getPackageObfuscationMode(), config.getPackagePrefix()); @@ -2451,7 +2440,7 @@ String flag, String value, Supplier<Void> checker) { try { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value))); + parser.parse(createConfigurationForTesting(flag + " " + value)); fail("Expect to fail due to un-closed quote."); } catch (RuntimeException e) { checker.get(); @@ -2514,7 +2503,7 @@ }); } - private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) { + private ProguardConfiguration parseAndVerifyParserEndsCleanly(String config) { parser.parse(createConfigurationForTesting(config)); verifyParserEndsCleanly(); return builder.build(); @@ -2537,7 +2526,7 @@ // Test spaces and quotes in class name list. parser.parse( createConfigurationForTesting( - ImmutableList.of( + StringUtils.lines( "-keepclassmembers class \"a.b.c.**\" ," + " !**d , '!**e' , \"!**f\" , g , 'h' , \"i\" { ", "<fields>;", @@ -2560,7 +2549,7 @@ ? "-flattenpackagehierarchy" : "-repackageclasses"; - parser.parse(createConfigurationForTesting(ImmutableList.of(directive + " -keep class *"))); + parser.parse(createConfigurationForTesting(directive + " -keep class *")); ProguardConfiguration configuration = builder.build(); assertEquals(packageObfuscationMode, configuration.getPackageObfuscationMode()); assertEquals("", configuration.getPackagePrefix()); @@ -2585,9 +2574,7 @@ ? "-flattenpackagehierarchy" : "-repackageclasses"; - parser.parse( - createConfigurationForTesting( - ImmutableList.of(directive + " @" + includeFile.toAbsolutePath()))); + parser.parse(createConfigurationForTesting(directive + " @" + includeFile.toAbsolutePath())); ProguardConfiguration configuration = builder.build(); assertEquals(packageObfuscationMode, configuration.getPackageObfuscationMode()); assertEquals("", configuration.getPackagePrefix()); @@ -2720,7 +2707,7 @@ @Test public void backReferenceElimination() { String configuration = StringUtils.lines("-if class *.*.*", "-keep class <1>.<2>$<3>"); - parser.parse(createConfigurationForTesting(ImmutableList.of(configuration))); + parser.parse(createConfigurationForTesting(configuration)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); @@ -2783,7 +2770,7 @@ "-keep @Foo @Bar public final class *"); for (String configurationContent : configurationContents) { reset(); - parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent))); + parser.parse(createConfigurationForTesting(configurationContent)); verifyParserEndsCleanly(); ProguardConfiguration configuration = builder.build(); @@ -2856,7 +2843,7 @@ (configurationContent, expectedExceptionClass) -> { reset(); try { - parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent))); + parser.parse(createConfigurationForTesting(configurationContent)); assertFalse(expectedExceptionClass.isPresent()); } catch (Throwable e) { assertTrue(expectedExceptionClass.isPresent()); @@ -2945,7 +2932,7 @@ (configurationContent, expectedExceptionClass) -> { reset(); try { - parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent))); + parser.parse(createConfigurationForTesting(configurationContent)); assertFalse(expectedExceptionClass.isPresent()); } catch (Throwable e) { assertTrue(expectedExceptionClass.isPresent()); @@ -2995,8 +2982,7 @@ ? rule.substring(0, rule.length() - 1) : rule) + modifier; - parser.parse( - createConfigurationForTesting(ImmutableList.of(ruleWithModifier + " class A"))); + parser.parse(createConfigurationForTesting(ruleWithModifier + " class A")); verifyParserEndsCleanly(); ProguardConfiguration configuration = builder.build(); @@ -3014,7 +3000,7 @@ @Test public void parseMaximumRemovedAndroidLogLevelWithoutClassSpecification() { String configuration = StringUtils.lines("-maximumremovedandroidloglevel 2"); - parser.parse(createConfigurationForTesting(ImmutableList.of(configuration))); + parser.parse(createConfigurationForTesting(configuration)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build(); @@ -3031,7 +3017,7 @@ }) { reset(); String configuration = StringUtils.lines(input); - parser.parse(createConfigurationForTesting(ImmutableList.of(configuration))); + parser.parse(createConfigurationForTesting(configuration)); verifyParserEndsCleanly(); ProguardConfiguration config = builder.build();
diff --git a/src/test/testbase/java/com/android/tools/r8/OriginMatcher.java b/src/test/testbase/java/com/android/tools/r8/OriginMatcher.java index 4f5ad82..4f994b1 100644 --- a/src/test/testbase/java/com/android/tools/r8/OriginMatcher.java +++ b/src/test/testbase/java/com/android/tools/r8/OriginMatcher.java
@@ -26,7 +26,7 @@ @Override public void describeTo(Description description) { - description.appendText("not a parent " + parent); + description.appendText("a parent " + parent); } }; } @@ -40,7 +40,7 @@ @Override public void describeTo(Description description) { - description.appendText("part is not " + part); + description.appendText("part " + part); } }; }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java index c38ef15..31158a3 100644 --- a/src/test/testbase/java/com/android/tools/r8/TestBase.java +++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -1990,6 +1990,30 @@ return extractor.getClassFileVersion(); } + protected static String extractSourceFile(byte[] classFileBytes) { + class SourceFileExtractor extends ClassVisitor { + private String source; + + private SourceFileExtractor() { + super(ASM_VERSION); + } + + @Override + public void visitSource(String source, String debug) { + this.source = source; + } + + String getSourceFile() { + return source; + } + } + + ClassReader reader = new ClassReader(classFileBytes); + SourceFileExtractor extractor = new SourceFileExtractor(); + reader.accept(extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); + return extractor.getSourceFile(); + } + public static void verifyAllInfoFromGenericSignatureTypeParameterValidation( TestCompileResult<?, ?> compileResult) { compileResult.assertAtLeastOneInfoMessage();