blob: 706ace94ed2d14d2e5a395151e8fedf65a1d952f [file] [log] [blame]
// Copyright (c) 2022, 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.synthesis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestBuilder;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.transformers.ClassTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ByteVector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
@RunWith(Parameterized.class)
public class SyntheticMarkerCfTest extends TestBase {
static final String EXPECTED = StringUtils.lines("Hello, world");
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters()
.withDefaultCfRuntime()
.withApiLevel(AndroidApiLevel.B)
.enableApiLevelsForCf()
.build();
}
public SyntheticMarkerCfTest(TestParameters parameters) {
this.parameters = parameters;
}
/**
* Mirror of the initial D8 synthetic marker format.
*
* <p>The legacy marker just had the synthetic kind id as payload.
*/
private static class SyntheticMarkerV1 extends Attribute {
static final SyntheticMarkerV1 PROTO = new SyntheticMarkerV1((short) 0);
final short kindId;
public SyntheticMarkerV1(short kindId) {
super("com.android.tools.r8.SynthesizedClass");
this.kindId = kindId;
}
@Override
protected Attribute read(
ClassReader classReader,
int offset,
int length,
char[] charBuffer,
int codeAttributeOffset,
Label[] labels) {
short id = classReader.readShort(offset);
return new SyntheticMarkerV1(id);
}
@Override
protected ByteVector write(
ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
ByteVector byteVector = new ByteVector();
byteVector.putShort(kindId);
return byteVector;
}
}
/**
* Format of the current synthetic marker.
*
* <p>The marker is distinguished by a new attribute type name.
*
* <p>The payload is the kind id, version hash length and then version hash bytes.
*/
private static class SyntheticMarkerV2 extends Attribute {
static final SyntheticMarkerV2 PROTO = new SyntheticMarkerV2((short) 0, null);
final short kindId;
final byte[] versionHash;
public SyntheticMarkerV2(short kindId, byte[] versionHash) {
super("com.android.tools.r8.SynthesizedClassV2");
this.versionHash = versionHash;
this.kindId = kindId;
}
@Override
protected Attribute read(
ClassReader classReader,
int offset,
int length,
char[] charBuffer,
int codeAttributeOffset,
Label[] labels) {
short kindId = classReader.readShort(offset);
offset += 2;
short versionLength = classReader.readShort(offset);
offset += 2;
byte[] versionBytes = new byte[versionLength];
for (int i = 0; i < versionLength; i++) {
versionBytes[i] = (byte) classReader.readByte(offset++);
}
return new SyntheticMarkerV2(kindId, versionBytes);
}
@Override
protected ByteVector write(
ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
ByteVector byteVector = new ByteVector();
byteVector.putShort(kindId);
byteVector.putShort(versionHash.length);
byteVector.putByteArray(versionHash, 0, versionHash.length);
return byteVector;
}
}
private static List<Attribute> readAttributes(byte[] bytes) {
List<Attribute> attributes = new ArrayList<>();
ClassReader reader = new ClassReader(bytes);
reader.accept(
new ClassVisitor(InternalOptions.ASM_VERSION) {
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public void visitAttribute(Attribute attribute) {
attributes.add(attribute);
}
},
new Attribute[] {SyntheticMarkerV1.PROTO, SyntheticMarkerV2.PROTO},
ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return attributes;
}
private byte[] getTestClassWithMarker(Attribute marker) throws IOException {
return transformer(TestClass.class)
.addClassTransformer(
new ClassTransformer() {
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
super.visitAttribute(marker);
}
})
.transform();
}
/** Test that reads the correct marker from a compilation unit and fails if then manipulated. */
@Test
public void testInvalidMarkerFailsCompilation() throws Exception {
Box<SyntheticMarkerV2> currentCompilerMarker = new Box<>();
testForD8(parameters.getBackend())
.addProgramClasses(TestClass.class)
.setMinApi(parameters.getApiLevel())
.setIntermediate(true)
.setProgramConsumer(
new ClassFileConsumer() {
private final List<Attribute> attributes = new ArrayList<>();
@Override
public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
attributes.addAll(readAttributes(data.copyByteData()));
}
@Override
public void finished(DiagnosticsHandler handler) {
assertEquals(1, attributes.size());
assertEquals(SyntheticMarkerV2.PROTO.type, attributes.get(0).type);
currentCompilerMarker.set((SyntheticMarkerV2) attributes.get(0));
}
})
.compile()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
// Test that if a "current" marker with invalid content is given to the compiler it will
// cause a failure. This test ensures that we can witness the markers being ignored in the
// tests below.
D8TestBuilder builder =
testForD8(parameters.getBackend())
.setMinApi(parameters.getApiLevel())
.addProgramClassFileData(
getTestClassWithMarker(
new SyntheticMarkerV2(
Short.MAX_VALUE, currentCompilerMarker.get().versionHash)));
assertThrows(CompilationFailedException.class, builder::compile);
}
@Test
public void testIgnoreV1Markers() throws Exception {
// Test that inputs with a legacy marker will be ignored.
// We do so by injecting an old marker and put in a non-valid ID which would cause the compiler
// to fail if it was read.
testForD8(parameters.getBackend())
.setMinApi(parameters.getApiLevel())
.addProgramClassFileData(getTestClassWithMarker(new SyntheticMarkerV1(Short.MAX_VALUE)))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testIgnorePreviousV2Markers() throws Exception {
// Test that inputs with a V2 marker from a previous compiler version are ignored.
// We do so by injecting an old marker and put in a non-valid ID which would cause the compiler
// to fail if it was read.
testForD8(parameters.getBackend())
.setMinApi(parameters.getApiLevel())
.addProgramClassFileData(
getTestClassWithMarker(new SyntheticMarkerV2(Short.MAX_VALUE, new byte[0])))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
public static class TestClass {
public static void run(Runnable r) {
r.run();
}
public static void main(String[] args) {
run(() -> System.out.println("Hello, world"));
}
}
}