From b94d1aed34391b6722ff4f4d686a9f471cec6070 Mon Sep 17 00:00:00 2001 From: Daohan Qu Date: Fri, 2 Jun 2023 15:35:42 +0800 Subject: [PATCH] Add Proxy support for JPF on Java 11 (#356) * Add a model class for java.lang.reflect.Proxy and add some helper methods in its native peer * Add some unit tests for the class Proxy * Assert proxy name explicitly in unit test * Update Proxy unit test in case of choice generator * Add a proxy invocation test --- .../java.base/java/lang/reflect/Proxy.java | 93 ++++++++++++++ .../jpf/vm/JPF_java_lang_reflect_Proxy.java | 101 ++++++++++++++- .../jpf/test/vm/reflection/ProxyTest.java | 118 ++++++++++++++++++ 3 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 src/classes/modules/java.base/java/lang/reflect/Proxy.java diff --git a/src/classes/modules/java.base/java/lang/reflect/Proxy.java b/src/classes/modules/java.base/java/lang/reflect/Proxy.java new file mode 100644 index 00000000..07aeabfe --- /dev/null +++ b/src/classes/modules/java.base/java/lang/reflect/Proxy.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014, United States Government, as represented by the + * Administrator of the National Aeronautics and Space Administration. + * All rights reserved. + * + * The Java Pathfinder core (jpf-core) platform is 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. + */ +package java.lang.reflect; + +import java.util.Objects; + +public class Proxy { + + protected InvocationHandler h; + + private Proxy() { } + + protected Proxy(InvocationHandler handler) { + Objects.requireNonNull(handler); + this.h = handler; + } + + // + // APIs for internal usage + // + private static native Class defineClass0(ClassLoader loader, String name, byte[] b, int off, int len); + // Proxy's implementation is uniquely defined by List. + // We give them canonical names to avoid redundant generation of class files and loading of classes. + private static native String getProxyClassCanonicalName(Class[] interfaces); + private static native Class getCachedProxyClass(String proxyName); + + // + // Public APIs of Proxy class + // + public static native boolean isProxyClass(Class cl); + public static native InvocationHandler getInvocationHandler(Object proxy); + + @Deprecated + public static Class getProxyClass(ClassLoader loader, + Class[] interfaces) throws IllegalArgumentException { + if (loader == null) { + throw new IllegalArgumentException("loader cannot be null"); + } + if (interfaces == null) { + throw new NullPointerException("interface array cannot be null"); + } + for (Class intf : interfaces) { + if (intf == null) { + throw new NullPointerException("interface arrray element cannot be null"); + } + } + + String proxyName = getProxyClassCanonicalName(interfaces); + if (proxyName == null) { + throw new IllegalArgumentException("non-public interfaces from different packages"); + } + Class cachedProxy = getCachedProxyClass(proxyName); + if (cachedProxy != null) { + return cachedProxy; + } + + byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, Modifier.PUBLIC | Modifier.FINAL); + Class proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); + return proxyClass; + } + + public static Object newProxyInstance(ClassLoader loader, + Class[] interfaces, + InvocationHandler handler) { + if (handler == null) { + throw new NullPointerException("handler cannot be null"); + } + + Class proxyClass = getProxyClass(loader, interfaces); + Object proxyObj = null; + try { + proxyObj = proxyClass.getDeclaredConstructor(InvocationHandler.class).newInstance(handler); + } catch (Exception e) { + e.printStackTrace(); + } + return proxyObj; + } +} diff --git a/src/peers/gov/nasa/jpf/vm/JPF_java_lang_reflect_Proxy.java b/src/peers/gov/nasa/jpf/vm/JPF_java_lang_reflect_Proxy.java index 5ed941a7..75c01f7d 100644 --- a/src/peers/gov/nasa/jpf/vm/JPF_java_lang_reflect_Proxy.java +++ b/src/peers/gov/nasa/jpf/vm/JPF_java_lang_reflect_Proxy.java @@ -17,27 +17,120 @@ */ package gov.nasa.jpf.vm; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import gov.nasa.jpf.annotation.MJI; public class JPF_java_lang_reflect_Proxy extends NativePeer { + + Set proxyCIs = new HashSet<>(); + @MJI - public int defineClass0 (MJIEnv env, int clsObjRef, int classLoaderRef, int nameRef, int bufferRef, int offset, int length) { + public int defineClass0 (MJIEnv env, int clsObjRef, int classLoaderRef, int nameRef, int bufferRef, int offset, int length) { String clsName = env.getStringObject(nameRef); byte[] buffer = env.getByteArrayObject(bufferRef); - + try { ClassInfo ci = ClassLoaderInfo.getCurrentClassLoader().getResolvedClassInfo( clsName, buffer, offset, length); if (!ci.isRegistered()) { ThreadInfo ti = env.getThreadInfo(); ci.registerClass(ti); } + proxyCIs.add(ci); return ci.getClassObjectRef(); - + } catch (ClassInfoException cix){ env.throwException("java.lang.ClassFormatError", clsName); // <2do> check if this is the right one return MJIEnv.NULL; } } -} + private List getInterfaceList(MJIEnv env, int interfaceArrayRef) { + int[] interfaces = env.getElementInfo(interfaceArrayRef).asReferenceArray(); + List interfaceClassObjRefs = new ArrayList<>(interfaces.length); + for (int interfaceClsObjRef : interfaces) { + interfaceClassObjRefs.add(interfaceClsObjRef); + } + return interfaceClassObjRefs; + } + + @MJI + public int getCachedProxyClass__Ljava_lang_String_2__Ljava_lang_Class_2(MJIEnv env, + int clsObjRef, + int proxyNameRef) { + String proxyName = env.getStringObject(proxyNameRef); + try { + ClassInfo ci = ClassLoaderInfo.getCurrentClassLoader().getAlreadyResolvedClassInfo(proxyName); + // Case 1. This class is not resolved, need generate class file and resolve + if (ci == null) { + return MJIEnv.NULL; + } + + // Case 2. This class has been resolved, create (if not created) and return its class object. + if (!ci.isRegistered()) { + ThreadInfo ti = env.getThreadInfo(); + ci.registerClass(ti); + } + return ci.getClassObjectRef(); + } catch (ClassInfoException cix){ + env.throwException("java.lang.ClassFormatError", proxyName); + return MJIEnv.NULL; + } + } + + @MJI + public int getProxyClassCanonicalName___3Ljava_lang_Class_2__Ljava_lang_String_2( + MJIEnv env, + int clsObjRef, + int interfaceArrayRef) { + List interfaceClassObjRefs = getInterfaceList(env, interfaceArrayRef); + StringBuilder interfaceNames = new StringBuilder(); + + String pkgName = null; + for (Integer interfaceRef : interfaceClassObjRefs) { + ClassInfo intf = env.getReferredClassInfo(interfaceRef); + + // Concat interface names to generate unique identifier + interfaceNames.append(intf.getName()); + + // Put Proxy class in the same package of the + // non-public interface for accessibility. + // Throw IllegalArgumentException if non-public interfaces + // reside in different packages. + if ((intf.getModifiers() & Modifier.PUBLIC) == 0) { + if (pkgName == null) { + pkgName = intf.getPackageName(); + } else if (!pkgName.equals(intf.getPackageName())) { + return MJIEnv.NULL; + } + } + } + if (pkgName == null) { + pkgName = "com.sun.proxy"; + } + String proxyId = interfaceNames.toString(); + String proxyName = pkgName + ".$Proxy$" + Integer.toHexString(proxyId.hashCode()); + return env.newString(proxyName); + } + + @MJI + public boolean isProxyClass__Ljava_lang_Class_2__Z(MJIEnv env, + int clsObjRef, + int targetClsObjRef) { + ClassInfo ci = env.getReferredClassInfo(targetClsObjRef); + return proxyCIs.contains(ci); + } + + @MJI + public int getInvocationHandler__Ljava_lang_Object_2__Ljava_lang_reflect_InvocationHandler_2(MJIEnv env, + int clsObjRef, + int proxyObjRef) { + ElementInfo proxyObj = env.getElementInfo(proxyObjRef); + return proxyObj.getReferenceField("h"); + } +} diff --git a/src/tests/gov/nasa/jpf/test/vm/reflection/ProxyTest.java b/src/tests/gov/nasa/jpf/test/vm/reflection/ProxyTest.java index f53af2b0..514b59ea 100644 --- a/src/tests/gov/nasa/jpf/test/vm/reflection/ProxyTest.java +++ b/src/tests/gov/nasa/jpf/test/vm/reflection/ProxyTest.java @@ -94,4 +94,122 @@ public void testAnnoProxy (){ assertTrue( res == 42); } } + + @Test + public void testProxyName() { + if (verifyNoPropertyViolation()){ + MyHandler handler = new MyHandler(42); + Ifc ifc = (Ifc) Proxy.newProxyInstance(Ifc.class.getClassLoader(), + new Class[] { Ifc.class }, + handler); + String proxyClassName = ifc.getClass().getName(); + + for (int i = 0; i < 10; i++) { + ifc = (Ifc) Proxy.newProxyInstance(Ifc.class.getClassLoader(), + new Class[] { Ifc.class }, + handler); + assertEquals(ifc.getClass().getName(), proxyClassName); + } + + String interfaceName = Ifc.class.getName(); + String packageName = interfaceName.substring(0, interfaceName.lastIndexOf('.')); + String desiredProxyClsName = packageName + ".$Proxy$" + + Integer.toHexString(Ifc.class.getName().hashCode()); + assertEquals(proxyClassName, desiredProxyClsName); + } + } + + static class NewThread extends Thread { + + Ifc ifc = null; + + @Override + public void run() { + MyHandler handler = new MyHandler(42); + ifc = (Ifc) Proxy.newProxyInstance(Ifc.class.getClassLoader(), + new Class[] { Ifc.class }, + handler); + } + } + + @Test + public void testProxyCreationInCaseOfChoiceGenerator() { + if (verifyNoPropertyViolation()){ + NewThread t = new NewThread(); + t.start(); + MyHandler handler = new MyHandler(42); + Ifc ifc = (Ifc) Proxy.newProxyInstance(Ifc.class.getClassLoader(), + new Class[] { Ifc.class }, + handler); + + try { + t.join(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + Ifc ifcInOtherThread = t.ifc; + assertEquals(ifc.getClass().getName(), ifcInOtherThread.getClass().getName()); + + String interfaceName = Ifc.class.getName(); + String packageName = interfaceName.substring(0, interfaceName.lastIndexOf('.')); + String desiredProxyClsName = packageName + ".$Proxy$" + + Integer.toHexString(Ifc.class.getName().hashCode()); + assertEquals(ifc.getClass().getName(), desiredProxyClsName); + } + } + + @Test + public void testIsProxyClass() { + if (verifyNoPropertyViolation()){ + MyHandler handler = new MyHandler(42); + Ifc ifc = (Ifc) Proxy.newProxyInstance(Ifc.class.getClassLoader(), + new Class[] { Ifc.class }, + handler); + assertTrue(Proxy.isProxyClass(ifc.getClass())); + assertFalse(Proxy.isProxyClass(this.getClass())); + } + } + + @Test + public void testGetInvocationHandler() { + if (verifyNoPropertyViolation()){ + MyHandler handler = new MyHandler(42); + Ifc ifc = (Ifc) Proxy.newProxyInstance(Ifc.class.getClassLoader(), + new Class[] { Ifc.class }, + handler); + assertTrue(handler == Proxy.getInvocationHandler(ifc)); + } + } + + interface F { + int add(int a, int b); + } + + interface G { + String concat(String s1, String s2); + } + + public static class SimpleHandler implements InvocationHandler { + + @Override + public Object invoke (Object proxy, Method mtd, Object[] args){ + if (mtd.getName().equals("add")) { + int a = (int) args[0] + (int) args[1]; + return a; + } else if (mtd.getName().equals("concat")) { + String s = (String) args[0] + (String) args[1]; + return s; + } + return null; + } + } + + @Test + public void testProxyInvocation() { + SimpleHandler h = new SimpleHandler(); + Object fg = Proxy.newProxyInstance(F.class.getClassLoader(), new Class[] { F.class, G.class }, h); + assertEquals(((F) fg).add(1, 2), 3); + assertEquals(((G) fg).concat("a", "b"), "ab"); + } + }