| // Copyright (c) 2019, 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; |
| |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.hamcrest.core.StringContains.containsString; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import com.android.tools.r8.utils.InternalOptions; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| |
| @RunWith(Parameterized.class) |
| public class FailCompilationOnFutureVersionsTest extends TestBase { |
| |
| static final int UNSUPPORTED_CF_VERSION = InternalOptions.SUPPORTED_CF_VERSION.major() + 1; |
| static final int UNSUPPORTED_DEX_VERSION = InternalOptions.SUPPORTED_DEX_VERSION + 1; |
| |
| private final TestParameters parameters; |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withDexRuntimes().withAllApiLevels().build(); |
| } |
| |
| public FailCompilationOnFutureVersionsTest(TestParameters parameters) { |
| this.parameters = parameters; |
| assertTrue("Test assumes the first DEX version char is '0'.", UNSUPPORTED_DEX_VERSION < 100); |
| } |
| |
| @Test |
| public void testDex() throws CompilationFailedException, IOException { |
| // Generate a DEX file with a version higher than the supported one. |
| Path out = |
| testForD8() |
| .addProgramClasses(TestClass.class) |
| .setMinApi(parameters.getApiLevel()) |
| .addOptionsModification( |
| options -> |
| options.testing.forceDexVersionBytes = |
| ("0" + UNSUPPORTED_DEX_VERSION).getBytes()) |
| .compile() |
| .writeToZip(); |
| try { |
| testForD8() |
| .addProgramFiles(out) |
| .setMinApi(parameters.getApiLevel()) |
| .compileWithExpectedDiagnostics( |
| diagnotics -> { |
| diagnotics.assertOnlyErrors(); |
| diagnotics.assertErrorsCount(1); |
| assertThat( |
| diagnotics.getErrors().get(0).getDiagnosticMessage(), |
| containsString("Unsupported DEX file version: 0" + UNSUPPORTED_DEX_VERSION)); |
| }); |
| } catch (CompilationFailedException e) { |
| return; |
| } |
| fail("Expected compilation error"); |
| } |
| |
| @Test |
| public void testCf() { |
| try { |
| testForD8() |
| .addProgramClassFileData(CfDump.dump()) |
| .setMinApi(parameters.getApiLevel()) |
| .compileWithExpectedDiagnostics( |
| diagnotics -> { |
| diagnotics.assertOnlyErrors(); |
| diagnotics.assertErrorsCount(1); |
| assertThat( |
| diagnotics.getErrors().get(0).getDiagnosticMessage(), |
| containsString("Unsupported class file version: " + UNSUPPORTED_CF_VERSION)); |
| assertTrue( |
| diagnotics.getErrors().stream() |
| .allMatch( |
| s -> |
| s.getDiagnosticMessage() |
| .toLowerCase() |
| .contains("unsupported class file version"))); |
| }); |
| } catch (CompilationFailedException e) { |
| return; |
| } |
| fail("Expected compilation error"); |
| } |
| |
| public static class CfDump implements Opcodes { |
| |
| public static byte[] dump() { |
| // Generate a class file with a version higher than the supported one. |
| ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); |
| MethodVisitor mv; |
| cw.visit( |
| UNSUPPORTED_CF_VERSION, ACC_PUBLIC + ACC_SUPER, "Test", null, "java/lang/Object", null); |
| { |
| mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); |
| mv.visitCode(); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); |
| mv.visitInsn(RETURN); |
| mv.visitMaxs(1, 1); |
| mv.visitEnd(); |
| } |
| cw.visitEnd(); |
| return cw.toByteArray(); |
| } |
| } |
| |
| public static class TestClass { |
| // Intentionally empty stub class for the DEX generation. |
| } |
| } |