Version 1.4.44
Cherry pick: Make lambda instance methods when possible.
CL: https://r8-review.googlesource.com/c/r8/+/34305
Cherry pick: Re-apply "Add tests showing issue with missing classes when desugaring."
CL: https://r8-review.googlesource.com/c/r8/+/33881
Cherry pick: Add regression test for missing locals in desugared lambdas.
CL: https://r8-review.googlesource.com/c/r8/+/33241/
Cherry pick: Set min-api thresholds in some misconfigured tests.
CL: https://r8-review.googlesource.com/c/r8/+/34120
Bug: 123068053
Change-Id: I8c2432aaef0cfcd4d06b5b386ed9b554c680a155
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index c9938d3..112d49a 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.4.43";
+ public static final String LABEL = "1.4.44";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 19d7e24..60893c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -61,7 +61,8 @@
private void write(ThrowingFunction<DexClass, PrintStream, IOException> outputStreamProvider,
Consumer<PrintStream> closer)
throws IOException {
- for (DexProgramClass clazz : application.classes()) {
+ Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
+ for (DexProgramClass clazz : classes) {
if (anyMethodMatches(clazz)) {
PrintStream ps = outputStreamProvider.apply(clazz);
try {
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 fab4a83..b189f6f 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;
@@ -99,17 +104,43 @@
this.debugInfo = debugInfo;
}
- 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 9fe23de..9d5e5fc 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.setStatic();
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);
@@ -154,8 +151,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/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 37e4dec..c807a5d 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -87,8 +87,8 @@
return self();
}
- public D8TestBuilder setIntermediate(boolean b) {
- builder.setIntermediate(true);
+ public D8TestBuilder setIntermediate(boolean intermediate) {
+ builder.setIntermediate(intermediate);
return self();
}
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 003607b..fb69657 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -100,7 +100,7 @@
@Test
public void lambdaDesugaring() throws Throwable {
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
- .withMinApiLevel(AndroidApiLevel.K)
+ .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
@@ -108,7 +108,7 @@
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
- .withMinApiLevel(AndroidApiLevel.K)
+ .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
@@ -144,7 +144,7 @@
@Test
public void lambdaDesugaringNPlus() throws Throwable {
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
- .withMinApiLevel(AndroidApiLevel.K)
+ .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
@@ -153,7 +153,7 @@
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
- .withMinApiLevel(AndroidApiLevel.K)
+ .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 4f98b6f..d103efc 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -362,7 +362,7 @@
@Test
public void lambdaDesugaring() throws Throwable {
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
- .withMinApiLevel(AndroidApiLevel.K)
+ .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withKeepAll()
.run();
}
@@ -370,7 +370,7 @@
@Test
public void lambdaDesugaringNPlus() throws Throwable {
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
- .withMinApiLevel(AndroidApiLevel.K)
+ .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withKeepAll()
.run();
@@ -399,7 +399,7 @@
@Test
public void lambdaDesugaringValueAdjustments() throws Throwable {
test("lambdadesugaring-value-adjustments", "lambdadesugaring", "ValueAdjustments")
- .withMinApiLevel(AndroidApiLevel.K)
+ .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withKeepAll()
.run();
}
@@ -631,7 +631,7 @@
output.replace("\r", "").equals(javaResult.stdout.replace("\r", "")));
}
} catch (Throwable t) {
- assert expectedToFail;
+ assertTrue("Test was not expected to fail. Failed with " + t.getMessage(), expectedToFail);
}
}
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/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 43d71b1..a0ed928 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
+import com.android.tools.r8.utils.ListUtils;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.Matcher;
@@ -44,25 +45,34 @@
return errors;
}
+ private void assertEmpty(String type, List<Diagnostic> messages) {
+ assertEquals(
+ "Expected no "
+ + type
+ + " messages, got:\n"
+ + String.join("\n", ListUtils.map(messages, m -> m.getDiagnosticMessage())),
+ 0,
+ messages.size());
+ }
public TestDiagnosticMessages assertNoMessages() {
- assertEquals(0, getInfos().size());
- assertEquals(0, getWarnings().size());
- assertEquals(0, getErrors().size());
+ assertEmpty("info", getInfos());
+ assertEmpty("warning", getWarnings());
+ assertEmpty("error", getErrors());
return this;
}
public TestDiagnosticMessages assertOnlyInfos() {
assertNotEquals(0, getInfos().size());
- assertEquals(0, getWarnings().size());
- assertEquals(0, getErrors().size());
+ assertEmpty("warning", getWarnings());
+ assertEmpty("error", getErrors());
return this;
}
public TestDiagnosticMessages assertOnlyWarnings() {
- assertEquals(0, getInfos().size());
+ assertEmpty("info", getInfos());
assertNotEquals(0, getWarnings().size());
- assertEquals(0, getErrors().size());
+ assertEmpty("error", getErrors());
return this;
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index dcfda2c..f53b8e6 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -735,6 +735,11 @@
}
}
+ public static AndroidApiLevel getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel threshold) {
+ AndroidApiLevel minApiLevelForDexVm = getMinApiLevelForDexVm();
+ return minApiLevelForDexVm.getLevel() < threshold.getLevel() ? minApiLevelForDexVm : threshold;
+ }
+
public static AndroidApiLevel getMinApiLevelForDexVm() {
return getMinApiLevelForDexVm(ToolHelper.getDexVm());
}
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 fb61f45..13d59dc 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -50,6 +51,7 @@
import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ObjectReferenceCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
@@ -153,6 +155,12 @@
}
protected final void runDebugTest(
+ DebugTestConfig config, Class<?> debuggeeClass, JUnit3Wrapper.Command... commands)
+ throws Throwable {
+ runInternal(config, debuggeeClass.getTypeName(), Arrays.asList(commands));
+ }
+
+ protected final void runDebugTest(
DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
throws Throwable {
runInternal(config, debuggeeClass, Arrays.asList(commands));
@@ -324,6 +332,14 @@
return new JUnit3Wrapper.Command.RunCommand();
}
+ protected final JUnit3Wrapper.Command breakpoint(MethodReference method) {
+ 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);
}
@@ -477,6 +493,10 @@
});
}
+ protected final JUnit3Wrapper.Command checkLine(int line) {
+ return inspect(t -> t.checkLine(null, line));
+ }
+
protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
return inspect(t -> t.checkLine(sourceFile, line));
}
@@ -532,6 +552,16 @@
});
}
+ protected final JUnit3Wrapper.Command checkFieldOnThis(
+ String fieldName, String fieldSignature, Value expectedValue) {
+ return inspect(
+ t -> {
+ Value value = t.getFieldOnThis(fieldName, fieldSignature);
+ Assert.assertEquals(
+ "Incorrect value for field 'this." + fieldName + "'", expectedValue, value);
+ });
+ }
+
protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
return t -> inspector.accept(t.debuggeeState);
}
@@ -1298,6 +1328,7 @@
@Override
public void checkLine(String sourceFile, int line) {
+ sourceFile = sourceFile != null ? sourceFile : getSourceFile();
if (!Objects.equals(sourceFile, getSourceFile()) || line != getLineNumber()) {
String locationString = convertCurrentLocationToString();
Assert.fail(
@@ -1501,7 +1532,15 @@
// The class is available, lookup and read the field.
long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
- return getField(getMirror(), classId, fieldId);
+ return internalStaticField(getMirror(), classId, fieldId);
+ }
+
+ public Value getFieldOnThis(String fieldName, String fieldSignature) {
+ long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+ long classId = getMirror().getReferenceType(thisObjectId);
+ // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+ long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+ return internalInstanceField(getMirror(), thisObjectId, fieldId);
}
private long findField(VmMirror mirror, long classId, String fieldName,
@@ -1547,10 +1586,10 @@
return matchingFieldIds.getLong(0);
}
- private Value getField(VmMirror mirror, long classId, long fieldId) {
-
- CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
- ReferenceTypeCommandSet.GetValuesCommand);
+ private static Value internalStaticField(VmMirror mirror, long classId, long fieldId) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.GetValuesCommand);
commandPacket.setNextValueAsReferenceTypeID(classId);
commandPacket.setNextValueAsInt(1);
commandPacket.setNextValueAsFieldID(fieldId);
@@ -1565,6 +1604,23 @@
}
}
+ private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.GetValuesCommand);
+ commandPacket.setNextValueAsObjectID(objectId);
+ commandPacket.setNextValueAsInt(1);
+ commandPacket.setNextValueAsFieldID(fieldId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+
+ int fieldsCount = replyPacket.getNextValueAsInt();
+ assert fieldsCount == 1;
+ Value result = replyPacket.getNextValueAsValue();
+ Assert.assertTrue(replyPacket.isAllDataRead());
+ return result;
+ }
+
public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
String localName) {
return getVariablesAt(mirror, location).stream()
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
new file mode 100644
index 0000000..9991b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
@@ -0,0 +1,32 @@
+// 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.debug;
+
+public class LambdaOuterContextTest {
+
+ public interface Converter {
+ String convert(int value);
+ }
+
+ public int outer;
+
+ public LambdaOuterContextTest(int outer) {
+ this.outer = outer;
+ }
+
+ public void foo(Converter converter) {
+ System.out.println(converter.convert(outer));
+ }
+
+ public void bar(int arg) {
+ foo(value -> {
+ // Ensure lambda uses parts of the outer context, otherwise javac will optimize it out.
+ return Integer.toString(outer + value + arg);
+ });
+ }
+
+ public static void main(String[] args) {
+ new LambdaOuterContextTest(args.length + 42).bar(args.length);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
new file mode 100644
index 0000000..99a19f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -0,0 +1,66 @@
+// 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.debug;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LambdaOuterContextTestRunner extends DebugTestBase {
+
+ public static final Class<?> CLASS = LambdaOuterContextTest.class;
+ public static final String EXPECTED = StringUtils.lines("84");
+
+ @Test
+ public void testJvm() throws Throwable {
+ JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
+ jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(jvmTestBuilder.debugConfig());
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ D8TestCompileResult compileResult =
+ testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
+ compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig());
+ }
+
+ @Test
+ public void testR8Cf() throws Throwable {
+ R8TestCompileResult compileResult =
+ testForR8(Backend.CF)
+ .addProgramClassesAndInnerClasses(CLASS)
+ .debug()
+ .noMinification()
+ .noTreeShaking()
+ .compile();
+ compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig());
+ }
+
+ private void runDebugger(DebugTestConfig config) throws Throwable {
+ runDebugTest(
+ config,
+ CLASS,
+ breakpoint(methodFromMethod(CLASS.getMethod("foo", Converter.class))),
+ run(),
+ checkLine(19),
+ checkLocals("this", "converter"),
+ checkFieldOnThis("outer", null, Value.createInt(42)),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(25),
+ checkLocals("this", "value", "arg"),
+ checkNoLocal("outer"),
+ checkFieldOnThis("outer", null, Value.createInt(42)),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 1b413ba..f9e6c34 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -13,7 +13,6 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-// TODO(shertz) test local variables
@RunWith(Parameterized.class)
public class LambdaTest extends DebugTestBase {
@@ -47,8 +46,11 @@
run(),
checkMethod(debuggeeClass, initialMethodName),
checkLine(SOURCE_FILE, 12),
+ checkLocals("i"),
+ checkNoLocal("j"),
stepInto(INTELLIJ_FILTER),
checkLine(SOURCE_FILE, 16),
+ checkLocals("i", "j"),
run());
}
@@ -63,8 +65,10 @@
run(),
checkMethod(debuggeeClass, initialMethodName),
checkLine(SOURCE_FILE, 32),
+ checkLocals("i", "a", "b"),
stepInto(INTELLIJ_FILTER),
checkLine(SOURCE_FILE, 37),
+ checkLocals("a", "b"),
run());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
new file mode 100644
index 0000000..9e8741d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
@@ -0,0 +1,31 @@
+// 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 DefaultLambdaWithInvokeInterfaceTest {
+
+ public interface I {
+ String run();
+ }
+
+ public interface J {
+ default String stateless() {
+ return "hest";
+ }
+
+ default I stateful() {
+ return () -> {
+ String stateless = stateless();
+ return "stateful(" + stateless + ")";
+ };
+ }
+ }
+
+ public static void main(String[] args) {
+ 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
new file mode 100644
index 0000000..b89f09a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
@@ -0,0 +1,88 @@
+// 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 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 DebugTestBase {
+
+ final Class<?> CLASS = DefaultLambdaWithInvokeInterfaceTest.class;
+ 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 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 {
+ 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
new file mode 100644
index 0000000..e122450
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
@@ -0,0 +1,30 @@
+// 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 DefaultLambdaWithSelfReferenceTest {
+
+ interface I {
+ String foo();
+
+ default I stateless() {
+ return () -> "stateless";
+ }
+
+ default I stateful() {
+ return () -> {
+ I stateless = stateless();
+ String foo = stateless.foo();
+ return "stateful(" + foo + ")";
+ };
+ }
+ }
+
+ 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/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
new file mode 100644
index 0000000..e209e44
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -0,0 +1,153 @@
+// 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 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.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;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class DefaultLambdaWithSelfReferenceTestRunner extends DebugTestBase {
+
+ final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
+ final String EXPECTED = StringUtils.lines("stateful(stateless)");
+
+ 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 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)
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .writeToZip(out1)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+
+ Path outPerClassDir = temp.newFolder().toPath();
+ Collection<Path> innerClasses =
+ ToolHelper.getClassFilesForInnerClasses(Collections.singleton(CLASS));
+
+ int i = 0;
+ List<Path> outs = new ArrayList<>();
+ {
+ Path mainOut = outPerClassDir.resolve("class" + i++ + ".zip");
+ outs.add(mainOut);
+ testForD8()
+ .addProgramClasses(CLASS)
+ .addClasspathFiles(ToolHelper.getClassPathForTests())
+ .setIntermediate(true)
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .writeToZip(mainOut);
+ }
+ for (Path innerClass : innerClasses) {
+ Path out = outPerClassDir.resolve("class" + i++ + ".zip");
+ outs.add(out);
+ testForD8()
+ .addProgramFiles(innerClass)
+ .addClasspathFiles(ToolHelper.getClassPathForTests())
+ .setIntermediate(true)
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .writeToZip(out);
+ }
+
+ Path out2 = temp.newFolder().toPath().resolve("out2.zip");
+ D8TestCompileResult compiledResult = testForD8()
+ .addProgramFiles(outs)
+ .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(
+ DisassembleCommand.builder().addProgramFiles(out1).setOutputPath(dissasemble1).build());
+ Disassemble.disassemble(
+ DisassembleCommand.builder().addProgramFiles(out2).setOutputPath(dissasemble2).build());
+ String content1 = StringUtils.join(Files.readAllLines(dissasemble1), "\n");
+ String content2 = StringUtils.join(Files.readAllLines(dissasemble2), "\n");
+ assertEquals(content1, content2);
+ }
+}
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());
+ }
+}