Version 1.5.51 Cherry pick: Update code inspector CL: https://r8-review.googlesource.com/c/r8/+/39674 Cherry pick: Add support for -keepparameternames CL: https://r8-review.googlesource.com/c/r8/+/39675 Cherry pick: Change handling of -keepparameternames to only take effect for kept methods CL: https://r8-review.googlesource.com/c/r8/+/39780 Bug: 132549918 Change-Id: I025137f1a70292ac5c94fc996a08b309d3ef261e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java index f244e33..14b53f4 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.5.50"; + public static final String LABEL = "1.5.51"; private Version() { }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java index f1f3eca..38b5e1a 100644 --- a/src/main/java/com/android/tools/r8/graph/JarCode.java +++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -18,13 +18,17 @@ import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.OffOrAuto; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.BitSet; import java.util.Iterator; +import java.util.Map; import java.util.function.BiFunction; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -34,6 +38,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TryCatchBlockNode; import org.objectweb.asm.util.Textifier; @@ -148,9 +153,15 @@ Position callerPosition) { triggerDelayedParsingIfNeccessary(); if (!keepLocals(encodedMethod, appView.options())) { + // If the locals are not kept, we might still need a bit of locals information to satisfy + // -keepparameternames for R8. + Map<Integer, DebugLocalInfo> parameterInfo = collectParameterInfo(encodedMethod, appView); // We strip locals here because we will not be able to recover from invalid info. - node.localVariables.clear(); - return internalBuild(context, encodedMethod, appView, generator, callerPosition); + if (canStripLocals(encodedMethod, appView)) { + node.localVariables.clear(); + } + return internalBuild( + context, encodedMethod, appView, generator, callerPosition, parameterInfo); } else { return internalBuildWithLocals(context, encodedMethod, appView, generator, callerPosition); } @@ -163,11 +174,13 @@ ValueNumberGenerator generator, Position callerPosition) { try { - return internalBuild(context, encodedMethod, appView, generator, callerPosition); + return internalBuild( + context, encodedMethod, appView, generator, callerPosition, IRCode.NO_PARAMETER_INFO); } catch (InvalidDebugInfoException e) { appView.options().warningInvalidDebugInfo(encodedMethod, origin, e); node.localVariables.clear(); - return internalBuild(context, encodedMethod, appView, generator, callerPosition); + return internalBuild( + context, encodedMethod, appView, generator, callerPosition, IRCode.NO_PARAMETER_INFO); } } @@ -181,13 +194,75 @@ return false; } + private Map<Integer, DebugLocalInfo> collectParameterInfo( + DexEncodedMethod encodedMethod, AppView<?> appView) { + if (!appView.options().hasProguardConfiguration() + || !appView.options().getProguardConfiguration().isKeepParameterNames()) { + return IRCode.NO_PARAMETER_INFO; + } + // The enqueuer might build IR to trace reflective behaviour. At that point liveness is not + // known, so be conservative with collection parameter name information. + if (appView.appInfo().hasLiveness() + && !appView.appInfo().withLiveness().isPinned(encodedMethod.method)) { + return IRCode.NO_PARAMETER_INFO; + } + // Collect the local slots used for parameters. + BitSet localSlotsForParameters = new BitSet(0); + int nextLocalSlotsForParameters = 0; + if (!encodedMethod.isStatic()) { + localSlotsForParameters.set(nextLocalSlotsForParameters++); + } + for (DexType type : encodedMethod.method.proto.parameters.values) { + localSlotsForParameters.set(nextLocalSlotsForParameters); + nextLocalSlotsForParameters += type.isLongType() || type.isDoubleType() ? 2 : 1; + } + // Collect the first piece of local variable information for each argument local slot, + // assuming that that does actually describe the parameter (name, type and possibly + // signature). + DexItemFactory factory = appView.options().itemFactory; + Map<Integer, DebugLocalInfo> parameterInfo = + new Int2ReferenceArrayMap<>(localSlotsForParameters.cardinality()); + for (Object o : node.localVariables) { + LocalVariableNode node = (LocalVariableNode) o; + if (node.index < nextLocalSlotsForParameters + && localSlotsForParameters.get(node.index) + && !parameterInfo.containsKey(node.index)) { + parameterInfo.put( + node.index, + new DebugLocalInfo( + factory.createString(node.name), + factory.createType(factory.createString(node.desc)), + node.signature == null ? null : factory.createString(node.signature))); + } + } + return parameterInfo; + } + + private boolean canStripLocals(DexEncodedMethod encodedMethod, AppView<?> appView) { + // If not keeping parameter names the locals can always be stripped. + if (!appView.options().hasProguardConfiguration() + || !appView.options().getProguardConfiguration().isKeepParameterNames()) { + return true; + } + // The enqueuer might build IR to trace reflective behaviour. At that point liveness is not + // known, so locals cannot be stripped as IR will built again in the IR converter. + if (appView.appInfo().hasLiveness() + && !appView.appInfo().withLiveness().isPinned(encodedMethod.method)) { + return true; + } + return false; + } + private IRCode internalBuild( DexEncodedMethod context, DexEncodedMethod encodedMethod, AppView<?> appView, ValueNumberGenerator generator, - Position callerPosition) { - assert node.localVariables.isEmpty() || keepLocals(encodedMethod, appView.options()); + Position callerPosition, + Map<Integer, DebugLocalInfo> parameterInfo) { + assert node.localVariables.isEmpty() + || keepLocals(encodedMethod, appView.options()) + || !canStripLocals(encodedMethod, appView); JarSourceCode source = new JarSourceCode( method.holder, @@ -196,7 +271,7 @@ appView.graphLense().getOriginalMethodSignature(encodedMethod.method), callerPosition); IRBuilder builder = new IRBuilder(encodedMethod, appView, source, origin, generator); - return builder.build(context); + return builder.build(context, parameterInfo); } @Override @@ -286,17 +361,19 @@ } private void parseCode(ReparseContext context, boolean useJsrInliner) { - // If the keep attributes do not specify keeping LocalVariableTable, LocalVariableTypeTable or - // LineNumberTable, then we can skip parsing all the debug related attributes during code read. - // If the method is reachability sensitive we have to include debug information in order - // to get locals information which we need to extend the live ranges of locals for their - // entire scope. + // If -keepparameternames is not specified and the keep attributes do not specify keeping + // either of LocalVariableTable, LocalVariableTypeTable or LineNumberTable, then we can skip + // parsing all the debug related attributes during code read. If the method is reachability + // sensitive we have to include debug information in order to get locals information which we + // need to extend the live ranges of locals for their entire scope. int parsingOptions = ClassReader.SKIP_FRAMES; - if (application.options.getProguardConfiguration() != null) { + ProguardConfiguration configuration = application.options.getProguardConfiguration(); + if (configuration != null && !configuration.isKeepParameterNames()) { ProguardKeepAttributes keep = application.options.getProguardConfiguration().getKeepAttributes(); - if (!keep.localVariableTable + if (!application.options.getProguardConfiguration().isKeepParameterNames() + && !keep.localVariableTable && !keep.localVariableTypeTable && !keep.lineNumberTable && !reachabilitySensitive) {
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 ea328b0..f427047 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
@@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -84,6 +85,8 @@ // When numbering instructions we number instructions only with even numbers. This allows us to // use odd instruction numbers for the insertion of moves during spilling. public static final int INSTRUCTION_NUMBER_DELTA = 2; + public static final Map<Integer, DebugLocalInfo> NO_PARAMETER_INFO = + new Int2ReferenceArrayMap<>(0); public final DexEncodedMethod method; @@ -107,6 +110,8 @@ public final Origin origin; + public final Map<Integer, DebugLocalInfo> parameterInfo; + public IRCode( InternalOptions options, DexEncodedMethod method, @@ -115,7 +120,8 @@ boolean hasDebugPositions, boolean hasMonitorInstruction, boolean hasConstString, - Origin origin) { + Origin origin, + Map<Integer, DebugLocalInfo> parameterInfo) { assert options != null; this.options = options; this.method = method; @@ -125,8 +131,10 @@ this.hasMonitorInstruction = hasMonitorInstruction; this.hasConstString = hasConstString; this.origin = origin; + this.parameterInfo = parameterInfo; // TODO(zerny): Remove or update this property now that all instructions have positions. allThrowingInstructionsHavePositions = computeAllThrowingInstructionsHavePositions(); + assert parameterInfo != null; } public void copyMetadataFromInlinee(IRCode inlinee) {
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 bfc8352..ffe8806 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
@@ -268,6 +268,7 @@ CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK; boolean previousFallthrough = false; + boolean firstBlock = true; do { CatchHandlers<BasicBlock> handlers = block.getCatchHandlers(); if (!tryCatchHandlers.equals(handlers)) { @@ -309,6 +310,11 @@ assert !block.exit().isReturn() || stackHeightTracker.isEmpty(); + if (firstBlock) { + addParameterNamesIfRequired(block, code.parameterInfo); + firstBlock = false; + } + block = nextBlock; previousFallthrough = fallthrough; } while (block != null); @@ -625,6 +631,24 @@ } } + private void addParameterNamesIfRequired( + BasicBlock block, Map<Integer, DebugLocalInfo> parameterInfo) { + // Don't add this information if the code already have full debug information. + if (appView.options().debug) { + return; + } + + if (code.parameterInfo != IRCode.NO_PARAMETER_INFO) { + for (Map.Entry<Integer, DebugLocalInfo> entries : parameterInfo.entrySet()) { + LocalVariableInfo localVariableInfo = + new LocalVariableInfo(entries.getKey(), entries.getValue(), getLabel(block)); + CfLabel endLabel = ensureLabel(); + localVariableInfo.setEnd(endLabel); + localVariablesTable.add(localVariableInfo); + } + } + } + // Callbacks public CfLabel getLabel(BasicBlock target) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index cbddf03..6d9aa3b 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -496,6 +496,19 @@ * @return The list of basic blocks. First block is the main entry. */ public IRCode build(DexEncodedMethod context) { + return build(context, IRCode.NO_PARAMETER_INFO); + } + + /** + * Build the high-level IR in SSA form. + * + * @param context Under what context this IRCode is built. Either the current method or caller. + * @param parameterInfo Parameter information to include in the output. Pass <code> + * IRCode.NO_PARAMETER_INFO</code> if this is not relevant. This information is only used if + * the generated code does not contain any debug information. + * @return The list of basic blocks. First block is the main entry. + */ + public IRCode build(DexEncodedMethod context, Map<Integer, DebugLocalInfo> parameterInfo) { assert source != null; source.setUp(); @@ -596,7 +609,8 @@ hasDebugPositions, hasMonitorInstruction, hasConstString, - origin); + origin, + parameterInfo); // Verify critical edges are split so we have a place to insert phi moves if necessary. assert ir.verifySplitCriticalEdges();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index 2d3100c..7d3b26e 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -62,6 +62,10 @@ public final DexItemFactory itemFactory; + public boolean hasProguardConfiguration() { + return proguardConfiguration != null; + } + public ProguardConfiguration getProguardConfiguration() { return proguardConfiguration; }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java index 97cb2b2..cd8d101 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -141,7 +141,8 @@ false, false, false, - Origin.unknown()); + Origin.unknown(), + IRCode.NO_PARAMETER_INFO); PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appView, code)); // Check that all four constant number instructions remain.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java index 48a8246..413efce 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -82,7 +82,8 @@ false, false, false, - Origin.unknown()); + Origin.unknown(), + IRCode.NO_PARAMETER_INFO); CodeRewriter.collapseTrivialGotos(null, code); assertTrue(code.entryBlock().isTrivialGoto()); assertTrue(blocks.contains(block0)); @@ -169,7 +170,8 @@ false, false, false, - Origin.unknown()); + Origin.unknown(), + IRCode.NO_PARAMETER_INFO); CodeRewriter.collapseTrivialGotos(null, code); assertTrue(block0.getInstructions().get(1).isIf()); assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock());
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java new file mode 100644 index 0000000..ea203f1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
@@ -0,0 +1,273 @@ +// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking.keepparameternames; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.KeepUnusedArguments; +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.TestShrinkerBuilder; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.LocalVariableTable; +import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.android.tools.r8.utils.codeinspector.TypeSubject; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class KeepParameterNamesTest extends TestBase { + + private final TestParameters parameters; + private final boolean keepParameterNames; + private final boolean enableMinification; + + @Parameterized.Parameters(name = "{0}, keepparameternames {1}, minification {2}") + public static Collection<Object[]> data() { + return buildParameters( + getTestParameters().withCfRuntimes().build(), BooleanUtils.values(), BooleanUtils.values()); + } + + public KeepParameterNamesTest( + TestParameters parameters, boolean keepParameterNames, boolean enableMinification) { + this.parameters = parameters; + this.keepParameterNames = keepParameterNames; + this.enableMinification = enableMinification; + } + + private void checkLocalVariable( + LocalVariableTableEntry localVariable, + int index, + String name, + TypeSubject type, + String signature) { + assertEquals(index, localVariable.index); + assertEquals(name, localVariable.name); + assertEquals(type, localVariable.type); + assertEquals(signature, localVariable.signature); + } + + private void checkLocalVariableTable(CodeInspector inspector) { + ClassSubject classSubject = inspector.clazz(Api.class); + assertThat(classSubject, isPresent()); + + MethodSubject method; + method = classSubject.uniqueMethodWithName("apiNoArgs"); + assertThat(method, isPresent()); + assertEquals(keepParameterNames, method.hasLocalVariableTable()); + if (keepParameterNames) { + LocalVariableTable localVariableTable = method.getLocalVariableTable(); + assertEquals(1, localVariableTable.size()); + assertEquals("this", localVariableTable.get(0).name); + assertTrue(localVariableTable.get(0).type.is(classSubject)); + } else { + assertTrue(method.getLocalVariableTable().isEmpty()); + } + + method = classSubject.uniqueMethodWithName("apiNoArgsStatic"); + assertThat(method, isPresent()); + assertFalse(method.hasLocalVariableTable()); + assertTrue(method.getLocalVariableTable().isEmpty()); + + method = classSubject.uniqueMethodWithName("api1"); + assertThat(method, isPresent()); + assertEquals(keepParameterNames, method.hasLocalVariableTable()); + if (keepParameterNames) { + LocalVariableTable localVariableTable = method.getLocalVariableTable(); + assertEquals(3, localVariableTable.size()); + checkLocalVariable( + localVariableTable.get(0), + 0, + "this", + classSubject.asFoundClassSubject().asTypeSybject(), + null); + checkLocalVariable( + localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("int"), null); + checkLocalVariable( + localVariableTable.get(2), + 2, + "parameter2", + inspector.getTypeSubject("java.lang.String"), + null); + } else { + assertTrue(method.getLocalVariableTable().isEmpty()); + } + + method = classSubject.uniqueMethodWithName("api2"); + assertThat(method, isPresent()); + assertEquals(keepParameterNames, method.hasLocalVariableTable()); + if (keepParameterNames) { + LocalVariableTable localVariableTable = method.getLocalVariableTable(); + assertEquals(3, localVariableTable.size()); + checkLocalVariable( + localVariableTable.get(0), + 0, + "this", + classSubject.asFoundClassSubject().asTypeSybject(), + null); + checkLocalVariable( + localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("long"), null); + checkLocalVariable( + localVariableTable.get(2), 3, "parameter2", inspector.getTypeSubject("double"), null); + } else { + assertTrue(method.getLocalVariableTable().isEmpty()); + } + + method = classSubject.uniqueMethodWithName("api3"); + assertThat(method, isPresent()); + assertEquals(keepParameterNames, method.hasLocalVariableTable()); + if (keepParameterNames) { + LocalVariableTable localVariableTable = method.getLocalVariableTable(); + assertEquals(3, localVariableTable.size()); + checkLocalVariable( + localVariableTable.get(0), + 0, + "this", + classSubject.asFoundClassSubject().asTypeSybject(), + null); + checkLocalVariable( + localVariableTable.get(1), + 1, + "parameter1", + inspector.getTypeSubject("java.util.List"), + "Ljava/util/List<Ljava/lang/String;>;"); + checkLocalVariable( + localVariableTable.get(2), + 2, + "parameter2", + inspector.getTypeSubject("java.util.Map"), + "Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;"); + } else { + assertTrue(method.getLocalVariableTable().isEmpty()); + } + } + + @Test + public void testApiKept() throws Exception { + String expectedOutput = + StringUtils.lines( + "In Api.apiNoArgs", + "In Api.apiNoArgsStatic", + "In Api.api1", + "In Api.api2", + "In Api.api3"); + testForR8(parameters.getBackend()) + .addInnerClasses(KeepParameterNamesTest.class) + .addKeepMainRule(TestClass.class) + .addKeepRules("-keep class " + Api.class.getTypeName() + "{ api*(...); }") + .minification(enableMinification) + .apply(this::configureKeepParameterNames) + .compile() + .inspect(this::checkLocalVariableTable) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(expectedOutput); + } + + private void checkLocalVariableTableNotKept(CodeInspector inspector) { + ClassSubject classSubject = inspector.clazz(Api.class); + assertThat(classSubject, isPresent()); + assertEquals(enableMinification, classSubject.isRenamed()); + + MethodSubject method; + for (String name : new String[] {"apiNoArgs", "apiNoArgsStatic", "api1", "api2", "api3"}) { + method = classSubject.uniqueMethodWithName(name); + assertThat(method, isPresent()); + assertEquals(enableMinification, method.isRenamed()); + assertFalse(method.hasLocalVariableTable()); + assertTrue(method.getLocalVariableTable().isEmpty()); + } + } + + @Test + public void testApiNotKept() throws Exception { + String expectedOutput = + StringUtils.lines( + "In Api.apiNoArgs", + "In Api.apiNoArgsStatic", + "In Api.api1", + "In Api.api2", + "In Api.api3"); + testForR8(parameters.getBackend()) + .enableClassInliningAnnotations() + .enableInliningAnnotations() + .enableUnusedArgumentAnnotations() + .addInnerClasses(KeepParameterNamesTest.class) + .addKeepMainRule(TestClass.class) + .minification(enableMinification) + .apply(this::configureKeepParameterNames) + .compile() + .inspect(this::checkLocalVariableTableNotKept) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(expectedOutput); + } + + private void configureKeepParameterNames(TestShrinkerBuilder builder) { + if (keepParameterNames) { + builder.addKeepRules("-keepparameternames"); + } + } + + static class TestClass { + + public static void main(String[] args) { + Api api = new Api(); + api.apiNoArgs(); + Api.apiNoArgsStatic(); + api.api1(0, "Hello, world!"); + api.api2(0, 0.0d); + api.api3(null, null); + } + } + + @NeverClassInline + static class Api { + @NeverInline + void apiNoArgs() { + System.out.println("In Api.apiNoArgs"); + } + + @NeverInline + static void apiNoArgsStatic() { + System.out.println("In Api.apiNoArgsStatic"); + } + + @NeverInline + @KeepUnusedArguments + void api1(int parameter1, String parameter2) { + try { + // Reflective behaviour to trigger IR building in the enqueuer. + Class.forName("NotFound"); + } catch (Exception e) { + } + System.out.println("In Api.api1"); + } + + @NeverInline + @KeepUnusedArguments + void api2(long parameter1, double parameter2) { + System.out.println("In Api.api2"); + } + + @NeverInline + @KeepUnusedArguments + void api3(List<String> parameter1, Map<String, Object> parameter2) { + System.out.println("In Api.api3"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java index fdde9c2..860c36f 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -119,6 +119,11 @@ } @Override + public LocalVariableTable getLocalVariableTable() { + return null; + } + + @Override public boolean hasLocalVariableTable() { throw new Unreachable("Cannot determine if an absent method has a local variable table"); }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java index 2d30f24..258fb43 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -163,6 +163,11 @@ return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string)); } + public TypeSubject getTypeSubject(String string) { + return new TypeSubject( + this, dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(string))); + } + String mapType(Map<String, String> mapping, String typeName) { final String ARRAY_POSTFIX = "[]"; int arrayCount = 0;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java index 6b3cf1e..74ba390 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -20,7 +20,6 @@ import com.android.tools.r8.naming.signature.GenericSignatureParser; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.StringUtils; -import java.util.Arrays; import java.util.List; import java.util.function.Consumer; @@ -293,4 +292,8 @@ public String toString() { return dexClass.toSourceString(); } + + public TypeSubject asTypeSybject() { + return new TypeSubject(codeInspector, getDexClass().type); + } }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java index 7770bd5..6b47139 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.CfCode.LocalVariableInfo; import com.android.tools.r8.graph.Code; import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexCode; @@ -30,6 +31,8 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry; +import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.Arrays; @@ -286,6 +289,46 @@ } @Override + public LocalVariableTable getLocalVariableTable() { + Code code = getMethod().getCode(); + if (code.isDexCode()) { + return getDexLocalVariableTable(code.asDexCode()); + } + if (code.isCfCode()) { + return getCfLocalVariableTable(code.asCfCode()); + } + if (code.isJarCode()) { + return getJarLocalVariableTable(code.asJarCode()); + } + throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName()); + } + + private LocalVariableTable getJarLocalVariableTable(JarCode code) { + throw new Unimplemented("No support for inspecting the line number table for JarCode"); + } + + private LocalVariableTable getCfLocalVariableTable(CfCode code) { + ImmutableList.Builder<LocalVariableTableEntry> builder = ImmutableList.builder(); + for (LocalVariableInfo localVariable : code.getLocalVariables()) { + builder.add( + new LocalVariableTableEntry( + localVariable.getIndex(), + localVariable.getLocal().name.toString(), + new TypeSubject(codeInspector, localVariable.getLocal().type), + localVariable.getLocal().signature == null + ? null + : localVariable.getLocal().signature.toString(), + new CfInstructionSubject(localVariable.getStart()), + new CfInstructionSubject(localVariable.getEnd()))); + } + return new LocalVariableTable(builder.build()); + } + + private LocalVariableTable getDexLocalVariableTable(DexCode code) { + throw new Unimplemented("No support for inspecting the line number table for DexCode"); + } + + @Override public String toString() { return dexMethod.toSourceString(); }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/LocalVariableTable.java b/src/test/java/com/android/tools/r8/utils/codeinspector/LocalVariableTable.java new file mode 100644 index 0000000..fefc4c6 --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/LocalVariableTable.java
@@ -0,0 +1,54 @@ +// 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.utils.codeinspector; + +import java.util.List; + +public class LocalVariableTable { + public static class LocalVariableTableEntry { + public final int index; + public final String name; + public final TypeSubject type; + public final String signature; + public final InstructionSubject start; + public final InstructionSubject end; + + LocalVariableTableEntry( + int index, + String name, + TypeSubject type, + String signature, + InstructionSubject start, + InstructionSubject end) { + this.index = index; + this.name = name; + this.type = type; + this.signature = signature; + this.start = start; + this.end = end; + } + } + + private final List<LocalVariableTableEntry> localVariableTable; + + public LocalVariableTable(List<LocalVariableTableEntry> localVariableTable) { + this.localVariableTable = localVariableTable; + } + + public int size() { + return localVariableTable.size(); + } + + public boolean isEmpty() { + return localVariableTable.isEmpty(); + } + + public LocalVariableTableEntry get(int i) { + return localVariableTable.get(i); + } + + public List<LocalVariableTableEntry> getEntries() { + return localVariableTable; + } +}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java index 0edb4e9..0dc81f1 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -68,6 +68,8 @@ public abstract LineNumberTable getLineNumberTable(); + public abstract LocalVariableTable getLocalVariableTable(); + public abstract boolean hasLocalVariableTable(); public Stream<InstructionSubject> streamInstructions() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java index 717c98d..ae59c47 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
@@ -44,6 +44,24 @@ return dexType == type.getDexClass().type; } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof TypeSubject) { + TypeSubject o = (TypeSubject) other; + assert codeInspector == o.codeInspector; + return codeInspector == o.codeInspector && dexType == o.dexType; + } + return false; + } + + @Override + public int hashCode() { + return dexType.hashCode(); + } + public String toString() { return dexType.toSourceString(); }