blob: e23ac8f80a9eb4d191d7a37225aa34d562523a43 [file] [log] [blame]
// Copyright (c) 2020, 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.kotlin.metadata;
import static com.android.tools.r8.ToolHelper.getKotlinC_1_3_72;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.objectweb.asm.Opcodes.ASM7;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.transformers.ClassTransformer;
import com.android.tools.r8.utils.StreamUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.io.IOException;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.AnnotationVisitor;
@RunWith(Parameterized.class)
public class MetadataVersionNumberBumpTest extends KotlinMetadataTestBase {
private final TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public MetadataVersionNumberBumpTest(TestParameters parameters) {
super(KotlinTargetVersion.JAVA_8, getKotlinC_1_3_72());
this.parameters = parameters;
}
@Test
public void testLessThan1_4() throws Exception {
final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 1, 16});
testBuilder
.setMinApi(parameters.getApiLevel())
.addKeepAllClassesRule()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.addDontWarnJetBrainsAnnotations()
.compile()
.inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
}
@Test
public void testEqualTo1_4() throws Exception {
final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 0});
testBuilder
.setMinApi(parameters.getApiLevel())
.addKeepAllClassesRule()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.addDontWarnJetBrainsAnnotations()
.compile()
.inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
}
@Test
public void testGreaterThan1_4() throws Exception {
final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 2});
testBuilder
.setMinApi(parameters.getApiLevel())
.addKeepAllClassesRule()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.addDontWarnJetBrainsAnnotations()
.compile()
.inspect(inspector -> inspectMetadataVersion(inspector, "1.4.2"));
}
private void rewriteMetadataVersion(Consumer<byte[]> rewrittenBytesConsumer, int[] newVersion)
throws IOException {
ZipUtils.iter(
ToolHelper.getKotlinStdlibJar(kotlinc).toString(),
((entry, input) -> {
if (!entry.getName().endsWith(".class")) {
return;
}
final byte[] bytes = StreamUtils.StreamToByteArrayClose(input);
final byte[] rewrittenBytes =
transformMetadataVersion(
entry.getName().substring(0, entry.getName().length() - 6), bytes, newVersion);
rewrittenBytesConsumer.accept(rewrittenBytes);
}));
}
private byte[] transformMetadataVersion(String descriptor, byte[] bytes, int[] newVersion) {
return transformer(bytes, Reference.classFromDescriptor(descriptor))
.addClassTransformer(
new ClassTransformer() {
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
if (!descriptor.equals("Lkotlin/Metadata;")) {
return super.visitAnnotation(descriptor, visible);
} else {
return new AnnotationVisitor(ASM7, super.visitAnnotation(descriptor, visible)) {
@Override
public void visit(String name, Object value) {
if (name.equals("mv")) {
super.visit(name, newVersion);
} else {
super.visit(name, value);
}
}
};
}
}
})
.transform();
}
private void inspectMetadataVersion(CodeInspector inspector, String expectedVersion) {
for (FoundClassSubject clazz : inspector.allClasses()) {
verifyExpectedVersionForClass(clazz, expectedVersion);
}
}
private void verifyExpectedVersionForClass(FoundClassSubject clazz, String expectedVersion) {
final AnnotationSubject annotationSubject = clazz.annotation("kotlin.Metadata");
// TODO(b/164418977): All classes should have an annotation?
if (!annotationSubject.isPresent()) {
return;
}
final DexAnnotationElement[] elements = annotationSubject.getAnnotation().elements;
for (DexAnnotationElement element : elements) {
if (!element.name.toString().equals("mv")) {
continue;
}
final String version =
Arrays.stream(element.value.asDexValueArray().getValues())
.map(val -> val.asDexValueInt().value + "")
.collect(Collectors.joining("."));
assertEquals(expectedVersion, version);
return;
}
fail("Could not find the mv (metadataVersion) element");
}
}