blob: b5dd40cde1d88aca36864236b702a6e7e1108f91 [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.dex;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestBuilder;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.DexSegments;
import com.android.tools.r8.DexSegments.SegmentInfo;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import org.junit.Test;
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.Opcodes;
@RunWith(Parameterized.class)
public class DexCodeInvokeSuperDeduppingTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
@Parameter(1)
public boolean enableMappingOutput;
@Parameters(name = "{0}, map output: {1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withDexRuntimesAndAllApiLevels().build(), BooleanUtils.values());
}
@Test
public void testD8MappingOutput() throws Exception {
testForD8(parameters.getBackend())
.addProgramClasses(Base.class, FooBase.class, BarBase.class, Main.class)
.addProgramClassFileData(getProgramClassFileData())
.setMinApi(parameters)
.release()
.applyIf(enableMappingOutput, D8TestBuilder::internalEnableMappingOutput)
.compile()
.apply(this::inspectCodeSegment)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("FooBase", "BarBase");
}
private List<byte[]> getProgramClassFileData() throws IOException {
return ImmutableList.of(
transformer(Foo.class)
.transformMethodInsnInMethod(
"foo",
(opcode, owner, name, descriptor, isInterface, visitor) -> {
if (opcode == Opcodes.INVOKESPECIAL) {
assertEquals(
"com/android/tools/r8/dex/DexCodeInvokeSuperDeduppingTest$FooBase", owner);
owner = binaryName(Base.class);
}
visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
})
.transform(),
transformer(Bar.class)
.transformMethodInsnInMethod(
"bar",
(opcode, owner, name, descriptor, isInterface, visitor) -> {
if (opcode == Opcodes.INVOKESPECIAL) {
assertEquals(
"com/android/tools/r8/dex/DexCodeInvokeSuperDeduppingTest$BarBase", owner);
owner = binaryName(Base.class);
}
visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
})
.transform());
}
private void inspectCodeSegment(D8TestCompileResult compileResult) throws Exception {
SegmentInfo codeSegmentInfo = getCodeSegmentInfo(compileResult.writeToZip());
int expectedCodeItems = 11;
if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S) && enableMappingOutput) {
// Main.<init> and Base.<init> are canonicalized, and so is FooBase.<init> and BarBase.<init>.
assertEquals(expectedCodeItems - 2, codeSegmentInfo.getItemCount());
} else {
assertEquals(expectedCodeItems, codeSegmentInfo.getItemCount());
}
}
public SegmentInfo getCodeSegmentInfo(Path path)
throws CompilationFailedException, ResourceException, IOException {
DexSegments.Command command = DexSegments.Command.builder().addProgramFiles(path).build();
Map<Integer, SegmentInfo> segmentInfoMap = DexSegments.runForTesting(command);
return segmentInfoMap.get(Constants.TYPE_CODE_ITEM);
}
static class Main {
public static void main(String[] args) {
new Foo().foo();
new Bar().bar();
}
}
abstract static class Base {
abstract void m();
}
static class FooBase extends Base {
@Override
void m() {
System.out.println("FooBase");
}
}
static class Foo extends FooBase {
void foo() {
// Symbolic holder transformed to Base class.
super.m();
}
}
static class BarBase extends Base {
@Override
void m() {
System.out.println("BarBase");
}
}
static class Bar extends BarBase {
void bar() {
// Symbolic holder transformed to Base class.
super.m();
}
}
}