Merge "Allow larger line ranges in map files for class file output"
diff --git a/build.gradle b/build.gradle
index ec53231..2671227 100644
--- a/build.gradle
+++ b/build.gradle
@@ -472,9 +472,11 @@
// Default disable errorprone (enabled and setup below).
options.errorprone.enabled = false
options.compilerArgs << '-Xlint:unchecked'
+ // Run all compilation tasks in a forked subprocess.
+ options.fork = true
// Javac often runs out of stack space when compiling the tests.
// Increase the stack size for the javac process.
- options.forkOptions.jvmArgs << "-Xss8m"
+ options.forkOptions.jvmArgs << "-Xss4m"
// Set the bootclass path so compilation is consistent with 1.8 target compatibility.
options.forkOptions.jvmArgs << "-Xbootclasspath/a:third_party/openjdk/openjdk-rt-1.8/rt.jar"
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 2d19d9d..4f472c6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -615,15 +615,17 @@
new DiscardedChecker(rootSet, application, options).run();
}
- timing.begin("Minification");
- // If we did not have keep rules, everything will be marked as keep, so no minification
- // will happen. Just avoid the overhead.
- NamingLens namingLens =
- options.enableMinification
- ? new Minifier(appView.appInfo().withLiveness(), rootSet, desugaredCallSites, options)
- .run(timing)
- : NamingLens.getIdentityLens();
- timing.end();
+ // Perform minification.
+ NamingLens namingLens;
+ if (options.enableMinification) {
+ timing.begin("Minification");
+ namingLens =
+ new Minifier(appView.appInfo().withLiveness(), rootSet, desugaredCallSites, options)
+ .run(timing);
+ timing.end();
+ } else {
+ namingLens = NamingLens.getIdentityLens();
+ }
ProguardMapSupplier proguardMapSupplier;
diff --git a/src/main/java/com/android/tools/r8/code/InstructionFactory.java b/src/main/java/com/android/tools/r8/code/InstructionFactory.java
index 14d2f4b..83b0570 100644
--- a/src/main/java/com/android/tools/r8/code/InstructionFactory.java
+++ b/src/main/java/com/android/tools/r8/code/InstructionFactory.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.code;
import com.android.tools.r8.graph.OffsetToObjectMapping;
-import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -18,11 +17,6 @@
return create(high, opcode, stream, mapping);
}
- public Instruction[] readSequenceFrom(ByteBuffer buffer, int startIndex, int length,
- OffsetToObjectMapping mapping) {
- return readSequenceFrom(buffer.asShortBuffer(), startIndex, length, mapping);
- }
-
public Instruction[] readSequenceFrom(ShortBuffer buffer, int startIndex, int length,
OffsetToObjectMapping mapping) {
ShortBufferBytecodeStream range =
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 825846e..26398d8 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -281,7 +281,7 @@
}
// Release use of the backing buffer now that accept has returned.
data.invalidate();
- byteBufferProvider.releaseByteBuffer(result.buffer);
+ byteBufferProvider.releaseByteBuffer(result.buffer.asByteBuffer());
return true;
}));
}
diff --git a/src/main/java/com/android/tools/r8/dex/BinaryReader.java b/src/main/java/com/android/tools/r8/dex/BinaryReader.java
index 5d51238..dd1cfee 100644
--- a/src/main/java/com/android/tools/r8/dex/BinaryReader.java
+++ b/src/main/java/com/android/tools/r8/dex/BinaryReader.java
@@ -10,14 +10,13 @@
import com.android.tools.r8.utils.LebUtils;
import com.android.tools.r8.utils.StreamUtils;
import java.io.IOException;
-import java.nio.ByteBuffer;
/**
* Base class for reading binary content.
*/
public abstract class BinaryReader {
protected final Origin origin;
- protected final ByteBuffer buffer;
+ protected final CompatByteBuffer buffer;
protected BinaryReader(ProgramResource resource) throws ResourceException, IOException {
this(resource.getOrigin(), StreamUtils.StreamToByteArrayClose(resource.getByteStream()));
@@ -26,7 +25,7 @@
protected BinaryReader(Origin origin, byte[] bytes) {
assert origin != null;
this.origin = origin;
- buffer = ByteBuffer.wrap(bytes);
+ buffer = CompatByteBuffer.wrap(bytes);
}
public Origin getOrigin() {
diff --git a/src/main/java/com/android/tools/r8/dex/CompatByteBuffer.java b/src/main/java/com/android/tools/r8/dex/CompatByteBuffer.java
new file mode 100644
index 0000000..6e32c6e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/CompatByteBuffer.java
@@ -0,0 +1,135 @@
+// 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.dex;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+
+/**
+ * In JDK 9 ByteBuffer ByteBuffer.position(int) started overriding Buffer Buffer.position(int) along
+ * with various other methods defined on java.nio.Buffer. To avoid issues with running R8 on JDK 8
+ * we internally wrap any use of ByteBuffer and make sure access to the overridden methods happens
+ * only at the super type Buffer which is known to be in both JDKs.
+ */
+public class CompatByteBuffer {
+
+ private final ByteBuffer buffer;
+
+ public CompatByteBuffer(ByteBuffer buffer) {
+ this.buffer = buffer;
+ }
+
+ public static CompatByteBuffer wrap(byte[] bytes) {
+ return new CompatByteBuffer(ByteBuffer.wrap(bytes));
+ }
+
+ private Buffer asBuffer() {
+ return buffer;
+ }
+
+ public ByteBuffer asByteBuffer() {
+ return buffer;
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // 1.8 compatible calls to java.nio.Buffer methods.
+ // ----------------------------------------------------------------------------------------------
+
+ // The position(int) became overridden with a ByteBuffer return value.
+ public void position(int newPosition) {
+ asBuffer().position(newPosition);
+ }
+
+ // The rewind() became overridden with a ByteBuffer return value.
+ public void rewind() {
+ asBuffer().rewind();
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods on java.nio.Buffer that are safely overridden or not non-overridden.
+ // ----------------------------------------------------------------------------------------------
+
+ // Note: array() overrides Buffer.array() in JDK 8 too, so that is safe for JDK 8 and 9.
+ public byte[] array() {
+ return asByteBuffer().array();
+ }
+
+ public int arrayOffset() {
+ return asByteBuffer().arrayOffset();
+ }
+
+ public int capacity() {
+ return asByteBuffer().capacity();
+ }
+
+ public boolean hasArray() {
+ return asByteBuffer().hasArray();
+ }
+
+ public boolean hasRemaining() {
+ return asByteBuffer().hasRemaining();
+ }
+
+ public int position() {
+ return asByteBuffer().position();
+ }
+
+ public int remaining() {
+ return asByteBuffer().remaining();
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods on java.nio.ByteBuffer.
+ // ----------------------------------------------------------------------------------------------
+
+ public ShortBuffer asShortBuffer() {
+ return asByteBuffer().asShortBuffer();
+ }
+
+ public void order(ByteOrder bo) {
+ asByteBuffer().order(bo);
+ }
+
+ public byte get() {
+ return asByteBuffer().get();
+ }
+
+ public byte get(int index) {
+ return asByteBuffer().get(index);
+ }
+
+ public void get(byte[] dst) {
+ asByteBuffer().get(dst);
+ }
+
+ public int getInt() {
+ return asByteBuffer().getInt();
+ }
+
+ public int getInt(int offset) {
+ return asByteBuffer().getInt(offset);
+ }
+
+ public short getShort() {
+ return asByteBuffer().getShort();
+ }
+
+ public void put(byte aByte) {
+ asByteBuffer().put(aByte);
+ }
+
+ public void putShort(short aShort) {
+ asByteBuffer().putShort(aShort);
+ }
+
+ public void putInt(int anInteger) {
+ asByteBuffer().putInt(anInteger);
+ }
+
+ public void put(byte[] bytes) {
+ asByteBuffer().put(bytes);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
index 000bc0e..2d1da51 100644
--- a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
@@ -22,7 +22,7 @@
private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
private final ByteBufferProvider byteBufferProvider;
- private ByteBuffer byteBuffer;
+ private CompatByteBuffer byteBuffer;
@VisibleForTesting
DexOutputBuffer() {
@@ -37,16 +37,16 @@
private void ensureSpaceFor(int bytes) {
if (byteBuffer.remaining() < bytes) {
int newSize = byteBuffer.capacity() + Math.max(byteBuffer.capacity(), bytes * 2);
- ByteBuffer newBuffer = allocateByteBuffer(newSize);
- System.arraycopy(byteBuffer.array(), 0, newBuffer.array(), 0, byteBuffer.position());
+ CompatByteBuffer newBuffer = allocateByteBuffer(newSize);
+ System.arraycopy(byteBuffer.array(), 0, newBuffer.array(), 0, position());
newBuffer.position(byteBuffer.position());
freeByteBuffer(byteBuffer);
byteBuffer = newBuffer;
}
}
- private ByteBuffer allocateByteBuffer(int size) {
- ByteBuffer buffer = byteBufferProvider.acquireByteBuffer(size);
+ private CompatByteBuffer allocateByteBuffer(int size) {
+ CompatByteBuffer buffer = new CompatByteBuffer(byteBufferProvider.acquireByteBuffer(size));
if (!buffer.hasArray()) {
throw new CompilationError(
"Provided byte-buffer is required to have an array backing, but does not.");
@@ -69,9 +69,9 @@
return buffer;
}
- private void freeByteBuffer(ByteBuffer buffer) {
+ private void freeByteBuffer(CompatByteBuffer buffer) {
assert buffer != null;
- byteBufferProvider.releaseByteBuffer(buffer);
+ byteBufferProvider.releaseByteBuffer(buffer.asByteBuffer());
}
public void putUleb128(int value) {
@@ -133,7 +133,7 @@
public int align(int bytes) {
assert bytes > 0;
int mask = bytes - 1;
- int newPosition = (byteBuffer.position() + mask) & ~mask;
+ int newPosition = (position() + mask) & ~mask;
ensureSpaceFor(newPosition - position());
byteBuffer.position(newPosition);
return newPosition;
@@ -165,8 +165,8 @@
return byteBuffer.array();
}
- public ByteBuffer stealByteBuffer() {
- ByteBuffer buffer = byteBuffer;
+ public CompatByteBuffer stealByteBuffer() {
+ CompatByteBuffer buffer = byteBuffer;
byteBuffer = null;
return buffer;
}
diff --git a/src/main/java/com/android/tools/r8/dex/DexReader.java b/src/main/java/com/android/tools/r8/dex/DexReader.java
index d8997f7..45a0696 100644
--- a/src/main/java/com/android/tools/r8/dex/DexReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexReader.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.utils.DexVersion;
import java.io.IOException;
import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
@@ -38,7 +37,7 @@
}
// Parse the magic header and determine the dex file version.
- private int parseMagic(ByteBuffer buffer) {
+ private int parseMagic(CompatByteBuffer buffer) {
try {
buffer.get();
buffer.rewind();
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 1af80fa..4197da5 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -57,7 +57,6 @@
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collection;
@@ -77,10 +76,10 @@
/** Simple pair of a byte buffer and its written length. */
public static class ByteBufferResult {
// Ownership of the buffer is transferred to the receiver of this result structure.
- public final ByteBuffer buffer;
+ public final CompatByteBuffer buffer;
public final int length;
- private ByteBufferResult(ByteBuffer buffer, int length) {
+ private ByteBufferResult(CompatByteBuffer buffer, int length) {
this.buffer = buffer;
this.length = length;
}
diff --git a/src/main/java/com/android/tools/r8/dex/VDexReader.java b/src/main/java/com/android/tools/r8/dex/VDexReader.java
index a3a5a2f..8d6f2d0 100644
--- a/src/main/java/com/android/tools/r8/dex/VDexReader.java
+++ b/src/main/java/com/android/tools/r8/dex/VDexReader.java
@@ -14,7 +14,6 @@
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
@@ -37,7 +36,7 @@
}
// Parse the magic header and determine the dex file version.
- private int parseMagic(ByteBuffer buffer) {
+ private int parseMagic(CompatByteBuffer buffer) {
int index = 0;
for (byte prefixByte : VDEX_FILE_MAGIC_PREFIX) {
if (buffer.get(index++) != prefixByte) {
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index e3fa01b..88b3b4b 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -15,6 +15,7 @@
TargetedBySuper,
InvokedFrom,
InvokedFromLambdaCreatedIn,
+ AnnotatedOn,
ReferencedFrom,
ReflectiveUseFrom,
ReachableFromLiveType,
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 28aabea..5e65aa3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -38,22 +38,17 @@
public DexTypeList interfaces;
public DexString sourceFile;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedField[] staticFields;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedField[] instanceFields;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedMethod[] directMethods;
- /**
- * Access has to be synchronized during concurrent collection/writing phase.
- */
- protected DexEncodedMethod[] virtualMethods;
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedField[] staticFields = NO_FIELDS;
+
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedField[] instanceFields = NO_FIELDS;
+
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedMethod[] directMethods = NO_METHODS;
+
+ /** Access has to be synchronized during concurrent collection/writing phase. */
+ protected DexEncodedMethod[] virtualMethods = NO_METHODS;
/** Enclosing context of this class if it is an inner class, null otherwise. */
private EnclosingMethodAttribute enclosingMethod;
@@ -146,7 +141,8 @@
System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
newMethods[directMethods.length] = method;
directMethods = newMethods;
- assert verifyNoDuplicateMethods(directMethods);
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
}
public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
@@ -158,7 +154,8 @@
i++;
}
directMethods = newMethods;
- assert verifyNoDuplicateMethods(directMethods);
+ assert verifyCorrectnessOfMethodHolders(methods);
+ assert verifyNoDuplicateMethods();
}
public void removeDirectMethod(int index) {
@@ -170,12 +167,14 @@
public void setDirectMethod(int index, DexEncodedMethod method) {
directMethods[index] = method;
- assert verifyNoDuplicateMethods(directMethods);
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
}
public void setDirectMethods(DexEncodedMethod[] values) {
directMethods = MoreObjects.firstNonNull(values, NO_METHODS);
- assert verifyNoDuplicateMethods(directMethods);
+ assert verifyCorrectnessOfMethodHolders(directMethods());
+ assert verifyNoDuplicateMethods();
}
public List<DexEncodedMethod> virtualMethods() {
@@ -191,7 +190,8 @@
System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
newMethods[virtualMethods.length] = method;
virtualMethods = newMethods;
- assert verifyNoDuplicateMethods(virtualMethods);
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
}
public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
@@ -203,7 +203,8 @@
i++;
}
virtualMethods = newMethods;
- assert verifyNoDuplicateMethods(virtualMethods);
+ assert verifyCorrectnessOfMethodHolders(methods);
+ assert verifyNoDuplicateMethods();
}
public void removeVirtualMethod(int index) {
@@ -216,22 +217,39 @@
public void setVirtualMethod(int index, DexEncodedMethod method) {
virtualMethods[index] = method;
- assert verifyNoDuplicateMethods(virtualMethods);
+ assert verifyCorrectnessOfMethodHolder(method);
+ assert verifyNoDuplicateMethods();
}
public void setVirtualMethods(DexEncodedMethod[] values) {
virtualMethods = MoreObjects.firstNonNull(values, NO_METHODS);
- assert verifyNoDuplicateMethods(virtualMethods);
+ assert verifyCorrectnessOfMethodHolders(virtualMethods());
+ assert verifyNoDuplicateMethods();
}
- private boolean verifyNoDuplicateMethods(DexEncodedMethod[] methods) {
+ private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) {
+ assert method.method.holder == type
+ : "Expected method `"
+ + method.method.toSourceString()
+ + "` to have holder `"
+ + type.toSourceString()
+ + "`";
+ return true;
+ }
+
+ private boolean verifyCorrectnessOfMethodHolders(Iterable<DexEncodedMethod> methods) {
+ for (DexEncodedMethod method : methods) {
+ assert verifyCorrectnessOfMethodHolder(method);
+ }
+ return true;
+ }
+
+ private boolean verifyNoDuplicateMethods() {
Set<DexMethod> unique = Sets.newIdentityHashSet();
- Arrays.stream(methods)
- .forEach(
- method -> {
- boolean changed = unique.add(method.method);
- assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
- });
+ for (DexEncodedMethod method : methods()) {
+ boolean changed = unique.add(method.method);
+ assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+ }
return true;
}
@@ -651,6 +669,8 @@
public boolean isValid() {
assert !isInterface()
|| Arrays.stream(virtualMethods).noneMatch(method -> method.accessFlags.isFinal());
+ assert verifyCorrectnessOfMethodHolders(methods());
+ assert verifyNoDuplicateMethods();
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 1757459..e7b3967 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -140,6 +140,8 @@
public final DexString contentEqualsMethodName = createString("contentEquals");
public final DexString indexOfMethodName = createString("indexOf");
public final DexString lastIndexOfMethodName = createString("lastIndexOf");
+ public final DexString compareToMethodName = createString("compareTo");
+ public final DexString compareToIgnoreCaseMethodName = createString("compareToIgnoreCase");
public final DexString cloneMethodName = createString("clone");
public final DexString valueOfMethodName = createString("valueOf");
@@ -613,6 +615,8 @@
public final DexMethod indexOfString;
public final DexMethod lastIndexOfInt;
public final DexMethod lastIndexOfString;
+ public final DexMethod compareTo;
+ public final DexMethod compareToIgnoreCase;
public final DexMethod valueOf;
public final DexMethod toString;
@@ -649,6 +653,11 @@
createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneString);
lastIndexOfInt =
createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneInt);
+ compareTo =
+ createMethod(stringDescriptor, compareToMethodName, intDescriptor, needsOneString);
+ compareToIgnoreCase =
+ createMethod(stringDescriptor, compareToIgnoreCaseMethodName, intDescriptor,
+ needsOneString);
valueOf = createMethod(
stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 55bc508..977c90b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -292,6 +292,13 @@
debugData = null;
}
+ public boolean hasSameOrNoLocal(Value other) {
+ assert other != null;
+ return hasLocalInfo()
+ ? other.getLocalInfo() == this.getLocalInfo()
+ : !other.hasLocalInfo();
+ }
+
public List<Instruction> getDebugLocalStarts() {
if (debugData == null) {
return Collections.emptyList();
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 30712e6..f7baf3e 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
@@ -48,6 +48,7 @@
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
@@ -1632,10 +1633,30 @@
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
- if (invoke.outValue() != null && !invoke.outValue().hasLocalInfo()) {
+ Value outValue = invoke.outValue();
+ // TODO(b/124246610): extend to other variants that receive error messages or supplier.
+ if (invoke.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
+ Value obj = invoke.arguments().get(0);
+ if ((outValue == null && obj.hasLocalInfo())
+ || (outValue != null && !obj.hasSameOrNoLocal(outValue))) {
+ continue;
+ }
+ Nullability nullability = obj.getTypeLattice().nullability();
+ if (nullability.isDefinitelyNotNull()) {
+ if (outValue != null) {
+ outValue.replaceUsers(obj);
+ needToNarrowValues.addAll(outValue.affectedValues());
+ }
+ iterator.removeOrReplaceByDebugLocalRead();
+ } else if (nullability.isDefinitelyNull()) {
+ // TODO(b/124246610): throw NPE.
+ // Refactor UninstantiatedTypeOptimization#replaceCurrentInstructionWithThrowNull
+ // and move it to iterator.
+ }
+ } else if (outValue != null && !outValue.hasLocalInfo()) {
if (libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod())) {
if (checkArgumentType(invoke, 0)) {
- invoke.outValue().replaceUsers(invoke.arguments().get(0));
+ outValue.replaceUsers(invoke.arguments().get(0));
invoke.setOutValue(null);
}
} else if (appInfoWithLiveness != null) {
@@ -1650,7 +1671,6 @@
// Replace the out value of the invoke with the argument and ignore the out value.
if (argumentIndex >= 0 && checkArgumentType(invoke, argumentIndex)) {
Value argument = invoke.arguments().get(argumentIndex);
- Value outValue = invoke.outValue();
assert outValue.verifyCompatible(argument.outType());
if (argument.getTypeLattice().lessThanOrEqual(
outValue.getTypeLattice(), appInfo)) {
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 3fc442a..5dfbb78 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
@@ -16,6 +16,7 @@
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.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.EscapeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -53,16 +54,18 @@
// int String#length()
// boolean String#isEmpty()
- // boolean String#startsWith(str)
- // boolean String#endsWith(str)
- // boolean String#contains(str)
- // boolean String#equals(str)
- // boolean String#equalsIgnoreCase(str)
- // boolean String#contentEquals(str)
- // int String#indexOf(str)
+ // boolean String#startsWith(String)
+ // boolean String#endsWith(String)
+ // boolean String#contains(String)
+ // boolean String#equals(String)
+ // boolean String#equalsIgnoreCase(String)
+ // boolean String#contentEquals(String)
+ // int String#indexOf(String)
// int String#indexOf(int)
- // int String#lastIndexOf(str)
+ // int String#lastIndexOf(String)
// int String#lastIndexOf(int)
+ // int String#compareTo(String)
+ // int String#compareToIgnoreCase(String)
public void computeTrivialOperationsOnConstString(IRCode code) {
if (!code.hasConstString) {
return;
@@ -102,6 +105,10 @@
operatorWithInt = String::lastIndexOf;
} else if (invokedMethod == factory.stringMethods.lastIndexOfString) {
operatorWithString = String::lastIndexOf;
+ } else if (invokedMethod == factory.stringMethods.compareTo) {
+ operatorWithString = String::compareTo;
+ } else if (invokedMethod == factory.stringMethods.compareToIgnoreCase) {
+ operatorWithString = String::compareToIgnoreCase;
} else {
continue;
}
@@ -111,12 +118,12 @@
|| !rcv.isConstant()) {
continue;
}
+ DexString rcvString = rcv.definition.asConstString().getValue();
ConstNumber constNumber;
if (operatorWithNoArg != null) {
assert invoke.inValues().size() == 1;
- ConstString rcvString = rcv.definition.asConstString();
- int v = operatorWithNoArg.apply(rcvString.getValue().toString());
+ int v = operatorWithNoArg.apply(rcvString.toString());
constNumber = code.createIntConstant(v);
} else if (operatorWithString != null) {
assert invoke.inValues().size() == 2;
@@ -126,10 +133,9 @@
|| !arg.isConstant()) {
continue;
}
- ConstString rcvString = rcv.definition.asConstString();
- ConstString argString = arg.definition.asConstString();
int v = operatorWithString.apply(
- rcvString.getValue().toString(), argString.getValue().toString());
+ rcvString.toString(),
+ arg.definition.asConstString().getValue().toString());
constNumber = code.createIntConstant(v);
} else {
assert operatorWithInt != null;
@@ -140,10 +146,9 @@
|| !arg.isConstant()) {
continue;
}
- ConstString rcvString = rcv.definition.asConstString();
- ConstNumber argInt = arg.definition.asConstNumber();
int v = operatorWithInt.apply(
- rcvString.getValue().toString(), argInt.getIntValue());
+ rcvString.toString(),
+ arg.definition.asConstNumber().getIntValue());
constNumber = code.createIntConstant(v);
}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index bbb3391..90dbbdb 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.function.Function;
@@ -33,7 +34,7 @@
}
}
- Map<DexField, DexString> computeRenaming(Timing timing) {
+ FieldRenaming computeRenaming(Timing timing) {
// Reserve names in all classes first. We do this in subtyping order so we do not
// shadow a reserved field in subclasses. While there is no concept of virtual field
// dispatch in Java, field resolution still traverses the super type chain and external
@@ -55,7 +56,20 @@
timing.begin("rename-references");
renameNonReboundReferences();
timing.end();
- return renaming;
+ return new FieldRenaming(renaming);
+ }
+
+ static class FieldRenaming {
+
+ final Map<DexField, DexString> renaming;
+
+ private FieldRenaming(Map<DexField, DexString> renaming) {
+ this.renaming = renaming;
+ }
+
+ public static FieldRenaming empty() {
+ return new FieldRenaming(ImmutableMap.of());
+ }
}
private void reserveNamesInSubtypes(DexType type, NamingState<DexType, ?> state) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 5f990ac..21e6bf9 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
@@ -96,6 +97,8 @@
private final Equivalence<DexMethod> equivalence;
private final Map<DexCallSite, DexString> callSiteRenaming = new IdentityHashMap<>();
+ private final Map<DexType, DexType> frontierMap = new IdentityHashMap<>();
+
MethodNameMinifier(
AppInfoWithLiveness appInfo,
RootSet rootSet,
@@ -121,6 +124,7 @@
}
static class MethodRenaming {
+
final Map<DexMethod, DexString> renaming;
final Map<DexCallSite, DexString> callSiteRenaming;
@@ -129,26 +133,27 @@
this.renaming = renaming;
this.callSiteRenaming = callSiteRenaming;
}
+
+ public static MethodRenaming empty() {
+ return new MethodRenaming(ImmutableMap.of(), ImmutableMap.of());
+ }
}
MethodRenaming computeRenaming(Timing timing) {
// Phase 1: Reserve all the names that need to be kept and allocate linked state in the
// library part.
timing.begin("Phase 1");
- Map<DexType, DexType> frontierMap = new IdentityHashMap<>();
- reserveNamesInClasses(appInfo.dexItemFactory.objectType,
- appInfo.dexItemFactory.objectType,
- null, frontierMap);
+ reserveNamesInClasses(
+ appInfo.dexItemFactory.objectType, appInfo.dexItemFactory.objectType, null);
timing.end();
// Phase 2: Reserve all the names that are required for interfaces.
timing.begin("Phase 2");
- DexType.forAllInterfaces(
- appInfo.dexItemFactory, iface -> reserveNamesInInterfaces(iface, frontierMap));
+ DexType.forAllInterfaces(appInfo.dexItemFactory, this::reserveNamesInInterfaces);
timing.end();
// Phase 3: Assign names to interface methods. These are assigned by finding a name that is
// free in all naming states that may hold an implementation.
timing.begin("Phase 3");
- assignNamesToInterfaceMethods(frontierMap, timing);
+ assignNamesToInterfaceMethods(timing);
timing.end();
// Phase 4: Assign names top-down by traversing the subtype hierarchy.
timing.begin("Phase 4");
@@ -201,8 +206,7 @@
}
}
- private Set<NamingState<DexProto, ?>> getReachableStates(DexType type,
- Map<DexType, DexType> frontierMap) {
+ private Set<NamingState<DexProto, ?>> getReachableStates(DexType type) {
Set<DexType> interfaces = Sets.newIdentityHashSet();
interfaces.add(type);
collectSuperInterfaces(type, interfaces);
@@ -221,7 +225,7 @@
return reachableStates;
}
- private void assignNamesToInterfaceMethods(Map<DexType, DexType> frontierMap, Timing timing) {
+ private void assignNamesToInterfaceMethods(Timing timing) {
// First compute a map from method signatures to a set of naming states for interfaces and
// frontier states of classes that implement them. We add the frontier states so that we can
// reserve the names for later method naming.
@@ -232,17 +236,20 @@
Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap = new HashMap<>();
// A map from DexMethods to the first interface state it was seen in. Used to pick good names.
Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates = new HashMap<>();
- DexType.forAllInterfaces(appInfo.dexItemFactory, iface -> {
- assert iface.isInterface();
- DexClass clazz = appInfo.definitionFor(iface);
- if (clazz != null) {
- Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap);
- for (DexEncodedMethod method : shuffleMethods(clazz.methods())) {
- addStatesToGlobalMapForMethod(
- method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
- }
- }
- });
+ DexType.forAllInterfaces(
+ appInfo.dexItemFactory,
+ iface -> {
+ assert iface.isInterface();
+ DexClass clazz = appInfo.definitionFor(iface);
+ if (clazz != null) {
+ assert clazz.isInterface();
+ Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface);
+ for (DexEncodedMethod method : shuffleMethods(clazz.methods())) {
+ addStatesToGlobalMapForMethod(
+ method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
+ }
+ }
+ });
// Collect the live call sites for multi-interface lambda expression renaming. For code with
// desugared lambdas this is a conservative estimate, as we don't track if the generated
// lambda classes survive into the output. As multi-interface lambda expressions are rare
@@ -272,7 +279,7 @@
for (DexEncodedMethod method : implementedMethods) {
DexType iface = method.method.holder;
assert iface.isInterface();
- Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap);
+ Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface);
addStatesToGlobalMapForMethod(
method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
callSiteMethods.add(equivalence.wrap(method.method));
@@ -434,34 +441,33 @@
return false;
}
- private void reserveNamesInClasses(DexType type, DexType libraryFrontier,
- NamingState<DexProto, ?> parent,
- Map<DexType, DexType> frontierMap) {
+ private void reserveNamesInClasses(
+ DexType type, DexType libraryFrontier, NamingState<DexProto, ?> parent) {
assert !type.isInterface();
DexClass holder = appInfo.definitionFor(type);
- NamingState<DexProto, ?> state = allocateNamingStateAndReserve(holder, type, libraryFrontier,
- parent, frontierMap);
+ NamingState<DexProto, ?> state =
+ allocateNamingStateAndReserve(holder, type, libraryFrontier, parent);
// If this is a library class (or effectively a library class as it is missing) move the
// frontier forward.
- type.forAllExtendsSubtypes(subtype -> {
- assert !subtype.isInterface();
- reserveNamesInClasses(subtype,
- holder == null || holder.isLibraryClass() ? subtype : libraryFrontier,
- state, frontierMap);
- });
+ type.forAllExtendsSubtypes(
+ subtype -> {
+ assert !subtype.isInterface();
+ reserveNamesInClasses(
+ subtype,
+ holder == null || holder.isLibraryClass() ? subtype : libraryFrontier,
+ state);
+ });
}
- private void reserveNamesInInterfaces(DexType type, Map<DexType, DexType> frontierMap) {
+ private void reserveNamesInInterfaces(DexType type) {
assert type.isInterface();
frontierMap.put(type, type);
DexClass holder = appInfo.definitionFor(type);
- allocateNamingStateAndReserve(holder, type, type, null, frontierMap);
+ allocateNamingStateAndReserve(holder, type, type, null);
}
- private NamingState<DexProto, ?> allocateNamingStateAndReserve(DexClass holder, DexType type,
- DexType libraryFrontier,
- NamingState<DexProto, ?> parent,
- Map<DexType, DexType> frontierMap) {
+ private NamingState<DexProto, ?> allocateNamingStateAndReserve(
+ DexClass holder, DexType type, DexType libraryFrontier, NamingState<DexProto, ?> parent) {
frontierMap.put(type, libraryFrontier);
NamingState<DexProto, ?> state =
computeStateIfAbsent(
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index e4ccbcd..becc643 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
+import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -56,21 +57,28 @@
ClassNameMinifier classNameMinifier = new ClassNameMinifier(appInfo, rootSet, options);
ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
timing.end();
+
+ assert new MinifiedRenaming(
+ classRenaming, MethodRenaming.empty(), FieldRenaming.empty(), appInfo)
+ .verifyNoCollisions(appInfo.classes(), appInfo.dexItemFactory);
+
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
new MethodNameMinifier(appInfo, rootSet, desugaredCallSites, options)
.computeRenaming(timing);
timing.end();
+
+ assert new MinifiedRenaming(classRenaming, methodRenaming, FieldRenaming.empty(), appInfo)
+ .verifyNoCollisions(appInfo.classes(), appInfo.dexItemFactory);
+
timing.begin("MinifyFields");
- Map<DexField, DexString> fieldRenaming =
+ FieldRenaming fieldRenaming =
new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
timing.end();
- NamingLens lens =
- new MinifiedRenaming(
- classRenaming,
- methodRenaming,
- fieldRenaming,
- appInfo);
+
+ NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+ assert lens.verifyNoCollisions(appInfo.classes(), appInfo.dexItemFactory);
+
timing.begin("MinifyIdentifiers");
new IdentifierMinifier(
appInfo, options.getProguardConfiguration().getAdaptClassStrings(), lens).run();
@@ -87,14 +95,14 @@
private MinifiedRenaming(
ClassRenaming classRenaming,
MethodRenaming methodRenaming,
- Map<DexField, DexString> fieldRenaming,
+ FieldRenaming fieldRenaming,
AppInfo appInfo) {
this.appInfo = appInfo;
this.packageRenaming = classRenaming.packageRenaming;
renaming.putAll(classRenaming.classRenaming);
renaming.putAll(methodRenaming.renaming);
renaming.putAll(methodRenaming.callSiteRenaming);
- renaming.putAll(fieldRenaming);
+ renaming.putAll(fieldRenaming.renaming);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index cee867e..aecae1e 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -7,10 +7,14 @@
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -19,7 +23,10 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -70,6 +77,40 @@
return lookupName(reference.asDexField());
}
+ public final DexField lookupField(DexField field, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createField(
+ lookupType(field.clazz, dexItemFactory),
+ lookupType(field.type, dexItemFactory),
+ lookupName(field));
+ }
+
+ public final DexMethod lookupMethod(DexMethod method, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createMethod(
+ lookupType(method.holder, dexItemFactory),
+ lookupProto(method.proto, dexItemFactory),
+ lookupName(method));
+ }
+
+ private DexProto lookupProto(DexProto proto, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createProto(
+ lookupType(proto.returnType, dexItemFactory),
+ Arrays.stream(proto.parameters.values)
+ .map(type -> lookupType(type, dexItemFactory))
+ .toArray(DexType[]::new));
+ }
+
+ public final DexType lookupType(DexType type, DexItemFactory dexItemFactory) {
+ if (type.isPrimitiveType() || type.isVoidType()) {
+ return type;
+ }
+ if (type.isArrayType()) {
+ DexType newBaseType = lookupType(type.toBaseType(dexItemFactory), dexItemFactory);
+ return type.replaceBaseType(newBaseType, dexItemFactory);
+ }
+ assert type.isClassType();
+ return dexItemFactory.createType(lookupDescriptor(type));
+ }
+
public static NamingLens getIdentityLens() {
return new IdentityLens();
}
@@ -97,6 +138,34 @@
*/
public abstract boolean checkTargetCanBeTranslated(DexMethod item);
+ public final boolean verifyNoCollisions(
+ Iterable<DexProgramClass> classes, DexItemFactory dexItemFactory) {
+ Set<DexReference> references = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : classes) {
+ {
+ DexType newType = lookupType(clazz.type, dexItemFactory);
+ boolean referencesChanged = references.add(newType);
+ assert referencesChanged
+ : "Duplicate definition of type `" + newType.toSourceString() + "`";
+ }
+
+ for (DexEncodedField field : clazz.fields()) {
+ DexField newField = lookupField(field.field, dexItemFactory);
+ boolean referencesChanged = references.add(newField);
+ assert referencesChanged
+ : "Duplicate definition of field `" + newField.toSourceString() + "`";
+ }
+
+ for (DexEncodedMethod method : clazz.methods()) {
+ DexMethod newMethod = lookupMethod(method.method, dexItemFactory);
+ boolean referencesChanged = references.add(newMethod);
+ assert referencesChanged
+ : "Duplicate definition of method `" + newMethod.toSourceString() + "`";
+ }
+ }
+ return true;
+ }
+
private static class IdentityLens extends NamingLens {
private IdentityLens() {
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 a51ca83..f011931 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -177,10 +177,10 @@
* for these.
*/
private final Set<DexType> liveTypes = Sets.newIdentityHashSet();
- /**
- * Set of annotation types that are instantiated.
- */
- private final Set<DexType> instantiatedAnnotations = Sets.newIdentityHashSet();
+
+ /** Set of annotation types that are instantiated. */
+ private final SetWithReason<DexAnnotation> instantiatedAnnotations =
+ new SetWithReason<>(this::registerAnnotation);
/** Set of types that are actually instantiated. These cannot be abstract. */
private final SetWithReason<DexType> instantiatedTypes = new SetWithReason<>(this::registerType);
/**
@@ -803,9 +803,6 @@
}
}
}
- if (!holder.annotations.isEmpty()) {
- processAnnotations(holder.annotations.annotations);
- }
// We also need to add the corresponding <clinit> to the set of live methods, as otherwise
// static field initialization (and other class-load-time sideeffects) will not happen.
KeepReason reason = KeepReason.reachableFromLiveType(type);
@@ -821,10 +818,15 @@
enqueueFirstNonSerializableClassInitializer(holder, reason);
}
- // If this type has deferred annotations, we have to process those now, too.
- Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
- if (annotations != null) {
- annotations.forEach(this::handleAnnotationOfLiveType);
+ if (!holder.isLibraryClass()) {
+ if (!holder.annotations.isEmpty()) {
+ processAnnotations(holder, holder.annotations.annotations);
+ }
+ // If this type has deferred annotations, we have to process those now, too.
+ Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
+ if (annotations != null) {
+ annotations.forEach(annotation -> handleAnnotationOfLiveType(holder, annotation));
+ }
}
Map<DexReference, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
@@ -834,36 +836,33 @@
}
}
- private void handleAnnotationOfLiveType(DexAnnotation annotation) {
- DexType type = annotation.annotation.type;
- // Record that it is instantiated if it should be kept when its type is live.
- if (shouldKeepAnnotation(annotation, true, appInfo.dexItemFactory, options)) {
- instantiatedAnnotations.add(type);
- }
- AnnotationReferenceMarker referenceMarker = new AnnotationReferenceMarker(
- annotation.annotation.type, appInfo.dexItemFactory);
- annotation.annotation.collectIndexedItems(referenceMarker);
+ private void handleAnnotationOfLiveType(DexDefinition holder, DexAnnotation annotation) {
+ handleAnnotation(holder, annotation, true);
}
- private void processAnnotations(DexAnnotation[] annotations) {
+ private void processAnnotations(DexDefinition holder, DexAnnotation[] annotations) {
for (DexAnnotation annotation : annotations) {
- processAnnotation(annotation);
+ processAnnotation(holder, annotation);
}
}
- private void processAnnotation(DexAnnotation annotation) {
+ private void processAnnotation(DexDefinition holder, DexAnnotation annotation) {
+ handleAnnotation(holder, annotation, false);
+ }
+
+ private void handleAnnotation(DexDefinition holder, DexAnnotation annotation, boolean forceLive) {
+ assert !holder.isDexClass() || !holder.asDexClass().isLibraryClass();
DexType type = annotation.annotation.type;
- if (liveTypes.contains(type)) {
- // The type of this annotation is already live, so pick up its dependencies.
- handleAnnotationOfLiveType(annotation);
- } else {
- // Record that it is instantiated if it should be kept although its type is not live.
- if (shouldKeepAnnotation(annotation, false, appInfo.dexItemFactory, options)) {
- instantiatedAnnotations.add(type);
- }
+ boolean isLive = forceLive || liveTypes.contains(type);
+ if (!shouldKeepAnnotation(annotation, isLive, appInfo.dexItemFactory, options)) {
// Remember this annotation for later.
deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
+ return;
}
+ instantiatedAnnotations.add(annotation, KeepReason.annotatedOn(holder));
+ AnnotationReferenceMarker referenceMarker =
+ new AnnotationReferenceMarker(annotation.annotation.type, appInfo.dexItemFactory);
+ annotation.annotation.collectIndexedItems(referenceMarker);
}
private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
@@ -956,8 +955,11 @@
}
markTypeAsLive(method.method.holder);
markParameterAndReturnTypesAsLive(method);
- processAnnotations(method.annotations.annotations);
- method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
+ if (!appInfo.definitionFor(method.method.holder).isLibraryClass()) {
+ processAnnotations(method, method.annotations.annotations);
+ method.parameterAnnotationsList.forEachAnnotation(
+ annotation -> processAnnotation(method, annotation));
+ }
if (Log.ENABLED) {
Log.verbose(getClass(), "Method `%s` is targeted.", method.method);
}
@@ -1614,8 +1616,11 @@
}
}
markParameterAndReturnTypesAsLive(method);
- processAnnotations(method.annotations.annotations);
- method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
+ if (!appInfo.definitionFor(method.method.holder).isLibraryClass()) {
+ processAnnotations(method, method.annotations.annotations);
+ method.parameterAnnotationsList.forEachAnnotation(
+ annotation -> processAnnotation(method, annotation));
+ }
method.registerCodeReferences(new UseRegistry(options.itemFactory, method));
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(method));
@@ -2013,8 +2018,11 @@
super(appInfo);
this.liveTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.liveTypes);
- this.instantiatedAnnotations = ImmutableSortedSet.copyOf(
- PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAnnotations);
+ ImmutableSortedSet.Builder<DexType> builder =
+ ImmutableSortedSet.orderedBy(PresortedComparable<DexType>::slowCompareTo);
+ enqueuer.instantiatedAnnotations.items.forEach(
+ annotation -> builder.add(annotation.annotation.type));
+ this.instantiatedAnnotations = builder.build();
this.instantiatedTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
this.instantiatedLambdas =
@@ -2795,6 +2803,14 @@
registerEdge(getClassGraphNode(type), reason);
}
+ private void registerAnnotation(DexAnnotation annotation, KeepReason reason) {
+ assert getSourceNode(reason) != null;
+ if (keptGraphConsumer == null) {
+ return;
+ }
+ registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
+ }
+
private void registerMethod(DexEncodedMethod method, KeepReason reason) {
if (reason.edgeKind() == EdgeKind.IsLibraryMethod) {
// Don't report edges to actual library methods.
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index a797742..2dd52eb 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexType;
@@ -17,6 +18,10 @@
public abstract GraphNode getSourceNode(Enqueuer enqueuer);
+ static KeepReason annotatedOn(DexDefinition definition) {
+ return new AnnotatedOn(definition);
+ }
+
static KeepReason dueToKeepRule(ProguardKeepRule rule) {
return new DueToKeepRule(rule);
}
@@ -302,6 +307,32 @@
}
}
+ private static class AnnotatedOn extends KeepReason {
+
+ private final DexDefinition holder;
+
+ private AnnotatedOn(DexDefinition holder) {
+ this.holder = holder;
+ }
+
+ @Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.AnnotatedOn;
+ }
+
+ @Override
+ public GraphNode getSourceNode(Enqueuer enqueuer) {
+ if (holder.isDexClass()) {
+ return enqueuer.getClassGraphNode(holder.asDexClass().type);
+ } else if (holder.isDexEncodedField()) {
+ return enqueuer.getFieldGraphNode(holder.asDexEncodedField().field);
+ } else {
+ assert holder.isDexEncodedMethod();
+ return enqueuer.getMethodGraphNode(holder.asDexEncodedMethod().method);
+ }
+ }
+ }
+
private static class ReflectiveUseFrom extends BasedOnOtherMethod {
private ReflectiveUseFrom(DexEncodedMethod method) {
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 6d34f44..5b5cf9d 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -104,7 +104,7 @@
private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
// Parent dir for on-the-fly compiled jctf dex output.
- private static final String JCTF_TESTS_PREFIX = "build/classes/jctfTests";
+ private static final String JCTF_TESTS_PREFIX = "build/classes/java/jctfTests";
private static final String JCTF_TESTS_LIB_PREFIX =
JCTF_TESTS_PREFIX + "/com/google/jctf/test/lib";
private static final String JUNIT_TEST_RUNNER = "org.junit.runner.JUnitCore";
diff --git a/src/test/java/com/android/tools/r8/code/InstructionFactoryTest.java b/src/test/java/com/android/tools/r8/code/InstructionFactoryTest.java
index ddd4f69..a1cfe7b 100644
--- a/src/test/java/com/android/tools/r8/code/InstructionFactoryTest.java
+++ b/src/test/java/com/android/tools/r8/code/InstructionFactoryTest.java
@@ -19,7 +19,7 @@
ByteBuffer emptyBuffer = ByteBuffer.allocate(0);
InstructionFactory factory = new InstructionFactory();
Instruction[] instructions =
- factory.readSequenceFrom(emptyBuffer, 0, 0, new OffsetToObjectMapping());
+ factory.readSequenceFrom(emptyBuffer.asShortBuffer(), 0, 0, new OffsetToObjectMapping());
assertTrue(instructions.length == 0);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
new file mode 100644
index 0000000..f8ef59e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -0,0 +1,139 @@
+// 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;
+
+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.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
+import com.android.tools.r8.graph.DexMethod;
+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 com.google.common.collect.Streams;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+class ObjectsRequireNonNullTestMain {
+
+ static class Foo {
+ @NeverInline
+ void bar() {
+ System.out.println("Foo::bar");
+ }
+
+ @NeverInline
+ @Override
+ public String toString() {
+ return "Foo::toString";
+ }
+ }
+
+ @NeverInline
+ static void unknownArg(Foo foo) {
+ // It's unclear the argument is definitely null or not null.
+ Foo checked = Objects.requireNonNull(foo);
+ checked.bar();
+ }
+
+ public static void main(String[] args) {
+ Foo instance = new Foo();
+ // Not removable in debug mode.
+ Object nonNull = Objects.requireNonNull(instance);
+ System.out.println(nonNull);
+ // Removable because associated locals are changed while type casting.
+ Foo checked = Objects.requireNonNull(instance);
+ checked.bar();
+
+ unknownArg(instance);
+ try {
+ unknownArg(null);
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ }
+}
+
+@RunWith(VmTestRunner.class)
+public class ObjectsRequireNonNullTest extends TestBase {
+ private static final String JAVA_OUTPUT = StringUtils.lines(
+ "Foo::toString",
+ "Foo::bar",
+ "Foo::bar",
+ "Expected NPE"
+ );
+ private static final Class<?> MAIN = ObjectsRequireNonNullTestMain.class;
+
+ @Test
+ public void testJvmOutput() throws Exception {
+ testForJvm().addTestClasspath().run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ private static boolean isObjectsRequireNonNull(DexMethod method) {
+ return method.toSourceString().equals(
+ "java.lang.Object java.util.Objects.requireNonNull(java.lang.Object)");
+ }
+
+ private long countObjectsRequireNonNull(MethodSubject method) {
+ return Streams.stream(method.iterateInstructions(instructionSubject -> {
+ if (instructionSubject.isInvoke()) {
+ return isObjectsRequireNonNull(instructionSubject.getMethod());
+ }
+ return false;
+ })).count();
+ }
+
+ private void test(TestRunResult result, int expectedCount) throws Exception {
+ CodeInspector codeInspector = result.inspector();
+ ClassSubject mainClass = codeInspector.clazz(MAIN);
+ MethodSubject mainMethod = mainClass.mainMethod();
+ assertThat(mainMethod, isPresent());
+ long count = countObjectsRequireNonNull(mainMethod);
+ assertEquals(expectedCount, count);
+
+ MethodSubject unknownArg = mainClass.uniqueMethodWithName("unknownArg");
+ assertThat(unknownArg, isPresent());
+ // Due to the nullable argument, requireNonNull should remain.
+ assertEquals(1, countObjectsRequireNonNull(unknownArg));
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V4_4_4)
+ public void testD8() throws Exception {
+ TestRunResult result = testForD8()
+ .debug()
+ .addProgramClassesAndInnerClasses(MAIN)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 2);
+
+ result = testForD8()
+ .release()
+ .addProgramClassesAndInnerClasses(MAIN)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 0);
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V4_4_4)
+ public void testR8() throws Exception {
+ // CF disables move result optimization.
+ TestRunResult result = testForR8(Backend.DEX)
+ .addProgramClassesAndInnerClasses(MAIN)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 0);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index cd3a131..7044366 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -35,7 +35,9 @@
&& "CONST".equalsIgnoreCase(arg)
&& "CONST".contentEquals(arg)
&& "CONST".indexOf(arg) > 0
- && "CONST".lastIndexOf(arg) > 0;
+ && "CONST".lastIndexOf(arg) > 0
+ && "CONST".compareTo(arg) > 0
+ && "CONST".compareToIgnoreCase(arg) > 0;
}
public static void main(String[] args) {
@@ -52,6 +54,8 @@
System.out.println(s1.indexOf("ix"));
System.out.println(s1.lastIndexOf('f'));
System.out.println(s1.lastIndexOf("ix"));
+ System.out.println(s1.compareTo("prefix-CONST-suffix") == 0);
+ System.out.println(s1.compareToIgnoreCase("PREFIX-const-SUFFIX") == 0);
}
{
@@ -68,6 +72,8 @@
System.out.println(s2.indexOf("ix"));
System.out.println(s2.lastIndexOf('f'));
System.out.println(s2.lastIndexOf("ix"));
+ System.out.println(s2.compareTo("prefix-CONST-suffix") == 0);
+ System.out.println(s2.compareToIgnoreCase("pre-con-suf") == 0);
}
{
@@ -90,15 +96,15 @@
StringContentCheckTestMain.class
);
private static final String JAVA_OUTPUT = StringUtils.lines(
- // s1, contains
+ // s1, contains(String)
"true",
- // s1, startsWith
+ // s1, startsWith(String)
"true",
- // s1, endsWith
+ // s1, endsWith(String)
"true",
- // s1, equals
+ // s1, equals(String)
"true",
- // s1, equalsIgnoreCase
+ // s1, equalsIgnoreCase(String)
"true",
// s1, contentEquals(CharSequence)
"true",
@@ -112,15 +118,19 @@
"16",
// s1, lastIndexOf(String)
"17",
- // s2, contains
+ // s1, compareTo(String)
+ "true",
+ // s1, compareToIgnoreCase(String)
+ "true",
+ // s2, contains(String)
"false",
- // s2, startsWith
+ // s2, startsWith(String)
"false",
- // s2, endsWith
+ // s2, endsWith(String)
"false",
- // s2, equals
+ // s2, equals(String)
"false",
- // s2, equalsIgnoreCase
+ // s2, equalsIgnoreCase(String)
"false",
// s2, contentEquals(CharSequence)
"false",
@@ -134,6 +144,10 @@
"-1",
// s2, lastIndexOf(String)
"-1",
+ // s2, compareTo(String)
+ "false",
+ // s2, compareToIgnoreCase(String)
+ "false",
// argCouldBeNull
"false"
);
@@ -166,7 +180,9 @@
|| method.name.toString().equals("equalsIgnoreCase")
|| method.name.toString().equals("contentEquals")
|| method.name.toString().equals("indexOf")
- || method.name.toString().equals("lastIndexOf"));
+ || method.name.toString().equals("lastIndexOf")
+ || method.name.toString().equals("compareTo")
+ || method.name.toString().equals("compareToIgnoreCase"));
}
private long countStringContentChecker(MethodSubject method) {
@@ -190,7 +206,7 @@
"boolean", "argCouldBeNull", ImmutableList.of("java.lang.String"));
assertThat(argCouldBeNull, isPresent());
// Because of nullable argument, all checkers should remain.
- assertEquals(8, countStringContentChecker(argCouldBeNull));
+ assertEquals(10, countStringContentChecker(argCouldBeNull));
}
@Test
@@ -202,14 +218,14 @@
.addProgramClasses(CLASSES)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 22);
+ test(result, 26);
result = testForD8()
.release()
.addProgramClasses(CLASSES)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 12);
+ test(result, 14);
}
@Test
@@ -221,6 +237,6 @@
.addKeepMainRule(MAIN)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 12);
+ test(result, 14);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index 877ffc0..4209cb6 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -312,11 +312,15 @@
assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
- // Due to package-private access, classes in the same package should remain as-is.
- DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
- assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+ // Due to package-private access, classes in the same package should remain in the same package.
+ DexType ac = dexItemFactory.createType("Lnaming101/a/c;");
+ assertEquals(
+ getPackageNameFromDescriptor(naming.lookupDescriptor(aa).toString()),
+ getPackageNameFromDescriptor(naming.lookupDescriptor(ac).toString()));
DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
- assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+ assertEquals(
+ getPackageNameFromDescriptor(naming.lookupDescriptor(ba).toString()),
+ getPackageNameFromDescriptor(naming.lookupDescriptor(bb).toString()));
// All other classes can be repackaged to naming101.a, but naming101.a.a exists to make a name
// conflict. Thus, those should not be renamed to 'a'.
@@ -340,11 +344,15 @@
assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
- // Due to package-private access, classes in the same package should remain as-is.
- DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
- assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+ // Due to package-private access, classes in the same package should remain in the same package.
+ DexType ac = dexItemFactory.createType("Lnaming101/a/c;");
+ assertEquals(
+ getPackageNameFromDescriptor(naming.lookupDescriptor(aa).toString()),
+ getPackageNameFromDescriptor(naming.lookupDescriptor(ac).toString()));
DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
- assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+ assertEquals(
+ getPackageNameFromDescriptor(naming.lookupDescriptor(ba).toString()),
+ getPackageNameFromDescriptor(naming.lookupDescriptor(bb).toString()));
// All other packages are flattened to naming101, hence all other classes will be in
// naming101.* package. Due to naming101.a.a, prefix naming101.a is already used. So,
diff --git a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
index 6a03a43..d199f57 100644
--- a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
@@ -4,16 +4,21 @@
package com.android.tools.r8.shaking;
+import static junit.framework.TestCase.assertTrue;
+
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
-import org.junit.Ignore;
+import java.util.concurrent.ExecutionException;
import org.junit.Test;
class UnusedTypeInThrowing {
+ public static final String EXPECTED = "42";
+
public static void main(String[] args) throws UnusedTypeInThrowingThrowable {
- System.out.print("42");
+ System.out.print(EXPECTED);
}
}
@@ -25,14 +30,20 @@
static final Class MAIN_CLASS = UnusedTypeInThrowing.class;
@Test
- @Ignore("b/124019003")
- public void testTypeIsMarkedAsLive() throws IOException, CompilationFailedException {
- testForR8(Backend.CF)
- .addProgramClasses(MAIN_CLASS)
- .addProgramClasses(THROWABLE_CLASS)
- .addKeepMainRule(MAIN_CLASS)
- .addKeepRules("-keepattributes Exceptions")
- .run(MAIN_CLASS)
- .assertSuccessWithOutput("42");
+ public void testTypeIsMarkedAsLive()
+ throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+ CodeInspector inspector =
+ testForR8(Backend.CF)
+ .enableGraphInspector()
+ .addProgramClasses(MAIN_CLASS)
+ .addProgramClasses(THROWABLE_CLASS)
+ .addKeepMainRule(MAIN_CLASS)
+ .addKeepRules("-keepattributes Exceptions")
+ .run(MAIN_CLASS)
+ .assertSuccessWithOutput(UnusedTypeInThrowing.EXPECTED)
+ .inspector();
+
+ assertTrue(inspector.clazz(THROWABLE_CLASS).isPresent());
+ // TODO(b/124217402) When done check that THROWABLE_CLASS is kept by the throwing annotation.
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java
deleted file mode 100644
index 2bf42a1..0000000
--- a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.TestBase;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.nio.file.Path;
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class UnusedTypeInThrowingTestRunner extends TestBase {
-
- static final Class THROWABLE_CLASS = UnusedTypeInThrowingThrowable.class;
- static final Class MAIN_CLASS = UnusedTypeInThrowingTest.class;
-
- @Test
- @Ignore("b/124019003")
- public void testTypeIsMarkedAsLive() throws IOException, CompilationFailedException {
- Path outDex = temp.newFile("out.zip").toPath();
- testForR8(Backend.CF)
- .addProgramClasses(MAIN_CLASS)
- .addProgramClasses(THROWABLE_CLASS)
- .addKeepMainRule(MAIN_CLASS)
- .addKeepRules(ImmutableList.of("-keepattributes Exceptions"))
- .setMode(CompilationMode.RELEASE)
- .enableInliningAnnotations()
- .minification(true)
- .compile()
- .run(MAIN_CLASS)
- .assertSuccessWithOutput("42");
- }
-}
diff --git a/tools/api_sample_coverage.py b/tools/api_sample_coverage.py
index b14ad1a..4eea8c7 100755
--- a/tools/api_sample_coverage.py
+++ b/tools/api_sample_coverage.py
@@ -8,6 +8,7 @@
'''
import argparse
+import jdk
import os
import subprocess
import utils
@@ -23,16 +24,17 @@
if output_dir is None:
output_dir = ''
+ javaExecutable = jdk.GetJavaExecutable()
printseeds_path = os.path.join(output_dir, 'keep-seeds.txt')
printseeds_args = [
- 'java', '-jar', utils.R8_JAR, 'printseeds',
+ javaExecutable, '-jar', utils.R8_JAR, 'printseeds',
utils.RT_JAR, utils.R8_JAR, utils.R8LIB_KEEP_RULES,
]
write_sorted_lines(printseeds_args, printseeds_path)
printuses_path = os.path.join(output_dir, 'sample-uses.txt')
printuses_args = [
- 'java', '-jar', utils.R8_JAR, 'printuses',
+ javaExecutable, '-jar', utils.R8_JAR, 'printuses',
utils.RT_JAR, utils.R8_JAR, API_SAMPLE_JAR,
]
write_sorted_lines(printuses_args, printuses_path)
diff --git a/tools/archive.py b/tools/archive.py
index d8fb4d0..9de199c 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -5,6 +5,7 @@
import create_maven_release
import gradle
+import jdk
import optparse
import os
import shutil
@@ -25,7 +26,9 @@
return result.parse_args()
def GetToolVersion(jar_path):
- output = subprocess.check_output(['java', '-jar', jar_path, '--version'])
+ output = subprocess.check_output([
+ jdk.GetJavaExecutable(), '-jar', jar_path, '--version'
+ ])
return output.splitlines()[0].strip()
def GetVersion():
diff --git a/tools/asmifier.py b/tools/asmifier.py
index 280c6f1..3080378 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -4,6 +4,7 @@
# BSD-style license that can be found in the LICENSE file.
import gradle
+import jdk
import os
import subprocess
import sys
@@ -13,7 +14,7 @@
if build:
gradle.RunGradle(['copyMavenDeps'])
cmd = []
- cmd.append('java')
+ cmd.append(jdk.GetJavaExecutable())
cp = ":".join([os.path.join(utils.REPO_ROOT, 'build/deps/asm-6.2.1.jar'),
os.path.join(utils.REPO_ROOT, 'build/deps/asm-util-6.2.1.jar')])
cmd.extend(['-cp', cp])
diff --git a/tools/build_r8lib.py b/tools/build_r8lib.py
index 5368f6f..980715e 100755
--- a/tools/build_r8lib.py
+++ b/tools/build_r8lib.py
@@ -10,6 +10,7 @@
import argparse
import gradle
+import jdk
import os
import subprocess
import toolhelper
@@ -76,7 +77,8 @@
def test_d8sample(paths):
with utils.TempDir() as path:
- args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
+ args = [jdk.GetJavaExecutable(),
+ '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
'com.android.tools.apiusagesample.D8ApiUsageSample',
'--output', path,
'--min-api', str(API_LEVEL),
@@ -94,7 +96,8 @@
# R8CommandParser should have been minified in LIB_JAR.
# Just in case R8CommandParser is also present in LIB_JAR, we put
# SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
- args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
+ args = [jdk.GetJavaExecutable(),
+ '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
'com.android.tools.r8.R8CommandParser',
'--output', path + "/output.zip",
'--min-api', str(API_LEVEL),
@@ -111,7 +114,8 @@
# R8CommandParser should have been minified in LIB_JAR.
# Just in case R8CommandParser is also present in LIB_JAR, we put
# SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
- args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
+ args = [jdk.GetJavaExecutable(),
+ '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
'com.android.tools.r8.R8CommandParser',
'--classfile',
'--output', path + "/output.jar",
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
index 9053e23..29f7459 100755
--- a/tools/build_sample_apk.py
+++ b/tools/build_sample_apk.py
@@ -8,6 +8,7 @@
import apk_utils
import fnmatch
import glob
+import jdk
import optparse
import os
import shutil
@@ -21,7 +22,7 @@
DEFAULT_AAPT = 'aapt' # Assume in path.
DEFAULT_D8 = os.path.join(utils.REPO_ROOT, 'tools', 'd8.py')
DEFAULT_DEXSPLITTER = os.path.join(utils.REPO_ROOT, 'tools', 'dexsplitter.py')
-DEFAULT_JAVAC = 'javac'
+DEFAULT_JAVAC = jdk.GetJavacExecutable()
SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
PACKAGE_PREFIX = 'com.android.tools.r8.sample'
diff --git a/tools/defines.py b/tools/defines.py
new file mode 100644
index 0000000..6fdd24f
--- /dev/null
+++ b/tools/defines.py
@@ -0,0 +1,22 @@
+# 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.
+
+import os
+import sys
+
+# Static defines for use in tools (and utils.py).
+# There must be no dependencies on other utils from this file!
+
+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')
+
+def IsWindows():
+ return sys.platform.startswith('win')
+
+def IsLinux():
+ return sys.platform.startswith('linux')
+
+def IsOsX():
+ return sys.platform.startswith('darwin')
\ No newline at end of file
diff --git a/tools/gradle.py b/tools/gradle.py
index 374f885..232e96d 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -44,9 +44,15 @@
utils.EnsureDepFromGoogleCloudStorage(
GRADLE, GRADLE_TGZ, GRADLE_SHA1, 'Gradle binary')
+def EnsureJdk():
+ jdkHome = jdk.GetJdkHome()
+ jdkTgz = jdkHome + '.tar.gz'
+ jdkSha1 = jdkTgz + '.sha1'
+ utils.EnsureDepFromGoogleCloudStorage(jdkHome, jdkTgz, jdkSha1, 'JDK')
+
def EnsureDeps():
EnsureGradle()
- jdk.EnsureJdk()
+ EnsureJdk()
def RunGradleIn(gradleCmd, args, cwd, throw_on_failure=True, env=None):
EnsureDeps()
diff --git a/tools/jdk.py b/tools/jdk.py
index 5a20901..5791e50 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -3,40 +3,33 @@
# 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.
+import defines
import os
import sys
-import utils
-JDK_DIR = os.path.join(utils.REPO_ROOT, 'third_party', 'openjdk')
+JDK_DIR = os.path.join(defines.THIRD_PARTY, 'openjdk')
def GetJdkHome():
root = os.path.join(JDK_DIR, 'openjdk-9.0.4')
- if utils.IsLinux():
+ if defines.IsLinux():
return os.path.join(root, 'linux')
- elif utils.IsOsX():
+ elif defines.IsOsX():
return os.path.join(root, 'osx')
- elif utils.IsWindows():
+ elif defines.IsWindows():
return os.path.join(root, 'windows')
else:
return os.environ['JAVA_HOME']
- return jdkHome
def GetJavaExecutable(jdkHome=None):
jdkHome = jdkHome if jdkHome else GetJdkHome()
- executable = 'java.exe' if utils.IsWindows() else 'java'
+ executable = 'java.exe' if defines.IsWindows() else 'java'
return os.path.join(jdkHome, 'bin', executable) if jdkHome else executable
def GetJavacExecutable(jdkHome=None):
jdkHome = jdkHome if jdkHome else GetJdkHome()
- executable = 'javac.exe' if utils.IsWindows() else 'javac'
+ executable = 'javac.exe' if defines.IsWindows() else 'javac'
return os.path.join(jdkHome, 'bin', executable) if jdkHome else executable
-def EnsureJdk():
- jdkHome = GetJdkHome()
- jdkTgz = jdkHome + '.tar.gz'
- jdkSha1 = jdkTgz + '.sha1'
- utils.EnsureDepFromGoogleCloudStorage(jdkHome, jdkTgz, jdkSha1, 'JDK')
-
def Main():
print GetJdkHome()
diff --git a/tools/proguard.py b/tools/proguard.py
index 640094f..b1bde54 100755
--- a/tools/proguard.py
+++ b/tools/proguard.py
@@ -6,6 +6,7 @@
# Run ProGuard, Google's internal version
from __future__ import print_function
+import jdk
import os
import subprocess
import sys
@@ -19,7 +20,7 @@
cmd = []
if track_memory_file:
cmd.extend(['tools/track_memory.sh', track_memory_file])
- cmd.extend(['java', '-jar', PROGUARD_JAR])
+ cmd.extend([jdk.GetJavaExecutable(), '-jar', PROGUARD_JAR])
cmd.extend(args)
utils.PrintCmd(cmd)
subprocess.call(cmd, stdout=stdout, stderr=stderr)
diff --git a/tools/retrace.py b/tools/retrace.py
index 1b77e9a..44120f2 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -5,6 +5,7 @@
import archive
import argparse
+import jdk
import subprocess
import sys
import tempfile
@@ -49,7 +50,9 @@
print('Could not find map file from argument: %s.' % hashOrVersion)
return 1
- retrace_args = ['java', '-jar', utils.RETRACE_JAR, r8lib_map_path]
+ retrace_args = [
+ jdk.GetJavaExecutable(), '-jar', utils.RETRACE_JAR, r8lib_map_path
+ ]
if args.stacktrace:
retrace_args.append(args.stacktrace)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 2889d74..506c583 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -7,6 +7,7 @@
import apk_utils
import golem
import gradle
+import jdk
import os
import optparse
import shutil
@@ -177,7 +178,7 @@
# Use the copy of r8.jar if it is there.
if os.path.isfile(r8_jar):
- cmd = ['java', '-ea', '-jar', r8_jar, 'extractmarker', apk]
+ cmd = [jdk.GetJavaExecutable(), '-ea', '-jar', r8_jar, 'extractmarker', apk]
else:
script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
cmd = ['python', script, apk]
@@ -493,7 +494,7 @@
# is 'r8'.
entry_point = 'com.android.tools.r8.R8'
- cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar, entry_point,
+ cmd = [jdk.GetJavaExecutable(), '-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]
diff --git a/tools/run_proguard_dx_on_app.py b/tools/run_proguard_dx_on_app.py
index e78d5d1..4e408ff 100755
--- a/tools/run_proguard_dx_on_app.py
+++ b/tools/run_proguard_dx_on_app.py
@@ -13,6 +13,7 @@
import argparse
import fnmatch
import gmscore_data
+import jdk
import os
import stat
import sys
@@ -140,7 +141,7 @@
if options.print_memoryuse:
track_memory_file = join(temp, utils.MEMORY_USE_TMP_FILE)
cmd.extend(['tools/track_memory.sh', track_memory_file])
- cmd.extend(['java', '-jar', jar, '--multi-dex',
+ cmd.extend([jdk.GetJavaExecutable(), '-jar', jar, '--multi-dex',
'--output=' + outdir])
if 'min-api' in values_proguarded:
cmd.append('--min-sdk-version=' + values_proguarded['min-api'])
diff --git a/tools/test_aosp_jar.py b/tools/test_aosp_jar.py
index bf12045..6f7e8f5 100755
--- a/tools/test_aosp_jar.py
+++ b/tools/test_aosp_jar.py
@@ -10,6 +10,7 @@
from itertools import chain
from os.path import join
import argparse
+import jdk
import os
import subprocess
import sys
@@ -36,7 +37,7 @@
if not args.no_build:
gradle.RunGradle(['CompatDx'])
- cmd = [REPLAY_SCRIPT, 'java', '-jar', utils.COMPATDX_JAR]
+ cmd = [REPLAY_SCRIPT, jdk.GetJavaExecutable(), '-jar', utils.COMPATDX_JAR]
utils.PrintCmd(cmd)
subprocess.check_call(cmd)
diff --git a/tools/test_framework.py b/tools/test_framework.py
index 7400541..f30d7d9 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -21,6 +21,7 @@
from glob import glob
import argparse
import golem
+import jdk
import os
import re
import subprocess
@@ -96,7 +97,7 @@
if tool_file.endswith('.jar'):
assert xmx is not None
- cmd.extend(['java', xmx, '-jar'])
+ cmd.extend([jdk.GetJavaExecutable(), xmx, '-jar'])
cmd.extend([tool_file] + tool_args + [FRAMEWORK_JAR])
diff --git a/tools/test_self_retrace.py b/tools/test_self_retrace.py
index 6e48aa6..46cb1d9 100755
--- a/tools/test_self_retrace.py
+++ b/tools/test_self_retrace.py
@@ -7,6 +7,7 @@
from __future__ import division
from __future__ import print_function
import gradle
+import jdk
import os
import subprocess
import sys
@@ -37,7 +38,9 @@
raise Exception("Only one argument is allowed, see '--help'.")
# Run 'r8 --help' which throws an exception.
- cmd = ['java','-cp', r8lib, 'com.android.tools.r8.R8', '--help']
+ cmd = [
+ jdk.GetJavaExecutable(),'-cp', r8lib, 'com.android.tools.r8.R8', '--help'
+ ]
os.environ["R8_THROW_EXCEPTION_FOR_TESTING_RETRACE"] = "1"
utils.PrintCmd(cmd)
p = subprocess.Popen(cmd, stderr=subprocess.PIPE)
@@ -48,7 +51,7 @@
assert('SelfRetraceTest' not in stacktrace)
# Run the retrace tool.
- cmd = ['java', '-jar', utils.RETRACE_JAR, r8lib + ".map"]
+ cmd = [jdk.GetJavaExecutable(), '-jar', utils.RETRACE_JAR, r8lib + ".map"]
utils.PrintCmd(cmd)
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
retrace_stdout, _ = p.communicate(stacktrace)
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 9371fb9..10da97d 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -4,7 +4,7 @@
import glob
import gradle
-import os
+import jdk
import subprocess
from threading import Timer
import utils
@@ -19,7 +19,7 @@
cmd = []
if track_memory_file:
cmd.extend(['tools/track_memory.sh', track_memory_file])
- cmd.append('java')
+ cmd.append(jdk.GetJavaExecutable())
if extra_args:
cmd.extend(extra_args)
if debug:
@@ -61,7 +61,7 @@
'compileTestJava',
])
cmd = []
- cmd.append('java')
+ cmd.append(jdk.GetJavaExecutable())
if extra_args:
cmd.extend(extra_args)
if debug:
diff --git a/tools/utils.py b/tools/utils.py
index 20d7bc7..892fe7d 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -4,7 +4,9 @@
# Different utility functions used accross scripts
+import defines
import hashlib
+import jdk
import os
import re
import shutil
@@ -16,9 +18,9 @@
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')
+TOOLS_DIR = defines.TOOLS_DIR
+REPO_ROOT = defines.REPO_ROOT
+THIRD_PARTY = defines.THIRD_PARTY
MEMORY_USE_TMP_FILE = 'memory_use.tmp'
DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
BUILD = os.path.join(REPO_ROOT, 'build')
@@ -156,13 +158,13 @@
return stdout
def IsWindows():
- return sys.platform.startswith('win')
+ return defines.IsWindows()
def IsLinux():
- return sys.platform.startswith('linux')
+ return defines.IsLinux()
def IsOsX():
- return sys.platform.startswith('darwin')
+ return defines.IsOsX()
def EnsureDepFromGoogleCloudStorage(dep, tgz, sha1, msg):
if not os.path.exists(dep) or os.path.getmtime(tgz) < os.path.getmtime(sha1):
@@ -385,7 +387,7 @@
# Return a dictionary: {segment_name -> segments_size}
def getDexSegmentSizes(dex_files):
assert len(dex_files) > 0
- cmd = ['java', '-jar', R8_JAR, 'dexsegments']
+ cmd = [jdk.GetJavaExecutable(), '-jar', R8_JAR, 'dexsegments']
cmd.extend(dex_files)
PrintCmd(cmd)
output = subprocess.check_output(cmd)
@@ -405,7 +407,7 @@
# Return a dictionary: {segment_name -> segments_size}
def getCfSegmentSizes(cfFile):
- cmd = ['java',
+ cmd = [jdk.GetJavaExecutable(),
'-cp',
CF_SEGMENTS_TOOL,
'com.android.tools.r8.cf_segments.MeasureLib',
@@ -443,7 +445,7 @@
# Ensure that we are not benchmarking with a google jvm.
def check_java_version():
- cmd= ['java', '-version']
+ cmd= [jdk.GetJavaExecutable(), '-version']
output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
m = re.search('openjdk version "([^"]*)"', output)
if m is None: