blob: 961be33534088af72960379fb3d7e2bd26eb21a9 [file] [log] [blame]
// 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"));
}
}