Merge "Use android.jar corresponding to api 28 for AndroidApiLevel.P"
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 4ef2370..89542a7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -30,6 +30,7 @@
assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
value instanceof StackValues;
this.outValue = value;
+ this.outValue.definition = this;
for (StackValue val : ((StackValues)value).getStackValues()) {
val.definition = this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 6d61f58..fcb5b8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -44,6 +44,7 @@
assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
value instanceof StackValues;
this.outValue = value;
+ this.outValue.definition = this;
for (StackValue val : ((StackValues)value).getStackValues()) {
val.definition = this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 773a648..bef2765 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -20,6 +20,7 @@
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
@@ -34,6 +35,7 @@
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class IRCode {
@@ -575,7 +577,10 @@
}
private boolean verifyDefinition(Value value) {
- assert value.definition.outValue() == value;
+ Value outValue = value.definition.outValue();
+ assert outValue == value
+ || (value instanceof StackValue
+ && Arrays.asList(((StackValues) outValue).getStackValues()).contains(value));
return true;
}
@@ -662,14 +667,15 @@
}
// After the throwing instruction only debug instructions and the final jump
// instruction is allowed.
- // TODO(ager): For now allow const instructions due to the way consts are pushed
- // towards their use
+ // TODO(mkroghj) Temporarily allow stack-operations to be after throwing instructions.
if (seenThrowing) {
assert instruction.isDebugInstruction()
|| instruction.isJumpInstruction()
- || instruction.isConstInstruction()
|| instruction.isStore()
- || instruction.isPop();
+ || instruction.isPop()
+ || instruction.isDup()
+ || instruction.isDup2()
+ || instruction.isSwap();
}
}
}
@@ -678,7 +684,7 @@
}
public boolean verifyNoImpreciseOrBottomTypes() {
- return verifySSATypeLattice(
+ Predicate<Value> verifyValue =
v -> {
assert v.getTypeLattice().isPreciseType();
assert !v.getTypeLattice().isFineGrainedType();
@@ -688,6 +694,16 @@
assert !(v.definition instanceof ImpreciseMemberTypeInstruction)
|| ((ImpreciseMemberTypeInstruction) v.definition).getMemberType().isPrecise();
return true;
+ };
+ return verifySSATypeLattice(
+ v -> {
+ // StackValues is an artificial type created to allow returning multiple values from an
+ // instruction.
+ if (v instanceof StackValues) {
+ return Stream.of(((StackValues) v).getStackValues()).allMatch(verifyValue);
+ } else {
+ return verifyValue(v);
+ }
});
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c010973..e235682 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -64,6 +64,24 @@
}
@Override
+ public boolean instructionInstanceCanThrow() {
+ return !(size().definition != null
+ && size().definition.isConstNumber()
+ && size().definition.asConstNumber().getRawValue() >= 0
+ && size().definition.asConstNumber().getRawValue() < Integer.MAX_VALUE);
+ }
+
+ @Override
+ public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+ if (instructionInstanceCanThrow()) {
+ return false;
+ }
+ // This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo.
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ return baseType.isPrimitiveType() || appInfo.definitionFor(baseType) != null;
+ }
+
+ @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other.isNewArrayEmpty() && other.asNewArrayEmpty().type == type;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index ed51913..f534fb9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.code.FillArrayDataPayload;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -73,6 +74,19 @@
}
@Override
+ public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+ if (!src().getTypeLattice().isNullable() && src().numberOfAllUsers() == 1) {
+ // The NewArrayFilledData instruction is only inserted by an R8 optimization following
+ // a NewArrayEmpty when there are more than one entry.
+ assert src().uniqueUsers().iterator().next() == this;
+ assert src().definition != null;
+ assert src().definition.isNewArrayEmpty();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public boolean instructionTypeCanThrow() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 80edc80..3c623d6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -33,6 +33,7 @@
assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
value instanceof StackValues;
this.outValue = value;
+ this.outValue.definition = this;
for (StackValue val : ((StackValues)value).getStackValues()) {
val.definition = this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 4dcc839..6b5f6c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -166,6 +166,7 @@
reachedFixpoint = !phiOptimizations.optimize(code);
}
}
+ assert code.isConsistentSSA();
registerAllocator = new CfRegisterAllocator(code, options, typeVerificationHelper);
registerAllocator.allocateRegisters();
loadStoreHelper.insertPhiMoves(registerAllocator);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 7c22bbf..fd968d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -210,7 +210,15 @@
iterator.replaceCurrentInstruction(newInvoke);
if (constantReturnMaterializingInstruction != null) {
- iterator.add(constantReturnMaterializingInstruction);
+ if (block.hasCatchHandlers()) {
+ // Split the block to ensure no instructions after throwing instructions.
+ iterator
+ .split(code, blocks)
+ .listIterator()
+ .add(constantReturnMaterializingInstruction);
+ } else {
+ iterator.add(constantReturnMaterializingInstruction);
+ }
}
DexType actualReturnType = actualTarget.proto.returnType;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index be171c9..fa259c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -115,16 +115,15 @@
continue;
}
Value outValue = current.outValue();
- // Instructions with no out value cannot be dead code by the current definition
- // (unused out value). They typically side-effect input values or deals with control-flow.
- assert outValue != null;
- if (!outValue.isDead(appInfo)) {
+ if (outValue != null && !outValue.isDead(appInfo)) {
continue;
}
updateWorklist(worklist, current);
// All users will be removed for this instruction. Eagerly clear them so further inspection
// of this instruction during dead code elimination will terminate here.
- outValue.clearUsers();
+ if (outValue != null) {
+ outValue.clearUsers();
+ }
iterator.removeOrReplaceByDebugLocalRead();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 173de18..357e949 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -423,14 +423,13 @@
}
}
- // Allow the first method to consume more than the allowance to be inlined.
- return instructionAllowance < 0;
+ return instructionAllowance < Inliner.numberOfInstructions(inlinee.code);
}
@Override
public void markInlined(InlineeWithReason inlinee) {
// TODO(118734615): All inlining use from the budget - should that only be SIMPLE?
- instructionAllowance -= inliner.numberOfInstructions(inlinee.code);
+ instructionAllowance -= Inliner.numberOfInstructions(inlinee.code);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 2b0d831..27ea742 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -446,7 +446,7 @@
}
}
- final int numberOfInstructions(IRCode code) {
+ static final int numberOfInstructions(IRCode code) {
int numOfInstructions = 0;
for (BasicBlock block : code.blocks) {
numOfInstructions += block.getInstructions().size();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 89eef31..ad3fb38 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,17 +17,20 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardMemberRule;
import com.google.common.collect.Sets;
+import java.util.ListIterator;
import java.util.Set;
import java.util.function.Predicate;
@@ -113,7 +116,8 @@
private boolean tryConstantReplacementFromProguard(
IRCode code,
Set<Value> affectedValues,
- InstructionIterator iterator,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
Instruction current,
ProguardMemberRuleLookup lookup) {
Instruction replacement = constantReplacementFromProguardRule(lookup.rule, code, current);
@@ -132,7 +136,11 @@
current.outValue().replaceUsers(replacement.outValue());
}
replacement.setPosition(current.getPosition());
- iterator.add(replacement);
+ if (current.getBlock().hasCatchHandlers()) {
+ iterator.split(code, blocks).listIterator().add(replacement);
+ } else {
+ iterator.add(replacement);
+ }
}
return true;
}
@@ -141,7 +149,8 @@
IRCode code,
DexType callingContext,
Set<Value> affectedValues,
- InstructionIterator iterator,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
InvokeMethod current) {
DexMethod invokedMethod = current.getInvokedMethod();
DexType invokedHolder = invokedMethod.getHolder();
@@ -161,7 +170,8 @@
} else if (!outValueNullOrNotUsed) {
// Check to see if a constant value can be assumed.
invokeReplaced =
- tryConstantReplacementFromProguard(code, affectedValues, iterator, current, lookup);
+ tryConstantReplacementFromProguard(
+ code, affectedValues, blocks, iterator, current, lookup);
}
}
if (invokeReplaced || current.outValue() == null) {
@@ -190,7 +200,11 @@
current.setOutValue(null);
replacement.setPosition(current.getPosition());
current.moveDebugValues(replacement);
- iterator.add(replacement);
+ if (current.getBlock().hasCatchHandlers()) {
+ iterator.split(code, blocks).listIterator().add(replacement);
+ } else {
+ iterator.add(replacement);
+ }
}
}
@@ -198,7 +212,8 @@
IRCode code,
Predicate<DexEncodedMethod> isProcessedConcurrently,
Set<Value> affectedValues,
- InstructionIterator iterator,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
StaticGet current) {
DexField field = current.getField();
DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
@@ -215,7 +230,8 @@
ProguardMemberRuleLookup lookup = lookupMemberRule(target);
if (lookup != null
&& lookup.type == RuleType.ASSUME_VALUES
- && tryConstantReplacementFromProguard(code, affectedValues, iterator, current, lookup)) {
+ && tryConstantReplacementFromProguard(
+ code, affectedValues, blocks, iterator, current, lookup)) {
return;
}
if (current.dest() != null) {
@@ -275,17 +291,26 @@
public void rewriteWithConstantValues(
IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
- InstructionIterator iterator = code.instructionIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isInvokeMethod()) {
- rewriteInvokeMethodWithConstantValues(
- code, callingContext, affectedValues, iterator, current.asInvokeMethod());
- } else if (current.isInstancePut() || current.isStaticPut()) {
- rewritePutWithConstantValues(iterator, current.asFieldInstruction());
- } else if (current.isStaticGet()) {
- rewriteStaticGetWithConstantValues(
- code, isProcessedConcurrently, affectedValues, iterator, current.asStaticGet());
+ ListIterator<BasicBlock> blocks = code.blocks.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isInvokeMethod()) {
+ rewriteInvokeMethodWithConstantValues(
+ code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
+ } else if (current.isInstancePut() || current.isStaticPut()) {
+ rewritePutWithConstantValues(iterator, current.asFieldInstruction());
+ } else if (current.isStaticGet()) {
+ rewriteStaticGetWithConstantValues(
+ code,
+ isProcessedConcurrently,
+ affectedValues,
+ blocks,
+ iterator,
+ current.asStaticGet());
+ }
}
}
if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index 0572e88..abd22bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -211,12 +211,16 @@
return "invoked from lambda created in";
case ReferencedFrom:
return "referenced from";
+ case ReflectiveUseFrom:
+ return "reflected from";
case ReachableFromLiveType:
return "reachable from";
case ReferencedInAnnotation:
return "referenced in annotation";
case IsLibraryMethod:
return "defined in library";
+ case MethodHandleUseFrom:
+ return "referenced by method handle";
default:
throw new Unreachable("Unexpected edge kind: " + info.edgeKind());
}
diff --git a/src/test/examples/classmerging/ArrayTypeCollisionTest.java b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
index 9e4c9fd..c422946 100644
--- a/src/test/examples/classmerging/ArrayTypeCollisionTest.java
+++ b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
@@ -12,11 +12,11 @@
}
private static void method(A[] obj) {
- System.out.println("In method(A[])");
+ System.out.println("In method(A[]), length: " + obj.length);
}
private static void method(B[] obj) {
- System.out.println("In method(B[])");
+ System.out.println("In method(B[]), length: " + obj.length);
}
// A cannot be merged into B because that would lead to a collision.
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index bfffcc9..a35ce71 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1740,6 +1740,7 @@
"lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
.put("lang.Runtime.gc.Runtime_gc_A01", cf())
+ .put("lang.Runtime.runFinalizersOnExitZ.Runtime_runFinalizersOnExit_A01", cf())
.put(
"util.concurrent.AbstractExecutorService.invokeAllLjava_util_CollectionJLjava_util_concurrent_TimeUnit.AbstractExecutorService_invokeAll_A06",
match(runtimes(Runtime.ART_V4_0_4)))
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index bb5677f..daf5568 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -182,7 +182,11 @@
}
public R8TestBuilder enableGraphInspector() {
- CollectingGraphConsumer consumer = new CollectingGraphConsumer(null);
+ return enableGraphInspector(null);
+ }
+
+ public R8TestBuilder enableGraphInspector(GraphConsumer subConsumer) {
+ CollectingGraphConsumer consumer = new CollectingGraphConsumer(subConsumer);
setKeptGraphConsumer(consumer);
graphConsumer = consumer;
return self();
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index c4151d1..688c91c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -206,7 +206,8 @@
" public static void main(...);",
"}",
"-neverinline class " + main + " {",
- " static void method(...);",
+ " static classmerging.A[] method(...);",
+ " static classmerging.B[] method(...);",
"}"));
}
diff --git a/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
new file mode 100644
index 0000000..5de7de0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.deadcode;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Aput;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RemoveDeadArray extends TestBase {
+
+ public static class TestClassWithCatch {
+ static {
+ try {
+ int[] foobar = new int[]{42, 42, 42, 42};
+ } catch (Exception ex) {
+ System.out.println("foobar");
+ }
+ }
+ }
+
+ public static class TestClass {
+ private static int[] foobar = new int[]{42, 42, 42, 42};
+ public static void main(java.lang.String[] args) {
+ ShouldGoAway[] willBeRemoved = new ShouldGoAway[4];
+ ShouldGoAway[] willAlsoBeRemoved = new ShouldGoAway[0];
+ System.out.println("foobar");
+ }
+
+ public static class ShouldGoAway { }
+ }
+
+ private Backend backend;
+
+ public RemoveDeadArray(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ // Todo(ricow): enable unused array removal for cf backend.
+ return new Backend[]{Backend.DEX};
+ }
+
+
+ @Test
+ public void testDeadArraysRemoved() throws Exception {
+ R8TestCompileResult result =
+ testForR8(backend)
+ .addProgramClasses(TestClass.class, TestClass.ShouldGoAway.class)
+ .addKeepMainRule(TestClass.class)
+ .compile();
+ CodeInspector inspector = result.inspector();
+ assertFalse(inspector.clazz(TestClass.class).clinit().isPresent());
+
+ MethodSubject main = inspector.clazz(TestClass.class).mainMethod();
+ main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray());
+ assertFalse(main.getMethod().getCode().asDexCode().toString().contains("NewArray"));
+ runOnArt(result.app, TestClass.class.getName());
+ }
+
+ @Test
+ public void testNotRemoveStaticCatch() throws Exception {
+ R8TestCompileResult result =
+ testForR8(backend)
+ .addProgramClasses(TestClassWithCatch.class)
+ .addKeepAllClassesRule()
+ .compile();
+ CodeInspector inspector = result.inspector();
+ MethodSubject clinit = inspector.clazz(TestClassWithCatch.class).clinit();
+ assertTrue(clinit.isPresent());
+ // Ensure that our optimization does not hit, we should still have 4 Aput instructions.
+ long aPutCount = Arrays.stream(clinit.getMethod().getCode().asDexCode().instructions)
+ .filter(instruction -> instruction instanceof Aput)
+ .count();
+ assertEquals(4, aPutCount);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
new file mode 100644
index 0000000..e8eb416
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByFieldReflectionTest {
+
+ public final int foo = 42;
+
+ public static void main(String[] args) throws Exception {
+ // Due to b/123210548 the object cannot be created by a reflective newInstance call.
+ KeptByFieldReflectionTest obj = new KeptByFieldReflectionTest();
+ System.out.println("got foo: " + KeptByFieldReflectionTest.class.getField("foo").getInt(obj));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
new file mode 100644
index 0000000..6feaa35
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.fieldFromField;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByFieldReflectionTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
+ private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+ private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
+
+ private final String EXPECTED_WHYAREYOUKEEPING =
+ StringUtils.lines(
+ "int com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.foo",
+ "|- is reflected from:",
+ "| void com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.main(java.lang.String[])",
+ "|- is referenced in keep rule:",
+ "| -keep class com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest { public static void main(java.lang.String[]); }");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptByFieldReflectionTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ @Ignore("b/123215165")
+ public void test() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
+
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+ }
+
+ WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector(consumer)
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED_STDOUT)
+ .graphInspector();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ consumer.printWhyAreYouKeeping(fooField, new PrintStream(baos));
+ assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+ inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
new file mode 100644
index 0000000..6394237
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByMethodReflectionTest {
+
+ public void foo() {
+ System.out.println("called foo");
+ }
+
+ public static void main(String[] args) throws Exception {
+ // Due to b/123210548 the object cannot be created by a reflective newInstance call.
+ KeptByMethodReflectionTest obj = new KeptByMethodReflectionTest();
+ KeptByMethodReflectionTest.class.getMethod("foo").invoke(obj);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
new file mode 100644
index 0000000..3224689
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByMethodReflectionTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = KeptByMethodReflectionTest.class;
+ private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+ private final String EXPECTED_STDOUT = StringUtils.lines("called foo");
+
+ private final String EXPECTED_WHYAREYOUKEEPING =
+ StringUtils.lines(
+ "void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.foo()",
+ "|- is reflected from:",
+ "| void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.main(java.lang.String[])",
+ "|- is referenced in keep rule:",
+ "| -keep class com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest { public static void main(java.lang.String[]); }");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptByMethodReflectionTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ MethodReference fooMethod = methodFromMethod(CLASS.getDeclaredMethod("foo"));
+
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+ }
+
+ WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector(consumer)
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED_STDOUT)
+ .graphInspector();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ consumer.printWhyAreYouKeeping(fooMethod, new PrintStream(baos));
+ assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+ inspector.method(fooMethod).assertRenamed().assertReflectedFrom(mainMethod);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index ee2155e..375f0b5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfMonitor;
import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
import com.android.tools.r8.cf.code.CfNop;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.cf.code.CfReturn;
@@ -280,6 +281,11 @@
}
@Override
+ public boolean isNewArray() {
+ return instruction instanceof CfNewArray;
+ }
+
+ @Override
public int size() {
// TODO(b/122302789): CfInstruction#getSize()
throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 7051226..e2e801e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -67,6 +67,7 @@
import com.android.tools.r8.code.MulIntLit8;
import com.android.tools.r8.code.MulLong;
import com.android.tools.r8.code.MulLong2Addr;
+import com.android.tools.r8.code.NewArray;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.Nop;
import com.android.tools.r8.code.PackedSwitch;
@@ -360,6 +361,11 @@
}
@Override
+ public boolean isNewArray() {
+ return instruction instanceof NewArray;
+ }
+
+ @Override
public boolean isMonitorEnter() {
return instruction instanceof MonitorEnter;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 294cc8c..dd5af7c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -82,6 +82,8 @@
boolean isMultiplication();
+ boolean isNewArray();
+
boolean isMonitorEnter();
boolean isMonitorExit();
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 940482b..1d53013 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -41,6 +41,8 @@
public static class EdgeKindPredicate implements Predicate<Set<GraphEdgeInfo>> {
public static final EdgeKindPredicate keepRule = new EdgeKindPredicate(EdgeKind.KeepRule);
public static final EdgeKindPredicate invokedFrom = new EdgeKindPredicate(EdgeKind.InvokedFrom);
+ public static final EdgeKindPredicate reflectedFrom =
+ new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
private final EdgeKind edgeKind;
@@ -69,6 +71,8 @@
abstract boolean isInvokedFrom(MethodReference method);
+ abstract boolean isReflectedFrom(MethodReference method);
+
abstract boolean isKeptBy(QueryNode node);
abstract String getNodeDescription();
@@ -119,6 +123,19 @@
return this;
}
+ public QueryNode assertReflectedFrom(MethodReference method) {
+ assertTrue(
+ errorMessage("reflection from " + method.toString(), "none"), isReflectedFrom(method));
+ return this;
+ }
+
+ public QueryNode assertNotReflectedFrom(MethodReference method) {
+ assertFalse(
+ errorMessage("no reflection from " + method.toString(), "reflection"),
+ isReflectedFrom(method));
+ return this;
+ }
+
public QueryNode assertKeptBy(QueryNode node) {
assertTrue(
"Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
@@ -174,6 +191,12 @@
}
@Override
+ public boolean isReflectedFrom(MethodReference method) {
+ fail("Invalid call to isReflectedFrom on " + getNodeDescription());
+ throw new Unreachable();
+ }
+
+ @Override
public boolean isKeptBy(QueryNode node) {
fail("Invalid call to isKeptBy on " + getNodeDescription());
throw new Unreachable();
@@ -238,6 +261,18 @@
}
@Override
+ public boolean isReflectedFrom(MethodReference method) {
+ GraphNode sourceMethod = inspector.methods.get(method);
+ if (sourceMethod == null) {
+ return false;
+ }
+ return filterSources(
+ (node, infos) -> node == sourceMethod && EdgeKindPredicate.reflectedFrom.test(infos))
+ .findFirst()
+ .isPresent();
+ }
+
+ @Override
public boolean isKeptBy(QueryNode node) {
if (!(node instanceof QueryNodeImpl)) {
return false;
@@ -356,6 +391,10 @@
return getQueryNode(methods.get(method), method.toString());
}
+ public QueryNode field(FieldReference field) {
+ return getQueryNode(fields.get(field), field.toString());
+ }
+
private QueryNode getQueryNode(GraphNode node, String absentString) {
return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
}
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 93c910e..b4eb396 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -66,6 +66,44 @@
if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
f.write(line)
+def GetMinAndCompileSdk(app, config, checkout_dir, apk_reference):
+ app_module = config.get('app_module', 'app')
+ build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle')
+ assert os.path.isfile(build_gradle_file), (
+ 'Expected to find build.gradle file at {}'.format(build_gradle_file))
+
+ compile_sdk = None
+ min_sdks = []
+ target_sdk = None
+
+ with open(build_gradle_file) as f:
+ for line in f.readlines():
+ stripped = line.strip()
+ if stripped.startswith('compileSdkVersion '):
+ assert not compile_sdk
+ compile_sdk = int(stripped[len('compileSdkVersion '):])
+ if stripped.startswith('minSdkVersion '):
+ min_sdks.append(int(stripped[len('minSdkVersion '):]))
+ elif stripped.startswith('targetSdkVersion '):
+ assert not target_sdk
+ target_sdk = int(stripped[len('targetSdkVersion '):])
+
+ if len(min_sdks) == 1:
+ min_sdk = min_sdks[0]
+ else:
+ assert 'min_sdk' in config
+ min_sdk = config.get('min_sdk')
+
+ assert min_sdk, (
+ 'Expected to find `minSdkVersion` in {}'.format(build_gradle_file))
+ assert compile_sdk, (
+ 'Expected to find `compileSdkVersion` in {}'.format(build_gradle_file))
+
+ assert not target_sdk or target_sdk == compile_sdk, (
+ 'Expected `compileSdkVersion` and `targetSdkVersion` to be the same')
+
+ return (min_sdk, compile_sdk)
+
def IsGradleTaskName(x):
# Check that it is non-empty.
if not x:
diff --git a/tools/compare_apk_sizes.py b/tools/compare_apk_sizes.py
index a8137c2..5e57ad5 100755
--- a/tools/compare_apk_sizes.py
+++ b/tools/compare_apk_sizes.py
@@ -11,6 +11,8 @@
import os
import shutil
import sys
+import threading
+import time
import toolhelper
import utils
import zipfile
@@ -19,6 +21,8 @@
USAGE = """%prog [options] app1 app2
NOTE: This only makes sense if minification is disabled"""
+MAX_THREADS=40
+
def parse_options():
result = optparse.OptionParser(usage=USAGE)
result.add_option('--temp',
@@ -68,9 +72,14 @@
return 0
class FileInfo:
- def __init__(self, path, root, use_code_size):
+ def __init__(self, path, root):
self.path = path
self.full_path = os.path.join(root, path)
+
+ def __eq__(self, other):
+ return self.full_path == other.full_path
+
+ def set_size(self, use_code_size):
if use_code_size:
self.size = get_code_size(self.full_path)
else:
@@ -83,8 +92,27 @@
for f in files:
assert f.endswith('dex')
file_path = os.path.join(root, f)
- entry = FileInfo(file_path, path, use_code_size=options.use_code_size)
+ entry = FileInfo(file_path, path)
+ if not options.use_code_size:
+ entry.set_size()
file_info_map[file_path] = entry
+ threads = []
+ file_infos = file_info_map.values() if options.use_code_size else []
+ while len(file_infos) > 0 or len(threads)> 0:
+ for t in threads:
+ if not t.is_alive():
+ threads.remove(t)
+ # sleep
+ if len(threads) == MAX_THREADS or len(file_infos) == 0:
+ time.sleep(0.5)
+ while len(threads) < MAX_THREADS and len(file_infos) > 0:
+ info = file_infos.pop()
+ print('Added %s for size calculation' % info.full_path)
+ t = threading.Thread(target=info.set_size, args=(options.use_code_size,))
+ threads.append(t)
+ t.start()
+ print('Missing %s files, threads=%s ' % (len(file_infos), len(threads)))
+
return file_info_map
def print_info(app, app_files, only_in_app, bigger_in_app, output):
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index a74f868..e9b2dcd 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -78,6 +78,7 @@
'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
'flavor': 'standard',
'releaseTarget': 'app:assembleRelease',
+ 'min_sdk': 16
},
'tivi': {
'app_id': 'app.tivi',
@@ -292,6 +293,10 @@
with open(proguard_config_file) as old:
assert(sum(1 for line in new) > sum(1 for line in old))
+ # Extract min-sdk and target-sdk
+ (min_sdk, compile_sdk) = as_utils.GetMinAndCompileSdk(app, config,
+ checkout_dir, apk_dest)
+
# Now rebuild generated apk.
previous_apk = apk_dest
for i in range(1, options.r8_compilation_steps):
@@ -300,7 +305,7 @@
checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
RebuildAppWithShrinker(
previous_apk, recompiled_apk_dest, ext_proguard_config_file,
- shrinker)
+ shrinker, min_sdk, compile_sdk)
recompilation_result = {
'apk_dest': recompiled_apk_dest,
'build_status': 'success',
@@ -422,18 +427,24 @@
return (apk_dest, profile_dest_dir, proguard_config_dest)
-def RebuildAppWithShrinker(apk, apk_dest, proguard_config_file, shrinker):
+def RebuildAppWithShrinker(
+ apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk):
assert 'r8' in shrinker
assert apk_dest.endswith('.apk')
# Compile given APK with shrinker to temporary zip file.
- api = 28 # TODO(christofferqa): Should be the one from build.gradle
- android_jar = os.path.join(utils.REPO_ROOT, utils.ANDROID_JAR.format(api=api))
+ android_jar = os.path.join(
+ utils.REPO_ROOT,
+ utils.ANDROID_JAR.format(api=compile_sdk))
r8_jar = utils.R8LIB_JAR if IsMinifiedR8(shrinker) else utils.R8_JAR
zip_dest = apk_dest[:-4] + '.zip'
- cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar,
- 'com.android.tools.r8.R8', '--release', '--pg-conf', proguard_config_file,
+ # TODO(christofferqa): Entry point should be CompatProguard if the shrinker
+ # is 'r8'.
+ entry_point = 'com.android.tools.r8.R8'
+
+ cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar, entry_point,
+ '--release', '--min-api', str(min_sdk), '--pg-conf', proguard_config_file,
'--lib', android_jar, '--output', zip_dest, apk]
utils.PrintCmd(cmd)