509 lines
17 KiB
Java
509 lines
17 KiB
Java
|
/*
|
||
|
* Copyright (C) 2013 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
import java.lang.reflect.Constructor;
|
||
|
import java.lang.reflect.InvocationHandler;
|
||
|
import java.lang.reflect.InvocationTargetException;
|
||
|
import java.lang.reflect.Method;
|
||
|
import java.lang.reflect.Field;
|
||
|
import java.lang.reflect.Proxy;
|
||
|
import java.util.regex.Pattern;
|
||
|
|
||
|
import dalvik.annotation.optimization.CriticalNative;
|
||
|
import dalvik.annotation.optimization.FastNative;
|
||
|
|
||
|
public class Main {
|
||
|
public static void main(String[] args) {
|
||
|
System.loadLibrary(args[0]);
|
||
|
|
||
|
if (!isSlowDebug()) {
|
||
|
throw new RuntimeException("Slow-debug flags unexpectedly off.");
|
||
|
}
|
||
|
|
||
|
testFieldSubclass();
|
||
|
testFindClassOnAttachedNativeThread();
|
||
|
testFindFieldOnAttachedNativeThread();
|
||
|
testReflectFieldGetFromAttachedNativeThreadNative();
|
||
|
testCallStaticVoidMethodOnSubClass();
|
||
|
testGetMirandaMethod();
|
||
|
testZeroLengthByteBuffers();
|
||
|
testByteMethod();
|
||
|
testShortMethod();
|
||
|
testBooleanMethod();
|
||
|
testCharMethod();
|
||
|
testIsAssignableFromOnPrimitiveTypes();
|
||
|
testShallowGetCallingClassLoader();
|
||
|
testShallowGetStackClass2();
|
||
|
testCallNonvirtual();
|
||
|
testNewStringObject();
|
||
|
testRemoveLocalObject();
|
||
|
testProxyGetMethodID();
|
||
|
testJniCriticalSectionAndGc();
|
||
|
testCallDefaultMethods();
|
||
|
String lambda = "λ";
|
||
|
testInvokeLambdaMethod(() -> { System.out.println("hi-lambda: " + lambda); });
|
||
|
String def = "δ";
|
||
|
testInvokeLambdaDefaultMethod(() -> { System.out.println("hi-default " + def + lambda); });
|
||
|
|
||
|
registerNativesJniTest();
|
||
|
testFastNativeMethods();
|
||
|
testCriticalNativeMethods();
|
||
|
|
||
|
testClinitMethodLookup();
|
||
|
|
||
|
testDoubleLoad(args[0]);
|
||
|
testUTFRegion("\0\0\0");
|
||
|
|
||
|
testBadFastCriticalNatives();
|
||
|
}
|
||
|
|
||
|
static class ABC { public static int XYZ = 12; }
|
||
|
static class DEF extends ABC {}
|
||
|
public static void testFieldSubclass() {
|
||
|
try {
|
||
|
System.out.println("ABC.XYZ = " + ABC.XYZ + ", GetStaticIntField(DEF.class, 'XYZ') = " +
|
||
|
getFieldSubclass(ABC.class.getDeclaredField("XYZ"), DEF.class));
|
||
|
} catch (Exception e) {
|
||
|
throw new RuntimeException("Failed to test get static field on a subclass", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static native void testUTFRegion(String null_str);
|
||
|
|
||
|
public static native int getFieldSubclass(Field f, Class sub);
|
||
|
|
||
|
private static native boolean registerNativesJniTest();
|
||
|
|
||
|
private static native void testCallDefaultMethods();
|
||
|
|
||
|
private static native void testFindClassOnAttachedNativeThread();
|
||
|
|
||
|
private static boolean testFindFieldOnAttachedNativeThreadField;
|
||
|
|
||
|
private static native void testReflectFieldGetFromAttachedNativeThreadNative();
|
||
|
|
||
|
public static boolean testReflectFieldGetFromAttachedNativeThreadField;
|
||
|
|
||
|
private static void testFindFieldOnAttachedNativeThread() {
|
||
|
testFindFieldOnAttachedNativeThreadNative();
|
||
|
if (!testFindFieldOnAttachedNativeThreadField) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native void testFindFieldOnAttachedNativeThreadNative();
|
||
|
|
||
|
private static void testCallStaticVoidMethodOnSubClass() {
|
||
|
testCallStaticVoidMethodOnSubClassNative();
|
||
|
if (!testCallStaticVoidMethodOnSubClass_SuperClass.executed) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native void testCallStaticVoidMethodOnSubClassNative();
|
||
|
|
||
|
private static class testCallStaticVoidMethodOnSubClass_SuperClass {
|
||
|
private static boolean executed = false;
|
||
|
private static void execute() {
|
||
|
executed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static class testCallStaticVoidMethodOnSubClass_SubClass
|
||
|
extends testCallStaticVoidMethodOnSubClass_SuperClass {
|
||
|
}
|
||
|
|
||
|
private static native Method testGetMirandaMethodNative();
|
||
|
|
||
|
private static void testGetMirandaMethod() {
|
||
|
Method m = testGetMirandaMethodNative();
|
||
|
if (m.getDeclaringClass() != testGetMirandaMethod_MirandaInterface.class) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native void testZeroLengthByteBuffers();
|
||
|
|
||
|
private static abstract class testGetMirandaMethod_MirandaAbstract implements testGetMirandaMethod_MirandaInterface {
|
||
|
public boolean inAbstract() {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static interface testGetMirandaMethod_MirandaInterface {
|
||
|
public boolean inInterface();
|
||
|
}
|
||
|
|
||
|
// Test sign-extension for values < 32b
|
||
|
|
||
|
static native byte byteMethod(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7,
|
||
|
byte b8, byte b9, byte b10);
|
||
|
|
||
|
private static void testByteMethod() {
|
||
|
byte returns[] = { 0, 1, 2, 127, -1, -2, -128 };
|
||
|
for (int i = 0; i < returns.length; i++) {
|
||
|
byte result = byteMethod((byte)i, (byte)2, (byte)(-3), (byte)4, (byte)(-5), (byte)6,
|
||
|
(byte)(-7), (byte)8, (byte)(-9), (byte)10);
|
||
|
if (returns[i] != result) {
|
||
|
System.out.println("Run " + i + " with " + returns[i] + " vs " + result);
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native void removeLocalObject(Object o);
|
||
|
|
||
|
private static void testRemoveLocalObject() {
|
||
|
removeLocalObject(new Object());
|
||
|
}
|
||
|
|
||
|
private static native short shortMethod(short s1, short s2, short s3, short s4, short s5, short s6, short s7,
|
||
|
short s8, short s9, short s10);
|
||
|
|
||
|
private static void testShortMethod() {
|
||
|
short returns[] = { 0, 1, 2, 127, 32767, -1, -2, -128, -32768 };
|
||
|
for (int i = 0; i < returns.length; i++) {
|
||
|
short result = shortMethod((short)i, (short)2, (short)(-3), (short)4, (short)(-5), (short)6,
|
||
|
(short)(-7), (short)8, (short)(-9), (short)10);
|
||
|
if (returns[i] != result) {
|
||
|
System.out.println("Run " + i + " with " + returns[i] + " vs " + result);
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Test zero-extension for values < 32b
|
||
|
|
||
|
private static native boolean booleanMethod(boolean b1, boolean b2, boolean b3, boolean b4, boolean b5, boolean b6, boolean b7,
|
||
|
boolean b8, boolean b9, boolean b10);
|
||
|
|
||
|
private static void testBooleanMethod() {
|
||
|
if (booleanMethod(false, true, false, true, false, true, false, true, false, true)) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
|
||
|
if (!booleanMethod(true, true, false, true, false, true, false, true, false, true)) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native char charMethod(char c1, char c2, char c3, char c4, char c5, char c6, char c7,
|
||
|
char c8, char c9, char c10);
|
||
|
|
||
|
private static void testCharMethod() {
|
||
|
char returns[] = { (char)0, (char)1, (char)2, (char)127, (char)255, (char)256, (char)15000,
|
||
|
(char)34000 };
|
||
|
for (int i = 0; i < returns.length; i++) {
|
||
|
char result = charMethod((char)i, 'a', 'b', 'c', '0', '1', '2', (char)1234, (char)2345,
|
||
|
(char)3456);
|
||
|
if (returns[i] != result) {
|
||
|
System.out.println("Run " + i + " with " + (int)returns[i] + " vs " + (int)result);
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// http://b/16531674
|
||
|
private static void testIsAssignableFromOnPrimitiveTypes() {
|
||
|
if (!nativeIsAssignableFrom(int.class, Integer.TYPE)) {
|
||
|
System.out.println("IsAssignableFrom(int.class, Integer.TYPE) returned false, expected true");
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
|
||
|
if (!nativeIsAssignableFrom(Integer.TYPE, int.class)) {
|
||
|
System.out.println("IsAssignableFrom(Integer.TYPE, int.class) returned false, expected true");
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native boolean nativeIsAssignableFrom(Class<?> from, Class<?> to);
|
||
|
|
||
|
private static void testShallowGetCallingClassLoader() {
|
||
|
nativeTestShallowGetCallingClassLoader();
|
||
|
}
|
||
|
|
||
|
private native static void nativeTestShallowGetCallingClassLoader();
|
||
|
|
||
|
private static void testShallowGetStackClass2() {
|
||
|
nativeTestShallowGetStackClass2();
|
||
|
}
|
||
|
|
||
|
private static native void nativeTestShallowGetStackClass2();
|
||
|
|
||
|
private static native void testCallNonvirtual();
|
||
|
|
||
|
private static native void testNewStringObject();
|
||
|
|
||
|
private interface SimpleInterface {
|
||
|
void a();
|
||
|
}
|
||
|
|
||
|
private static class MinimalInvocationHandler implements InvocationHandler {
|
||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void testProxyGetMethodID() {
|
||
|
InvocationHandler handler = new MinimalInvocationHandler();
|
||
|
SimpleInterface proxy =
|
||
|
(SimpleInterface) Proxy.newProxyInstance(SimpleInterface.class.getClassLoader(),
|
||
|
new Class<?>[] {SimpleInterface.class}, handler);
|
||
|
if (testGetMethodID(SimpleInterface.class) == 0) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
if (testGetMethodID(proxy.getClass()) == 0) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native long testGetMethodID(Class<?> c);
|
||
|
|
||
|
// Exercise GC and JNI critical sections in parallel.
|
||
|
private static void testJniCriticalSectionAndGc() {
|
||
|
Thread runGcThread = new Thread(new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
for (int i = 0; i < 10; ++i) {
|
||
|
Runtime.getRuntime().gc();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
Thread jniCriticalThread = new Thread(new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
final int arraySize = 32;
|
||
|
byte[] array0 = new byte[arraySize];
|
||
|
byte[] array1 = new byte[arraySize];
|
||
|
enterJniCriticalSection(arraySize, array0, array1);
|
||
|
}
|
||
|
});
|
||
|
jniCriticalThread.start();
|
||
|
runGcThread.start();
|
||
|
try {
|
||
|
jniCriticalThread.join();
|
||
|
runGcThread.join();
|
||
|
} catch (InterruptedException ignored) {}
|
||
|
}
|
||
|
|
||
|
private static native void enterJniCriticalSection(int arraySize, byte[] array0, byte[] array);
|
||
|
|
||
|
private static native void testInvokeLambdaMethod(LambdaInterface iface);
|
||
|
|
||
|
private static native void testInvokeLambdaDefaultMethod(LambdaInterface iface);
|
||
|
|
||
|
// Test invoking @FastNative methods works correctly.
|
||
|
|
||
|
// Return sum of a+b+c.
|
||
|
@FastNative
|
||
|
static native int intFastNativeMethod(int a, int b, int c);
|
||
|
|
||
|
private static void testFastNativeMethods() {
|
||
|
int returns[] = { 0, 3, 6, 9, 12 };
|
||
|
for (int i = 0; i < returns.length; i++) {
|
||
|
int result = intFastNativeMethod(i, i, i);
|
||
|
if (returns[i] != result) {
|
||
|
System.out.println("FastNative Int Run " + i + " with " + returns[i] + " vs " + result);
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Smoke test for @CriticalNative
|
||
|
// TODO: Way more thorough tests since it involved quite a bit of changes.
|
||
|
|
||
|
// Return sum of a+b+c.
|
||
|
@CriticalNative
|
||
|
static native int intCriticalNativeMethod(int a, int b, int c);
|
||
|
|
||
|
private static void testCriticalNativeMethods() {
|
||
|
int returns[] = { 3, 6, 9, 12, 15 };
|
||
|
for (int i = 0; i < returns.length; i++) {
|
||
|
int result = intCriticalNativeMethod(i, i+1, i+2);
|
||
|
if (returns[i] != result) {
|
||
|
System.out.println("CriticalNative Int Run " + i + " with " + returns[i] + " vs " + result);
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static native boolean isSlowDebug();
|
||
|
|
||
|
private static void testClinitMethodLookup() {
|
||
|
// Expect this to print <NSME Exception>
|
||
|
try {
|
||
|
System.out.println("Clinit Lookup: ClassWithoutClinit: " + methodString(lookupClinit(ClassWithoutClinit.class)));
|
||
|
} catch (NoSuchMethodError e) {
|
||
|
System.out.println("Clinit Lookup: ClassWithoutClinit: <NSME Exception>");
|
||
|
}
|
||
|
// Expect this to print <clinit>
|
||
|
try {
|
||
|
System.out.println("Clinit Lookup: ClassWithClinit: " + methodString(lookupClinit(ClassWithClinit.class)));
|
||
|
} catch (NoSuchMethodError e) {
|
||
|
System.out.println("Clinit Lookup: ClassWithClinit: <NSME Exception>");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static String methodString(java.lang.reflect.Executable method) {
|
||
|
if (method == null) {
|
||
|
return "<<null>>";
|
||
|
} else {
|
||
|
return method.toString() + "(Class: " + method.getClass().toString() + ")";
|
||
|
}
|
||
|
}
|
||
|
private static native java.lang.reflect.Executable lookupClinit(Class kls);
|
||
|
|
||
|
private static class ClassWithoutClinit {
|
||
|
}
|
||
|
private static class ClassWithClinit {
|
||
|
static {}
|
||
|
}
|
||
|
|
||
|
private static void testDoubleLoad(String library) {
|
||
|
// Test that nothing observably happens on loading "library" again.
|
||
|
System.loadLibrary(library);
|
||
|
|
||
|
// Now load code in a separate classloader and try to let it load.
|
||
|
ClassLoader loader = createClassLoader();
|
||
|
try {
|
||
|
Class<?> aClass = loader.loadClass("A");
|
||
|
Method runMethod = aClass.getDeclaredMethod("run", String.class);
|
||
|
runMethod.invoke(null, library);
|
||
|
} catch (InvocationTargetException ite) {
|
||
|
if (ite.getCause() instanceof UnsatisfiedLinkError) {
|
||
|
if (!(loader instanceof java.net.URLClassLoader)) {
|
||
|
String msg = ite.getCause().getMessage();
|
||
|
String pattern = "^Shared library .*libarttest.* already opened by ClassLoader.*" +
|
||
|
"004-JniTest.jar.*; can't open in ClassLoader.*004-JniTest-ex.jar.*";
|
||
|
if (!Pattern.matches(pattern, msg)) {
|
||
|
throw new RuntimeException("Could not find pattern in message", ite.getCause());
|
||
|
}
|
||
|
}
|
||
|
System.out.println("Got UnsatisfiedLinkError for duplicate loadLibrary");
|
||
|
} else {
|
||
|
throw new RuntimeException(ite);
|
||
|
}
|
||
|
} catch (Throwable t) {
|
||
|
// Anything else just let die.
|
||
|
throw new RuntimeException(t);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static ClassLoader createClassLoader() {
|
||
|
String location = System.getenv("DEX_LOCATION");
|
||
|
try {
|
||
|
Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader");
|
||
|
Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class);
|
||
|
|
||
|
return (ClassLoader)ctor.newInstance(location + "/004-JniTest-ex.jar",
|
||
|
Main.class.getClassLoader());
|
||
|
} catch (ClassNotFoundException e) {
|
||
|
// Running on RI. Use URLClassLoader.
|
||
|
try {
|
||
|
return new java.net.URLClassLoader(
|
||
|
new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") });
|
||
|
} catch (Throwable t) {
|
||
|
throw new RuntimeException(t);
|
||
|
}
|
||
|
} catch (Throwable t) {
|
||
|
throw new RuntimeException(t);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void testBadFastCriticalNatives() {
|
||
|
testBadFastCriticalNative("BadFastNative");
|
||
|
testBadFastCriticalNative("BadCriticalNative");
|
||
|
testBadFastCriticalNative("BadCriticalNative2");
|
||
|
testBadFastCriticalNative("BadCriticalNative3");
|
||
|
}
|
||
|
|
||
|
private static void testBadFastCriticalNative(String className) {
|
||
|
try {
|
||
|
Class.forName(className);
|
||
|
throw new Error("Expected verification error");
|
||
|
} catch (VerifyError e) {
|
||
|
// Expected.
|
||
|
} catch (Throwable e) {
|
||
|
throw new Error("Expected verification error");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@FunctionalInterface
|
||
|
interface LambdaInterface {
|
||
|
public void sayHi();
|
||
|
public default void sayHiTwice() {
|
||
|
sayHi();
|
||
|
sayHi();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class JniCallNonvirtualTest {
|
||
|
public boolean nonstaticMethodSuperCalled = false;
|
||
|
public boolean nonstaticMethodSubCalled = false;
|
||
|
|
||
|
private static native void testCallNonvirtual();
|
||
|
|
||
|
public JniCallNonvirtualTest() {
|
||
|
System.out.println("Super.<init>");
|
||
|
}
|
||
|
|
||
|
public static void staticMethod() {
|
||
|
System.out.println("Super.staticMethod");
|
||
|
}
|
||
|
|
||
|
public void nonstaticMethod() {
|
||
|
System.out.println("Super.nonstaticMethod");
|
||
|
nonstaticMethodSuperCalled = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class JniCallNonvirtualTestSubclass extends JniCallNonvirtualTest {
|
||
|
|
||
|
public JniCallNonvirtualTestSubclass() {
|
||
|
System.out.println("Subclass.<init>");
|
||
|
}
|
||
|
|
||
|
public static void staticMethod() {
|
||
|
System.out.println("Subclass.staticMethod");
|
||
|
}
|
||
|
|
||
|
public void nonstaticMethod() {
|
||
|
System.out.println("Subclass.nonstaticMethod");
|
||
|
nonstaticMethodSubCalled = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class BadFastNative {
|
||
|
@FastNative
|
||
|
public static native synchronized void test();
|
||
|
}
|
||
|
|
||
|
class BadCriticalNative {
|
||
|
@CriticalNative
|
||
|
public static native synchronized void test();
|
||
|
}
|
||
|
|
||
|
class BadCriticalNative2 {
|
||
|
@CriticalNative
|
||
|
public native void test();
|
||
|
}
|
||
|
|
||
|
class BadCriticalNative3 {
|
||
|
@CriticalNative
|
||
|
public static native void test(Object o);
|
||
|
}
|