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();
   }