blob: ecbe2e453a96d9d8d5dafabac73b9ce5ec4ed93d [file] [log] [blame]
// Copyright (c) 2023, 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.debuginfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.debug.DebugTestBase;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.graph.DexDebugEvent.Default;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Arrays;
import java.util.List;
import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
import org.apache.harmony.jpda.tests.framework.jdwp.VmMirror;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class LocalVariableTableForParameterWithSignatureTest extends DebugTestBase {
private final TestParameters parameters;
private final boolean startBeforeDefault;
@Parameterized.Parameters(name = "{0}, startBeforeDefault:{1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters()
.withDefaultCfRuntime()
// VMs 4.0 and 4.4 hang on exit so skip testing on those. Their locals are the same as
// VMs 5.1 and 6.0.
.withDexRuntimesStartingFromIncluding(Version.V5_1_1)
.withMinimumApiLevel()
.build(),
BooleanUtils.values());
}
public LocalVariableTableForParameterWithSignatureTest(
TestParameters parameters, boolean startBeforeDefault) {
this.parameters = parameters;
this.startBeforeDefault = startBeforeDefault;
}
@Test
public void test() throws Exception {
// Only run one configuration in CF.
assumeTrue(parameters.isDexRuntime() || startBeforeDefault);
testForD8(parameters.getBackend())
.addProgramClasses(TestClass.class)
.applyIf(
parameters.isDexRuntime(),
b ->
b.setMinApi(parameters)
.addOptionsModification(
o -> o.testing.emitDebugLocalStartBeforeDefaultEvent = startBeforeDefault))
.compile()
.inspect(this::checkDebugInfo)
.apply(b -> runDebugger(b.debugConfig(parameters.getRuntime())));
}
private void checkDebugInfo(CodeInspector inspector) throws NoSuchMethodException {
if (parameters.isCfRuntime()) {
return;
}
MethodSubject method =
inspector.method(TestClass.class.getMethod("fun", int.class, List.class));
EventBasedDebugInfo debugInfo =
method.getMethod().getCode().asDexCode().getDebugInfo().asEventBasedInfo();
assertEquals("value", debugInfo.parameters[0].toString());
assertNull(debugInfo.parameters[1]);
assertEquals(2, debugInfo.parameters.length);
Default defaultEvent = (Default) debugInfo.events[startBeforeDefault ? 1 : 0];
StartLocal startEvent = (StartLocal) debugInfo.events[startBeforeDefault ? 0 : 1];
assertEquals(inspector.getFactory().zeroChangeDefaultEvent, defaultEvent);
}
private void runDebugger(DebugTestConfig debugConfig) throws Throwable {
runDebugTest(
debugConfig,
TestClass.class,
breakpoint(Reference.methodFromMethod(TestClass.class.getMethod("main", String[].class))),
run(),
inspect(
inspector -> {
VmMirror mirror = inspector.getMirror();
long classID = mirror.getClassID(inspector.getClassSignature());
long methodID = mirror.getMethodID(classID, "fun");
List<Variable> variableTable = mirror.getVariableTable(classID, methodID);
boolean hasEmptyRange = false;
boolean hasParamValue = false;
boolean hasParamStringsWithSignature = false;
for (Variable variable : variableTable) {
if (variable.getLength() == 0) {
hasEmptyRange = true;
} else if ("value".equals(variable.getName())) {
hasParamValue = true;
} else if ("strings".equals(variable.getName())) {
assertEquals(
"Ljava/util/List<Ljava/lang/String;>;", variable.getGenericSignature());
hasParamStringsWithSignature = true;
} else {
fail("Unexpected variable");
}
}
assertTrue(hasParamValue);
assertTrue(hasParamStringsWithSignature);
if (parameters.isCfRuntime()
|| parameters.isDexRuntimeVersionOlderThanOrEqual(Version.V6_0_1)) {
// CF runtimes and the old DEX runtimes report the correct local variable table.
// The variable table should be just the two parameters.
assertEquals(2, variableTable.size());
assertFalse(hasEmptyRange);
} else {
// Newer ART runtimes report a variable with an empty range. That variable is the
// parameter without a signature, e.g., List, and is ended immediately as a variable
// is started that also includes the signature, e.g., List<String>.
assertEquals(variableTable.toString(), 3, variableTable.size());
assertTrue(hasEmptyRange);
}
}),
run());
}
static class TestClass {
public static void fun(int value, List<String> strings) {
System.out.println("" + value + strings);
}
public static void main(String[] args) {
fun(42, Arrays.asList(args));
}
}
}