Skip to content

Commit

Permalink
Add Proxy support for JPF on Java 11 (#356)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
quadhier authored Jun 2, 2023
1 parent 4f78911 commit b94d1ae
Show file tree
Hide file tree
Showing 3 changed files with 308 additions and 4 deletions.
93 changes: 93 additions & 0 deletions src/classes/modules/java.base/java/lang/reflect/Proxy.java
Original file line number Diff line number Diff line change
@@ -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<interface>.
// 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;
}
}
101 changes: 97 additions & 4 deletions src/peers/gov/nasa/jpf/vm/JPF_java_lang_reflect_Proxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClassInfo> 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<Integer> getInterfaceList(MJIEnv env, int interfaceArrayRef) {
int[] interfaces = env.getElementInfo(interfaceArrayRef).asReferenceArray();
List<Integer> 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<Integer> 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");
}
}
118 changes: 118 additions & 0 deletions src/tests/gov/nasa/jpf/test/vm/reflection/ProxyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

}

0 comments on commit b94d1ae

Please sign in to comment.