Merge "Move dup to block with store in case of linear flow"
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 72e2887..8c16d04 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
-import static com.android.tools.r8.ir.optimize.CodeRewriter.checksNullReceiverBeforeSideEffect;
+import static com.android.tools.r8.ir.optimize.CodeRewriter.checksNullBeforeSideEffect;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -35,7 +35,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
@@ -880,10 +879,6 @@
     printC1VisualizerHeader(method);
     String previous = printMethod(code, "Initial IR (SSA)", null);
 
-    if (method.getCode() != null && method.getCode().isJarCode()) {
-      computeKotlinNonNullParamHints(feedback, method, code);
-    }
-
     if (options.canHaveArtStringNewInitBug()) {
       CodeRewriter.ensureDirectStringNewToInit(code);
     }
@@ -1110,11 +1105,10 @@
 
     codeRewriter.identifyReturnsArgument(method, code, feedback);
     if (options.enableInlining && inliner != null) {
-      codeRewriter.identifyInvokeSemanticsForInlining(method, code, feedback);
+      codeRewriter.identifyInvokeSemanticsForInlining(method, code, graphLense(), feedback);
     }
 
-    // If hints from Kotlin metadata or use of Kotlin Intrinsics were not available, track usage of
-    // parameters and compute their nullability and possibility of NPE.
+    // Track usage of parameters and compute their nullability and possibility of NPE.
     if (method.getOptimizationInfo().getNonNullParamOrThrow() == null) {
       computeNonNullParamHints(feedback, method, code);
     }
@@ -1159,33 +1153,25 @@
     finalizeIR(method, code, feedback);
   }
 
-  private void computeKotlinNonNullParamHints(
-      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+  private void computeNonNullParamHints(
+    OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     List<Value> arguments = code.collectArguments(true);
     BitSet paramsCheckedForNull = new BitSet();
-    DexMethod checkParameterIsNotNull =
-        code.options.itemFactory.kotlin.intrinsics.checkParameterIsNotNull;
     for (int index = 0; index < arguments.size(); index++) {
       Value argument = arguments.get(index);
-      for (Instruction user : argument.uniqueUsers()) {
-        // To enforce parameter non-null requirement Kotlin uses intrinsic:
-        //    kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
-        //
-        // with the following we simply look if the parameter is ever passed
-        // to the mentioned intrinsic as the first argument. We do it only for
-        // code coming from Java classfile, since after the method is rewritten
-        // by R8 this call gets inlined.
-        if (!user.isInvokeStatic()) {
-          continue;
-        }
-        InvokeMethod invoke = user.asInvokeMethod();
-        DexMethod invokedMethod =
-            graphLense().getOriginalMethodSignature(invoke.getInvokedMethod());
-        // TODO(b/121377154): Make sure there is no other side-effect before argument's null check.
-        // E.g., is this the first method invocation inside the method?
-        if (invokedMethod == checkParameterIsNotNull && user.inValues().indexOf(argument) == 0) {
-          paramsCheckedForNull.set(index);
-        }
+      // This handles cases where the parameter is checked via Kotlin Intrinsics:
+      //
+      //   kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
+      //
+      // or its inlined version:
+      //
+      //   if (param != null) return;
+      //   invoke-static throwParameterIsNullException(msg)
+      //
+      // or some other variants, e.g., throw null or NPE after the direct null check.
+      if (argument.isUsed()
+          && checksNullBeforeSideEffect(code, appInfo, graphLense(), argument)) {
+        paramsCheckedForNull.set(index);
       }
     }
     if (paramsCheckedForNull.length() > 0) {
@@ -1213,28 +1199,6 @@
     }
   }
 
-  // TODO(b/121377154): Consider merging compute(Kotlin)?NonNullParamHints into one.
-  private void computeNonNullParamHints(
-    OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    List<Value> arguments = code.collectArguments(true);
-    BitSet paramsCheckedForNull = new BitSet();
-    for (int index = 0; index < arguments.size(); index++) {
-      Value argument = arguments.get(index);
-      if (argument.isUsed()
-          && checksNullReceiverBeforeSideEffect(code, appInfo.dexItemFactory, argument)) {
-        paramsCheckedForNull.set(index);
-      }
-      // The above one only catches something like:
-      //   if (param != null) return;
-      //   invoke-static throwParameterIsNullException(msg)
-      // This is good enough to handle checkParameterIsNotNull(param, msg), which is generated by
-      // kotlinc for arguments that are not nullable.
-    }
-    if (paramsCheckedForNull.length() > 0) {
-      feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
-    }
-  }
-
   private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
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 2244653..40a7692 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
@@ -38,6 +38,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
@@ -1003,7 +1004,7 @@
   }
 
   public void identifyInvokeSemanticsForInlining(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+      DexEncodedMethod method, IRCode code, GraphLense graphLense, OptimizationFeedback feedback) {
     if (method.isStatic()) {
       // Identifies if the method preserves class initialization after inlining.
       feedback.markTriggerClassInitBeforeAnySideEffect(method,
@@ -1013,7 +1014,7 @@
       final Value receiver = code.getThis();
       feedback.markCheckNullReceiverBeforeAnySideEffect(method,
           receiver.isUsed()
-              && checksNullReceiverBeforeSideEffect(code, appInfo.dexItemFactory, receiver));
+              && checksNullBeforeSideEffect(code, appInfo, graphLense, receiver));
     }
   }
 
@@ -1342,55 +1343,66 @@
   }
 
   /**
-   * Returns true if the given code unconditionally throws if receiver is null before any other
-   * side effect instruction.
+   * Returns true if the given code unconditionally throws if value is null before any other side
+   * effect instruction.
    *
    * Note: we do not track phis so we may return false negative. This is a conservative approach.
    */
-  public static boolean checksNullReceiverBeforeSideEffect(
-      IRCode code, DexItemFactory factory, Value receiver) {
-    Wrapper<DexMethod> throwParamIsNullException =
-        MethodSignatureEquivalence.get()
-            .wrap(factory.kotlin.intrinsics.throwParameterIsNullException);
+  public static boolean checksNullBeforeSideEffect(
+      IRCode code, AppInfo appInfo, GraphLense graphLense, Value value) {
     return alwaysTriggerExpectedEffectBeforeAnythingElse(
         code,
         (instr, it) -> {
           BasicBlock currentBlock = instr.getBlock();
-          // If the code explicitly checks the nullability of receiver, we should visit the next
-          // block that corresponds to the null receiver where NPE semantic could be preserved.
-          if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, receiver)) {
+          // If the code explicitly checks the nullability of the value, we should visit the next
+          // block that corresponds to the null value where NPE semantic could be preserved.
+          if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
             return InstructionEffect.CONDITIONAL_EFFECT;
           }
-          // Kotlin specific way of throwing NPE: throwParameterIsNullException.
-          // Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
-          // receiver.
-          if (instr.isInvokeStatic()) {
-            DexMethod method = instr.asInvokeStatic().getInvokedMethod();
-            if (MethodSignatureEquivalence.get().wrap(method).equals(throwParamIsNullException)) {
+          if (isKotlinNullCheck(appInfo, graphLense, instr, value)) {
+            DexMethod invokedMethod = instr.asInvokeStatic().getInvokedMethod();
+            // Kotlin specific way of throwing NPE: throwParameterIsNullException.
+            // Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
+            // the value.
+            if (invokedMethod.name
+                == appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException.name) {
               // We found a NPE (or similar exception) throwing code.
-              // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on receiver.
+              // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on the value.
               for (BasicBlock predecessor : currentBlock.getPredecessors()) {
                 Instruction last =
                     predecessor.listIterator(predecessor.getInstructions().size()).previous();
-                if (isNullCheck(last, receiver)) {
+                if (isNullCheck(last, value)) {
                   return InstructionEffect.DESIRED_EFFECT;
                 }
               }
+              // Hitting here means that this call might be used for other parameters. If we don't
+              // bail out, it will be regarded as side effects for the current value.
+              return InstructionEffect.NO_EFFECT;
+            } else {
+              // Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
+              assert invokedMethod.name
+                  == appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull.name;
+              return InstructionEffect.DESIRED_EFFECT;
             }
           }
-          if (isInstantiationOfNullPointerException(instr, it, factory)) {
+          if (isInstantiationOfNullPointerException(instr, it, appInfo.dexItemFactory)) {
             it.next(); // Skip call to NullPointerException.<init>.
             return InstructionEffect.NO_EFFECT;
-          } else if (instr.throwsNpeIfValueIsNull(receiver, factory)) {
+          } else if (instr.throwsNpeIfValueIsNull(value, appInfo.dexItemFactory)) {
             // In order to preserve NPE semantic, the exception must not be caught by any handler.
             // Therefore, we must ignore this instruction if it is covered by a catch handler.
             // Note: this is a conservative approach where we consider that any catch handler could
             // catch the exception, even if it cannot catch a NullPointerException.
             if (!currentBlock.hasCatchHandlers()) {
-              // We found a NPE check on receiver.
+              // We found a NPE check on the value.
               return InstructionEffect.DESIRED_EFFECT;
             }
           } else if (instructionHasSideEffects(instr)) {
+            // If the current instruction is const-string, this could load the parameter name.
+            // Just make sure it is indeed not throwing.
+            if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
+              return InstructionEffect.NO_EFFECT;
+            }
             // We found a side effect before a NPE check.
             return InstructionEffect.OTHER_EFFECT;
           }
@@ -1398,9 +1410,36 @@
         });
   }
 
-  private static boolean isNullCheck(Instruction instr, Value receiver) {
+  // Note that this method may have false positives, since the application could in principle
+  // declare a method called checkParameterIsNotNull(parameter, message) or
+  // throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
+  private static boolean isKotlinNullCheck(
+      AppInfo appInfo, GraphLense graphLense, Instruction instr, Value value) {
+    if (!instr.isInvokeStatic()) {
+      return false;
+    }
+    // We need to strip the holder, since Kotlin adds different versions of null-check machinery,
+    // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
+    MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
+    Wrapper<DexMethod> checkParameterIsNotNull =
+        wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull);
+    Wrapper<DexMethod> throwParamIsNullException =
+        wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException);
+    DexMethod invokedMethod =
+        graphLense.getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
+    Wrapper<DexMethod> methodWrap = wrapper.wrap(invokedMethod);
+    if (methodWrap.equals(throwParamIsNullException)
+        || (methodWrap.equals(checkParameterIsNotNull) && instr.inValues().get(0).equals(value))) {
+      if (invokedMethod.getHolder().getPackageDescriptor().startsWith("kotlin")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean isNullCheck(Instruction instr, Value value) {
     return instr.isIf() && instr.asIf().isZeroTest()
-        && instr.inValues().get(0).equals(receiver)
+        && instr.inValues().get(0).equals(value)
         && (instr.asIf().getType() == Type.EQ || instr.asIf().getType() == Type.NE);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 91bb786..ec12f82 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -10,14 +10,12 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
-import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
 import com.android.tools.r8.graph.DexDebugEvent.EndLocal;
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
@@ -401,56 +399,45 @@
         new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
 
     // Debug event visitor to map line numbers.
-    // TODO(117268618): Cleanup the duplicate pc tracking.
     DexDebugEventVisitor visitor =
-        new DexDebugEventVisitor() {
-          DexDebugPositionState state =
-              new DexDebugPositionState(debugInfo.startLine, method.method);
-          int currentPc = 0;
+        new DexDebugPositionState(debugInfo.startLine, method.method) {
 
+          // Keep track of what PC has been emitted.
+          private int emittedPc = 0;
+
+          // Force the current PC to emitted.
           private void flushPc() {
-            if (currentPc != state.getCurrentPc()) {
-              positionEventEmitter.emitAdvancePc(state.getCurrentPc());
-              currentPc = state.getCurrentPc();
+            if (emittedPc != getCurrentPc()) {
+              positionEventEmitter.emitAdvancePc(getCurrentPc());
+              emittedPc = getCurrentPc();
             }
           }
 
-          @Override
-          public void visit(AdvancePC advancePC) {
-            state.visit(advancePC);
-          }
-
-          @Override
-          public void visit(AdvanceLine advanceLine) {
-            state.visit(advanceLine);
-          }
-
-          @Override
-          public void visit(SetInlineFrame setInlineFrame) {
-            state.visit(setInlineFrame);
-          }
-
+          // A default event denotes a line table entry and must always be emitted. Remap its line.
           @Override
           public void visit(Default defaultEvent) {
-            state.visit(defaultEvent);
-            int currentLine = state.getCurrentLine();
-            assert currentLine >= 0;
+            super.visit(defaultEvent);
+            assert getCurrentLine() >= 0;
             Position position =
                 positionRemapper.createRemappedPosition(
-                    state.getCurrentLine(),
-                    state.getCurrentFile(),
-                    state.getCurrentMethod(),
-                    state.getCurrentCallerPosition());
+                    getCurrentLine(),
+                    getCurrentFile(),
+                    getCurrentMethod(),
+                    getCurrentCallerPosition());
             mappedPositions.add(
                 new MappedPosition(
-                    state.getCurrentMethod(),
-                    currentLine,
-                    state.getCurrentCallerPosition(),
+                    getCurrentMethod(),
+                    getCurrentLine(),
+                    getCurrentCallerPosition(),
                     position.line));
-            positionEventEmitter.emitPositionEvents(state.getCurrentPc(), position);
-            currentPc = state.getCurrentPc();
+            positionEventEmitter.emitPositionEvents(getCurrentPc(), position);
+            emittedPc = getCurrentPc();
           }
 
+          // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
+
+          // Materializing events are just amended to the stream.
+
           @Override
           public void visit(SetFile setFile) {
             processedEvents.add(setFile);
@@ -466,6 +453,8 @@
             processedEvents.add(setEpilogueBegin);
           }
 
+          // Local changes must force flush the PC ensuing they pertain to the correct point.
+
           @Override
           public void visit(StartLocal startLocal) {
             flushPc();
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index da46d60..3b5ddd8 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -47,6 +47,9 @@
   // Proguard configuration file lines.
   private final List<String> config = new ArrayList<>();
 
+  // Additional Proguard configuration files.
+  private List<Path> proguardConfigFiles = new ArrayList<>();
+
   private ExternalR8TestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
   }
@@ -67,11 +70,8 @@
     try {
       Path outputFolder = getState().getNewTempFolder();
       Path outputJar = outputFolder.resolve("output.jar");
-      Path proguardConfigFile = outputFolder.resolve("proguard-config.txt");
       Path proguardMapFile = outputFolder.resolve("output.jar.map");
 
-      FileUtils.writeTextFile(proguardConfigFile, config);
-
       List<String> command = new ArrayList<>();
       Collections.addAll(
           command,
@@ -81,12 +81,20 @@
           R8.class.getTypeName(),
           "--output",
           outputJar.toAbsolutePath().toString(),
-          "--pg-conf",
-          proguardConfigFile.toAbsolutePath().toString(),
           "--pg-map-output",
           proguardMapFile.toString(),
           backend == Backend.CF ? "--classfile" : "--dex",
           builder.getMode() == CompilationMode.DEBUG ? "--debug" : "--release");
+      if (!config.isEmpty()) {
+        Path proguardConfigFile = outputFolder.resolve("proguard-config.txt");
+        FileUtils.writeTextFile(proguardConfigFile, config);
+        command.add("--pg-conf");
+        command.add(proguardConfigFile.toAbsolutePath().toString());
+      }
+      for (Path proguardConfigFile : proguardConfigFiles) {
+        command.add("--pg-conf");
+        command.add(proguardConfigFile.toAbsolutePath().toString());
+      }
       if (libJars.isEmpty()) {
         command.add("--lib");
         command.add(TestBase.runtimeJar(backend).toAbsolutePath().toString());
@@ -154,14 +162,8 @@
   }
 
   @Override
-  public ExternalR8TestBuilder addKeepRuleFiles(List<Path> files) {
-    try {
-      for (Path file : files) {
-        config.addAll(FileUtils.readAllLines(file));
-      }
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
+  public ExternalR8TestBuilder addKeepRuleFiles(List<Path> proguardConfigFiles) {
+    this.proguardConfigFiles.addAll(proguardConfigFiles);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 2e4b1bd..ea482fa 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -98,9 +98,13 @@
 
   // Ordered list of injar entries.
   private List<Path> injars = new ArrayList<>();
+
   // Proguard configuration file lines.
   private List<String> config = new ArrayList<>();
 
+  // Additional Proguard configuration files.
+  private List<Path> proguardConfigFiles = new ArrayList<>();
+
   private ProguardTestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
   }
@@ -138,6 +142,10 @@
       command.add(ToolHelper.getJava8RuntimeJar().toString());
       command.add("-include");
       command.add(configFile.toString());
+      for (Path proguardConfigFile : proguardConfigFiles) {
+        command.add("-include");
+        command.add(proguardConfigFile.toAbsolutePath().toString());
+      }
       command.add("-outjar");
       command.add(outJar.toString());
       command.add("-printmapping");
@@ -153,11 +161,9 @@
       if (result.exitCode != 0) {
         throw new CompilationFailedException(result.toString());
       }
-      AndroidApp.Builder aaabuilder = AndroidApp.builder();
-      aaabuilder.addProgramFiles(outJar);
       String proguardMap =
           Files.exists(mapFile) ? FileUtils.readTextFile(mapFile, Charsets.UTF_8) : "";
-      return new ProguardTestCompileResult(getState(), aaabuilder.build(), proguardMap);
+      return new ProguardTestCompileResult(getState(), outJar, proguardMap);
     } catch (IOException e) {
       throw new CompilationFailedException(e);
     }
@@ -203,14 +209,8 @@
   }
 
   @Override
-  public ProguardTestBuilder addKeepRuleFiles(List<Path> files) {
-    try {
-      for (Path file : files) {
-        config.addAll(FileUtils.readAllLines(file));
-      }
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
+  public ProguardTestBuilder addKeepRuleFiles(List<Path> proguardConfigFiles) {
+    this.proguardConfigFiles.addAll(proguardConfigFiles);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index 4cc1def..6f47adb 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -8,18 +8,25 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.concurrent.ExecutionException;
 
 public class ProguardTestCompileResult
     extends TestCompileResult<ProguardTestCompileResult, ProguardTestRunResult> {
 
+  private final Path outputJar;
   private final String proguardMap;
 
-  ProguardTestCompileResult(TestState state, AndroidApp app, String proguardMap) {
-    super(state, app);
+  ProguardTestCompileResult(TestState state, Path outputJar, String proguardMap) {
+    super(state, AndroidApp.builder().addProgramFiles(outputJar).build());
+    this.outputJar = outputJar;
     this.proguardMap = proguardMap;
   }
 
+  public Path outputJar() {
+    return outputJar;
+  }
+
   @Override
   public ProguardTestCompileResult self() {
     return this;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 195e01c..7d6af00 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.google.common.collect.ImmutableList;
@@ -80,8 +81,8 @@
   private final CompilationMode mode;
   private final String pkg;
   private final String mainClass;
-  private final Frontend frontend;
-  private final Output output;
+  protected final Frontend frontend;
+  protected final Output output;
 
   public R8RunExamplesCommon(
       String pkg,
@@ -174,16 +175,7 @@
                   .setDisableMinification(true)
                   .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
                   .build();
-          ToolHelper.runR8(
-              command,
-              options -> {
-                options.lineNumberOptimization = LineNumberOptimization.OFF;
-                options.enableCfFrontend = frontend == Frontend.CF;
-                if (output == Output.CF) {
-                  // Class inliner is not supported with CF backend yet.
-                  options.enableClassInlining = false;
-                }
-              });
+          ToolHelper.runR8(command, this::configure);
         break;
       }
       default:
@@ -191,6 +183,15 @@
     }
   }
 
+  protected void configure(InternalOptions options) {
+    options.lineNumberOptimization = LineNumberOptimization.OFF;
+    options.enableCfFrontend = frontend == Frontend.CF;
+    if (output == Output.CF) {
+      // Class inliner is not supported with CF backend yet.
+      options.enableClassInlining = false;
+    }
+  }
+
   private boolean shouldCompileFail() {
     if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
       return true;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
index 4451910..c2c0869 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -17,6 +18,15 @@
 @RunWith(Parameterized.class)
 public class R8RunExamplesKotlinTest extends R8RunExamplesCommon {
 
+  @Override
+  protected void configure(InternalOptions options) {
+    options.enableCfFrontend = frontend == Frontend.CF;
+    if (output == Output.CF) {
+      // Class inliner is not supported with CF backend yet.
+      options.enableClassInlining = false;
+    }
+  }
+
   @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
   public static Collection<String[]> data() {
     String[] tests = {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index 10b9a63..518ea8d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -92,7 +92,7 @@
 
     MethodSubject selfCheck = mainSubject.method("void", "selfCheck", ImmutableList.of());
     assertThat(selfCheck, isPresent());
-    assertEquals(0, countCallToParamNullCheck(selfCheck));
+    assertEquals(1, countCallToParamNullCheck(selfCheck));
     assertEquals(1, countPrintCall(selfCheck));
     assertEquals(0, countThrow(selfCheck));
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 1d3f25e..1a690e2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+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.InstructionSubject;
@@ -212,7 +213,13 @@
   protected void runTest(String folder, String mainClass,
       boolean enabled, AndroidAppInspector inspector) throws Exception {
     runTest(
-        folder, mainClass, null,
+        folder, mainClass,
+        // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
+        StringUtils.lines(
+            "-neverinline class * { void test*State*(...); }",
+            "-neverinline class * { void testBigExtraMethod(...); }",
+            "-neverinline class * { void testBigExtraMethodReturningLambda(...); }"
+        ),
         options -> {
           options.enableInlining = true;
           options.enableClassInlining = enabled;
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
index f0031d2..757e06f 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.shaking;
 
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.ExternalR8TestCompileResult;
 import com.android.tools.r8.TestBase;
@@ -38,18 +40,53 @@
 
   @Test
   public void testSingleConfigurationWithRelativePath() throws Exception {
+    Path subDirectory = temp.newFolder().toPath();
+    Path proguardConfigFile = subDirectory.resolve("proguard-config.txt");
+    Path proguardConfigOutFile = subDirectory.resolve("proguard-config-out.txt");
+
     String proguardConfig =
         StringUtils.lines(
             keepMainProguardConfiguration(PrintConfigurationTestClass.class),
-            "-printconfiguration generated-proguard-config.txt");
+            "-printconfiguration proguard-config-out.txt");
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig.trim());
+
     ExternalR8TestCompileResult result =
         testForExternalR8(Backend.DEX)
             .addProgramClasses(PrintConfigurationTestClass.class)
-            .addKeepRules(proguardConfig.trim())
+            .addKeepRuleFiles(proguardConfigFile)
             .compile();
-    Path printConfigurationFile =
-        result.outputJar().getParent().resolve("generated-proguard-config.txt");
-    assertEquals(proguardConfig, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8));
+
+    assertEquals(proguardConfig, FileUtils.readTextFile(proguardConfigOutFile, Charsets.UTF_8));
+  }
+
+  @Test
+  public void testSingleConfigurationWithRelativePathCompatibility() throws Exception {
+    Path subDirectory = temp.newFolder().toPath();
+    Path proguardConfigFile = subDirectory.resolve("proguard-config.txt");
+    Path proguardConfigOutFile = subDirectory.resolve("proguard-config-out.txt");
+
+    String proguardConfig =
+        StringUtils.lines(
+            keepMainProguardConfiguration(PrintConfigurationTestClass.class),
+            "-printconfiguration proguard-config-out.txt");
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+
+    testForProguard()
+        .addProgramClasses(PrintConfigurationTestClass.class)
+        .addKeepRuleFiles(proguardConfigFile)
+        .compile();
+
+    String proguardConfigOut = FileUtils.readTextFile(proguardConfigOutFile, Charsets.UTF_8);
+    assertThat(
+        proguardConfigOut,
+        containsString(
+            StringUtils.lines(
+                "-keep class com.android.tools.r8.shaking.PrintConfigurationTestClass {",
+                "    public static void main(java.lang.String[]);",
+                "}")));
+    assertThat(
+        proguardConfigOut,
+        containsString("-printconfiguration " + proguardConfigOutFile.toString()));
   }
 
   @Test
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
index ebf26cd..f95125c 100644
--- a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
@@ -16,7 +16,6 @@
     testStateful3()
 }
 
-@Synchronized
 fun testStateless() {
     SamIface.Consumer.consume { "123" }
     SamIface.Consumer.consume { "ABC" }
@@ -31,7 +30,6 @@
     }
 }
 
-@Synchronized
 fun testStateful() {
     var someVariable = 0
 
@@ -62,7 +60,6 @@
     }
 }
 
-@Synchronized
 fun testStateful2() {
     var someVariable = 0
     SamIface.Consumer.consumeBig {
@@ -80,7 +77,6 @@
     }
 }
 
-@Synchronized
 fun testStateful3() {
     var someVariable = 0
     SamIface.Consumer.consumeBig {
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
index 06b937e..ea315ba 100644
--- a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
@@ -17,14 +17,12 @@
 
 data class Record(val foo: String, val good: Boolean)
 
-@Synchronized
 fun testKotlinSequencesStateless(strings: Sequence<String>) {
     useRecord()
     // Stateless k-style lambda
     strings.map { Record(it, false) }.forEach { println(it) }
 }
 
-@Synchronized
 fun testKotlinSequencesStateful(a: Int, b: Int, strings: Sequence<String>) {
     useRecord()
     // Big stateful k-style lambda
@@ -40,7 +38,6 @@
     }
 }
 
-@Synchronized
 fun testBigExtraMethod() {
     useRecord()
     bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
@@ -62,7 +59,6 @@
     return "$id: ${lambda()}"
 }
 
-@Synchronized
 fun testBigExtraMethodReturningLambda() {
     useRecord()
     bigUserReturningLambda(next()) { next() } // Not used