Make lambda instance methods when possible.
This ensures a valid 'this' reference that can be seen by the debugger.
Bug: 123068053
Change-Id: I04698f09bb657a17ec4a1660b3c4d6424a85d4ed
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index f8fb8e1..cea5e4d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -19,6 +20,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.base.Strings;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import java.util.Arrays;
import java.util.Collections;
@@ -31,6 +33,9 @@
// DexCode corresponds to code item in dalvik/dex-format.html
public class DexCode extends Code {
+ private static final String FAKE_THIS_PREFIX = "_";
+ private static final String FAKE_THIS_SUFFIX = "this";
+
public final int registerSize;
public final int incomingRegisterSize;
public final int outgoingRegisterSize;
@@ -103,17 +108,43 @@
}
}
- public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
+ public DexDebugInfo debugInfoWithFakeThisParameter(DexItemFactory factory) {
if (debugInfo == null) {
return null;
}
+ // User code may already have variables named '_*this'. Use one more than the largest number of
+ // underscores present as a prefix to 'this'.
+ int largestPrefix = 0;
+ for (DexString parameter : debugInfo.parameters) {
+ largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, parameter));
+ }
+ for (DexDebugEvent event : debugInfo.events) {
+ if (event instanceof DexDebugEvent.StartLocal) {
+ DexString name = ((StartLocal) event).name;
+ largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, name));
+ }
+ }
+
+ String fakeThisName = Strings.repeat(FAKE_THIS_PREFIX, largestPrefix + 1) + FAKE_THIS_SUFFIX;
DexString[] parameters = debugInfo.parameters;
DexString[] newParameters = new DexString[parameters.length + 1];
- newParameters[0] = name;
+ newParameters[0] = factory.createString(fakeThisName);
System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
}
+ private static int getLargestPrefix(DexItemFactory factory, DexString name) {
+ if (name != null && name.endsWith(factory.thisName)) {
+ String string = name.toString();
+ for (int i = 0; i < string.length(); i++) {
+ if (string.charAt(i) != '_') {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+
public DexDebugInfo debugInfoWithoutFirstParameter() {
if (debugInfo == null) {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 9e18c94..054b950 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -90,10 +90,7 @@
newFlags.unsetBridge();
newFlags.promoteToStatic();
DexCode dexCode = code.asDexCode();
- // We cannot name the parameter "this" because the debugger may omit it due to the method
- // actually being static. Instead we prepend it with a special character.
- dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(
- rewriter.factory.createString("-this")));
+ dexCode.setDebugInfo(dexCode.debugInfoWithFakeThisParameter(rewriter.factory));
assert (dexCode.getDebugInfo() == null)
|| (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
@@ -153,8 +150,7 @@
+ "interface method: " + oldMethod.toSourceString(), iface.origin);
}
DexCode dexCode = code.asDexCode();
- // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
- dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
+ dexCode.setDebugInfo(dexCode.debugInfoWithFakeThisParameter(rewriter.factory));
assert (dexCode.getDebugInfo() == null)
|| (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index a0bc184..865a62c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.google.common.base.Suppliers;
@@ -343,17 +344,28 @@
assert implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
- // If lambda$ method is an instance method we convert it into a static methods and
- // relax its accessibility.
- DexProto implProto = implMethod.proto;
- DexType[] implParams = implProto.parameters.values;
- DexType[] newParams = new DexType[implParams.length + 1];
- newParams[0] = implMethod.holder;
- System.arraycopy(implParams, 0, newParams, 1, implParams.length);
+ // If the lambda$ method is an instance-private method on an interface we convert it into a
+ // public static method as it will be placed on the companion class.
+ if (implHandle.type.isInvokeDirect()
+ && rewriter.appInfo.definitionFor(implMethod.holder).isInterface()) {
+ DexProto implProto = implMethod.proto;
+ DexType[] implParams = implProto.parameters.values;
+ DexType[] newParams = new DexType[implParams.length + 1];
+ newParams[0] = implMethod.holder;
+ System.arraycopy(implParams, 0, newParams, 1, implParams.length);
- DexProto newProto = rewriter.factory.createProto(implProto.returnType, newParams);
- return new InstanceLambdaImplTarget(
- rewriter.factory.createMethod(implMethod.holder, newProto, implMethod.name));
+ DexProto newProto = rewriter.factory.createProto(implProto.returnType, newParams);
+ return new InterfaceLambdaImplTarget(
+ rewriter.factory.createMethod(implMethod.holder, newProto, implMethod.name));
+ } else {
+ // Otherwise we need to ensure the method can be reached publicly by virtual dispatch.
+ // To avoid potential conflicts on the name of the lambda method once dispatch becomes virtual
+ // we add the method-holder name as suffix to the lambda-method name.
+ return new InstanceLambdaImplTarget(
+ rewriter.factory.createMethod(implMethod.holder, implMethod.proto,
+ rewriter.factory.createString(
+ implMethod.name.toString() + "$" + implMethod.holder.getName())));
+ }
}
// Create targets for instance method referenced directly without
@@ -498,17 +510,17 @@
}
}
- // Used for instance private lambda$ methods. Needs to be converted to
- // a package-private static method.
- private class InstanceLambdaImplTarget extends Target {
+ // Used for instance private lambda$ methods on interfaces which need to be converted to public
+ // static methods. They can't remain instance methods as they will end up on the companion class.
+ private class InterfaceLambdaImplTarget extends Target {
- InstanceLambdaImplTarget(DexMethod staticMethod) {
- super(staticMethod, Invoke.Type.STATIC);
+ InterfaceLambdaImplTarget(DexMethod staticMethod) {
+ super(staticMethod, Type.STATIC);
}
@Override
boolean ensureAccessibility() {
- // For all instantiation points for which compiler creates lambda$
+ // For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
DexMethod implMethod = descriptor.implHandle.asMethod();
DexClass implMethodHolder = definitionFor(implMethod.holder);
@@ -534,15 +546,58 @@
encodedMethod.getCode());
newMethod.copyMetadata(encodedMethod);
rewriter.methodMapping.put(encodedMethod.method, callTarget);
- // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
DexCode dexCode = newMethod.getCode().asDexCode();
- dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
+ dexCode.setDebugInfo(dexCode.debugInfoWithFakeThisParameter(rewriter.factory));
assert (dexCode.getDebugInfo() == null)
|| (callTarget.getArity() == dexCode.getDebugInfo().parameters.length);
implMethodHolder.setDirectMethod(i, newMethod);
return true;
}
}
+ assert false
+ : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
+ return false;
+ }
+ }
+
+ // Used for instance private lambda$ methods which need to be converted to public methods.
+ private class InstanceLambdaImplTarget extends Target {
+
+ InstanceLambdaImplTarget(DexMethod staticMethod) {
+ super(staticMethod, Type.VIRTUAL);
+ }
+
+ @Override
+ boolean ensureAccessibility() {
+ // For all instantiation points for which the compiler creates lambda$
+ // methods, it creates these methods in the same class/interface.
+ DexMethod implMethod = descriptor.implHandle.asMethod();
+ DexClass implMethodHolder = definitionFor(implMethod.holder);
+
+ List<DexEncodedMethod> oldDirectMethods = implMethodHolder.directMethods();
+ for (int i = 0; i < oldDirectMethods.size(); i++) {
+ DexEncodedMethod encodedMethod = oldDirectMethods.get(i);
+ if (implMethod.match(encodedMethod)) {
+ // We need to create a new method with the same code to be able to safely relax its
+ // accessibility and make it virtual.
+ MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
+ newAccessFlags.unsetPrivate();
+ newAccessFlags.setPublic();
+ DexEncodedMethod newMethod =
+ new DexEncodedMethod(
+ callTarget,
+ newAccessFlags,
+ encodedMethod.annotations,
+ encodedMethod.parameterAnnotationsList,
+ encodedMethod.getCode());
+ newMethod.copyMetadata(encodedMethod);
+ rewriter.methodMapping.put(encodedMethod.method, callTarget);
+ // Move the method from the direct methods to the virtual methods set.
+ implMethodHolder.removeDirectMethod(i);
+ implMethodHolder.appendVirtualMethod(newMethod);
+ return true;
+ }
+ }
return false;
}
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 77dc0df..9e8f74a 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -113,6 +113,12 @@
return setMode(CompilationMode.RELEASE);
}
+ public T setMinApiThreshold(AndroidApiLevel minApiThreshold) {
+ assert backend == Backend.DEX;
+ AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVmNoHigherThan(minApiThreshold);
+ return setMinApi(minApi);
+ }
+
public T setMinApi(AndroidApiLevel minApiLevel) {
// Should we ignore min-api calls when backend == CF?
this.defaultMinApiLevel = null;
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 1c0287e..13d59dc 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -336,6 +336,10 @@
return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName());
}
+ protected final JUnit3Wrapper.Command breakpoint(MethodReference method, int line) {
+ return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName(), line);
+ }
+
protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
return breakpoint(className, methodName, null);
}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
index 8af2a6e..99a19f5 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -27,7 +27,6 @@
}
@Test
- @Ignore("b/123068053")
public void testD8() throws Throwable {
D8TestCompileResult compileResult =
testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
index e9b1b8c..9e8741d 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
@@ -6,7 +6,7 @@
public class DefaultLambdaWithInvokeInterfaceTest {
public interface I {
- int run();
+ String run();
}
public interface J {
@@ -15,11 +15,17 @@
}
default I stateful() {
- return () -> stateless().length();
+ return () -> {
+ String stateless = stateless();
+ return "stateful(" + stateless + ")";
+ };
}
}
public static void main(String[] args) {
- System.out.println(new J() {}.stateful().run());
+ J j = new J() {};
+ I stateful = j.stateful();
+ String run = stateful.run();
+ System.out.println(run);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
index 732612d..b89f09a 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
@@ -6,30 +6,83 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
import org.junit.Test;
-public class DefaultLambdaWithInvokeInterfaceTestRunner extends TestBase {
+public class DefaultLambdaWithInvokeInterfaceTestRunner extends DebugTestBase {
final Class<?> CLASS = DefaultLambdaWithInvokeInterfaceTest.class;
- final String EXPECTED = StringUtils.lines("4");
+ final String EXPECTED = StringUtils.lines("stateful(hest)");
@Test
public void testJvm() throws Exception {
testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
}
+ private void runDebugger(DebugTestConfig config) throws Throwable {
+ MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
+ Command checkThis = conditional((state) ->
+ state.isCfRuntime()
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(
+ checkNoLocal("this"),
+ checkLocal("_this")));
+
+ runDebugTest(config, CLASS,
+ breakpoint(main, 27),
+ run(),
+ checkLine(27),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(18),
+ checkThis,
+ breakpoint(main, 28),
+ run(),
+ checkLine(28),
+ checkLocal("stateful"),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(19),
+ checkThis,
+ run());
+ }
+
@Test
- public void test() throws Exception {
- testForD8()
+ public void testR8Cf() throws Throwable {
+ R8TestCompileResult compileResult = testForR8(Backend.CF)
.addProgramClassesAndInnerClasses(CLASS)
- .setMinApi(AndroidApiLevel.K)
- .compile()
+ .noMinification()
+ .noTreeShaking()
+ .debug()
+ .compile();
+ compileResult
// TODO(b/123506120): Add .assertNoMessages()
.run(CLASS)
.assertSuccessWithOutput(EXPECTED)
.inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
+ runDebugger(compileResult.debugConfig());
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ D8TestCompileResult compileResult = testForD8()
+ .addProgramClassesAndInnerClasses(CLASS)
+ .setMinApi(AndroidApiLevel.K)
+ .compile();
+ compileResult
+ // TODO(b/123506120): Add .assertNoMessages()
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
+ runDebugger(compileResult.debugConfig());
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
index eb4e75b..e122450 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
@@ -13,11 +13,18 @@
}
default I stateful() {
- return () -> "stateful(" + stateless().foo() + ")";
+ return () -> {
+ I stateless = stateless();
+ String foo = stateless.foo();
+ return "stateful(" + foo + ")";
+ };
}
}
public static void main(String[] args) {
- System.out.println(((I) () -> "foo").stateful().foo());
+ I i = () -> "foo";
+ I stateful = i.stateful();
+ String foo = stateful.foo();
+ System.out.println(foo);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index bc193c8..e209e44 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -3,14 +3,24 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.Disassemble;
import com.android.tools.r8.Disassemble.DisassembleCommand;
-import com.android.tools.r8.TestBase;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -19,18 +29,63 @@
import java.util.List;
import org.junit.Test;
-public class DefaultLambdaWithSelfReferenceTestRunner extends TestBase {
+public class DefaultLambdaWithSelfReferenceTestRunner extends DebugTestBase {
final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
final String EXPECTED = StringUtils.lines("stateful(stateless)");
- @Test
- public void testJvm() throws Exception {
- testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
+ private void runDebugger(DebugTestConfig config) throws Throwable {
+ MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
+ Command checkThis = conditional((state) ->
+ state.isCfRuntime()
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(
+ checkNoLocal("this"),
+ checkLocal("_this")));
+
+ runDebugTest(config, CLASS,
+ breakpoint(main, 26),
+ run(),
+ checkLine(26),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(16),
+ // When desugaring, the InterfaceProcessor makes this static on the companion class.
+ checkThis,
+ breakpoint(main, 27),
+ run(),
+ checkLine(27),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(17),
+ // When desugaring, the LambdaClass will change this to a static (later moved to companion).
+ checkThis,
+ run());
}
@Test
- public void test() throws Exception {
+ public void testJvm() throws Throwable {
+ JvmTestBuilder builder = testForJvm().addTestClasspath();
+ builder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(builder.debugConfig());
+ }
+
+ @Test
+ public void testR8Cf() throws Throwable {
+ R8TestCompileResult compileResult = testForR8(Backend.CF)
+ .addProgramClassesAndInnerClasses(CLASS)
+ .noMinification()
+ .noTreeShaking()
+ .debug()
+ .compile();
+ compileResult
+ // TODO(b/123506120): Add .assertNoMessages()
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
+ runDebugger(compileResult.debugConfig());
+ }
+
+ @Test
+ public void testD8() throws Throwable {
Path out1 = temp.newFolder().toPath().resolve("out1.zip");
testForD8()
.addProgramClassesAndInnerClasses(CLASS)
@@ -73,14 +128,18 @@
}
Path out2 = temp.newFolder().toPath().resolve("out2.zip");
- testForD8()
+ D8TestCompileResult compiledResult = testForD8()
.addProgramFiles(outs)
- .compile()
+ .compile();
+
+ compiledResult
// TODO(b/123506120): Add .assertNoMessages()
.writeToZip(out2)
.run(CLASS)
.assertSuccessWithOutput(EXPECTED);
+ runDebugger(compiledResult.debugConfig());
+
Path dissasemble1 = temp.newFolder().toPath().resolve("disassemble1.txt");
Path dissasemble2 = temp.newFolder().toPath().resolve("disassemble2.txt");
Disassemble.disassemble(
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTest.java
new file mode 100644
index 0000000..7df85c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTest.java
@@ -0,0 +1,26 @@
+// 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.desugar;
+
+public class DefaultLambdaWithUnderscoreThisTest {
+
+ public interface I {
+ String foo();
+
+ default I stateful() {
+ String _this = "My _this variable";
+ return () -> {
+ String ___this = "Another ___this variable";
+ return "stateful(" + _this + " " + foo() + " " + ___this + ")";
+ };
+ }
+ }
+
+ public static void main(String[] args) {
+ I i = () -> "foo";
+ I stateful = i.stateful();
+ String foo = stateful.foo();
+ System.out.println(foo);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
new file mode 100644
index 0000000..10d74a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
@@ -0,0 +1,81 @@
+// 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.desugar;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.desugar.DefaultLambdaWithUnderscoreThisTest.I;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.function.Function;
+import org.junit.Test;
+
+public class DefaultLambdaWithUnderscoreThisTestRunner extends DebugTestBase {
+
+ final Class<?> CLASS = DefaultLambdaWithUnderscoreThisTest.class;
+
+ final String EXPECTED = StringUtils
+ .lines("stateful(My _this variable foo Another ___this variable)");
+
+ private void runDebugger(DebugTestConfig config) throws Throwable {
+ MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
+ MethodReference stateful = Reference.methodFromMethod(I.class.getMethod("stateful"));
+ Function<String, Command> checkThis = (String desugarThis) -> conditional((state) ->
+ state.isCfRuntime()
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(
+ checkNoLocal("this"),
+ checkLocal(desugarThis)));
+
+ runDebugTest(config, CLASS,
+ breakpoint(main, 22),
+ run(),
+ checkLine(22),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(12),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(13),
+ // Desugaring will insert '__this' in place of 'this' here.
+ checkThis.apply("__this"),
+ checkLocal("_this"),
+ breakpoint(main, 23),
+ run(),
+ checkLine(23),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(14),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(15),
+ // Desugaring will insert '____this' in place of 'this' here.
+ checkThis.apply("____this"),
+ checkLocals("_this", "___this"),
+ run());
+ }
+
+ @Test
+ public void testJvm() throws Throwable {
+ JvmTestBuilder builder = testForJvm().addTestClasspath();
+ builder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(builder.debugConfig());
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ D8TestCompileResult compileResult = testForD8()
+ .addProgramClassesAndInnerClasses(CLASS)
+ .setMinApiThreshold(AndroidApiLevel.K)
+ .compile();
+ compileResult
+ // TODO(b/123506120): Add .assertNoMessages()
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig());
+ }
+}