Merge commit 'b21566f8' into dev-release
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json index dcd5329..244aa34 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -214,6 +214,9 @@ "java.nio.file.ProviderNotFoundException", "java.nio.file.ReadOnlyFileSystemException" ], + "retarget_method": { + "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.nio.file.OpenOption[])": "java.nio.channels.DesugarChannels" + }, "retarget_method_with_emulated_dispatch": { "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile" }, @@ -434,7 +437,6 @@ "java.util.Date java.util.Date#from(java.time.Instant)": "java.util.DesugarDate", "java.util.GregorianCalendar java.util.GregorianCalendar#from(java.time.ZonedDateTime)": "java.util.DesugarGregorianCalendar", "java.util.TimeZone java.util.TimeZone#getTimeZone(java.lang.String)": "java.util.DesugarTimeZone", - "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.nio.file.OpenOption[])": "java.nio.channels.DesugarChannels", "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])": "java.nio.channels.DesugarChannels" } },
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java index 911789d..4367a38 100644 --- a/src/main/java/com/android/tools/r8/dex/Marker.java +++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -33,7 +33,8 @@ D8, R8, L8, - Relocator; + Relocator, + TraceReferences; public static Tool[] valuesR8andD8() { return new Tool[] {Tool.D8, Tool.R8};
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java index 27dddcc..ef3a443 100644 --- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java +++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -46,6 +46,7 @@ private static final String ENABLE_MISSING_LIBRARY_API_MODELING = "enable-missing-library-api-modeling"; private static final String ANDROID_PLATFORM_BUILD = "android-platform-build"; + private static final String TRACE_REFERENCES_CONSUMER = "trace_references_consumer"; private final Tool tool; private final CompilationMode compilationMode; @@ -70,6 +71,9 @@ private final Map<String, String> systemProperties; + // TraceReferences only. + private final String traceReferencesConsumer; + // Reporting only. private final boolean dumpInputToFile; @@ -93,7 +97,8 @@ boolean enableMissingLibraryApiModeling, boolean isAndroidPlatformBuild, Map<String, String> systemProperties, - boolean dumpInputToFile) { + boolean dumpInputToFile, + String traceReferencesConsumer) { this.tool = tool; this.compilationMode = compilationMode; this.minApi = minAPI; @@ -114,6 +119,7 @@ this.isAndroidPlatformBuild = isAndroidPlatformBuild; this.systemProperties = systemProperties; this.dumpInputToFile = dumpInputToFile; + this.traceReferencesConsumer = traceReferencesConsumer; } public String getBuildPropertiesFileContent() { @@ -126,29 +132,33 @@ public Map<String, String> getBuildProperties() { Map<String, String> buildProperties = new LinkedHashMap<>(); addDumpEntry(buildProperties, TOOL_KEY, tool.name()); - // We keep the following values for backward compatibility. - addDumpEntry( - buildProperties, - MODE_KEY, - compilationMode == CompilationMode.DEBUG ? DEBUG_MODE_VALUE : RELEASE_MODE_VALUE); - addDumpEntry(buildProperties, MIN_API_KEY, minApi); - addDumpEntry( - buildProperties, OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY, optimizeMultidexForLinearAlloc); if (threadCount != ThreadUtils.NOT_SPECIFIED) { addDumpEntry(buildProperties, THREAD_COUNT_KEY, threadCount); } - addDumpEntry(buildProperties, DESUGAR_STATE_KEY, desugarState); - addDumpEntry( - buildProperties, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling); - if (isAndroidPlatformBuild) { - addDumpEntry(buildProperties, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild); + if (tool != Tool.TraceReferences) { + // We keep the following values for backward compatibility. + addDumpEntry( + buildProperties, + MODE_KEY, + compilationMode == CompilationMode.DEBUG ? DEBUG_MODE_VALUE : RELEASE_MODE_VALUE); + addDumpEntry(buildProperties, MIN_API_KEY, minApi); + addDumpEntry( + buildProperties, OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY, optimizeMultidexForLinearAlloc); + addDumpEntry(buildProperties, DESUGAR_STATE_KEY, desugarState); + addDumpEntry( + buildProperties, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling); + if (isAndroidPlatformBuild) { + addDumpEntry(buildProperties, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild); + } + addOptionalDumpEntry(buildProperties, INTERMEDIATE_KEY, intermediate); + addOptionalDumpEntry(buildProperties, INCLUDE_DATA_RESOURCES_KEY, includeDataResources); + addOptionalDumpEntry(buildProperties, TREE_SHAKING_KEY, treeShaking); + addOptionalDumpEntry( + buildProperties, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility); + } else { + addDumpEntry(buildProperties, TRACE_REFERENCES_CONSUMER, traceReferencesConsumer); } - addOptionalDumpEntry(buildProperties, INTERMEDIATE_KEY, intermediate); - addOptionalDumpEntry(buildProperties, INCLUDE_DATA_RESOURCES_KEY, includeDataResources); - addOptionalDumpEntry(buildProperties, TREE_SHAKING_KEY, treeShaking); addOptionalDumpEntry(buildProperties, MINIFICATION_KEY, minification); - addOptionalDumpEntry( - buildProperties, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility); ArrayList<String> sortedKeys = new ArrayList<>(systemProperties.keySet()); sortedKeys.sort(String::compareTo); sortedKeys.forEach( @@ -212,6 +222,9 @@ case FORCE_PROGUARD_COMPATIBILITY_KEY: builder.setForceProguardCompatibility(Boolean.parseBoolean(value)); return; + case TRACE_REFERENCES_CONSUMER: + builder.setTraceReferencesConsumer(value); + return; default: if (key.startsWith(SYSTEM_PROPERTY_PREFIX)) { builder.setSystemProperty(key.substring(SYSTEM_PROPERTY_PREFIX.length()), value); @@ -311,6 +324,8 @@ private boolean enableMissingLibraryApiModeling = false; private boolean isAndroidPlatformBuild = false; + private String traceReferencesConsumer = null; + private Map<String, String> systemProperties = new HashMap<>(); // Reporting only. @@ -323,6 +338,11 @@ return this; } + public Builder setTraceReferencesConsumer(String traceReferencesConsumer) { + this.traceReferencesConsumer = traceReferencesConsumer; + return this; + } + public Builder setCompilationMode(CompilationMode compilationMode) { this.compilationMode = compilationMode; return this; @@ -456,7 +476,8 @@ enableMissingLibraryApiModeling, isAndroidPlatformBuild, systemProperties, - dumpInputToFile); + dumpInputToFile, + traceReferencesConsumer); } } }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java index 4b70a1c..369e60c 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -275,8 +275,8 @@ public boolean isSubtype(DexType subtype, DexType supertype) { assert subtype != null; assert supertype != null; - assert subtype.isClassType() : "subtype not a class: " + subtype; - assert supertype.isClassType() : "supertype not a class: " + supertype; + assert subtype.isClassType(); + assert supertype.isClassType(); return subtype == supertype || isStrictSubtypeOf(subtype, supertype); }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java index 0d3c55a..a3e5972 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -91,6 +91,10 @@ // application writer. We therefore simulate that we are in D8, to allow building IR for each of // the class initializers without applying the unapplied code rewritings, to avoid that we apply // the lens more than once to the same piece of code. + + // Since we are now running in D8 mode clear type elements cache. + appView.dexItemFactory().clearTypeElementsCache(); + AppView<AppInfo> appViewForConversion = AppView.createForD8( AppInfo.createInitialAppInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java index 2a543a8..30c3b67 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,7 +12,6 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.DexValue.DexValueNull; import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound; @@ -30,7 +29,6 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InvokeDirect; -import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.code.Value; @@ -39,7 +37,6 @@ import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Timing; import java.util.IdentityHashMap; -import java.util.List; import java.util.Map; public class StaticFieldValueAnalysis extends FieldValueAnalysis { @@ -213,7 +210,7 @@ if (value.isPhi()) { return null; } - if (value.definition.isNewArrayEmptyOrInvokeNewArray()) { + if (value.definition.isNewArrayEmpty()) { return computeSingleEnumFieldValueForValuesArray(value); } if (value.definition.isNewInstance()) { @@ -223,7 +220,7 @@ } private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) { - if (!value.definition.isNewArrayEmptyOrInvokeNewArray()) { + if (!value.definition.isNewArrayEmpty()) { return null; } AbstractValue valuesValue = computedValues.get(value); @@ -244,38 +241,26 @@ } private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) { - NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty(); - InvokeNewArray invokeNewArray = value.definition.asInvokeNewArray(); - assert newArrayEmpty != null || invokeNewArray != null; + assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty); - DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : invokeNewArray.getArrayType(); - if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) { + NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty(); + if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) { return null; } if (value.hasDebugUsers() || value.hasPhiUsers()) { return null; } + if (!newArrayEmpty.size().isConstNumber()) { + return null; + } - int valuesSize = newArrayEmpty != null ? newArrayEmpty.sizeIfConst() : invokeNewArray.size(); - if (valuesSize < 1) { - // Array is empty or non-const size. + int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue(); + if (valuesSize == 0) { + // No need to compute the state of an empty array. return null; } ObjectState[] valuesState = new ObjectState[valuesSize]; - - if (invokeNewArray != null) { - // Populate array values from filled-new-array values. - List<Value> inValues = invokeNewArray.inValues(); - for (int i = 0; i < valuesSize; ++i) { - if (!updateEnumValueState(valuesState, i, inValues.get(i))) { - return null; - } - } - } - - // Populate / update array values from aput-object instructions, and find the static-put - // instruction. DexEncodedField valuesField = null; for (Instruction user : value.aliasedUsers()) { switch (user.opcode()) { @@ -291,9 +276,18 @@ if (index < 0 || index >= valuesSize) { return null; } - if (!updateEnumValueState(valuesState, index, arrayPut.value())) { + ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value()); + if (objectState == null || objectState.isEmpty()) { + // We need the state of all fields for the analysis to be valuable. return null; } + if (!valuesArrayIndexMatchesOrdinal(index, objectState)) { + return null; + } + if (valuesState[index] != null) { + return null; + } + valuesState[index] = objectState; break; case ASSUME: @@ -334,34 +328,24 @@ .createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState)); } - private boolean updateEnumValueState(ObjectState[] valuesState, int index, Value value) { + private ObjectState computeEnumInstanceObjectState(Value value) { Value root = value.getAliasedValue(); if (root.isPhi()) { - return false; + return ObjectState.empty(); } Instruction definition = root.getDefinition(); + if (definition.isNewInstance()) { + return computeObjectState(definition.outValue()); + } if (definition.isStaticGet()) { // Enums with many instance rely on staticGets to set the $VALUES data instead of directly // keeping the values in registers, due to the max capacity of the redundant field load // elimination. The capacity has already been increased, so that this case is extremely // uncommon (very large enums). // TODO(b/169050248): We could consider analysing these to answer the object state here. - return false; + return ObjectState.empty(); } - ObjectState objectState = - definition.isNewInstance() ? computeObjectState(definition.outValue()) : null; - if (objectState == null || objectState.isEmpty()) { - // We need the state of all fields for the analysis to be valuable. - return false; - } - if (!valuesArrayIndexMatchesOrdinal(index, objectState)) { - return false; - } - if (valuesState[index] != null) { - return false; - } - valuesState[index] = objectState; - return true; + return ObjectState.empty(); } private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java index b4b2359..74a3fb5 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -33,7 +33,6 @@ import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; -import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.MemberType; import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.NewInstance; @@ -47,7 +46,6 @@ import com.android.tools.r8.utils.Timing; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Sets; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -318,37 +316,17 @@ Value sizeValue = instructionIterator.insertConstIntInstruction(code, appView.options(), objects.size()); Value newObjectsValue = code.createValue(objectArrayType); + instructionIterator.add( + new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType)); // Populate the `objects` array. - var rewriteOptions = appView.options().rewriteArrayOptions(); - if (rewriteOptions.canUseFilledNewArrayOfObjects() - && objects.size() < rewriteOptions.maxRangeInputs) { - List<Value> arrayValues = new ArrayList<>(objects.size()); - for (int i = 0; i < objects.size(); i++) { - Instruction materializingInstruction = objects.get(i).buildIR(appView, code); - instructionIterator.add(materializingInstruction); - arrayValues.add(materializingInstruction.outValue()); - } + for (int i = 0; i < objects.size(); i++) { + Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i); + Instruction materializingInstruction = objects.get(i).buildIR(appView, code); + instructionIterator.add(materializingInstruction); instructionIterator.add( - new InvokeNewArray( - appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues)); - } else { - instructionIterator.add( - new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType)); - - // Populate the `objects` array. - for (int i = 0; i < objects.size(); i++) { - Value indexValue = - instructionIterator.insertConstIntInstruction(code, appView.options(), i); - Instruction materializingInstruction = objects.get(i).buildIR(appView, code); - instructionIterator.add(materializingInstruction); - instructionIterator.add( - new ArrayPut( - MemberType.OBJECT, - newObjectsValue, - indexValue, - materializingInstruction.outValue())); - } + new ArrayPut( + MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue())); } // Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java index c25c456..fbe3ff5 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -31,7 +31,6 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.StaticGet; @@ -303,30 +302,18 @@ */ private static ThrowingIterator<Value, InvalidRawMessageInfoException> createObjectIterator( Value objectsValue) throws InvalidRawMessageInfoException { - if (objectsValue.isPhi()) { + if (objectsValue.isPhi() || !objectsValue.definition.isNewArrayEmpty()) { throw new InvalidRawMessageInfoException(); } NewArrayEmpty newArrayEmpty = objectsValue.definition.asNewArrayEmpty(); - InvokeNewArray invokeNewArray = objectsValue.definition.asInvokeNewArray(); + int expectedArraySize = objectsValue.uniqueUsers().size() - 1; - if (newArrayEmpty == null && invokeNewArray == null) { - throw new InvalidRawMessageInfoException(); - } - // Verify that the array is used in only one spot. - if (invokeNewArray != null) { - if (objectsValue.uniqueUsers().size() != 1) { - throw new InvalidRawMessageInfoException(); - } - return ThrowingIterator.fromIterator(invokeNewArray.inValues().iterator()); - } - + // Verify that the size is correct. Value sizeValue = newArrayEmpty.size().getAliasedValue(); - if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) { - throw new InvalidRawMessageInfoException(); - } - int arraySize = sizeValue.definition.asConstNumber().getIntValue(); - if (arraySize != objectsValue.uniqueUsers().size() - 1) { + if (sizeValue.isPhi() + || !sizeValue.definition.isConstNumber() + || sizeValue.definition.asConstNumber().getIntValue() != expectedArraySize) { throw new InvalidRawMessageInfoException(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java index 99f92eb..e06cdbee 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -73,25 +73,20 @@ * <p>Use with caution! */ public static void removeArrayAndTransitiveInputsIfNotUsed(IRCode code, Instruction definition) { + Deque<InstructionOrPhi> worklist = new ArrayDeque<>(); if (definition.isConstNumber()) { // No need to explicitly remove `null`, it will be removed by ordinary dead code elimination // anyway. assert definition.asConstNumber().isZero(); return; } - Value arrayValue = definition.outValue(); - if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) { - return; - } - if (!definition.isNewArrayEmptyOrInvokeNewArray()) { - assert false; - return; - } - Deque<InstructionOrPhi> worklist = new ArrayDeque<>(); - InvokeNewArray invokeNewArray = definition.asInvokeNewArray(); - if (invokeNewArray != null) { - worklist.add(definition); - } else if (definition.isNewArrayEmpty()) { + + if (definition.isNewArrayEmpty()) { + Value arrayValue = definition.outValue(); + if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) { + return; + } + for (Instruction user : arrayValue.uniqueUsers()) { // If we encounter an Assume instruction here, we also need to consider indirect users. assert !user.isAssume(); @@ -100,10 +95,11 @@ } worklist.add(user); } - } else { - assert false; + internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist); + return; } - internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist); + + assert false; } /**
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 8892da0..c99ee11 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
@@ -1038,10 +1038,6 @@ return false; } - public boolean isNewArrayEmptyOrInvokeNewArray() { - return isNewArrayEmpty() || isInvokeNewArray(); - } - public NewArrayEmpty asNewArrayEmpty() { return null; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java index f0715b5..a2e815f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -221,9 +221,4 @@ void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) { registry.registerTypeReference(type); } - - // Returns the number of elements in the array. - public int size() { - return inValues.size(); - } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java index a829247..c6d527f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java +++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -49,6 +49,10 @@ return super.toString() + " " + type.toString(); } + public Value dest() { + return outValue; + } + public Value size() { return inValues.get(0); } @@ -56,7 +60,7 @@ @Override public void buildDex(DexBuilder builder) { int size = builder.allocatedRegister(size(), getNumber()); - int dest = builder.allocatedRegister(outValue, getNumber()); + int dest = builder.allocatedRegister(dest(), getNumber()); builder.add(this, new DexNewArray(dest, size, type)); } @@ -167,10 +171,4 @@ void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) { registry.registerTypeReference(type); } - - // Returns the size of the array if it is known, -1 otherwise. - public int sizeIfConst() { - Value size = size(); - return size.isConstNumber() ? size.getConstInstruction().asConstNumber().getIntValue() : -1; - } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java index f0ca38f..23f0de9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.algorithms.scc.SCC; import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.dex.Constants; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AccessControl; @@ -54,6 +55,7 @@ import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstClass; +import com.android.tools.r8.ir.code.ConstInstruction; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DebugLocalWrite; @@ -82,7 +84,6 @@ import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; -import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.NewArrayFilledData; @@ -137,7 +138,6 @@ import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.Collections; @@ -162,6 +162,7 @@ FALSE } + private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE; // This constant was determined by experimentation. private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50; @@ -2100,21 +2101,16 @@ } } - private short[] computeArrayFilledData(Value[] values, int size, int elementSize) { - for (Value v : values) { - if (!v.isConstant()) { - return null; - } + private short[] computeArrayFilledData(ConstInstruction[] values, int size, int elementSize) { + if (values == null) { + return null; } if (elementSize == 1) { short[] result = new short[(size + 1) / 2]; for (int i = 0; i < size; i += 2) { - short value = - (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF); + short value = (short) (values[i].asConstNumber().getIntValue() & 0xFF); if (i + 1 < size) { - value |= - (short) - ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8); + value |= (short) ((values[i + 1].asConstNumber().getIntValue() & 0xFF) << 8); } result[i / 2] = value; } @@ -2124,7 +2120,7 @@ int shortsPerConstant = elementSize / 2; short[] result = new short[size * shortsPerConstant]; for (int i = 0; i < size; i++) { - long value = values[i].getConstInstruction().asConstNumber().getRawValue(); + long value = values[i].asConstNumber().getRawValue(); for (int part = 0; part < shortsPerConstant; part++) { result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL); } @@ -2132,45 +2128,40 @@ return result; } - private Value[] computeArrayValues(LinearFlowInstructionListIterator it, int size) { - NewArrayEmpty newArrayEmpty = it.next().asNewArrayEmpty(); - assert newArrayEmpty != null; - Value arrayValue = newArrayEmpty.outValue(); - - // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify - // if types require a cast. - // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g. - // if aput-object throws a ClassCastException if given an object that does not implement the - // desired interface, then we could add check-cast instructions for arguments we're not sure - // about. - DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory); - boolean needsTypeCheck = - !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType; - - Value[] values = new Value[size]; + private ConstInstruction[] computeConstantArrayValues( + NewArrayEmpty newArray, BasicBlock block, int size) { + if (size > MAX_FILL_ARRAY_SIZE) { + return null; + } + ConstInstruction[] values = new ConstInstruction[size]; int remaining = size; - Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers(); + Set<Instruction> users = newArray.outValue().uniqueUsers(); + Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet(); + // We allow the array instantiations to cross block boundaries as long as it hasn't encountered + // an instruction instance that can throw an exception. + InstructionIterator it = block.iterator(); + it.nextUntil(i -> i == newArray); + do { + visitedBlocks.add(block); while (it.hasNext()) { Instruction instruction = it.next(); - // If we encounter an instruction that can throw an exception we need to bail out of the - // optimization so that we do not transform half-initialized arrays into fully initialized - // arrays on exceptional edges. If the block has no handlers it is not observable so - // we perform the rewriting. - // TODO(b/246971330): Allow simplification when all users of the array are in the same - // try/catch. - if (instruction.getBlock().hasCatchHandlers() && instruction.instructionInstanceCanThrow()) { + // If we encounter an instruction that can throw an exception we need to bail out of the + // optimization so that we do not transform half-initialized arrays into fully initialized + // arrays on exceptional edges. If the block has no handlers it is not observable so + // we perform the rewriting. + if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) { return null; } if (!users.contains(instruction)) { continue; } - ArrayPut arrayPut = instruction.asArrayPut(); - // If the initialization sequence is broken by another use we cannot use a fill-array-data - // instruction. - if (arrayPut == null || arrayPut.array() != arrayValue) { + // If the initialization sequence is broken by another use we cannot use a + // fill-array-data instruction. + if (!instruction.isArrayPut()) { return null; } - if (!arrayPut.index().isConstNumber()) { + ArrayPut arrayPut = instruction.asArrayPut(); + if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) { return null; } int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue(); @@ -2180,38 +2171,40 @@ if (values[index] != null) { return null; } - Value value = arrayPut.value(); - if (needsTypeCheck && !value.isAlwaysNull(appView)) { - DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory); - if (elementType.isArrayType()) { - if (elementType != valueDexType) { - return null; - } - } else if (valueDexType.isArrayType()) { - // isSubtype asserts for this case. - return null; - } else if (valueDexType.isNullValueType()) { - // Assume instructions can cause value.isAlwaysNull() == false while the DexType is - // null. - // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest - // that hits this case. - } else { - // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for - // library types (which this helper does not do). - if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) { - return null; - } - } - } + ConstInstruction value = arrayPut.value().getConstInstruction(); values[index] = value; --remaining; if (remaining == 0) { return values; } } + BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null; + block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null; + it = block != null ? block.iterator() : null; + } while (it != null); return null; } + private boolean allowNewFilledArrayConstruction(Instruction instruction) { + if (!(instruction instanceof NewArrayEmpty)) { + return false; + } + NewArrayEmpty newArray = instruction.asNewArrayEmpty(); + if (!newArray.size().isConstant()) { + return false; + } + assert newArray.size().isConstNumber(); + int size = newArray.size().getConstInstruction().asConstNumber().getIntValue(); + if (size < 1) { + return false; + } + if (newArray.type.isPrimitiveArrayType()) { + return true; + } + return newArray.type == dexItemFactory.stringArrayType + && options.canUseFilledNewArrayOfObjects(); + } + /** * Replace new-array followed by stores of constants to all entries with new-array * and fill-array-data / filled-new-array. @@ -2220,11 +2213,6 @@ if (options.isGeneratingClassFiles()) { return; } - InternalOptions.RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions(); - boolean canUseForStrings = rewriteOptions.canUseFilledNewArrayOfStrings(); - boolean canUseForObjects = rewriteOptions.canUseFilledNewArrayOfObjects(); - boolean canUseForArrays = rewriteOptions.canUseFilledNewArrayOfArrays(); - for (BasicBlock block : code.blocks) { // Map from the array value to the number of array put instruction to remove for that value. Map<Value, Instruction> instructionToInsertForArray = new HashMap<>(); @@ -2233,56 +2221,31 @@ InstructionListIterator it = block.listIterator(code); while (it.hasNext()) { Instruction instruction = it.next(); - NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty(); - if (newArrayEmpty == null || !newArrayEmpty.size().isConstant()) { + if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) { continue; } - if (instruction.getLocalInfo() != null) { - continue; - } - int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue(); - if (size < 1 || size > rewriteOptions.maxFillArrayDataInputs) { - continue; - } - DexType arrayType = newArrayEmpty.type; - if (!arrayType.isPrimitiveArrayType()) { - if (arrayType == dexItemFactory.stringArrayType) { - if (!canUseForStrings) { - continue; - } - } else if (!canUseForObjects) { - continue; - } else if (!canUseForArrays && arrayType.getNumberOfLeadingSquareBrackets() > 1) { - continue; - } - } - - Value[] values = - computeArrayValues( - new LinearFlowInstructionListIterator(code, block, it.previousIndex()), size); + NewArrayEmpty newArray = instruction.asNewArrayEmpty(); + int size = newArray.size().getConstInstruction().asConstNumber().getIntValue(); + ConstInstruction[] values = computeConstantArrayValues(newArray, block, size); if (values == null) { continue; } - // filled-new-array is implemented only for int[] and Object[]. - // For int[], using filled-new-array is usually smaller than filled-array-data. - // filled-new-array supports up to 5 registers before it's filled-new-array/range. - if (!arrayType.isPrimitiveArrayType() - || (arrayType == dexItemFactory.intArrayType && size <= 5)) { + if (newArray.type == dexItemFactory.stringArrayType) { // Don't replace with filled-new-array if it requires more than 200 consecutive registers. - if (size > rewriteOptions.maxRangeInputs) { + if (size > 200) { continue; } - // block.hasCatchHandlers() is fine here since new-array-filled replaces new-array-empty - // and computeArrayValues already checks that no throwing instructions exist between the - // original new-array-empty and the final aput-object (where the new-array-filled will be - // positioned). - Value invokeValue = - code.createValue(newArrayEmpty.getOutType(), newArrayEmpty.getLocalInfo()); - InvokeNewArray invoke = new InvokeNewArray(arrayType, invokeValue, Arrays.asList(values)); - for (Value value : newArrayEmpty.inValues()) { - value.removeUser(newArrayEmpty); + List<Value> stringValues = new ArrayList<>(size); + for (ConstInstruction value : values) { + stringValues.add(value.outValue()); } - newArrayEmpty.outValue().replaceUsers(invokeValue); + Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo()); + InvokeNewArray invoke = + new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues); + for (Value value : newArray.inValues()) { + value.removeUser(newArray); + } + newArray.outValue().replaceUsers(invokeValue); it.removeOrReplaceByDebugLocalRead(); instructionToInsertForArray.put(invokeValue, invoke); storesToRemoveForArray.put(invokeValue, size); @@ -2292,33 +2255,26 @@ if (size == 1) { continue; } - // TODO(b/246971330): Allow simplification when all users of the array are in the same - // try/catch. - if (block.hasCatchHandlers()) { - // NewArrayFilledData can throw, so creating one as done below would add a second - // throwing instruction to the same block (the first one being NewArrayEmpty). - continue; - } - int elementSize = arrayType.elementSizeForPrimitiveArrayType(); + int elementSize = newArray.type.elementSizeForPrimitiveArrayType(); short[] contents = computeArrayFilledData(values, size, elementSize); if (contents == null) { continue; } - int arraySize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue(); - // fill-array-data requires the new-array-empty instruction to remain, as it does not - // itself create an array. + if (block.hasCatchHandlers()) { + continue; + } + int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue(); NewArrayFilledData fillArray = - new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, arraySize, contents); - fillArray.setPosition(newArrayEmpty.getPosition()); + new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents); + fillArray.setPosition(newArray.getPosition()); it.add(fillArray); - storesToRemoveForArray.put(newArrayEmpty.outValue(), size); + storesToRemoveForArray.put(newArray.outValue(), size); } } // Second pass: remove all the array put instructions for the array for which we have // inserted a fill array data instruction instead. if (!storesToRemoveForArray.isEmpty()) { Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet(); - int numInstructionsInserted = 0; do { visitedBlocks.add(block); it = block.listIterator(code); @@ -2340,7 +2296,6 @@ // last removed put at which point we are now adding the construction. construction.setPosition(instruction.getPosition()); it.add(construction); - numInstructionsInserted += 1; } } } @@ -2349,7 +2304,6 @@ BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null; block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null; } while (block != null); - assert numInstructionsInserted == instructionToInsertForArray.size(); } } assert code.isConsistentSSA(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index 6fb553c..a649af6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -18,7 +18,6 @@ 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_NEW_ARRAY; 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; @@ -69,7 +68,6 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InvokeCustom; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.MemberType; @@ -1062,10 +1060,6 @@ instruction.asInstancePut(), code, context, enumClass, enumValue); case INVOKE_DIRECT: case INVOKE_INTERFACE: - return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue); - case INVOKE_NEW_ARRAY: - return analyzeInvokeNewArrayUser( - instruction.asInvokeNewArray(), code, context, enumClass, enumValue); case INVOKE_STATIC: case INVOKE_SUPER: case INVOKE_VIRTUAL: @@ -1136,40 +1130,6 @@ return Reason.INVALID_ARRAY_PUT; } - private Reason analyzeInvokeNewArrayUser( - InvokeNewArray invokeNewArray, - IRCode code, - ProgramMethod context, - DexProgramClass enumClass, - Value enumValue) { - // MyEnum[] array = new MyEnum[] { MyEnum.A }; is valid. - // We need to prove that the value to put in and the array have correct types. - TypeElement arrayType = invokeNewArray.getOutType(); - assert arrayType.isArrayType(); - - ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType(); - if (arrayBaseType == null) { - assert false; - return Reason.INVALID_INVOKE_NEW_ARRAY; - } - if (arrayBaseType.getClassType() != enumClass.type) { - return Reason.INVALID_INVOKE_NEW_ARRAY; - } - - for (Value value : invokeNewArray.inValues()) { - TypeElement valueBaseType = value.getType(); - if (valueBaseType.isArrayType()) { - assert valueBaseType.asArrayType().getBaseType().isClassType(); - assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1; - valueBaseType = valueBaseType.asArrayType().getBaseType(); - } - if (!arrayBaseType.equalUpToNullability(valueBaseType)) { - return Reason.INVALID_INVOKE_NEW_ARRAY; - } - } - return Reason.ELIGIBLE; - } - private Reason analyzeCheckCastUser( CheckCast checkCast, IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java index 7cb0290..3e17a0a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -32,8 +32,6 @@ new StringReason("IMPLICIT_UP_CAST_IN_RETURN"); public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT"); public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT"); - public static final Reason INVALID_INVOKE_NEW_ARRAY = - new StringReason("INVALID_INVOKE_NEW_ARRAY"); public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT"); public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES"); public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java index 6e14907..3e4078b 100644 --- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java +++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -27,7 +27,6 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeStatic; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.NewArrayEmpty; @@ -425,16 +424,12 @@ // Perform a conservative evaluation of an array content of dex type values from its construction // until its use at a given instruction. - private static DexTypeList evaluateTypeArrayContentFromConstructionToUse( - NewArrayEmpty newArray, List<CheckCast> aliases, Instruction user, DexItemFactory factory) { - int size = newArray.sizeIfConst(); - if (size < 0) { - return null; - } else if (size == 0) { - // TODO: We should likely still scan to ensure no ArrayPut instructions exist. - return DexTypeList.empty(); - } - + private static DexType[] evaluateTypeArrayContentFromConstructionToUse( + NewArrayEmpty newArray, + List<CheckCast> aliases, + int size, + Instruction user, + DexItemFactory factory) { DexType[] values = new DexType[size]; int remaining = size; Set<Instruction> users = Sets.newIdentityHashSet(); @@ -458,7 +453,7 @@ if (instruction == user) { // Return the array content if all elements are known when hitting the user for which // the content was requested. - return remaining == 0 ? new DexTypeList(values) : null; + return remaining == 0 ? values : null; } // Any other kinds of use besides array-put mean that the array escapes and its content // could be altered. @@ -503,21 +498,6 @@ return null; } - private static DexTypeList evaluateTypeArrayContent( - InvokeNewArray newArray, DexItemFactory factory) { - List<Value> arrayValues = newArray.inValues(); - int size = arrayValues.size(); - DexType[] values = new DexType[size]; - for (int i = 0; i < size; ++i) { - DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayValues.get(i), factory); - if (type == null) { - return null; - } - values[i] = type; - } - return new DexTypeList(values); - } - /** * Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class} * as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases. @@ -571,13 +551,30 @@ } // Make sure this Value refers to a new array. - if (classListValue.definition.isNewArrayEmpty()) { - return evaluateTypeArrayContentFromConstructionToUse( - classListValue.definition.asNewArrayEmpty(), aliases, invoke, factory); - } else if (classListValue.definition.isInvokeNewArray()) { - return evaluateTypeArrayContent(classListValue.definition.asInvokeNewArray(), factory); - } else { + if (!classListValue.definition.isNewArrayEmpty() + || !classListValue.definition.asNewArrayEmpty().size().isConstant()) { return null; } + + int size = + classListValue + .definition + .asNewArrayEmpty() + .size() + .getConstInstruction() + .asConstNumber() + .getIntValue(); + if (size == 0) { + return DexTypeList.empty(); + } + + DexType[] arrayContent = + evaluateTypeArrayContentFromConstructionToUse( + classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory); + + if (arrayContent == null) { + return null; + } + return new DexTypeList(arrayContent); } }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index 93d387e..213819d 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -104,9 +104,7 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.InvokeNewArray; import com.android.tools.r8.ir.code.InvokeVirtual; -import com.android.tools.r8.ir.code.NewArrayEmpty; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; @@ -5081,43 +5079,25 @@ return; } Value parametersValue = constructorDefinition.inValues().get(1); - if (parametersValue.isPhi()) { + if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) { // Give up, we can't tell which constructor is being invoked. return; } - NewArrayEmpty newArrayEmpty = parametersValue.definition.asNewArrayEmpty(); - InvokeNewArray invokeNewArray = parametersValue.definition.asInvokeNewArray(); - int parametersSize = - newArrayEmpty != null - ? newArrayEmpty.sizeIfConst() - : invokeNewArray != null ? invokeNewArray.size() : -1; - if (parametersSize < 0) { + + Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size(); + if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) { + // Give up, we can't tell which constructor is being invoked. return; } ProgramMethod initializer = null; + int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue(); if (parametersSize == 0) { initializer = clazz.getProgramDefaultInitializer(); } else { DexType[] parameterTypes = new DexType[parametersSize]; int missingIndices = parametersSize; - - if (newArrayEmpty != null) { - missingIndices = parametersSize; - } else { - missingIndices = 0; - List<Value> values = invokeNewArray.inValues(); - for (int i = 0; i < parametersSize; ++i) { - DexType type = - ConstantValueUtils.getDexTypeRepresentedByValueForTracing(values.get(i), appView); - if (type == null) { - return; - } - parameterTypes[i] = type; - } - } - for (Instruction user : parametersValue.uniqueUsers()) { if (user.isArrayPut()) { ArrayPut arrayPutInstruction = user.asArrayPut(); @@ -5179,31 +5159,20 @@ } Value interfacesValue = invoke.arguments().get(1); - if (interfacesValue.isPhi()) { + if (interfacesValue.isPhi() || !interfacesValue.definition.isNewArrayEmpty()) { // Give up, we can't tell which interfaces the proxy implements. return; } - InvokeNewArray invokeNewArray = interfacesValue.definition.asInvokeNewArray(); - NewArrayEmpty newArrayEmpty = interfacesValue.definition.asNewArrayEmpty(); - List<Value> values; - if (invokeNewArray != null) { - values = invokeNewArray.inValues(); - } else if (newArrayEmpty != null) { - values = new ArrayList<>(interfacesValue.uniqueUsers().size()); - for (Instruction user : interfacesValue.uniqueUsers()) { - ArrayPut arrayPut = user.asArrayPut(); - if (arrayPut != null) { - values.add(arrayPut.value()); - } - } - } else { - return; - } - WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(); - for (Value value : values) { - DexType type = ConstantValueUtils.getDexTypeRepresentedByValueForTracing(value, appView); + for (Instruction user : interfacesValue.uniqueUsers()) { + if (!user.isArrayPut()) { + continue; + } + + ArrayPut arrayPut = user.asArrayPut(); + DexType type = + ConstantValueUtils.getDexTypeRepresentedByValueForTracing(arrayPut.value(), appView); if (type == null || !type.isClassType()) { continue; }
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 1344816..416a9a4 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -28,10 +28,7 @@ public class TraceReferences { public static void run(TraceReferencesCommand command) throws CompilationFailedException { - InternalOptions options = new InternalOptions(); - options.loadAllClassDefinitions = true; - ExceptionUtils.withCompilationHandler( - command.getReporter(), () -> runInternal(command, options)); + runForTesting(command, command.getInternalOptions()); } private static void forEachDescriptor(ProgramResourceProvider provider, Consumer<String> consumer)
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 9aff1d0..2635b95 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -18,12 +18,15 @@ import com.android.tools.r8.ProgramResource.Kind; import com.android.tools.r8.ProgramResourceProvider; import com.android.tools.r8.ResourceException; +import com.android.tools.r8.dex.Marker.Tool; +import com.android.tools.r8.dump.DumpOptions; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.utils.ArchiveResourceProvider; import com.android.tools.r8.utils.Box; 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 com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.ImmutableList; @@ -396,4 +399,21 @@ TraceReferencesConsumer getConsumer() { return consumer; } + + InternalOptions getInternalOptions() { + InternalOptions options = new InternalOptions(); + options.loadAllClassDefinitions = true; + TraceReferencesConsumer consumer = getConsumer(); + DumpOptions.Builder builder = + DumpOptions.builder(Tool.TraceReferences) + .readCurrentSystemProperties() + // The behavior of TraceReferences greatly differs depending if we have a CheckConsumer + // or a KeepRules consumer. We log the consumer type and obfuscation if relevant. + .setTraceReferencesConsumer(consumer.getClass().getName()); + if (consumer instanceof TraceReferencesKeepRules) { + builder.setMinification(((TraceReferencesKeepRules) consumer).allowObfuscation()); + } + options.dumpOptions = builder.build(); + return options; + } }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java index f648da4..52ea13c 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java +++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java
@@ -40,6 +40,10 @@ this.allowObfuscation = allowObfuscation; } + public boolean allowObfuscation() { + return allowObfuscation; + } + /** * Builder for constructing a {@link TraceReferencesKeepRules]. *
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index 09641c3..b726531 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,7 +816,6 @@ public boolean debug = false; - private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions(); private final CallSiteOptimizationOptions callSiteOptimizationOptions = new CallSiteOptimizationOptions(); private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions(); @@ -851,10 +850,6 @@ public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON; - public RewriteArrayOptions rewriteArrayOptions() { - return rewriteArrayOptions; - } - public CallSiteOptimizationOptions callSiteOptimizationOptions() { return callSiteOptimizationOptions; } @@ -1388,42 +1383,6 @@ System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null; } - public class RewriteArrayOptions { - // Arbitrary limit of number of inputs to new-filled-array/range. - // The technical limit is 255 (Constants.U8BIT_MAX). - public int maxRangeInputs = 200; - // Arbitrary limit of number of inputs to fill-array-data. - public int maxFillArrayDataInputs = 8 * 1024; - - // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for - // arrays of objects. This is unfortunate, since this never hits arm devices, but we have - // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was - // removed during the jelly-bean release cycle and is not there from kitkat. - // - // Buggy code that accidentally call code that only works on primitives arrays. - // - // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106 - public boolean canUseFilledNewArrayOfStrings() { - assert isGeneratingDex(); - return hasFeaturePresentFrom(AndroidApiLevel.K); - } - - // When adding support for emitting new-filled-array for non-String types, ART 6.0.1 had issues. - // https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-6.0.1/6507/overview - // It somehow had a new-array-filled return null. - public boolean canUseFilledNewArrayOfObjects() { - assert isGeneratingDex(); - return hasFeaturePresentFrom(AndroidApiLevel.N); - } - - // Dalvik doesn't handle new-filled-array with arrays as values. It fails with: - // W(629880) VFY: [Ljava/lang/Integer; is not instance of Ljava/lang/Integer; (dalvikvm) - public boolean canUseFilledNewArrayOfArrays() { - assert isGeneratingDex(); - return hasFeaturePresentFrom(AndroidApiLevel.L); - } - } - public class CallSiteOptimizationOptions { private boolean enabled = true; @@ -2431,6 +2390,19 @@ return hasFeaturePresentFrom(AndroidApiLevel.J); } + // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for + // arrays of objects. This is unfortunate, since this never hits arm devices, but we have + // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was + // removed during the jelly-bean release cycle and is not there from kitkat. + // + // Buggy code that accidentally call code that only works on primitives arrays. + // + // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106 + public boolean canUseFilledNewArrayOfObjects() { + assert isGeneratingDex(); + return hasFeaturePresentFrom(AndroidApiLevel.K); + } + // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter // where an aget-wide instruction using the same register for the array // and the first register of the result could lead to the wrong exception
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java index 7d41ae1..50d23c9 100644 --- a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java +++ b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.utils; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -33,18 +32,4 @@ } return result; } - - public static <T, E extends Exception> ThrowingIterator<T, E> fromIterator(Iterator<T> it) { - return new ThrowingIterator<>() { - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public T next() throws E { - return it.next(); - } - }; - } }
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java index e1ab7a6..8b56639 100644 --- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java +++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -107,7 +107,8 @@ throw new RuntimeException(e); } } else { - throw new Unimplemented("No support for adding file: " + file); + assert Files.isDirectory(file); + classpath.add(file); } } return self();
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java index b00d78b..812eb63 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java +++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -10,9 +10,7 @@ import com.android.tools.r8.dex.code.DexConst4; import com.android.tools.r8.dex.code.DexConstClass; import com.android.tools.r8.dex.code.DexConstString; -import com.android.tools.r8.dex.code.DexFilledNewArray; import com.android.tools.r8.dex.code.DexInvokeVirtual; -import com.android.tools.r8.dex.code.DexMoveResultObject; import com.android.tools.r8.dex.code.DexNewArray; import com.android.tools.r8.dex.code.DexReturnVoid; import com.android.tools.r8.graph.DexCode; @@ -95,29 +93,16 @@ assertTrue(method.isPresent()); DexCode code = method.getMethod().getCode().asDexCode(); - - // Accept either array construction style (differs based on minSdkVersion). - if (code.instructions[1] instanceof DexFilledNewArray) { - assertTrue(code.instructions[0] instanceof DexConstClass); - assertTrue(code.instructions[1] instanceof DexFilledNewArray); - assertTrue(code.instructions[2] instanceof DexMoveResultObject); - assertTrue(code.instructions[3] instanceof DexConstClass); - assertTrue(code.instructions[4] instanceof DexConstString); - assertNotEquals("foo", code.instructions[4].asConstString().getString().toString()); - assertTrue(code.instructions[5] instanceof DexInvokeVirtual); - assertTrue(code.instructions[6] instanceof DexReturnVoid); - } else { - assertTrue(code.instructions[0] instanceof DexConst4); - assertTrue(code.instructions[1] instanceof DexNewArray); - assertTrue(code.instructions[2] instanceof DexConst4); - assertTrue(code.instructions[3] instanceof DexConstClass); - assertTrue(code.instructions[4] instanceof DexAputObject); - assertTrue(code.instructions[5] instanceof DexConstClass); - assertTrue(code.instructions[6] instanceof DexConstString); - assertNotEquals("foo", code.instructions[6].asConstString().getString().toString()); - assertTrue(code.instructions[7] instanceof DexInvokeVirtual); - assertTrue(code.instructions[8] instanceof DexReturnVoid); - } + assertTrue(code.instructions[0] instanceof DexConst4); + assertTrue(code.instructions[1] instanceof DexNewArray); + assertTrue(code.instructions[2] instanceof DexConst4); + assertTrue(code.instructions[3] instanceof DexConstClass); + assertTrue(code.instructions[4] instanceof DexAputObject); + assertTrue(code.instructions[5] instanceof DexConstClass); + assertTrue(code.instructions[6] instanceof DexConstString); + assertNotEquals("foo", code.instructions[6].asConstString().getString().toString()); + assertTrue(code.instructions[7] instanceof DexInvokeVirtual); + assertTrue(code.instructions[8] instanceof DexReturnVoid); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java index fb9ca6a..5ae7b6c 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -18,6 +18,7 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -35,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -43,51 +45,47 @@ @RunWith(Parameterized.class) public class FilesTest extends DesugaredLibraryTestBase { + private static final String END_EXPECTED_RESULT = + StringUtils.lines("tmp", "/", "true", "This", "is", "fun!"); private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM = StringUtils.lines( - "bytes written: 11", - "String written: Hello World", - "bytes read: 11", - "String read: Hello World", - "bytes read: 11", - "String read: Hello World", - "null", - "true", - "unsupported", - "j$.nio.file.attribute", - "tmp", - "/", - "true"); + "bytes written: 11", + "String written: Hello World", + "bytes read: 11", + "String read: Hello World", + "bytes read: 11", + "String read: Hello World", + "null", + "true", + "unsupported", + "j$.nio.file.attribute") + + END_EXPECTED_RESULT; private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING = StringUtils.lines( - "bytes written: 11", - "String written: Hello World", - "bytes read: 11", - "String read: Hello World", - "bytes read: 11", - "String read: Hello World", - "true", - "true", - "true", - "j$.nio.file.attribute", - "tmp", - "/", - "true"); + "bytes written: 11", + "String written: Hello World", + "bytes read: 11", + "String read: Hello World", + "bytes read: 11", + "String read: Hello World", + "true", + "true", + "true", + "j$.nio.file.attribute") + + END_EXPECTED_RESULT; private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM = StringUtils.lines( - "bytes written: 11", - "String written: Hello World", - "bytes read: 11", - "String read: Hello World", - "bytes read: 11", - "String read: Hello World", - "true", - "true", - "true", - "java.nio.file.attribute", - "tmp", - "/", - "true"); + "bytes written: 11", + "String written: Hello World", + "bytes read: 11", + "String read: Hello World", + "bytes read: 11", + "String read: Hello World", + "true", + "true", + "true", + "java.nio.file.attribute") + + END_EXPECTED_RESULT; private final TestParameters parameters; private final LibraryDesugaringSpecification libraryDesugaringSpecification; @@ -145,6 +143,7 @@ Files.setAttribute(path, "basic:lastModifiedTime", FileTime.from(Instant.EPOCH)); fspMethodsWithGeneric(path); pathGeneric(); + lines(path); } private static void pathGeneric() throws IOException { @@ -192,6 +191,12 @@ } } + private static void lines(Path path) throws IOException { + Files.write(path, "This\nis\nfun!".getBytes(StandardCharsets.UTF_8)); + Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8); + lines.forEach(System.out::println); + } + private static void readWriteThroughFilesAPI(Path path) throws IOException { try (SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java index 7141ad08..1e43777 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -53,7 +53,8 @@ .addProgramClasses(I.class, A.class) .addProgramClassFileData(getTransformedMain()) .addKeepMainRule(Main.class) - .addKeepMethodRules(Reference.methodFromMethod(Main.class.getDeclaredMethod("get"))) + // Keep get() to prevent that we optimize it into having static return type A. + .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }") .addOptionsModification( options -> options
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmptyTest.java similarity index 95% rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmptyTest.java index 7a7aec4..d8a78c9 100644 --- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmptyTest.java
@@ -22,7 +22,7 @@ import org.junit.runners.Parameterized; @RunWith(Parameterized.class) -public class MetadataRewriteDoNotEmitValuesIfEmpty extends KotlinMetadataTestBase { +public class MetadataRewriteDoNotEmitValuesIfEmptyTest extends KotlinMetadataTestBase { private final Set<String> nullableFieldKeys = Sets.newHashSet("pn", "xs", "xi"); @@ -35,7 +35,7 @@ private final TestParameters parameters; - public MetadataRewriteDoNotEmitValuesIfEmpty( + public MetadataRewriteDoNotEmitValuesIfEmptyTest( TestParameters parameters, KotlinTestParameters kotlinParameters) { super(kotlinParameters); this.parameters = parameters;
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java new file mode 100644 index 0000000..939eb30 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
@@ -0,0 +1,97 @@ +// Copyright (c) 2022, 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.kotlin.metadata; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.KotlinTestParameters; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import java.nio.file.Path; +import java.util.Collection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** This is a regression test for b/259389417. */ +@RunWith(Parameterized.class) +public class MetadataRewriteEnumTest extends KotlinMetadataTestBase { + + private final String[] EXPECTED = + new String[] {"UP", "RIGHT", "DOWN", "LEFT", "UP", "RIGHT", "DOWN", "LEFT"}; + private final String DIRECTION_TYPE_NAME = PKG + ".enum_lib.Direction"; + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}, {1}") + public static Collection<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), + getKotlinTestParameters().withAllCompilersAndTargetVersions().build()); + } + + public MetadataRewriteEnumTest(TestParameters parameters, KotlinTestParameters kotlinParameters) { + super(kotlinParameters); + this.parameters = parameters; + } + + private static final KotlinCompileMemoizer jarMap = + getCompileMemoizer(getKotlinFileInTest(PKG_PREFIX + "/enum_lib", "lib")); + + @Test + public void smokeTest() throws Exception { + Path libJar = jarMap.getForConfiguration(kotlinc, targetVersion); + Path output = + kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/enum_app", "main")) + .compile(); + testForRuntime(parameters) + .addProgramFiles( + libJar, output, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar()) + .run(parameters.getRuntime(), PKG + ".enum_app.MainKt") + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() throws Exception { + R8TestCompileResult r8libResult = + testForR8(parameters.getBackend()) + .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion)) + .addClasspathFiles(kotlinc.getKotlinStdlibJar()) + .addKeepKotlinMetadata() + .addKeepEnumsRule() + .addKeepClassRules(DIRECTION_TYPE_NAME) + .addKeepClassAndMembersRulesWithAllowObfuscation(DIRECTION_TYPE_NAME) + .compile() + .inspect( + inspector -> { + ClassSubject direction = inspector.clazz(DIRECTION_TYPE_NAME); + assertThat(direction, isPresentAndNotRenamed()); + direction.allFields().forEach(field -> assertTrue(field.isRenamed())); + }); + Path libJar = r8libResult.writeToZip(); + Path output = + kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion) + .addClasspathFiles(libJar) + .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/enum_app", "main")) + .compile(); + Path path = + testForR8(parameters.getBackend()) + .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), libJar) + .addProgramFiles(output) + .addKeepAllClassesRule() + .addApplyMapping(r8libResult.getProguardMap()) + .compile() + .writeToZip(); + // TODO(b/259389417): We should rename enum values in metadata. + testForRuntime(parameters) + .addProgramFiles(libJar, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), path) + .run(parameters.getRuntime(), PKG + ".enum_app.MainKt") + .assertFailureWithErrorThatThrows(NoSuchFieldError.class); + } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/enum_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/enum_app/main.kt new file mode 100644 index 0000000..ff23216 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/enum_app/main.kt
@@ -0,0 +1,16 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.metadata.enum_app + +import com.android.tools.r8.kotlin.metadata.enum_lib.Direction + +fun main() { + println(Direction.UP) + println(Direction.RIGHT) + println(Direction.DOWN) + println(Direction.LEFT) + + Direction::class.java.enumConstants.forEach { println(it) } +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/enum_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/enum_lib/lib.kt new file mode 100644 index 0000000..0423b6d --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/metadata/enum_lib/lib.kt
@@ -0,0 +1,9 @@ +// Copyright (c) 2022, 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.kotlin.metadata.enum_lib + +enum class Direction { + UP, RIGHT, DOWN, LEFT +}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java index c544c9e..83ea780 100644 --- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java +++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -16,12 +16,10 @@ import com.android.tools.r8.dex.code.DexConst4; import com.android.tools.r8.dex.code.DexConstClass; import com.android.tools.r8.dex.code.DexConstString; -import com.android.tools.r8.dex.code.DexFilledNewArray; import com.android.tools.r8.dex.code.DexInvokeDirect; import com.android.tools.r8.dex.code.DexInvokeStatic; import com.android.tools.r8.dex.code.DexInvokeVirtual; import com.android.tools.r8.dex.code.DexIputObject; -import com.android.tools.r8.dex.code.DexMoveResultObject; import com.android.tools.r8.dex.code.DexNewArray; import com.android.tools.r8.dex.code.DexReturnVoid; import com.android.tools.r8.dex.code.DexSgetObject; @@ -641,8 +639,7 @@ + "}", "-keep class " + CLASS_NAME, "-keep class R { *; }"); - CodeInspector inspector = - compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector(); + CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector(); ClassSubject clazz = inspector.clazz(CLASS_NAME); assertTrue(clazz.isPresent()); @@ -650,33 +647,19 @@ assertNotNull(method); DexCode code = method.getCode().asDexCode(); - // Accept either array construction style (differs based on minSdkVersion). - if (code.instructions[2].getClass() == DexFilledNewArray.class) { - checkInstructions( - code, - ImmutableList.of( - DexInvokeDirect.class, - DexConstClass.class, - DexFilledNewArray.class, - DexMoveResultObject.class, - DexConstString.class, - DexInvokeStatic.class, - DexReturnVoid.class)); - } else { - checkInstructions( - code, - ImmutableList.of( - DexInvokeDirect.class, - DexConst4.class, - DexNewArray.class, - DexConst4.class, - DexConstClass.class, - DexAputObject.class, - DexConstString.class, - DexInvokeStatic.class, - DexReturnVoid.class)); - } - DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3]; + checkInstructions( + code, + ImmutableList.of( + DexInvokeDirect.class, + DexConst4.class, + DexNewArray.class, + DexConst4.class, + DexConstClass.class, + DexAputObject.class, + DexConstString.class, + DexInvokeStatic.class, + DexReturnVoid.class)); + DexConstString constString = (DexConstString) code.instructions[6]; assertEquals("foo", constString.getString().toString()); } @@ -717,8 +700,7 @@ + "}", "-keep class " + CLASS_NAME, "-keep,allowobfuscation class R { *; }"); - CodeInspector inspector = - compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector(); + CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector(); ClassSubject clazz = inspector.clazz(CLASS_NAME); assertTrue(clazz.isPresent()); @@ -726,33 +708,19 @@ assertNotNull(method); DexCode code = method.getCode().asDexCode(); - // Accept either array construction style (differs based on minSdkVersion). - if (code.instructions[2].getClass() == DexFilledNewArray.class) { - checkInstructions( - code, - ImmutableList.of( - DexInvokeDirect.class, - DexConstClass.class, - DexFilledNewArray.class, - DexMoveResultObject.class, - DexConstString.class, - DexInvokeStatic.class, - DexReturnVoid.class)); - } else { - checkInstructions( - code, - ImmutableList.of( - DexInvokeDirect.class, - DexConst4.class, - DexNewArray.class, - DexConst4.class, - DexConstClass.class, - DexAputObject.class, - DexConstString.class, - DexInvokeStatic.class, - DexReturnVoid.class)); - } - DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3]; + checkInstructions( + code, + ImmutableList.of( + DexInvokeDirect.class, + DexConst4.class, + DexNewArray.class, + DexConst4.class, + DexConstClass.class, + DexAputObject.class, + DexConstString.class, + DexInvokeStatic.class, + DexReturnVoid.class)); + DexConstString constString = (DexConstString) code.instructions[6]; assertNotEquals("foo", constString.getString().toString()); }
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java index 513dc73..4a76a6a 100644 --- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java +++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -6,7 +6,6 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.CompilationMode; -import com.android.tools.r8.NeverInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.graph.AppView; @@ -16,7 +15,6 @@ import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; -import com.android.tools.r8.utils.codeinspector.MethodSubject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -35,7 +33,7 @@ this.parameters = parameters; } - private static final String[] expectedOutput = {"3", "2"}; + private static final String[] expectedOutput = {"3"}; @Test public void d8() throws Exception { @@ -46,7 +44,7 @@ .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines(expectedOutput) - .inspect(i -> inspect(i, true)); + .inspect(this::assertNoArrayLength); } @Test @@ -56,52 +54,32 @@ .addProgramClasses(Main.class) .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray) .addKeepMainRule(Main.class) - .enableInliningAnnotations() .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines(expectedOutput) - .inspect(i -> inspect(i, false)); + .inspect(this::assertNoArrayLength); } private void transformArray(IRCode irCode, AppView<?> appView) { + if (irCode.context().getReference().getName().toString().contains("main")) { new CodeRewriter(appView).simplifyArrayConstruction(irCode); - String name = irCode.context().getReference().getName().toString(); - if (name.contains("filledArrayData")) { assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData)); - } else if (name.contains("filledNewArray")) { - assertTrue(irCode.streamInstructions().anyMatch(Instruction::isInvokeNewArray)); } } - private void inspect(CodeInspector inspector, boolean d8) { + private void assertNoArrayLength(CodeInspector inspector) { ClassSubject mainClass = inspector.clazz(Main.class); assertTrue(mainClass.isPresent()); - MethodSubject filledArrayData = mainClass.uniqueMethodWithOriginalName("filledArrayData"); - assertTrue(filledArrayData.streamInstructions().noneMatch(InstructionSubject::isArrayLength)); - if (!d8) { - MethodSubject filledNewArray = mainClass.uniqueMethodWithOriginalName("filledNewArray"); - assertTrue(filledNewArray.streamInstructions().noneMatch(InstructionSubject::isArrayLength)); - } + assertTrue( + mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength)); } public static final class Main { - @NeverInline - public static void filledArrayData() { - short[] values = new short[3]; - values[0] = 5; - values[1] = 6; - values[2] = 1; - System.out.println(values.length); - } - - @NeverInline - public static void filledNewArray() { - int[] values = new int[] {7, 8}; - System.out.println(values.length); - } - public static void main(String[] args) { - filledArrayData(); - filledNewArray(); + int[] ints = new int[3]; + ints[0] = 5; + ints[1] = 6; + ints[2] = 1; + System.out.println(ints.length); } } }
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java deleted file mode 100644 index 939753f..0000000 --- a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java +++ /dev/null
@@ -1,774 +0,0 @@ -// Copyright (c) 2022, 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.rewrite.arrays; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; - -import com.android.tools.r8.CompilationMode; -import com.android.tools.r8.Keep; -import com.android.tools.r8.NeverInline; -import com.android.tools.r8.TestBase; -import com.android.tools.r8.TestParameters; -import com.android.tools.r8.dex.code.DexFillArrayData; -import com.android.tools.r8.dex.code.DexFilledNewArray; -import com.android.tools.r8.dex.code.DexFilledNewArrayRange; -import com.android.tools.r8.dex.code.DexNewArray; -import com.android.tools.r8.references.Reference; -import com.android.tools.r8.transformers.ClassFileTransformer; -import com.android.tools.r8.utils.AndroidApiLevel; -import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; -import com.android.tools.r8.utils.codeinspector.InstructionSubject; -import com.android.tools.r8.utils.codeinspector.MethodSubject; -import com.beust.jcommander.internal.Lists; -import java.io.IOException; -import java.io.Serializable; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; -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 SimplifyArrayConstructionTest extends TestBase { - @Parameters(name = "{0}, mode = {1}") - public static Iterable<?> data() { - return buildParameters( - getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(), - CompilationMode.values()); - } - - private final TestParameters parameters; - private final CompilationMode compilationMode; - - public SimplifyArrayConstructionTest(TestParameters parameters, CompilationMode compilationMode) { - this.parameters = parameters; - this.compilationMode = compilationMode; - } - - private static final Class<?>[] DEX_ARRAY_INSTRUCTIONS = { - DexNewArray.class, DexFilledNewArray.class, DexFilledNewArrayRange.class, DexFillArrayData.class - }; - - private static final String[] EXPECTED_OUTPUT = { - "[a]", - "[a, 1, null]", - "[1, null]", - "[1, null, 2]", - "[1, null, 2]", - "[1]", - "[1, 2]", - "[1, 2, 3, 4, 5]", - "[1]", - "[a, 1, null, d, e, f]", - "[1, null, 3, null, null, 6]", - "[1, 2, 3, 4, 5, 6]", - "[true, false]", - "[1, 2]", - "[1, 2]", - "[1, 2]", - "[1.0, 2.0]", - "[1.0, 2.0]", - "[]", - "[]", - "[true]", - "[1]", - "[1]", - "[1]", - "[1.0]", - "[1.0]", - "[0, 1]", - "[1, null]", - "[a]", - "[0, 1]", - "200", - "[0, 1, 2, 3, 4]", - "[4, 0, 0, 0, 0]", - "[4, 1, 2, 3, 4]", - "[0, 1, 2]", - "[0]", - "[0, 1, 2]", - "[1, 2, 3]", - "[1, 2, 3, 4, 5, 6]", - "[0]", - "[null, null]", - }; - - private static final byte[] TRANSFORMED_MAIN = transformedMain(); - - @Test - public void testRuntime() throws Exception { - assumeFalse(compilationMode == CompilationMode.DEBUG); - testForRuntime( - parameters.getRuntime(), - d8TestBuilder -> - d8TestBuilder.setMinApi(parameters.getApiLevel()).setMode(compilationMode)) - .addProgramClassFileData(TRANSFORMED_MAIN) - .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines(EXPECTED_OUTPUT) - .inspect(inspector -> inspect(inspector, false)); - } - - @Test - public void testR8() throws Exception { - testForR8(parameters.getBackend()) - .setMinApi(parameters.getApiLevel()) - .addOptionsModification( - options -> - options - .getOpenClosedInterfacesOptions() - .suppressSingleOpenInterface(Reference.classFromClass(Serializable.class))) - .setMode(compilationMode) - .addProgramClassFileData(TRANSFORMED_MAIN) - .addKeepMainRule(Main.class) - .enableInliningAnnotations() - .addKeepAnnotation() - .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }") - .addKeepRules("-assumenosideeffects class * { *** assumedNullField return null; }") - .addKeepRules("-assumenosideeffects class * { *** assumedNonNullField return _NONNULL_; }") - .addDontObfuscate() - .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines(EXPECTED_OUTPUT) - .inspect(inspector -> inspect(inspector, true)); - } - - private static byte[] transformedMain() { - try { - return transformer(Main.class) - .transformMethodInsnInMethod( - "interfaceArrayWithRawObject", - (opcode, owner, name, descriptor, isInterface, visitor) -> { - if (name.equals("getObjectThatImplementsSerializable")) { - visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface); - } else { - visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); - } - }) - .setReturnType( - ClassFileTransformer.MethodPredicate.onName("getObjectThatImplementsSerializable"), - Object.class.getTypeName()) - .transform(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void inspect(CodeInspector inspector, boolean isR8) { - if (parameters.isCfRuntime()) { - return; - } - ClassSubject mainClass = inspector.clazz(Main.class); - assertTrue(mainClass.isPresent()); - - MethodSubject stringArrays = mainClass.uniqueMethodWithOriginalName("stringArrays"); - MethodSubject referenceArraysNoCasts = - mainClass.uniqueMethodWithOriginalName("referenceArraysNoCasts"); - MethodSubject referenceArraysWithSubclasses = - mainClass.uniqueMethodWithOriginalName("referenceArraysWithSubclasses"); - MethodSubject interfaceArrayWithRawObject = - mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject"); - - MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray"); - MethodSubject intsThatUseFilledNewArray = - mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray"); - MethodSubject twoDimensionalArrays = - mainClass.uniqueMethodWithOriginalName("twoDimensionalArrays"); - MethodSubject objectArraysFilledNewArrayRange = - mainClass.uniqueMethodWithOriginalName("objectArraysFilledNewArrayRange"); - MethodSubject arraysThatUseFilledData = - mainClass.uniqueMethodWithOriginalName("arraysThatUseFilledData"); - MethodSubject arraysThatUseNewArrayEmpty = - mainClass.uniqueMethodWithOriginalName("arraysThatUseNewArrayEmpty"); - MethodSubject reversedArray = mainClass.uniqueMethodWithOriginalName("reversedArray"); - MethodSubject arrayWithCorrectCountButIncompleteCoverage = - mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage"); - MethodSubject arrayWithExtraInitialPuts = - mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts"); - MethodSubject catchHandlerThrowing = - mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing"); - MethodSubject catchHandlerNonThrowingFilledNewArray = - mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray"); - MethodSubject catchHandlerNonThrowingFillArrayData = - mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData"); - MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues"); - - assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class); - assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class); - assertFilledArrayData(arraysThatUseFilledData); - - if (compilationMode == CompilationMode.DEBUG) { - // The explicit assignments can't be collapsed without breaking the debugger's ability to - // visit each line. - assertArrayTypes(reversedArray, DexNewArray.class); - } else { - assertArrayTypes(reversedArray, DexFilledNewArray.class); - } - - // Cannot use filled-new-array of String before K. - if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) { - assertArrayTypes(stringArrays, DexNewArray.class); - } else { - assertArrayTypes(stringArrays, DexFilledNewArray.class); - } - // Cannot use filled-new-array of Object before L. - if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) { - assertArrayTypes(referenceArraysNoCasts, DexNewArray.class); - assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class); - assertArrayTypes(phiFilledNewArray, DexNewArray.class); - assertArrayTypes(objectArraysFilledNewArrayRange, DexNewArray.class); - assertArrayTypes(twoDimensionalArrays, DexNewArray.class); - assertArrayTypes(assumedValues, DexNewArray.class); - } else { - assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class); - // TODO(b/246971330): Add support for arrays with subtypes. - if (isR8) { - assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class); - } else { - assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class); - } - - // TODO(b/246971330): Add support for arrays whose values have conditionals. - // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class); - - assertArrayTypes(objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class); - - if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) { - assertArrayTypes(twoDimensionalArrays, DexFilledNewArray.class); - } else { - // No need to assert this case. If it's wrong, Dalvik verify errors cause test failures. - } - - assertArrayTypes(assumedValues, DexFilledNewArray.class); - } - // filled-new-array fails verification when types of parameters are not subclasses (aput-object - // does not). - assertArrayTypes(interfaceArrayWithRawObject, DexNewArray.class); - - // These could be optimized to use InvokeNewArray, but they seem like rare code patterns so we - // haven't bothered. - assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class); - assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class); - - assertArrayTypes(catchHandlerThrowing, DexNewArray.class); - assertArrayTypes(catchHandlerNonThrowingFillArrayData, DexNewArray.class); - assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class); - } - - private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) { - return (ins) -> allowlist.contains(ins.asDexInstruction().getInstruction().getClass()); - } - - private static Predicate<InstructionSubject> isInstruction(Class<?> clazz) { - return isInstruction(Arrays.asList(clazz)); - } - - private static void assertArrayTypes(MethodSubject method, Class<?> allowedArrayInst) { - assertTrue(method.isPresent()); - List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS); - disallowedClasses.remove(allowedArrayInst); - - assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedArrayInst))); - assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses))); - } - - private static void assertFilledArrayData(MethodSubject method) { - assertTrue(method.isPresent()); - List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS); - disallowedClasses.remove(DexFillArrayData.class); - disallowedClasses.remove(DexNewArray.class); - - assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses))); - assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayPut)); - long numNewArray = method.streamInstructions().filter(InstructionSubject::isNewArray).count(); - long numFillArray = - method.streamInstructions().filter(isInstruction(DexFillArrayData.class)).count(); - assertEquals(numNewArray, numFillArray); - } - - public static final class Main { - static final String assumedNonNullField = null; - static final String assumedNullField = null; - - public static void main(String[] args) { - stringArrays(); - referenceArraysNoCasts(); - referenceArraysWithSubclasses(); - interfaceArrayWithRawObject(); - phiFilledNewArray(); - intsThatUseFilledNewArray(); - twoDimensionalArrays(); - objectArraysFilledNewArrayRange(); - arraysThatUseFilledData(); - arraysThatUseNewArrayEmpty(); - reversedArray(); - arrayWithCorrectCountButIncompleteCoverage(); - arrayWithExtraInitialPuts(); - catchHandlerThrowing(); - catchHandlerNonThrowingFilledNewArray(); - catchHandlerNonThrowingFillArrayData(); - arrayIntoAnotherArray(); - assumedValues(); - } - - @NeverInline - private static void stringArrays() { - // Test exact class, no null. - String[] stringArr = {"a"}; - System.out.println(Arrays.toString(stringArr)); - } - - @NeverInline - private static void referenceArraysNoCasts() { - // Tests that no type info is needed when array type is Object[]. - Object[] objectArr = {"a", 1, null}; - System.out.println(Arrays.toString(objectArr)); - // Test that interface arrays work when we have the exact interface already. - Serializable[] interfaceArr = {getSerializable(1), null}; - System.out.println(Arrays.toString(interfaceArr)); - } - - @Keep - private static Serializable getSerializable(Integer value) { - return value; - } - - @NeverInline - private static void referenceArraysWithSubclasses() { - Serializable[] interfaceArr = {1, null, 2}; - System.out.println(Arrays.toString(interfaceArr)); - Number[] objArray = {1, null, 2}; - System.out.println(Arrays.toString(objArray)); - } - - @NeverInline - private static void interfaceArrayWithRawObject() { - // Interfaces can use filled-new-array only when we know types implement the interface. - Serializable[] arr = new Serializable[1]; - // Transformed from `I get()` to `Object get()`. - arr[0] = getObjectThatImplementsSerializable(); - System.out.println(Arrays.toString(arr)); - } - - @Keep - private static /*Object*/ Serializable getObjectThatImplementsSerializable() { - return 1; - } - - @NeverInline - private static void reversedArray() { - int[] arr = new int[5]; - arr[4] = 4; - arr[3] = 3; - arr[2] = 2; - arr[1] = 1; - arr[0] = 0; - System.out.println(Arrays.toString(arr)); - } - - @NeverInline - private static void arrayWithCorrectCountButIncompleteCoverage() { - int[] arr = new int[5]; - arr[0] = 0; - arr[0] = 1; - arr[0] = 2; - arr[0] = 3; - arr[0] = 4; - System.out.println(Arrays.toString(arr)); - } - - @NeverInline - private static void arrayWithExtraInitialPuts() { - int[] arr = new int[5]; - arr[0] = 0; - arr[0] = 1; - arr[0] = 2; - arr[0] = 3; - arr[0] = 4; - arr[1] = 1; - arr[2] = 2; - arr[3] = 3; - arr[4] = 4; - System.out.println(Arrays.toString(arr)); - } - - @NeverInline - private static void catchHandlerNonThrowingFilledNewArray() { - try { - int[] arr1 = {1, 2, 3}; - System.currentTimeMillis(); - System.out.println(Arrays.toString(arr1)); - } catch (Throwable t) { - throw new RuntimeException(t); - } - } - - @NeverInline - private static void catchHandlerNonThrowingFillArrayData() { - try { - int[] arr = {1, 2, 3, 4, 5, 6}; - System.currentTimeMillis(); - System.out.println(Arrays.toString(arr)); - } catch (Throwable t) { - throw new RuntimeException(t); - } - } - - @NeverInline - private static void catchHandlerThrowing() { - int[] arr1 = new int[3]; - arr1[0] = 0; - arr1[1] = 1; - // Since the array is used in only one spot, and that spot is not within the try/catch, it - // should be safe to use filled-new-array, but we don't. - try { - System.currentTimeMillis(); - arr1[2] = 2; - } catch (Throwable t) { - throw new RuntimeException(t); - } - System.out.println(Arrays.toString(arr1)); - - try { - // Test filled-new-array with a throwing instruction before the last array-put. - int[] arr2 = new int[1]; - System.currentTimeMillis(); - arr2[0] = 0; - System.out.println(Arrays.toString(arr2)); - - // Test filled-array-data with a throwing instruction before the last array-put. - short[] arr3 = new short[3]; - arr3[0] = 0; - arr3[1] = 1; - System.currentTimeMillis(); - arr3[2] = 2; - System.out.println(Arrays.toString(arr3)); - } catch (Throwable t) { - throw new RuntimeException(t); - } - } - - @NeverInline - private static void arrayIntoAnotherArray() { - // Tests that our computeValues() method does not assume all array-put-object users have - // the array as the target. - int[] intArr = new int[1]; - Object[] objArr = new Object[2]; - objArr[0] = intArr; - System.out.println(Arrays.toString((int[]) objArr[0])); - } - - @NeverInline - private static void assumedValues() { - Object[] arr = {assumedNonNullField, assumedNullField}; - System.out.println(Arrays.toString(arr)); - } - - @NeverInline - private static void phiFilledNewArray() { - // The presence of ? should not affect use of filled-new-array. - Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3}; - System.out.println(Arrays.toString(phiArray)); - } - - @NeverInline - private static void intsThatUseFilledNewArray() { - // Up to 5 ints uses filled-new-array rather than filled-array-data. - int[] intArr = {1, 2, 3, 4, 5}; - System.out.println(Arrays.toString(intArr)); - } - - @NeverInline - private static void twoDimensionalArrays() { - Integer[][] twoDimensions = {new Integer[] {1}, null}; - System.out.println(Arrays.toString(Arrays.asList(twoDimensions).get(0))); - } - - @NeverInline - private static void objectArraysFilledNewArrayRange() { - // 6 or more elements use /range variant. - Object[] objectArr = {"a", 1, null, "d", "e", "f"}; - System.out.println(Arrays.toString(objectArr)); - Serializable[] interfaceArr = { - getSerializable(1), null, getSerializable(3), null, null, getSerializable(6) - }; - System.out.println(Arrays.toString(interfaceArr)); - } - - @NeverInline - private static void arraysThatUseFilledData() { - // For int[], <= 5 elements should use NewArrayFilledData (otherwise NewFilledArray is used). - int[] intArr = {1, 2, 3, 4, 5, 6}; - // For other primitives, > 1 element leads to fill-array-data. - System.out.println(Arrays.toString(intArr)); - boolean[] boolArr = {true, false}; - System.out.println(Arrays.toString(boolArr)); - byte[] byteArr = {1, 2}; - System.out.println(Arrays.toString(byteArr)); - char[] charArr = {'1', '2'}; - System.out.println(Arrays.toString(charArr)); - long[] longArr = {1, 2}; - System.out.println(Arrays.toString(longArr)); - float[] floatArr = {1, 2}; - System.out.println(Arrays.toString(floatArr)); - double[] doubleArr = {1, 2}; - System.out.println(Arrays.toString(doubleArr)); - } - - @NeverInline - private static void arraysThatUseNewArrayEmpty() { - // int/object of size zero should not use filled-new-array. - int[] intArr = {}; - System.out.println(Arrays.toString(intArr)); - String[] strArr = {}; - System.out.println(Arrays.toString(strArr)); - - // Other primitives with size <= 1 should not use filled-array-data. - boolean[] boolArr = {true}; - System.out.println(Arrays.toString(boolArr)); - byte[] byteArr = {1}; - System.out.println(Arrays.toString(byteArr)); - char[] charArr = {'1'}; - System.out.println(Arrays.toString(charArr)); - long[] longArr = {1}; - System.out.println(Arrays.toString(longArr)); - float[] floatArr = {1}; - System.out.println(Arrays.toString(floatArr)); - double[] doubleArr = {1}; - System.out.println(Arrays.toString(doubleArr)); - - // Array used before all members are set. - int[] readArray = new int[2]; - readArray[0] = (int) (System.currentTimeMillis() / System.nanoTime()); - readArray[1] = readArray[0] + 1; - System.out.println(Arrays.toString(readArray)); - - // Array does not have all elements set (we could make this work, but likely this is rare). - Integer[] partialArray = new Integer[2]; - partialArray[0] = 1; - System.out.println(Arrays.toString(partialArray)); - - // Non-constant array size. - int trickyZero = (int) (System.currentTimeMillis() / System.nanoTime()); - Object[] nonConstSize = new Object[trickyZero + 1]; - nonConstSize[0] = "a"; - System.out.println(Arrays.toString(nonConstSize)); - - // Non-constant index. - Object[] nonConstIndex = new Object[2]; - nonConstIndex[trickyZero] = 0; - nonConstIndex[trickyZero + 1] = 1; - System.out.println(Arrays.toString(nonConstIndex)); - - // Exceeds our (arbitrary) size limit for /range. - String[] bigArr = new String[201]; - bigArr[0] = "0"; - bigArr[1] = "1"; - bigArr[2] = "2"; - bigArr[3] = "3"; - bigArr[4] = "4"; - bigArr[5] = "5"; - bigArr[6] = "6"; - bigArr[7] = "7"; - bigArr[8] = "8"; - bigArr[9] = "9"; - bigArr[10] = "10"; - bigArr[11] = "11"; - bigArr[12] = "12"; - bigArr[13] = "13"; - bigArr[14] = "14"; - bigArr[15] = "15"; - bigArr[16] = "16"; - bigArr[17] = "17"; - bigArr[18] = "18"; - bigArr[19] = "19"; - bigArr[20] = "20"; - bigArr[21] = "21"; - bigArr[22] = "22"; - bigArr[23] = "23"; - bigArr[24] = "24"; - bigArr[25] = "25"; - bigArr[26] = "26"; - bigArr[27] = "27"; - bigArr[28] = "28"; - bigArr[29] = "29"; - bigArr[30] = "30"; - bigArr[31] = "31"; - bigArr[32] = "32"; - bigArr[33] = "33"; - bigArr[34] = "34"; - bigArr[35] = "35"; - bigArr[36] = "36"; - bigArr[37] = "37"; - bigArr[38] = "38"; - bigArr[39] = "39"; - bigArr[40] = "40"; - bigArr[41] = "41"; - bigArr[42] = "42"; - bigArr[43] = "43"; - bigArr[44] = "44"; - bigArr[45] = "45"; - bigArr[46] = "46"; - bigArr[47] = "47"; - bigArr[48] = "48"; - bigArr[49] = "49"; - bigArr[50] = "50"; - bigArr[51] = "51"; - bigArr[52] = "52"; - bigArr[53] = "53"; - bigArr[54] = "54"; - bigArr[55] = "55"; - bigArr[56] = "56"; - bigArr[57] = "57"; - bigArr[58] = "58"; - bigArr[59] = "59"; - bigArr[60] = "60"; - bigArr[61] = "61"; - bigArr[62] = "62"; - bigArr[63] = "63"; - bigArr[64] = "64"; - bigArr[65] = "65"; - bigArr[66] = "66"; - bigArr[67] = "67"; - bigArr[68] = "68"; - bigArr[69] = "69"; - bigArr[70] = "70"; - bigArr[71] = "71"; - bigArr[72] = "72"; - bigArr[73] = "73"; - bigArr[74] = "74"; - bigArr[75] = "75"; - bigArr[76] = "76"; - bigArr[77] = "77"; - bigArr[78] = "78"; - bigArr[79] = "79"; - bigArr[80] = "80"; - bigArr[81] = "81"; - bigArr[82] = "82"; - bigArr[83] = "83"; - bigArr[84] = "84"; - bigArr[85] = "85"; - bigArr[86] = "86"; - bigArr[87] = "87"; - bigArr[88] = "88"; - bigArr[89] = "89"; - bigArr[90] = "90"; - bigArr[91] = "91"; - bigArr[92] = "92"; - bigArr[93] = "93"; - bigArr[94] = "94"; - bigArr[95] = "95"; - bigArr[96] = "96"; - bigArr[97] = "97"; - bigArr[98] = "98"; - bigArr[99] = "99"; - bigArr[100] = "100"; - bigArr[101] = "101"; - bigArr[102] = "102"; - bigArr[103] = "103"; - bigArr[104] = "104"; - bigArr[105] = "105"; - bigArr[106] = "106"; - bigArr[107] = "107"; - bigArr[108] = "108"; - bigArr[109] = "109"; - bigArr[110] = "110"; - bigArr[111] = "111"; - bigArr[112] = "112"; - bigArr[113] = "113"; - bigArr[114] = "114"; - bigArr[115] = "115"; - bigArr[116] = "116"; - bigArr[117] = "117"; - bigArr[118] = "118"; - bigArr[119] = "119"; - bigArr[120] = "120"; - bigArr[121] = "121"; - bigArr[122] = "122"; - bigArr[123] = "123"; - bigArr[124] = "124"; - bigArr[125] = "125"; - bigArr[126] = "126"; - bigArr[127] = "127"; - bigArr[128] = "128"; - bigArr[129] = "129"; - bigArr[130] = "130"; - bigArr[131] = "131"; - bigArr[132] = "132"; - bigArr[133] = "133"; - bigArr[134] = "134"; - bigArr[135] = "135"; - bigArr[136] = "136"; - bigArr[137] = "137"; - bigArr[138] = "138"; - bigArr[139] = "139"; - bigArr[140] = "140"; - bigArr[141] = "141"; - bigArr[142] = "142"; - bigArr[143] = "143"; - bigArr[144] = "144"; - bigArr[145] = "145"; - bigArr[146] = "146"; - bigArr[147] = "147"; - bigArr[148] = "148"; - bigArr[149] = "149"; - bigArr[150] = "150"; - bigArr[151] = "151"; - bigArr[152] = "152"; - bigArr[153] = "153"; - bigArr[154] = "154"; - bigArr[155] = "155"; - bigArr[156] = "156"; - bigArr[157] = "157"; - bigArr[158] = "158"; - bigArr[159] = "159"; - bigArr[160] = "160"; - bigArr[161] = "161"; - bigArr[162] = "162"; - bigArr[163] = "163"; - bigArr[164] = "164"; - bigArr[165] = "165"; - bigArr[166] = "166"; - bigArr[167] = "167"; - bigArr[168] = "168"; - bigArr[169] = "169"; - bigArr[170] = "170"; - bigArr[171] = "171"; - bigArr[172] = "172"; - bigArr[173] = "173"; - bigArr[174] = "174"; - bigArr[175] = "175"; - bigArr[176] = "176"; - bigArr[177] = "177"; - bigArr[178] = "178"; - bigArr[179] = "179"; - bigArr[180] = "180"; - bigArr[181] = "181"; - bigArr[182] = "182"; - bigArr[183] = "183"; - bigArr[184] = "184"; - bigArr[185] = "185"; - bigArr[186] = "186"; - bigArr[187] = "187"; - bigArr[188] = "188"; - bigArr[189] = "189"; - bigArr[190] = "190"; - bigArr[191] = "191"; - bigArr[192] = "192"; - bigArr[193] = "193"; - bigArr[194] = "194"; - bigArr[195] = "195"; - bigArr[196] = "196"; - bigArr[197] = "197"; - bigArr[198] = "198"; - bigArr[199] = "199"; - bigArr[200] = "200"; - System.out.println(Arrays.asList(bigArr).get(200)); - } - } -}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java new file mode 100644 index 0000000..963c2e7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java
@@ -0,0 +1,123 @@ +// Copyright (c) 2022, 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.tracereferences; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.StringConsumer.FileConsumer; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.DumpInputFlags; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ZipUtils; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class TraceReferenceDumpInputsTest extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withSystemRuntime().build(); + } + + public TraceReferenceDumpInputsTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testDumpToDirectory() throws Exception { + Path dumpDir = temp.newFolder().toPath(); + TraceReferencesKeepRules keepRulesConsumer = + TraceReferencesKeepRules.builder() + .setAllowObfuscation(true) + .setOutputConsumer(new FileConsumer(temp.newFile().toPath())) + .build(); + TraceReferencesCommand command = + TraceReferencesCommand.builder() + .setConsumer(keepRulesConsumer) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addTargetFiles(ToolHelper.getClassFileForTestClass(TestClass.class)) + .addSourceFiles(ToolHelper.getClassFileForTestClass(OtherTestClass.class)) + .build(); + InternalOptions internalOptions = command.getInternalOptions(); + internalOptions.setDumpInputFlags(DumpInputFlags.dumpToDirectory(dumpDir)); + TraceReferences.runForTesting(command, internalOptions); + verifyDumpDirectory(dumpDir); + } + + private void verifyDumpDirectory(Path dumpDir) throws IOException { + assertTrue(Files.isDirectory(dumpDir)); + List<Path> paths = Files.walk(dumpDir, 1).collect(Collectors.toList()); + boolean hasVerified = false; + for (Path path : paths) { + if (!path.equals(dumpDir)) { + verifyDump(path); + hasVerified = true; + } + } + assertTrue(hasVerified); + } + + private void verifyDump(Path dumpFile) throws IOException { + assertTrue(Files.exists(dumpFile)); + Path unzipped = temp.newFolder().toPath(); + ZipUtils.unzip(dumpFile.toString(), unzipped.toFile()); + assertTrue(Files.exists(unzipped.resolve("r8-version"))); + assertTrue(Files.exists(unzipped.resolve("build.properties"))); + assertTrue(Files.exists(unzipped.resolve("program.jar"))); + assertTrue(Files.exists(unzipped.resolve("library.jar"))); + assertTrue(Files.exists(unzipped.resolve("classpath.jar"))); + contains(unzipped, "program.jar", OtherTestClass.class); + contains(unzipped, "classpath.jar", TestClass.class); + checkProperties(unzipped.resolve("build.properties")); + } + + private void checkProperties(Path properties) throws IOException { + List<String> lines = Files.readAllLines(properties); + assertEquals(4, lines.size()); + assertEquals("tool=TraceReferences", lines.get(0)); + assertEquals( + "trace_references_consumer=com.android.tools.r8.tracereferences.TraceReferencesKeepRules", + lines.get(2)); + assertEquals("minification=true", lines.get(3)); + } + + private void contains(Path unzipped, String jar, Class<?> clazz) throws IOException { + Set<String> entries = new HashSet<>(); + ZipUtils.iter(unzipped.resolve(jar).toString(), (entry, input) -> entries.add(entry.getName())); + assertTrue( + entries.contains( + DescriptorUtils.getClassFileName( + DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))); + } + + static class TestClass { + public static void main(String[] args) { + System.out.println("Hello, world"); + } + } + + static class OtherTestClass { + public static void main(String[] args) { + TestClass.main(args); + } + } +}