-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Access to default methods in @JImplements
#1182
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,23 +23,6 @@ jobject JPClassLoader::getBootLoader() | |
return m_BootLoader.get(); | ||
} | ||
|
||
static jobject toURL(JPJavaFrame &frame, const string& path) | ||
{ | ||
// file = new File("org.jpype.jar"); | ||
jclass fileClass = frame.FindClass("java/io/File"); | ||
jmethodID newFile = frame.GetMethodID(fileClass, "<init>", "(Ljava/lang/String;)V"); | ||
jvalue v[3]; | ||
v[0].l = frame.NewStringUTF(path.c_str()); | ||
jobject file = frame.NewObjectA(fileClass, newFile, v); | ||
|
||
// url = file.toURI().toURL(); | ||
jmethodID toURI = frame.GetMethodID(fileClass, "toURI", "()Ljava/net/URI;"); | ||
jobject uri = frame.CallObjectMethodA(file, toURI, nullptr); | ||
jclass uriClass = frame.GetObjectClass(uri); | ||
jmethodID toURL = frame.GetMethodID(uriClass, "toURL", "()Ljava/net/URL;"); | ||
return frame.CallObjectMethodA(uri, toURL, nullptr); | ||
} | ||
|
||
Comment on lines
-26
to
-42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the Jar loaded as an agent we don't need to worry about loading it late in the process so all this code can be removed. |
||
JPClassLoader::JPClassLoader(JPJavaFrame& frame) | ||
{ | ||
JP_TRACE_IN("JPClassLoader::JPClassLoader"); | ||
|
@@ -68,44 +51,8 @@ JPClassLoader::JPClassLoader(JPJavaFrame& frame) | |
} | ||
frame.ExceptionClear(); | ||
|
||
// Harder, we need to find the _jpype module and use __file__ to obtain a | ||
// path. | ||
JPPyObject pypath = JPPyObject::call(PyObject_GetAttrString(PyJPModule, "__file__")); | ||
string path = JPPyString::asStringUTF8(pypath.get()); | ||
string::size_type i = path.find_last_of('\\'); | ||
if (i == string::npos) | ||
i = path.find_last_of('/'); | ||
if (i == string::npos) | ||
JP_RAISE(PyExc_RuntimeError, "Can't find jar path"); | ||
path = path.substr(0, i + 1); | ||
jobject url1 = toURL(frame, path + "org.jpype.jar"); | ||
// jobject url2 = toURL(frame, path + "lib/asm-8.0.1.jar"); | ||
|
||
// urlArray = new URL[]{url}; | ||
jclass urlClass = frame.GetObjectClass(url1); | ||
jobjectArray urlArray = frame.NewObjectArray(1, urlClass, nullptr); | ||
frame.SetObjectArrayElement(urlArray, 0, url1); | ||
// frame.SetObjectArrayElement(urlArray, 1, url2); | ||
|
||
// cl = new URLClassLoader(urlArray); | ||
jclass urlLoaderClass = frame.FindClass("java/net/URLClassLoader"); | ||
jmethodID newURLClassLoader = frame.GetMethodID(urlLoaderClass, "<init>", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V"); | ||
jvalue v[3]; | ||
v[0].l = (jobject) urlArray; | ||
v[1].l = (jobject) m_SystemClassLoader.get(); | ||
jobject cl = frame.NewObjectA(urlLoaderClass, newURLClassLoader, v); | ||
|
||
// Class dycl = Class.forName("org.jpype.classloader.DynamicClassLoader", true, cl); | ||
v[0].l = frame.NewStringUTF("org.jpype.classloader.DynamicClassLoader"); | ||
v[1].z = true; | ||
v[2].l = cl; | ||
auto dyClass = (jclass) frame.CallStaticObjectMethodA(m_ClassClass.get(), m_ForNameID, v); | ||
|
||
// dycl.newInstance(systemClassLoader); | ||
jmethodID newDyLoader = frame.GetMethodID(dyClass, "<init>", "(Ljava/lang/ClassLoader;)V"); | ||
v[0].l = cl; | ||
m_BootLoader = JPObjectRef(frame, frame.NewObjectA(dyClass, newDyLoader, v)); | ||
|
||
// org.jpype was not loaded already so we can't proceed | ||
JP_RAISE(PyExc_RuntimeError, "Can't find org.jpype.jar support library"); | ||
JP_TRACE_OUT; // GCOVR_EXCL_LINE | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,8 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke( | |
jlong hostObj, | ||
jlong returnTypePtr, | ||
jlongArray parameterTypePtrs, | ||
jobjectArray args) | ||
jobjectArray args, | ||
jobject missing) | ||
{ | ||
auto* context = (JPContext*) contextPtr; | ||
JPJavaFrame frame = JPJavaFrame::external(context, env); | ||
|
@@ -84,11 +85,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke( | |
|
||
// If method can't be called, throw an exception | ||
if (callable.isNull() || callable.get() == Py_None) | ||
{ | ||
JP_TRACE("Callable not found"); | ||
JP_RAISE_METHOD_NOT_FOUND(cname); | ||
return nullptr; | ||
} | ||
return missing; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method to fail is moved to the Java code so the corresponding JNI code to trigger has been removed. |
||
|
||
// Find the return type | ||
auto* returnClass = (JPClass*) returnTypePtr; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Manifest-Version: 1.0 | ||
Premain-Class: org.jpype.agent.JPypeAgent |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package org.jpype.agent; | ||
|
||
import java.lang.instrument.Instrumentation; | ||
|
||
public class JPypeAgent | ||
{ | ||
public static void premain(String agentArgs, Instrumentation inst) { | ||
// This doesn't have to do anything. | ||
// We just need to be an agent to load elevated privileges | ||
} | ||
} | ||
Comment on lines
+1
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We aren't going to do anything with the agent status except use the load path. So we don't need to have a body. We can use the agent to do things like change byte code or access low level memory statistics for the JVM which may be handy. We can also late load a jar file to the same level which is required for things like DB services. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
**************************************************************************** */ | ||
package org.jpype.proxy; | ||
|
||
import java.lang.invoke.MethodHandles; | ||
import java.lang.reflect.InvocationHandler; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Proxy; | ||
|
@@ -35,6 +36,7 @@ public class JPypeProxy implements InvocationHandler | |
public long cleanup; | ||
Class<?>[] interfaces; | ||
ClassLoader cl = ClassLoader.getSystemClassLoader(); | ||
public static Object missing = new Object(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This static object will be passed back to indicate that Python did not implement the method. There is nothing special about it other than being an object that the user is unlikely to ever return from a valid proxy. The object was left public so that I can pass it during testing to trigger the not implemented logic if needed. |
||
|
||
public static JPypeProxy newProxy(JPypeContext context, | ||
long instance, | ||
|
@@ -69,35 +71,46 @@ public Object newInstance() | |
public Object invoke(Object proxy, Method method, Object[] args) | ||
throws Throwable | ||
{ | ||
try | ||
{ | ||
// context.incrementProxy(); | ||
if (context.isShutdown()) | ||
throw new RuntimeException("Proxy called during shutdown"); | ||
|
||
// We can save a lot of effort on the C++ side by doing all the | ||
// type lookup work here. | ||
TypeManager typeManager = context.getTypeManager(); | ||
long returnType; | ||
long[] parameterTypes; | ||
synchronized (typeManager) | ||
if (context.isShutdown()) | ||
throw new RuntimeException("Proxy called during shutdown"); | ||
|
||
// We can save a lot of effort on the C++ side by doing all the | ||
// type lookup work here. | ||
TypeManager typeManager = context.getTypeManager(); | ||
long returnType; | ||
long[] parameterTypes; | ||
synchronized (typeManager) | ||
{ | ||
returnType = typeManager.findClass(method.getReturnType()); | ||
Class<?>[] types = method.getParameterTypes(); | ||
parameterTypes = new long[types.length]; | ||
for (int i = 0; i < types.length; ++i) | ||
{ | ||
returnType = typeManager.findClass(method.getReturnType()); | ||
Class<?>[] types = method.getParameterTypes(); | ||
parameterTypes = new long[types.length]; | ||
for (int i = 0; i < types.length; ++i) | ||
{ | ||
parameterTypes[i] = typeManager.findClass(types[i]); | ||
} | ||
parameterTypes[i] = typeManager.findClass(types[i]); | ||
} | ||
} | ||
|
||
return hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args); | ||
} finally | ||
// Check first to see if Python has implementated it | ||
Object result = hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args, missing); | ||
|
||
// If we get a good result than return it | ||
if (result != missing) | ||
return result; | ||
|
||
// If it is a default method in the interface then we have to invoke it using special reflection. | ||
if (method.isDefault()) | ||
{ | ||
// context.decrementProxy(); | ||
return MethodHandles.lookup() | ||
.unreflectSpecial(method, method.getDeclaringClass()) | ||
.bindTo(proxy) | ||
.invokeWithArguments(args); | ||
Comment on lines
+104
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the heart of the change as we need to call these privileged methods to invoke a special method. Of course given the method we are trying to invoke is nothing more than a public method implemented in the interface there is absolutely nothing privileged about the call. The issue is simply that the API through which we are accessing a public method can also be used to invoke private methods. |
||
} | ||
|
||
// Else throw... (this should never happen as proxies are checked when created.) | ||
throw new NoSuchMethodError(method.getName()); | ||
} | ||
|
||
private static native Object hostInvoke(long context, String name, long pyObject, | ||
long returnType, long[] argsTypes, Object[] args); | ||
long returnType, long[] argsTypes, Object[] args, Object bad); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -315,8 +315,12 @@ def build_java_ext(self, ext): | |
os.makedirs("build/classes", exist_ok=True) | ||
self.announce(" %s" % " ".join(cmd1), level=distutils.log.INFO) | ||
subprocess.check_call(cmd1) | ||
manifest = None | ||
try: | ||
for file in glob.iglob("native/java/**/*.*", recursive=True): | ||
if file.endswith("manifest.txt"): | ||
manifest = file | ||
continue | ||
Comment on lines
+321
to
+323
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another requirement for an agent. We must add contents to the manifest and include it in the jar file. |
||
if file.endswith(".java") or os.path.isdir(file): | ||
continue | ||
p = os.path.join(build_dir, os.path.relpath(file, "native/java")) | ||
|
@@ -326,7 +330,7 @@ def build_java_ext(self, ext): | |
print("FAIL", ex) | ||
pass | ||
cmd3 = shlex.split( | ||
'%s cvf "%s" -C "%s" .' % (jar, jarFile, build_dir)) | ||
'%s cvfm "%s" "%s" -C "%s" .' % (jar, jarFile, manifest, build_dir)) | ||
self.announce(" %s" % " ".join(cmd3), level=distutils.log.INFO) | ||
subprocess.check_call(cmd3) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,4 +19,6 @@ public interface TestInterface1 | |
{ | ||
|
||
int testMethod1(); | ||
|
||
default int testDefault() { return 1234; } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code changes the way that the jar gets loaded to place in a privileged enough position to call reflect. Prior to Java 9 this was the norm, but after 9 there was a new module based security policy and org.jpype is not organized into a module.