Merge "Revert "Fix bug making us not inline when a simple class without clinit had static fields with nothing setting them.""
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/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..c59b56b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -194,6 +194,7 @@
new CfApplicationWriter(
application,
options,
+ marker,
deadCode,
graphLense,
namingLens,
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 83d115d..b5814d2 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.2-dev";
private Version() {
}
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/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/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/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..37b3d1b 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);
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..5d2daed 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
@@ -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/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/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/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/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/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 9118fda..31a76ab 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 =
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/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/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/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/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/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/tools/apk_masseur.py b/tools/apk_masseur.py
index 6013e6f..fa46485 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,44 @@
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)
+ 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 +88,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 9c2c8c8..10fb29f 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -30,23 +30,15 @@
'Unexpected line with \'dependencies {\'')
is_inside_dependencies = True
if is_inside_dependencies:
- if '/r8.jar' in stripped:
- if minified:
- # Skip line to avoid dependency on r8.jar
- continue
- added_r8_dependency = True
- elif '/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 = os.path.join(temp_dir, 'r8lib.jar' if minified else '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)
@@ -147,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)
@@ -158,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:
@@ -181,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/run_on_as_app.py b/tools/run_on_as_app.py
index 128b6e3..e084bad 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -42,6 +42,8 @@
'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',
@@ -49,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'
@@ -69,6 +75,14 @@
'app_id': 'org.schabi.newpipe',
'git_repo': 'https://github.com/christofferqa/NewPipe',
},
+ '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',
@@ -89,18 +103,6 @@
# 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',
- },
# This does not build yet.
'muzei': {
'git_repo': 'https://github.com/sgjesse/muzei.git',
@@ -128,7 +130,7 @@
dex_size += z.getinfo(filename).file_size
return dex_size
-def IsBuiltWithR8(apk, temp_dir):
+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.
@@ -138,7 +140,7 @@
script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
cmd = ['python', script, apk]
- utils.PrintCmd(cmd)
+ utils.PrintCmd(cmd, quiet=options.quiet)
return '~~R8' in subprocess.check_output(cmd).strip()
def IsMinifiedR8(shrinker):
@@ -157,9 +159,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:
@@ -167,7 +173,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],
@@ -215,10 +221,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')
@@ -240,7 +246,7 @@
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
@@ -280,15 +286,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,
- temp_dir, options, extra_env_vars)
+ temp_dir, options, keepRuleSynthesisForRecompilation=True)
dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
recompilation_result = {
'apk_dest': apk_dest,
@@ -314,8 +314,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, temp_dir)
+ 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',
@@ -334,13 +335,20 @@
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, temp_dir, options,
- env_vars=None):
- print()
- print('Building {} with {}'.format(app, shrinker))
+ 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:
@@ -361,11 +369,9 @@
as_utils.SetPrintConfigurationDirective(
app, config, checkout_dir, proguard_config_dest)
- env = os.environ.copy()
+ env = {}
env['ANDROID_HOME'] = android_home
env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
- if env_vars:
- env.update(env_vars)
releaseTarget = config.get('releaseTarget')
if not releaseTarget:
@@ -381,20 +387,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')
@@ -425,25 +423,27 @@
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, temp_dir) == ('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,
- temp_dir):
+ 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 = utils.get_android_jar(compile_sdk)
r8_jar = os.path.join(
@@ -462,20 +462,19 @@
cmd.append('--lib')
cmd.append(android_optional_jar)
- utils.PrintCmd(cmd)
-
- subprocess.check_output(cmd)
+ 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
@@ -486,27 +485,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
@@ -613,6 +613,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
@@ -653,7 +657,7 @@
result_per_shrinker_per_app[options.app] = GetResultsForApp(
options.app, APPS.get(options.app), options, temp_dir)
else:
- for app, config in APPS.iteritems():
+ 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)
diff --git a/tools/utils.py b/tools/utils.py
index 72febdc..8c37a55 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -54,13 +54,86 @@
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
+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'
@@ -196,16 +269,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