| // Copyright (c) 2018, 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.code; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNull; |
| |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.VmTestRunner; |
| import com.android.tools.r8.jasmin.JasminBuilder; |
| import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.google.common.collect.ImmutableList; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.concurrent.ExecutionException; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| @RunWith(VmTestRunner.class) |
| public class NativeMethodWithCodeTest extends TestBase { |
| |
| // Test that D8 removes code from native methods (to match the behavior of dx). |
| // Note that the JVM rejects a class if it has a native method with a code attribute, |
| // but D8 has to handle that and cannot simply throw an error even though the JVM does. |
| |
| @Test |
| public void test() throws Exception { |
| JasminBuilder jasminBuilder = new JasminBuilder(); |
| |
| ClassBuilder cls = jasminBuilder.addClass("Main"); |
| cls.addDefaultConstructor(); |
| cls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V", |
| ".limit stack 3", |
| ".limit locals 2", |
| "getstatic java/lang/System/out Ljava/io/PrintStream;", |
| "aload_1", |
| "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V", |
| "return"); |
| cls.addNativeMethodWithCode("n1", ImmutableList.of("Ljava/lang/String;"), "V", |
| "return"); |
| cls.addNativeMethodWithCode("n2", ImmutableList.of("Ljava/lang/String;"), "V", |
| "return"); |
| cls.addNativeMethodWithCode("n3", ImmutableList.of("Ljava/lang/String;"), "V", |
| "return"); |
| cls.addNativeMethodWithCode("n4", ImmutableList.of("Ljava/lang/String;"), "V", |
| "return"); |
| cls.addMainMethod( |
| ".limit stack 4", |
| ".limit locals 2", |
| "new " + cls.name, |
| "dup", |
| "invokespecial " + cls.name + "/<init>()V", |
| "astore_0", |
| "aload_0", |
| "ldc \"foo\"", |
| "invokevirtual " + cls.name + "/foo(Ljava/lang/String;)V", |
| "return"); |
| |
| String mainClassName = cls.name; |
| |
| Path inputJar = temp.getRoot().toPath().resolve("input.jar"); |
| jasminBuilder.writeJar(inputJar, null); |
| AndroidApp inputApp = AndroidApp.builder().addProgramFiles(inputJar).build(); |
| // TODO(mathiasr): Consider making frontend read in code on native methods. |
| // If we do that, this assertNull should change to assertNotNull. |
| assertNull(getNativeMethod(mainClassName, inputApp).getMethod().getCode()); |
| |
| // JVM throws ClassFormatError because the input contains code on a native method. (JVM8ยง4.7.3) |
| ProcessResult javaResult = ToolHelper.runJava(inputJar, mainClassName); |
| assertNotEquals(0, javaResult.exitCode); |
| assertThat(javaResult.stderr, containsString("ClassFormatError")); |
| |
| // DX strips code from native methods. |
| Path dxOutputDir = temp.newFolder().toPath(); |
| Path dxOutputFile = dxOutputDir.resolve("classes.dex"); |
| ToolHelper.runDexer(inputJar.toString(), dxOutputDir.toString()); |
| AndroidApp dxApp = AndroidApp.builder().addProgramFiles(dxOutputFile).build(); |
| assertNull(getNativeMethod(mainClassName, dxApp).getMethod().getCode()); |
| |
| // D8 should also strip code from native methods. |
| AndroidApp processedApp = compileWithD8(inputApp); |
| assertThat(getNativeMethod(mainClassName, processedApp), isPresent()); |
| // TODO(mathiasr): Consider making D8 maintain code on native methods. |
| // If we do that, this assertNull should change to assertNotNull. |
| assertNull(getNativeMethod(mainClassName, processedApp).getMethod().getCode()); |
| |
| ProcessResult artResult = runOnArtRaw(processedApp, mainClassName); |
| assertEquals(0, artResult.exitCode); |
| assertThat(artResult.stdout, containsString("foo")); |
| } |
| |
| private MethodSubject getNativeMethod(String mainClassName, AndroidApp processedApp) |
| throws IOException, ExecutionException { |
| CodeInspector inspector = new CodeInspector(processedApp); |
| ClassSubject mainSubject = inspector.clazz(mainClassName); |
| return mainSubject.method("void", "n1", ImmutableList.of("java.lang.String")); |
| } |
| } |