Merge "Add tests for name clash while applying mappings."
diff --git a/build.gradle b/build.gradle
index 33d967f..5606a9e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -567,6 +567,7 @@
from repackageSources.outputs.files
from repackageDeps.outputs.files
configureRelocations(it)
+ exclude "META-INF/*.kotlin_module"
}
task r8WithoutDeps(type: ShadowJar) {
@@ -596,6 +597,7 @@
} else {
from sourceSets.main.output
}
+ exclude "META-INF/*.kotlin_module"
}
task R8NoManifestNoDeps(type: ShadowJar) {
@@ -619,6 +621,7 @@
} else {
from sourceSets.main.output
}
+ exclude "META-INF/*.kotlin_module"
}
task D8(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 3521429..364a794 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -161,7 +161,7 @@
String className = resource.getClassDescriptors().iterator().next();
String entryName = getClassFileName(className);
byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
- ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
+ ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 7b17ec8..c5f0a40 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
@@ -88,13 +90,14 @@
"Usage: disasm [options] <input-files>\n"
+ " where <input-files> are dex files\n"
+ " and options are:\n"
- + " --all # Include all information in disassembly.\n"
- + " --smali # Disassemble using smali syntax.\n"
- + " --ir # Print IR before and after optimization.\n"
- + " --pg-map <file> # Proguard map <file> for mapping names.\n"
- + " --output # Specify a file or directory to write to.\n"
- + " --version # Print the version of r8.\n"
- + " --help # Print this message.";
+ + " --all # Include all information in disassembly.\n"
+ + " --smali # Disassemble using smali syntax.\n"
+ + " --ir # Print IR before and after optimization.\n"
+ + " --pg-map <file> # Proguard map <file> for mapping names.\n"
+ + " --pg-map-charset <charset> # Charset for Proguard map file.\n"
+ + " --output # Specify a file or directory to write to.\n"
+ + " --version # Print the version of r8.\n"
+ + " --help # Print this message.";
private final boolean allInfo;
private final boolean useSmali;
@@ -127,6 +130,18 @@
builder.setUseIr(true);
} else if (arg.equals("--pg-map")) {
builder.setProguardMapFile(Paths.get(args[++i]));
+ } else if (arg.equals("--pg-map-charset")) {
+ String charset = args[++i];
+ try {
+ Charset.forName(charset);
+ } catch (UnsupportedCharsetException e) {
+ builder.getReporter().error(
+ new StringDiagnostic(
+ "Unsupported charset: " + charset + "." + System.lineSeparator()
+ + "Supported charsets are: "
+ + String.join(", ", Charset.availableCharsets().keySet()),
+ CommandLineOrigin.INSTANCE));
+ }
} else if (arg.equals("--output")) {
String outputPath = args[++i];
builder.setOutputPath(Paths.get(outputPath));
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index fd85c25..c9f2032 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -70,6 +70,12 @@
return extractMarker(app);
}
+ public static Collection<Marker> extractMarkerFromClassProgramData(byte[] data)
+ throws IOException, ExecutionException {
+ AndroidApp app = AndroidApp.builder().addClassProgramData(data, Origin.unknown()).build();
+ return extractMarker(app);
+ }
+
private static void addDexResources(AndroidApp.Builder appBuilder, Path file)
throws IOException, ResourceException {
if (FileUtils.isVDexFile(file)) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 30b57da..75b0c81 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.AppliedGraphLens;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
@@ -194,6 +195,7 @@
new CfApplicationWriter(
application,
options,
+ marker,
deadCode,
graphLense,
namingLens,
@@ -485,6 +487,13 @@
timing.end();
}
+ // At this point all code has been mapped according to the graph lens. We cannot remove the
+ // graph lens entirely, though, since it is needed for mapping all field and method signatures
+ // back to the original program.
+ timing.begin("AppliedGraphLens construction");
+ appView.setGraphLense(new AppliedGraphLens(appView, application.classes()));
+ timing.end();
+
if (options.printCfg) {
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
System.out.print(printer.toString());
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 83d115d..4d27b8a 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.5.1-dev";
+ public static final String LABEL = "1.5.3-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 6bebb0d..8c79ea1 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -243,7 +243,7 @@
stackValue, constant.asConstString().getValue(), ThrowingInfo.NO_THROW);
} else if (constant.isDexItemBasedConstString()) {
return new DexItemBasedConstString(
- stackValue, constant.asDexItemBasedConstString().getItem());
+ stackValue, constant.asDexItemBasedConstString().getItem(), ThrowingInfo.NO_THROW);
} else if (constant.isConstClass()) {
return new ConstClass(stackValue, constant.asConstClass().getValue());
} else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index c38cf60..0467fcd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -49,7 +49,7 @@
@Override
public boolean canThrow() {
- // The ldc instruction may throw in Java bytecode.
+ // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
return true;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index f0c9636..8ba58b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -57,7 +57,7 @@
@Override
public boolean canThrow() {
- // The ldc instruction may throw in Java bytecode.
+ // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a45064e..e415b0f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -119,8 +119,10 @@
// For mapping invoke virtual instruction to target methods.
public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
- Set<DexEncodedMethod> result = new HashSet<>();
- // First add the target for receiver type method.type.
+ if (method.holder.isArrayType()) {
+ assert method.name == dexItemFactory.cloneMethodName;
+ return null;
+ }
DexClass root = definitionFor(method.holder);
if (root == null) {
// type specified in method does not have a materialized class.
@@ -131,6 +133,8 @@
// This will fail at runtime.
return null;
}
+ // First add the target for receiver type method.type.
+ Set<DexEncodedMethod> result = new HashSet<>();
topTargets.forEachTarget(result::add);
// Add all matching targets from the subclass hierarchy.
for (DexType type : subtypes(method.holder)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
new file mode 100644
index 0000000..d2387f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2019, 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.graph;
+
+import com.android.tools.r8.ir.code.Invoke;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A graph lens that will not lead to any code rewritings in the {@link
+ * com.android.tools.r8.ir.conversion.LensCodeRewriter}, or parameter removals in the {@link
+ * com.android.tools.r8.ir.conversion.IRBuilder}.
+ *
+ * <p>The mappings from the original program to the generated program are kept, though.
+ */
+public class AppliedGraphLens extends GraphLense {
+
+ private final AppView<? extends AppInfo> appView;
+
+ private final BiMap<DexType, DexType> originalTypeNames = HashBiMap.create();
+ private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
+ private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+ private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
+ new IdentityHashMap<>();
+
+ public AppliedGraphLens(
+ AppView<? extends AppInfoWithSubtyping> appView, List<DexProgramClass> classes) {
+ this.appView = appView;
+
+ for (DexProgramClass clazz : classes) {
+ // Record original type names.
+ {
+ DexType type = clazz.type;
+ if (appView.verticallyMergedClasses() != null
+ && !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
+ DexType original = appView.graphLense().getOriginalType(type);
+ if (original != type) {
+ DexType existing = originalTypeNames.forcePut(type, original);
+ assert existing == null;
+ }
+ }
+ }
+
+ // Record original field signatures.
+ for (DexEncodedField encodedField : clazz.fields()) {
+ DexField field = encodedField.field;
+ DexField original = appView.graphLense().getOriginalFieldSignature(field);
+ if (original != field) {
+ DexField existing = originalFieldSignatures.forcePut(field, original);
+ assert existing == null;
+ }
+ }
+
+ // Record original method signatures.
+ for (DexEncodedMethod encodedMethod : clazz.methods()) {
+ DexMethod method = encodedMethod.method;
+ DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
+ if (original != method) {
+ DexMethod existing = originalMethodSignatures.inverse().get(original);
+ if (existing == null) {
+ originalMethodSignatures.put(method, original);
+ } else {
+ DexMethod renamed = getRenamedMethodSignature(original);
+ if (renamed == existing) {
+ originalMethodSignaturesForBridges.put(method, original);
+ } else {
+ originalMethodSignatures.forcePut(method, original);
+ originalMethodSignaturesForBridges.put(existing, original);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public DexType getOriginalType(DexType type) {
+ return originalTypeNames.getOrDefault(type, type);
+ }
+
+ @Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ return originalFieldSignatures.getOrDefault(field, field);
+ }
+
+ @Override
+ public DexMethod getOriginalMethodSignature(DexMethod method) {
+ if (originalMethodSignaturesForBridges.containsKey(method)) {
+ return originalMethodSignaturesForBridges.get(method);
+ }
+ return originalMethodSignatures.getOrDefault(method, method);
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ return originalFieldSignatures.inverse().getOrDefault(originalField, originalField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ return originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod);
+ }
+
+ @Override
+ public DexType lookupType(DexType type) {
+ if (appView.verticallyMergedClasses() != null
+ && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
+ return lookupType(appView.verticallyMergedClasses().getTargetFor(type));
+ }
+ return originalTypeNames.inverse().getOrDefault(type, type);
+ }
+
+ @Override
+ public GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexMethod context, Invoke.Type type) {
+ return new GraphLenseLookupResult(method, type);
+ }
+
+ @Override
+ public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+ return RewrittenPrototypeDescription.none();
+ }
+
+ @Override
+ public DexField lookupField(DexField field) {
+ return field;
+ }
+
+ @Override
+ public boolean isContextFreeForMethods() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 755402d..2042392 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -267,7 +267,8 @@
encodedMethod,
graphLense.getOriginalMethodSignature(encodedMethod.method),
callerPosition,
- origin);
+ origin,
+ options.getInternalOutputMode());
return new IRBuilder(encodedMethod, appInfo, source, options, origin, generator, graphLense)
.build(context);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e02869d..f8fb8e1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -197,7 +197,15 @@
graphLense.getOriginalMethodSignature(encodedMethod.method),
null,
appInfo);
- IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options, origin);
+ IRBuilder builder =
+ new IRBuilder(
+ encodedMethod,
+ appInfo,
+ source,
+ options,
+ origin,
+ new ValueNumberGenerator(),
+ graphLense);
return builder.build(encodedMethod);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 44294db..32d3c37 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -812,7 +812,6 @@
public static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
public static boolean UNKNOWN_RETURNS_CONSTANT = false;
public static int UNKNOWN_RETURNED_CONSTANT = -1;
- public static boolean NOT_PUBLICZED = false;
public static boolean DOES_NOT_USE_IDNETIFIER_NAME_STRING = false;
public static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
public static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
@@ -932,7 +931,6 @@
DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NORMALLY;
private boolean returnsConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT;
private long returnedConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT;
- private boolean publicized = DefaultOptimizationInfoImpl.NOT_PUBLICZED;
private InlinePreference inlining = InlinePreference.Default;
private boolean useIdentifierNameString =
DefaultOptimizationInfoImpl.DOES_NOT_USE_IDNETIFIER_NAME_STRING;
@@ -978,7 +976,6 @@
neverReturnsNormally = template.neverReturnsNormally;
returnsConstant = template.returnsConstant;
returnedConstant = template.returnedConstant;
- publicized = template.publicized;
inlining = template.inlining;
useIdentifierNameString = template.useIdentifierNameString;
checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
@@ -1165,16 +1162,6 @@
}
@Override
- public void markPublicized() {
- publicized = true;
- }
-
- @Override
- public void unsetPublicized() {
- publicized = false;
- }
-
- @Override
public void markUseIdentifierNameString() {
useIdentifierNameString = true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 09094aa..1e125d9 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -45,6 +45,7 @@
@Override
public DexClass definitionFor(DexType type) {
+ assert type.isClassType() : "Cannot lookup definition for type: " + type;
DexClass result = programClasses.get(type);
if (result == null) {
result = libraryClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 3f06bbc..11d9730 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -74,6 +74,7 @@
private int argumentIndex = -1;
private boolean isAlwaysNull = false;
+ private DexType type = null;
public Builder setArgumentIndex(int argumentIndex) {
this.argumentIndex = argumentIndex;
@@ -85,18 +86,26 @@
return this;
}
+ public Builder setType(DexType type) {
+ this.type = type;
+ return this;
+ }
+
public RemovedArgumentInfo build() {
assert argumentIndex >= 0;
- return new RemovedArgumentInfo(argumentIndex, isAlwaysNull);
+ assert type != null;
+ return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type);
}
}
private final int argumentIndex;
private final boolean isAlwaysNull;
+ private final DexType type;
- private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull) {
+ private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) {
this.argumentIndex = argumentIndex;
this.isAlwaysNull = isAlwaysNull;
+ this.type = type;
}
public static Builder builder() {
@@ -107,6 +116,10 @@
return argumentIndex;
}
+ public DexType getType() {
+ return type;
+ }
+
public boolean isAlwaysNull() {
return isAlwaysNull;
}
@@ -117,7 +130,7 @@
public RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
return this.argumentIndex != argumentIndex
- ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull)
+ ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
: this;
}
}
@@ -694,8 +707,8 @@
* <p>Subclasses can override the lookup methods.
*
* <p>For method mapping where invocation type can change just override {@link
- * #mapInvocationType(DexMethod, DexMethod, DexMethod, Type)} if the default name mapping applies,
- * and only invocation type might need to change.
+ * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
+ * invocation type might need to change.
*/
public static class NestedGraphLense extends GraphLense {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 55ece50..3170b63 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.FieldSignatureEquivalence;
@@ -110,6 +111,22 @@
reader.accept(
new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
parsingOptions);
+
+ // Read marker.
+ if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
+ && reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
+ try {
+ Object maybeMarker =
+ reader.readConst(
+ CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
+ new char[reader.getMaxStringLength()]);
+ if (maybeMarker instanceof String) {
+ application.getFactory().createString((String) maybeMarker);
+ }
+ } catch (IllegalArgumentException e) {
+ // Ignore if the type of the constant is not something readConst() allows.
+ }
+ }
}
private static int cleanAccessFlags(int access) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 6dea825..b236428 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -42,9 +42,7 @@
@Override
public DexClass definitionFor(DexType type) {
- if (type == null) {
- return null;
- }
+ assert type.isClassType() : "Cannot lookup definition for type: " + type;
DexClass clazz = programClasses.get(type);
if (clazz == null && classpathClasses != null) {
clazz = classpathClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
index aefd2a8..8751816 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
@@ -42,8 +42,4 @@
void unsetForceInline();
void markNeverInline();
-
- void markPublicized();
-
- void unsetPublicized();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 53a6245..75a9726 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -78,7 +78,7 @@
return true;
}
- if (valueType.isReference()) {
+ if (fieldType.isClassType() && valueType.isReference()) {
// Interface types are treated like Object according to the JVM spec.
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
DexClass clazz = appInfo.definitionFor(instruction.getField().type);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index bd57f29..7610f9c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -91,6 +91,9 @@
// A const-class instruction can be dead code only if the resulting program is known to contain
// the class mentioned.
DexType baseType = clazz.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return true;
+ }
DexClass holder = appInfo.definitionFor(baseType);
return holder != null && holder.isProgramClass();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 04e9a1b..692dba2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -98,6 +98,9 @@
@Override
public boolean instructionInstanceCanThrow() {
+ if (throwingInfo == ThrowingInfo.NO_THROW) {
+ return false;
+ }
// The const-string instruction can be a throwing instruction in DEX, if decode() fails.
try {
value.toString();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index fa4e6c5..c3b8f80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
@@ -20,22 +21,27 @@
private final DexReference item;
private final ClassNameComputationInfo classNameComputationInfo;
+ private final ThrowingInfo throwingInfo;
- public DexItemBasedConstString(Value dest, DexReference item) {
- this(dest, item, ClassNameComputationInfo.none());
+ public DexItemBasedConstString(Value dest, DexReference item, ThrowingInfo throwingInfo) {
+ this(dest, item, throwingInfo, ClassNameComputationInfo.none());
}
public DexItemBasedConstString(
- Value dest, DexReference item, ClassNameComputationInfo classNameComputationInfo) {
+ Value dest,
+ DexReference item,
+ ThrowingInfo throwingInfo,
+ ClassNameComputationInfo classNameComputationInfo) {
super(dest);
dest.markNeverNull();
this.item = item;
this.classNameComputationInfo = classNameComputationInfo;
+ this.throwingInfo = throwingInfo;
}
public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
return new DexItemBasedConstString(
- newValue, original.getItem(), original.classNameComputationInfo);
+ newValue, original.getItem(), original.throwingInfo, original.classNameComputationInfo);
}
public DexReference getItem() {
@@ -88,7 +94,7 @@
@Override
public boolean instructionTypeCanThrow() {
- return true;
+ return throwingInfo == ThrowingInfo.CAN_THROW;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index a852d28..d5f86fa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -105,8 +105,8 @@
}
@Override
- public InlineAction computeInlining(InliningOracle decider, DexType inocationContext) {
- return decider.computeForInvokeStatic(this, inocationContext);
+ public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+ return decider.computeForInvokeStatic(this, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index a745659..2c10fbd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.conversion.CfState.Snapshot;
import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOutputMode;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -196,13 +197,15 @@
private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
private final CanonicalPositions canonicalPositions;
+ private final InternalOutputMode internalOutputMode;
public CfSourceCode(
CfCode code,
DexEncodedMethod method,
DexMethod originalMethod,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ InternalOutputMode internalOutputMode) {
this.code = code;
this.method = method;
this.origin = origin;
@@ -218,6 +221,7 @@
}
this.state = new CfState(origin);
canonicalPositions = new CanonicalPositions(callerPosition, cfPositionCount, originalMethod);
+ this.internalOutputMode = internalOutputMode;
}
@Override
@@ -249,8 +253,9 @@
// Utility method that treats constant strings as not throwing in the case of having CF output.
// This is the only instruction that differ in throwing between DEX and CF. If we find more
// consider rewriting CfInstruction.canThrow to take in options.
- private boolean canThrowHelper(IRBuilder builder, CfInstruction instruction) {
- if (builder.isGeneratingClassFiles() && instruction.isConstString()) {
+ private boolean canThrowHelper(CfInstruction instruction) {
+ if (internalOutputMode.isGeneratingClassFiles()
+ && (instruction.isConstString() || instruction.isDexItemBasedConstString())) {
return false;
}
return instruction.canThrow();
@@ -259,7 +264,8 @@
@Override
public int traceInstruction(int instructionIndex, IRBuilder builder) {
CfInstruction instruction = code.getInstructions().get(instructionIndex);
- if (canThrowHelper(builder, instruction)) {
+ assert builder.isGeneratingClassFiles() == internalOutputMode.isGeneratingClassFiles();
+ if (canThrowHelper(instruction)) {
TryHandlerList tryHandlers = getTryHandlers(instructionIndex);
if (!tryHandlers.isEmpty()) {
// Ensure the block starts at the start of the try-range (don't enqueue, not a target).
@@ -433,7 +439,7 @@
assert currentBlockInfo != null;
setLocalVariableLists();
- if (canThrowHelper(builder, instruction)) {
+ if (canThrowHelper(instruction)) {
Snapshot exceptionTransfer =
state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
for (int target : currentBlockInfo.exceptionalSuccessors) {
@@ -632,7 +638,7 @@
@Override
public boolean verifyCurrentInstructionCanThrow() {
- return code.getInstructions().get(currentInstructionIndex).canThrow();
+ return canThrowHelper(code.getInstructions().get(currentInstructionIndex));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 881b255..4738403 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -38,9 +38,9 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.CanonicalPositions;
@@ -50,14 +50,14 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
public class DexSourceCode implements SourceCode {
private final DexCode code;
- private final MethodAccessFlags accessFlags;
- private final DexProto proto;
+ private final DexEncodedMethod method;
// Mapping from instruction offset to instruction index in the DexCode instruction array.
private final Map<Integer, Integer> offsetToInstructionIndex = new HashMap<>();
@@ -73,8 +73,6 @@
private Position currentPosition = null;
private final CanonicalPositions canonicalPositions;
- private final List<TypeLatticeElement> argumentTypes;
-
private List<DexDebugEntry> debugEntries = null;
// In case of inlining the position of the invoke in the caller.
private final DexMethod originalMethod;
@@ -86,11 +84,8 @@
Position callerPosition,
AppInfo appInfo) {
this.code = code;
- this.proto = method.method.proto;
- this.accessFlags = method.accessFlags;
+ this.method = method;
this.originalMethod = originalMethod;
-
- argumentTypes = computeArgumentTypes(appInfo);
DexDebugInfo info = code.getDebugInfo();
if (info != null) {
debugEntries = info.computeEntries(originalMethod);
@@ -149,16 +144,46 @@
if (code.incomingRegisterSize == 0) {
return;
}
+
+ RemovedArgumentsInfo removedArgumentsInfo = builder.prototypeChanges.getRemovedArgumentsInfo();
+ ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator();
+ RemovedArgumentInfo nextRemovedArgument =
+ removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+
// Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
// block.
+ int argumentIndex = 0;
+
int register = code.registerSize - code.incomingRegisterSize;
- if (!accessFlags.isStatic()) {
+ if (!method.isStatic()) {
builder.addThisArgument(register);
+ ++argumentIndex;
++register;
}
- for (TypeLatticeElement typeLattice : argumentTypes) {
- builder.addNonThisArgument(register, typeLattice);
- register += typeLattice.requiredRegisters();
+
+ int numberOfArguments =
+ method.method.proto.parameters.values.length
+ + removedArgumentsInfo.numberOfRemovedArguments()
+ + (method.isStatic() ? 0 : 1);
+
+ for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) {
+ TypeLatticeElement type;
+ if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) {
+ type =
+ TypeLatticeElement.fromDexType(
+ nextRemovedArgument.getType(), Nullability.maybeNull(), builder.getAppInfo());
+ builder.addConstantOrUnusedArgument(register);
+ nextRemovedArgument =
+ removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+ } else {
+ type =
+ TypeLatticeElement.fromDexType(
+ method.method.proto.parameters.values[usedArgumentIndex++],
+ Nullability.maybeNull(),
+ builder.getAppInfo());
+ builder.addNonThisArgument(register, type);
+ }
+ register += type.requiredRegisters();
}
builder.flushArgumentInstructions();
}
@@ -310,14 +335,6 @@
arrayFilledDataPayloadResolver.getData(payloadOffset));
}
- private List<TypeLatticeElement> computeArgumentTypes(AppInfo appInfo) {
- List<TypeLatticeElement> types = new ArrayList<>(proto.parameters.size());
- for (DexType type : proto.parameters.values) {
- types.add(TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appInfo));
- }
- return types;
- }
-
private boolean isInvoke(Instruction dex) {
return dex instanceof InvokeDirect
|| dex instanceof InvokeDirectRange
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 045ba3e..31b0ec6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -396,7 +396,7 @@
private DexEncodedMethod context;
private final AppInfo appInfo;
private final Origin origin;
- private RewrittenPrototypeDescription prototypeChanges;
+ final RewrittenPrototypeDescription prototypeChanges;
private ListIterator<RemovedArgumentInfo> removedArgumentsIterator;
private int argumentCount = 0;
@@ -894,15 +894,8 @@
DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
addInstruction(new Argument(value));
- } else if (removedArgumentInfo.isAlwaysNull()) {
- if (pendingArgumentInstructions == null) {
- pendingArgumentInstructions = new ArrayList<>();
- }
- DebugLocalInfo local = getOutgoingLocal(register);
- Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
- pendingArgumentInstructions.add(new ConstNumber(value, 0));
} else {
- assert removedArgumentInfo.isNeverUsed();
+ handleConstantOrUnusedArgument(register, removedArgumentInfo);
}
}
@@ -918,6 +911,25 @@
}
}
+ public void addConstantOrUnusedArgument(int register) {
+ handleConstantOrUnusedArgument(register, getRemovedArgumentInfo());
+ }
+
+ private void handleConstantOrUnusedArgument(
+ int register, RemovedArgumentInfo removedArgumentInfo) {
+ assert removedArgumentInfo != null;
+ if (removedArgumentInfo.isAlwaysNull()) {
+ if (pendingArgumentInstructions == null) {
+ pendingArgumentInstructions = new ArrayList<>();
+ }
+ DebugLocalInfo local = getOutgoingLocal(register);
+ Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
+ pendingArgumentInstructions.add(new ConstNumber(value, 0));
+ } else {
+ assert removedArgumentInfo.isNeverUsed();
+ }
+ }
+
public void flushArgumentInstructions() {
if (pendingArgumentInstructions != null) {
pendingArgumentInstructions.forEach(this::addInstruction);
@@ -1135,11 +1147,14 @@
add(instruction);
}
+ private ThrowingInfo throwingInfoForConstStrings() {
+ return options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+ }
+
public void addConstString(int dest, DexString string) {
TypeLatticeElement typeLattice =
TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
- ThrowingInfo throwingInfo =
- options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+ ThrowingInfo throwingInfo = throwingInfoForConstStrings();
add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string, throwingInfo));
}
@@ -1147,8 +1162,9 @@
assert method.getOptimizationInfo().useIdentifierNameString();
TypeLatticeElement typeLattice =
TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
- Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
- DexItemBasedConstString instruction = new DexItemBasedConstString(out, item);
+ ThrowingInfo throwingInfo = throwingInfoForConstStrings();
+ Value out = writeRegister(dest, typeLattice, throwingInfo);
+ DexItemBasedConstString instruction = new DexItemBasedConstString(out, item, throwingInfo);
add(instruction);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index ed1994b..5bf9154 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -194,7 +194,7 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
: null;
- this.stringOptimizer = new StringOptimizer(appInfo, options);
+ this.stringOptimizer = new StringOptimizer(appInfo, options.getInternalOutputMode());
this.enableWholeProgramOptimizations = appView != null;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
@@ -1011,7 +1011,8 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code);
- codeRewriter.redundantConstNumberRemoval(code);
+ // TODO(b/123284765) This produces a runtime-crash in Q. Activate again when fixed.
+ // codeRewriter.redundantConstNumberRemoval(code);
new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
if (options.testing.invertConditionals) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 4e91c62..a9a571f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -151,6 +151,10 @@
InvokeStatic invokeStatic = instruction.asInvokeStatic();
DexMethod method = invokeStatic.getInvokedMethod();
DexClass clazz = findDefinitionFor(method.holder);
+ if (Java8MethodRewriter.hasJava8MethodRewritePrefix(method.holder)) {
+ // We did not create this code yet, but it will not require rewriting.
+ continue;
+ }
if (clazz == null) {
// NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
// exception but we can not report it as error since it can also be the intended
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
index c0a7569..9e2e9ab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -84,6 +84,10 @@
return null;
}
+ public static boolean hasJava8MethodRewritePrefix(DexType clazz) {
+ return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
+ }
+
public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
if (holders.isEmpty()) {
return;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index e1e534e..568e021 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -65,7 +65,7 @@
twrUtilityClass, twrCloseResourceProto, factory.twrCloseResourceMethodName);
}
- // Rewrites calls to $closeResource() method. Can me invoked concurrently.
+ // Rewrites calls to $closeResource() method. Can be invoked concurrently.
public void rewriteMethodCode(IRCode code) {
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
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 7758d83..fbdb9f3 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
@@ -2112,6 +2112,16 @@
return false;
}
+ // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+ // elimination may lead to verification errors. See b/123269162.
+ if (options.canHaveArtCheckCastVerifierBug()) {
+ if (inValue.getTypeLattice().isNullType()
+ && castType.isArrayType()
+ && castType.toBaseType(dexItemFactory).isFloatType()) {
+ return false;
+ }
+ }
+
// We might see chains of casts on subtypes. It suffices to cast to the lowest subtype,
// as that will fail if a cast on a supertype would have failed.
Predicate<Instruction> isCheckcastToSubtype =
@@ -2156,6 +2166,9 @@
private boolean isTypeInaccessibleInCurrentContext(DexType type, DexEncodedMethod context) {
DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return false;
+ }
DexClass clazz = definitionFor(baseType);
if (clazz == null) {
// Conservatively say yes.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c1364f6..e41593f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -298,7 +298,11 @@
removedArgumentsInfo = new ArrayList<>();
}
removedArgumentsInfo.add(
- RemovedArgumentInfo.builder().setArgumentIndex(i + offset).setIsAlwaysNull().build());
+ RemovedArgumentInfo.builder()
+ .setArgumentIndex(i + offset)
+ .setIsAlwaysNull()
+ .setType(type)
+ .build());
}
}
return removedArgumentsInfo != null
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 061907f..aae8429 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -208,8 +208,8 @@
return null;
}
assert method.getCode().getOwner() == method;
- int argumentCount =
- method.method.proto.parameters.size() + (method.accessFlags.isStatic() ? 0 : 1);
+ int offset = method.accessFlags.isStatic() ? 0 : 1;
+ int argumentCount = method.method.proto.parameters.size() + offset;
// TODO(65810338): Implement for virtual methods as well.
if (method.accessFlags.isPrivate()
|| method.accessFlags.isStatic()
@@ -224,9 +224,13 @@
BitSet used = collector.getUsedArguments();
if (used.cardinality() < argumentCount) {
List<RemovedArgumentInfo> unused = new ArrayList<>();
- for (int i = 0; i < argumentCount; i++) {
- if (!used.get(i)) {
- unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
+ for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
+ if (!used.get(argumentIndex)) {
+ unused.add(
+ RemovedArgumentInfo.builder()
+ .setArgumentIndex(argumentIndex)
+ .setType(method.method.proto.parameters.values[argumentIndex - offset])
+ .build());
}
}
return new RemovedArgumentsInfo(unused);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 7375303..cdf37c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -113,6 +113,9 @@
eligibleClass =
root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+ if (!eligibleClass.isClassType()) {
+ return false;
+ }
eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
if (eligibleClassDefinition == null && lambdaRewriter != null) {
// Check if the class is synthesized for a desugared lambda
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 0ff9331..fd326d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -32,7 +32,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
import com.google.common.annotations.VisibleForTesting;
import java.util.Set;
import java.util.function.BiFunction;
@@ -44,11 +44,11 @@
private final DexItemFactory factory;
private final ThrowingInfo throwingInfo;
- public StringOptimizer(AppInfo appInfo, InternalOptions options) {
+ public StringOptimizer(AppInfo appInfo, InternalOutputMode outputMode) {
this.appInfo = appInfo;
this.factory = appInfo.dexItemFactory;
- throwingInfo = options.isGeneratingClassFiles()
- ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+ this.throwingInfo =
+ outputMode.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
}
// int String#length()
@@ -194,8 +194,12 @@
String name = null;
if (invokedMethod == factory.classMethods.getName) {
if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
- deferred = new DexItemBasedConstString(
- invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
+ deferred =
+ new DexItemBasedConstString(
+ invoke.outValue(),
+ baseType,
+ throwingInfo,
+ new ClassNameComputationInfo(NAME, arrayDepth));
} else {
name = computeClassName(descriptor, holder, NAME, arrayDepth);
}
@@ -218,6 +222,7 @@
new DexItemBasedConstString(
invoke.outValue(),
baseType,
+ throwingInfo,
new ClassNameComputationInfo(CANONICAL_NAME, arrayDepth));
} else {
name = computeClassName(descriptor, holder, CANONICAL_NAME, arrayDepth);
@@ -234,8 +239,12 @@
continue;
}
if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
- deferred = new DexItemBasedConstString(
- invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
+ deferred =
+ new DexItemBasedConstString(
+ invoke.outValue(),
+ baseType,
+ throwingInfo,
+ new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
} else {
name = computeClassName(descriptor, holder, SIMPLE_NAME, arrayDepth);
}
@@ -313,8 +322,8 @@
code.createValue(
TypeLatticeElement.stringClassType(appInfo, definitelyNotNull()),
invoke.getLocalInfo());
- ConstString nullString = new ConstString(
- nullStringValue, factory.createString("null"), throwingInfo);
+ ConstString nullString =
+ new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
it.replaceCurrentInstruction(nullString);
} else if (inType.nullability().isDefinitelyNotNull()
&& inType.isClassType()
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 14459fe..05120db 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.Code;
@@ -61,10 +62,15 @@
private static final boolean RUN_VERIFIER = false;
private static final boolean PRINT_CF = false;
+ // First item inserted into the constant pool is the marker string which generates an UTF8 to
+ // pool index #1 and a String entry to #2, referencing #1.
+ public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
+
private final DexApplication application;
private final GraphLense graphLense;
private final NamingLens namingLens;
private final InternalOptions options;
+ private final String markerString;
public final ProguardMapSupplier proguardMapSupplier;
public final String deadCode;
@@ -73,6 +79,7 @@
public CfApplicationWriter(
DexApplication application,
InternalOptions options,
+ Marker marker,
String deadCode,
GraphLense graphLense,
NamingLens namingLens,
@@ -82,6 +89,7 @@
this.graphLense = graphLense;
this.namingLens = namingLens;
this.options = options;
+ this.markerString = marker == null ? null : marker.toString();
this.proguardMapSupplier = proguardMapSupplier;
this.deadCode = deadCode;
this.proguardSeedsData = proguardSeedsData;
@@ -117,6 +125,10 @@
private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
ClassWriter writer = new ClassWriter(0);
+ if (markerString != null) {
+ int index = writer.newConst(markerString);
+ assert index == MARKER_STRING_CONSTANT_POOL_INDEX;
+ }
writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
int version = getClassFileVersion(clazz);
int access = clazz.accessFlags.getAsCfAccessFlags();
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 95fcfd2..0638816 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
@@ -87,6 +88,8 @@
if (!code.hasConstString) {
return;
}
+ final ThrowingInfo throwingInfo =
+ options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.NO_THROW;
DexType originHolder = code.method.method.getHolder();
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
@@ -131,7 +134,8 @@
iterator.previous();
// Prepare $decoupled just before $fieldPut
Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
- DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+ DexItemBasedConstString decoupled =
+ new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
decoupled.setPosition(fieldPut.getPosition());
// If the current block has catch handler, split into two blocks.
// Because const-string we're about to add is also a throwing instr, we need to split
@@ -191,7 +195,8 @@
iterator.previous();
// Prepare $decoupled just before $invoke
Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
- DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+ DexItemBasedConstString decoupled =
+ new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
decoupled.setPosition(invoke.getPosition());
changes[positionOfIdentifier] = newIn;
// If the current block has catch handler, split into two blocks.
@@ -238,7 +243,7 @@
// Prepare $decoupled just before $invoke
Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
DexItemBasedConstString decoupled =
- new DexItemBasedConstString(newIn, itemBasedString);
+ new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
decoupled.setPosition(invoke.getPosition());
changes[i] = newIn;
// If the current block has catch handler, split into two blocks.
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 cdf96c2..706620f 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -171,6 +171,11 @@
return null;
}
DexType holderType = classValue.getConstInstruction().asConstClass().getValue();
+ if (holderType.isArrayType()) {
+ // None of the fields or methods of an array type will be renamed, since they are all
+ // declared in the library. Hence there is no need to handle this case.
+ return null;
+ }
DexClass holder = appInfo.definitionFor(holderType);
if (holder == null) {
return null;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 9934ab2..5547315 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -318,7 +318,8 @@
throw new ParseException("Identifier expected");
}
nextCodePoint();
- while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
+ while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())
+ || IdentifierUtils.isQuestionMark(peekCodePoint())) {
nextCodePoint();
}
if (isInit) {
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 72fc987..d3f336f 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -133,7 +133,6 @@
// Although the current method became public, it surely has the single virtual target.
encodedMethod.method.setSingleVirtualMethodCache(
encodedMethod.method.getHolder(), encodedMethod);
- encodedMethod.getMutableOptimizationInfo().markPublicized();
return true;
}
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 adc6979..0761a71 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1460,7 +1460,10 @@
new RootSetBuilder(appView, rootSet.ifRules, options);
IfRuleEvaluator ifRuleEvaluator =
consequentSetBuilder.getIfRuleEvaluator(
- liveMethods.getItems(), liveFields.getItems(), executorService);
+ liveFields.getItems(),
+ liveMethods.getItems(),
+ targetedMethods.getItems(),
+ executorService);
ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
enqueueRootItems(consequentRootSet.noShrinking);
rootSet.neverInline.addAll(consequentRootSet.neverInline);
@@ -1736,6 +1739,14 @@
markInstantiated(holderClass.type, KeepReason.reflectiveUseIn(method));
}
markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
+ // Fields accessed by reflection is marked as both read and written.
+ if (encodedField.isStatic()) {
+ registerItemWithTargetAndContext(staticFieldsRead, encodedField.field, method);
+ registerItemWithTargetAndContext(staticFieldsWritten, encodedField.field, method);
+ } else {
+ registerItemWithTargetAndContext(instanceFieldsRead, encodedField.field, method);
+ registerItemWithTargetAndContext(instanceFieldsWritten, encodedField.field, method);
+ }
}
} else {
assert identifierItem.isDexMethod();
@@ -2414,6 +2425,10 @@
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
assert method != null;
assert refinedReceiverType.isSubtypeOf(method.holder, this);
+ if (method.holder.isArrayType()) {
+ assert method.name == dexItemFactory.cloneMethodName;
+ return null;
+ }
DexClass holder = definitionFor(method.holder);
if (holder == null || holder.isLibraryClass() || holder.isInterface()) {
return null;
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 543dcc4..71b3d53 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
-import com.google.common.collect.ImmutableSet;
import java.util.Set;
import java.util.function.Consumer;
@@ -61,35 +60,23 @@
boolean value = false;
}
- public static boolean hasReferencesOutside(
- AppInfoWithSubtyping appInfo, DexProgramClass clazz, Set<DexType> types) {
- BooleanBox result = new BooleanBox();
-
- new MainDexDirectReferenceTracer(appInfo, type -> {
- if (!types.contains(type)) {
- DexClass cls = appInfo.definitionFor(type);
- if (cls != null && !cls.isLibraryClass()) {
- result.value = true;
- }
- }
- }).run(ImmutableSet.of(clazz.type));
-
- return result.value;
- }
-
public static boolean hasReferencesOutsideFromCode(
AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
BooleanBox result = new BooleanBox();
- new MainDexDirectReferenceTracer(appInfo, type -> {
- if (!classes.contains(type)) {
- DexClass cls = appInfo.definitionFor(type);
- if (cls != null && !cls.isLibraryClass()) {
- result.value = true;
- }
- }
- }).runOnCode(method);
+ new MainDexDirectReferenceTracer(
+ appInfo,
+ type -> {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType() && !classes.contains(baseType)) {
+ DexClass cls = appInfo.definitionFor(baseType);
+ if (cls != null && !cls.isLibraryClass()) {
+ result.value = true;
+ }
+ }
+ })
+ .runOnCode(method);
return result.value;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 898d06c..203cf82 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -277,26 +277,31 @@
}
IfRuleEvaluator getIfRuleEvaluator(
- Set<DexEncodedMethod> liveMethods,
Set<DexEncodedField> liveFields,
+ Set<DexEncodedMethod> liveMethods,
+ Set<DexEncodedMethod> targetedMethods,
ExecutorService executorService) {
- return new IfRuleEvaluator(liveMethods, liveFields, executorService);
+ return new IfRuleEvaluator(liveFields, liveMethods, targetedMethods, executorService);
}
class IfRuleEvaluator {
- private final Set<DexEncodedMethod> liveMethods;
private final Set<DexEncodedField> liveFields;
+ private final Set<DexEncodedMethod> liveMethods;
+ private final Set<DexEncodedMethod> targetedMethods;
+
private final ExecutorService executorService;
private final List<Future<?>> futures = new ArrayList<>();
public IfRuleEvaluator(
- Set<DexEncodedMethod> liveMethods,
Set<DexEncodedField> liveFields,
+ Set<DexEncodedMethod> liveMethods,
+ Set<DexEncodedMethod> targetedMethods,
ExecutorService executorService) {
- this.liveMethods = liveMethods;
this.liveFields = liveFields;
+ this.liveMethods = liveMethods;
+ this.targetedMethods = targetedMethods;
this.executorService = executorService;
}
@@ -391,7 +396,7 @@
filteredMembers,
targetClass.methods(
m ->
- liveMethods.contains(m)
+ (liveMethods.contains(m) || targetedMethods.contains(m))
&& appView.graphLense().getOriginalMethodSignature(m.method).getHolder()
== sourceClass.type));
@@ -843,6 +848,9 @@
}
private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRule context) {
+ if (type.isVoidType()) {
+ return;
+ }
if (type.isArrayType()) {
type = type.toBaseType(application.dexItemFactory);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index d793e3b..15dcafa 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -336,15 +336,16 @@
}
private void markTypeAsPinned(DexType type, AbortReason reason) {
- if (appInfo.isPinned(type)) {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (!baseType.isClassType() || appInfo.isPinned(baseType)) {
// We check for the case where the type is pinned according to appInfo.isPinned,
// so we only need to add it here if it is not the case.
return;
}
- DexClass clazz = appInfo.definitionFor(type);
+ DexClass clazz = appInfo.definitionFor(baseType);
if (clazz != null && clazz.isProgramClass()) {
- boolean changed = pinnedTypes.add(type);
+ boolean changed = pinnedTypes.add(baseType);
if (Log.ENABLED) {
if (changed && isMergeCandidate(clazz.asProgramClass(), ImmutableSet.of())) {
@@ -743,10 +744,6 @@
return false;
}
- private boolean hasReferencesOutside(DexProgramClass clazz, Set<DexType> types) {
- return MainDexDirectReferenceTracer.hasReferencesOutside(appInfo, clazz, types);
- }
-
private void mergeClassIfPossible(DexProgramClass clazz) {
if (!mergeCandidates.contains(clazz)) {
return;
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 69d65ea..2276790 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -150,7 +150,7 @@
private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
try {
- ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
+ ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.DEFLATED);
} catch (IOException e) {
handleIOException(e, handler);
}
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 3015132..3997982 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -898,4 +898,12 @@
public boolean canHaveExceptionTypeBug() {
return minApiLevel < AndroidApiLevel.Q.getLevel();
}
+
+ // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
+ // aget instruction. We therefore need to be careful when performing trivial check-cast
+ // elimination of check-cast instructions where the value being cast is the constant null.
+ // See b/123269162.
+ public boolean canHaveArtCheckCastVerifierBug() {
+ return minApiLevel < AndroidApiLevel.J.getLevel();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 3b2b878..1cf61c0 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import java.util.List;
@@ -68,6 +69,7 @@
private static boolean assumeClassesAreEqual(DexProgramClass a) {
return LambdaRewriter.hasLambdaClassPrefix(a.type)
+ || Java8MethodRewriter.hasJava8MethodRewritePrefix(a.type)
|| InterfaceMethodRewriter.hasDispatchClassSuffix(a.type)
|| a.type.descriptor.toString().equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR);
}
diff --git a/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
new file mode 100644
index 0000000..4a8b5be
--- /dev/null
+++ b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, 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 classmerging;
+
+import java.lang.reflect.Method;
+
+public class PinnedArrayParameterTypesTest {
+
+ public static void main(String[] args) throws Exception {
+ for (Method method : TestClass.class.getMethods()) {
+ if (method.getName().equals("method")) {
+ Class<?> parameterType = method.getParameterTypes()[0];
+
+ // Should print classmerging.PinnedArrayParameterTypesTest$Interface when
+ // -keepparameternames is used.
+ System.out.println(parameterType.getName());
+
+ method.invoke(null, new Object[]{ new InterfaceImpl[]{ new InterfaceImpl() } });
+ break;
+ }
+ }
+ }
+
+ public interface Interface {
+
+ void foo();
+ }
+
+ public static class InterfaceImpl implements Interface {
+
+ @Override
+ public void foo() {
+ System.out.println("In InterfaceImpl.foo()");
+ }
+ }
+
+ public static class TestClass {
+
+ // This method has been kept explicitly by a keep rule. Therefore, since -keepparameternames is
+ // used, Interface must not be merged into InterfaceImpl.
+ public static void method(Interface[] obj) {
+ obj[0].foo();
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index e3e0006..c75058d 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -43,6 +43,12 @@
-keep public class classmerging.PinnedParameterTypesTest$TestClass {
public static void method(...);
}
+-keep public class classmerging.PinnedArrayParameterTypesTest {
+ public static void main(...);
+}
+-keep public class classmerging.PinnedArrayParameterTypesTest$TestClass {
+ public static void method(...);
+}
-keep public class classmerging.ProguardFieldMapTest {
public static void main(...);
}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 9118fda..84b6675 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -89,10 +90,11 @@
Assert.assertNotNull(mainClassDescriptor);
for (String descriptor : descriptors) {
// classes are either lambda classes used by the main class, companion classes of the main
- // interface or the main class/interface
+ // interface, the main class/interface, or for JDK9, desugaring of try-with-resources.
Assert.assertTrue(descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
|| descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
|| descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
+ || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
|| descriptor.equals(mainClassDescriptor));
}
String classDescriptor =
@@ -188,11 +190,11 @@
}
}
- ProgramResource mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
+ AndroidApp mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
return mergeClassFiles(dexFiles, out, OutputMode.DexIndexed);
}
- ProgramResource mergeClassFiles(
+ AndroidApp mergeClassFiles(
List<ProgramResource> dexFiles, Path outputPath, OutputMode outputMode) throws Throwable {
Builder builder = D8Command.builder();
for (ProgramResource dexFile : dexFiles) {
@@ -213,7 +215,7 @@
try {
AndroidApp app = ToolHelper.runD8(builder, this::combinedOptionConsumer);
assert app.getDexProgramResourcesForTesting().size() == 1;
- return app.getDexProgramResourcesForTesting().get(0);
+ return app;
} catch (Unimplemented | CompilationError | InternalCompilerError re) {
throw re;
} catch (RuntimeException re) {
@@ -247,13 +249,16 @@
Assert.assertArrayEquals(readResource(entry.getValue()), readResource(otherResource));
}
- ProgramResource mergedFromCompiledSeparately =
+ AndroidApp mergedFromCompiledSeparately =
test.mergeClassFiles(Lists.newArrayList(compiledSeparately.values()), null);
- ProgramResource mergedFromCompiledTogether =
+ AndroidApp mergedFromCompiledTogether =
test.mergeClassFiles(Lists.newArrayList(compiledTogether.values()), null);
+
+ // TODO(b/123504206): Add a main method and test the output runs.
+
Assert.assertArrayEquals(
- readResource(mergedFromCompiledSeparately),
- readResource(mergedFromCompiledTogether));
+ readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+ readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
}
@Test
@@ -267,16 +272,24 @@
D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
- ProgramResource mergedFromCompiledSeparately =
+ AndroidApp mergedFromCompiledSeparately =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
- ProgramResource mergedFromCompiledTogether =
+ AndroidApp mergedFromCompiledTogether =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
+ Path out1 = temp.newFolder().toPath().resolve("out-together.zip");
+ mergedFromCompiledTogether.writeToZip(out1, OutputMode.DexIndexed);
+ ToolHelper.runArtNoVerificationErrors(out1.toString(), testPackage + "." + mainClass);
+
+ Path out2 = temp.newFolder().toPath().resolve("out-separate.zip");
+ mergedFromCompiledSeparately.writeToZip(out2, OutputMode.DexIndexed);
+ ToolHelper.runArtNoVerificationErrors(out2.toString(), testPackage + "." + mainClass);
+
Assert.assertArrayEquals(
- readResource(mergedFromCompiledSeparately),
- readResource(mergedFromCompiledTogether));
+ readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+ readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
}
@Test
@@ -290,16 +303,19 @@
D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
- ProgramResource mergedFromCompiledSeparately =
+ AndroidApp mergedFromCompiledSeparately =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
- ProgramResource mergedFromCompiledTogether =
+ AndroidApp mergedFromCompiledTogether =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
+ // TODO(b/123504206): This test throws an index out of bounds exception.
+ // Re-write or verify running fails in the expected way.
+
Assert.assertArrayEquals(
- readResource(mergedFromCompiledSeparately),
- readResource(mergedFromCompiledTogether));
+ readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+ readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 88d077b..37e4dec 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -3,13 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.D8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -17,6 +22,9 @@
extends TestCompilerBuilder<
D8Command, Builder, D8TestCompileResult, D8TestRunResult, D8TestBuilder> {
+ // Consider an in-order collection of both class and files on the classpath.
+ private List<Class<?>> classpathClasses = new ArrayList<>();
+
private D8TestBuilder(TestState state, Builder builder) {
super(state, builder, Backend.DEX);
}
@@ -34,6 +42,29 @@
D8TestCompileResult internalCompile(
Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
throws CompilationFailedException {
+ if (!classpathClasses.isEmpty()) {
+ Path cp;
+ try {
+ cp = getState().getNewTempFolder().resolve("cp.jar");
+ } catch (IOException e) {
+ throw builder.getReporter().fatalError("Failed to create temp file for classpath archive");
+ }
+ ArchiveConsumer archiveConsumer = new ArchiveConsumer(cp);
+ for (Class<?> classpathClass : classpathClasses) {
+ try {
+ archiveConsumer.accept(
+ ByteDataView.of(ToolHelper.getClassAsBytes(classpathClass)),
+ DescriptorUtils.javaTypeToDescriptor(classpathClass.getTypeName()),
+ builder.getReporter());
+ } catch (IOException e) {
+ builder
+ .getReporter()
+ .error("Failed to read bytes for classpath class: " + classpathClass.getTypeName());
+ }
+ }
+ archiveConsumer.finished(builder.getReporter());
+ builder.addClasspathFiles(cp);
+ }
ToolHelper.runD8(builder, optionsConsumer);
return new D8TestCompileResult(getState(), app.get());
}
@@ -43,7 +74,8 @@
}
public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
- return addClasspathFiles(getFilesForClasses(classes));
+ classpathClasses.addAll(classes);
+ return self();
}
public D8TestBuilder addClasspathFiles(Path... files) {
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 5a12e62..6a47607 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
@@ -15,8 +16,9 @@
public class ExtractMarkerTest {
@Test
- public void extractMarkerTest() throws CompilationFailedException {
+ public void extractMarkerTestDex() throws CompilationFailedException {
String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+ boolean[] testExecuted = {false};
D8.run(
D8Command.builder()
.addProgramFiles(Paths.get(classFile))
@@ -42,8 +44,46 @@
assertEquals(
CompilationMode.DEBUG.toString().toLowerCase(),
marker.getCompilationMode());
+ testExecuted[0] = true;
}
})
.build());
+ assertTrue(testExecuted[0]);
+ }
+
+ @Test
+ public void extractMarkerTestCf() throws CompilationFailedException {
+ String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+ boolean[] testExecuted = {false};
+ R8.run(
+ R8Command.builder()
+ .addProgramFiles(Paths.get(classFile))
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setProgramConsumer(
+ new ClassFileConsumer.ForwardingConsumer(null) {
+ @Override
+ public void accept(
+ ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+ Marker marker;
+ try {
+ Collection<Marker> markers =
+ ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+ assertEquals(1, markers.size());
+ marker = markers.iterator().next();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ assertEquals(Tool.R8, marker.getTool());
+ assertEquals(Version.LABEL, marker.getVersion());
+ assertEquals(
+ CompilationMode.DEBUG.toString().toLowerCase(),
+ marker.getCompilationMode());
+ testExecuted[0] = true;
+ }
+ })
+ .build());
+ assertTrue(testExecuted[0]);
}
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 42870fa..55730eb 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
public class R8TestRunResult extends TestRunResult<R8TestRunResult> {
@@ -50,6 +51,13 @@
return graphInspector.get();
}
+ public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
+ throws IOException, ExecutionException {
+ consumer.accept(graphInspector());
+ return self();
+ }
+
+
public String proguardMap() {
return proguardMap;
}
diff --git a/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
new file mode 100644
index 0000000..b43bb8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, 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;
+
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+class Tester {
+ public int foo() {
+ float[][][] fs = new float[1][2][3];
+ return fs.length;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(new Tester().foo());
+ }
+}
+
+// The DirectoryClasspathProvider asserts lookups are reference types which witnessed the issue.
+public class RegressionForPrimitiveDefinitionForLookup extends TestBase {
+
+ public final Class<Tester> CLASS = Tester.class;
+ public String EXPECTED = StringUtils.lines("1");
+
+ @Test
+ public void testWithArchiveClasspath() throws Exception {
+ testForD8()
+ .addClasspathClasses(CLASS)
+ .addProgramClasses(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithDirectoryClasspath() throws Exception {
+ testForD8()
+ .addClasspathFiles(ToolHelper.getClassPathForTests())
+ .addProgramClasses(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 688c91c..89561f8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -464,6 +464,29 @@
}
@Test
+ public void testPinnedArrayParameterTypes() throws Throwable {
+ String main = "classmerging.PinnedArrayParameterTypesTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("PinnedArrayParameterTypesTest.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$Interface.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$InterfaceImpl.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$TestClass.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.PinnedArrayParameterTypesTest",
+ "classmerging.PinnedArrayParameterTypesTest$Interface",
+ "classmerging.PinnedArrayParameterTypesTest$InterfaceImpl",
+ "classmerging.PinnedArrayParameterTypesTest$TestClass");
+ runTest(
+ main,
+ programFiles,
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"));
+ }
+
+ @Test
public void testProguardFieldMap() throws Throwable {
String main = "classmerging.ProguardFieldMapTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
index 5de7de0..b522155 100644
--- a/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
+++ b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
@@ -66,10 +66,9 @@
.compile();
CodeInspector inspector = result.inspector();
assertFalse(inspector.clazz(TestClass.class).clinit().isPresent());
-
MethodSubject main = inspector.clazz(TestClass.class).mainMethod();
- main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray());
- assertFalse(main.getMethod().getCode().asDexCode().toString().contains("NewArray"));
+ assertTrue(
+ main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray()));
runOnArt(result.app, TestClass.class.getName());
}
@@ -83,10 +82,8 @@
CodeInspector inspector = result.inspector();
MethodSubject clinit = inspector.clazz(TestClassWithCatch.class).clinit();
assertTrue(clinit.isPresent());
- // Ensure that our optimization does not hit, we should still have 4 Aput instructions.
- long aPutCount = Arrays.stream(clinit.getMethod().getCode().asDexCode().instructions)
- .filter(instruction -> instruction instanceof Aput)
- .count();
- assertEquals(4, aPutCount);
+ // Ensure that our optimization does not hit, we should still have 4 ArrayPut instructions.
+ long count = clinit.streamInstructions().filter(a -> a.isArrayPut()).count();
+ assertEquals(4, count);
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index fb61f45..1c0287e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -50,6 +51,7 @@
import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ObjectReferenceCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
@@ -153,6 +155,12 @@
}
protected final void runDebugTest(
+ DebugTestConfig config, Class<?> debuggeeClass, JUnit3Wrapper.Command... commands)
+ throws Throwable {
+ runInternal(config, debuggeeClass.getTypeName(), Arrays.asList(commands));
+ }
+
+ protected final void runDebugTest(
DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
throws Throwable {
runInternal(config, debuggeeClass, Arrays.asList(commands));
@@ -324,6 +332,10 @@
return new JUnit3Wrapper.Command.RunCommand();
}
+ protected final JUnit3Wrapper.Command breakpoint(MethodReference method) {
+ return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName());
+ }
+
protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
return breakpoint(className, methodName, null);
}
@@ -477,6 +489,10 @@
});
}
+ protected final JUnit3Wrapper.Command checkLine(int line) {
+ return inspect(t -> t.checkLine(null, line));
+ }
+
protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
return inspect(t -> t.checkLine(sourceFile, line));
}
@@ -532,6 +548,16 @@
});
}
+ protected final JUnit3Wrapper.Command checkFieldOnThis(
+ String fieldName, String fieldSignature, Value expectedValue) {
+ return inspect(
+ t -> {
+ Value value = t.getFieldOnThis(fieldName, fieldSignature);
+ Assert.assertEquals(
+ "Incorrect value for field 'this." + fieldName + "'", expectedValue, value);
+ });
+ }
+
protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
return t -> inspector.accept(t.debuggeeState);
}
@@ -1298,6 +1324,7 @@
@Override
public void checkLine(String sourceFile, int line) {
+ sourceFile = sourceFile != null ? sourceFile : getSourceFile();
if (!Objects.equals(sourceFile, getSourceFile()) || line != getLineNumber()) {
String locationString = convertCurrentLocationToString();
Assert.fail(
@@ -1501,7 +1528,15 @@
// The class is available, lookup and read the field.
long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
- return getField(getMirror(), classId, fieldId);
+ return internalStaticField(getMirror(), classId, fieldId);
+ }
+
+ public Value getFieldOnThis(String fieldName, String fieldSignature) {
+ long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+ long classId = getMirror().getReferenceType(thisObjectId);
+ // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+ long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+ return internalInstanceField(getMirror(), thisObjectId, fieldId);
}
private long findField(VmMirror mirror, long classId, String fieldName,
@@ -1547,10 +1582,10 @@
return matchingFieldIds.getLong(0);
}
- private Value getField(VmMirror mirror, long classId, long fieldId) {
-
- CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
- ReferenceTypeCommandSet.GetValuesCommand);
+ private static Value internalStaticField(VmMirror mirror, long classId, long fieldId) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.GetValuesCommand);
commandPacket.setNextValueAsReferenceTypeID(classId);
commandPacket.setNextValueAsInt(1);
commandPacket.setNextValueAsFieldID(fieldId);
@@ -1565,6 +1600,23 @@
}
}
+ private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.GetValuesCommand);
+ commandPacket.setNextValueAsObjectID(objectId);
+ commandPacket.setNextValueAsInt(1);
+ commandPacket.setNextValueAsFieldID(fieldId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+
+ int fieldsCount = replyPacket.getNextValueAsInt();
+ assert fieldsCount == 1;
+ Value result = replyPacket.getNextValueAsValue();
+ Assert.assertTrue(replyPacket.isAllDataRead());
+ return result;
+ }
+
public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
String localName) {
return getVariablesAt(mirror, location).stream()
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 17d5077..c398e8b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -255,7 +255,8 @@
@Test
public void testRegress62300145() throws Exception {
- testDebugging("regress_62300145", "Regress");
+ // TODO(b/67936230): Executes differently for Java 8 and 9, so don't compare to DEX output.
+ testDebuggingJvmOnly("regress_62300145", "Regress");
}
@Test
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
new file mode 100644
index 0000000..9991b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, 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.debug;
+
+public class LambdaOuterContextTest {
+
+ public interface Converter {
+ String convert(int value);
+ }
+
+ public int outer;
+
+ public LambdaOuterContextTest(int outer) {
+ this.outer = outer;
+ }
+
+ public void foo(Converter converter) {
+ System.out.println(converter.convert(outer));
+ }
+
+ public void bar(int arg) {
+ foo(value -> {
+ // Ensure lambda uses parts of the outer context, otherwise javac will optimize it out.
+ return Integer.toString(outer + value + arg);
+ });
+ }
+
+ public static void main(String[] args) {
+ new LambdaOuterContextTest(args.length + 42).bar(args.length);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
new file mode 100644
index 0000000..8af2a6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2019, 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.debug;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LambdaOuterContextTestRunner extends DebugTestBase {
+
+ public static final Class<?> CLASS = LambdaOuterContextTest.class;
+ public static final String EXPECTED = StringUtils.lines("84");
+
+ @Test
+ public void testJvm() throws Throwable {
+ JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
+ jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(jvmTestBuilder.debugConfig());
+ }
+
+ @Test
+ @Ignore("b/123068053")
+ public void testD8() throws Throwable {
+ D8TestCompileResult compileResult =
+ testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
+ compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig());
+ }
+
+ @Test
+ public void testR8Cf() throws Throwable {
+ R8TestCompileResult compileResult =
+ testForR8(Backend.CF)
+ .addProgramClassesAndInnerClasses(CLASS)
+ .debug()
+ .noMinification()
+ .noTreeShaking()
+ .compile();
+ compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig());
+ }
+
+ private void runDebugger(DebugTestConfig config) throws Throwable {
+ runDebugTest(
+ config,
+ CLASS,
+ breakpoint(methodFromMethod(CLASS.getMethod("foo", Converter.class))),
+ run(),
+ checkLine(19),
+ checkLocals("this", "converter"),
+ checkFieldOnThis("outer", null, Value.createInt(42)),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(25),
+ checkLocals("this", "value", "arg"),
+ checkNoLocal("outer"),
+ checkFieldOnThis("outer", null, Value.createInt(42)),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 1b413ba..f9e6c34 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -13,7 +13,6 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-// TODO(shertz) test local variables
@RunWith(Parameterized.class)
public class LambdaTest extends DebugTestBase {
@@ -47,8 +46,11 @@
run(),
checkMethod(debuggeeClass, initialMethodName),
checkLine(SOURCE_FILE, 12),
+ checkLocals("i"),
+ checkNoLocal("j"),
stepInto(INTELLIJ_FILTER),
checkLine(SOURCE_FILE, 16),
+ checkLocals("i", "j"),
run());
}
@@ -63,8 +65,10 @@
run(),
checkMethod(debuggeeClass, initialMethodName),
checkLine(SOURCE_FILE, 32),
+ checkLocals("i", "a", "b"),
stepInto(INTELLIJ_FILTER),
checkLine(SOURCE_FILE, 37),
+ checkLocals("a", "b"),
run());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
index f94d5a5..4d5ade8 100644
--- a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
@@ -5,6 +5,8 @@
package com.android.tools.r8.desugar;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
import org.junit.Before;
import org.junit.Test;
@@ -26,6 +28,60 @@
.assertSuccessWithOutput(expectedOutput);
}
+ @Test
+ public void testD8Merge() throws Exception {
+ String jvmOutput = testForJvm()
+ .addTestClasspath()
+ .run(MergeRun.class).getStdOut();
+ // See b/123242448
+ Path zip1 = temp.newFile("first.zip").toPath();
+ Path zip2 = temp.newFile("second.zip").toPath();
+
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(MergeRun.class, MergeInputB.class)
+ .compile()
+ .assertNoMessages()
+ .writeToZip(zip1);
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(MergeInputA.class)
+ .compile()
+ .assertNoMessages()
+ .writeToZip(zip2);
+ testForD8()
+ .addProgramFiles(zip1, zip2)
+ .setMinApi(AndroidApiLevel.L)
+ .compile()
+ .assertNoMessages()
+ .run(MergeRun.class)
+ .assertSuccessWithOutput(jvmOutput);
+ }
+
+
+ static class MergeInputA {
+ public void foo() {
+ System.out.println(Integer.hashCode(42));
+ System.out.println(Double.hashCode(42.0));
+ }
+ }
+
+ static class MergeInputB {
+ public void foo() {
+ System.out.println(Integer.hashCode(43));
+ System.out.println(Double.hashCode(43.0));
+ }
+ }
+
+ static class MergeRun {
+ public static void main(String[] args) {
+ MergeInputA a = new MergeInputA();
+ MergeInputB b = new MergeInputB();
+ a.foo();
+ b.foo();
+ }
+ }
+
static class Java8Methods {
public static void main(String[] args) {
byte[] aBytes = new byte[]{42, 1, -1, Byte.MAX_VALUE, Byte.MIN_VALUE};
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
index fc382ee..6b281cb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.Streams;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -41,6 +42,7 @@
}
@Test
+ @Ignore("b/123284765")
public void test() throws Exception {
String expectedOutput =
StringUtils.lines(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
new file mode 100644
index 0000000..8cf8b26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.checkcast;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+/** Regression test for b/123269162. */
+public class TrivialArrayCheckCastTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput =
+ StringUtils.lines(
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ InternalOptions options = new InternalOptions();
+ options.minApiLevel = AndroidApiLevel.I_MR1.getLevel();
+ assert options.canHaveArtCheckCastVerifierBug();
+
+ testForR8(Backend.DEX)
+ .addInnerClasses(TrivialArrayCheckCastTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(AndroidApiLevel.I_MR1)
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testBooleanArray();
+ testByteArray();
+ testCharArray();
+ testDoubleArray();
+ testFloatArray();
+ testFloatArrayNested();
+ testIntArray();
+ testLongArray();
+ testObjectArray();
+ testShortArray();
+ }
+
+ @NeverInline
+ private static void testBooleanArray() {
+ boolean[] array = (boolean[]) null;
+ try {
+ boolean value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testByteArray() {
+ byte[] array = (byte[]) null;
+ try {
+ byte value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testCharArray() {
+ char[] array = (char[]) null;
+ try {
+ char value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testDoubleArray() {
+ double[] array = (double[]) null;
+ try {
+ double value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testFloatArray() {
+ float[] array = (float[]) null;
+ try {
+ float value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testFloatArrayNested() {
+ float[][] nestedArray = (float[][]) null;
+ try {
+ float[] array = nestedArray[42];
+ float value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testIntArray() {
+ int[] array = (int[]) null;
+ try {
+ int value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testLongArray() {
+ long[] array = (long[]) null;
+ try {
+ long value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testObjectArray() {
+ Object[] array = (Object[]) null;
+ try {
+ Object value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testShortArray() {
+ short[] array = (short[]) null;
+ try {
+ short value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
new file mode 100644
index 0000000..71c5fbe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningFromCurrentClassTest extends TestBase {
+
+ @Ignore("b/123327416")
+ @Test
+ public void test() throws Exception {
+ String expectedOutput =
+ StringUtils.lines(
+ "In A.<clinit>()",
+ "In B.<clinit>()",
+ "In A.inlineable1()",
+ "In B.inlineable2()",
+ "In C.<clinit>()",
+ "In C.notInlineable()");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(InliningFromCurrentClassTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ ClassSubject classB = inspector.clazz(B.class);
+ assertThat(classB, isPresent());
+
+ ClassSubject classC = inspector.clazz(C.class);
+ assertThat(classC, isPresent());
+
+ MethodSubject inlineable1Method = classA.uniqueMethodWithName("inlineable1");
+ assertThat(inlineable1Method, not(isPresent()));
+
+ MethodSubject inlineable2Method = classB.uniqueMethodWithName("inlineable2");
+ assertThat(inlineable2Method, not(isPresent()));
+
+ MethodSubject notInlineableMethod = classC.uniqueMethodWithName("notInlineable");
+ assertThat(notInlineableMethod, isPresent());
+
+ MethodSubject testMethod = classB.uniqueMethodWithName("test");
+ assertThat(testMethod, isPresent());
+ assertThat(testMethod, not(invokesMethod(inlineable1Method)));
+ assertThat(testMethod, not(invokesMethod(inlineable2Method)));
+ assertThat(testMethod, invokesMethod(notInlineableMethod));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ B.test();
+ }
+ }
+
+ @NeverMerge
+ static class A {
+
+ static {
+ System.out.println("In A.<clinit>()");
+ }
+
+ static void inlineable1() {
+ System.out.println("In A.inlineable1()");
+ }
+ }
+
+ @NeverMerge
+ static class B extends A {
+
+ static {
+ System.out.println("In B.<clinit>()");
+ }
+
+ @NeverInline
+ static void test() {
+ A.inlineable1();
+ B.inlineable2();
+ C.notInlineable();
+ }
+
+ static void inlineable2() {
+ System.out.println("In B.inlineable2()");
+ }
+ }
+
+ static class C extends B {
+
+ static {
+ System.out.println("In C.<clinit>()");
+ }
+
+ static void notInlineable() {
+ System.out.println("In C.notInlineable()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
new file mode 100644
index 0000000..9691149
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningWithClassInitializerTest extends TestBase {
+
+ @Ignore("b/123327413")
+ @Test
+ public void test() throws Exception {
+ String expectedOutput =
+ StringUtils.lines("In A.<clinit>()", "In B.inlineable()", "In B.other()");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(InliningWithClassInitializerTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ ClassSubject classB = inspector.clazz(B.class);
+ assertThat(classB, isPresent());
+
+ MethodSubject inlineableMethod = classB.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+
+ MethodSubject otherMethod = classB.uniqueMethodWithName("other");
+ assertThat(otherMethod, isPresent());
+
+ MethodSubject mainMethod = inspector.clazz(TestClass.class).mainMethod();
+ assertThat(mainMethod, invokesMethod(otherMethod));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // Should be inlined since the call to `B.other()` will ensure that the static initalizer in
+ // A will continue to be executed even after inlining.
+ B.inlineable();
+ }
+ }
+
+ @NeverMerge
+ static class A {
+
+ static {
+ System.out.println("In A.<clinit>()");
+ }
+ }
+
+ static class B extends A {
+
+ static void inlineable() {
+ System.out.println("In B.inlineable()");
+ other();
+ }
+
+ @NeverInline
+ static void other() {
+ System.out.println("In B.other()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 8a8f137..7cb49d3 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -29,7 +29,13 @@
String mapping =
"com.c.c.b -> com.c.c.b:\n" +
" 1287:1287:int ?(int,int) -> ?";
+ ClassNameMapper.mapperFromString(mapping);
+ // From some other proguard generated map
+ mapping = "com.moat.analytics.mobile.cha.b -> com.moat.analytics.mobile.cha.b:\n"
+ + " com.moat.analytics.mobile.cha.MoatAdEventType[] ? -> ?\n"
+ + " java.util.HashMap ? -> ?\n"
+ + " java.util.HashSet ?? -> ??\n";
ClassNameMapper.mapperFromString(mapping);
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
new file mode 100644
index 0000000..25497e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2019, 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.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ProguardTestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class ToBeRenamedClass {
+ void toBeRenamedMethod() {
+ System.out.println("foo");
+ }
+}
+
+class WhenToApplyTestRunner {
+ public static void main(String[] args) {
+ ToBeRenamedClass instance = new ToBeRenamedClass();
+ instance.toBeRenamedMethod();
+ System.out.println(instance.getClass().getSimpleName());
+ }
+}
+
+@RunWith(Parameterized.class)
+public class WhenToApplyTest extends TestBase {
+
+ @ClassRule
+ public static TemporaryFolder temporaryFolder = ToolHelper.getTemporaryFolderForTest();
+
+ private static Class<?> MAIN = WhenToApplyTestRunner.class;
+ private static String RENAMED_CLASS_NAME =
+ ToBeRenamedClass.class.getPackage().getName() + ".ABC";
+ private static String NORMAL_OUTPUT = StringUtils.lines("foo", "ToBeRenamedClass");
+ private static String APPLIED_OUTPUT = StringUtils.lines("foo", "ABC");
+ private static String RENAMED_OUTPUT = StringUtils.lines("foo", "a");
+
+ private static Path mappingFile;
+ private static Path configuration;
+ private boolean minification;
+
+ @Parameterized.Parameters(name = "minification: {0}")
+ public static Boolean[] data() {
+ return BooleanUtils.values();
+ }
+
+ public WhenToApplyTest(boolean minification) {
+ this.minification = minification;
+ }
+
+ @BeforeClass
+ public static void setUpMappingFile() throws Exception {
+ mappingFile = temporaryFolder.newFile("mapping.txt").toPath().toAbsolutePath();
+ FileUtils.writeTextFile(mappingFile, StringUtils.lines(
+ ToBeRenamedClass.class.getTypeName() + " -> " + RENAMED_CLASS_NAME + ":",
+ " void toBeRenamedMethod() -> abc"));
+ configuration = temporaryFolder.newFile("pg.conf").toPath().toAbsolutePath();
+ FileUtils.writeTextFile(configuration, StringUtils.lines(
+ "-dontoptimize",
+ "-applymapping " + mappingFile
+ ));
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ ProguardTestRunResult result = testForProguard()
+ .addProgramClasses(ToBeRenamedClass.class, MAIN)
+ .addKeepMainRule(MAIN)
+ .addKeepRuleFiles(configuration)
+ .minification(minification)
+ .compile().run(MAIN);
+ if (minification) {
+ result.assertSuccessWithOutput(APPLIED_OUTPUT);
+ } else {
+ result.assertSuccessWithOutput(NORMAL_OUTPUT);
+ }
+ result.inspect(inspector -> {
+ ClassSubject classSubject = inspector.clazz(ToBeRenamedClass.class);
+ assertThat(classSubject, isPresent());
+ // As renaming won't happen again, we can use the original name to search for the method.
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("toBeRenamedMethod");
+ assertThat(methodSubject, isPresent());
+ String methodName =
+ minification
+ ? "abc" // mapped name with minification
+ : "toBeRenamedMethod"; // original name without minification
+ assertEquals(methodName, methodSubject.getFinalName());
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestRunResult result = testForR8(Backend.DEX)
+ .addProgramClasses(ToBeRenamedClass.class, MAIN)
+ .addKeepMainRule(MAIN)
+ .addKeepRuleFiles(configuration)
+ .minification(minification)
+ .compile().run(MAIN);
+ if (minification) {
+ result.assertSuccessWithOutput(RENAMED_OUTPUT);
+ } else {
+ result.assertSuccessWithOutput(APPLIED_OUTPUT);
+ }
+ result.inspect(inspector -> {
+ ClassSubject classSubject = inspector.clazz(RENAMED_CLASS_NAME);
+ assertThat(classSubject, isPresent());
+ // Mapped name will be regarded as an original name if minification is disabled.
+ String methodName =
+ minification
+ ? "toBeRenamedMethod" // original name
+ : "abc"; // mapped name without minification
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
+ assertThat(methodSubject, isPresent());
+ methodName =
+ minification
+ ? "a" // minified name
+ : "abc"; // mapped name without minification
+ assertEquals(methodName, methodSubject.getFinalName());
+ });
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
index f3746fa..fea123d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index a690bce..1609bfd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -7,7 +7,6 @@
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 2157cd3..99707b6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 820afc2..74a4efe 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 1dd353e..49af47a 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
index 9ef14bf..dbae1da 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index fac36d6..8430bb6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index b691046..fa76296 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index 02b61f5..d034d84 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -7,7 +7,6 @@
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
index 17cfb7f..044d21d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index 5f87781..49a5910 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
index 6ed0099..49a1e14 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
index 0d2a486..d2e1cd0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
index 7484255..212e883 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
index f5733cc..01c0de9 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
index 9d2faf3..4cbd1c0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index b957c64..5b1009e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index e64abe1..8974d6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
index a5b13a7..c204e06 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
index 825262a..4d64e0e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index d7d45f0..a373627 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
index cf7f2ad..9c2c387 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
index 1474ff8..e4b9ed6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
index 18123a3..72b6d17 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
index 819e11e..c36ddd7 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
index 967dbc5..60cbd65 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
index dc51fa1..59d8dd2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
index be844e3..15e8195 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
index fd38ee7..6f2d721 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
index a1fc427..a765f30 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
index 3ba2ce7..61b4154 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
index 8427e6a..48ef889 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
index e093f00..de515d0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
index 789e40c..e02b4cd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index b77b29e..2a53f1b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
index 235bb56..779c140 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
index 80573ff..7ca8334 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
index 4f04aed..0a48ba2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
index f548035..977de65 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
new file mode 100644
index 0000000..edf2b89
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.Proxy;
+import org.junit.Test;
+
+public class IfOnTargetedMethodTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = StringUtils.lines("Hello world!");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(IfOnTargetedMethodTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if interface * { @" + MyAnnotation.class.getTypeName() + " <methods>; }",
+ "-keep,allowobfuscation interface <1>")
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject interfaceSubject = inspector.clazz(Interface.class);
+ assertThat(interfaceSubject, isPresent());
+ assertThat(interfaceSubject, isRenamed());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Interface obj = getInstance();
+ obj.method();
+ }
+
+ static Interface getInstance() {
+ return (Interface)
+ Proxy.newProxyInstance(
+ Interface.class.getClassLoader(),
+ new Class[] {Interface.class},
+ (o, method, objects) -> {
+ System.out.println("Hello world!");
+ return null;
+ });
+ }
+ }
+
+ interface Interface {
+
+ @MyAnnotation
+ void method();
+ }
+
+ @interface MyAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
index e8eb416..e518181 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -8,7 +8,9 @@
@Keep
public class KeptByFieldReflectionTest {
- public final int foo = 42;
+ // TODO(b/123262024): This field must be kept un-initialized. Otherwise the "-whyareyoukeeping"
+ // output tested will hit the initialization in <init> and not the reflective access.
+ public int foo;
public static void main(String[] args) throws Exception {
// Due to b/123210548 the object cannot be created by a reflective newInstance call.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index 6feaa35..62dcebe 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -19,7 +19,6 @@
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collection;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -31,7 +30,7 @@
private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
- private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
+ private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 0");
private final String EXPECTED_WHYAREYOUKEEPING =
StringUtils.lines(
@@ -53,7 +52,6 @@
}
@Test
- @Ignore("b/123215165")
public void test() throws Exception {
MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
diff --git a/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
new file mode 100644
index 0000000..7aa573e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.reflection;
+import static com.android.tools.r8.references.Reference.fieldFromField;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import org.junit.Assert;
+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 FieldAccessTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public FieldAccessTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ private boolean isInvokeGetField(InstructionSubject instruction) {
+ return
+ instruction.isInvoke()
+ && instruction.getMethod().qualifiedName().equals("java.lang.Class.getField");
+ }
+
+ private void runTest(Class<?> testClass) throws Exception {
+ MethodReference mainMethod =
+ methodFromMethod(testClass.getDeclaredMethod("main", String[].class));
+ FieldReference fooField = fieldFromField(testClass.getDeclaredField("foo"));
+
+ testForR8(Backend.DEX)
+ .enableGraphInspector()
+ .addProgramClasses(testClass)
+ .addKeepMainRule(testClass)
+ .run(testClass)
+ .inspectGraph(inspector -> {
+ // The only root should be the keep annotation rule.
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(root);
+ inspector.field(fooField).assertRenamed();
+ })
+ .inspect(inspector -> {
+ Assert.assertTrue(
+ inspector
+ .clazz(testClass)
+ .uniqueMethodWithName("main")
+ .streamInstructions()
+ .anyMatch(this::isInvokeGetField));
+ })
+ .assertSuccessWithOutput("42");
+ }
+
+ @Test
+ public void reflectiveGet() throws Exception {
+ runTest(FieldAccessTestGet.class);
+ }
+
+ @Test
+ public void reflectiveGetStatic() throws Exception {
+ runTest(FieldAccessTestGetStatic.class);
+ }
+
+ @Test
+ public void reflectivePut() throws Exception {
+ runTest(FieldAccessTestPut.class);
+ }
+
+ @Test
+ public void reflectivePutStatic() throws Exception {
+ runTest(FieldAccessTestPutStatic.class);
+ }
+
+ @Test
+ public void reflectivePutGet() throws Exception {
+ runTest(FieldAccessTestPutGet.class);
+ }
+
+ @Test
+ public void reflectivePutGetStatic() throws Exception {
+ runTest(FieldAccessTestPutGetStatic.class);
+ }
+}
+
+class FieldAccessTestGet {
+
+ public int foo = 42;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestGet obj = new FieldAccessTestGet();
+ System.out.print(FieldAccessTestGet.class.getField("foo").getInt(obj));
+ }
+}
+
+class FieldAccessTestGetStatic {
+
+ public static int foo = 42;
+
+ public static void main(String[] args) throws Exception {
+ System.out.print(FieldAccessTestGetStatic.class.getField("foo").getInt(null));
+ }
+}
+
+class FieldAccessTestPut {
+
+ public int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPut obj = new FieldAccessTestPut();
+ FieldAccessTestPut.class.getField("foo").setInt(obj, 42);
+ System.out.print(42);
+ }
+}
+
+class FieldAccessTestPutStatic {
+
+ public static int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPutStatic.class.getField("foo").setInt(null, 42);
+ System.out.print(42);
+ }
+}
+
+class FieldAccessTestPutGet {
+
+ public int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPutGet obj = new FieldAccessTestPutGet();
+ FieldAccessTestPutGet.class.getField("foo").setInt(obj, 42);
+ System.out.print(FieldAccessTestPutGet.class.getField("foo").getInt(obj));
+ }
+}
+
+class FieldAccessTestPutGetStatic {
+
+ public static int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPutGetStatic.class.getField("foo").setInt(null, 42);
+ System.out.print(FieldAccessTestPutGetStatic.class.getField("foo").getInt(null));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 375f0b5..93a1c37 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayStore;
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.cf.code.CfConstNull;
@@ -286,6 +287,11 @@
}
@Override
+ public boolean isArrayPut() {
+ return instruction instanceof CfArrayStore;
+ }
+
+ @Override
public int size() {
// TODO(b/122302789): CfInstruction#getSize()
throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
new file mode 100644
index 0000000..7d5381a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.function.Predicate;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class CodeMatchers {
+
+ public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
+ if (!targetSubject.isPresent()) {
+ throw new IllegalArgumentException();
+ }
+ DexMethod target = targetSubject.getMethod().method;
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ protected boolean matchesSafely(MethodSubject subject) {
+ if (!subject.isPresent()) {
+ return false;
+ }
+ if (!subject.getMethod().hasCode()) {
+ return false;
+ }
+ return subject.streamInstructions().anyMatch(isInvokeWithTarget(target));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(" does not invoke `" + target.toSourceString() + "`");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject subject, Description description) {
+ description.appendText("method did not");
+ }
+ };
+ }
+
+ public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
+ return instruction -> instruction.isInvokeStatic() && instruction.getMethod() == target;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index e2e801e..c9b7ee5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.code.Aput;
import com.android.tools.r8.code.CheckCast;
import com.android.tools.r8.code.Const;
import com.android.tools.r8.code.Const16;
@@ -366,6 +367,11 @@
}
@Override
+ public boolean isArrayPut() {
+ return instruction instanceof Aput;
+ }
+
+ @Override
public boolean isMonitorEnter() {
return instruction instanceof MonitorEnter;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index dd5af7c..0528185 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -84,6 +84,8 @@
boolean isNewArray();
+ boolean isArrayPut();
+
boolean isMonitorEnter();
boolean isMonitorExit();
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index 6013e6f..be380ed 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -8,7 +8,6 @@
import optparse
import os
import shutil
-import subprocess
import sys
import utils
@@ -32,6 +31,9 @@
parser.add_option('--adb-options',
help='additional adb options when running adb',
default=None)
+ parser.add_option('--quiet',
+ help='disable verbose logging',
+ default=False)
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -41,44 +43,53 @@
def findKeystore():
return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
-def repack(processed_out, original_apk, temp):
+def repack(processed_out, original_apk, temp, quiet):
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(original_apk, processed_apk)
if not processed_out:
- print 'Using original APK as is'
+ utils.Print('Using original APK as is', quiet=quiet)
return processed_apk
- print 'Repacking APK with dex files from', processed_apk
- with utils.ChangedWorkingDirectory(temp):
+ utils.Print(
+ 'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
+ with utils.ChangedWorkingDirectory(temp, quiet=quiet):
cmd = ['zip', '-d', 'processed.apk', '*.dex']
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
cmd = ['unzip', processed_out, '-d', temp]
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ if quiet:
+ cmd.insert(1, '-q')
+ utils.RunCmd(cmd, quiet=quiet)
processed_out = temp
- with utils.ChangedWorkingDirectory(processed_out):
+ with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
dex = glob.glob('*.dex')
cmd = ['zip', '-u', '-9', processed_apk] + dex
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
return processed_apk
-def sign(unsigned_apk, keystore, temp):
+def sign(unsigned_apk, keystore, temp, quiet):
signed_apk = os.path.join(temp, 'unaligned.apk')
- apk_utils.sign(unsigned_apk, signed_apk, keystore)
+ apk_utils.sign(unsigned_apk, signed_apk, keystore, quiet=quiet)
return signed_apk
-def align(signed_apk, temp):
- print 'Aligning'
+def align(signed_apk, temp, quiet):
+ utils.Print('Aligning', quiet=quiet)
aligned_apk = os.path.join(temp, 'aligned.apk')
- cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
- print ' '.join(cmd)
- subprocess.check_call(cmd)
+ zipalign_path = (
+ 'zipalign' if 'build_tools' in os.environ.get('PATH')
+ else os.path.join(utils.ANDROID_BUILD_TOOLS, 'zipalign'))
+ cmd = [
+ zipalign_path,
+ '-f',
+ '4',
+ signed_apk,
+ aligned_apk
+ ]
+ utils.RunCmd(cmd, quiet=quiet)
return signed_apk
def masseur(
- apk, dex=None, out=None, adb_options=None, keystore=None, install=False):
+ apk, dex=None, out=None, adb_options=None, keystore=None, install=False,
+ quiet=False):
if not out:
out = os.path.basename(apk)
if not keystore:
@@ -86,23 +97,23 @@
with utils.TempDir() as temp:
processed_apk = None
if dex:
- processed_apk = repack(dex, apk, temp)
+ processed_apk = repack(dex, apk, temp, quiet)
else:
- print 'Signing original APK without modifying dex files'
+ utils.Print(
+ 'Signing original APK without modifying dex files', quiet=quiet)
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(apk, processed_apk)
- signed_apk = sign(processed_apk, keystore, temp)
- aligned_apk = align(signed_apk, temp)
- print 'Writing result to', out
+ signed_apk = sign(processed_apk, keystore, temp, quiet=quiet)
+ aligned_apk = align(signed_apk, temp, quiet=quiet)
+ utils.Print('Writing result to {}'.format(out), quiet=quiet)
shutil.copyfile(aligned_apk, out)
- adb_cmd = ['adb']
- if adb_options:
- adb_cmd.extend(
- [option for option in adb_options.split(' ') if option])
if install:
+ adb_cmd = ['adb']
+ if adb_options:
+ adb_cmd.extend(
+ [option for option in adb_options.split(' ') if option])
adb_cmd.extend(['install', '-t', '-r', '-d', out]);
- utils.PrintCmd(adb_cmd)
- subprocess.check_call(adb_cmd)
+ utils.RunCmd(adb_cmd, quiet=quiet)
def main():
(options, apk) = parse_options()
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index aaae1e4..5a6aa94 100644
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -7,11 +7,10 @@
import subprocess
import utils
-def sign(unsigned_apk, signed_apk, keystore):
- print 'Signing (ignore the warnings)'
+def sign(unsigned_apk, signed_apk, keystore, quiet=False):
+ utils.Print('Signing (ignore the warnings)', quiet=quiet)
cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
- utils.PrintCmd(cmd)
- subprocess.call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
cmd = [
'jarsigner',
'-sigalg', 'SHA1withRSA',
@@ -22,8 +21,7 @@
unsigned_apk,
'androiddebugkey'
]
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
def sign_with_apksigner(build_tools_dir, unsigned_apk, signed_apk, keystore, password):
cmd = [
diff --git a/tools/as_utils.py b/tools/as_utils.py
index b4eb396..10fb29f 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -10,9 +10,10 @@
import utils
-def add_r8_dependency(checkout_dir, minified):
+def add_r8_dependency(checkout_dir, temp_dir, minified):
build_file = os.path.join(checkout_dir, 'build.gradle')
- assert os.path.isfile(build_file), 'Expected a file to be present at {}'.format(build_file)
+ assert os.path.isfile(build_file), (
+ 'Expected a file to be present at {}'.format(build_file))
with open(build_file) as f:
lines = f.readlines()
@@ -25,26 +26,19 @@
for line in lines:
stripped = line.strip()
if stripped == 'dependencies {':
- assert not is_inside_dependencies, 'Unexpected line with \'dependencies {\''
+ assert not is_inside_dependencies, (
+ 'Unexpected line with \'dependencies {\'')
is_inside_dependencies = True
if is_inside_dependencies:
- if utils.R8_JAR in stripped:
- if minified:
- # Skip line to avoid dependency on r8.jar
- continue
- added_r8_dependency = True
- elif utils.R8LIB_JAR in stripped:
- if not minified:
- # Skip line to avoid dependency on r8lib.jar
- continue
- added_r8_dependency = True
+ if '/r8.jar' in stripped or '/r8lib.jar' in stripped:
+ # Skip line to avoid dependency on r8.jar
+ continue
elif 'com.android.tools.build:gradle:' in stripped:
gradle_version = stripped[stripped.rindex(':')+1:-1]
- if not added_r8_dependency:
- indent = ''.ljust(line.index('classpath'))
- jar = utils.R8LIB_JAR if minified else utils.R8_JAR
- f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
- added_r8_dependency = True
+ indent = ''.ljust(line.index('classpath'))
+ jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
+ f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
+ added_r8_dependency = True
elif stripped == '}':
is_inside_dependencies = False
f.write(line)
@@ -63,45 +57,38 @@
lines = f.readlines()
with open(build_file, 'w') as f:
for line in lines:
- if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
+ if ('/r8.jar' not in line) and ('/r8lib.jar' not in line):
f.write(line)
def GetMinAndCompileSdk(app, config, checkout_dir, apk_reference):
- app_module = config.get('app_module', 'app')
- build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle')
- assert os.path.isfile(build_gradle_file), (
- 'Expected to find build.gradle file at {}'.format(build_gradle_file))
- compile_sdk = None
- min_sdks = []
- target_sdk = None
+ compile_sdk = config.get('compile_sdk', None)
+ min_sdk = config.get('min_sdk', None)
- with open(build_gradle_file) as f:
- for line in f.readlines():
- stripped = line.strip()
- if stripped.startswith('compileSdkVersion '):
- assert not compile_sdk
- compile_sdk = int(stripped[len('compileSdkVersion '):])
- if stripped.startswith('minSdkVersion '):
- min_sdks.append(int(stripped[len('minSdkVersion '):]))
- elif stripped.startswith('targetSdkVersion '):
- assert not target_sdk
- target_sdk = int(stripped[len('targetSdkVersion '):])
+ if not compile_sdk or not min_sdk:
+ app_module = config.get('app_module', 'app')
+ build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle')
+ assert os.path.isfile(build_gradle_file), (
+ 'Expected to find build.gradle file at {}'.format(build_gradle_file))
- if len(min_sdks) == 1:
- min_sdk = min_sdks[0]
- else:
- assert 'min_sdk' in config
- min_sdk = config.get('min_sdk')
+ # Attempt to find the sdk values from build.gradle.
+ with open(build_gradle_file) as f:
+ for line in f.readlines():
+ stripped = line.strip()
+ if stripped.startswith('compileSdkVersion '):
+ if 'compile_sdk' not in config:
+ assert not compile_sdk
+ compile_sdk = int(stripped[len('compileSdkVersion '):])
+ elif stripped.startswith('minSdkVersion '):
+ if 'min_sdk' not in config:
+ assert not min_sdk
+ min_sdk = int(stripped[len('minSdkVersion '):])
assert min_sdk, (
'Expected to find `minSdkVersion` in {}'.format(build_gradle_file))
assert compile_sdk, (
'Expected to find `compileSdkVersion` in {}'.format(build_gradle_file))
- assert not target_sdk or target_sdk == compile_sdk, (
- 'Expected `compileSdkVersion` and `targetSdkVersion` to be the same')
-
return (min_sdk, compile_sdk)
def IsGradleTaskName(x):
@@ -152,8 +139,9 @@
# one of the predefined locations.
assert False
-def Move(src, dst):
- print('Moving `{}` to `{}`'.format(src, dst))
+def Move(src, dst, quiet=False):
+ if not quiet:
+ print('Moving `{}` to `{}`'.format(src, dst))
dst_parent = os.path.dirname(dst)
if not os.path.isdir(dst_parent):
os.makedirs(dst_parent)
@@ -163,15 +151,15 @@
os.remove(dst)
os.rename(src, dst)
-def MoveDir(src, dst):
+def MoveDir(src, dst, quiet=False):
assert os.path.isdir(src)
- Move(src, dst)
+ Move(src, dst, quiet=quiet)
-def MoveFile(src, dst):
+def MoveFile(src, dst, quiet=False):
assert os.path.isfile(src)
- Move(src, dst)
+ Move(src, dst, quiet=quiet)
-def MoveProfileReportTo(dest_dir, build_stdout):
+def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
html_file = None
profile_message = 'See the profiling report at: '
for line in build_stdout:
@@ -186,11 +174,12 @@
assert os.path.isfile(html_file), 'Expected to find HTML file at {}'.format(
html_file)
- MoveFile(html_file, os.path.join(dest_dir, 'index.html'))
+ MoveFile(html_file, os.path.join(dest_dir, 'index.html'), quiet=quiet)
html_dir = os.path.dirname(html_file)
for dir_name in ['css', 'js']:
- MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name))
+ MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name),
+ quiet=quiet)
def ParseProfileReport(profile_dir):
html_file = os.path.join(profile_dir, 'index.html')
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 5bf1da2..a9a90eb 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -235,6 +235,10 @@
if get_magic_file_exists(READY_FOR_TESTING):
git_hash = get_magic_file_content(READY_FOR_TESTING)
checked_out = git_checkout(git_hash)
+ # If the script changed, we need to restart now to get correct commands
+ # Note that we have not removed the READY_FOR_TESTING yet, so if we
+ # execv we will pick up the same version.
+ restart_if_new_version(own_content)
# Sanity check, if this does not succeed stop.
if checked_out != git_hash:
log('Inconsistent state: %s %s' % (git_hash, checked_out))
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
index ad26959..9f2f7e4 100755
--- a/tools/run_bootstrap_benchmark.py
+++ b/tools/run_bootstrap_benchmark.py
@@ -60,11 +60,11 @@
sys.exit(return_code)
dex(r8_output, d8_r8_output)
- print "BootstrapR8(CodeSize):", os.path.getsize(r8_output)
- print "BootstrapR8Dex(CodeSize):", os.path.getsize(d8_r8_output)
+ print "BootstrapR8(CodeSize):", utils.uncompressed_size(r8_output)
+ print "BootstrapR8Dex(CodeSize):", utils.uncompressed_size(d8_r8_output)
dex(PINNED_PGR8_JAR, d8_pg_output)
- print "BootstrapR8PG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
- print "BootstrapR8PGDex(CodeSize):", os.path.getsize(d8_pg_output)
+ print "BootstrapR8PG(CodeSize):", utils.uncompressed_size(PINNED_PGR8_JAR)
+ print "BootstrapR8PGDex(CodeSize):", utils.uncompressed_size(d8_pg_output)
sys.exit(0)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index e9b2dcd..aacfbe9 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -8,9 +8,9 @@
import gradle
import os
import optparse
+import shutil
import subprocess
import sys
-import tempfile
import time
import utils
import zipfile
@@ -36,11 +36,14 @@
'git_repo': 'https://github.com/christofferqa/AnExplorer',
'flavor': 'googleMobilePro',
'signed-apk-name': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
+ 'min_sdk': 17
},
'AntennaPod': {
'app_id': 'de.danoeh.antennapod',
'git_repo': 'https://github.com/christofferqa/AntennaPod.git',
'flavor': 'play',
+ 'min_sdk': 14,
+ 'compile_sdk': 26
},
'apps-android-wikipedia': {
'app_id': 'org.wikipedia',
@@ -48,6 +51,10 @@
'flavor': 'prod',
'signed-apk-name': 'app-prod-universal-release.apk'
},
+ 'chanu': {
+ 'app_id': 'com.chanapps.four.activity',
+ 'git_repo': 'https://github.com/mkj-gram/chanu.git',
+ },
'friendlyeats-android': {
'app_id': 'com.google.firebase.example.fireeats',
'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git'
@@ -68,6 +75,19 @@
'app_id': 'org.schabi.newpipe',
'git_repo': 'https://github.com/christofferqa/NewPipe',
},
+ 'rover-android': {
+ 'app_id': 'io.rover.app.debug',
+ 'app_module': 'debug-app',
+ 'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+ },
+ 'Signal-Android': {
+ 'app_id': 'org.thoughtcrime.securesms',
+ 'app_module': '',
+ 'flavor': 'play',
+ 'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
+ 'releaseTarget': 'assemblePlayRelease',
+ 'signed-apk-name': 'Signal-play-release-4.32.7.apk',
+ },
'Simple-Calendar': {
'app_id': 'com.simplemobiletools.calendar.pro',
'git_repo': 'https://github.com/christofferqa/Simple-Calendar',
@@ -88,17 +108,10 @@
# TODO(123047413): Fails with R8.
'skip': True,
},
- 'Signal-Android': {
- 'app_id': 'org.thoughtcrime.securesms',
- 'app_module': '',
- 'flavor': 'play',
- 'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
- 'releaseTarget': 'assemblePlayRelease',
- 'signed-apk-name': 'Signal-play-release-4.32.7.apk',
- },
- 'chanu': {
- 'app_id': 'com.chanapps.four.activity',
- 'git_repo': 'https://github.com/mkj-gram/chanu.git',
+ 'Tusky': {
+ 'app_id': 'com.keylesspalace.tusky',
+ 'git_repo': 'https://github.com/mkj-gram/Tusky.git',
+ 'flavor': 'blue'
},
# This does not build yet.
'muzei': {
@@ -109,13 +122,6 @@
},
}
-# Common environment setup.
-user_home = os.path.expanduser('~')
-android_home = os.path.join(user_home, 'Android', 'Sdk')
-android_build_tools_version = '28.0.3'
-android_build_tools = os.path.join(
- android_home, 'build-tools', android_build_tools_version)
-
# TODO(christofferqa): Do not rely on 'emulator-5554' name
emulator_id = 'emulator-5554'
@@ -127,9 +133,18 @@
dex_size += z.getinfo(filename).file_size
return dex_size
-def IsBuiltWithR8(apk):
- script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
- return '~~R8' in subprocess.check_output(['python', script, apk]).strip()
+def IsBuiltWithR8(apk, temp_dir, options):
+ r8_jar = os.path.join(temp_dir, 'r8.jar')
+
+ # Use the copy of r8.jar if it is there.
+ if os.path.isfile(r8_jar):
+ cmd = ['java', '-ea', '-jar', r8_jar, 'extractmarker', apk]
+ else:
+ script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
+ cmd = ['python', script, apk]
+
+ utils.PrintCmd(cmd, quiet=options.quiet)
+ return '~~R8' in subprocess.check_output(cmd).strip()
def IsMinifiedR8(shrinker):
return shrinker == 'r8-minified' or shrinker == 'r8full-minified'
@@ -147,9 +162,13 @@
def GitCheckout(file):
return subprocess.check_output(['git', 'checkout', file]).strip()
-def InstallApkOnEmulator(apk_dest):
- subprocess.check_call(
- ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+def InstallApkOnEmulator(apk_dest, options):
+ cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest]
+ if options.quiet:
+ with open(os.devnull, 'w') as devnull:
+ subprocess.check_call(cmd, stdout=devnull)
+ else:
+ subprocess.check_call(cmd)
def PercentageDiffAsString(before, after):
if after < before:
@@ -157,7 +176,7 @@
else:
return '+' + str(round((after - before) / before * 100)) + '%'
-def UninstallApkOnEmulator(app, config):
+def UninstallApkOnEmulator(app, config, options):
app_id = config.get('app_id')
process = subprocess.Popen(
['adb', 'uninstall', app_id],
@@ -196,7 +215,7 @@
else:
return True
-def GetResultsForApp(app, config, options):
+def GetResultsForApp(app, config, options, temp_dir):
git_repo = config['git_repo']
# Checkout and build in the build directory.
@@ -205,10 +224,10 @@
result = {}
if not os.path.exists(checkout_dir):
- with utils.ChangedWorkingDirectory(WORKING_DIR):
+ with utils.ChangedWorkingDirectory(WORKING_DIR, quiet=options.quiet):
GitClone(git_repo)
elif options.pull:
- with utils.ChangedWorkingDirectory(checkout_dir):
+ with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
# Checkout build.gradle to avoid merge conflicts.
if IsTrackedByGit('build.gradle'):
GitCheckout('build.gradle')
@@ -221,16 +240,16 @@
result['status'] = 'success'
result_per_shrinker = BuildAppWithSelectedShrinkers(
- app, config, options, checkout_dir)
+ app, config, options, checkout_dir, temp_dir)
for shrinker, shrinker_result in result_per_shrinker.iteritems():
result[shrinker] = shrinker_result
return result
-def BuildAppWithSelectedShrinkers(app, config, options, checkout_dir):
+def BuildAppWithSelectedShrinkers(app, config, options, checkout_dir, temp_dir):
result_per_shrinker = {}
- with utils.ChangedWorkingDirectory(checkout_dir):
+ with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
for shrinker in SHRINKERS:
if options.shrinker and shrinker not in options.shrinker:
continue
@@ -242,7 +261,7 @@
out_dir = os.path.join(checkout_dir, 'out', shrinker)
(apk_dest, profile_dest_dir, proguard_config_file) = \
BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
- options)
+ temp_dir, options)
dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
result['apk_dest'] = apk_dest,
result['build_status'] = 'success'
@@ -270,15 +289,9 @@
# Build app with gradle using -D...keepRuleSynthesisForRecompilation=
# true.
out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
- extra_env_vars = {
- 'JAVA_OPTS': ' '.join([
- '-ea:com.android.tools.r8...',
- '-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true'
- ])
- }
(apk_dest, profile_dest_dir, ext_proguard_config_file) = \
BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
- options, extra_env_vars)
+ temp_dir, options, keepRuleSynthesisForRecompilation=True)
dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
recompilation_result = {
'apk_dest': apk_dest,
@@ -304,8 +317,9 @@
recompiled_apk_dest = os.path.join(
checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
RebuildAppWithShrinker(
- previous_apk, recompiled_apk_dest, ext_proguard_config_file,
- shrinker, min_sdk, compile_sdk)
+ app, previous_apk, recompiled_apk_dest,
+ ext_proguard_config_file, shrinker, min_sdk, compile_sdk,
+ options, temp_dir)
recompilation_result = {
'apk_dest': recompiled_apk_dest,
'build_status': 'success',
@@ -324,18 +338,26 @@
result_per_shrinker[shrinker] = result
+ if not options.app:
+ print('')
+ LogResultsForApp(app, result_per_shrinker, options)
+ print('')
+
return result_per_shrinker
def BuildAppWithShrinker(
- app, config, shrinker, checkout_dir, out_dir, options, env_vars=None):
- print()
- print('Building {} with {}'.format(app, shrinker))
+ app, config, shrinker, checkout_dir, out_dir, temp_dir, options,
+ keepRuleSynthesisForRecompilation=False):
+ print('Building {} with {}{}'.format(
+ app,
+ shrinker,
+ ' for recompilation' if keepRuleSynthesisForRecompilation else ''))
# Add/remove 'r8.jar' from top-level build.gradle.
if options.disable_tot:
as_utils.remove_r8_dependency(checkout_dir)
else:
- as_utils.add_r8_dependency(checkout_dir, IsMinifiedR8(shrinker))
+ as_utils.add_r8_dependency(checkout_dir, temp_dir, IsMinifiedR8(shrinker))
app_module = config.get('app_module', 'app')
archives_base_name = config.get('archives_base_name', app_module)
@@ -350,11 +372,9 @@
as_utils.SetPrintConfigurationDirective(
app, config, checkout_dir, proguard_config_dest)
- env = os.environ.copy()
- env['ANDROID_HOME'] = android_home
+ env = {}
+ env['ANDROID_HOME'] = utils.ANDROID_HOME
env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
- if env_vars:
- env.update(env_vars)
releaseTarget = config.get('releaseTarget')
if not releaseTarget:
@@ -370,20 +390,12 @@
'--profile', '--stacktrace',
'-Pandroid.enableR8=' + str(enableR8).lower(),
'-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+ if keepRuleSynthesisForRecompilation:
+ cmd.append('-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
if options.gradle_flags:
cmd.extend(options.gradle_flags.split(' '))
- utils.PrintCmd(cmd)
- build_process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
- stdout = []
- while True:
- line = build_process.stdout.readline()
- if line != b'':
- stripped = line.rstrip()
- stdout.append(stripped)
- print(stripped)
- else:
- break
+ stdout = utils.RunCmd(cmd, env, quiet=options.quiet)
apk_base_name = (archives_base_name
+ (('-' + flavor) if flavor else '') + '-release')
@@ -406,7 +418,7 @@
keystore = 'app.keystore'
keystore_password = 'android'
apk_utils.sign_with_apksigner(
- android_build_tools,
+ utils.ANDROID_BUILD_TOOLS,
unsigned_apk,
signed_apk,
keystore,
@@ -414,29 +426,31 @@
if os.path.isfile(signed_apk):
apk_dest = os.path.join(out_dir, signed_apk_name)
- as_utils.MoveFile(signed_apk, apk_dest)
+ as_utils.MoveFile(signed_apk, apk_dest, quiet=options.quiet)
else:
apk_dest = os.path.join(out_dir, unsigned_apk_name)
- as_utils.MoveFile(unsigned_apk, apk_dest)
+ as_utils.MoveFile(unsigned_apk, apk_dest, quiet=options.quiet)
- assert IsBuiltWithR8(apk_dest) == ('r8' in shrinker), (
+ assert IsBuiltWithR8(apk_dest, temp_dir, options) == ('r8' in shrinker), (
'Unexpected marker in generated APK for {}'.format(shrinker))
profile_dest_dir = os.path.join(out_dir, 'profile')
- as_utils.MoveProfileReportTo(profile_dest_dir, stdout)
+ as_utils.MoveProfileReportTo(profile_dest_dir, stdout, quiet=options.quiet)
return (apk_dest, profile_dest_dir, proguard_config_dest)
def RebuildAppWithShrinker(
- apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk):
+ app, apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
+ options, temp_dir):
assert 'r8' in shrinker
assert apk_dest.endswith('.apk')
+ print('Rebuilding {} with {}'.format(app, shrinker))
+
# Compile given APK with shrinker to temporary zip file.
- android_jar = os.path.join(
- utils.REPO_ROOT,
- utils.ANDROID_JAR.format(api=compile_sdk))
- r8_jar = utils.R8LIB_JAR if IsMinifiedR8(shrinker) else utils.R8_JAR
+ android_jar = utils.get_android_jar(compile_sdk)
+ r8_jar = os.path.join(
+ temp_dir, 'r8lib.jar' if IsMinifiedR8(shrinker) else 'r8.jar')
zip_dest = apk_dest[:-4] + '.zip'
# TODO(christofferqa): Entry point should be CompatProguard if the shrinker
@@ -446,20 +460,24 @@
cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar, entry_point,
'--release', '--min-api', str(min_sdk), '--pg-conf', proguard_config_file,
'--lib', android_jar, '--output', zip_dest, apk]
- utils.PrintCmd(cmd)
- subprocess.check_output(cmd)
+ for android_optional_jar in utils.get_android_optional_jars(compile_sdk):
+ cmd.append('--lib')
+ cmd.append(android_optional_jar)
+
+ utils.RunCmd(cmd, quiet=options.quiet)
# Make a copy of the given APK, move the newly generated dex files into the
# copied APK, and then sign the APK.
- apk_masseur.masseur(apk, dex=zip_dest, out=apk_dest)
+ apk_masseur.masseur(
+ apk, dex=zip_dest, out=apk_dest, quiet=options.quiet)
def RunMonkey(app, config, options, apk_dest):
if not WaitForEmulator():
return False
- UninstallApkOnEmulator(app, config)
- InstallApkOnEmulator(apk_dest)
+ UninstallApkOnEmulator(app, config, options)
+ InstallApkOnEmulator(apk_dest, options)
app_id = config.get('app_id')
number_of_events_to_generate = options.monkey_events
@@ -470,27 +488,28 @@
cmd = ['adb', 'shell', 'monkey', '-p', app_id, '-s', str(random_seed),
str(number_of_events_to_generate)]
- utils.PrintCmd(cmd)
try:
- stdout = subprocess.check_output(cmd)
+ stdout = utils.RunCmd(cmd, quiet=options.quiet)
succeeded = (
'Events injected: {}'.format(number_of_events_to_generate) in stdout)
except subprocess.CalledProcessError as e:
succeeded = False
- UninstallApkOnEmulator(app, config)
+ UninstallApkOnEmulator(app, config, options)
return succeeded
def LogResultsForApps(result_per_shrinker_per_app, options):
- for app, result_per_shrinker in result_per_shrinker_per_app.iteritems():
+ print('')
+ for app, result_per_shrinker in sorted(
+ result_per_shrinker_per_app.iteritems()):
LogResultsForApp(app, result_per_shrinker, options)
def LogResultsForApp(app, result_per_shrinker, options):
print(app + ':')
- if result_per_shrinker.get('status') != 'success':
+ if result_per_shrinker.get('status', 'success') != 'success':
error_message = result_per_shrinker.get('error_message')
print(' skipped ({})'.format(error_message))
return
@@ -597,6 +616,10 @@
action='store_true')
result.add_option('--gradle-flags', '--gradle_flags',
help='Flags to pass in to gradle')
+ result.add_option('--quiet',
+ help='Disable verbose logging',
+ default=False,
+ action='store_true')
(options, args) = result.parse_args(argv)
if options.disable_tot:
# r8.jar is required for recompiling the generated APK
@@ -610,32 +633,39 @@
global SHRINKERS
(options, args) = ParseOptions(argv)
- assert options.disable_tot or os.path.isfile(utils.R8_JAR), (
- 'Cannot build from ToT without r8.jar')
- assert options.disable_tot or os.path.isfile(utils.R8LIB_JAR), (
- 'Cannot build from ToT without r8lib.jar')
- if options.disable_tot:
- # Cannot run r8 lib without adding r8lib.jar as an dependency
- SHRINKERS = [
- shrinker for shrinker in SHRINKERS
- if 'minified' not in shrinker]
+ with utils.TempDir() as temp_dir:
+ if options.disable_tot:
+ # Cannot run r8 lib without adding r8lib.jar as an dependency
+ SHRINKERS = [
+ shrinker for shrinker in SHRINKERS
+ if 'minified' not in shrinker]
+ else:
+ if not options.no_build:
+ gradle.RunGradle(['r8', 'r8lib'])
- if not options.no_build and not options.disable_tot:
- gradle.RunGradle(['r8', 'r8lib'])
+ assert os.path.isfile(utils.R8_JAR), (
+ 'Cannot build from ToT without r8.jar')
+ assert os.path.isfile(utils.R8LIB_JAR), (
+ 'Cannot build from ToT without r8lib.jar')
- result_per_shrinker_per_app = {}
+ # Make a copy of r8.jar and r8lib.jar such that they stay the same for
+ # the entire execution of this script.
+ shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
+ shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
- if options.app:
- result_per_shrinker_per_app[options.app] = GetResultsForApp(
- options.app, APPS.get(options.app), options)
- else:
- for app, config in APPS.iteritems():
- if not config.get('skip', False):
- result_per_shrinker_per_app[app] = GetResultsForApp(
- app, config, options)
+ result_per_shrinker_per_app = {}
- LogResultsForApps(result_per_shrinker_per_app, options)
+ if options.app:
+ result_per_shrinker_per_app[options.app] = GetResultsForApp(
+ options.app, APPS.get(options.app), options, temp_dir)
+ else:
+ for app, config in sorted(APPS.iteritems()):
+ if not config.get('skip', False):
+ result_per_shrinker_per_app[app] = GetResultsForApp(
+ app, config, options, temp_dir)
+
+ LogResultsForApps(result_per_shrinker_per_app, options)
def success(message):
CGREEN = '\033[32m'
diff --git a/tools/utils.py b/tools/utils.py
index f8b4469..ad85089 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -12,8 +12,10 @@
import sys
import tarfile
import tempfile
+import zipfile
-ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
+ANDROID_JAR_DIR = 'third_party/android_jar/lib-v{api}'
+ANDROID_JAR = os.path.join(ANDROID_JAR_DIR, 'android.jar')
TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..'))
THIRD_PARTY = os.path.join(REPO_ROOT, 'third_party')
@@ -53,13 +55,93 @@
R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
RETRACE_JAR = os.path.join(THIRD_PARTY, 'proguard', 'proguard6.0.1', 'lib', 'retrace.jar')
-def PrintCmd(s):
- if type(s) is list:
- s = ' '.join(s)
- print 'Running: %s' % s
+# Common environment setup.
+USER_HOME = os.path.expanduser('~')
+ANDROID_HOME = os.path.join(USER_HOME, 'Android', 'Sdk')
+ANDROID_BUILD_TOOLS_VERSION = '28.0.3'
+ANDROID_BUILD_TOOLS = os.path.join(
+ ANDROID_HOME, 'build-tools', ANDROID_BUILD_TOOLS_VERSION)
+
+def Print(s, quiet=False):
+ if quiet:
+ return
+ print(s)
+
+def Warn(message):
+ CRED = '\033[91m'
+ CEND = '\033[0m'
+ print(CRED + message + CEND)
+
+def PrintCmd(cmd, env=None, quiet=False):
+ if quiet:
+ return
+ if type(cmd) is list:
+ cmd = ' '.join(cmd)
+ if env:
+ env = ' '.join(['{}=\"{}\"'.format(x, y) for x, y in env.iteritems()])
+ print('Running: {} {}'.format(env, cmd))
+ else:
+ print('Running: {}'.format(cmd))
# I know this will hit os on windows eventually if we don't do this.
sys.stdout.flush()
+class ProgressLogger(object):
+ CLEAR_LINE = '\033[K'
+ UP = '\033[F'
+
+ def __init__(self, quiet=False):
+ self._count = 0
+ self._has_printed = False
+ self._quiet = quiet
+
+ def log(self, text):
+ if self._quiet:
+ if self._has_printed:
+ sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+ if len(text) > 140:
+ text = text[0:140] + '...'
+ print(text)
+ self._has_printed = True
+
+ def done(self):
+ if self._quiet and self._has_printed:
+ sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+ print('')
+ sys.stdout.write(ProgressLogger.UP)
+
+def RunCmd(cmd, env_vars=None, quiet=False):
+ PrintCmd(cmd, env=env_vars, quiet=quiet)
+ env = os.environ.copy()
+ if env_vars:
+ env.update(env_vars)
+ process = subprocess.Popen(
+ cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ stdout = []
+ logger = ProgressLogger(quiet=quiet)
+ failed = False
+ while True:
+ line = process.stdout.readline()
+ if line != b'':
+ stripped = line.rstrip()
+ stdout.append(stripped)
+ logger.log(stripped)
+
+ # TODO(christofferqa): r8 should fail with non-zero exit code.
+ if ('AssertionError:' in stripped
+ or 'CompilationError:' in stripped
+ or 'CompilationFailedException:' in stripped
+ or 'Compilation failed' in stripped):
+ failed = True
+ else:
+ logger.done()
+ exit_code = process.poll()
+ if exit_code or failed:
+ for line in stdout:
+ Warn(line)
+ raise subprocess.CalledProcessError(
+ exit_code, cmd, output='\n'.join(stdout))
+ return stdout
+
def IsWindows():
return os.name == 'nt'
@@ -195,16 +277,19 @@
shutil.rmtree(self._temp_dir, ignore_errors=True)
class ChangedWorkingDirectory(object):
- def __init__(self, working_directory):
+ def __init__(self, working_directory, quiet=False):
+ self._quiet = quiet
self._working_directory = working_directory
def __enter__(self):
self._old_cwd = os.getcwd()
- print 'Enter directory = ', self._working_directory
+ if not self._quiet:
+ print 'Enter directory:', self._working_directory
os.chdir(self._working_directory)
def __exit__(self, *_):
- print 'Enter directory = ', self._old_cwd
+ if not self._quiet:
+ print 'Enter directory:', self._old_cwd
os.chdir(self._old_cwd)
# Reading Android CTS test_result.xml
@@ -308,8 +393,29 @@
if m is not None:
raise Exception("Do not use google JVM for benchmarking: " + version)
+def get_android_jar_dir(api):
+ return os.path.join(REPO_ROOT, ANDROID_JAR_DIR.format(api=api))
+
def get_android_jar(api):
return os.path.join(REPO_ROOT, ANDROID_JAR.format(api=api))
+def get_android_optional_jars(api):
+ android_optional_jars_dir = os.path.join(get_android_jar_dir(api), 'optional')
+ android_optional_jars = [
+ os.path.join(android_optional_jars_dir, 'android.test.base.jar'),
+ os.path.join(android_optional_jars_dir, 'android.test.mock.jar'),
+ os.path.join(android_optional_jars_dir, 'android.test.runner.jar'),
+ os.path.join(android_optional_jars_dir, 'org.apache.http.legacy.jar')
+ ]
+ return [
+ android_optional_jar for android_optional_jar in android_optional_jars
+ if os.path.isfile(android_optional_jar)]
+
def is_bot():
return 'BUILDBOT_BUILDERNAME' in os.environ
+
+
+def uncompressed_size(path):
+ return sum(z.file_size for z in zipfile.ZipFile(path).infolist())
+
+