blob: 01fd9297224218f9f97b29bec4c5d9c2c8e3e2ca [file]
// Copyright (c) 2025, 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.retrace.stacksamples;
import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import static com.android.tools.r8.utils.StringUtils.UNIX_LINE_SEPARATOR;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.R8TestCompileResultBase;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestDiagnosticMessagesImpl;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.retrace.ProguardMapProducer;
import com.android.tools.r8.retrace.RetraceClassResult;
import com.android.tools.r8.retrace.RetraceMethodElement;
import com.android.tools.r8.retrace.RetraceMethodResult;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
@RunWith(Parameterized.class)
public abstract class StackSampleRetraceTestBase extends TestBase {
@Parameter(0)
public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
void runTest(ThrowableConsumer<R8TestBuilder<?, ?, ?>> testBuilderConsumer) throws Exception {
testForR8(parameters)
.addKeepClassRulesWithAllowObfuscation(getMainClass())
.addKeepRules(
"-keepclassmembers class " + getMainClass().getTypeName() + " {",
" public static void main(java.lang.String[]);",
"}")
.addHorizontallyMergedClassesInspector(this::inspectHorizontallyMergedClasses)
.apply(testBuilderConsumer)
.compile()
.inspect(this::inspectCode)
.applyIf(parameters.isDexRuntime(), this::inspectMap)
.apply(this::testRetrace)
.run(parameters.getRuntime(), getMainClass())
.assertSuccessWithOutput(getExpectedOutput());
}
abstract Class<?> getMainClass();
abstract String getExpectedMap();
abstract String getExpectedOutput();
abstract void inspectCode(CodeInspector inspector);
void inspectHorizontallyMergedClasses(HorizontallyMergedClassesInspector inspector) {}
abstract void testRetrace(R8TestCompileResultBase<?> compileResult) throws Exception;
private void inspectMap(R8TestCompileResultBase<?> compileResult) {
assertEquals(getExpectedMap(), getMapWithoutHeader(compileResult));
}
RetraceMethodElement getSingleRetraceMethodElement(
ClassReference obfuscatedClassReference,
String obfuscatedMethodName,
R8TestCompileResultBase<?> compileResult) {
List<RetraceMethodElement> retraceMethodElements =
getRetraceMethodElements(obfuscatedClassReference, obfuscatedMethodName, compileResult);
assertEquals(1, retraceMethodElements.size());
return retraceMethodElements.get(0);
}
List<RetraceMethodElement> getRetraceMethodElements(
ClassReference obfuscatedClassReference,
String obfuscatedMethodName,
R8TestCompileResultBase<?> compileResult) {
TestDiagnosticMessages diagnostics = new TestDiagnosticMessagesImpl();
Retracer retracer =
Retracer.createDefault(
ProguardMapProducer.fromString(compileResult.getProguardMap()), diagnostics);
RetraceClassResult retraceClassResult = retracer.retraceClass(obfuscatedClassReference);
RetraceMethodResult retraceMethodResult = retraceClassResult.lookupMethod(obfuscatedMethodName);
List<RetraceMethodElement> retraceMethodElements =
retraceMethodResult.stream().collect(Collectors.toList());
diagnostics.assertNoMessages();
return retraceMethodElements;
}
static int getFirstLineNumber(Class<?> clazz) {
return getFirstLineNumber(ToolHelper.getClassAsBytes(clazz));
}
static int getFirstLineNumber(byte[] classFileData) {
ClassReader reader = new ClassReader(classFileData);
IntBox result = new IntBox(Integer.MAX_VALUE);
reader.accept(
new ClassVisitor(ASM_VERSION) {
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor subvisitor =
super.visitMethod(access, name, descriptor, signature, exceptions);
return new MethodVisitor(ASM_VERSION, subvisitor) {
@Override
public void visitLineNumber(int line, Label start) {
super.visitLineNumber(line, start);
result.setMin(line);
}
};
}
},
0);
return result.get();
}
static String getMapWithoutHeader(R8TestCompileResultBase<?> compileResult) {
BooleanBox pastHeader = new BooleanBox();
return StringUtils.splitLines(compileResult.getProguardMap()).stream()
.filter(
line -> {
if (line.startsWith("#") && pastHeader.isFalse()) {
return false;
} else {
pastHeader.set();
return true;
}
})
.collect(Collectors.joining(UNIX_LINE_SEPARATOR));
}
}