Merge "Make desugaring more tolerant on missing hierarchy"
diff --git a/build.gradle b/build.gradle
index 9b8d59b..f857f9e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -300,6 +300,7 @@
"android_jar/lib-v25",
"android_jar/lib-v26",
"proguard/proguard5.2.1",
+ "proguard/proguard6.0",
"gradle/gradle",
"jdwp-tests",
"jasmin",
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 89dee13..b038fcf 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -86,7 +86,7 @@
private static String getDefaultDexFileName(int fileIndex) {
return fileIndex == 0
- ? "classes.dex"
+ ? "classes" + FileUtils.DEX_EXTENSION
: ("classes" + (fileIndex + 1) + FileUtils.DEX_EXTENSION);
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7004d5f..9a61a9f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -319,6 +319,9 @@
assert getProgramConsumer() != null;
+ boolean desugaring =
+ (getProgramConsumer() instanceof ClassFileConsumer) ? false : !getDisableDesugaring();
+
R8Command command =
new R8Command(
getAppBuilder().build(),
@@ -329,7 +332,7 @@
getMode(),
getMinApiLevel(),
reporter,
- !getDisableDesugaring(),
+ desugaring,
configuration.isShrinking(),
configuration.isObfuscating(),
forceProguardCompatibility,
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 5d77ad3..fed4a00 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 = "v1.1.7-dev";
+ public static final String LABEL = "v1.1.8-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index f7c3b24..58451eb 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -10,10 +10,13 @@
import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
@@ -150,11 +153,26 @@
@Override
public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
throws ApiLevelException {
- throw new Unimplemented("Converting Java class- file bytecode to IR not yet supported");
+ throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
}
@Override
- public void registerReachableDefinitions(UseRegistry registry) {
+ public IRCode buildInliningIR(
+ DexEncodedMethod encodedMethod,
+ InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator,
+ Position callerPosition)
+ throws ApiLevelException {
+ throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
+ }
+
+ @Override
+ public void registerInstructionsReferences(UseRegistry registry) {
+ throw new Unimplemented("Inspecting Java class-file bytecode not yet supported");
+ }
+
+ @Override
+ public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
throw new Unimplemented("Inspecting Java class-file bytecode not yet supported");
}
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 14c4bbc..b6c1583 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
public abstract class Code extends CachedHashValueDexItem {
@@ -29,7 +30,9 @@
+ getClass().getCanonicalName());
}
- public abstract void registerReachableDefinitions(UseRegistry registry);
+ public abstract void registerInstructionsReferences(UseRegistry registry);
+
+ public abstract void registerCaughtTypes(Consumer<DexType> dexTypeConsumer);
@Override
public abstract String toString();
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 a522e82..1517dfb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -27,6 +28,7 @@
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
// DexCode corresponds to code item in dalvik/dex-format.html
public class DexCode extends Code {
@@ -188,13 +190,22 @@
}
@Override
- public void registerReachableDefinitions(UseRegistry registry) {
+ public void registerInstructionsReferences(UseRegistry registry) {
for (Instruction insn : instructions) {
insn.registerUse(registry);
}
}
@Override
+ public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
+ for (TryHandler handler : handlers) {
+ for (TypeAddrPair pair : handler.pairs) {
+ dexTypeConsumer.accept(pair.type);
+ }
+ }
+ }
+
+ @Override
public String toString() {
return toString(null, null);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 69fb348..bfc0a9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -195,11 +195,13 @@
private void emitDebugPosition(int pc, Position position) {
assert !position.equals(emittedPosition);
if (startLine == NO_LINE_INFO) {
- if (position.synthetic) {
+ assert emittedPosition.isNone();
+ if (position.synthetic && position.callerPosition == null) {
// Ignore synthetic positions prior to any actual position.
+ // We do need to preserve synthetic position establishing the stack frame for inlined
+ // methods.
return;
}
- assert emittedPosition.isNone();
startLine = position.line;
emittedPosition = new Position(position.line, null, method.method, null);
}
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 a1e8821..4fdd8ac 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -501,12 +501,21 @@
return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
}
- public void registerReachableDefinitions(UseRegistry registry) {
+ public void registerInstructionsReferences(UseRegistry registry) {
if (code != null) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
}
- code.registerReachableDefinitions(registry);
+ code.registerInstructionsReferences(registry);
+ }
+ }
+
+ public void registerCatchedTypes(Consumer<DexType> dexTypeConsumer) {
+ if (code != null) {
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Visiting catched types `%s`.", method);
+ }
+ code.registerCaughtTypes(dexTypeConsumer);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 62dc43f..f9ea993 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -13,11 +13,13 @@
import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
@@ -60,6 +62,11 @@
context.codeList.add(this);
}
+ public MethodNode getNode() {
+ triggerDelayedParsingIfNeccessary();
+ return node;
+ }
+
@Override
public boolean isJarCode() {
return true;
@@ -148,13 +155,20 @@
}
@Override
- public void registerReachableDefinitions(UseRegistry registry) {
+ public void registerInstructionsReferences(UseRegistry registry) {
triggerDelayedParsingIfNeccessary();
node.instructions.accept(
new JarRegisterEffectsVisitor(method.getHolder(), registry, application));
}
@Override
+ public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
+ node.tryCatchBlocks.forEach(tryCatchBlockNode ->
+ dexTypeConsumer.accept(application.getTypeFromDescriptor(
+ DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
+ }
+
+ @Override
public String toString() {
triggerDelayedParsingIfNeccessary();
TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index ba08ebb..821a318 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -34,17 +34,23 @@
}
@Override
+ public void analyze() {
+ while (!worklist.isEmpty()) {
+ analyzeValue(worklist.poll());
+ }
+ }
+
+ @Override
public void analyzeBlocks(List<BasicBlock> blocks) {
assert worklist.isEmpty();
for (BasicBlock block : blocks) {
processBasicBlock(block);
}
- while (!worklist.isEmpty()) {
- processValue(worklist.poll());
- }
+ analyze();
}
- private void addToWorklist(Value v) {
+ @Override
+ public void enqueue(Value v) {
assert v != null;
if (!worklist.contains(v)) {
worklist.add(v);
@@ -72,16 +78,16 @@
updateTypeOfValue(outValue, derived);
} else {
if (outValue != null) {
- addToWorklist(outValue);
+ enqueue(outValue);
}
}
}
for (Phi phi : block.getPhis()) {
- addToWorklist(phi);
+ enqueue(phi);
}
}
- private void processValue(Value value) {
+ private void analyzeValue(Value value) {
TypeLatticeElement derived =
value.isPhi()
? computePhiType(value.asPhi())
@@ -100,14 +106,12 @@
for (Instruction instruction : value.uniqueUsers()) {
Value outValue = instruction.outValue();
if (outValue != null) {
- // TODO(b/72693244): processValue instead of queueing.
- addToWorklist(outValue);
+ enqueue(outValue);
}
}
// Propagate the type change to phi users if any.
for (Phi phi : value.uniquePhiUsers()) {
- // TODO(b/72693244): processValue instead of queueing.
- addToWorklist(phi);
+ enqueue(phi);
}
}
@@ -141,6 +145,18 @@
private static final TypeEnvironment DEFAULT_ENVIRONMENT = new TypeEnvironment() {
@Override
+ public void analyze() {
+ }
+
+ @Override
+ public void analyzeBlocks(List<BasicBlock> blocks) {
+ }
+
+ @Override
+ public void enqueue(Value value) {
+ }
+
+ @Override
public TypeLatticeElement getLatticeElement(Value value) {
return Top.getInstance();
}
@@ -149,10 +165,6 @@
public DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke) {
return invoke.getInvokedMethod().holder;
}
-
- @Override
- public void analyzeBlocks(List<BasicBlock> blocks) {
- }
};
// TODO(b/72693244): By annotating type lattice to value, remove the default type env at all.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
index 890af8a..85fc5d9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
@@ -10,7 +10,10 @@
import java.util.List;
public interface TypeEnvironment {
+ void analyze();
+ void analyzeBlocks(List<BasicBlock> blocks);
+ void enqueue(Value value);
+
TypeLatticeElement getLatticeElement(Value value);
DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke);
- void analyzeBlocks(List<BasicBlock> blocks);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index cde22b0..8d522de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -31,6 +31,7 @@
@Override
public void buildDex(DexBuilder builder) {
+ assert getPosition().isSome() && !getPosition().synthetic;
builder.addDebugPosition(this);
}
@@ -73,6 +74,7 @@
@Override
public void buildCf(CfBuilder builder) {
+ assert getPosition().isSome() && !getPosition().synthetic;
// All redundant debug positions are removed. Remaining ones must force a pc advance.
builder.add(new CfNop());
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 0fcb330..51ef234 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -15,10 +15,13 @@
public class NonNull extends Instruction {
private final static String ERROR_MESSAGE = "This fake IR should be removed after inlining.";
- public NonNull(Value dest, Value src) {
+ final Instruction origin;
+
+ public NonNull(Value dest, Value src, Instruction origin) {
super(dest, src);
assert !src.isNeverNull();
dest.markNeverNull();
+ this.origin = origin;
}
public Value dest() {
@@ -29,6 +32,10 @@
return inValues.get(0);
}
+ public Instruction origin() {
+ return origin;
+ }
+
@Override
public boolean isNonNull() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index b2dafdd..f863778 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -144,7 +144,7 @@
for (DexEncodedMethod method : clazz.allMethodsSorted()) {
Node node = graph.ensureMethodNode(method);
InvokeExtractor extractor = new InvokeExtractor(appInfo, graphLense, node, graph);
- method.registerReachableDefinitions(extractor);
+ method.registerInstructionsReferences(extractor);
}
}
assert allMethodsExists(application, graph);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 96b2122..3ad0168 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -984,14 +984,7 @@
int source = builder.getInfo(jump).getOffset();
Info targetInfo = builder.getTargetInfo(jump.getTarget());
int relativeOffset = targetInfo.getOffset() - source;
- // Emit a return if the target is a return and the size of the return is the computed
- // size of this instruction.
- Return ret = targetInfo.getIR().asReturn();
- if (ret != null && size == targetInfo.getSize() && ret.getPosition().isNone()) {
- Instruction dex = ret.createDexInstruction(builder);
- dex.setOffset(getOffset()); // for better printing of the dex code.
- instructions.add(dex);
- } else if (size == relativeOffset) {
+ if (size == relativeOffset) {
// We should never generate a goto targeting the next instruction. However, if we do
// we replace it with nops. This works around a dalvik bug where the dalvik tracing
// jit crashes on 'goto next instruction' on Android 4.1.1.
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 55e4b83..b9bbe5a 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
@@ -136,7 +136,7 @@
@Override
public void buildPrelude(IRBuilder builder) {
- currentPosition = Position.none();
+ currentPosition = Position.synthetic(0, method, null);
if (code.incomingRegisterSize == 0) {
return;
}
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 98c1015..6364c82 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,10 +396,6 @@
// necessary.
ir.splitCriticalEdges();
- if (options.testing.invertConditionals) {
- invertConditionalsForTesting(ir);
- }
-
// Create block order and make sure that all blocks are immediately followed by their
// fallthrough block if any.
ir.traceBlocks();
@@ -440,7 +436,7 @@
} else {
current = position;
}
- } else if (position.isSome() && !position.equals(current)) {
+ } else if (position.isSome() && !position.synthetic && !position.equals(current)) {
DebugPosition positionChange = new DebugPosition();
positionChange.setPosition(position);
it.previous();
@@ -2006,14 +2002,6 @@
return type != NumericType.FLOAT && type != NumericType.DOUBLE && type != NumericType.LONG;
}
- private static void invertConditionalsForTesting(IRCode code) {
- for (BasicBlock block : code.blocks) {
- if (block.exit().isIf()) {
- block.exit().asIf().invert();
- }
- }
- }
-
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
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 39cea77..f9bcf1d 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
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -31,6 +32,7 @@
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.ir.optimize.Devirtualizer;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.MemberValuePropagation;
@@ -86,6 +88,7 @@
private final Inliner inliner;
private final ProtoLitePruner protoLiteRewriter;
private final IdentifierNameStringMarker identifierNameStringMarker;
+ private final Devirtualizer devirtualizer;
private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
private DexString highestSortingString;
@@ -129,9 +132,12 @@
} else {
this.identifierNameStringMarker = null;
}
+ this.devirtualizer =
+ options.enableDevirtualization ? new Devirtualizer(appInfo.withLiveness()) : null;
} else {
this.protoLiteRewriter = null;
this.identifierNameStringMarker = null;
+ this.devirtualizer = null;
}
} else {
this.nonNullTracker = null;
@@ -141,6 +147,7 @@
this.lensCodeRewriter = null;
this.protoLiteRewriter = null;
this.identifierNameStringMarker = null;
+ this.devirtualizer = null;
}
}
@@ -511,6 +518,14 @@
}
}
+ private static void invertConditionalsForTesting(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ if (block.exit().isIf()) {
+ block.exit().asIf().invert();
+ }
+ }
+ }
+
private void rewriteCode(DexEncodedMethod method,
OptimizationFeedback feedback,
Predicate<DexEncodedMethod> isProcessedConcurrently,
@@ -578,7 +593,9 @@
inliner.performInlining(
method, code, typeEnvironment, isProcessedConcurrently, callSiteInformation);
}
- // TODO(b/69962188): MethodDevirtualizer can perform optimizations using type analysis.
+ if (devirtualizer != null) {
+ devirtualizer.devirtualizeInvokeInterface(code, typeEnvironment, method.method.getHolder());
+ }
codeRewriter.removeCasts(code, typeEnvironment);
codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
codeRewriter.commonSubexpressionElimination(code);
@@ -589,6 +606,12 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code, typeEnvironment);
+
+ if (options.testing.invertConditionals) {
+ invertConditionalsForTesting(code);
+ code.traceBlocks();
+ }
+
if (options.enableNonNullTracking && nonNullTracker != null) {
nonNullTracker.cleanupNonNull(code);
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 69ecb83..12429e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -186,6 +186,9 @@
// Canonicalized positions to lower memory usage.
private final Int2ReferenceMap<Position> canonicalPositions = new Int2ReferenceOpenHashMap<>();
+ // Synthetic position with line = 0.
+ private Position preamblePosition = null;
+
// Cooked position to indicate positions in synthesized code (ie, for synchronization).
private Position syntheticPosition = null;
@@ -262,6 +265,8 @@
@Override
public void buildPrelude(IRBuilder builder) {
+ currentPosition = getPreamblePosition();
+
// Record types for arguments.
Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
@@ -327,8 +332,6 @@
locals = state.openLocals(getOffset(initialLabel));
}
- currentPosition = Position.none();
-
// Build the actual argument instructions now that type and debug information is known
// for arguments.
buildArgumentInstructions(builder);
@@ -457,7 +460,7 @@
private void buildExceptionalPostlude(IRBuilder builder) {
assert isSynchronized();
generatingMethodSynchronization = true;
- currentPosition = getSyntheticPosition();
+ currentPosition = getExceptionalExitPosition();
buildMonitorExit(builder);
builder.addThrow(getMoveExceptionRegister());
generatingMethodSynchronization = false;
@@ -503,7 +506,9 @@
// writes after the line has changed and thus causing locals to become visible too late.
currentPosition =
getDebugPositionAtOffset(
- insn instanceof LabelNode ? instructionIndex - 1 : instructionIndex);
+ ((instructionIndex > 0) && (insn instanceof LabelNode))
+ ? instructionIndex - 1
+ : instructionIndex);
build(insn, builder);
@@ -2906,10 +2911,11 @@
@Override
public Position getDebugPositionAtOffset(int offset) {
if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
- return getSyntheticPosition();
+ return getExceptionalExitPosition();
}
int index = instructionIndex(offset);
if (index < 0 || instructionCount() <= index) {
+ assert false;
return Position.none();
}
AbstractInsnNode insn = node.instructions.get(index);
@@ -2923,7 +2929,7 @@
LineNumberNode line = (LineNumberNode) insn;
return getCanonicalPosition(line.line);
}
- return Position.none();
+ return getPreamblePosition();
}
@Override
@@ -2936,12 +2942,19 @@
line, l -> new Position(l, null, method, callerPosition));
}
+ private Position getPreamblePosition() {
+ if (preamblePosition == null) {
+ preamblePosition = Position.synthetic(0, method, null);
+ }
+ return preamblePosition;
+ }
+
// If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
// position that is not actually a valid program position, so as not to incorrectly position the
// user on an exit that is not the actual exit being taken. Our heuristic for this is that if the
// method has at least two positions we use the first position minus one as the synthetic exit.
// If the method only has one position it is safe to just use that position.
- private Position getSyntheticPosition() {
+ private Position getExceptionalExitPosition() {
if (syntheticPosition == null) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
new file mode 100644
index 0000000..a62d43f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeInterface;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NonNull;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.util.IdentityHashMap;
+import java.util.ListIterator;
+import java.util.Map;
+
+public class Devirtualizer {
+
+ private final AppInfoWithLiveness appInfo;
+
+ public Devirtualizer(AppInfoWithLiveness appInfo) {
+ this.appInfo = appInfo;
+ }
+
+ public void devirtualizeInvokeInterface(
+ IRCode code, TypeEnvironment typeEnvironment, DexType invocationContext) {
+ Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
+ DominatorTree dominatorTree = new DominatorTree(code);
+ Map<Value, Map<DexType, Value>> castedReceiverCache = new IdentityHashMap<>();
+
+ ListIterator<BasicBlock> blocks = code.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction current = it.next();
+
+ // (out <-) invoke-interface rcv_i, ... I#foo
+ // ... // could be split due to catch handlers
+ // non_null_rcv <- non-null rcv_i
+ //
+ // ~>
+ //
+ // rcv_c <- check-cast C rcv_i
+ // (out <-) invoke-virtual rcv_c, ... C#foo
+ // ...
+ // non_null_rcv <- non-null rcv_c // <- Update the input rcv to the non-null, too.
+ if (current.isNonNull()) {
+ NonNull nonNull = current.asNonNull();
+ Instruction origin = nonNull.origin();
+ if (origin.isInvokeInterface()
+ && devirtualizedCall.containsKey(origin.asInvokeInterface())) {
+ InvokeVirtual devirtualizedInvoke = devirtualizedCall.get(origin.asInvokeInterface());
+ if (dominatorTree.dominatedBy(block, devirtualizedInvoke.getBlock())) {
+ nonNull.src().replaceSelectiveUsers(
+ devirtualizedInvoke.getReceiver(), ImmutableSet.of(nonNull), ImmutableSet.of());
+ }
+ }
+ }
+
+ if (!current.isInvokeInterface()) {
+ continue;
+ }
+ InvokeInterface invoke = current.asInvokeInterface();
+ DexEncodedMethod target =
+ invoke.computeSingleTarget(appInfo, typeEnvironment, invocationContext);
+ if (target == null) {
+ continue;
+ }
+ DexType holderType = target.method.getHolder();
+ DexClass holderClass = appInfo.definitionFor(holderType);
+ // Make sure we are not landing on another interface, e.g., interface's default method.
+ if (holderClass == null || holderClass.isInterface()) {
+ continue;
+ }
+ // Due to the potential downcast below, make sure the new target holder is visible.
+ Constraint visibility = Constraint.classIsVisible(invocationContext, holderType, appInfo);
+ if (visibility == Constraint.NEVER) {
+ continue;
+ }
+
+ InvokeVirtual devirtualizedInvoke =
+ new InvokeVirtual(target.method, invoke.outValue(), invoke.inValues());
+ it.replaceCurrentInstruction(devirtualizedInvoke);
+ devirtualizedCall.put(invoke, devirtualizedInvoke);
+
+ // We may need to add downcast together. E.g.,
+ // i <- check-cast I o // suppose it is known to be of type class A, not interface I
+ // (out <-) invoke-interface i, ... I#foo
+ //
+ // ~>
+ //
+ // i <- check-cast I o // could be removed by {@link CodeRewriter#removeCasts}.
+ // a <- check-cast A i // Otherwise ART verification error.
+ // (out <-) invoke-virtual a, ... A#foo
+ if (holderType != invoke.getInvokedMethod().getHolder()) {
+ Value receiver = invoke.getReceiver();
+ TypeLatticeElement receiverTypeLattice = typeEnvironment.getLatticeElement(receiver);
+ TypeLatticeElement castTypeLattice =
+ TypeLatticeElement.fromDexType(holderType, receiverTypeLattice.isNullable());
+ // Avoid adding trivial cast and up-cast.
+ // We should not use strictlyLessThan(castType, receiverType), which detects downcast,
+ // due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B.
+ if (!TypeLatticeElement.lessThanOrEqual(appInfo, receiverTypeLattice, castTypeLattice)) {
+ Value newReceiver = null;
+ // If this value is ever downcast'ed to the same holder type before, and that casted
+ // value is safely accessible, i.e., the current line is dominated by that cast, use it.
+ // Otherwise, we will see something like:
+ // ...
+ // a1 <- check-cast A i
+ // invoke-virtual a1, ... A#m1 (from I#m1)
+ // ...
+ // a2 <- check-cast A i // We should be able to reuse a1 here!
+ // invoke-virtual a2, ... A#m2 (from I#m2)
+ if (castedReceiverCache.containsKey(receiver)
+ && castedReceiverCache.get(receiver).containsKey(holderType)) {
+ Value cachedReceiver = castedReceiverCache.get(receiver).get(holderType);
+ if (dominatorTree.dominatedBy(block, cachedReceiver.definition.getBlock())) {
+ newReceiver = cachedReceiver;
+ }
+ }
+
+ // No cached, we need a new downcast'ed receiver.
+ if (newReceiver == null) {
+ newReceiver =
+ receiver.definition != null
+ ? code.createValue(receiver.outType(), receiver.definition.getLocalInfo())
+ : code.createValue(receiver.outType());
+ // Cache the new receiver with a narrower type to avoid redundant checkcast.
+ castedReceiverCache.putIfAbsent(receiver, new IdentityHashMap<>());
+ castedReceiverCache.get(receiver).put(holderType, newReceiver);
+ CheckCast checkCast = new CheckCast(newReceiver, receiver, holderType);
+ checkCast.setPosition(invoke.getPosition());
+
+ // We need to add this checkcast *before* the devirtualized invoke-virtual.
+ it.previous();
+ it.add(checkCast);
+ // If the current block has catch handlers, split the new checkcast on its own block.
+ if (block.hasCatchHandlers()) {
+ // Move the cursor backward to the newly added checkcast.
+ assert it.previous() == checkCast;
+ it.split(code, 1, blocks);
+ // Update the dominator tree after the split.
+ dominatorTree = new DominatorTree(code);
+ }
+ }
+
+ receiver.replaceSelectiveUsers(
+ newReceiver, ImmutableSet.of(devirtualizedInvoke), ImmutableSet.of());
+ // TODO(b/72693244): Analyze it when creating a new Value or after replace*Users
+ typeEnvironment.enqueue(newReceiver);
+ typeEnvironment.analyze();
+ }
+ }
+ }
+ }
+ assert code.isConsistentSSA();
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 9f15bc2..4eae3d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -100,7 +100,7 @@
// ...y
Value nonNullValue =
code.createValue(ValueType.OBJECT, knownToBeNonNullValue.getLocalInfo());
- NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+ NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
nonNull.setPosition(current.getPosition());
if (blockWithNonNullInstruction != block) {
// If we split, add non-null IR on top of the new split block.
@@ -194,7 +194,7 @@
if (!dominatedUsers.isEmpty() && !dominatedPhiUsers.isEmpty()) {
Value nonNullValue = code.createValue(
knownToBeNonNullValue.outType(), knownToBeNonNullValue.getLocalInfo());
- NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+ NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, theIf);
InstructionListIterator targetIterator = target.listIterator();
nonNull.setPosition(targetIterator.next().getPosition());
targetIterator.previous();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index e834b61..536e4b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -62,6 +62,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.function.Consumer;
public class Outliner {
@@ -1009,7 +1010,12 @@
}
@Override
- public void registerReachableDefinitions(UseRegistry registry) {
+ public void registerInstructionsReferences(UseRegistry registry) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 24def66..fefe8c5 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -2039,6 +2039,24 @@
private void computeLiveRanges() {
computeLiveRanges(options, code, liveAtEntrySets, liveIntervals);
+ // Art VMs before Android M assume that the register for the receiver never changes its value.
+ // This assumption is used during verification. Allowing the receiver register to be
+ // overwritten can therefore lead to verification errors. If we could be targeting one of these
+ // VMs we block the receiver register throughout the method.
+ if (options.canHaveThisTypeVerifierBug() && !code.method.accessFlags.isStatic()) {
+ for (Instruction instruction : code.blocks.get(0).getInstructions()) {
+ if (instruction.isArgument() && instruction.outValue().isThis()) {
+ Value thisValue = instruction.outValue();
+ LiveIntervals thisIntervals = thisValue.getLiveIntervals();
+ thisIntervals.getRanges().clear();
+ thisIntervals.addRange(new LiveRange(0, code.getNextInstructionNumber()));
+ for (Set<Value> values : liveAtEntrySets.values()) {
+ values.add(thisValue);
+ }
+ return;
+ }
+ }
+ }
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 0de034a..26ef333 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -47,11 +48,18 @@
}
@Override
- public void registerReachableDefinitions(UseRegistry registry) {
+ public void registerInstructionsReferences(UseRegistry registry) {
registryCallback.accept(registry);
}
@Override
+ public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
+ // Support for synthesized code with catch handler is not implemented.
+ // Let's check that we're in a well known where no catch handler is possible.
+ assert sourceCode.instructionCount() == 1 || sourceCode instanceof SingleBlockSourceCode;
+ }
+
+ @Override
protected final int computeHashCode() {
return sourceCode.hashCode();
}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index a915849..321001e 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -38,7 +38,7 @@
// be removed.
if (method.accessFlags.isBridge() && !method.accessFlags.isAbstract()) {
InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
- method.getCode().registerReachableDefinitions(targetExtractor);
+ method.getCode().registerInstructionsReferences(targetExtractor);
DexMethod target = targetExtractor.getTarget();
InvokeKind kind = targetExtractor.getKind();
if (target != null && target.getArity() == method.method.getArity()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 7f5a4f3..685e733 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -30,7 +30,7 @@
private void identifyBridgeMethod(DexEncodedMethod method) {
if (method.accessFlags.isBridge()) {
InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
- method.getCode().registerReachableDefinitions(targetExtractor);
+ method.getCode().registerInstructionsReferences(targetExtractor);
DexMethod target = targetExtractor.getTarget();
InvokeKind kind = targetExtractor.getKind();
if (target != null &&
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 ead6bb7..f36f8a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1122,7 +1122,7 @@
if (protoLiteExtension != null && protoLiteExtension.appliesTo(method)) {
protoLiteExtension.processMethod(method, new UseRegistry(method), protoLiteFields);
} else {
- method.registerReachableDefinitions(new UseRegistry(method));
+ method.registerInstructionsReferences(new UseRegistry(method));
}
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(method));
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index f8b54ab..de21187 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -151,7 +151,8 @@
clazz.forEachField(field -> addMainDexType(field.field.type));
clazz.forEachMethod(method -> {
traceMethodDirectDependencies(method.method);
- method.registerReachableDefinitions(codeDirectReferenceCollector);
+ method.registerInstructionsReferences(codeDirectReferenceCollector);
+ method.registerCatchedTypes(this::addMainDexType);
});
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 9cf59b3..ca62583 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -46,19 +46,28 @@
private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList
.of("protomapping",
- "target");
+ "target"
+ );
private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList
- .of("keepdirectories", "runtype", "laststageoutput");
+ .of("keepdirectories",
+ "runtype",
+ "laststageoutput"
+ );
private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList
- .of("forceprocessing", "dontusemixedcaseclassnames",
- "dontpreverify", "experimentalshrinkunusedprotofields",
+ .of("forceprocessing",
+ "dontusemixedcaseclassnames",
+ "dontpreverify",
+ "experimentalshrinkunusedprotofields",
"filterlibraryjarswithorginalprogramjars",
"dontskipnonpubliclibraryclasses",
"dontskipnonpubliclibraryclassmembers",
- "invokebasemethod");
+ "invokebasemethod",
+ "android"
+ );
private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList
.of("isclassnamestring",
- "whyarenotsimple");
+ "whyarenotsimple"
+ );
private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList
.of("dontnote",
@@ -66,9 +75,22 @@
// TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
// should be reported as errors, not just as warnings!
"outjars",
- "adaptresourcefilecontents");
+ "adaptresourcefilecontents"
+ );
private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList
- .of();
+ .of(
+ // TODO(b/73707846): add support -addconfigurationdebugging
+ "addconfigurationdebugging"
+ );
+ private static final List<String> WARNED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList
+ .of(
+ // TODO(b/73708157): add support -assumenoexternalsideeffects <class_spec>
+ "assumenoexternalsideeffects",
+ // TODO(b/73707404): add support -assumenoescapingparameters <class_spec>
+ "assumenoescapingparameters",
+ // TODO(b/73708085): add support -assumenoexternalreturnvalues <class_spec>
+ "assumenoexternalreturnvalues"
+ );
// Those options are unsupported and are treated as compilation errors.
// Just ignoring them would produce outputs incompatible with user expectations.
@@ -91,7 +113,7 @@
if (configurationBuilder.isKeepParameterNames() && configurationBuilder.isObfuscating()) {
// The flag -keepparameternames has only effect when minifying, so ignore it if we
// are not.
- reporter.fatalError(new StringDiagnostic(
+ throw reporter.fatalError(new StringDiagnostic(
"-keepparameternames is not supported",
configurationBuilder.getKeepParameterNamesOptionOrigin(),
configurationBuilder.getKeepParameterNamesOptionPosition()));
@@ -181,14 +203,17 @@
} else if (
(option = Iterables.find(WARNED_SINGLE_ARG_OPTIONS,
this::skipOptionWithSingleArg, null)) != null
- || (option = Iterables.find(WARNED_FLAG_OPTIONS, this::skipFlag, null)) != null) {
+ || (option = Iterables.find(WARNED_FLAG_OPTIONS,
+ this::skipFlag, null)) != null
+ || (option = Iterables.find(WARNED_CLASS_DESCRIPTOR_OPTIONS,
+ this::skipOptionWithClassSpec, null)) != null) {
warnIgnoringOptions(option, optionStart);
} else if (
(option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null)) != null) {
reporter.error(new StringDiagnostic(
"Unsupported option: -" + option,
origin,
- getPostion(optionStart)));
+ getPosition(optionStart)));
} else if (acceptString("renamesourcefileattribute")) {
skipWhitespace();
if (isOptionalArgumentGiven()) {
@@ -202,8 +227,7 @@
ProguardKeepPackageNamesRule rule = parseKeepPackageNamesRule();
configurationBuilder.addRule(rule);
} else if (acceptString("keepparameternames")) {
- configurationBuilder.setKeepParameterNames(true,
- origin, getPostion(optionStart));
+ configurationBuilder.setKeepParameterNames(true, origin, getPosition(optionStart));
} else if (acceptString("checkdiscard")) {
ProguardCheckDiscardRule rule = parseCheckDiscardRule();
configurationBuilder.addRule(rule);
@@ -220,9 +244,7 @@
Integer expectedOptimizationPasses = acceptInteger();
if (expectedOptimizationPasses == null) {
throw reporter.fatalError(new StringDiagnostic(
- "Missing n of \"-optimizationpasses n\"",
- origin,
- getPostion(optionStart)));
+ "Missing n of \"-optimizationpasses n\"", origin, getPosition(optionStart)));
}
warnIgnoringOptions("optimizationpasses", optionStart);
} else if (acceptString("dontobfuscate")) {
@@ -249,8 +271,7 @@
}
} else if (acceptString("repackageclasses")) {
if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.FLATTEN) {
- warnOverridingOptions("repackageclasses", "flattenpackagehierarchy",
- optionStart);
+ warnOverridingOptions("repackageclasses", "flattenpackagehierarchy", optionStart);
}
skipWhitespace();
if (acceptChar('\'')) {
@@ -261,8 +282,7 @@
}
} else if (acceptString("flattenpackagehierarchy")) {
if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
- warnOverridingOptions("repackageclasses", "flattenpackagehierarchy",
- optionStart);
+ warnOverridingOptions("repackageclasses", "flattenpackagehierarchy", optionStart);
skipWhitespace();
if (isOptionalArgumentGiven()) {
skipSingleArgument();
@@ -331,15 +351,30 @@
}
} else if (acceptString("identifiernamestring")) {
configurationBuilder.addRule(parseIdentifierNameStringRule());
+ } else if (acceptString("if")) {
+ // TODO(b/73708139): add support -if <class_spec>
+ ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
+ parseClassSpec(keepRuleBuilder, true);
+ ProguardKeepRule ifRule = keepRuleBuilder.build();
+ assert ifRule.getClassType() == ProguardClassType.CLASS;
+
+ // Required a subsequent keep rule.
+ skipWhitespace();
+ if (acceptString("-keep")) {
+ ProguardKeepRule subsequentRule = parseKeepRule();
+ warnIgnoringOptions("if", optionStart);
+ } else {
+ throw reporter.fatalError(new StringDiagnostic(
+ "Option -if without a subsequent keep rule.", origin, getPosition(optionStart)));
+ }
} else {
String unknownOption = acceptString();
- reporter.error(new StringDiagnostic("Unknown option \"-" + unknownOption + "\"",
- origin, getPostion(optionStart)));
+ reporter.error(new StringDiagnostic(
+ "Unknown option \"-" + unknownOption + "\"", origin, getPosition(optionStart)));
}
return true;
}
-
private void parseInclude() throws ProguardRuleParserException {
TextPosition start = getPosition();
Path included = parseFileName();
@@ -639,9 +674,8 @@
} else if (acceptString("enum")) {
builder.setClassType(ProguardClassType.ENUM);
} else {
- throw reporter
- .fatalError(new StringDiagnostic("Expected [!]interface|@interface|class|enum",
- origin, getPostion(start)));
+ throw reporter.fatalError(new StringDiagnostic(
+ "Expected [!]interface|@interface|class|enum", origin, getPosition(start)));
}
}
@@ -1199,28 +1233,25 @@
private ProguardRuleParserException parseError(String message, TextPosition start,
Throwable cause) {
return new ProguardRuleParserException(message, snippetForPosition(start),
- origin, getPostion(start), cause);
+ origin, getPosition(start), cause);
}
private ProguardRuleParserException parseError(String message, TextPosition start) {
return new ProguardRuleParserException(message, snippetForPosition(start),
- origin, getPostion(start));
+ origin, getPosition(start));
}
private void warnIgnoringOptions(String optionName, TextPosition start) {
reporter.warning(new StringDiagnostic(
- "Ignoring option: -" + optionName,
- origin,
- getPostion(start)));
+ "Ignoring option: -" + optionName, origin, getPosition(start)));
}
private void warnOverridingOptions(String optionName, String victim, TextPosition start) {
- reporter.warning(
- new StringDiagnostic("Option -" + optionName + " overrides -" + victim,
- origin, getPostion(start)));
+ reporter.warning(new StringDiagnostic(
+ "Option -" + optionName + " overrides -" + victim, origin, getPosition(start)));
}
- private Position getPostion(TextPosition start) {
+ private Position getPosition(TextPosition start) {
if (start.getOffset() == position) {
return start;
} else {
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index 02159cf..63475b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -371,7 +371,7 @@
return existing;
} else if (existing.accessFlags.isBridge()) {
InvokeSingleTargetExtractor extractor = new InvokeSingleTargetExtractor();
- existing.getCode().registerReachableDefinitions(extractor);
+ existing.getCode().registerInstructionsReferences(extractor);
if (extractor.getTarget() != method.method) {
abortMerge = true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
index 8049bf4..c2ba7e5 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
@@ -116,13 +116,13 @@
// serialized stream for this proto. As we mask all reads in the writing code and normally
// remove fields that are only written but never read, we have to mark fields used in setters
// as read and written.
- method.registerReachableDefinitions(
+ method.registerInstructionsReferences(
new FieldWriteImpliesReadUseRegistry(registry, method.method.holder));
} else {
// Filter all getters and field accesses in these methods. We do not want fields to become
// live just due to being referenced in a special method. The pruning phase will remove
// all references to dead fields in the code later.
- method.registerReachableDefinitions(new FilteringUseRegistry(registry, method.method.holder,
+ method.registerInstructionsReferences(new FilteringUseRegistry(registry, method.method.holder,
protoLiteFields));
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index cccbb4b..b79e8e6 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -234,6 +234,17 @@
}
/**
+ * Convert class name to a binary name.
+ *
+ * @param className a package name i.e., "java.lang.Object"
+ * @return java class name in a binary name format, i.e., java/lang/Object
+ */
+ public static String getBinaryNameFromJavaType(String className) {
+ return className.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR);
+ }
+
+
+ /**
* Convert a class binary name to a descriptor.
*
* @param typeBinaryName class binary name i.e. "java/lang/Object"
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 6fc9e17..1c0face 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -68,6 +68,7 @@
// -dontoptimize disables optimizations by flipping related flags.
if (!proguardConfiguration.isOptimizing()) {
enableClassMerging = false;
+ enableDevirtualization = false;
enableNonNullTracking = false;
enableInlining = false;
enableSwitchMapRemoval = false;
@@ -83,6 +84,7 @@
// Optimization-related flags. These should conform to -dontoptimize.
public boolean enableClassMerging = false;
+ public boolean enableDevirtualization = true;
public boolean enableNonNullTracking = true;
public boolean enableInlining = true;
public boolean enableSwitchMapRemoval = true;
@@ -489,4 +491,11 @@
public boolean canUseNotInstruction() {
return minApiLevel >= AndroidApiLevel.L.getLevel();
}
+
+ // Art before M has a verifier bug where the type of the contents of the receiver register is
+ // assumed to not change. If the receiver register is reused for something else the verifier
+ // will fail and the code will not run.
+ public boolean canHaveThisTypeVerifierBug() {
+ return minApiLevel < AndroidApiLevel.M.getLevel();
+ }
}
diff --git a/src/test/examples/multidex006/ClassForMainDex.java b/src/test/examples/multidex006/ClassForMainDex.java
new file mode 100644
index 0000000..86c2e9a
--- /dev/null
+++ b/src/test/examples/multidex006/ClassForMainDex.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, 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 multidex006;
+
+/**
+ * Class directly referenced from Activity, will be kept in main dex. The class is not referenced
+ * by <clinit> or <init>, its direct references are not kept in main dex.
+ */
+public class ClassForMainDex {
+
+ public static void method1() {
+ try {
+ doNothing();
+ } catch (NotThrownException e) {
+ // ignore
+ }
+ }
+
+ public static void doNothing() {
+
+ }
+
+}
diff --git a/src/test/examples/multidex006/NotThrownException.java b/src/test/examples/multidex006/NotThrownException.java
new file mode 100644
index 0000000..4aa5e6c
--- /dev/null
+++ b/src/test/examples/multidex006/NotThrownException.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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 multidex006;
+
+public class NotThrownException extends RuntimeException {
+}
diff --git a/src/test/examples/multidex006/main-dex-rules-1.txt b/src/test/examples/multidex006/main-dex-rules-1.txt
new file mode 100644
index 0000000..333d866
--- /dev/null
+++ b/src/test/examples/multidex006/main-dex-rules-1.txt
@@ -0,0 +1,6 @@
+# Copyright (c) 2018, 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.
+
+-keep public class *.ClassForMainDex {
+}
\ No newline at end of file
diff --git a/src/test/examples/multidex006/ref-list-1.txt b/src/test/examples/multidex006/ref-list-1.txt
new file mode 100644
index 0000000..b39deba
--- /dev/null
+++ b/src/test/examples/multidex006/ref-list-1.txt
@@ -0,0 +1,2 @@
+Lmultidex006/ClassForMainDex;
+Lmultidex006/NotThrownException;
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index d03a03f..8ff72c7 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4541,10 +4541,6 @@
// 1) t02
// java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/util/concurrent/PriorityBlockingQueue/serialization/PriorityBlockingQueue_serialization_A01.golden.0.ser
- .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01", match(R8_NOT_AFTER_D8_COMPILER))
- // 1) t05
- // java.lang.AssertionError: Destroyed thread group was not finalized
-
.put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01",
match(D8_COMPILER, runtimesUpTo(Version.V6_0_1)))
// 1) t02
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 77f9ad9..fbb4d20 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.SmaliWriter;
+import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -25,6 +26,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
+import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
@@ -44,6 +46,7 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.jar.JarOutputStream;
+import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
@@ -481,26 +484,26 @@
}
protected ProcessResult runOnJava(String main, byte[]... classes) throws IOException {
- Path file = writeToZip(classes);
+ Path file = writeToZip(Arrays.asList(classes));
return ToolHelper.runJavaNoVerify(file, main);
}
- private Path writeToZip(byte[]... classes) throws IOException {
- File result = temp.newFile();
+ private Path writeToZip(List<byte[]> classes) throws IOException {
+ File result = temp.newFile("tmp.zip");
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
for (byte[] clazz : classes) {
String name = loadClassFromDump(clazz).getTypeName();
- ZipEntry zipEntry = new ZipEntry(DescriptorUtils.getPathFromJavaType(name));
- zipEntry.setSize(clazz.length);
- out.putNextEntry(zipEntry);
- out.write(clazz);
- out.closeEntry();
+ ZipUtils.writeToZipStream(out, DescriptorUtils.getPathFromJavaType(name), clazz);
}
}
return result.toPath();
}
+ protected Path writeToZip(JasminBuilder jasminBuilder) throws Exception {
+ return writeToZip(jasminBuilder.buildClasses());
+ }
+
protected static Class loadClassFromDump(byte[] dump) {
return new DumpLoader().loadClass(dump);
}
@@ -557,6 +560,13 @@
}
}
+ protected Stream<Instruction> filterInstructionKind(
+ DexCode dexCode, Class<? extends Instruction> kind) {
+ return Arrays.stream(dexCode.instructions)
+ .filter(kind::isInstance)
+ .map(kind::cast);
+ }
+
public enum MinifyMode {
NONE,
JAVA,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index cc325e2..0c0cd84 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -90,7 +90,9 @@
private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
- private static final String PROGUARD = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
+ private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
+ private static final String PROGUARD6_0 = "third_party/proguard/proguard6.0/bin/proguard.sh";
+ private static final String PROGUARD = PROGUARD5_2_1;
public enum DexVm {
ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
@@ -947,6 +949,10 @@
return Paths.get(System.getProperty("java.home"), "bin", "java").toString();
}
+ public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
+ return runArtProcessRaw(builder);
+ }
+
public static ProcessResult runArtRaw(String file, String mainClass)
throws IOException {
return runArtRaw(Collections.singletonList(file), mainClass, null);
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTest.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTest.java
new file mode 100644
index 0000000..c1d079e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTest.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2018, 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.cf;
+
+public class SynchronizedNoopTest {
+ public static synchronized void noop() {
+ System.out.println("Foo");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
new file mode 100644
index 0000000..20acb9f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018, 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.cf;
+
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.JarCode;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DexInspector;
+import java.util.ArrayList;
+import java.util.Collections;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodNode;
+
+public class SynchronizedNoopTestRunner {
+ private byte[] data;
+ static final Class CLASS = SynchronizedNoopTest.class;
+
+ @Test
+ public void testSynchronizedNoop() throws Exception {
+ AndroidAppConsumers a = new AndroidAppConsumers();
+ R8.run(
+ R8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(a.wrapClassFileConsumer(null))
+ .build());
+ DexInspector inspector = new DexInspector(a.build());
+ DexEncodedMethod method =
+ inspector.clazz(CLASS).method("void", "noop", Collections.emptyList()).getMethod();
+ ArrayList<AbstractInsnNode> insns = new ArrayList<>();
+ JarCode jarCode = method.getCode().asJarCode();
+ MethodNode node = jarCode.getNode();
+ assert node != null;
+ InsnList asmInsns = node.instructions;
+ for (int i = 0; i < asmInsns.size(); i++) {
+ insns.add(asmInsns.get(i));
+ }
+ boolean hasMonitor =
+ insns
+ .stream()
+ .anyMatch(
+ insn ->
+ insn.getOpcode() == Opcodes.MONITORENTER
+ || insn.getOpcode() == Opcodes.MONITOREXIT);
+ // TODO(b/73921688): Should not have monitor instruction here
+ assert hasMonitor;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java
new file mode 100644
index 0000000..14f9f98
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2018, 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.debuginfo;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class PreamblePositionTestRunner {
+
+ private static final String TEST_CLASS = "PreamblePositionTestSource";
+ private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
+
+ @ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void testBothBranchesDefaultConditionals() throws Exception {
+ testBothBranches(false);
+ }
+
+ @Test
+ public void testBothBranchesInvertConditionals() throws Exception {
+ testBothBranches(true);
+ }
+
+ private void testBothBranches(boolean invertConditionals) throws Exception {
+ Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
+ Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
+ Path outputDexPath = temp.newFolder().toPath();
+
+ Files.write(testClassPath, PreamblePositionTestSourceDump.dump());
+
+ ToolHelper.runD8(
+ D8Command.builder()
+ .addProgramFiles(testClassPath)
+ .setOutput(outputDexPath, OutputMode.DexIndexed)
+ .setMode(CompilationMode.RELEASE),
+ options -> {
+ options.testing.invertConditionals = invertConditionals;
+ });
+
+ String fileName = TEST_CLASS + ".java";
+
+ for (boolean testTrueBranch : new boolean[] {false, true}) {
+ ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
+ artCommandBuilder.appendClasspath(outputDexPath.resolve("classes.dex").toString());
+ artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
+ if (!testTrueBranch) {
+ artCommandBuilder.appendProgramArgument("1");
+ }
+
+ ProcessResult result = ToolHelper.runArtRaw(artCommandBuilder);
+
+ assertNotEquals(result.exitCode, 0);
+ if (testTrueBranch) {
+ assertTrue(result.stderr.contains("<true-branch-exception>"));
+ // Must have either explicit line = 0 or no line info at all.
+ assertTrue(
+ result.stderr.contains(fileName + ":0")
+ || (result.stderr.contains("at " + TEST_PACKAGE + "." + TEST_CLASS + ".main")
+ && !result.stderr.contains(fileName + ":")));
+ } else {
+ assertTrue(result.stderr.contains("<false-branch-exception>"));
+ assertTrue(
+ result.stderr.contains(
+ fileName + ":" + PreamblePositionTestSourceDump.FALSE_BRANCH_LINE_NUMBER));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
new file mode 100644
index 0000000..5ed2a4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2018, 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.debuginfo;
+
+import java.util.*;
+import org.objectweb.asm.*;
+
+/*
+Generated from the source code below, line numbers removed, except for the false branch,
+which is set to FALSE_BRANCH_LINE_NUMBER.
+
+ package com.android.tools.r8.debuginfo;
+
+ public class PreamblePositionTestSource {
+ public static void main(String[] args) {
+ System.err.println("<first-line>");
+ if (args.length == 0) {
+ throw new RuntimeException("<true-branch-exception>");
+ } else {
+ throw new RuntimeException("<false-branch-exception>");
+ }
+ }
+ }
+*/
+
+public class PreamblePositionTestSourceDump implements Opcodes {
+
+ static final int FALSE_BRANCH_LINE_NUMBER = 123;
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/debuginfo/PreamblePositionTestSource",
+ null,
+ "java/lang/Object",
+ null);
+
+ cw.visitSource("PreamblePositionTestSource.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable(
+ "this", "Lcom/android/tools/r8/debuginfo/PreamblePositionTestSource;", null, l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("<first-line>");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ Label l2 = new Label();
+ mv.visitJumpInsn(IFNE, l2);
+ Label l3 = new Label();
+ mv.visitLabel(l3);
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("<true-branch-exception>");
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(l2);
+ mv.visitLineNumber(FALSE_BRANCH_LINE_NUMBER, l2);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("<false-branch-exception>");
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(ATHROW);
+ Label l4 = new Label();
+ mv.visitLabel(l4);
+ mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l4, 0);
+ mv.visitMaxs(3, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
new file mode 100644
index 0000000..b152308
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2018, 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.devirtualize;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.CheckCast;
+import com.android.tools.r8.code.InvokeInterface;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A0;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A1;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.I;
+import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.Main;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class InvokeInterfaceToInvokeVirtualTest extends TestBase {
+
+ private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception {
+ R8Command command =
+ ToolHelper.addProguardConfigurationConsumer(
+ ToolHelper.prepareR8CommandBuilder(app),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+ })
+ .addProguardConfiguration(
+ ImmutableList.of(keepMainProguardConfiguration(main)),
+ Origin.unknown())
+ .setOutput(out, OutputMode.DexIndexed)
+ .build();
+ return ToolHelper.runR8(command);
+ }
+
+ @Test
+ public void listOfInterface() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(I.class),
+ ToolHelper.getClassAsBytes(A.class),
+ ToolHelper.getClassAsBytes(A0.class),
+ ToolHelper.getClassAsBytes(A1.class),
+ ToolHelper.getClassAsBytes(Main.class)
+ };
+ String main = Main.class.getCanonicalName();
+ ProcessResult javaOutput = runOnJava(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+
+ AndroidApp originalApp = buildAndroidApp(classes);
+ Path out = temp.getRoot().toPath();
+ AndroidApp processedApp = runR8(originalApp, Main.class, out);
+
+ DexInspector dexInspector = new DexInspector(processedApp);
+ ClassSubject clazz = dexInspector.clazz(main);
+ DexEncodedMethod m = clazz.method(DexInspector.MAIN).getMethod();
+ DexCode code = m.getCode().asDexCode();
+ long numOfInvokeInterface = filterInstructionKind(code, InvokeInterface.class).count();
+ // List#add, List#get
+ assertEquals(2, numOfInvokeInterface);
+ long numOfInvokeVirtual = filterInstructionKind(code, InvokeVirtual.class).count();
+ // System.out.println, I#get ~> A0#get
+ assertEquals(2, numOfInvokeVirtual);
+ long numOfCast = filterInstructionKind(code, CheckCast.class).count();
+ // check-cast I ~> check-cast A0
+ assertEquals(1, numOfCast);
+
+ ProcessResult artOutput = runOnArtRaw(processedApp, main);
+ assertEquals(0, artOutput.exitCode);
+ assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A.java
new file mode 100644
index 0000000..c767950
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2018, 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.devirtualize.invokeinterface;
+
+public abstract class A implements I {
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A0.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A0.java
new file mode 100644
index 0000000..67de6f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A0.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.devirtualize.invokeinterface;
+
+public class A0 extends A {
+ @Override
+ public int get() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A1.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A1.java
new file mode 100644
index 0000000..10f80cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/A1.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.devirtualize.invokeinterface;
+
+public class A1 extends A {
+ @Override
+ public int get() {
+ return 1;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/I.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/I.java
new file mode 100644
index 0000000..ee0a0a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/I.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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.devirtualize.invokeinterface;
+
+public interface I {
+ int get();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
new file mode 100644
index 0000000..570122d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, 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.devirtualize.invokeinterface;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Main {
+ private static final int COUNT = 8;
+
+ public static void main(String[] args) {
+ I instance = new A0();
+ List<I> l = new ArrayList<>();
+ for (int i = 0; i < COUNT; i++) {
+ l.add(instance);
+ }
+
+ int sum = 0;
+ for (int i = 0; i < COUNT; i++) {
+ sum += l.get(i).get();
+ }
+ System.out.println(sum);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 9f520a2..b3820dd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -20,14 +20,16 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import org.junit.Assume;
import org.junit.runner.RunWith;
@@ -40,7 +42,7 @@
@Parameters(name = "{0}_{1}")
public static Collection<Object[]> data() {
- ImmutableList.Builder<Object[]> builder = new Builder<>();
+ ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
builder.add(new Object[]{Boolean.TRUE, targetVersion});
builder.add(new Object[]{Boolean.FALSE, targetVersion});
@@ -51,6 +53,12 @@
@Parameter(0) public boolean allowAccessModification;
@Parameter(1) public KotlinTargetVersion targetVersion;
+ private final List<Path> extraClasspath = new ArrayList<>();
+
+ protected void addExtraClasspath(Path path) {
+ extraClasspath.add(path);
+ }
+
protected static void checkMethodIsInvokedAtLeastOnce(DexCode dexCode,
MethodSignature... methodSignatures) {
for (MethodSignature methodSignature : methodSignatures) {
@@ -101,14 +109,29 @@
return classSubject;
}
+ protected static FieldSubject checkFieldIsPresent(ClassSubject classSubject, String fieldType,
+ String fieldName) {
+ FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
+ assertNotNull(fieldSubject);
+ assertTrue(fieldSubject.isPresent());
+ return fieldSubject;
+ }
+
+ protected static void checkFieldIsAbsent(ClassSubject classSubject, String fieldType,
+ String fieldName) {
+ FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
+ assertNotNull(fieldSubject);
+ assertFalse(fieldSubject.isPresent());
+ }
+
protected static MethodSubject checkMethodIsPresent(ClassSubject classSubject,
MethodSignature methodSignature) {
return checkMethod(classSubject, methodSignature, true);
}
- protected static MethodSubject checkMethodIsAbsent(ClassSubject classSubject,
+ protected static void checkMethodIsAbsent(ClassSubject classSubject,
MethodSignature methodSignature) {
- return checkMethod(classSubject, methodSignature, false);
+ checkMethod(classSubject, methodSignature, false);
}
protected static MethodSubject checkMethod(ClassSubject classSubject,
@@ -162,9 +185,14 @@
proguardRules += extraProguardRules;
}
+ // Build classpath for compilation (and java execution)
+ List<Path> classpath = new ArrayList<>(extraClasspath.size() + 1);
+ classpath.add(jarFile);
+ classpath.addAll(extraClasspath);
+
// Build with R8
AndroidApp.Builder builder = AndroidApp.builder();
- builder.addProgramFiles(jarFile);
+ builder.addProgramFiles(classpath);
AndroidApp app = compileWithR8(builder.build(), proguardRules.toString());
// Materialize file for execution.
@@ -176,7 +204,7 @@
ToolHelper.runArtNoVerificationErrors(generatedDexFile.toString(), mainClass);
// Compare with Java.
- ToolHelper.ProcessResult javaResult = ToolHelper.runJava(jarFile, mainClass);
+ ToolHelper.ProcessResult javaResult = ToolHelper.runJava(classpath, mainClass);
if (javaResult.exitCode != 0) {
System.out.println(javaResult.stdout);
System.err.println(javaResult.stderr);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
index 87ffcef..56a727e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.naming.MemberNaming;
import com.google.common.collect.Maps;
-
import java.util.Collections;
import java.util.Map;
@@ -16,15 +15,34 @@
* <p>See https://kotlinlang.org/docs/reference/classes.html</p>
*/
class KotlinClass {
+
+ /**
+ * This is the suffix appended by Kotlin compiler to getter and setter method names of
+ * internal properties.
+ *
+ * It must match the string passed in command-line option "-module-name" of Kotlin compiler. The
+ * default value is "main".
+ */
+ private static final String KOTLIN_MODULE_NAME = "main";
+
+ enum Visibility {
+ PUBLIC,
+ INTERNAL,
+ PROTECTED,
+ PRIVATE;
+ }
+
protected static class KotlinProperty {
private final String name;
private final String type;
+ private final Visibility visibility;
private final int index;
- private KotlinProperty(String name, String type, int index) {
+ private KotlinProperty(String name, String type, Visibility visibility, int index) {
this.name = name;
this.type = type;
this.index = index;
+ this.visibility = visibility;
}
public String getName() {
@@ -35,6 +53,10 @@
return type;
}
+ public Visibility getVisibility() {
+ return visibility;
+ }
+
public int getIndex() {
return index;
}
@@ -50,9 +72,9 @@
return className;
}
- public KotlinClass addProperty(String name, String type) {
+ public KotlinClass addProperty(String name, String type, Visibility visibility) {
assert !properties.containsKey(name);
- properties.put(name, new KotlinProperty(name, type, properties.size()));
+ properties.put(name, new KotlinProperty(name, type, visibility, properties.size()));
return this;
}
@@ -61,17 +83,37 @@
return properties.get(name);
}
- public MemberNaming.MethodSignature getGetterForProperty(String name) {
- String type = getProperty(name).type;
+ public MemberNaming.MethodSignature getGetterForProperty(String propertyName) {
+ KotlinProperty property = getProperty(propertyName);
+ String type = property.type;
String getterName;
- if (name.length() > 2 && name.startsWith("is")
- && (name.charAt(2) == '_' || Character.isUpperCase(name.charAt(2)))) {
+ if (propertyName.length() > 2 && propertyName.startsWith("is")
+ && (propertyName.charAt(2) == '_' || Character.isUpperCase(propertyName.charAt(2)))) {
// Getter for property "isAbc" is "isAbc".
- getterName = name;
+ getterName = propertyName;
} else {
// Getter for property "abc" is "getAbc".
- getterName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ getterName =
+ "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+ }
+ if (property.getVisibility() == Visibility.INTERNAL) {
+ // Append module name
+ getterName += "$" + KOTLIN_MODULE_NAME;
}
return new MemberNaming.MethodSignature(getterName, type, Collections.emptyList());
}
+
+ public MemberNaming.MethodSignature getSetterForProperty(String propertyName) {
+ KotlinProperty property = getProperty(propertyName);
+ String setterName = "set"
+ + Character.toUpperCase(property.name.charAt(0))
+ + property.name.substring(1);
+ if (property.getVisibility() == Visibility.INTERNAL) {
+ // Append module name
+ setterName += "$" + KOTLIN_MODULE_NAME;
+ }
+ return new MemberNaming.MethodSignature(setterName, "void",
+ Collections.singleton(property.getType()));
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
index f8861cd..bb24adc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
@@ -23,8 +23,8 @@
}
@Override
- public KotlinDataClass addProperty(String name, String type) {
- return (KotlinDataClass) super.addProperty(name, type);
+ public KotlinDataClass addProperty(String name, String type, Visibility visibility) {
+ return (KotlinDataClass) super.addProperty(name, type, visibility);
}
public MemberNaming.MethodSignature getComponentNFunctionForProperty(String name) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 379579f..daf05f5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.kotlin.KotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -15,8 +16,8 @@
public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
private static final KotlinDataClass TEST_DATA_CLASS = new KotlinDataClass("dataclass.Person")
- .addProperty("name", "java.lang.String")
- .addProperty("age", "int");
+ .addProperty("name", "java.lang.String", Visibility.PUBLIC)
+ .addProperty("age", "int", Visibility.PUBLIC);
private static final MethodSignature NAME_GETTER_METHOD =
TEST_DATA_CLASS.getGetterForProperty("name");
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
new file mode 100644
index 0000000..c9e1298
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -0,0 +1,329 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.kotlin.KotlinClass.Visibility;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import java.nio.file.Path;
+import org.junit.Test;
+
+public class R8KotlinPropertiesTest extends AbstractR8KotlinTestBase {
+
+ private static final String PACKAGE_NAME = "properties";
+
+ private static final String JAVA_LANG_STRING = "java.lang.String";
+
+ // This is the name of the Jasmin-generated class which contains the "main" method which will
+ // invoke the tested method.
+ private static final String JASMIN_MAIN_CLASS = "properties.TestMain";
+
+ private static final KotlinClass MUTABLE_PROPERTY_CLASS =
+ new KotlinClass("properties.MutableProperty")
+ .addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+ .addProperty("protectedProp", JAVA_LANG_STRING, Visibility.PROTECTED)
+ .addProperty("internalProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+ .addProperty("publicProp", JAVA_LANG_STRING, Visibility.PUBLIC)
+ .addProperty("primitiveProp", "int", Visibility.PUBLIC);
+
+ private static final KotlinClass USER_DEFINED_PROPERTY_CLASS =
+ new KotlinClass("properties.UserDefinedProperty")
+ .addProperty("durationInMilliSeconds", "int", Visibility.PUBLIC)
+ .addProperty("durationInSeconds", "int", Visibility.PUBLIC);
+
+ private static final KotlinClass LATE_INIT_PROPERTY_CLASS =
+ new KotlinClass("properties.LateInitProperty")
+ .addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
+ .addProperty("protectedLateInitProp", JAVA_LANG_STRING, Visibility.PROTECTED)
+ .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
+ .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
+
+ @Test
+ public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_noUseOfProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ for (String propertyName : MUTABLE_PROPERTY_CLASS.properties.keySet()) {
+ checkMethodIsAbsent(classSubject,
+ MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrivateProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "privateProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+ if (!allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ }
+
+ // Private property has no getter or setter.
+ checkMethodIsAbsent(classSubject, MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject, MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useProtectedProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "protectedProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+
+ // Protected property has private field.
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_useInternalProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "internalProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+
+ // Internal property has private field
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePublicProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "publicProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, JAVA_LANG_STRING, propertyName);
+
+ // Public property has private field
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ @Test
+ public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/MutablePropertyKt", "mutableProperty_usePrimitiveProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ MUTABLE_PROPERTY_CLASS.getClassName());
+ String propertyName = "primitiveProp";
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, "int", propertyName);
+
+ MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ MethodSignature setter = MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ checkMethodIsAbsent(classSubject, setter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ checkMethodIsPresent(classSubject, setter);
+ }
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_noUseOfProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ for (String propertyName : LATE_INIT_PROPERTY_CLASS.properties.keySet()) {
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ }
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "privateLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ if (!allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ }
+
+ // Private late init property have no getter or setter.
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt",
+ "lateInitProperty_useProtectedLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "protectedLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ if (!allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isProtected());
+ }
+
+ // Protected late init property have protected getter
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "internalLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+
+ // Internal late init property have protected getter
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ LATE_INIT_PROPERTY_CLASS.getClassName());
+ String propertyName = "publicLateInitProp";
+ FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
+ assertTrue("Field is absent", fieldSubject.isPresent());
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+
+ // Internal late init property have protected getter
+ checkMethodIsAbsent(classSubject,
+ LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ });
+ }
+
+ @Test
+ public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
+ addMainToClasspath("properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ USER_DEFINED_PROPERTY_CLASS.getClassName());
+ for (String propertyName : USER_DEFINED_PROPERTY_CLASS.properties.keySet()) {
+ checkMethodIsAbsent(classSubject,
+ USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName));
+ checkMethodIsAbsent(classSubject,
+ USER_DEFINED_PROPERTY_CLASS.getSetterForProperty(propertyName));
+ }
+ });
+ }
+
+ @Test
+ public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
+ addMainToClasspath("properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
+ runTest(PACKAGE_NAME, JASMIN_MAIN_CLASS, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject classSubject = checkClassExists(dexInspector,
+ USER_DEFINED_PROPERTY_CLASS.getClassName());
+ String propertyName = "durationInSeconds";
+ // The 'wrapper' property is not assigned to a backing field, it only relies on the wrapped
+ // property.
+ checkFieldIsAbsent(classSubject, "int", "durationInSeconds");
+
+ FieldSubject fieldSubject = checkFieldIsPresent(classSubject, "int",
+ "durationInMilliSeconds");
+ MethodSignature getter = USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ if (allowAccessModification) {
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ checkMethodIsAbsent(classSubject, getter);
+ } else {
+ assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ checkMethodIsPresent(classSubject, getter);
+ }
+ });
+ }
+
+ /**
+ * Generates a "main" class which invokes the given static method on the given klass. This new
+ * class is then added to the test classpath.
+ */
+ private void addMainToClasspath(String klass, String method) throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ ClassBuilder mainClassBuilder =
+ builder.addClass(DescriptorUtils.getBinaryNameFromJavaType(JASMIN_MAIN_CLASS));
+ mainClassBuilder.addMainMethod(
+ "invokestatic " + klass + "/" + method + "()V",
+ "return"
+ );
+
+ Path output = writeToZip(builder);
+ addExtraClasspath(output);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 36ad4c9..2e8645c 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -124,6 +124,17 @@
doTest5(6);
}
+ @Test
+ public void traceMainDexList006() throws Throwable {
+ doTest(
+ "traceMainDexList006",
+ "multidex006",
+ EXAMPLE_BUILD_DIR,
+ Paths.get(EXAMPLE_SRC_DIR, "multidex006", "main-dex-rules-1.txt"),
+ Paths.get(EXAMPLE_SRC_DIR, "multidex006", "ref-list-1.txt"),
+ AndroidApiLevel.I);
+ }
+
private void doTest5(int variant) throws Throwable {
doTest(
"traceMainDexList003",
@@ -229,9 +240,15 @@
int nonLambdaOffset = 0;
for (int i = 0; i < refList.length; i++) {
String reference = refList[i].trim();
+ if (r8MainDexList.size() <= i) {
+ Assert.fail("R8 main dex list is missing '" + reference + "'");
+ }
checkSameMainDexEntry(reference, r8MainDexList.get(i));
// The main dex list generator does not do any lambda desugaring.
if (!isLambda(reference)) {
+ if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
+ Assert.fail("Main dex list generator is missing '" + reference + "'");
+ }
checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
checkSameMainDexEntry(
reference, mainDexGeneratorMainDexListFromConsumer.get(i - nonLambdaOffset));
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 183c458..00fa060 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -157,6 +157,7 @@
// Parse from file.
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PROGUARD_SPEC_FILE));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(24, rules.size());
assertEquals(1, rules.get(0).getMemberRules().size());
@@ -175,6 +176,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(MULTIPLE_NAME_PATTERNS_FILE));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
@@ -204,6 +206,7 @@
" p-.-OtherNameWithDash- -method-(-p.-WithDash-, -package-.-ClassNameWithDash-[]); ",
"}"));
parser.parse(createConfigurationForTesting(ImmutableList.of(nonJavaIdentifiers)));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
assertEquals(ProguardClassType.CLASS, rules.get(0).getClassType());
@@ -239,6 +242,7 @@
new ProguardConfigurationParser(dexItemFactory, reporter);
String dontwarn = "-dontwarn !foobar,*bar";
parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarn)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(
config.getDontWarnPatterns().matches(dexItemFactory.createType("Lboobaz;")));
@@ -257,6 +261,7 @@
List<String> configuration2 = ImmutableList.of("-dontwarn foo.**", "-dontwarn bar.**");
for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
parser.parse(createConfigurationForTesting(configuration));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getDontWarnPatterns().matches(dexItemFactory.createType("Lfoo/Bar;")));
@@ -274,6 +279,7 @@
new ProguardConfigurationParser(dexItemFactory, reporter);
String dontwarnAll = "-dontwarn *";
parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getDontWarnPatterns().matches(dexItemFactory.createType("Lboobaz;")));
@@ -305,6 +311,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ACCESS_FLAGS_FILE));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
@@ -350,6 +357,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(WHY_ARE_YOU_KEEPING_FILE));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
@@ -364,6 +372,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
assertEquals(1, assumeNoSideEffects.size());
assumeNoSideEffects.get(0).getMemberRules().forEach(rule -> {
@@ -376,6 +385,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
assertEquals(1, assumeNoSideEffects.size());
int matches = 0;
@@ -432,6 +442,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_VALUES_WITH_RETURN_VALUE));
+ verifyParserEndsCleanly();
List<ProguardConfigurationRule> assumeValues = parser.getConfig().getRules();
assertEquals(1, assumeValues.size());
int matches = 0;
@@ -490,6 +501,7 @@
new ProguardConfigurationParser(dexItemFactory, reporter);
String adaptClassStrings = "-adaptclassstrings !foobar,*bar";
parser.parse(createConfigurationForTesting(ImmutableList.of(adaptClassStrings)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
@@ -509,6 +521,7 @@
ImmutableList.of("-adaptclassstrings foo.**", "-adaptclassstrings bar.**");
for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
parser.parse(createConfigurationForTesting(configuration));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoo/Bar;")));
@@ -526,6 +539,7 @@
new ProguardConfigurationParser(dexItemFactory, reporter);
String adaptAll = "-adaptclassstrings *";
parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
@@ -542,6 +556,7 @@
new ProguardConfigurationParser(dexItemFactory, reporter);
String adaptAll = "-adaptclassstrings";
parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
@@ -568,6 +583,7 @@
+ " @my.annotations.IdentifierNameString *;\n"
+ "}";
parser.parse(createConfigurationForTesting(ImmutableList.of(config1, config2, config3)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
List<ProguardConfigurationRule> identifierNameStrings = config.getRules();
assertEquals(3, identifierNameStrings.size());
@@ -600,6 +616,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_OBFUSCATE));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(config.isObfuscating());
}
@@ -609,6 +626,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_1));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
@@ -620,6 +638,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_2));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
@@ -631,6 +650,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_3));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
@@ -642,6 +662,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_4));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
@@ -649,11 +670,13 @@
}
@Test
- public void flattenPackageHierarchyCannotOverrideRepackageClasses()
- throws Exception {
+ public void flattenPackageHierarchyCannotOverrideRepackageClasses() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
- parser.parse(Paths.get(PACKAGE_OBFUSCATION_5));
+ Path path = Paths.get(PACKAGE_OBFUSCATION_5);
+ parser.parse(path);
+ checkDiagnostic(handler.warnings, path, 6, 1,
+ "repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
@@ -661,11 +684,13 @@
}
@Test
- public void repackageClassesOverridesFlattenPackageHierarchy()
- throws Exception {
+ public void repackageClassesOverridesFlattenPackageHierarchy() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
- parser.parse(Paths.get(PACKAGE_OBFUSCATION_6));
+ Path path = Paths.get(PACKAGE_OBFUSCATION_6);
+ parser.parse(path);
+ checkDiagnostic(handler.warnings, path, 6, 1,
+ "repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
@@ -677,6 +702,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(APPLY_MAPPING));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.hasApplyMappingFile());
}
@@ -696,8 +722,10 @@
@Test
public void parseIncluding() throws Exception {
- new ProguardConfigurationParser(new DexItemFactory(), reporter)
- .parse(Paths.get(INCLUDING));
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(Paths.get(INCLUDING));
+ verifyParserEndsCleanly();
}
@Test
@@ -743,11 +771,10 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(
Collections.singletonList("-injars abc.jar(*.zip;*.class)")));
+ fail();
} catch (AbortException e) {
assertEquals(1, handler.errors.size());
- return;
}
- fail();
}
@Test
@@ -755,6 +782,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(SEEDS));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintSeeds());
assertNull(config.getSeedFile());
@@ -765,6 +793,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(SEEDS_2));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintSeeds());
assertNotNull(config.getSeedFile());
@@ -775,6 +804,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(VERBOSE));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isVerbose());
}
@@ -784,6 +814,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(KEEPDIRECTORIES));
+ verifyParserEndsCleanly();
}
@Test
@@ -791,6 +822,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_SHRINK));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(config.isShrinking());
}
@@ -800,6 +832,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES));
+ verifyParserEndsCleanly();
}
@Test
@@ -807,6 +840,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS));
+ verifyParserEndsCleanly();
}
@Test
@@ -815,8 +849,7 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path source = Paths.get(IDENTIFIER_NAME_STRING);
parser.parse(source);
- assertEquals(0, handler.infos.size());
- assertEquals(0, handler.warnings.size());
+ verifyParserEndsCleanly();
}
@Test
@@ -824,6 +857,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(OVERLOAD_AGGRESIVELY));
+ verifyParserEndsCleanly();
}
@Test
@@ -832,21 +866,30 @@
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_OPTIMIZE));
ProguardConfiguration config = parser.getConfig();
+ verifyParserEndsCleanly();
+ assertFalse(config.isOptimizing());
}
@Test
public void parseDontOptimizeOverridesPasses() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
- parser.parse(Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES));
+ Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES);
+ parser.parse(path);
+ checkDiagnostic(handler.warnings, path, 7, 1,
+ "Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
+ assertFalse(config.isOptimizing());
}
@Test
public void parseOptimizationPasses() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
- parser.parse(Paths.get(OPTIMIZATION_PASSES));
+ Path path = Paths.get(OPTIMIZATION_PASSES);
+ parser.parse(path);
+ checkDiagnostic(handler.warnings, path, 5, 1,
+ "Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
}
@@ -882,6 +925,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PARSE_AND_SKIP_SINGLE_ARGUMENT));
+ verifyParserEndsCleanly();
}
@Test
@@ -889,6 +933,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PRINT_USAGE));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintUsage());
assertNull(config.getPrintUsageFile());
@@ -899,6 +944,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PRINT_USAGE_TO_FILE));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintUsage());
assertNotNull(config.getPrintUsageFile());
@@ -909,6 +955,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(TARGET));
+ verifyParserEndsCleanly();
}
@Test
@@ -938,6 +985,7 @@
"-laststageoutput /some/file/name "
);
parser.parse(proguardConfig);
+ verifyParserEndsCleanly();
}
@Test
@@ -947,6 +995,7 @@
String config1 = "-renamesourcefileattribute PG\n";
String config2 = "-keepattributes SourceFile,SourceDir\n";
parser.parse(createConfigurationForTesting(ImmutableList.of(config1, config2)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals("PG", config.getRenameSourceFileAttribute());
assertTrue(config.getKeepAttributes().sourceFile);
@@ -969,6 +1018,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(config)));
+ verifyParserEndsCleanly();
assertEquals(
ProguardKeepAttributes.fromPatterns(expected),
parser.getConfigRawForTesting().getKeepAttributes());
@@ -1011,6 +1061,7 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-useuniqueclassmembernames"
)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isUseUniqueClassMemberNames());
}
@@ -1039,6 +1090,7 @@
"-keepparameternames",
"-dontobfuscate"
)));
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isKeepParameterNames());
@@ -1046,9 +1098,11 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-keepparameternames"
)));
+ verifyParserEndsCleanly();
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-dontobfuscate"
)));
+ verifyParserEndsCleanly();
config = parser.getConfig();
assertTrue(config.isKeepParameterNames());
}
@@ -1059,12 +1113,11 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(Collections.singletonList("-")));
+ fail();
} catch (AbortException e) {
assertEquals(1, handler.errors.size());
assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("-"));
- return;
}
- fail();
}
@Test
@@ -1073,12 +1126,160 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(Collections.singletonList("--no-locals")));
+ fail();
} catch (AbortException e) {
+
assertEquals(1, handler.errors.size());
assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("--no-locals"));
- return;
}
- fail();
+ }
+
+ @Test
+ public void parse_if() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-if class **$$ModuleAdapter",
+ "-keep class A",
+ "-if class **$$InjectAdapter",
+ "-keep class B",
+ "-if class **$$StaticInjection",
+ "-keep class C",
+ "-keepnames class dagger.Lazy"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ assertEquals(3, handler.warnings.size());
+ for (int i = 0; i < 3; i++) {
+ assertTrue(handler.warnings.get(i).getDiagnosticMessage().contains("Ignoring option: -if"));
+ }
+ }
+
+ @Test
+ public void parse_if_if() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-if class **$$ModuleAdapter",
+ "-if class **$$InjectAdapter"
+ );
+ try {
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ fail();
+ } catch (AbortException e) {
+ checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ "without", "subsequent", "keep");
+ }
+ }
+
+ @Test
+ public void parse_if_end() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-if class **$$ModuleAdapter"
+ );
+ try {
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ fail();
+ } catch (AbortException e) {
+ checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ "without", "subsequent", "keep");
+ }
+ }
+
+ @Test
+ public void parse_assumenoexternalsideeffects() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-assumenoexternalsideeffects class java.lang.StringBuilder {",
+ " public java.lang.StringBuilder();",
+ " public java.lang.StringBuilder(int);",
+ " public java.lang.StringBuilder(java.lang.String);",
+ " public java.lang.StringBuilder append(java.lang.Object);",
+ " public java.lang.StringBuilder append(java.lang.String);",
+ " public java.lang.StringBuilder append(java.lang.StringBuffer);",
+ " public java.lang.StringBuilder append(char[]);",
+ " public java.lang.StringBuilder append(char[], int, int);",
+ " public java.lang.StringBuilder append(boolean);",
+ " public java.lang.StringBuilder append(char);",
+ " public java.lang.StringBuilder append(int);",
+ " public java.lang.StringBuilder append(long);",
+ " public java.lang.StringBuilder append(float);",
+ " public java.lang.StringBuilder append(double);",
+ " public java.lang.String toString();",
+ "}"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ "Ignoring", "-assumenoexternalsideeffects");
+ }
+
+ @Test
+ public void parse_assumenoescapingparameters() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-assumenoescapingparameters class java.lang.System {",
+ " public static void arraycopy(java.lang.Object, int, java.lang.Object, int, int);",
+ "}"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ "Ignoring", "-assumenoescapingparameters");
+ }
+
+ @Test
+ public void parse_assumenoexternalreturnvalues() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-assumenoexternalreturnvalues class java.lang.StringBuilder {",
+ " public java.lang.StringBuilder append(java.lang.Object);",
+ " public java.lang.StringBuilder append(java.lang.String);",
+ " public java.lang.StringBuilder append(java.lang.StringBuffer);",
+ " public java.lang.StringBuilder append(char[]);",
+ " public java.lang.StringBuilder append(char[], int, int);",
+ " public java.lang.StringBuilder append(boolean);",
+ " public java.lang.StringBuilder append(char);",
+ " public java.lang.StringBuilder append(int);",
+ " public java.lang.StringBuilder append(long);",
+ " public java.lang.StringBuilder append(float);",
+ " public java.lang.StringBuilder append(double);",
+ "}"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ "Ignoring", "-assumenoexternalreturnvalues");
+ }
+
+ @Test
+ public void parse_android() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-android"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ verifyParserEndsCleanly();
+ }
+
+ @Test
+ public void parse_addconfigurationdebugging() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-addconfigurationdebugging"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ "Ignoring", "-addconfigurationdebugging");
+ }
+
+ private void verifyParserEndsCleanly() {
+ assertEquals(0, handler.infos.size());
+ assertEquals(0, handler.warnings.size());
+ assertEquals(0, handler.errors.size());
}
private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
@@ -1094,8 +1295,8 @@
}
assertEquals(lineStart, position.getLine());
assertEquals(columnStart, position.getColumn());
- for (String part:messageParts) {
- assertTrue(diagnostic.getDiagnosticMessage()+ "doesn't contain \"" + part + "\"",
+ for (String part : messageParts) {
+ assertTrue(diagnostic.getDiagnosticMessage()+ " doesn't contain \"" + part + "\"",
diagnostic.getDiagnosticMessage().contains(part));
}
return diagnostic;
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 085e46c..cfbfa84 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -296,9 +296,12 @@
ImmutableList.of(mainClass, forNameClass1, forNameClass2)),
proguardedJar, proguardConfigFile);
Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
+ assertEquals(3, classesAfterProguard.size());
assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
- assertTrue(classesAfterProguard.contains(forNameClass1.getCanonicalName()));
- assertTrue(classesAfterProguard.contains(forNameClass2.getCanonicalName()));
+ if (!allowObfuscation) {
+ assertTrue(classesAfterProguard.contains(forNameClass1.getCanonicalName()));
+ assertTrue(classesAfterProguard.contains(forNameClass2.getCanonicalName()));
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index 16545bd..ca3fd2a 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -11,18 +11,15 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
import org.junit.Test;
public class IncludeDescriptorClassesTest extends TestBase {
+ // Actually running Proguard should only be during development.
+ private final boolean RUN_PROGUARD = false;
private class Result {
final DexInspector inspector;
@@ -75,8 +72,8 @@
Set<String> classesAfterProguard = null;
// Actually running Proguard should only be during development.
- if (false) {
- Path proguardedJar = temp.newFile("proguarded.jar").toPath();
+ if (RUN_PROGUARD) {
+ Path proguardedJar = temp.newFolder().toPath().resolve("proguarded.jar");
ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig);
classesAfterProguard = readClassesInJar(proguardedJar);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index 9affccf..8e39b67 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeInterface;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.graph.DexCode;
@@ -24,7 +25,6 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
import java.util.List;
import org.junit.Test;
@@ -50,58 +50,51 @@
);
builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
inspection.accept(new DexInspector(app));
assertEquals(expectedResult, runOnArt(app, mainClass));
}
- private int countInvokeInterfaceInX(DexInspector inspector) {
+ private int countInstructionInX(DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForX =
new MethodSignature("x", "void", ImmutableList.of(BaseInterface.class.getCanonicalName()));
DexCode x = inspector.clazz(Main.class).method(signatureForX).getMethod().getCode().asDexCode();
- return (int) Arrays.stream(x.instructions)
- .filter(instruction -> instruction instanceof InvokeInterface)
- .count();
+ return (int) filterInstructionKind(x, invoke).count();
}
- private int countInvokeInterfaceInY(DexInspector inspector) {
+ private int countInstructionInY(DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForY =
new MethodSignature("y", "void", ImmutableList.of(SubInterface.class.getCanonicalName()));
DexCode y = inspector.clazz(Main.class).method(signatureForY).getMethod().getCode().asDexCode();
- return (int) Arrays.stream(y.instructions)
- .filter(instruction -> instruction instanceof InvokeInterface)
- .map(instruction -> (InvokeInterface) instruction)
+ return (int) filterInstructionKind(y, invoke)
.filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
.count();
}
- private int countInvokeVirtualInZ(DexInspector inspector) {
+ private int countInstructionInZ(DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(TestClass.class.getCanonicalName()));
DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
- return (int) Arrays.stream(z.instructions)
- .filter(instruction -> instruction instanceof InvokeVirtual)
- .map(instruction -> (InvokeVirtual) instruction)
+ return (int) filterInstructionKind(z, invoke)
.filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
.count();
}
- private int countInvokeVirtualInZSubClass(DexInspector inspector) {
+ private int countInstructionInZSubClass(
+ DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(SubClass.class.getCanonicalName()));
DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
- return (int) Arrays.stream(z.instructions)
- .filter(instruction -> instruction instanceof InvokeVirtual)
- .map(instruction -> (InvokeVirtual) instruction)
+ return (int) filterInstructionKind(z, invoke)
.filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
.count();
}
private void noInterfaceKept(DexInspector inspector) {
// Indirectly assert that method is inlined into x, y and z.
- assertEquals(1, countInvokeInterfaceInX(inspector));
- assertEquals(1, countInvokeInterfaceInY(inspector));
- assertEquals(1, countInvokeVirtualInZ(inspector));
+ assertEquals(1, countInstructionInX(inspector, InvokeInterface.class));
+ assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
+ assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
}
@Test
@@ -113,11 +106,11 @@
private void baseInterfaceKept(DexInspector inspector) {
// Indirectly assert that method is not inlined into x.
- assertEquals(3, countInvokeInterfaceInX(inspector));
+ assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
// Indirectly assert that method is inlined into y and z.
- assertEquals(1, countInvokeInterfaceInY(inspector));
- assertEquals(1, countInvokeVirtualInZ(inspector));
- assertEquals(1, countInvokeVirtualInZSubClass(inspector));
+ assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
+ assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
}
@Test
@@ -133,11 +126,11 @@
private void subInterfaceKept(DexInspector inspector) {
// Indirectly assert that method is not inlined into x or y.
- assertEquals(3, countInvokeInterfaceInX(inspector));
- assertEquals(3, countInvokeInterfaceInY(inspector));
+ assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
+ assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
// Indirectly assert that method is inlined into z.
- assertEquals(1, countInvokeVirtualInZ(inspector));
- assertEquals(1, countInvokeVirtualInZSubClass(inspector));
+ assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
}
@Test
@@ -155,10 +148,10 @@
private void classKept(DexInspector inspector) {
// Indirectly assert that method is not inlined into x, y or z.
- assertEquals(3, countInvokeInterfaceInX(inspector));
- assertEquals(3, countInvokeInterfaceInY(inspector));
- assertEquals(3, countInvokeVirtualInZ(inspector));
- assertEquals(3, countInvokeVirtualInZSubClass(inspector));
+ assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
+ assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
+ assertEquals(3, countInstructionInZ(inspector, InvokeVirtual.class));
+ assertEquals(3, countInstructionInZSubClass(inspector, InvokeVirtual.class));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 64ea9b4..5088aeb 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.code.ConstWideHigh16;
import com.android.tools.r8.code.DivInt;
import com.android.tools.r8.code.DivInt2Addr;
+import com.android.tools.r8.code.Goto;
import com.android.tools.r8.code.InvokeStatic;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.MoveResult;
@@ -1244,7 +1245,7 @@
assertTrue(code.instructions[1] instanceof InvokeStatic);
assertTrue(code.instructions[2] instanceof MoveResult);
assertTrue(code.instructions[3] instanceof DivInt2Addr);
- assertTrue(code.instructions[4] instanceof Return);
+ assertTrue(code.instructions[4] instanceof Goto);
assertTrue(code.instructions[5] instanceof Const4);
assertTrue(code.instructions[6] instanceof Return);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
diff --git a/src/test/kotlinR8TestResources/properties/LateInitProperty.kt b/src/test/kotlinR8TestResources/properties/LateInitProperty.kt
new file mode 100644
index 0000000..41c8ab2
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/LateInitProperty.kt
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, 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 properties
+
+open class LateInitProperty {
+ private lateinit var privateLateInitProp: String
+ protected lateinit var protectedLateInitProp: String
+ internal lateinit var internalLateInitProp: String
+ public lateinit var publicLateInitProp: String
+
+ fun callSetterPrivateLateInitProp(v: String) {
+ privateLateInitProp = v
+ }
+
+ fun callGetterPrivateLateInitProp(): String {
+ return privateLateInitProp
+ }
+}
+
+class SubLateInitProperty: LateInitProperty() {
+ fun callSetterProtectedLateInitProp(v: String) {
+ protectedLateInitProp = v
+ }
+
+ fun callGetterProtectedLateInitProp(): String {
+ return protectedLateInitProp
+ }
+}
+
+fun lateInitProperty_noUseOfProperties() {
+ LateInitProperty()
+ println("DONE")
+}
+
+fun lateInitProperty_usePrivateLateInitProp() {
+ val obj = LateInitProperty()
+ obj.callSetterPrivateLateInitProp("foo")
+ println(obj.callGetterPrivateLateInitProp())
+}
+
+fun lateInitProperty_useProtectedLateInitProp() {
+ val obj = SubLateInitProperty()
+ obj.callSetterProtectedLateInitProp("foo")
+ println(obj.callGetterProtectedLateInitProp())
+}
+
+fun lateInitProperty_useInternalLateInitProp() {
+ val obj = LateInitProperty()
+ obj.internalLateInitProp = "foo"
+ println(obj.internalLateInitProp)
+}
+
+fun lateInitProperty_usePublicLateInitProp() {
+ val obj = LateInitProperty()
+ obj.publicLateInitProp = "foo"
+ println(obj.publicLateInitProp)
+}
diff --git a/src/test/kotlinR8TestResources/properties/MutableProperty.kt b/src/test/kotlinR8TestResources/properties/MutableProperty.kt
new file mode 100644
index 0000000..ae945ff
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/MutableProperty.kt
@@ -0,0 +1,66 @@
+// Copyright (c) 2018, 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 properties
+
+open class MutableProperty {
+ private var privateProp: String = "privateProp"
+ protected var protectedProp: String = "protectedProp"
+ internal var internalProp: String = "internalProp"
+ public var publicProp: String = "publicProp"
+
+ public var primitiveProp: Int = Int.MAX_VALUE
+
+ fun callSetterPrivateProp(v: String) {
+ privateProp = v
+ }
+
+ fun callGetterPrivateProp(): String {
+ return privateProp
+ }
+}
+
+class SubMutableProperty : MutableProperty() {
+ fun callSetterProtectedProp(v: String) {
+ protectedProp = v
+ }
+
+ fun callGetterProtectedProp(): String {
+ return protectedProp
+ }
+}
+
+fun mutableProperty_noUseOfProperties() {
+ MutableProperty()
+ println("DONE")
+}
+
+fun mutableProperty_usePrivateProp() {
+ val obj = MutableProperty()
+ obj.callSetterPrivateProp("foo")
+ println(obj.callGetterPrivateProp())
+}
+
+fun mutableProperty_useProtectedProp() {
+ val obj = SubMutableProperty()
+ obj.callSetterProtectedProp("foo")
+ println(obj.callGetterProtectedProp())
+}
+
+fun mutableProperty_useInternalProp() {
+ val obj = MutableProperty()
+ obj.internalProp = "foo"
+ println(obj.internalProp)
+}
+
+fun mutableProperty_usePublicProp() {
+ val obj = MutableProperty()
+ obj.publicProp = "foo"
+ println(obj.publicProp)
+}
+
+fun mutableProperty_usePrimitiveProp() {
+ val obj = MutableProperty()
+ obj.primitiveProp = Int.MIN_VALUE
+ println(obj.primitiveProp)
+}
diff --git a/src/test/kotlinR8TestResources/properties/UserDefinedProperty.kt b/src/test/kotlinR8TestResources/properties/UserDefinedProperty.kt
new file mode 100644
index 0000000..5bc55e6
--- /dev/null
+++ b/src/test/kotlinR8TestResources/properties/UserDefinedProperty.kt
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, 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 properties
+
+class UserDefinedProperty() {
+ public var durationInMilliSeconds: Int = 0
+
+ var durationInSeconds: Int
+ get() = durationInMilliSeconds / 1000
+ set(v) { durationInMilliSeconds = v * 1000 }
+}
+
+fun userDefinedProperty_noUseOfProperties() {
+ UserDefinedProperty()
+}
+
+fun userDefinedProperty_useProperties() {
+ val obj = UserDefinedProperty()
+ obj.durationInSeconds = 5
+ println(obj.durationInSeconds)
+}
\ No newline at end of file
diff --git a/third_party/proguard/README.google b/third_party/proguard/README.google
index 4dc91ad..4f85601 100644
--- a/third_party/proguard/README.google
+++ b/third_party/proguard/README.google
@@ -1,7 +1,8 @@
URL: https://sourceforge.net/projects/proguard/files/proguard/5.2/
-Version: 5.2.1
+URL: https://sourceforge.net/projects/proguard/files/proguard/6.0/
+Version: 5.2.1, 6.0
License: GPL
-License File: docs/license.html
+License File: proguard5.2.1/docs/license.html, proguard6.0/docs/license.html
Description:
ProGuard Java Optimizer and Obfuscator
diff --git a/third_party/proguard/proguard6.0.tar.gz.sha1 b/third_party/proguard/proguard6.0.tar.gz.sha1
new file mode 100644
index 0000000..4596bc8
--- /dev/null
+++ b/third_party/proguard/proguard6.0.tar.gz.sha1
@@ -0,0 +1 @@
+57d0702f38196c81ff506d2e34a4a5569c3af583
\ No newline at end of file