Enable inlining of synchronized methods
Change-Id: I399f5583f940efb9035bb813e4d7ddcabb835e8b
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 f34e2bc..09a2c32 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -464,9 +464,6 @@
Position callerPosition,
Origin origin) {
checkIfObsolete();
- if (accessFlags.isSynchronized()) {
- throw new Unreachable("Invalid attempt to build synchronized method for inlining");
- }
return code.buildInliningIR(
context, this, appView, valueNumberGenerator, callerPosition, origin);
}
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 fa9384a..c9c6c86 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
@@ -214,6 +214,13 @@
return normals.build();
}
+ public int numberOfNormalSuccessors() {
+ if (hasCatchHandlers()) {
+ return successors.size() - catchHandlers.getUniqueTargets().size();
+ }
+ return successors.size();
+ }
+
public boolean hasUniquePredecessor() {
return predecessors.size() == 1;
}
@@ -893,11 +900,39 @@
catchHandlers = new CatchHandlers<>(guards, successorIndexes);
}
- public void addCatchHandler(BasicBlock rethrowBlock, DexType guard) {
- assert !hasCatchHandlers();
- getMutableSuccessors().add(0, rethrowBlock);
- rethrowBlock.getMutablePredecessors().add(this);
- catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
+ public void appendCatchHandler(BasicBlock target, DexType guard) {
+ if (!canThrow()) {
+ // Nothing to catch.
+ return;
+ }
+ if (hasCatchHandlers()) {
+ if (catchHandlers.getGuards().contains(guard)) {
+ // Subsumed by an existing catch handler.
+ return;
+ }
+ int targetIndex = successors.indexOf(target);
+ if (targetIndex < 0) {
+ List<BasicBlock> successors = getMutableSuccessors();
+ int numberOfSuccessors = successors.size();
+ int numberOfNormalSuccessors = numberOfNormalSuccessors();
+ if (numberOfNormalSuccessors > 0) {
+ // Increase the size of the successor list by 1, and increase the index of each normal
+ // successor by 1.
+ successors.add(numberOfSuccessors - numberOfNormalSuccessors - 1, target);
+ } else {
+ // If there are no normal successors we can simply add the new catch handler.
+ targetIndex = successors.size();
+ successors.add(target);
+ }
+ target.getMutablePredecessors().add(this);
+ }
+ catchHandlers = catchHandlers.appendGuard(guard, targetIndex);
+ } else {
+ assert instructions.stream().filter(Instruction::instructionTypeCanThrow).count() == 1;
+ getMutableSuccessors().add(0, target);
+ target.getMutablePredecessors().add(this);
+ catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
+ }
}
/**
@@ -1795,8 +1830,7 @@
continue;
}
BasicBlock catchSuccessor = fromBlock.successors.get(prevCatchTarget);
- // We assume that all the catch handlers targets has only one
- // predecessor and, thus, no phis.
+ // We assume that all the catch handlers targets has only one predecessor and, thus, no phis.
assert catchSuccessor.getPredecessors().size() == 1;
assert catchSuccessor.getPhis().isEmpty();
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 237c0cc..95ee7e0 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
@@ -76,6 +76,13 @@
&& getGuards().get(getGuards().size() - 1) == factory.throwableType;
}
+ public CatchHandlers<T> appendGuard(DexType guard, T target) {
+ assert !guards.contains(guard);
+ List<DexType> newGuards = ImmutableList.<DexType>builder().addAll(guards).add(guard).build();
+ List<T> newTargets = ImmutableList.<T>builder().addAll(targets).add(target).build();
+ return new CatchHandlers<>(newGuards, newTargets);
+ }
+
public CatchHandlers<T> removeGuard(DexType guardToBeRemoved) {
List<DexType> newGuards = new ArrayList<>();
List<T> newTargets = new ArrayList<>();
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 fee2c94..1078172 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
@@ -13,12 +13,14 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -133,11 +135,6 @@
return metadata;
}
- public void mergeMetadataFromInlinee(IRCode inlinee) {
- assert !inlinee.metadata.mayHaveMonitorInstruction();
- this.metadata.merge(inlinee.metadata);
- }
-
public BasicBlock entryBlock() {
return blocks.getFirst();
}
@@ -286,6 +283,53 @@
return false;
}
+ /**
+ * Prepares every block for getting catch handlers. This involves splitting blocks until each
+ * block has only one throwing instruction, and all instructions after the throwing instruction
+ * are allowed to follow a throwing instruction.
+ *
+ * <p>It is also a requirement that the entry block does not have any catch handlers, thus the
+ * entry block is split to ensure that it contains no throwing instructions.
+ *
+ * <p>This method also inserts a split block between each block with more than two predecessors
+ * and the predecessors that have a throwing instruction. This is necessary because adding catch
+ * handlers to a predecessor would otherwise lead to critical edges.
+ */
+ public void prepareBlocksForCatchHandlers() {
+ BasicBlock entryBlock = entryBlock();
+ ListIterator<BasicBlock> blockIterator = listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ InstructionListIterator instructionIterator = block.listIterator(this);
+ boolean hasSeenThrowingInstruction = false;
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ boolean instructionTypeCanThrow = instruction.instructionTypeCanThrow();
+ if ((hasSeenThrowingInstruction && !instruction.isAllowedAfterThrowingInstruction())
+ || (instructionTypeCanThrow && block == entryBlock)) {
+ instructionIterator.previous();
+ instructionIterator.split(this, blockIterator);
+ blockIterator.previous();
+ break;
+ }
+ if (instructionTypeCanThrow) {
+ hasSeenThrowingInstruction = true;
+ }
+ }
+ if (hasSeenThrowingInstruction) {
+ List<BasicBlock> successors = block.getSuccessors();
+ if (successors.size() == 1 && ListUtils.first(successors).getPredecessors().size() > 1) {
+ BasicBlock splitBlock = block.createSplitBlock(getHighestBlockNumber() + 1, true);
+ Goto newGoto = new Goto(block);
+ newGoto.setPosition(Position.none());
+ splitBlock.listIterator(this).add(newGoto);
+ blockIterator.add(splitBlock);
+ }
+ }
+ }
+ assert blocks.stream().allMatch(block -> block.numberOfThrowingInstructions() <= 1);
+ }
+
public void splitCriticalEdges() {
List<BasicBlock> newBlocks = new ArrayList<>();
int nextBlockNumber = getHighestBlockNumber() + 1;
@@ -959,6 +1003,10 @@
return createValue(typeLattice, null);
}
+ public Phi createPhi(BasicBlock block, TypeLatticeElement type) {
+ return new Phi(valueNumberGenerator.next(), block, type, null, RegisterReadType.NORMAL);
+ }
+
public ConstNumber createIntConstant(int value) {
Value out = createValue(TypeLatticeElement.INT);
return new ConstNumber(out, value);
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 7c1590c..b7d3dcd 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
@@ -441,7 +441,7 @@
BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition(), guard, appView);
code.blocks.add(rethrowBlock);
// Add catch handler to the block containing the last recursive call.
- newBlock.addCatchHandler(rethrowBlock, guard);
+ newBlock.appendCatchHandler(rethrowBlock, guard);
}
}
@@ -1220,16 +1220,18 @@
// and for the constructor. To be eligible method should only be using its
// receiver in the following ways:
//
- // (1) as a receiver of reads/writes of instance fields of the holder class
- // (2) as a return value
+ // (1) as a receiver of reads/writes of instance fields of the holder class,
+ // (2) as a return value,
// (3) as a receiver of a call to the superclass initializer. Note that we don't
// check what is passed to superclass initializer as arguments, only check
- // that it is not the instance being initialized.
+ // that it is not the instance being initialized,
+ // (4) as argument to a monitor instruction.
//
+ // Note that (4) can safely be removed as the receiver is guaranteed not to escape when we class
+ // inline it, and hence any monitor instructions are no-ops.
boolean instanceInitializer = method.isInstanceInitializer();
if (method.accessFlags.isNative()
- || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)
- || method.accessFlags.isSynchronized()) {
+ || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)) {
return;
}
@@ -1248,6 +1250,10 @@
boolean receiverUsedAsReturnValue = false;
boolean seenSuperInitCall = false;
for (Instruction insn : receiver.uniqueUsers()) {
+ if (insn.isMonitor()) {
+ continue;
+ }
+
if (insn.isReturn()) {
receiverUsedAsReturnValue = true;
continue;
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 263da6c..7306af2 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
@@ -264,12 +264,6 @@
return false;
}
- // Don't inline if target is synchronized.
- if (singleTarget.accessFlags.isSynchronized()) {
- whyAreYouNotInliningReporter.reportSynchronizedMethod();
- return false;
- }
-
if (reason == Reason.DUAL_CALLER) {
if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
// When we have a method with two call sites, we simply inline the method as we normally do
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 44c72c5..ce040ff 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
@@ -3,20 +3,27 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.google.common.base.Predicates.not;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
@@ -24,6 +31,9 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.MoveException;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
@@ -41,6 +51,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -575,14 +586,26 @@
AppView<? extends AppInfoWithSubtyping> appView,
Position callerPosition,
LensCodeRewriter lensCodeRewriter) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ InternalOptions options = appView.options();
Origin origin = appView.appInfo().originFor(target.method.holder);
// Build the IR for a yet not processed method, and perform minimal IR processing.
IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
- // Insert a null check if this is needed to preserve the implicit null check for the
- // receiver.
- if (shouldSynthesizeNullCheckForReceiver) {
+ // Insert a null check if this is needed to preserve the implicit null check for the receiver.
+ // This is only needed if we do not also insert a monitor-enter instruction, since that will
+ // throw a NPE if the receiver is null.
+ //
+ // Note: When generating DEX, we synthesize monitor-enter/exit instructions during IR
+ // building, and therefore, we do not need to do anything here. Upon writing, we will use the
+ // flag "declared synchronized" instead of "synchronized".
+ boolean shouldSynthesizeMonitorEnterExit =
+ target.accessFlags.isSynchronized() && options.isGeneratingClassFiles();
+ boolean isSynthesizingNullCheckForReceiverUsingMonitorEnter =
+ shouldSynthesizeMonitorEnterExit && !target.isStatic();
+ if (shouldSynthesizeNullCheckForReceiver
+ && !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
List<Value> arguments = code.collectArguments();
if (!arguments.isEmpty()) {
Value receiver = arguments.get(0);
@@ -617,9 +640,119 @@
assert false : "Unable to synthesize a null check for the receiver";
}
}
+
+ // Insert monitor-enter and monitor-exit instructions if the method is synchronized.
+ if (shouldSynthesizeMonitorEnterExit) {
+ TypeLatticeElement throwableType =
+ TypeLatticeElement.fromDexType(
+ dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView);
+
+ code.prepareBlocksForCatchHandlers();
+
+ int nextBlockNumber = code.getHighestBlockNumber() + 1;
+
+ // Create a block for holding the monitor-exit instruction.
+ BasicBlock monitorExitBlock = new BasicBlock();
+ monitorExitBlock.setNumber(nextBlockNumber++);
+
+ // For each block in the code that may throw, add a catch-all handler targeting the
+ // monitor-exit block.
+ List<BasicBlock> moveExceptionBlocks = new ArrayList<>();
+ for (BasicBlock block : code.blocks) {
+ if (!block.canThrow()) {
+ continue;
+ }
+ if (block.hasCatchHandlers()
+ && block.getCatchHandlersWithSuccessorIndexes().hasCatchAll(dexItemFactory)) {
+ continue;
+ }
+ BasicBlock moveExceptionBlock =
+ BasicBlock.createGotoBlock(
+ nextBlockNumber++, Position.none(), code.metadata(), monitorExitBlock);
+ InstructionListIterator moveExceptionBlockIterator =
+ moveExceptionBlock.listIterator(code);
+ moveExceptionBlockIterator.setInsertionPosition(Position.syntheticNone());
+ moveExceptionBlockIterator.add(
+ new MoveException(
+ code.createValue(throwableType), dexItemFactory.throwableType, options));
+ block.appendCatchHandler(moveExceptionBlock, dexItemFactory.throwableType);
+ moveExceptionBlocks.add(moveExceptionBlock);
+ }
+
+ // Create a phi for the exception values such that we can rethrow the exception if needed.
+ Value exceptionValue;
+ if (moveExceptionBlocks.size() == 1) {
+ exceptionValue =
+ ListUtils.first(moveExceptionBlocks).getInstructions().getFirst().outValue();
+ } else {
+ Phi phi = code.createPhi(monitorExitBlock, throwableType);
+ List<Value> operands =
+ ListUtils.map(
+ moveExceptionBlocks, block -> block.getInstructions().getFirst().outValue());
+ phi.addOperands(operands);
+ exceptionValue = phi;
+ }
+
+ InstructionListIterator monitorExitBlockIterator = monitorExitBlock.listIterator(code);
+ monitorExitBlockIterator.setInsertionPosition(Position.syntheticNone());
+ monitorExitBlockIterator.add(new Throw(exceptionValue));
+ monitorExitBlock.getMutablePredecessors().addAll(moveExceptionBlocks);
+
+ // Insert the newly created blocks.
+ code.blocks.addAll(moveExceptionBlocks);
+ code.blocks.add(monitorExitBlock);
+
+ // Create a block for holding the monitor-enter instruction. Note that, since this block
+ // is created after we attach catch-all handlers to the code, this block will not have any
+ // catch handlers.
+ BasicBlock entryBlock = code.entryBlock();
+ InstructionListIterator entryBlockIterator = entryBlock.listIterator(code);
+ entryBlockIterator.nextUntil(not(Instruction::isArgument));
+ entryBlockIterator.previous();
+ BasicBlock monitorEnterBlock = entryBlockIterator.split(code, 0, null);
+ assert !monitorEnterBlock.hasCatchHandlers();
+
+ InstructionListIterator monitorEnterBlockIterator = monitorEnterBlock.listIterator(code);
+ monitorEnterBlockIterator.setInsertionPosition(Position.syntheticNone());
+
+ // If this is a static method, then the class object will act as the lock, so we load it
+ // using a const-class instruction.
+ Value lockValue;
+ if (target.isStatic()) {
+ lockValue =
+ code.createValue(
+ TypeLatticeElement.fromDexType(
+ dexItemFactory.objectType, definitelyNotNull(), appView));
+ monitorEnterBlockIterator.add(new ConstClass(lockValue, target.method.holder));
+ } else {
+ lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
+ }
+
+ // Insert the monitor-enter and monitor-exit instructions.
+ monitorEnterBlockIterator.add(new Monitor(Monitor.Type.ENTER, lockValue));
+ monitorExitBlockIterator.previous();
+ monitorExitBlockIterator.add(new Monitor(Monitor.Type.EXIT, lockValue));
+ monitorExitBlock.close(null);
+
+ for (BasicBlock block : code.blocks) {
+ if (block.exit().isReturn()) {
+ // Since return instructions are not allowed after a throwing instruction in a block
+ // with catch handlers, the call to prepareBlocksForCatchHandlers() has already taken
+ // care of ensuring that all return blocks have no throwing instructions.
+ assert !block.canThrow();
+
+ InstructionListIterator instructionIterator =
+ block.listIterator(code, block.getInstructions().size() - 1);
+ instructionIterator.setInsertionPosition(Position.syntheticNone());
+ instructionIterator.add(new Monitor(Monitor.Type.EXIT, lockValue));
+ }
+ }
+ }
+
if (!target.isProcessed()) {
lensCodeRewriter.rewrite(code, target);
}
+ assert code.isConsistentSSA();
return new InlineeWithReason(code, reason);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 074160b..f1256d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -208,8 +208,7 @@
}
public ConstraintWithTarget forMonitor() {
- // Conservative choice.
- return ConstraintWithTarget.NEVER;
+ return ConstraintWithTarget.ALWAYS;
}
public ConstraintWithTarget forMove() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index f836195..809db6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -494,6 +494,13 @@
continue;
}
+ if (user.isMonitor()) {
+ // Since this instance never escapes and is guaranteed to be non-null, any monitor
+ // instructions are no-ops.
+ removeInstruction(user);
+ continue;
+ }
+
throw new Unreachable(
"Unexpected usage left in method `"
+ method.method.toSourceString()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index b5c96d4..815bc20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -104,9 +104,6 @@
public void reportRecursiveMethod() {}
@Override
- public void reportSynchronizedMethod() {}
-
- @Override
public void reportUnknownTarget() {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 1e869eb..12f6a5a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -101,8 +101,6 @@
public abstract void reportRecursiveMethod();
- public abstract void reportSynchronizedMethod();
-
abstract void reportUnknownTarget();
public abstract void reportUnsafeConstructorInliningDueToFinalFieldAssignment(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index dcc122b..f64622c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -210,11 +210,6 @@
}
@Override
- public void reportSynchronizedMethod() {
- print("synchronized methods are not inlined.");
- }
-
- @Override
public void reportUnknownTarget() {
print("could not find a single target.");
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 370d5e2..79f4f6c 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -12,6 +12,15 @@
public class ListUtils {
+ public static <T> T first(List<T> list) {
+ return list.get(0);
+ }
+
+ public static <T> T last(List<T> list) {
+ assert list instanceof ArrayList;
+ return list.get(list.size() - 1);
+ }
+
public static <T> int lastIndexMatching(List<T> list, Predicate<T> tester) {
for (int i = list.size() - 1; i >= 0; i--) {
if (tester.test(list.get(i))) {
diff --git a/src/test/examples/getmembers/B.java b/src/test/examples/getmembers/B.java
index d29d0a2..1f41948 100644
--- a/src/test/examples/getmembers/B.java
+++ b/src/test/examples/getmembers/B.java
@@ -15,9 +15,9 @@
return bazResult;
}
- synchronized static String inliner() throws Exception {
+ @NeverInline
+ static String inliner() throws Exception {
B self = new B();
return self.toBeInlined();
}
-
}
diff --git a/src/test/examples/getmembers/NeverInline.java b/src/test/examples/getmembers/NeverInline.java
new file mode 100644
index 0000000..0c5a4ef
--- /dev/null
+++ b/src/test/examples/getmembers/NeverInline.java
@@ -0,0 +1,10 @@
+// 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 getmembers;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverInline {}
diff --git a/src/test/examples/getmembers/keep-rules.txt b/src/test/examples/getmembers/keep-rules.txt
index 9319c68..9fbea4d 100644
--- a/src/test/examples/getmembers/keep-rules.txt
+++ b/src/test/examples/getmembers/keep-rules.txt
@@ -19,3 +19,7 @@
java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]);
java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);
}
+
+-neverinline class * {
+ @getmembers.NeverInline <methods>;
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index e60900c..fbcc6a1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -378,6 +378,7 @@
.addKeepAttributes("LineNumberTable")
.addOptionsModification(this::configure)
.allowAccessModification()
+ .enableInliningAnnotations()
.noMinification()
.run(main)
.assertSuccessWithOutput(javaOutput);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
index 130f895..566d3f9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.classinliner.lambdas;
+import com.android.tools.r8.NeverInline;
+
public class LambdasTestClass {
private static int ID = 0;
@@ -39,14 +41,16 @@
return next();
}
- private synchronized void testStatelessLambda() {
+ @NeverInline
+ private void testStatelessLambda() {
IfaceUtil.act(() -> next());
IfaceUtil.act(LambdasTestClass::next);
IfaceUtil.act(LambdasTestClass::exact);
IfaceUtil.act(LambdasTestClass::almost);
}
- private synchronized void testStatefulLambda(String a, String b) {
+ @NeverInline
+ private void testStatefulLambda(String a, String b) {
IfaceUtil.act(() -> a);
IfaceUtil.act(() -> a + b);
IfaceUtil.act((a + b)::toLowerCase);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
index dcfe291..b3a7ce8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -5,20 +5,17 @@
package com.android.tools.r8.ir.optimize.inliner;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.util.Iterator;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -26,71 +23,89 @@
@RunWith(Parameterized.class)
public class InlineSynchronizedTest extends TestBase {
- private static final List<String> METHOD_NAMES =
- ImmutableList.of(
- "println",
- "normalInlinedSynchronized",
- "classInlinedSynchronized",
- "normalInlinedControl",
- "classInlinedControl");
-
- @Parameterized.Parameters(name = "{1}")
- public static List<Object[]> data() {
- return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- private final Backend backend;
private final TestParameters parameters;
- public InlineSynchronizedTest(TestParameters parameters, Backend backend) {
+ public InlineSynchronizedTest(TestParameters parameters) {
this.parameters = parameters;
- this.backend = backend;
}
@Test
public void test() throws Exception {
- CodeInspector codeInspector =
- testForR8(backend)
- .addProgramClasses(InlineSynchronizedTestClass.class)
- .addKeepMainRule(InlineSynchronizedTestClass.class)
- .enableInliningAnnotations()
- .noMinification()
- .compile()
- .inspector();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InlineSynchronizedTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
- ClassSubject classSubject = codeInspector.clazz(InlineSynchronizedTestClass.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.mainMethod();
- Iterator<InstructionSubject> it = methodSubject.iterateInstructions();
- int[] counts = new int[METHOD_NAMES.size()];
- while (it.hasNext()) {
- InstructionSubject instruction = it.next();
- if (!instruction.isInvoke()) {
- continue;
- }
- DexString invokedName = ((InvokeInstructionSubject) instruction).invokedMethod().name;
- int idx = METHOD_NAMES.indexOf(invokedName.toString());
- if (idx >= 0) {
- ++counts[idx];
- }
- }
- // Synchronized methods can never be inlined.
- assertCount(counts, "normalInlinedSynchronized", 1);
- assertCount(counts, "classInlinedSynchronized", 1);
- // Only the never-merge method is inlined, classInlining should run on the kept class.
- assertCount(counts, "normalInlinedControl", 0);
- assertCount(counts, "classInlinedControl", 1);
- // Double check the total.
- int total = 0;
- for (int i = 0; i < counts.length; ++i) {
- total += counts[i];
- }
- assertEquals(4, total);
+ // Check that there is a single monitor-enter instruction (for the normal inlining
+ // case).
+ assertEquals(
+ 1,
+ testClassSubject
+ .mainMethod()
+ .streamInstructions()
+ .filter(InstructionSubject::isMonitorEnter)
+ .count());
+
+ // Check that there are two monitor-exit instructions (for the normal inlining case,
+ // one for the normal exit and one for the exceptional exit).
+ assertEquals(
+ 2,
+ testClassSubject
+ .mainMethod()
+ .streamInstructions()
+ .filter(InstructionSubject::isMonitorExit)
+ .count());
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(
+ aClassSubject.uniqueMethodWithName("normalInlinedSynchronized"),
+ not(isPresent()));
+
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, not(isPresent()));
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(
+ "A::normalInlinedSynchronized", "B::classInlinedSynchronized");
}
- private static void assertCount(int counts[], String methodName, int expectedCount) {
- int idx = METHOD_NAMES.indexOf(methodName);
- assert idx >= 0;
- assertEquals(expectedCount, counts[idx]);
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // Test normal inlining.
+ new A().normalInlinedSynchronized();
+
+ // Test class-inlining.
+ new B().classInlinedSynchronized();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ synchronized void normalInlinedSynchronized() {
+ System.out.println("A::normalInlinedSynchronized");
+ }
+ }
+
+ static class B {
+
+ @NeverInline
+ synchronized void classInlinedSynchronized() {
+ System.out.println("B::classInlinedSynchronized");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
deleted file mode 100644
index 45d3a5c..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.inliner;
-
-import com.android.tools.r8.NeverInline;
-
-class InlineSynchronizedTestClass {
- private synchronized void normalInlinedSynchronized() {
- System.out.println("InlineSynchronizedTestClass::normalInlinedSynchronized");
- }
-
- public synchronized void classInlinedSynchronized() {
- System.out.println("InlineSynchronizedTestClass::classInlinedSynchronized");
- }
-
- private void normalInlinedControl() {
- System.out.println("InlineSynchronizedTestClass::normalInlinedControl");
- }
-
- @NeverInline
- public void classInlinedControl() {
- System.out.println("InlineSynchronizedTestClass::classInlinedControl");
- }
-
- public static void main(String[] args) {
- // Test normal inlining.
- InlineSynchronizedTestClass testClass = new InlineSynchronizedTestClass();
- testClass.normalInlinedSynchronized();
- testClass.normalInlinedControl();
-
- // Test class-inlining.
- new InlineSynchronizedTestClass().classInlinedSynchronized();
- new InlineSynchronizedTestClass().classInlinedControl();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
new file mode 100644
index 0000000..a8578e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.sync;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.inliner.sync.InlineStaticSynchronizedMethodTest.TestClass.RunnableImpl;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.Thread.State;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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 InlineStaticSynchronizedMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public InlineStaticSynchronizedMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InlineStaticSynchronizedMethodTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::verifySynchronizedMethodsAreInlined)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(
+ "t1 START", "m1 ENTER", "t2 START", "m1 EXIT", "m2 ENTER", "m2 EXIT");
+ }
+
+ private void verifySynchronizedMethodsAreInlined(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(RunnableImpl.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject.uniqueMethodWithName("m1"), not(isPresent()));
+ assertThat(classSubject.uniqueMethodWithName("m2"), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ private static List<String> logs = Collections.synchronizedList(new ArrayList<>());
+
+ private static volatile Thread t1 = new Thread(new RunnableImpl(1));
+ private static volatile Thread t2 = new Thread(new RunnableImpl(2));
+
+ public static void main(String[] args) {
+ System.out.println("t1 START");
+ t1.start();
+ waitUntil(() -> logs.contains("m1 ENTER"));
+ System.out.println("t2 START");
+ t2.start();
+ }
+
+ static void log(String message) {
+ logs.add(message);
+ System.out.println(message);
+ }
+
+ static void waitUntil(BooleanSupplier condition) {
+ while (!condition.getAsBoolean()) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ static class RunnableImpl implements Runnable {
+
+ int which;
+
+ RunnableImpl(int which) {
+ this.which = which;
+ }
+
+ @Override
+ public void run() {
+ if (which == 1) {
+ m1();
+ } else {
+ m2();
+ }
+ }
+
+ static synchronized void m1() {
+ log("m1 ENTER");
+ // Intentionally not using a lambda, since we do not allow inlining of methods with an
+ // invoke-custom instruction. (This only makes a difference for the CF backend.)
+ waitUntil(
+ new BooleanSupplier() {
+
+ @Override
+ public boolean getAsBoolean() {
+ return t2.getState() == State.BLOCKED;
+ }
+ });
+ log("m1 EXIT");
+ }
+
+ static synchronized void m2() {
+ log("m2 ENTER");
+ log("m2 EXIT");
+ }
+ }
+ }
+
+ interface BooleanSupplier {
+
+ boolean getAsBoolean();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 8f4aca9..2cd766b 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -65,8 +65,11 @@
testForR8(parameters.getBackend())
.addProgramFiles(Paths.get(appFileName))
.addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
+ .allowUnusedProguardConfigurationRules()
+ .enableProguardTestOptions()
.setMinApi(parameters.getRuntime())
- .compile().inspector();
+ .compile()
+ .inspector();
inspection.accept(parameters, codeInspector);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index 8585a9c..9e92530 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -31,10 +31,10 @@
private final TestParameters parameters;
private final boolean defaultEnumValueInAnnotation;
- @Parameterized.Parameters(name = "Backend: {0}, default value in annotation: {1}")
+ @Parameterized.Parameters(name = "{0}, default value in annotation: {1}")
public static Collection<Object[]> data() {
return buildParameters(
- getTestParameters().withAllRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
public B137392797(TestParameters parameters, boolean defaultEnumValueInAnnotation) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index ab0fc8b..d8afbb3 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -50,6 +50,10 @@
|| this == R8_CF;
}
+ public boolean isProguard() {
+ return this == PROGUARD5 || this == PROGUARD6 || this == PROGUARD6_THEN_D8;
+ }
+
public boolean isFullModeR8() {
return this == R8
|| this == R8_CF;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 2b6c5db..02c722a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -9,11 +9,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -25,180 +29,231 @@
@RunWith(Parameterized.class)
public class IfOnAccessModifierTest extends ProguardCompatibilityTestBase {
+
private static final List<Class<?>> CLASSES =
- ImmutableList.of(ClassForIf.class, ClassForSubsequent.class, MainForAccessModifierTest.class);
+ ImmutableList.of(
+ ClassForIf.class,
+ ClassForSubsequent.class,
+ MainForAccessModifierTest.class,
+ NeverClassInline.class,
+ NeverInline.class);
+ private final TestParameters parameters;
private final Shrinker shrinker;
- private final MethodSignature nonPublicMethod;
- private final MethodSignature publicMethod;
- public IfOnAccessModifierTest(Shrinker shrinker) {
+ public IfOnAccessModifierTest(TestParameters parameters, Shrinker shrinker) {
+ this.parameters = parameters;
this.shrinker = shrinker;
- nonPublicMethod = new MethodSignature("nonPublicMethod", "void", ImmutableList.of());
- publicMethod = new MethodSignature("publicMethod", "void", ImmutableList.of());
}
- @Parameters(name = "shrinker: {0}")
- public static Collection<Object> data() {
- return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
+ @Parameters(name = "{0}, shrinker: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().build(),
+ ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8));
+ }
+
+ private TestShrinkerBuilder<?, ?, ?, ?, ?> getTestBuilder() {
+ switch (shrinker) {
+ case PROGUARD6:
+ assertTrue(parameters.isCfRuntime());
+ return testForProguard();
+ case R8:
+ return testForR8(parameters.getBackend())
+ .allowUnusedProguardConfigurationRules()
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations();
+ default:
+ throw new Unreachable();
+ }
}
@Test
public void ifOnPublic_noPublicClassForIfRule() throws Exception {
- List<String> config = ImmutableList.of(
- "-repackageclasses 'top'",
- "-keep class **.Main* {",
- " public static void callIfNonPublic();",
- "}",
- "-if public class **.ClassForIf {",
- " <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, not(isPresent()));
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if public class **.ClassForIf {",
+ " <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, not(isPresent()));
+ });
}
@Test
public void ifOnNonPublic_keepOnPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfNonPublic();",
- "}",
- "-if class **.ClassForIf {",
- " !public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " !public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ });
}
@Test
public void ifOnNonPublic_keepOnNonPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfNonPublic();",
- "}",
- "-if class **.ClassForIf {",
- " !public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " !public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfNonPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " !public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " !public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ });
}
@Test
public void ifOnPublic_keepOnPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfPublic();",
- "}",
- "-if class **.ClassForIf {",
- " public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ });
}
@Test
public void ifOnPublic_keepOnNonPublic() throws Exception {
- List<String> config = ImmutableList.of(
- "-printmapping",
- "-repackageclasses 'top'",
- "-allowaccessmodification",
- "-keep class **.Main* {",
- " public static void callIfPublic();",
- "}",
- "-if class **.ClassForIf {",
- " public <methods>;",
- "}",
- "-keep,allowobfuscation class **.ClassForSubsequent {",
- " !public <methods>;",
- "}"
- );
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
- ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, isPresent());
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, not(isPresent()));
+ assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
- classSubject = codeInspector.clazz(ClassForSubsequent.class);
- assertThat(classSubject, isPresent());
- methodSubject = classSubject.method(publicMethod);
- assertThat(methodSubject, not(isPresent()));
- methodSubject = classSubject.method(nonPublicMethod);
- assertThat(methodSubject, isPresent());
- assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ getTestBuilder()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(
+ "-repackageclasses 'top'",
+ "-allowaccessmodification",
+ "-keep class **.Main* {",
+ " public static void callIfPublic();",
+ "}",
+ "-if class **.ClassForIf {",
+ " public <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.ClassForSubsequent {",
+ " !public <methods>;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, not(isPresent()));
+
+ classSubject = inspector.clazz(ClassForSubsequent.class);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+ assertThat(methodSubject, not(isPresent()));
+ methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+ assertThat(methodSubject, isPresent());
+ assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
index 1b5bd75..49d75f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
@@ -3,14 +3,20 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
class ClassForIf {
ClassForIf() {
}
+ @NeverInline
synchronized void nonPublicMethod() {
System.out.println("ClassForIf::nonPublicMethod");
}
+ @NeverInline
synchronized public void publicMethod() {
System.out.println("ClassForIf::publicMethod");
}
@@ -20,10 +26,12 @@
ClassForSubsequent() {
}
+ @NeverInline
synchronized void nonPublicMethod() {
System.out.println("ClassForSubsequent::nonPublicMethod");
}
+ @NeverInline
synchronized public void publicMethod() {
System.out.println("ClassForSubsequent::publicMethod");
}