Merge "Rewrite instance.getClass() to const-class if possible."
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 3c2faea..9c0cd21 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.6-dev";
+ public static final String LABEL = "1.4.9-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 1132193..ce98387 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -402,10 +402,12 @@
public boolean isPreciseType() {
return isArrayType()
|| isClassType()
+ || isNull()
|| isInt()
|| isFloat()
|| isLong()
- || isDouble();
+ || isDouble()
+ || isBottom();
}
public boolean isNull() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 89a5f78..5d7b1f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -676,8 +676,10 @@
}
private void removeCatchHandlerWithGuard(DexType guard) {
- int successorIndex = catchHandlers.getGuards().indexOf(guard);
- if (successorIndex >= 0) {
+ int guardIndex = catchHandlers.getGuards().indexOf(guard);
+ if (guardIndex >= 0) {
+ int successorIndex = catchHandlers.getAllTargets().get(guardIndex);
+ assert successorIndex >= 0;
catchHandlers = catchHandlers.removeGuard(guard);
if (getCatchHandlers().getAllTargets().stream()
.noneMatch(target -> target == successors.get(successorIndex))) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index a85e826..3f6a2ef 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -14,6 +14,17 @@
public class CatchHandlers<T> {
+ public static class CatchHandler<T> {
+
+ public final DexType guard;
+ public final T target;
+
+ public CatchHandler(DexType guard, T target) {
+ this.guard = guard;
+ this.target = target;
+ }
+ }
+
private final List<DexType> guards;
private final List<T> targets;
private Set<T> uniqueTargets;
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 75a95cd..c68baf9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -124,10 +124,7 @@
TypeLatticeElement inType = object().getTypeLattice();
- // TODO(b/72693244): There should never be a value with imprecise type lattice.
- if (!inType.isPreciseType()) {
- return true;
- }
+ assert inType.isPreciseType();
TypeLatticeElement outType = outValue().getTypeLattice();
TypeLatticeElement castType =
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 95b3f45..1ea6ab0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
import java.util.List;
@@ -38,4 +41,11 @@
public boolean throwsNpeIfValueIsNull(Value value) {
return getReceiver() == value;
}
+
+ @Override
+ public boolean verifyTypes(AppInfo appInfo, GraphLense graphLense) {
+ TypeLatticeElement receiverType = getReceiver().getTypeLattice();
+ assert receiverType.isPreciseType();
+ return true;
+ }
}
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 de05fcc..1ee4dbd 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
@@ -44,7 +44,6 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.optimize.CodeRewriter;
-import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
import com.android.tools.r8.ir.optimize.peepholes.BasicBlockMuncher;
import com.android.tools.r8.utils.InternalOptions;
@@ -145,7 +144,7 @@
typeVerificationHelper = new TypeVerificationHelper(code, factory, appInfo);
typeVerificationHelper.computeVerificationTypes();
splitExceptionalBlocks();
- new DeadCodeRemover(appInfo, code, rewriter, graphLense, options).run();
+ rewriter.converter.deadCodeRemover.run(code);
rewriteNots();
LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, typeVerificationHelper, appInfo);
loadStoreHelper.insertLoadsAndStores();
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 84fb0bd..a41ef09 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
@@ -122,6 +122,8 @@
private final StringOptimizer stringOptimizer;
private final UninstantiatedTypeOptimization uninstantiatedTypeOptimization;
+ final DeadCodeRemover deadCodeRemover;
+
public final boolean enableWholeProgramOptimizations;
private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
@@ -204,6 +206,9 @@
: null;
this.classStaticizer = options.enableClassStaticizer && appInfo.hasLiveness()
? new ClassStaticizer(appInfo.withLiveness(), this) : null;
+ this.deadCodeRemover =
+ new DeadCodeRemover(
+ appInfo, codeRewriter, graphLense(), options, enableWholeProgramOptimizations);
}
public GraphLense graphLense() {
@@ -577,7 +582,7 @@
// StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
// unused out-values.
codeRewriter.rewriteMoveResult(code);
- new DeadCodeRemover(appInfo, code, codeRewriter, graphLense(), options).run();
+ deadCodeRemover.run(code);
consumer.accept(code, method);
return null;
}));
@@ -846,6 +851,7 @@
stringOptimizer.removeValueOfIfTrivial(code, appInfo);
}
if (devirtualizer != null) {
+ assert code.verifyTypes(appInfo, graphLense());
devirtualizer.devirtualizeInvokeInterface(code, method.method.getHolder());
}
if (uninstantiatedTypeOptimization != null) {
@@ -853,9 +859,9 @@
}
assert code.verifyTypes(appInfo, graphLense());
-
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
code, enableWholeProgramOptimizations);
+
codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
codeRewriter.commonSubexpressionElimination(code);
codeRewriter.simplifyArrayConstruction(code);
@@ -885,7 +891,7 @@
// Dead code removal. Performed after simplifications to remove code that becomes dead
// as a result of those simplifications. The following optimizations could reveal more
// dead code which is removed right before register allocation in performRegisterAllocation.
- new DeadCodeRemover(appInfo, code, codeRewriter, graphLense(), options).run();
+ deadCodeRemover.run(code);
assert code.isConsistentSSA();
if (options.enableDesugaring && enableTryWithResourcesDesugaring()) {
@@ -1108,7 +1114,7 @@
private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
// Always perform dead code elimination before register allocation. The register allocator
// does not allow dead code (to make sure that we do not waste registers for unneeded values).
- new DeadCodeRemover(appInfo, code, codeRewriter, graphLense(), options).run();
+ deadCodeRemover.run(code);
materializeInstructionBeforeLongOperationsWorkaround(code);
workaroundForwardingInitializerBug(code);
LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5a7e6a5..df3f4ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1808,11 +1808,6 @@
}
TypeLatticeElement inTypeLattice = inValue.getTypeLattice();
- // TODO(b/72693244): Soon, there won't be a value with imprecise type at this point.
- if (!inTypeLattice.isPreciseType() && !inTypeLattice.isNull()) {
- return false;
- }
-
TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
TypeLatticeElement castTypeLattice =
TypeLatticeElement.fromDexType(castType, inTypeLattice.isNullable(), appInfo);
@@ -1866,11 +1861,6 @@
Value inValue = instanceOf.value();
TypeLatticeElement inType = inValue.getTypeLattice();
- // TODO(b/72693244): Soon, there won't be a value with imprecise type at this point.
- if (!inType.isPreciseType() && !inType.isNull()) {
- return false;
- }
-
TypeLatticeElement instanceOfType =
TypeLatticeElement.fromDexType(instanceOf.type(), inType.isNullable(), appInfo);
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 7e76dc0..91ac562 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
@@ -5,10 +5,12 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -16,6 +18,8 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
@@ -23,26 +27,26 @@
public class DeadCodeRemover {
private final AppInfo appInfo;
- private final IRCode code;
private final CodeRewriter codeRewriter;
private final GraphLense graphLense;
private final InternalOptions options;
+ private final boolean enableWholeProgramOptimizations;
public DeadCodeRemover(
AppInfo appInfo,
- IRCode code,
CodeRewriter codeRewriter,
GraphLense graphLense,
- InternalOptions options) {
+ InternalOptions options,
+ boolean enableWholeProgramOptimizations) {
this.appInfo = appInfo;
- this.code = code;
this.codeRewriter = codeRewriter;
this.graphLense = graphLense;
this.options = options;
+ this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
}
- public void run() {
- removeUnneededCatchHandlers();
+ public void run(IRCode code) {
+ removeUnneededCatchHandlers(code);
Queue<BasicBlock> worklist = new LinkedList<>();
worklist.addAll(code.blocks);
for (BasicBlock block = worklist.poll(); block != null; block = worklist.poll()) {
@@ -51,7 +55,7 @@
}
// We may encounter unneeded catch handlers again, e.g., if a dead instruction (due to
// const-string canonicalization for example) is the only throwing instruction in a block.
- removeUnneededCatchHandlers();
+ removeUnneededCatchHandlers(code);
assert code.isConsistentSSA();
codeRewriter.rewriteMoveResult(code);
}
@@ -70,8 +74,7 @@
}
// Add all blocks from where the in/debug-values to the instruction originates.
- private static void updateWorklist(
- Queue<BasicBlock> worklist, Instruction instruction) {
+ private static void updateWorklist(Queue<BasicBlock> worklist, Instruction instruction) {
for (Value inValue : instruction.inValues()) {
updateWorklist(worklist, inValue);
}
@@ -80,8 +83,8 @@
}
}
- private static void removeDeadPhis(Queue<BasicBlock> worklist, BasicBlock block,
- InternalOptions options) {
+ private static void removeDeadPhis(
+ Queue<BasicBlock> worklist, BasicBlock block, InternalOptions options) {
Iterator<Phi> phiIt = block.getPhis().iterator();
while (phiIt.hasNext()) {
Phi phi = phiIt.next();
@@ -124,24 +127,18 @@
}
}
- private void removeUnneededCatchHandlers() {
+ private void removeUnneededCatchHandlers(IRCode code) {
boolean mayHaveIntroducedUnreachableBlocks = false;
for (BasicBlock block : code.blocks) {
if (block.hasCatchHandlers()) {
if (block.canThrow()) {
- if (appInfo.hasLiveness()) {
- AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
- CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
- for (int i = 0; i < catchHandlers.size(); ++i) {
- DexType guard = catchHandlers.getGuards().get(i);
- BasicBlock target = catchHandlers.getAllTargets().get(i);
- DexClass clazz = appInfo.definitionFor(guard);
- if (clazz != null
- && clazz.isProgramClass()
- && !appInfoWithLiveness.isInstantiatedDirectlyOrIndirectly(guard)) {
- target.unlinkCatchHandlerForGuard(guard);
- mayHaveIntroducedUnreachableBlocks = true;
+ if (enableWholeProgramOptimizations) {
+ Collection<CatchHandler<BasicBlock>> deadCatchHandlers = getDeadCatchHandlers(block);
+ if (!deadCatchHandlers.isEmpty()) {
+ for (CatchHandler<BasicBlock> catchHandler : deadCatchHandlers) {
+ catchHandler.target.unlinkCatchHandlerForGuard(catchHandler.guard);
}
+ mayHaveIntroducedUnreachableBlocks = true;
}
}
} else {
@@ -158,4 +155,48 @@
}
assert code.isConsistentGraph();
}
+
+ /**
+ * Returns the catch handlers of the given block that are dead, if any.
+ */
+ private Collection<CatchHandler<BasicBlock>> getDeadCatchHandlers(BasicBlock block) {
+ AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
+ ImmutableList.Builder<CatchHandler<BasicBlock>> builder = ImmutableList.builder();
+ CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
+ for (int i = 0; i < catchHandlers.size(); ++i) {
+ DexType guard = catchHandlers.getGuards().get(i);
+ BasicBlock target = catchHandlers.getAllTargets().get(i);
+ if (guard == DexItemFactory.catchAllType) {
+ continue;
+ }
+
+ // We can exploit subtyping information to eliminate a catch handler if the guard is
+ // subsumed by a previous guard.
+ boolean isSubsumedByPreviousGuard = false;
+ for (int j = 0; j < i; ++j) {
+ DexType previousGuard = catchHandlers.getGuards().get(j);
+ if (guard.isSubtypeOf(previousGuard, appInfo)) {
+ isSubsumedByPreviousGuard = true;
+ break;
+ }
+ }
+ if (isSubsumedByPreviousGuard) {
+ builder.add(new CatchHandler<>(guard, target));
+ continue;
+ }
+
+ // We can exploit that a catch handler must be dead if its guard is never instantiated
+ // directly or indirectly.
+ if (appInfoWithLiveness != null && options.enableUninstantiatedTypeOptimization) {
+ DexClass clazz = appInfo.definitionFor(guard);
+ if (clazz != null
+ && clazz.isProgramClass()
+ && !appInfoWithLiveness.isInstantiatedDirectlyOrIndirectly(guard)) {
+ builder.add(new CatchHandler<>(guard, target));
+ continue;
+ }
+ }
+ }
+ return builder.build();
+ }
}
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
index 2f27e83..ba5f1b3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -116,9 +116,7 @@
// 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.
- // TODO(b/72693244): Soon, there won't be a value with imprecise type at this point.
- if (!receiverTypeLattice.isPreciseType()
- || !receiverTypeLattice.lessThanOrEqual(castTypeLattice, appInfo)) {
+ if (!receiverTypeLattice.lessThanOrEqual(castTypeLattice, appInfo)) {
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.
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 19eb127..e96497b 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -127,7 +127,7 @@
// Members mentioned at -keepclassmembers always depend on their holder.
preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
- markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier);
break;
}
case KEEP_CLASSES_WITH_MEMBERS: {
@@ -150,7 +150,7 @@
preconditionSupplier.put((definition -> true), null);
}
markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
- markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier);
break;
}
case CONDITIONAL:
@@ -170,10 +170,10 @@
|| rule instanceof ProguardKeepPackageNamesRule) {
markClass(clazz, rule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
- markMatchingFields(clazz, memberKeepRules, rule, null);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
- markMatchingFields(clazz, memberKeepRules, rule, null);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ClassMergingRule) {
if (allRulesSatisfied(memberKeepRules, clazz)) {
markClass(clazz, rule);
@@ -182,7 +182,7 @@
markMatchingMethods(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeValuesRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
- markMatchingFields(clazz, memberKeepRules, rule, null);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
} else {
assert rule instanceof ProguardIdentifierNameStringRule;
markMatchingFields(clazz, memberKeepRules, rule, null);
@@ -460,6 +460,21 @@
});
}
+ private void markMatchingVisibleFields(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+ while (clazz != null) {
+ clazz.forEachField(
+ field -> {
+ DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+ markField(field, memberKeepRules, rule, precondition);
+ });
+ clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
+ }
+ }
+
private void markMatchingFields(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/CatchHandlerRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/CatchHandlerRemovalTest.java
new file mode 100644
index 0000000..5e5a490
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/CatchHandlerRemovalTest.java
@@ -0,0 +1,72 @@
+// 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.TestBase;
+import inlining.NeverInline;
+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 CatchHandlerRemovalTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public CatchHandlerRemovalTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expected = "In TestClass.method()";
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
+
+ testForR8(backend)
+ .addInnerClasses(CatchHandlerRemovalTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expected)
+ .inspector();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // Instantiate ExceptionA and ExceptionB such that the uninstantiated type optimization
+ // cannot unlink the catch handlers guarded by these types.
+ new ExceptionA();
+ new ExceptionB();
+
+ // Since ExceptionC is never instantiated, the catch handler for it will be removed by the
+ // uninstantiated type optimization.
+ try {
+ method();
+ } catch (ExceptionA | ExceptionB | ExceptionC e) {
+ System.out.println("Caught exception");
+ }
+ }
+
+ @NeverInline
+ public static void method() {
+ System.out.print("In TestClass.method()");
+ }
+ }
+
+ static class ExceptionA extends RuntimeException {}
+
+ static class ExceptionB extends RuntimeException {}
+
+ static class ExceptionC extends RuntimeException {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SubsumedCatchHandlerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SubsumedCatchHandlerTest.java
new file mode 100644
index 0000000..e31325b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SubsumedCatchHandlerTest.java
@@ -0,0 +1,120 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SubsumedCatchHandlerTest extends TestBase {
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ int exitCode = 0;
+ try {
+ exitCode = foo();
+ } catch (RuntimeException e) {
+ // This catch handler will be subsumed by the catch handler in foo() after inlining.
+ handleCaughtRuntimeException();
+ }
+ System.out.print(" -> " + exitCode);
+ }
+
+ @ForceInline
+ private static int foo() {
+ try {
+ bar();
+ } catch (Exception e) {
+ // No statements with side-effects here; otherwise this block will be guarded by the
+ // RuntimeException catch handler from main().
+ return 1;
+ }
+ return 0;
+ }
+
+ @NeverInline
+ private static void bar() {
+ System.out.print("In bar()");
+ }
+
+ @NeverInline
+ private static void handleCaughtRuntimeException() {
+ System.out.print("In handleCaughtRuntimeException()");
+ }
+ }
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ private final Backend backend;
+
+ public SubsumedCatchHandlerTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expected = "In bar() -> 0";
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
+
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(SubsumedCatchHandlerTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expected)
+ .inspector();
+
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject.method("void", "handleCaughtRuntimeException"), not(isPresent()));
+
+ MethodSubject mainMethodSubject = classSubject.mainMethod();
+ Code code = mainMethodSubject.getMethod().getCode();
+ if (code.isDexCode()) {
+ DexCode dexCode = code.asDexCode();
+ assertEquals(1, dexCode.handlers.length);
+
+ TryHandler handler = dexCode.handlers[0];
+ assertEquals(1, handler.pairs.length);
+
+ DexType guard = handler.pairs[0].type;
+ assertEquals("java.lang.Exception", guard.toSourceString());
+ } else {
+ assert code.isCfCode();
+ CfCode cfCode = code.asCfCode();
+ assertEquals(1, cfCode.getTryCatchRanges().size());
+
+ CfTryCatch handler = cfCode.getTryCatchRanges().get(0);
+ assertEquals(1, handler.guards.size());
+
+ DexType guard = handler.guards.get(0);
+ assertEquals("java.lang.Exception", guard.toSourceString());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
index 3377aa3..2bf8b2a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -101,7 +102,7 @@
"Another_shared",
"2",
"1",
- "𐀀",
+ "𐀀", // Different output in Windows.
"3"
);
main = StringLengthTestMain.class;
@@ -110,7 +111,9 @@
@Before
public void setUp() throws Exception {
classes = ImmutableList.of(ForceInline.class, NeverInline.class, StringLengthTestMain.class);
- testForJvm().addTestClasspath().run(main).assertSuccessWithOutput(javaOutput);
+ if (!ToolHelper.isWindows()) {
+ testForJvm().addTestClasspath().run(main).assertSuccessWithOutput(javaOutput);
+ }
}
private static boolean isStringLength(DexMethod method) {
@@ -156,15 +159,19 @@
TestRunResult result = testForD8()
.release()
.addProgramClasses(classes)
- .run(main)
- .assertSuccessWithOutput(javaOutput);
+ .run(main);
+ if (!ToolHelper.isWindows()) {
+ result.assertSuccessWithOutput(javaOutput);
+ }
test(result, 1, 4);
result = testForD8()
.debug()
.addProgramClasses(classes)
- .run(main)
- .assertSuccessWithOutput(javaOutput);
+ .run(main);
+ if (!ToolHelper.isWindows()) {
+ result.assertSuccessWithOutput(javaOutput);
+ }
test(result, 6, 0);
}
@@ -175,8 +182,10 @@
.enableProguardTestOptions()
.enableInliningAnnotations()
.addKeepMainRule(main)
- .run(main)
- .assertSuccessWithOutput(javaOutput);
+ .run(main);
+ if (!ToolHelper.isWindows()) {
+ result.assertSuccessWithOutput(javaOutput);
+ }
// TODO we could remove const counting if it needs to be changed too frequently, since
// the string length count is what we're interested in.
test(result, 0, backend == Backend.DEX ? 5 : 6);
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
new file mode 100644
index 0000000..e5f9799
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
@@ -0,0 +1,80 @@
+// 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.shaking.fields;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+
+public abstract class FieldsTestBase extends TestBase {
+
+ public abstract Collection<Class<?>> getClasses();
+
+ public abstract Class<?> getMainClass();
+
+ public void testOnR8(List<String> keepRules, Consumer<CodeInspector> inspector, String expected)
+ throws Throwable {
+ testForR8(Backend.DEX)
+ .enableMergeAnnotations()
+ .addProgramClasses(getClasses())
+ .addKeepRules(keepRules)
+ .compile()
+ .inspect(inspector)
+ .run(getMainClass())
+ .assertSuccessWithOutput(expected);
+ }
+
+ public void testOnProguard(
+ List<String> keepRules, Consumer<CodeInspector> inspector, String expected) throws Throwable {
+ testForProguard()
+ .addProgramClasses(getClasses())
+ .addProgramClasses(NeverMerge.class)
+ .addKeepRules(keepRules)
+ .compile()
+ .inspect(inspector)
+ .run(getMainClass())
+ .assertSuccessWithOutput(expected);
+ }
+
+ public void runTest(List<String> keepRules, Consumer<CodeInspector> inspector, String expected)
+ throws Throwable {
+ testOnProguard(keepRules, inspector, expected);
+ testOnR8(keepRules, inspector, expected);
+ }
+
+ public void runTest(String keepRules, Consumer<CodeInspector> inspector, String expected)
+ throws Throwable {
+ runTest(
+ ImmutableList.of(
+ keepRules,
+ "-dontobfuscate",
+ "-keep class " + getMainClass().getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}"),
+ inspector,
+ expected);
+ }
+
+ public String allFieldsOutput() {
+ return StringUtils.lines("Super.f1 found", "Sub.f2 found", "SubSub.f3 found");
+ }
+
+ public String onlyF1Output() {
+ return StringUtils.lines("Super.f1 found", "Sub.f2 not found", "SubSub.f3 not found");
+ }
+
+ public String onlyF2Output() {
+ return StringUtils.lines("Super.f1 not found", "Sub.f2 found", "SubSub.f3 not found");
+ }
+
+ public String onlyF3Output() {
+ return StringUtils.lines("Super.f1 not found", "Sub.f2 not found", "SubSub.f3 found");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/pblc/PublicFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/pblc/PublicFieldsTest.java
new file mode 100644
index 0000000..6b8048e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/fields/pblc/PublicFieldsTest.java
@@ -0,0 +1,137 @@
+// 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.shaking.fields.pblc;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.shaking.fields.FieldsTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+
+@NeverMerge
+class Super {
+ public int f1;
+}
+
+@NeverMerge
+class Sub extends Super {
+ public int f2;
+}
+
+@NeverMerge
+class SubSub extends Sub {
+ public int f3;
+}
+
+class Main {
+
+ private static void printField(Class<?> clazz, String name) {
+ try {
+ clazz.getDeclaredField(name);
+ System.out.println(clazz.getSimpleName() + "." + name + " found");
+ } catch (NoSuchFieldException e) {
+ System.out.println(clazz.getSimpleName() + "." + name + " not found");
+ }
+ }
+
+ public static void main(String[] args) {
+ printField(Super.class, "f1");
+ printField(Sub.class, "f2");
+ printField(SubSub.class, "f3");
+ }
+}
+
+public class PublicFieldsTest extends FieldsTestBase {
+
+ @Override
+ public Collection<Class<?>> getClasses() {
+ return ImmutableSet.of(Super.class, Sub.class, SubSub.class, getMainClass());
+ }
+
+ @Override
+ public Class<?> getMainClass() {
+ return Main.class;
+ }
+
+ private void checkFields(CodeInspector inspector, Set<String> expected) {
+ ClassSubject superSubject = inspector.clazz(Super.class);
+ assertThat(superSubject, isPresent());
+ assertEquals(superSubject.field("int", "f1").isPresent(), expected.contains("f1"));
+ ClassSubject subSubject = inspector.clazz(Sub.class);
+ assertThat(subSubject, isPresent());
+ assertEquals(subSubject.field("int", "f2").isPresent(), expected.contains("f2"));
+ ClassSubject subSubSubject = inspector.clazz(SubSub.class);
+ assertThat(subSubSubject, isPresent());
+ assertEquals(subSubSubject.field("int", "f3").isPresent(), expected.contains("f3"));
+ }
+
+ private void checkAllFields(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f1", "f2", "f3"));
+ }
+
+ private void checkOnlyF1(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f1"));
+ }
+
+ private void checkOnlyF2(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f2"));
+ }
+
+ private void checkOnlyF3(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f3"));
+ }
+
+ @Test
+ public void testKeepAllFieldsWithWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { *; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithNameWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { int f*; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithFields() throws Throwable {
+ runTest("-keep class **.SubSub { <fields>; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepF1() throws Throwable {
+ runTest("-keep class **.SubSub { int f1; }", this::checkOnlyF1, onlyF1Output());
+ }
+
+ @Test
+ public void testKeepF2() throws Throwable {
+ runTest("-keep class **.SubSub { int f2; }", this::checkOnlyF2, onlyF2Output());
+ }
+
+ @Test
+ public void testKeepF3() throws Throwable {
+ runTest("-keep class **.SubSub { int f3; }", this::checkOnlyF3, onlyF3Output());
+ }
+
+ @Test
+ public void testKeepF1WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f1; }", this::checkOnlyF1, onlyF1Output());
+ }
+
+ @Test
+ public void testKeepF2WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f2; }", this::checkOnlyF2, onlyF2Output());
+ }
+
+ @Test
+ public void testKeepF3WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f3; }", this::checkOnlyF3, onlyF3Output());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/pckg/PackageFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/pckg/PackageFieldsTest.java
new file mode 100644
index 0000000..00b4ead
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/fields/pckg/PackageFieldsTest.java
@@ -0,0 +1,134 @@
+// 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.shaking.fields.pckg;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.shaking.fields.FieldsTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+
+@NeverMerge
+class Super {
+ int f1;
+}
+
+@NeverMerge
+class Sub extends Super {
+ int f2;
+}
+
+@NeverMerge
+class SubSub extends Sub {
+ int f3;
+}
+
+class Main {
+
+ private static void printField(Class<?> clazz, String name) {
+ try {
+ clazz.getDeclaredField(name);
+ System.out.println(clazz.getSimpleName() + "." + name + " found");
+ } catch (NoSuchFieldException e) {
+ System.out.println(clazz.getSimpleName() + "." + name + " not found");
+ }
+ }
+
+ public static void main(String[] args) {
+ printField(Super.class, "f1");
+ printField(Sub.class, "f2");
+ printField(SubSub.class, "f3");
+ }
+}
+
+public class PackageFieldsTest extends FieldsTestBase {
+ public Collection<Class<?>> getClasses() {
+ return ImmutableSet.of(Super.class, Sub.class, SubSub.class, getMainClass());
+ }
+
+ public Class<?> getMainClass() {
+ return Main.class;
+ }
+
+ private void checkFields(CodeInspector inspector, Set<String> expected) {
+ ClassSubject superSubject = inspector.clazz(Super.class);
+ assertThat(superSubject, isPresent());
+ assertEquals(superSubject.field("int", "f1").isPresent(), expected.contains("f1"));
+ ClassSubject subSubject = inspector.clazz(Sub.class);
+ assertThat(subSubject, isPresent());
+ assertEquals(subSubject.field("int", "f2").isPresent(), expected.contains("f2"));
+ ClassSubject subSubSubject = inspector.clazz(SubSub.class);
+ assertThat(subSubSubject, isPresent());
+ assertEquals(subSubSubject.field("int", "f3").isPresent(), expected.contains("f3"));
+ }
+
+ private void checkAllFields(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f1", "f2", "f3"));
+ }
+
+ private void checkOnlyF1(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f1"));
+ }
+
+ private void checkOnlyF2(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f2"));
+ }
+
+ private void checkOnlyF3(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f3"));
+ }
+
+ @Test
+ public void testKeepAllFieldsWithWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { *; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithNameWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { int f*; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithFields() throws Throwable {
+ runTest("-keep class **.SubSub { <fields>; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepF1() throws Throwable {
+ runTest("-keep class **.SubSub { int f1; }", this::checkOnlyF1, onlyF1Output());
+ }
+
+ @Test
+ public void testKeepF2() throws Throwable {
+ runTest("-keep class **.SubSub { int f2; }", this::checkOnlyF2, onlyF2Output());
+ }
+
+ @Test
+ public void testKeepF3() throws Throwable {
+ runTest("-keep class **.SubSub { int f3; }", this::checkOnlyF3, onlyF3Output());
+ }
+
+ @Test
+ public void testKeepF1WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f1; }", this::checkOnlyF1, onlyF1Output());
+ }
+
+ @Test
+ public void testKeepF2WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f2; }", this::checkOnlyF2, onlyF2Output());
+ }
+
+ @Test
+ public void testKeepF3WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f3; }", this::checkOnlyF3, onlyF3Output());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/prvt/PrivateFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/prvt/PrivateFieldsTest.java
new file mode 100644
index 0000000..28cf204
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/fields/prvt/PrivateFieldsTest.java
@@ -0,0 +1,135 @@
+// 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.shaking.fields.prvt;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.shaking.fields.FieldsTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+
+@NeverMerge
+class Super {
+ private int f1;
+}
+
+@NeverMerge
+class Sub extends Super {
+ private int f2;
+}
+
+@NeverMerge
+class SubSub extends Sub {
+ private int f3;
+}
+
+class Main {
+
+ private static void printField(Class<?> clazz, String name) {
+ try {
+ clazz.getDeclaredField(name);
+ System.out.println(clazz.getSimpleName() + "." + name + " found");
+ } catch (NoSuchFieldException e) {
+ System.out.println(clazz.getSimpleName() + "." + name + " not found");
+ }
+ }
+
+ public static void main(String[] args) {
+ printField(Super.class, "f1");
+ printField(Sub.class, "f2");
+ printField(SubSub.class, "f3");
+ }
+}
+
+public class PrivateFieldsTest extends FieldsTestBase {
+
+ public Collection<Class<?>> getClasses() {
+ return ImmutableSet.of(Super.class, Sub.class, SubSub.class, getMainClass());
+ }
+
+ public Class<?> getMainClass() {
+ return Main.class;
+ }
+
+ private void checkFields(CodeInspector inspector, Set<String> expected) {
+ ClassSubject superSubject = inspector.clazz(Super.class);
+ assertThat(superSubject, isPresent());
+ assertEquals(superSubject.field("int", "f1").isPresent(), expected.contains("f1"));
+ ClassSubject subSubject = inspector.clazz(Sub.class);
+ assertThat(subSubject, isPresent());
+ assertEquals(subSubject.field("int", "f2").isPresent(), expected.contains("f2"));
+ ClassSubject subSubSubject = inspector.clazz(SubSub.class);
+ assertThat(subSubSubject, isPresent());
+ assertEquals(subSubSubject.field("int", "f3").isPresent(), expected.contains("f3"));
+ }
+
+ private void checkAllFields(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f1", "f2", "f3"));
+ }
+
+ private void checkOnlyF1(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f1"));
+ }
+
+ private void checkOnlyF2(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f2"));
+ }
+
+ private void checkOnlyF3(CodeInspector inspector) {
+ checkFields(inspector, ImmutableSet.of("f3"));
+ }
+
+ @Test
+ public void testKeepAllFieldsWithWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { *; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithNameWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { int f*; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithFields() throws Throwable {
+ runTest("-keep class **.SubSub { <fields>; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepF1() throws Throwable {
+ runTest("-keep class **.SubSub { int f1; }", this::checkOnlyF1, onlyF1Output());
+ }
+
+ @Test
+ public void testKeepF2() throws Throwable {
+ runTest("-keep class **.SubSub { int f2; }", this::checkOnlyF2, onlyF2Output());
+ }
+
+ @Test
+ public void testKeepF3() throws Throwable {
+ runTest("-keep class **.SubSub { int f3; }", this::checkOnlyF3, onlyF3Output());
+ }
+
+ @Test
+ public void testKeepF1WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f1; }", this::checkOnlyF1, onlyF1Output());
+ }
+
+ @Test
+ public void testKeepF2WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f2; }", this::checkOnlyF2, onlyF2Output());
+ }
+
+ @Test
+ public void testKeepF3WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f3; }", this::checkOnlyF3, onlyF3Output());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/shadow/ShadowFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/shadow/ShadowFieldsTest.java
new file mode 100644
index 0000000..f8f403d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/fields/shadow/ShadowFieldsTest.java
@@ -0,0 +1,105 @@
+// 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.shaking.fields.shadow;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.shaking.fields.FieldsTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import org.junit.Test;
+
+@NeverMerge
+class Super {
+ public int f1;
+}
+
+@NeverMerge
+class Sub extends Super {
+ public int f1;
+}
+
+@NeverMerge
+class SubSub extends Sub {
+ public int f1;
+}
+
+class Main {
+
+ private static void printField(Class<?> clazz, String name) {
+ try {
+ clazz.getDeclaredField(name);
+ System.out.println(clazz.getSimpleName() + "." + name + " found");
+ } catch (NoSuchFieldException e) {
+ System.out.println(clazz.getSimpleName() + "." + name + " not found");
+ }
+ }
+
+ public static void main(String[] args) {
+ printField(Super.class, "f1");
+ printField(Sub.class, "f1");
+ printField(SubSub.class, "f1");
+ }
+}
+
+public class ShadowFieldsTest extends FieldsTestBase {
+
+ @Override
+ public Collection<Class<?>> getClasses() {
+ return ImmutableSet.of(Super.class, Sub.class, SubSub.class, getMainClass());
+ }
+
+ @Override
+ public Class<?> getMainClass() {
+ return Main.class;
+ }
+
+ private void checkAllFields(CodeInspector inspector) {
+ ClassSubject superSubject = inspector.clazz(Super.class);
+ assertThat(superSubject, isPresent());
+ assertThat(superSubject.field("int", "f1"), isPresent());
+ ClassSubject subSubject = inspector.clazz(Sub.class);
+ assertThat(subSubject, isPresent());
+ assertThat(subSubject.field("int", "f1"), isPresent());
+ ClassSubject subSubSubject = inspector.clazz(SubSub.class);
+ assertThat(subSubSubject, isPresent());
+ assertThat(subSubSubject.field("int", "f1"), isPresent());
+ }
+
+ @Override
+ public String allFieldsOutput() {
+ return StringUtils.lines("Super.f1 found", "Sub.f1 found", "SubSub.f1 found");
+ }
+
+ @Test
+ public void testKeepAllFieldsWithWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { *; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithNameWildcard() throws Throwable {
+ runTest("-keep class **.SubSub { int f*; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepAllFieldsWithFields() throws Throwable {
+ runTest("-keep class **.SubSub { <fields>; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepF1() throws Throwable {
+ runTest("-keep class **.SubSub { int f1; }", this::checkAllFields, allFieldsOutput());
+ }
+
+ @Test
+ public void testKeepF1WithExtends() throws Throwable {
+ runTest("-keep class * extends **.Sub { int f1; }", this::checkAllFields, allFieldsOutput());
+ }
+}