diff --git a/.gitignore b/.gitignore index 171de2920..4deaa58f9 100755 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,7 @@ jacoco/ wheelhouse/ vc*.pdb *.class +headers/ +/project/epypj_java/nbproject/private/ +/project/epype_java/nbproject/private/ +test-output/ diff --git a/LICENSE b/LICENSE index 4a1737519..b7362585f 100644 --- a/LICENSE +++ b/LICENSE @@ -185,3 +185,37 @@ liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. **END OF TERMS AND CONDITIONS** + + +============== +ASM License +============== + + ASM: a very small and fast Java bytecode manipulation framework + Copyright (c) 2000-2011 INRIA, France Telecom + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/ivy.xml b/ivy.xml index d48e2f996..8686b408d 100644 --- a/ivy.xml +++ b/ivy.xml @@ -17,5 +17,6 @@ + diff --git a/jpype/_jclass.py b/jpype/_jclass.py index faa905f24..def8aa117 100644 --- a/jpype/_jclass.py +++ b/jpype/_jclass.py @@ -241,6 +241,12 @@ def _jclassDoc(cls): return "\n".join(out) +# Future hook for Extension types + + +def _JExtension(name, bases, members): + raise TypeError("Java classes cannot be extended in Python") + # Install module hooks _jpype.JClass = JClass @@ -248,3 +254,4 @@ def _jclassDoc(cls): _jpype._jclassDoc = _jclassDoc _jpype._jclassPre = _jclassPre _jpype._jclassPost = _jclassPost +_jpype._JExtension = _JExtension diff --git a/jpype/protocol.py b/jpype/protocol.py index bf447ac39..5d4fd5af0 100644 --- a/jpype/protocol.py +++ b/jpype/protocol.py @@ -70,8 +70,6 @@ def _JFileConvert(jcls, obj): return jcls(obj.__fspath__()) # To be added in 1.1.x - - @_jcustomizer.JConversion("java.lang.Iterable", instanceof=Sequence, excludes=str) @_jcustomizer.JConversion("java.util.Collection", instanceof=Sequence, excludes=str) def _JSequenceConvert(jcls, obj): diff --git a/native/common/ejp_module.cpp b/native/common/ejp_module.cpp new file mode 100644 index 000000000..31e65a984 --- /dev/null +++ b/native/common/ejp_module.cpp @@ -0,0 +1,111 @@ +/***************************************************************************** + 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. + + See NOTICE file for details. + **************************************************************************** */ +#include +#include "jpype.h" +#include "pyjp.h" +#include "jp_boxedtype.h" +#include "epypj.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_jpype_python_internal_PyModuleDef + * Method: _getModuleDef + * Signature: (Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_org_jpype_python_internal_PyModuleDef__1find + (JNIEnv *env, jclass cls, jobject pymodule) +{ + EJP_TRACE_JAVA_IN("moduleDef::find"); + JPPyObject param1 = EJP_ToPython(frame, pymodule); + struct PyModuleDef* def = PyModule_GetDef(param1.get()); + return (jlong) def; // This is a reference, so no need to worry + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyModuleDef + * Method: _getName + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jpype_python_internal_PyModuleDef__1getName + (JNIEnv *env, jclass cls, jlong jdef) +{ + EJP_TRACE_JAVA_IN("moduleDef::getName"); + struct PyModuleDef* def = (struct PyModuleDef*) jdef; + if (def->m_name == NULL) + return NULL; + return frame.NewStringUTF(def->m_name); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyModuleDef + * Method: _getMethods + * Signature: (J)[[Ljava/lang/Object; + */ +JNIEXPORT jobjectArray JNICALL Java_org_jpype_python_internal_PyModuleDef__1getMethods + (JNIEnv *env, jclass cls, jlong jdef) +{ + EJP_TRACE_JAVA_IN("moduleDef::getName"); + struct PyModuleDef* def = (struct PyModuleDef*) jdef; + + // Count the number of methods + int methodCount = 0; + PyMethodDef *methodDef = def->m_methods; + while( methodDef->ml_name!=NULL) + { + methodCount++; + methodDef++; + } + + jobjectArray out = frame.NewObjectArray(3, + context->_java_lang_Object->getJavaClass(), NULL); + jobjectArray names = frame.NewObjectArray(methodCount, + context->_java_lang_Object->getJavaClass(), NULL); + jlongArray ptr = frame.NewLongArray(methodCount); + jlongArray flags = frame.NewLongArray(methodCount); + frame.SetObjectArrayElement(out, 0, names); + frame.SetObjectArrayElement(out, 1, ptr); + frame.SetObjectArrayElement(out, 2, flags); + jboolean copy; + jlong* p1 = frame.GetLongArrayElements(ptr, ©); + jlong* p2 = frame.GetLongArrayElements(flags, ©); + + methodCount = 0; + methodDef = def->m_methods; + while( methodDef->ml_name!=NULL) + { + jstring name = frame.fromStringUTF8(methodDef->ml_name); + frame.SetObjectArrayElement(names, methodCount, name); + p1[methodCount] = (jlong) (methodDef->ml_meth); + p2[2] = methodDef->ml_flags; + methodCount++; + methodDef++; + frame.DeleteLocalRef(name); + } + frame.ReleaseLongArrayElements(ptr, p1, JNI_COMMIT); + frame.ReleaseLongArrayElements(flags, p2, JNI_COMMIT); + + return out; + EJP_TRACE_JAVA_OUT(NULL); +} + +#ifdef __cplusplus +} +#endif diff --git a/native/common/include/jp_exception.h b/native/common/include/jp_exception.h index ad9596366..190e54a28 100644 --- a/native/common/include/jp_exception.h +++ b/native/common/include/jp_exception.h @@ -152,6 +152,11 @@ class JPypeException : std::runtime_error return m_Type; } + jthrowable getThrowable() + { + return m_Throwable.get(); + } + private: JPContext* m_Context{}; int m_Type; diff --git a/native/common/include/jp_javaframe.h b/native/common/include/jp_javaframe.h index a2ebbe25f..20c7ca68e 100644 --- a/native/common/include/jp_javaframe.h +++ b/native/common/include/jp_javaframe.h @@ -391,7 +391,6 @@ class JPJavaFrame void newWrapper(JPClass* cls); void registerRef(jobject obj, PyObject* hostRef); void registerRef(jobject obj, void* ref, JCleanupHook cleanup); - void clearInterrupt(bool throws); } ; diff --git a/native/common/include/jp_modifier.h b/native/common/include/jp_modifier.h index e715e8093..fdb4b481a 100644 --- a/native/common/include/jp_modifier.h +++ b/native/common/include/jp_modifier.h @@ -180,6 +180,12 @@ inline bool isBeanMutator(jlong modifier) { return (modifier & 0x40000000) == 0x40000000; } + +inline bool isPython(jlong modifier) +{ + return (modifier & 0x02000000) == 0x02000000; +} + } #ifdef __cplusplus diff --git a/native/common/include/jp_pyobjecttype.h b/native/common/include/jp_pyobjecttype.h new file mode 100644 index 000000000..c56d6d098 --- /dev/null +++ b/native/common/include/jp_pyobjecttype.h @@ -0,0 +1,42 @@ +/* + * Copyright 2020 nelson85. + * + * 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. + */ + +/* + * File: jp_pyobjecttype.h + * Author: nelson85 + * + * Created on June 30, 2020, 6:45 AM + */ +#ifndef _JPPYOBJECTCLASS_H_ +#define _JPPYOBJECTCLASS_H_ + +class JPPyObjectType : public JPClass +{ +public: + JPPyObjectType(JPJavaFrame& frame, + jclass clss, + const string& name, + JPClass* super, + JPClassList& interfaces, + jint modifiers); + virtual ~JPPyObjectType(); + virtual JPPyObject convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override; + +protected: + jfieldID m_SelfID; +} ; + +#endif // _JPPYOBJECTCLASS_H_ \ No newline at end of file diff --git a/native/common/jp_classhints.cpp b/native/common/jp_classhints.cpp index c719e6b9e..df330c830 100644 --- a/native/common/jp_classhints.cpp +++ b/native/common/jp_classhints.cpp @@ -189,7 +189,7 @@ class JPAttributeConversion : public JPPythonConversion JPMatch::Type matches(JPClass *cls, JPMatch &match) override { JP_TRACE_IN("JPAttributeConversion::matches"); - JPPyObject attr = JPPyObject::accept(PyObject_GetAttrString(match.object, attribute_.c_str())); + JPPyObject attr = JPPyObject::acceptClear(PyObject_GetAttrString(match.object, attribute_.c_str())); if (attr.isNull()) return JPMatch::_none; match.conversion = this; diff --git a/native/common/jp_classloader.cpp b/native/common/jp_classloader.cpp index 9e754fb8d..4dd906af1 100644 --- a/native/common/jp_classloader.cpp +++ b/native/common/jp_classloader.cpp @@ -79,15 +79,11 @@ JPClassLoader::JPClassLoader(JPJavaFrame& frame) 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, "", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V"); jvalue v[3]; diff --git a/native/common/jp_context.cpp b/native/common/jp_context.cpp index c304fe4b2..af91b5f73 100644 --- a/native/common/jp_context.cpp +++ b/native/common/jp_context.cpp @@ -21,6 +21,7 @@ #include "jp_proxy.h" #include "jp_platform.h" #include "jp_gc.h" +#include "epypj.h" JPResource::~JPResource() = default; @@ -152,9 +153,7 @@ void JPContext::startJVM(const string& vmPath, const StringVector& args, void JPContext::attachJVM(JNIEnv* env) { env->GetJavaVM(&m_JavaVM); -#ifndef ANDROID m_Embedded = true; -#endif initializeResources(env, false); } @@ -228,6 +227,9 @@ void JPContext::initializeResources(JNIEnv* env, bool interrupt) m_JavaContext = JPObjectRef(frame, frame.CallStaticObjectMethodA(contextClass, startMethod, val)); + // Set up Java wrappers for Python + EJP_Init(frame); + // Post launch JP_TRACE("Connect resources"); // Hook up the type manager @@ -309,8 +311,8 @@ void JPContext::shutdownJVM(bool destroyJVM, bool freeJVM) JP_TRACE_IN("JPContext::shutdown"); if (m_JavaVM == nullptr) JP_RAISE(PyExc_RuntimeError, "Attempt to shutdown without a live JVM"); - // if (m_Embedded) - // JP_RAISE(PyExc_RuntimeError, "Cannot shutdown from embedded Python"); + if (m_Embedded) + JP_RAISE(PyExc_RuntimeError, "Cannot shutdown from embedded Python"); // Wait for all non-demon threads to terminate if (destroyJVM) diff --git a/native/common/jp_exception.cpp b/native/common/jp_exception.cpp index 088f4721b..e2f9ebf27 100644 --- a/native/common/jp_exception.cpp +++ b/native/common/jp_exception.cpp @@ -19,6 +19,8 @@ #include "jpype.h" #include "jp_exception.h" #include "pyjp.h" +#include "epypj.h" +#include "jp_reference_queue.h" static_assert(std::is_nothrow_copy_constructible::value, "S must be nothrow copy constructible"); @@ -56,7 +58,7 @@ JPypeException::JPypeException(int type, void* errType, const string& msn, const // GCOVR_EXCL_START // This is only used during startup for OSError -JPypeException::JPypeException(int type, const string& msn, int errType, const JPStackInfo& stackInfo) +JPypeException::JPypeException(int type, const string& msn, int errType, const JPStackInfo& stackInfo) : std::runtime_error(msn), m_Type(type) { JP_TRACE("EXCEPTION THROWN", errType, msn); @@ -70,7 +72,7 @@ JPypeException::JPypeException(const JPypeException &ex) noexcept { } -JPypeException& JPypeException::operator = (const JPypeException& ex) +JPypeException& JPypeException::operator=(const JPypeException& ex) { if(this == &ex) { @@ -155,6 +157,7 @@ void JPypeException::convertJavaToPython() return; } // GCOVR_EXCL_STOP + jlong pycls = frame.CallLongMethodA(m_Context->getJavaContext(), m_Context->m_Context_GetExcClassID, &v); if (pycls != 0) { @@ -222,7 +225,7 @@ void JPypeException::convertJavaToPython() } PyException_SetTraceback(cause.get(), trace.get()); PyException_SetCause(pyvalue.get(), cause.keep()); - } catch (JPypeException& ex) + } catch (JPypeException& ex) { JP_TRACE("FAILURE IN CAUSE"); // Any failures in this optional action should be ignored. @@ -262,6 +265,12 @@ void JPypeException::convertPythonToJava(JPContext* context) // Otherwise jvalue v[2]; + // We need to normalize the exception for transport + eframe.normalize(); + +// th = (jthrowable) EJP_ToJava(frame, eframe.m_ExceptionValue.get(), 0); + + // This needs to follow the ToPython path v[0].j = (jlong) eframe.m_ExceptionClass.get(); v[1].j = (jlong) eframe.m_ExceptionValue.get(); th = (jthrowable) frame.CallObjectMethodA(context->getJavaContext(), @@ -283,7 +292,7 @@ void JPypeException::toPython() // Check the signals before processing the exception // It may be a signal when interrupted Java in which case // the signal takes precedence. - if (PyErr_CheckSignals()!=0) + if (PyErr_CheckSignals() != 0) return; mesg = std::runtime_error::what(); @@ -364,11 +373,14 @@ void JPypeException::toPython() eframe.normalize(); JPPyObject args = JPPyObject::call(Py_BuildValue("(s)", "C++ Exception")); JPPyObject trace = JPPyObject::call(PyTrace_FromJPStackTrace(m_Trace)); - JPPyObject cause = JPPyObject::accept(PyObject_Call(PyExc_Exception, args.get(), nullptr)); + JPPyObject cause = JPPyObject::acceptClear(PyObject_Call(PyExc_Exception, args.get(), nullptr)); if (!cause.isNull()) { PyException_SetTraceback(cause.get(), trace.get()); PyException_SetCause(eframe.m_ExceptionValue.get(), cause.keep()); + } else + { + PyErr_Clear(); } } }// GCOVR_EXCL_START @@ -455,8 +467,8 @@ void JPypeException::toJava(JPContext *context) JP_TRACE("String exception"); frame.ThrowNew(context->m_RuntimeException.get(), mesg); return; - } catch (JPypeException& ex) // GCOVR_EXCL_LINE - { // GCOVR_EXCL_START + } catch (JPypeException& ex) // GCOVR_EXCL_LINE + { // GCOVR_EXCL_START // Print our parting words. JPTracer::trace("Fatal error in exception handling"); JPStackInfo info = ex.m_Trace.front(); @@ -573,7 +585,7 @@ JPPyObject PyTrace_FromJavaException(JPJavaFrame& frame, jthrowable th, jthrowab jint lineNum = frame.CallIntMethodA(frame.GetObjectArrayElement(obj, i + 3), context->_java_lang_Integer->m_IntValueID, nullptr); - last_traceback = tb_create(last_traceback, dict, filename.c_str(), + last_traceback = tb_create(last_traceback, dict, filename.c_str(), method.c_str(), lineNum); frame.DeleteLocalRef(jclassname); frame.DeleteLocalRef(jmethodname); @@ -583,3 +595,19 @@ JPPyObject PyTrace_FromJavaException(JPJavaFrame& frame, jthrowable th, jthrowab return {}; return JPPyObject::call((PyObject*) last_traceback); } + +/* + * Class: org_jpype_PyExceptionProxy + * Method: _getMessage * Signature: (Ljava/lang/Object;)Ljava/lang/String; + */ +extern "C" JNIEXPORT jstring JNICALL Java_org_jpype_PyExceptionProxy__1getMessage +(JNIEnv *env, jclass, jlong jcls, jlong jval) +{ + JPJavaFrame frame = JPJavaFrame::external(JPContext_global, env); + JPPyCallAcquire callback; + PyObject* value = (PyObject*) jval; + PyObject* str = PyObject_Str(value); + string cvalue = JPPyString::asStringUTF8(str); + Py_DECREF(str); + return (jstring) frame.keep(frame.fromStringUTF8(cvalue)); +} diff --git a/native/common/jp_extension.cpp b/native/common/jp_extension.cpp new file mode 100644 index 000000000..42d1990ac --- /dev/null +++ b/native/common/jp_extension.cpp @@ -0,0 +1,139 @@ +/***************************************************************************** + 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. + + See NOTICE file for details. + **************************************************************************** */ +#include "jpype.h" +#include "pyjp.h" +#include "jp_proxy.h" +#include "jp_classloader.h" +#include "jp_reference_queue.h" +#include "jp_primitive_accessor.h" +#include "jp_boxedtype.h" +#include "jp_functional.h" + +static JPPyObject packArgs(JPContext* context, jlongArray parameterTypePtrs, + jobjectArray args) +{ + JP_TRACE_IN("JProxy::getArgs"); + JPJavaFrame frame = JPJavaFrame::outer(context); + jsize argLen = frame.GetArrayLength(parameterTypePtrs); + JPPyObject pyargs = JPPyObject::call(PyTuple_New(argLen)); + JPPrimitiveArrayAccessor accessor(frame, parameterTypePtrs, + &JPJavaFrame::GetLongArrayElements, &JPJavaFrame::ReleaseLongArrayElements); + + jlong* types = accessor.get(); + for (jsize i = 0; i < argLen; i++) + { + jobject obj = frame.GetObjectArrayElement(args, i); + JPClass* type = frame.findClassForObject(obj); + if (type == NULL) + type = reinterpret_cast (types[i]); + JPValue val = type->getValueFromObject(JPValue(type, obj)); + PyTuple_SetItem(pyargs.get(), i, type->convertToPythonObject(frame, val, false).keep()); + } + return pyargs; + JP_TRACE_OUT; +} + +extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_extension_Factory__call( + JNIEnv *env, + jclass clazz, + jlong contextPtr, + jlong functionId, + jlong returnTypePtr, + jlongArray parameterTypePtrs, + jobjectArray args, + jlong flags + ) +{ + JPContext* context = (JPContext*) contextPtr; + JPJavaFrame frame = JPJavaFrame::external(context, env); + + // We need the resources to be held for the full duration of the proxy. + JPPyCallAcquire callback; + { + JP_TRACE_IN("JPype_InvocationHandler_hostInvoke"); + JP_TRACE("context", context); + JP_TRACE("hostObj", (void*) hostObj); + try + { + // Find the return type + JPClass* returnClass = (JPClass*) returnTypePtr; + JP_TRACE("Get return type", returnClass->getCanonicalName()); + + // convert the arguments into a python list + JP_TRACE("Convert arguments"); + JPPyObject pyargs = packArgs(context, parameterTypePtrs, args); + + // Copy the privilege flags into the first argument + // FIXME how should this be stored. + + JP_TRACE("Call Python"); + JPPyObject returnValue = JPPyObject::call(PyObject_Call( + reinterpret_cast (functionId), + pyargs.get(), NULL)); + + JP_TRACE("Handle return", Py_TYPE(returnValue.get())->tp_name); + if (returnClass == context->_void) + { + JP_TRACE("Void return"); + return NULL; + } + + // This is a SystemError where the caller return null without + // setting a Python error. + if (returnValue.isNull()) + { + JP_TRACE("Null return"); + JP_RAISE(PyExc_TypeError, "Return value is null when it cannot be"); + } + + // We must box here. + JPMatch returnMatch(&frame, returnValue.get()); + if (returnClass->isPrimitive()) + { + JP_TRACE("Box return"); + if (returnClass->findJavaConversion(returnMatch) == JPMatch::_none) + JP_RAISE(PyExc_TypeError, "Return value is not compatible with required type."); + jvalue res = returnMatch.convert(); + JPBoxedType *boxed = (JPBoxedType *) ((JPPrimitiveType*) returnClass)->getBoxedClass(context); + jvalue res2; + res2.l = boxed->box(frame, res); + return frame.keep(res2.l); + } + + if (returnClass->findJavaConversion(returnMatch) == JPMatch::_none) + { + JP_TRACE("Cannot convert"); + JP_RAISE(PyExc_TypeError, "Return value is not compatible with required type."); + } + + JP_TRACE("Convert return to", returnClass->getCanonicalName()); + jvalue res = returnMatch.convert(); + return frame.keep(res.l); + } catch (JPypeException& ex) + { + JP_TRACE("JPypeException raised"); + ex.toJava(context); + } catch (...) // GCOVR_EXCL_LINE + { + JP_TRACE("Other Exception raised"); + env->functions->ThrowNew(env, context->m_RuntimeException.get(), + "unknown error occurred"); + } + return NULL; + JP_TRACE_OUT; // GCOVR_EXCL_LINE + } +} + diff --git a/native/common/jp_proxy.cpp b/native/common/jp_proxy.cpp index a9d83de2d..3ad745720 100644 --- a/native/common/jp_proxy.cpp +++ b/native/common/jp_proxy.cpp @@ -268,7 +268,7 @@ JPProxyDirect::~JPProxyDirect() JPPyObject JPProxyDirect::getCallable(const string& cname) { - return JPPyObject::accept(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str())); + return JPPyObject::acceptClear(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str())); } JPProxyIndirect::JPProxyIndirect(JPContext* context, PyJPProxy* inst, JPClassList& intf) @@ -281,10 +281,10 @@ JPProxyIndirect::~JPProxyIndirect() JPPyObject JPProxyIndirect::getCallable(const string& cname) { - JPPyObject out = JPPyObject::accept(PyObject_GetAttrString(m_Instance->m_Target, cname.c_str())); + JPPyObject out = JPPyObject::acceptClear(PyObject_GetAttrString(m_Instance->m_Target, cname.c_str())); if (!out.isNull()) return out; - return JPPyObject::accept(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str())); + return JPPyObject::acceptClear(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str())); } JPProxyFunctional::JPProxyFunctional(JPContext* context, PyJPProxy* inst, JPClassList& intf) @@ -299,6 +299,6 @@ JPProxyFunctional::~JPProxyFunctional() JPPyObject JPProxyFunctional::getCallable(const string& cname) { if (cname == m_Functional->getMethod()) - return JPPyObject::accept(PyObject_GetAttrString(m_Instance->m_Target, "__call__")); - return JPPyObject::accept(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str())); + return JPPyObject::acceptClear(PyObject_GetAttrString(m_Instance->m_Target, "__call__")); + return JPPyObject::acceptClear(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str())); } diff --git a/native/common/jp_pyobjecttype.cpp b/native/common/jp_pyobjecttype.cpp new file mode 100644 index 000000000..d9851249d --- /dev/null +++ b/native/common/jp_pyobjecttype.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2020 nelson85. + * + * 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. + */ +#include "jpype.h" +#include "pyjp.h" +#include "jp_pyobjecttype.h" + +JPPyObjectType::JPPyObjectType(JPJavaFrame& frame, jclass clss, + const string& name, + JPClass* super, + JPClassList& interfaces, + jint modifiers) +: JPClass(frame, clss, name, super, interfaces, modifiers) +{ + m_SelfID = frame.GetFieldID(clss, "_self", "J"); +} + +JPPyObjectType::~JPPyObjectType() +{ +} + +JPPyObject JPPyObjectType::convertToPythonObject(JPJavaFrame& frame, jvalue value, bool cast) +{ + + // This loses type + if (value.l == NULL) + { + return JPPyObject::getNone(); + } + + JPClass *cls = this; + if (!cast) + { + cls = frame.findClassForObject(value.l); + if (cls != this) + return cls->convertToPythonObject(frame, value, true); + } + + // Just unwrap it. + PyObject* pyobj = (PyObject*) frame.GetLongField(value.l, m_SelfID); + return JPPyObject::use(pyobj); +} + diff --git a/native/common/jp_typefactory.cpp b/native/common/jp_typefactory.cpp index 6388f3764..221a5be0b 100644 --- a/native/common/jp_typefactory.cpp +++ b/native/common/jp_typefactory.cpp @@ -39,6 +39,7 @@ #include "jp_doubletype.h" #include "jp_functional.h" #include "jp_proxy.h" +#include "jp_pyobjecttype.h" void JPTypeFactory_rethrow(JPJavaFrame& frame) { @@ -189,6 +190,8 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineObjectCla if (JPModifier::isBuffer(modifiers)) return (jlong) new JPBufferType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers); // Certain classes require special implementations + if (JPModifier::isPython(modifiers)) + return (jlong) new JPPyObjectType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers); if (className == "java.lang.Object") return (jlong) (context->_java_lang_Object = new JPObjectType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers)); diff --git a/native/embedded/ejp_call.cpp b/native/embedded/ejp_call.cpp new file mode 100644 index 000000000..90ca85fde --- /dev/null +++ b/native/embedded/ejp_call.cpp @@ -0,0 +1,143 @@ +#include +#include +#include "jpype.h" +#include "epypj.h" +#include "pyjp.h" +#include "jp_classloader.h" +#include "jp_primitive_accessor.h" + +// This file contains methods that we are adding to the Python API to support +// Java. The goal here will be to reduce these as much as possible. + +// FIXME What should be the naming convention for these functions? + +// Wrapper for methods that have different conventions. +// Required for methods which: +// - Steal a reference. +// - Fail to return a required argument for Java class conventions. +// - Have overloads the require passing null +// - Implemented as macros. + +extern "C" PyObject *PyBytes_FromStringAndSizeE(jobject buffer) // PyInvocation.Unary +{ + try + { + JPContext* context = PyJPModule_getContext(); + JPJavaFrame frame = JPJavaFrame::outer(context); + if (frame.IsInstanceOf(buffer, context->_java_nio_ByteBuffer->getJavaClass())) + { + void *v = frame.GetDirectBufferAddress(buffer); + Py_ssize_t sz = (Py_ssize_t) frame.GetDirectBufferCapacity(buffer); + return PyBytes_FromStringAndSize((char*) v, sz); + } + JPPrimitiveArrayAccessor accessor(frame, (jarray) buffer, + &JPJavaFrame::GetByteArrayElements, &JPJavaFrame::ReleaseByteArrayElements); + return PyBytes_FromStringAndSize((char*) accessor.get(), accessor.size()); + } catch (JPypeException& ex) + { + ex.toPython(); + } + return NULL; +} + +extern "C" PyObject *PyFrame_Interactive(PyObject *globals, PyObject *locals) +{ + JP_PY_TRY("PyJPModule_convertToDirectByteBuffer"); + try + { + JPPyObject u1 = JPPyObject::call(PyRun_String("import code as _code", + Py_single_input, globals, locals)); + JPPyObject u2 = JPPyObject::call(PyRun_String("_code.interact(local=locals())", + Py_single_input, globals, locals)); + return u2.keep(); + } catch (...) + { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) + { + PyErr_Clear(); + return NULL; + } + throw; + } + JP_PY_CATCH(NULL); +} + +extern "C" PyObject *PyFrame_RunString(PyObject *obj, PyObject *globals, PyObject *locals) +{ + JPContext *context = PyJPModule_getContext(); + JPJavaFrame frame = JPJavaFrame::outer(context); + JPValue *value = PyJPValue_getJavaSlot(obj); + string cmd = frame.toStringUTF8((jstring) (value->getJavaObject())); + return PyRun_String(cmd.c_str(), Py_file_input, globals, locals); +} + +extern "C" int PyList_SetItemS(PyObject *func, Py_ssize_t i, PyObject *value) +{ + Py_XINCREF(value); + return PyList_SetItem(func, i, value); +} + +extern "C" int PyNumber_IntValue(PyObject* obj) +{ + if (PyLong_Check(obj)) + return PyLong_AsLong(obj); + if (PyFloat_Check(obj)) + return (jint) PyFloat_AsDouble(obj); + PyErr_SetString(PyExc_TypeError, "Bad Object"); + return -1; +} + +extern "C" jlong PyNumber_LongValue(PyObject* obj) +{ + if (PyLong_Check(obj)) + return PyLong_AsLongLong(obj); + if (PyFloat_Check(obj)) + return (jlong) PyFloat_AsDouble(obj); + PyErr_SetString(PyExc_TypeError, "Bad Object"); + return -1; +} + +extern "C" jfloat PyNumber_FloatValue(PyObject* obj) +{ + if (PyLong_Check(obj)) + return PyLong_AsLongLong(obj); + if (PyFloat_Check(obj)) + return (jfloat) PyFloat_AsDouble(obj); + PyErr_SetString(PyExc_TypeError, "Bad Object"); + return -1; +} + +extern "C" jdouble PyNumber_DoubleValue(PyObject* obj) +{ + if (PyLong_Check(obj)) + return PyLong_AsLongLong(obj); + if (PyFloat_Check(obj)) + return (jdouble) PyFloat_AsDouble(obj); + PyErr_SetString(PyExc_TypeError, "Bad Object"); + return -1; +} + +extern "C" PyObject *PyNumber_Power2(PyObject *self, PyObject *a) +{ + return PyNumber_Power(self, a, NULL); +} + +extern "C" PyObject *PyNumber_InPlacePower2(PyObject *self, PyObject *a) +{ + return PyNumber_InPlacePower(self, a, NULL); +} + +extern "C" int PyObject_DelAttrE(PyObject *self, PyObject *key) +{ + return PyObject_DelAttr(self, key); +} + +extern "C" int PyObject_DelAttrStringE(PyObject *self, const char *key) +{ + return PyObject_DelAttrString(self, key); +} + +extern "C" PyObject *PyType_Name(PyObject *self) +{ + return PyUnicode_FromString(Py_TYPE(self)->tp_name); +} diff --git a/native/embedded/ejp_context.cpp b/native/embedded/ejp_context.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/native/embedded/ejp_ctor.cpp b/native/embedded/ejp_ctor.cpp new file mode 100644 index 000000000..875fdaf45 --- /dev/null +++ b/native/embedded/ejp_ctor.cpp @@ -0,0 +1,206 @@ +#include +#include "jpype.h" +#include "pyjp.h" +#include "jp_boxedtype.h" +#include "epypj.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static void releasePython(void* host) +{ + Py_XDECREF((PyObject*) host); +} + +/* + * Class: org_jpype_python_internal_PyConstructor + * Method: init + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_org_jpype_python_internal_PyConstructor_init +(JNIEnv *, jclass) +{ + return (jlong) &releasePython; +} + +/* + * Class: org_jpype_python_internal_PyConstructor + * Method: incref + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_jpype_python_internal_PyConstructor_incref +(JNIEnv *, jclass, jlong l) +{ + Py_INCREF((PyObject*) l); +} + +static jsize toArg(vector& args, JPJavaFrame& frame, + jobjectArray elements, jint begin, jint end) +{ + args.reserve(end - begin); + for (jsize i = begin; i < end; ++i) + { + jobject e = frame.GetObjectArrayElement(elements, i); + args.push_back(EJP_ToPython(frame, e)); + frame.DeleteLocalRef(e); + } + return end - begin; +} + +/* + * Class: python_lang_PyDict + * Method: _ctor + * Signature: (Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_python_lang_PyDict__1ctor +(JNIEnv *env, jclass, jobject map) +{ + EJP_TRACE_JAVA_IN("dict::new"); + if (map == NULL) + return (jlong) PyDict_New(); + JPPyObject obj = JPPyObject::call(PyDict_New()); + JPPyObject param1 = EJP_ToPython(frame, map); + PyDict_Merge(obj.get(), param1.get(), true); + return (jlong) obj.keep(); // Dangling reference for Java + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: python_lang_PyFloat + * Method: _ctor + * Signature: (D)J + */ +JNIEXPORT jlong JNICALL Java_python_lang_PyFloat__1ctor +(JNIEnv *env, jclass, jdouble d) +{ + EJP_TRACE_JAVA_IN("float::new"); + return (jlong) PyFloat_FromDouble(d); + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: python_lang_PyList + * Method: _ctor + * Signature: ([Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_python_lang_PyList__1ctor +(JNIEnv *env, jclass, jobjectArray elements) +{ + EJP_TRACE_JAVA_IN("list::new"); + + // If we have no elements return an empty list + if (elements == NULL) + return (jlong) PyList_New(0); + + // Convert the arguments + jsize sz = frame.GetArrayLength(elements); + vector args; + toArg(args, frame, elements, 0, sz); + + // Copy to the tuple + JPPyObject obj = JPPyObject::call(PyList_New(sz)); + for (jsize i = 0; i < sz; ++i) + PyList_SetItem(obj.get(), i, args[i].keep()); + return (jlong) obj.keep(); // Dangling reference for Java + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: python_lang_PyLong + * Method: _ctor + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_python_lang_PyLong__1ctor +(JNIEnv *env, jclass, jlong d) +{ + EJP_TRACE_JAVA_IN("float::new"); + return (jlong) PyLong_FromLongLong(d); + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: python_lang_PySet + * Method: _ctor + * Signature: ([Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_python_lang_PySet__1ctor +(JNIEnv *env, jclass, jobjectArray map) +{ + EJP_TRACE_JAVA_IN("set::new"); + if (map == NULL) + { + JPPyObject obj = JPPyObject::call(PyTuple_New(0)); + return (jlong) PySet_New(obj.get()); + } + JPPyObject param1 = EJP_ToPython(frame, map); + JPPyObject obj = JPPyObject::call(PySet_New(param1.get())); + return (jlong) obj.keep(); // Dangling reference for Java + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: python_lang_PyString + * Method: _ctor + * Signature: (Ljava/lang/String;)J + */ +JNIEXPORT jlong JNICALL Java_python_lang_PyString__1ctor +(JNIEnv *env, jclass, jstring str) +{ + EJP_TRACE_JAVA_IN("str::new"); + string param = frame.toStringUTF8(str); + return (jlong) JPPyString::fromStringUTF8(param).keep(); + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: python_lang_PyString + * Method: _toString + * Signature: (Ljava/lang/Object;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_python_lang_PyString__1toString + (JNIEnv *env, jclass cls, jobject obj) +{ + EJP_TRACE_JAVA_IN("str::toString"); + string out = JPPyString::asStringUTF8(EJP_ToPython(frame, obj).get()); + return (jstring) frame.keep(frame.fromStringUTF8(out)); + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: python_lang_PyTuple + * Method: _ctor + * Signature: ([Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_python_lang_PyTuple__1ctor +(JNIEnv *env, jclass cls, jobjectArray elements, jint begin, jint end) +{ + EJP_TRACE_JAVA_IN("tuple::new"); + if (elements == NULL) + return (jlong) PyTuple_New(0); + + // Verify the size is correct before proceeding (this could be done on the + // Java side) + jsize sz = frame.GetArrayLength(elements); + if (end > sz || begin > end) + { + frame.ThrowNew(context->m_RuntimeException.get(), "Size out of range"); + return 0; + } + + // Convert the arguments into a tuple + vector args; + sz = toArg(args, frame, elements, begin, end); + + // Create a new tuple + JPPyObject obj = JPPyObject::call(PyTuple_New(sz)); + for (jsize i = 0; i < sz; ++i) + PyTuple_SetItem(obj.get(), i, args[i].keep()); + return (jlong) obj.keep(); // Dangling reference for Java + EJP_TRACE_JAVA_OUT(0); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/native/embedded/ejp_engine.cpp b/native/embedded/ejp_engine.cpp new file mode 100644 index 000000000..6c225743d --- /dev/null +++ b/native/embedded/ejp_engine.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include "jpype.h" +#include "pyjp.h" +#include "epypj.h" + +PyThreadState *s_ThreadState; +PyThreadState *m_State1; +#ifdef __cplusplus +extern "C" { +#endif + +static void fail(JNIEnv *env, const char* msg) +{ + // This is a low frequency path so we don't need efficiency. + jclass runtimeException = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(runtimeException, msg); +} + +static void convertException(JNIEnv *env, JPypeException& ex) +{ + // This is a low frequency path so we don't need efficiency. + // We can't use ex.toJava() because this is part of initialization. + jclass runtimeException = env->FindClass("java/lang/RuntimeException"); + + // If it is a Java exception, we can simply throw it + if (ex.getExceptionType() == JPError::_java_error) + { + env->Throw(ex.getThrowable()); + return; + } + + // No guarantees that the exception will make it back so print it first + PyObject *err = PyErr_Occurred(); + if (err != NULL) + { + PyErr_Print(); + env->ThrowNew(runtimeException, "Exception in Python"); + } else + { + env->ThrowNew(runtimeException, ex.getMessage().c_str()); + } +} + +JNIEXPORT void JNICALL Java_org_jpype_python_internal_Native_start +(JNIEnv *env, jobject engine) +{ + try + { + // FIXME for testing we need to add "." to the path. + Py_SetPath(L".:/usr/lib/python36.zip:/usr/lib/python3.6:/usr/lib/python3.6:/usr/lib/python3.6/lib-dynload"); + + // Get Python started + PyImport_AppendInittab("_jpype", &PyInit__jpype); + Py_InitializeEx(0); + PyEval_InitThreads(); + s_ThreadState = PyThreadState_Get(); + + // Import the Python side to create the hooks + PyObject *jpype = PyImport_ImportModule("jpype"); + if (jpype == NULL) + { + fail(env, "jpype module not found"); + return; + } + Py_DECREF(jpype); + + // Next install the hooks into the private side. + PyObject *jpypep = PyImport_ImportModule("_jpype"); + if (jpypep == NULL) + { + fail(env, "_jpype module not found"); + return; + } + PyJPModule_loadResources(jpypep); + Py_DECREF(jpypep); + + // Then attach the private module to the JVM + JPContext* context = JPContext_global; + context->attachJVM(env); + + // Initialize the resources in the jpype module + PyObject *obj = PyObject_GetAttrString(jpype, "_core"); + PyObject *obj2 = PyObject_GetAttrString(obj, "initializeResources"); + PyObject *obj3 = PyTuple_New(0); + PyObject_Call(obj2, obj3, NULL); + Py_DECREF(obj); + Py_DECREF(obj2); + Py_DECREF(obj3); + + // Everything is up and ready + + // Next, we need to release the state so we can return to Java. + m_State1 = PyEval_SaveThread(); + } catch (JPypeException& ex) + { + convertException(env, ex); + } catch (...) + { + fail(env, "C++ exception during start"); + } +} + + +// This section will allow us to look up symbols in shared libraries from within +// Java. We will then call those symbols using the invoker. + +// FIXME global +static list libraries; + +/* + * Class: python_lang_PyEngine + * Method: getSymbol + * Signature: (Ljava/lang/String;)J + */ +JNIEXPORT jlong JNICALL Java_org_jpype_python_internal_Native_getSymbol +(JNIEnv *env, jobject, jstring str) +{ + try + { + jboolean copy; + const char* name = env->GetStringUTFChars(str, ©); + for (std::list::iterator iter = libraries.begin(); + iter != libraries.end(); iter++) + { + void* res = NULL; +#ifdef WIN32 + res = (void*) GetProcAddress(*iter, name.c_str()); +#else + res = dlsym(*iter, name); +#endif + if (res != NULL) + return (jlong) res; + } + env->ReleaseStringUTFChars(str, name); + return 0; + } catch (JPypeException& ex) + { + convertException(env, ex); + } catch (...) + { + fail(env, "C++ exception during getSymbol"); + } + return 0; +} + +/* + * Class: python_lang_PyEngine + * Method: addLibrary + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_org_jpype_python_internal_Native_addLibrary +(JNIEnv *env, jobject, jstring str) +{ + try + { + jboolean copy; + const char* name = env->GetStringUTFChars(str, ©); + void *library = NULL; +#ifdef WIN32 + library = LoadLibrary(name); +#else +#if defined(_HPUX) && !defined(_IA64) + jvmLibrary = shl_load(name, BIND_DEFERRED | BIND_VERBOSE, 0L); +#else + library = dlopen(name, RTLD_LAZY | RTLD_GLOBAL); +#endif // HPUX +#endif + env->ReleaseStringUTFChars(str, name); + + if (library == NULL) + fail(env, "Failed to load library"); + else + libraries.push_back(library); + return; + } catch (...) + { + fail(env, "C++ exception in addLibrary"); + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/native/embedded/ejp_invoker.cpp b/native/embedded/ejp_invoker.cpp new file mode 100644 index 000000000..5f0d55011 --- /dev/null +++ b/native/embedded/ejp_invoker.cpp @@ -0,0 +1,667 @@ +#include +#include "jpype.h" +#include "pyjp.h" +#include "jp_boxedtype.h" +#include "epypj.h" +#include + +void EJP_rethrow(JNIEnv *env, JPContext *context) +{ + try + { + throw; + } catch (JPypeException& ex) + { + ex.toJava(context); + } catch (...) // GCOVR_EXCL_LINE + { + env->functions->ThrowNew(env, context->m_RuntimeException.get(), + "unknown error occurred"); + } +} + +#ifdef __cplusplus +extern "C" { +#endif + +void NotImplemented(JPJavaFrame& frame) +{ + frame.ThrowNew(frame.getContext()->m_RuntimeException.get(), "Flag not implemented"); +} + +#define FLAGS_ACCEPT 1 +#define FLAGS_BORROWED 2 + +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeNone +(JNIEnv *env, jclass invoker, jlong entry, jint flags) +{ + EJP_TRACE_JAVA_IN("invoke::none"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(); + func f = (func) entry; + JPPyObject out = JPPyObject::accept(f()); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeFromInt +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jint i) +{ + EJP_TRACE_JAVA_IN("invoke::fromInt"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(int i); + func f = (func) entry; + JPPyObject out = JPPyObject::accept(f(i)); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeFromLong +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jlong i) +{ + EJP_TRACE_JAVA_IN("invoke::fromLong"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(jlong i); + func f = (func) entry; + JPPyObject out = JPPyObject::accept(f(i)); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeFromDouble +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jdouble i) +{ + EJP_TRACE_JAVA_IN("invoke::fromDouble"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(jdouble i); + func f = (func) entry; + JPPyObject out = JPPyObject::accept(f(i)); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeFromJObject +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject i) +{ + EJP_TRACE_JAVA_IN("invoke::fromJObject"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(jobject i); + func f = (func) entry; + JPPyObject out = JPPyObject::accept(f(i)); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeUnary + * Signature: (JLjava/lang/Object;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeUnary +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1) +{ + EJP_TRACE_JAVA_IN("invoke::unary"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(PyObject * obj); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject out = JPPyObject::accept(f(param1.get())); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeAsBoolean + * Signature: (JLjava/lang/Object;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpype_python_internal_PyInvoker_invokeAsBoolean +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1) +{ + EJP_TRACE_JAVA_IN("invoke::asBoolean"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jboolean(*func)(PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + jboolean ret = f(param1.get()); + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeAsInt + * Signature: (JLjava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeAsInt +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1) +{ + EJP_TRACE_JAVA_IN("invoke::asInt"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jint(*func)(PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + jint ret = f(param1.get()); + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeAsLong + * Signature: (JLjava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_org_jpype_python_internal_PyInvoker_invokeAsLong +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1) +{ + EJP_TRACE_JAVA_IN("invoke::asLong"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jlong(*func)(PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + printf("Object 0x%p %s\n", param1.get(), Py_TYPE(param1.get())->tp_name); + jlong ret = f(param1.get()); + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeAsFloat + * Signature: (JLjava/lang/Object;)F + */ +JNIEXPORT jfloat JNICALL Java_org_jpype_python_internal_PyInvoker_invokeAsFloat +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1) +{ + EJP_TRACE_JAVA_IN("invoke::asFloat"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jfloat(*func)(PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + jfloat ret = f(param1.get()); + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeAsDouble + * Signature: (JLjava/lang/Object;)D + */ +JNIEXPORT jdouble JNICALL Java_org_jpype_python_internal_PyInvoker_invokeAsDouble +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1) +{ + EJP_TRACE_JAVA_IN("invoke::asDouble"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jdouble(*func)(PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + jdouble ret = f(param1.get()); + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeBinary + * Signature: (JLjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeBinary +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jobject jparam2) +{ + EJP_TRACE_JAVA_IN("invoke::binary"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(PyObject*, PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param2 = EJP_ToPython(frame, jparam2); + JPPyObject out = JPPyObject::accept(f(param1.get(), param2.get())); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeBinaryInt + * Signature: (JLjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeBinaryInt +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jint jparam2) +{ + EJP_TRACE_JAVA_IN("invoke::binaryInt"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(PyObject*, jint); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject out = JPPyObject::accept(f(param1.get(), jparam2)); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeBinaryToInt + * Signature: (JLjava/lang/Object;Ljava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeBinaryToInt +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jobject jparam2) +{ + EJP_TRACE_JAVA_IN("invoke::binaryToInt"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jint(*func)(PyObject*, PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param2 = EJP_ToPython(frame, jparam2); + jint ret = f(param1.get(), param2.get()); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeBinaryToLong + * Signature: (JLjava/lang/Object;Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_org_jpype_python_internal_PyInvoker_invokeBinaryToLong +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jobject jparam2) +{ + EJP_TRACE_JAVA_IN("invoke::binaryToLong"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jint(*func)(PyObject*, PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param2 = EJP_ToPython(frame, jparam2); + jint ret = f(param1.get(), param2.get()); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeTernary + * Signature: (JLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeTernary +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jobject jparam2, jobject jparam3) +{ + EJP_TRACE_JAVA_IN("invoke::ternary"); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + typedef PyObject * (*func)(PyObject*, PyObject*, PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param2 = EJP_ToPython(frame, jparam2); + JPPyObject param3 = EJP_ToPython(frame, jparam3); + JPPyObject out = JPPyObject::accept(f(param1.get(), param2.get(), param3.get())); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeDelSlice + * Signature: (JLjava/lang/Object;II)Ljava/lang/Object; + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeDelSlice +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jint param2, jint param3) +{ + EJP_TRACE_JAVA_IN("invoke::delSlice"); + typedef int (*func)(PyObject*, Py_ssize_t, Py_ssize_t); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + jint ret = f(param1.get(), param2, param3); + if (ret == -1) + JP_PY_CHECK() + return ret; + EJP_TRACE_JAVA_OUT(-1); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeGetSlice + * Signature: (JLjava/lang/Object;II)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeGetSlice +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jint param2, jint param3) +{ + EJP_TRACE_JAVA_IN("invoke::binary"); + typedef PyObject * (*func)(PyObject*, Py_ssize_t, Py_ssize_t); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject out = JPPyObject::accept(f(param1.get(), param2, param3)); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeSetSlice + * Signature: (JLjava/lang/Object;IILjava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeSetSlice +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jint i1, jint i2, jobject jparam2) +{ + EJP_TRACE_JAVA_IN("invoke::setSlice"); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + typedef jint(*func)(PyObject*, Py_ssize_t, Py_ssize_t, PyObject*); + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param2 = EJP_ToPython(frame, jparam2); + jint ret = f(param1.get(), i1, i2, param2.get()); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeDelStr + * Signature: (JLjava/lang/Object;Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeDelStr +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jstring jname) +{ + EJP_TRACE_JAVA_IN("invoke::delStr"); + typedef jint(*func)(PyObject*, const char*); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + string name = frame.toStringUTF8(jname); + jint ret = f(param1.get(), name.c_str()); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeDelStr + * Signature: (JLjava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeGetStr +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jstring jname) +{ + EJP_TRACE_JAVA_IN("invoke::getStr"); + typedef PyObject * (*func)(PyObject*, const char*); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + string name = frame.toStringUTF8(jname); + JPPyObject out = JPPyObject::accept(f(param1.get(), name.c_str())); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeSetObj + * Signature: (JLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeSetObj +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jobject jparam2, jobject jparam3) +{ + EJP_TRACE_JAVA_IN("invoke::ternary"); + typedef int (*func)(PyObject*, PyObject*, PyObject*); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param2 = EJP_ToPython(frame, jparam2); + JPPyObject param3 = EJP_ToPython(frame, jparam3); + jint ret = f(param1.get(), param2.get(), param3.get()); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeSetStr + * Signature: (JLjava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeSetStr +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, + jstring jparam2, jobject jparam3) +{ + EJP_TRACE_JAVA_IN("invoke::ternary"); + typedef int (*func)(PyObject*, const char*, PyObject*); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + string param2 = frame.toStringUTF8(jparam2); + JPPyObject param3 = EJP_ToPython(frame, jparam3); + jint ret = f(param1.get(), param2.c_str(), param3.get()); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeSetInt + * Signature: (JLjava/lang/Object;ILjava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeSetInt +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jint jparam2, jobject jparam3) +{ + EJP_TRACE_JAVA_IN("invoke::ternary"); + typedef int (*func)(PyObject*, jint, PyObject*); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param3 = EJP_ToPython(frame, jparam3); + jint ret = f(param1.get(), jparam2, param3.get()); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeSetIntToObj + * Signature: (JLjava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeSetIntToObj +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jint jparam2, jobject jparam3) +{ + EJP_TRACE_JAVA_IN("invoke::ternary"); + typedef PyObject * (*func)(PyObject*, jint, PyObject*); + if ((flags&~(FLAGS_ACCEPT|FLAGS_BORROWED)) != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param3 = EJP_ToPython(frame, jparam3); + JPPyObject out = JPPyObject::accept(f(param1.get(), jparam2, param3.get())); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeRichCompare + * Signature: (JLjava/lang/Object;Ljava/lang/Object;I)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeIntOperator1 +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jint op) +{ + EJP_TRACE_JAVA_IN("invoke::intoperator1"); + typedef int (*func)(PyObject*, int); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + int ret = f(param1.get(), op); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeRichCompare + * Signature: (JLjava/lang/Object;Ljava/lang/Object;I)I + */ +JNIEXPORT jint JNICALL Java_org_jpype_python_internal_PyInvoker_invokeIntOperator2 +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobject jparam1, jobject jparam2, jint op) +{ + EJP_TRACE_JAVA_IN("invoke::intoperator2"); + typedef int (*func)(PyObject*, PyObject*, int); + if (flags != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + JPPyObject param1 = EJP_ToPython(frame, jparam1); + JPPyObject param2 = EJP_ToPython(frame, jparam2); + int ret = f(param1.get(), param2.get(), op); + if (ret == -1) + JP_PY_CHECK(); + return ret; + EJP_TRACE_JAVA_OUT(0); +} + +/* + * Class: org_jpype_python_internal_PyInvoker + * Method: invokeUnary + * Signature: (J[Ljava/lang/Object;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpype_python_internal_PyInvoker_invokeArray +(JNIEnv *env, jclass invoker, jlong entry, jint flags, jobjectArray jparam1) +{ + EJP_TRACE_JAVA_IN("invoke::unary"); + typedef PyObject * (*func)(jobjectArray a); + if ((flags&~2) != 0) + { + NotImplemented(frame); + return 0; + } + func f = (func) entry; + PyObject *ret = f(jparam1); + if (flags&2) + Py_XINCREF(ret); + JPPyObject out = JPPyObject::accept(ret); + return EJP_ToJava(frame, out.get(), flags); + EJP_TRACE_JAVA_OUT(NULL); +} + +#ifdef __cplusplus +} +#endif diff --git a/native/embedded/ejp_typemanager.cpp b/native/embedded/ejp_typemanager.cpp new file mode 100644 index 000000000..b5b5cd533 --- /dev/null +++ b/native/embedded/ejp_typemanager.cpp @@ -0,0 +1,272 @@ +#include "jpype.h" +#include "pyjp.h" +#include "epypj.h" +#include "jp_classloader.h" +#include "jp_reference_queue.h" +#include "jp_boxedtype.h" +#include + +// Global resources +jclass ejp_managerClass; +jobject ejp_manager; +jmethodID ejp_getWrapper; +PyObject* ejp_typedict = NULL; + +// Protocols (not currently expandable) +jobject ejp_numberProtocol; +jobject ejp_sequenceProtocol; +jobject ejp_mappingProtocol; +jobject ejp_iterableProtocol; +jobject ejp_iteratorProtocol; +jobject ejp_bufferProtocol; +jobject ejp_descriptorProtocol; +jobject ejp_callableProtocol; +jobject ejp_containerProtocol; +jobject ejp_sizedProtocol; + +JPPyObject ejp_mappingType; + +jclass EJP_CreateJavaWrapper(JPJavaFrame &frame, const string& moduleName, const char* typeName, jobjectArray a); + +EJPClass::EJPClass(JPJavaFrame& frame, jclass wrapper) +{ + m_Wrapper = wrapper; + m_Allocator = frame.GetStaticMethodID(wrapper, "_allocate", "(J)Ljava/lang/Object;"); +} + +EJPClass::~EJPClass() +{ + JPContext *context = JPContext_global; + if (!context->isRunning()) + return; + JPJavaFrame frame = JPJavaFrame::outer(context); + frame.DeleteGlobalRef(m_Wrapper); +} + +void EJPClass::destroy(PyObject *wrapper) +{ + EJPClass *cls = (EJPClass*) PyCapsule_GetPointer(wrapper, NULL); + delete cls; +} + +/** Set up resource required for Java to handle Python objects. + * + * This is called as part of the _jpype start up sequence. + */ +void EJP_Init(JPJavaFrame &frame) +{ + // FIXME allocate weak dict + ejp_managerClass = frame.getContext()->getClassLoader()->findClass(frame, "org.jpype.python.PyTypeManager"); + ejp_managerClass = (jclass) frame.NewGlobalRef((jobject) ejp_managerClass); + jmethodID get = frame.GetStaticMethodID(ejp_managerClass, "getInstance", "()Lorg/jpype/python/PyTypeManager;"); + ejp_manager = frame.CallStaticObjectMethodA(ejp_managerClass, get, NULL); + jmethodID init = frame.GetMethodID(ejp_managerClass, "initialize", "()V"); + frame.CallVoidMethodA(ejp_manager, init, NULL); + ejp_manager = frame.NewGlobalRef( ejp_manager); + ejp_getWrapper = frame.GetMethodID(ejp_managerClass, "getWrapper", + "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/Class;"); + + // Define the protocols + ejp_numberProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.number", NULL)); + ejp_sequenceProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.sequence", NULL)); + ejp_mappingProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.mapping", NULL)); + ejp_iterableProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.iterable", NULL)); + ejp_iteratorProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.iterator", NULL)); + ejp_bufferProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.buffer", NULL)); + ejp_descriptorProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.descriptor", NULL)); + ejp_callableProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.callable", NULL)); + ejp_containerProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.container", NULL)); + ejp_sizedProtocol = frame.NewGlobalRef(EJP_CreateJavaWrapper(frame, "builtins", "protocol.sized", NULL)); + + PyObject *typing = PyImport_AddModule("jpype.protocol"); + ejp_mappingType = JPPyObject::call(PyObject_GetAttrString(typing, "Mapping")); + + // Create a weak dictionary for holding wrappers. + // This must be a weak dict so that we don't cause dynamic types to + // leak in Python. + PyObject *weakref = PyImport_AddModule("weakref"); + JPPyObject dict = JPPyObject::call(PyObject_GetAttrString(weakref, "WeakKeyDictionary")); + JPPyObject args = JPPyObject::call(PyTuple_New(0)); + ejp_typedict = PyObject_Call(dict.get(), args.get(), NULL); + + // Push singletons over to Java (these will be cached on the first call) + EJP_ToJava(frame, Py_None, 0); + EJP_ToJava(frame, Py_True, 0); + EJP_ToJava(frame, Py_False, 0); + EJP_ToJava(frame, Py_Ellipsis, 0); +} + +jclass EJP_CreateJavaWrapper(JPJavaFrame &frame, const string& moduleName, const char* typeName, jobjectArray a) +{ + jvalue v[3]; + v[0].l = frame.NewStringUTF(moduleName.c_str()); + v[1].l = frame.NewStringUTF(typeName); + v[2].l = (jobject) a; + return (jclass) frame.NewGlobalRef(frame.CallObjectMethodA(ejp_manager, ejp_getWrapper, v)); +} + +bool EJP_HasPyType(PyTypeObject *type) +{ + return PyObject_GetItem(ejp_typedict, (PyObject*) type) != NULL; +} + +EJPClass *EJP_GetClass(JPJavaFrame &frame, PyTypeObject *type) +{ + // Check for an existing wrapper using the cache. + PyObject *capsule = PyObject_GetItem(ejp_typedict, (PyObject*) type); + if (capsule != NULL) + { + EJPClass *cls = (EJPClass*) PyCapsule_GetPointer(capsule, NULL); + JP_PY_CHECK(); + return cls; + } + PyErr_Clear(); + + // Check the mro to make sure all base classes have wrappers + Py_ssize_t sz = PyTuple_Size(type->tp_mro); + for (Py_ssize_t i = 0; i < sz; ++i) + { + PyObject *item = PyTuple_GetItem(type->tp_mro, i); + if (item != (PyObject*) type && PyDict_GetItem(ejp_typedict, item) == NULL) + { + // Not found, then construct it first. + EJP_GetClass(frame, (PyTypeObject*) item); + } + } + + // Check for protocols + vector protocols; + bool isDict = PyType_FastSubclass(type, Py_TPFLAGS_DICT_SUBCLASS); + if (type->tp_as_number && (type->tp_as_number->nb_index || type->tp_as_number->nb_int + || type->tp_as_number->nb_float)) + protocols.push_back(ejp_numberProtocol); + if (!isDict && type->tp_as_sequence && type->tp_as_sequence->sq_item != NULL) + protocols.push_back(ejp_sequenceProtocol); + if (type->tp_as_mapping && type->tp_as_mapping->mp_subscript) + { + // it may be a mapping, but check with Python + if (PyObject_IsSubclass((PyObject*) type, ejp_mappingType.get())) + protocols.push_back(ejp_mappingProtocol); + } + // These two share a slot so we look for next to see if iterable vs iterator + if (type->tp_iternext && type->tp_iternext != &_PyObject_NextNotImplemented) + protocols.push_back(ejp_iteratorProtocol); + else if (type->tp_iter) + protocols.push_back(ejp_iterableProtocol); + if (type->tp_as_buffer && type->tp_as_buffer->bf_getbuffer) + protocols.push_back(ejp_bufferProtocol); + if (type->tp_descr_get || type->tp_descr_set != NULL) + protocols.push_back(ejp_descriptorProtocol); + if (type->tp_call) + protocols.push_back(ejp_callableProtocol); + if (type->tp_as_sequence && type->tp_as_sequence->sq_contains) + protocols.push_back(ejp_containerProtocol); + if ((type->tp_as_sequence && type->tp_as_sequence->sq_length) || + (type->tp_as_mapping && type->tp_as_mapping->mp_length)) + protocols.push_back(ejp_sizedProtocol); + + // Convert the types to a Java array + jobjectArray jprotocols = frame.NewObjectArray(sz - 1 + protocols.size(), + frame.getContext()->_java_lang_Class->getJavaClass(), NULL); + jsize j = 0; + for (Py_ssize_t i = 0; i < sz; ++i) + { + PyObject *item = PyTuple_GetItem(type->tp_mro, i); + if (item == (PyObject*) type) + continue; + + // Get the wrapper + PyObject *wrapper = PyObject_GetItem(ejp_typedict, item); + EJPClass *cls = (EJPClass*) PyCapsule_GetPointer(wrapper, NULL); + + // Copy to Java + frame.SetObjectArrayElement(jprotocols, j++, (jobject) cls->m_Wrapper); + } + + // Add the protocols + for (size_t k = 0; k < protocols.size(); ++k) + { + frame.SetObjectArrayElement(jprotocols, j++, protocols[k]); + } + + // Create a Java wrapper class + JPPyObject pyModuleName = JPPyObject::call(PyObject_GetAttrString((PyObject*) type, "__module__")); + string moduleName = JPPyString::asStringUTF8(pyModuleName.get()); + jclass wrapper = EJP_CreateJavaWrapper(frame, moduleName, type->tp_name, jprotocols); + EJPClass *cls = new EJPClass(frame, wrapper); + + // Place it in a capsule for reuse. + JPPyObject ecapsule = JPPyObject::call(PyCapsule_New(cls, NULL, EJPClass::destroy)); + if (PyObject_SetItem(ejp_typedict, (PyObject*) type, ecapsule.get()) == -1) + JP_PY_CHECK(); + + // Return it to Python + return cls; +} + +/** + * + * We are getting a new reference here. + * + * @param frame + * @param obj + * @return + */ +jobject EJP_ToJava(JPJavaFrame& frame, PyObject *obj, int flags) +{ + if (obj == NULL) + { + if (flags & 1) + return (jobject) 0; + + // This is a sanity check. It usually means that we forgot to + // add PyMethodInfo.ACCEPT on the flags to a method which can produce + // NULL as part of normal operations. + if (!PyErr_Occurred()) + { + JP_RAISE(PyExc_RuntimeError, "Null without exception"); + return (jobject) 0; + } + JP_RAISE_PYTHON(); + } + + if (flags & 2) + Py_INCREF(obj); + + JPValue *value = PyJPValue_getJavaSlot(obj); + if (value != NULL) + { + JPClass *cls = value->getClass(); + jvalue v = value->getValue(); + if (cls->isPrimitive()) + { + JPBoxedType *boxed = (JPBoxedType *) ((JPPrimitiveType*) cls) + ->getBoxedClass(frame.getContext()); + return boxed->box(frame, v); + } + return v.l; + } + + // Create a wrapper for Python object. + EJPClass *cls = EJP_GetClass(frame, Py_TYPE(obj)); + jvalue val; + val.j = (jlong) obj; + jobject ret = frame.CallStaticObjectMethodA(cls->m_Wrapper, cls->m_Allocator, &val); + return ret; +} + +/** + * + * @param frame + * @param obj + * @return + */ +JPPyObject EJP_ToPython(JPJavaFrame& frame, jobject obj) +{ + if (obj == NULL) + return JPPyObject::getNone(); + JPClass *cls = frame.findClassForObject(obj); + jvalue value; + value.l = obj; + return cls->convertToPythonObject(frame, value, true); +} diff --git a/native/embedded/include/epypj.h b/native/embedded/include/epypj.h new file mode 100644 index 000000000..1b4fbe6c5 --- /dev/null +++ b/native/embedded/include/epypj.h @@ -0,0 +1,47 @@ +#ifndef EPYJP_H +#define EPYJP_H +#include +#include + +void EJP_rethrow(JNIEnv *env, JPContext *context); + +#ifdef JP_TRACING_ENABLE +#define EJP_TRACE_JAVA_IN(...) \ + JPContext* context = JPContext_global; \ + JPJavaFrame frame = JPJavaFrame::external(context, env); \ + JPPyCallAcquire callback; \ + JPypeTracer _trace(__VA_ARGS__); try { +#define EJP_TRACE_JAVA_OUT } \ + catch(...) { _trace.gotError(JP_STACKINFO()); EJP_rethrow(env, context); } \ + return X +#else + +#define EJP_TRACE_JAVA_IN(...) \ + JPContext* context = JPContext_global; \ + JPJavaFrame frame = JPJavaFrame::external(context, env); \ + JPPyCallAcquire callback; \ + try { +#define EJP_TRACE_JAVA_OUT(X) } \ + catch(...) { EJP_rethrow(env, context); } return X +#endif + +class EJPClass +{ +public: + jclass m_Wrapper; + jmethodID m_Allocator; + + EJPClass(JPJavaFrame& frame, jclass wrapper); + ~EJPClass(); + static void destroy(PyObject *wrapper); + +} ; + +void EJP_Init(JPJavaFrame &frame); +void EJP_RegisterCalls(JPJavaFrame &frame); +bool EJP_HasPyType(PyTypeObject *type); +EJPClass *EJP_GetClass(JPJavaFrame &frame, PyTypeObject *type); +JPPyObject EJP_ToPython(JPJavaFrame& frame, jobject obj); +jobject EJP_ToJava(JPJavaFrame& frame, PyObject *obj, int flags); + +#endif /* EPYJP_H */ \ No newline at end of file diff --git a/native/java/org/jpype/JPypeContext.java b/native/java/jpype.core/org/jpype/JPypeContext.java similarity index 95% rename from native/java/org/jpype/JPypeContext.java rename to native/java/jpype.core/org/jpype/JPypeContext.java index 59396d1e9..89c05fba2 100644 --- a/native/java/org/jpype/JPypeContext.java +++ b/native/java/jpype.core/org/jpype/JPypeContext.java @@ -22,8 +22,6 @@ import java.lang.reflect.Modifier; import java.nio.Buffer; import java.nio.ByteOrder; -import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -84,6 +82,8 @@ public class JPypeContext private final AtomicInteger shutdownFlag = new AtomicInteger(); private final List shutdownHooks = new ArrayList<>(); private final List postHooks = new ArrayList<>(); + private static final JPypeSignal signal = JPypeSignal.newInstance(); + private static Thread main; public static boolean freeResources = true; static public JPypeContext getInstance() @@ -104,6 +104,7 @@ static JPypeContext createContext(long context, ClassLoader bootLoader, String n { System.load(nativeLib); } + INSTANCE.main = Thread.currentThread(); INSTANCE.context = context; INSTANCE.classLoader = (DynamicClassLoader) bootLoader; INSTANCE.typeFactory = new TypeFactoryNative(); @@ -123,8 +124,10 @@ void initialize(boolean interrupt) // Okay everything is setup so lets give it a go. this.typeManager.init(); JPypeReferenceQueue.getInstance().start(); - if (!interrupt) - JPypeSignal.installHandlers(); + if (!interrupt && signal != null) + { + signal.installHandlers(); + } // Install a shutdown hook to clean up Python resources. Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() @@ -135,7 +138,6 @@ public void run() INSTANCE.shutdown(); } })); - } /** @@ -205,7 +207,9 @@ private void shutdown() for (Thread t : threads.keySet()) { if (t1 == t || t.isDaemon()) + { continue; + } t.interrupt(); } @@ -215,15 +219,6 @@ private void shutdown() // Wait for any unregistered proxies to finish so that we don't yank // the rug out from under them result in a segfault. -// while (this.proxyCount.get() > 0) -// { -// try -// { -// Thread.sleep(10); -// } catch (InterruptedException ex) -// { -// } -// } // // Check to see if who is alive // threads = Thread.getAllStackTraces(); // System.out.println("Check for remaining"); @@ -283,7 +278,9 @@ public boolean removeShutdownHook(Thread th) this.shutdownHooks.remove(th); return true; } else + { return Runtime.getRuntime().removeShutdownHook(th); + } } /** @@ -354,7 +351,9 @@ public Object callMethod(Method method, Object obj, Object[] args) private static boolean collect(List l, Object o, int q, int[] shape, int d) { if (Array.getLength(o) != shape[q]) + { return false; + } if (q + 1 == d) { l.add(o); @@ -363,7 +362,9 @@ private static boolean collect(List l, Object o, int q, int[] shape, int d) for (int i = 0; i < shape[q]; ++i) { if (!collect(l, Array.get(o, i), q + 1, shape, d)) + { return false; + } } return true; } @@ -385,7 +386,9 @@ private static boolean collect(List l, Object o, int q, int[] shape, int d) public Object[] collectRectangular(Object o) { if (o == null || !o.getClass().isArray()) + { return null; + } int[] shape = new int[5]; int d = 0; ArrayList out = new ArrayList<>(); @@ -395,28 +398,42 @@ public Object[] collectRectangular(Object o) { int l = Array.getLength(o1); if (l == 0) + { return null; + } shape[d++] = l; o1 = Array.get(o1, 0); if (o1 == null) + { return null; + } c1 = c1.getComponentType(); if (!c1.isArray()) + { break; + } } if (!c1.isPrimitive()) + { return null; + } out.add(c1); shape = Arrays.copyOfRange(shape, 0, d); out.add(shape); int total = 1; for (int i = 0; i < d - 1; i++) + { total *= shape[i]; + } out.ensureCapacity(total + 2); if (d == 5) + { return null; + } if (!collect(out, o, 0, shape, d)) + { return null; + } return out.toArray(); } @@ -437,7 +454,9 @@ private Object unpack(int size, Object parts) } Array.set(a1, i, a2); if (i < segments - 1) + { a2 = Array.newInstance(c, size); + } } return a1; } @@ -446,9 +465,13 @@ public Object assemble(int[] dims, Object parts) { int n = dims.length; if (n == 1) + { return Array.get(parts, 0); + } if (n == 2) + { return Array.get(unpack(dims[0], parts), 0); + } for (int i = 0; i < n - 2; ++i) { parts = unpack(dims[n - i - 2], parts); @@ -461,15 +484,6 @@ public boolean isShutdown() return shutdownFlag.get() > 0; } -// public void incrementProxy() -// { -// proxyCount.incrementAndGet(); -// } -// -// public void decrementProxy() -// { -// proxyCount.decrementAndGet(); -// } /** * Clear the current interrupt. * @@ -483,20 +497,24 @@ public static void clearInterrupt(boolean x) throws InterruptedException Thread th = Thread.currentThread(); // Only relevant if this is the main thread for signal handling - if (th != JPypeSignal.main) + if (th != main) + { return; + } // Unconditionally clear the interrupt flag if we are called from // C++. This happens when a field get() or method call() is // invoked. - if (!x) - JPypeSignal.acknowledgePy(); + if (!x && signal != null) + { + signal.acknowledgePy(); + } // Check if this thread is interrupted - if (th.isInterrupted()) + if (th.isInterrupted() && signal != null) { // Clear the flag in C++ - JPypeSignal.acknowledgePy(); + signal.acknowledgePy(); // Clear the flag in Java Thread.sleep(1); @@ -504,21 +522,27 @@ public static void clearInterrupt(boolean x) throws InterruptedException } catch (InterruptedException ex) { if (x) + { throw ex; + } } } public long getExcClass(Throwable th) { if (th instanceof PyExceptionProxy) + { return ((PyExceptionProxy) th).cls; + } return 0; } public long getExcValue(Throwable th) { if (th instanceof PyExceptionProxy) + { return ((PyExceptionProxy) th).value; + } return 0; } @@ -530,19 +554,33 @@ public Exception createException(long l0, long l1) public boolean order(Buffer b) { if (b instanceof java.nio.ByteBuffer) + { return ((java.nio.ByteBuffer) b).order() == ByteOrder.LITTLE_ENDIAN; + } if (b instanceof java.nio.ShortBuffer) + { return ((java.nio.ShortBuffer) b).order() == ByteOrder.LITTLE_ENDIAN; + } if (b instanceof java.nio.CharBuffer) + { return ((java.nio.CharBuffer) b).order() == ByteOrder.LITTLE_ENDIAN; + } if (b instanceof java.nio.IntBuffer) + { return ((java.nio.IntBuffer) b).order() == ByteOrder.LITTLE_ENDIAN; + } if (b instanceof java.nio.LongBuffer) + { return ((java.nio.LongBuffer) b).order() == ByteOrder.LITTLE_ENDIAN; + } if (b instanceof java.nio.FloatBuffer) + { return ((java.nio.FloatBuffer) b).order() == ByteOrder.LITTLE_ENDIAN; + } if (b instanceof java.nio.DoubleBuffer) + { return ((java.nio.DoubleBuffer) b).order() == ByteOrder.LITTLE_ENDIAN; + } return true; } @@ -556,7 +594,9 @@ public JPypePackage getPackage(String s) { s = JPypeKeywords.safepkg(s); if (!JPypePackageManager.isPackage(s)) + { return null; + } return new JPypePackage(s); } @@ -587,10 +627,14 @@ public Object[] getStackTrace(Throwable th, Throwable enclosing) { StackTraceElement[] trace = th.getStackTrace(); if (trace == null || enclosing == null) + { return toFrames(trace); + } StackTraceElement[] te = enclosing.getStackTrace(); if (te == null) + { return toFrames(trace); + } for (int i = 0; i < trace.length; ++i) { if (trace[i].equals(te[0])) @@ -604,7 +648,9 @@ public Object[] getStackTrace(Throwable th, Throwable enclosing) private Object[] toFrames(StackTraceElement[] stackTrace) { if (stackTrace == null) + { return null; + } Object[] out = new Object[4 * stackTrace.length]; int i = 0; for (StackTraceElement fr : stackTrace) diff --git a/native/java/org/jpype/JPypeKeywords.java b/native/java/jpype.core/org/jpype/JPypeKeywords.java similarity index 100% rename from native/java/org/jpype/JPypeKeywords.java rename to native/java/jpype.core/org/jpype/JPypeKeywords.java diff --git a/native/java/jpype.core/org/jpype/JPypeSignal.java b/native/java/jpype.core/org/jpype/JPypeSignal.java new file mode 100644 index 000000000..c906ef8e4 --- /dev/null +++ b/native/java/jpype.core/org/jpype/JPypeSignal.java @@ -0,0 +1,43 @@ +/** *************************************************************************** + * 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. + * + * See NOTICE file for details. + **************************************************************************** */ +package org.jpype; + +/** + * + * @author nelson85 + */ +public interface JPypeSignal +{ + + static public JPypeSignal newInstance() + { + try + { + Class cls = Class.forName("org.jpype.jvm.JPypeSignalImpl"); + return (JPypeSignal) cls.newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) + { + } + return null; + } + + void acknowledgePy(); + + void installHandlers(); + + void interruptPy(); + +} diff --git a/native/java/org/jpype/JPypeUtilities.java b/native/java/jpype.core/org/jpype/JPypeUtilities.java similarity index 100% rename from native/java/org/jpype/JPypeUtilities.java rename to native/java/jpype.core/org/jpype/JPypeUtilities.java diff --git a/native/java/org/jpype/PyExceptionProxy.java b/native/java/jpype.core/org/jpype/PyExceptionProxy.java similarity index 57% rename from native/java/org/jpype/PyExceptionProxy.java rename to native/java/jpype.core/org/jpype/PyExceptionProxy.java index ffbe93184..88408c4f6 100644 --- a/native/java/org/jpype/PyExceptionProxy.java +++ b/native/java/jpype.core/org/jpype/PyExceptionProxy.java @@ -16,4 +16,12 @@ public PyExceptionProxy(long l0, long l1) value = l1; } + @Override + public String getMessage() + { + return _getMessage(this.cls, this.value); + } + + private native static String _getMessage(long cls, long value); + } diff --git a/native/java/org/jpype/manager/ClassDescriptor.java b/native/java/jpype.core/org/jpype/manager/ClassDescriptor.java similarity index 100% rename from native/java/org/jpype/manager/ClassDescriptor.java rename to native/java/jpype.core/org/jpype/manager/ClassDescriptor.java diff --git a/native/java/org/jpype/manager/MethodResolution.java b/native/java/jpype.core/org/jpype/manager/MethodResolution.java similarity index 100% rename from native/java/org/jpype/manager/MethodResolution.java rename to native/java/jpype.core/org/jpype/manager/MethodResolution.java diff --git a/native/java/org/jpype/manager/ModifierCode.java b/native/java/jpype.core/org/jpype/manager/ModifierCode.java similarity index 96% rename from native/java/org/jpype/manager/ModifierCode.java rename to native/java/jpype.core/org/jpype/manager/ModifierCode.java index aa0c9cb27..108f6f2c8 100644 --- a/native/java/org/jpype/manager/ModifierCode.java +++ b/native/java/jpype.core/org/jpype/manager/ModifierCode.java @@ -21,7 +21,7 @@ /** * Definitions for JPype modifiers. *

- * These pretty much match Java plus a few codes we need. + * These match Java plus a few codes we need. * * @author nelson85 */ @@ -49,6 +49,7 @@ public enum ModifierCode PRIMITIVE_ARRAY(0x00400000), COMPARABLE(0x00800000), BUFFER(0x01000000), + PYTHON(0x02010000), CTOR(0x10000000), BEAN_ACCESSOR(0x20000000), BEAN_MUTATOR(0x40000000); diff --git a/native/java/org/jpype/manager/TypeAudit.java b/native/java/jpype.core/org/jpype/manager/TypeAudit.java similarity index 100% rename from native/java/org/jpype/manager/TypeAudit.java rename to native/java/jpype.core/org/jpype/manager/TypeAudit.java diff --git a/native/java/org/jpype/manager/TypeFactory.java b/native/java/jpype.core/org/jpype/manager/TypeFactory.java similarity index 100% rename from native/java/org/jpype/manager/TypeFactory.java rename to native/java/jpype.core/org/jpype/manager/TypeFactory.java diff --git a/native/java/org/jpype/manager/TypeFactoryNative.java b/native/java/jpype.core/org/jpype/manager/TypeFactoryNative.java similarity index 100% rename from native/java/org/jpype/manager/TypeFactoryNative.java rename to native/java/jpype.core/org/jpype/manager/TypeFactoryNative.java diff --git a/native/java/jpype.core/org/jpype/manager/TypeInfo.java b/native/java/jpype.core/org/jpype/manager/TypeInfo.java new file mode 100644 index 000000000..2148c3ef4 --- /dev/null +++ b/native/java/jpype.core/org/jpype/manager/TypeInfo.java @@ -0,0 +1,29 @@ +/** *************************************************************************** + * 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. + * + * See NOTICE file for details. + **************************************************************************** */ +package org.jpype.manager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * + * @author nelson85 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface TypeInfo +{ + int modifier(); +} diff --git a/native/java/org/jpype/manager/TypeManager.java b/native/java/jpype.core/org/jpype/manager/TypeManager.java similarity index 96% rename from native/java/org/jpype/manager/TypeManager.java rename to native/java/jpype.core/org/jpype/manager/TypeManager.java index 491a53834..0f9b5536d 100644 --- a/native/java/org/jpype/manager/TypeManager.java +++ b/native/java/jpype.core/org/jpype/manager/TypeManager.java @@ -27,6 +27,7 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.nio.Buffer; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -46,12 +47,13 @@ public class TypeManager public boolean isStarted = false; public boolean isShutdown = false; public HashMap classMap = new HashMap<>(); - public TypeFactory typeFactory = null; + TypeFactory typeFactory = null; public TypeAudit audit = null; private ClassDescriptor java_lang_Object; // For reasons that are less than clear, this object cannot be created // during shutdown private Destroyer destroyer = new Destroyer(); + private List extensions = new ArrayList<>(); public TypeManager() { @@ -74,7 +76,7 @@ public synchronized void init() isShutdown = false; // Create the required minimum classes - this.java_lang_Object = createClass(Object.class, true); + this.java_lang_Object = defineClass(Object.class, true, ModifierCode.SPECIAL.value); // Note that order is very important when creating these initial wrapper // types. If something inherits from another type then the super class @@ -90,7 +92,7 @@ public synchronized void init() }; for (Class c : cls) { - createClass(c, true); + defineClass(c, true, ModifierCode.SPECIAL.value); } // Create the primitive types @@ -367,6 +369,11 @@ public synchronized void shutdown() // // + public void registerExtension(TypeManagerExtension arg0) + { + this.extensions.add(arg0); + } + private ClassDescriptor getClass(Class cls) { if (cls == null) @@ -377,28 +384,27 @@ private ClassDescriptor getClass(Class cls) if (ptr != null) return ptr; - // If we can't find it create a new class - return createClass(cls, false); - } - - /** - * Allocate a new wrapper for a java class. - *

- * Boxed types require special handlers, as does java.lang.String - * - * @param cls is the Java class to wrap. - * @param special marks class as requiring a specialized C++ wrapper. - * @return a C++ wrapper handle for a jp_classtype - */ - private ClassDescriptor createClass(Class cls, boolean special) - { if (cls.isArray()) return this.createArrayClass(cls); - return createOrdinaryClass(cls, special, true); + // Check if this is managed by a different extension. + for (TypeManagerExtension extension:extensions) + { + if (extension.handles(cls)) + return extension.createClass(this, cls); + } + + return defineClass(cls, true, 0); } - private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boolean bases) + /** + * Creates a C++ resource for a class. + * + * @param cls is the class wrapper to be created. + * @param bases is true if the bases should be included in the C++ wrapper. + * @return + */ + public ClassDescriptor defineClass(Class cls, boolean bases, int modifier) { // Verify the class will be loadable prior to creating the class. // If we fail to do this then the class may end up crashing later when the @@ -437,8 +443,7 @@ private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boole // Set up the modifiers int modifiers = cls.getModifiers() & 0xffff; - if (special) - modifiers |= ModifierCode.SPECIAL.value; + modifiers |= modifier; if (Throwable.class.isAssignableFrom(cls)) modifiers |= ModifierCode.THROWABLE.value; if (Serializable.class.isAssignableFrom(cls)) diff --git a/native/java/jpype.core/org/jpype/manager/TypeManagerExtension.java b/native/java/jpype.core/org/jpype/manager/TypeManagerExtension.java new file mode 100644 index 000000000..8ad724294 --- /dev/null +++ b/native/java/jpype.core/org/jpype/manager/TypeManagerExtension.java @@ -0,0 +1,41 @@ +/** *************************************************************************** + * 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. + * + * See NOTICE file for details. + **************************************************************************** */ +package org.jpype.manager; + +/** + * + * @author nelson85 + */ +public interface TypeManagerExtension +{ + /** + * Create a class descriptor for this class. + * + * @param typeManager + * @param cls + * @return + */ + ClassDescriptor createClass(TypeManager typeManager, Class cls); + + /** + * Returns true if this typemanager extension handles this type. + * + * @param cls + * @return + */ + boolean handles(Class cls); + +} diff --git a/native/java/org/jpype/pkg/JPypePackage.java b/native/java/jpype.core/org/jpype/pkg/JPypePackage.java similarity index 100% rename from native/java/org/jpype/pkg/JPypePackage.java rename to native/java/jpype.core/org/jpype/pkg/JPypePackage.java diff --git a/native/java/org/jpype/pkg/JPypePackageManager.java b/native/java/jpype.core/org/jpype/pkg/JPypePackageManager.java similarity index 99% rename from native/java/org/jpype/pkg/JPypePackageManager.java rename to native/java/jpype.core/org/jpype/pkg/JPypePackageManager.java index 0068d5a67..f386f3b63 100644 --- a/native/java/org/jpype/pkg/JPypePackageManager.java +++ b/native/java/jpype.core/org/jpype/pkg/JPypePackageManager.java @@ -149,6 +149,7 @@ private static FileSystemProvider getFileSystemProvider(String str) */ static { + System.out.println("Package manager init"); env.put("create", "true"); ClassLoader cl = ClassLoader.getSystemClassLoader(); diff --git a/native/java/org/jpype/proxy/JPypeProxy.java b/native/java/jpype.core/org/jpype/proxy/JPypeProxy.java similarity index 100% rename from native/java/org/jpype/proxy/JPypeProxy.java rename to native/java/jpype.core/org/jpype/proxy/JPypeProxy.java diff --git a/native/java/org/jpype/ref/JPypeReference.java b/native/java/jpype.core/org/jpype/ref/JPypeReference.java similarity index 100% rename from native/java/org/jpype/ref/JPypeReference.java rename to native/java/jpype.core/org/jpype/ref/JPypeReference.java diff --git a/native/java/org/jpype/ref/JPypeReferenceNative.java b/native/java/jpype.core/org/jpype/ref/JPypeReferenceNative.java similarity index 100% rename from native/java/org/jpype/ref/JPypeReferenceNative.java rename to native/java/jpype.core/org/jpype/ref/JPypeReferenceNative.java diff --git a/native/java/org/jpype/ref/JPypeReferenceQueue.java b/native/java/jpype.core/org/jpype/ref/JPypeReferenceQueue.java similarity index 100% rename from native/java/org/jpype/ref/JPypeReferenceQueue.java rename to native/java/jpype.core/org/jpype/ref/JPypeReferenceQueue.java diff --git a/native/java/org/jpype/ref/JPypeReferenceSet.java b/native/java/jpype.core/org/jpype/ref/JPypeReferenceSet.java similarity index 100% rename from native/java/org/jpype/ref/JPypeReferenceSet.java rename to native/java/jpype.core/org/jpype/ref/JPypeReferenceSet.java diff --git a/native/java/org/jpype/html/AttrGrammar.java b/native/java/jpype.doc/org/jpype/html/AttrGrammar.java similarity index 100% rename from native/java/org/jpype/html/AttrGrammar.java rename to native/java/jpype.doc/org/jpype/html/AttrGrammar.java diff --git a/native/java/org/jpype/html/AttrParser.java b/native/java/jpype.doc/org/jpype/html/AttrParser.java similarity index 100% rename from native/java/org/jpype/html/AttrParser.java rename to native/java/jpype.doc/org/jpype/html/AttrParser.java diff --git a/native/java/org/jpype/html/Html.java b/native/java/jpype.doc/org/jpype/html/Html.java similarity index 100% rename from native/java/org/jpype/html/Html.java rename to native/java/jpype.doc/org/jpype/html/Html.java diff --git a/native/java/org/jpype/html/HtmlGrammar.java b/native/java/jpype.doc/org/jpype/html/HtmlGrammar.java similarity index 100% rename from native/java/org/jpype/html/HtmlGrammar.java rename to native/java/jpype.doc/org/jpype/html/HtmlGrammar.java diff --git a/native/java/org/jpype/html/HtmlHandler.java b/native/java/jpype.doc/org/jpype/html/HtmlHandler.java similarity index 100% rename from native/java/org/jpype/html/HtmlHandler.java rename to native/java/jpype.doc/org/jpype/html/HtmlHandler.java diff --git a/native/java/org/jpype/html/HtmlParser.java b/native/java/jpype.doc/org/jpype/html/HtmlParser.java similarity index 100% rename from native/java/org/jpype/html/HtmlParser.java rename to native/java/jpype.doc/org/jpype/html/HtmlParser.java diff --git a/native/java/org/jpype/html/HtmlTreeHandler.java b/native/java/jpype.doc/org/jpype/html/HtmlTreeHandler.java similarity index 100% rename from native/java/org/jpype/html/HtmlTreeHandler.java rename to native/java/jpype.doc/org/jpype/html/HtmlTreeHandler.java diff --git a/native/java/org/jpype/html/HtmlWriter.java b/native/java/jpype.doc/org/jpype/html/HtmlWriter.java similarity index 100% rename from native/java/org/jpype/html/HtmlWriter.java rename to native/java/jpype.doc/org/jpype/html/HtmlWriter.java diff --git a/native/java/org/jpype/html/Parser.java b/native/java/jpype.doc/org/jpype/html/Parser.java similarity index 100% rename from native/java/org/jpype/html/Parser.java rename to native/java/jpype.doc/org/jpype/html/Parser.java diff --git a/native/java/org/jpype/html/entities.txt b/native/java/jpype.doc/org/jpype/html/entities.txt similarity index 100% rename from native/java/org/jpype/html/entities.txt rename to native/java/jpype.doc/org/jpype/html/entities.txt diff --git a/native/java/org/jpype/javadoc/DomUtilities.java b/native/java/jpype.doc/org/jpype/javadoc/DomUtilities.java similarity index 100% rename from native/java/org/jpype/javadoc/DomUtilities.java rename to native/java/jpype.doc/org/jpype/javadoc/DomUtilities.java diff --git a/native/java/org/jpype/javadoc/Javadoc.java b/native/java/jpype.doc/org/jpype/javadoc/Javadoc.java similarity index 100% rename from native/java/org/jpype/javadoc/Javadoc.java rename to native/java/jpype.doc/org/jpype/javadoc/Javadoc.java diff --git a/native/java/org/jpype/javadoc/JavadocException.java b/native/java/jpype.doc/org/jpype/javadoc/JavadocException.java similarity index 100% rename from native/java/org/jpype/javadoc/JavadocException.java rename to native/java/jpype.doc/org/jpype/javadoc/JavadocException.java diff --git a/native/java/org/jpype/javadoc/JavadocExtractor.java b/native/java/jpype.doc/org/jpype/javadoc/JavadocExtractor.java similarity index 100% rename from native/java/org/jpype/javadoc/JavadocExtractor.java rename to native/java/jpype.doc/org/jpype/javadoc/JavadocExtractor.java diff --git a/native/java/org/jpype/javadoc/JavadocRenderer.java b/native/java/jpype.doc/org/jpype/javadoc/JavadocRenderer.java similarity index 100% rename from native/java/org/jpype/javadoc/JavadocRenderer.java rename to native/java/jpype.doc/org/jpype/javadoc/JavadocRenderer.java diff --git a/native/java/org/jpype/javadoc/JavadocTransformer.java b/native/java/jpype.doc/org/jpype/javadoc/JavadocTransformer.java similarity index 100% rename from native/java/org/jpype/javadoc/JavadocTransformer.java rename to native/java/jpype.doc/org/jpype/javadoc/JavadocTransformer.java diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/AnnotationVisitor.java b/native/java/jpype.jvm.asm/org/jpype/asm/AnnotationVisitor.java new file mode 100644 index 000000000..bc34fd7e8 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/AnnotationVisitor.java @@ -0,0 +1,157 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A visitor to visit a Java annotation. The methods of this class must be called in the following + * order: ( {@code visit} | {@code visitEnum} | {@code visitAnnotation} | {@code visitArray} )* + * {@code visitEnd}. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public abstract class AnnotationVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + protected final int api; + + /** + * The annotation visitor to which this visitor must delegate method calls. May be {@literal + * null}. + */ + protected AnnotationVisitor av; + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + public AnnotationVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param annotationVisitor the annotation visitor to which this visitor must delegate method + * calls. May be {@literal null}. + */ + public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); + } + this.api = api; + this.av = annotationVisitor; + } + + /** + * Visits a primitive value of the annotation. + * + * @param name the value name. + * @param value the actual value, whose type must be {@link Byte}, {@link Boolean}, {@link + * Character}, {@link Short}, {@link Integer} , {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type} of {@link Type#OBJECT} or {@link Type#ARRAY} sort. This + * value can also be an array of byte, boolean, short, char, int, long, float or double values + * (this is equivalent to using {@link #visitArray} and visiting each array element in turn, + * but is more convenient). + */ + public void visit(final String name, final Object value) { + if (av != null) { + av.visit(name, value); + } + } + + /** + * Visits an enumeration value of the annotation. + * + * @param name the value name. + * @param descriptor the class descriptor of the enumeration class. + * @param value the actual enumeration value. + */ + public void visitEnum(final String name, final String descriptor, final String value) { + if (av != null) { + av.visitEnum(name, descriptor, value); + } + } + + /** + * Visits a nested annotation value of the annotation. + * + * @param name the value name. + * @param descriptor the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or {@literal null} if this + * visitor is not interested in visiting this nested annotation. The nested annotation + * value must be fully visited before calling other methods on this annotation visitor. + */ + public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { + if (av != null) { + return av.visitAnnotation(name, descriptor); + } + return null; + } + + /** + * Visits an array value of the annotation. Note that arrays of primitive types (such as byte, + * boolean, short, char, int, long, float or double) can be passed as value to {@link #visit + * visit}. This is what {@link ClassReader} does. + * + * @param name the value name. + * @return a visitor to visit the actual array value elements, or {@literal null} if this visitor + * is not interested in visiting these values. The 'name' parameters passed to the methods of + * this visitor are ignored. All the array values must be visited before calling other + * methods on this annotation visitor. + */ + public AnnotationVisitor visitArray(final String name) { + if (av != null) { + return av.visitArray(name); + } + return null; + } + + /** Visits the end of the annotation. */ + public void visitEnd() { + if (av != null) { + av.visitEnd(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/AnnotationWriter.java b/native/java/jpype.jvm.asm/org/jpype/asm/AnnotationWriter.java new file mode 100644 index 000000000..6ab397d3a --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/AnnotationWriter.java @@ -0,0 +1,553 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * An {@link AnnotationVisitor} that generates a corresponding 'annotation' or 'type_annotation' + * structure, as defined in the Java Virtual Machine Specification (JVMS). AnnotationWriter + * instances can be chained in a doubly linked list, from which Runtime[In]Visible[Type]Annotations + * attributes can be generated with the {@link #putAnnotations} method. Similarly, arrays of such + * lists can be used to generate Runtime[In]VisibleParameterAnnotations attributes. + * + * @see JVMS + * 4.7.16 + * @see JVMS + * 4.7.20 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class AnnotationWriter extends AnnotationVisitor { + + /** Where the constants used in this AnnotationWriter must be stored. */ + private final SymbolTable symbolTable; + + /** + * Whether values are named or not. AnnotationWriter instances used for annotation default and + * annotation arrays use unnamed values (i.e. they generate an 'element_value' structure for each + * value, instead of an element_name_index followed by an element_value). + */ + private final boolean useNamedValues; + + /** + * The 'annotation' or 'type_annotation' JVMS structure corresponding to the annotation values + * visited so far. All the fields of these structures, except the last one - the + * element_value_pairs array, must be set before this ByteVector is passed to the constructor + * (num_element_value_pairs can be set to 0, it is reset to the correct value in {@link + * #visitEnd()}). The element_value_pairs array is filled incrementally in the various visit() + * methods. + * + *

Note: as an exception to the above rules, for AnnotationDefault attributes (which contain a + * single element_value by definition), this ByteVector is initially empty when passed to the + * constructor, and {@link #numElementValuePairsOffset} is set to -1. + */ + private final ByteVector annotation; + + /** + * The offset in {@link #annotation} where {@link #numElementValuePairs} must be stored (or -1 for + * the case of AnnotationDefault attributes). + */ + private final int numElementValuePairsOffset; + + /** The number of element value pairs visited so far. */ + private int numElementValuePairs; + + /** + * The previous AnnotationWriter. This field is used to store the list of annotations of a + * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations + * (annotation values of annotation type), or for AnnotationDefault attributes. + */ + private final AnnotationWriter previousAnnotation; + + /** + * The next AnnotationWriter. This field is used to store the list of annotations of a + * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations + * (annotation values of annotation type), or for AnnotationDefault attributes. + */ + private AnnotationWriter nextAnnotation; + + // ----------------------------------------------------------------------------------------------- + // Constructors and factories + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param useNamedValues whether values are named or not. AnnotationDefault and annotation arrays + * use unnamed values. + * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to + * the visited content must be stored. This ByteVector must already contain all the fields of + * the structure except the last one (the element_value_pairs array). + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + */ + AnnotationWriter( + final SymbolTable symbolTable, + final boolean useNamedValues, + final ByteVector annotation, + final AnnotationWriter previousAnnotation) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.useNamedValues = useNamedValues; + this.annotation = annotation; + // By hypothesis, num_element_value_pairs is stored in the last unsigned short of 'annotation'. + this.numElementValuePairsOffset = annotation.length == 0 ? -1 : annotation.length - 2; + this.previousAnnotation = previousAnnotation; + if (previousAnnotation != null) { + previousAnnotation.nextAnnotation = this; + } + } + + /** + * Creates a new {@link AnnotationWriter} using named values. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param descriptor the class descriptor of the annotation class. + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + * @return a new {@link AnnotationWriter} for the given annotation descriptor. + */ + static AnnotationWriter create( + final SymbolTable symbolTable, + final String descriptor, + final AnnotationWriter previousAnnotation) { + // Create a ByteVector to hold an 'annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. + ByteVector annotation = new ByteVector(); + // Write type_index and reserve space for num_element_value_pairs. + annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter( + symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation); + } + + /** + * Creates a new {@link AnnotationWriter} using named values. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + * @return a new {@link AnnotationWriter} for the given type annotation reference and descriptor. + */ + static AnnotationWriter create( + final SymbolTable symbolTable, + final int typeRef, + final TypePath typePath, + final String descriptor, + final AnnotationWriter previousAnnotation) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget(typeRef, typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter( + symbolTable, /* useNamedValues = */ true, typeAnnotation, previousAnnotation); + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the AnnotationVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public void visit(final String name, final Object value) { + // Case of an element_value with a const_value_index, class_info_index or array_index field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + if (value instanceof String) { + annotation.put12('s', symbolTable.addConstantUtf8((String) value)); + } else if (value instanceof Byte) { + annotation.put12('B', symbolTable.addConstantInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + int booleanValue = ((Boolean) value).booleanValue() ? 1 : 0; + annotation.put12('Z', symbolTable.addConstantInteger(booleanValue).index); + } else if (value instanceof Character) { + annotation.put12('C', symbolTable.addConstantInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + annotation.put12('S', symbolTable.addConstantInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + annotation.put12('c', symbolTable.addConstantUtf8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + byte[] byteArray = (byte[]) value; + annotation.put12('[', byteArray.length); + for (byte byteValue : byteArray) { + annotation.put12('B', symbolTable.addConstantInteger(byteValue).index); + } + } else if (value instanceof boolean[]) { + boolean[] booleanArray = (boolean[]) value; + annotation.put12('[', booleanArray.length); + for (boolean booleanValue : booleanArray) { + annotation.put12('Z', symbolTable.addConstantInteger(booleanValue ? 1 : 0).index); + } + } else if (value instanceof short[]) { + short[] shortArray = (short[]) value; + annotation.put12('[', shortArray.length); + for (short shortValue : shortArray) { + annotation.put12('S', symbolTable.addConstantInteger(shortValue).index); + } + } else if (value instanceof char[]) { + char[] charArray = (char[]) value; + annotation.put12('[', charArray.length); + for (char charValue : charArray) { + annotation.put12('C', symbolTable.addConstantInteger(charValue).index); + } + } else if (value instanceof int[]) { + int[] intArray = (int[]) value; + annotation.put12('[', intArray.length); + for (int intValue : intArray) { + annotation.put12('I', symbolTable.addConstantInteger(intValue).index); + } + } else if (value instanceof long[]) { + long[] longArray = (long[]) value; + annotation.put12('[', longArray.length); + for (long longValue : longArray) { + annotation.put12('J', symbolTable.addConstantLong(longValue).index); + } + } else if (value instanceof float[]) { + float[] floatArray = (float[]) value; + annotation.put12('[', floatArray.length); + for (float floatValue : floatArray) { + annotation.put12('F', symbolTable.addConstantFloat(floatValue).index); + } + } else if (value instanceof double[]) { + double[] doubleArray = (double[]) value; + annotation.put12('[', doubleArray.length); + for (double doubleValue : doubleArray) { + annotation.put12('D', symbolTable.addConstantDouble(doubleValue).index); + } + } else { + Symbol symbol = symbolTable.addConstant(value); + annotation.put12(".s.IFJDCS".charAt(symbol.tag), symbol.index); + } + } + + @Override + public void visitEnum(final String name, final String descriptor, final String value) { + // Case of an element_value with an enum_const_value field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + annotation + .put12('e', symbolTable.addConstantUtf8(descriptor)) + .putShort(symbolTable.addConstantUtf8(value)); + } + + @Override + public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { + // Case of an element_value with an annotation_value field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs. + annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ true, annotation, null); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + // Case of an element_value with an array_value field. + // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1 + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + // Write tag, and reserve 2 bytes for num_values. Here we take advantage of the fact that the + // end of an element_value of array type is similar to the end of an 'annotation' structure: an + // unsigned short num_values followed by num_values element_value, versus an unsigned short + // num_element_value_pairs, followed by num_element_value_pairs { element_name_index, + // element_value } tuples. This allows us to use an AnnotationWriter with unnamed values to + // visit the array elements. Its num_element_value_pairs will correspond to the number of array + // elements and will be stored in what is in fact num_values. + annotation.put12('[', 0); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null); + } + + @Override + public void visitEnd() { + if (numElementValuePairsOffset != -1) { + byte[] data = annotation.data; + data[numElementValuePairsOffset] = (byte) (numElementValuePairs >>> 8); + data[numElementValuePairsOffset + 1] = (byte) numElementValuePairs; + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of a Runtime[In]Visible[Type]Annotations attribute containing this annotation + * and all its predecessors (see {@link #previousAnnotation}. Also adds the attribute name + * to the constant pool of the class (if not null). + * + * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or {@literal null}. + * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this + * annotation and all its predecessors. This includes the size of the attribute_name_index and + * attribute_length fields. + */ + int computeAnnotationsSize(final String attributeName) { + if (attributeName != null) { + symbolTable.addConstantUtf8(attributeName); + } + // The attribute_name_index, attribute_length and num_annotations fields use 8 bytes. + int attributeSize = 8; + AnnotationWriter annotationWriter = this; + while (annotationWriter != null) { + attributeSize += annotationWriter.annotation.length; + annotationWriter = annotationWriter.previousAnnotation; + } + return attributeSize; + } + + /** + * Returns the size of the Runtime[In]Visible[Type]Annotations attributes containing the given + * annotations and all their predecessors (see {@link #previousAnnotation}. Also adds the + * attribute names to the constant pool of the class (if not null). + * + * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or + * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be + * {@literal null}. + * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field, + * method or class. The previous ones can be accessed with the {@link #previousAnnotation} + * field. May be {@literal null}. + * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a + * field, method or class. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a + * field, method or class field. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing the + * given annotations and all their predecessors. This includes the size of the + * attribute_name_index and attribute_length fields. + */ + static int computeAnnotationsSize( + final AnnotationWriter lastRuntimeVisibleAnnotation, + final AnnotationWriter lastRuntimeInvisibleAnnotation, + final AnnotationWriter lastRuntimeVisibleTypeAnnotation, + final AnnotationWriter lastRuntimeInvisibleTypeAnnotation) { + int size = 0; + if (lastRuntimeVisibleAnnotation != null) { + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + return size; + } + + /** + * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its + * predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are + * put in the same order they have been visited. + * + * @param attributeNameIndex the constant pool index of the attribute name (one of + * "Runtime[In]Visible[Type]Annotations"). + * @param output where the attribute must be put. + */ + void putAnnotations(final int attributeNameIndex, final ByteVector output) { + int attributeLength = 2; // For num_annotations. + int numAnnotations = 0; + AnnotationWriter annotationWriter = this; + AnnotationWriter firstAnnotation = null; + while (annotationWriter != null) { + // In case the user forgot to call visitEnd(). + annotationWriter.visitEnd(); + attributeLength += annotationWriter.annotation.length; + numAnnotations++; + firstAnnotation = annotationWriter; + annotationWriter = annotationWriter.previousAnnotation; + } + output.putShort(attributeNameIndex); + output.putInt(attributeLength); + output.putShort(numAnnotations); + annotationWriter = firstAnnotation; + while (annotationWriter != null) { + output.putByteArray(annotationWriter.annotation.data, 0, annotationWriter.annotation.length); + annotationWriter = annotationWriter.nextAnnotation; + } + } + + /** + * Puts the Runtime[In]Visible[Type]Annotations attributes containing the given annotations and + * all their predecessors (see {@link #previousAnnotation} in the given ByteVector. + * Annotations are put in the same order they have been visited. + * + * @param symbolTable where the constants used in the AnnotationWriter instances are stored. + * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or + * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be + * {@literal null}. + * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field, + * method or class. The previous ones can be accessed with the {@link #previousAnnotation} + * field. May be {@literal null}. + * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a + * field, method or class. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a + * field, method or class field. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param output where the attributes must be put. + */ + static void putAnnotations( + final SymbolTable symbolTable, + final AnnotationWriter lastRuntimeVisibleAnnotation, + final AnnotationWriter lastRuntimeInvisibleAnnotation, + final AnnotationWriter lastRuntimeVisibleTypeAnnotation, + final AnnotationWriter lastRuntimeInvisibleTypeAnnotation, + final ByteVector output) { + if (lastRuntimeVisibleAnnotation != null) { + lastRuntimeVisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleAnnotation != null) { + lastRuntimeInvisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + lastRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + lastRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + } + + /** + * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the + * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the + * constant pool of the class. + * + * @param attributeName one of "Runtime[In]VisibleParameterAnnotations". + * @param annotationWriters an array of AnnotationWriter lists (designated by their last + * element). + * @param annotableParameterCount the number of elements in annotationWriters to take into account + * (elements [0..annotableParameterCount[ are taken into account). + * @return the size in bytes of a Runtime[In]VisibleParameterAnnotations attribute corresponding + * to the given sub-array of AnnotationWriter lists. This includes the size of the + * attribute_name_index and attribute_length fields. + */ + static int computeParameterAnnotationsSize( + final String attributeName, + final AnnotationWriter[] annotationWriters, + final int annotableParameterCount) { + // Note: attributeName is added to the constant pool by the call to computeAnnotationsSize + // below. This assumes that there is at least one non-null element in the annotationWriters + // sub-array (which is ensured by the lazy instantiation of this array in MethodWriter). + // The attribute_name_index, attribute_length and num_parameters fields use 7 bytes, and each + // element of the parameter_annotations array uses 2 bytes for its num_annotations field. + int attributeSize = 7 + 2 * annotableParameterCount; + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + attributeSize += + annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8; + } + return attributeSize; + } + + /** + * Puts a Runtime[In]VisibleParameterAnnotations attribute containing all the annotation lists + * from the given AnnotationWriter sub-array in the given ByteVector. + * + * @param attributeNameIndex constant pool index of the attribute name (one of + * Runtime[In]VisibleParameterAnnotations). + * @param annotationWriters an array of AnnotationWriter lists (designated by their last + * element). + * @param annotableParameterCount the number of elements in annotationWriters to put (elements + * [0..annotableParameterCount[ are put). + * @param output where the attribute must be put. + */ + static void putParameterAnnotations( + final int attributeNameIndex, + final AnnotationWriter[] annotationWriters, + final int annotableParameterCount, + final ByteVector output) { + // The num_parameters field uses 1 byte, and each element of the parameter_annotations array + // uses 2 bytes for its num_annotations field. + int attributeLength = 1 + 2 * annotableParameterCount; + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + attributeLength += + annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8; + } + output.putShort(attributeNameIndex); + output.putInt(attributeLength); + output.putByte(annotableParameterCount); + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + AnnotationWriter firstAnnotation = null; + int numAnnotations = 0; + while (annotationWriter != null) { + // In case user the forgot to call visitEnd(). + annotationWriter.visitEnd(); + numAnnotations++; + firstAnnotation = annotationWriter; + annotationWriter = annotationWriter.previousAnnotation; + } + output.putShort(numAnnotations); + annotationWriter = firstAnnotation; + while (annotationWriter != null) { + output.putByteArray( + annotationWriter.annotation.data, 0, annotationWriter.annotation.length); + annotationWriter = annotationWriter.nextAnnotation; + } + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Attribute.java b/native/java/jpype.jvm.asm/org/jpype/asm/Attribute.java new file mode 100644 index 000000000..b0981122d --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Attribute.java @@ -0,0 +1,392 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A non standard class, field, method or Code attribute, as defined in the Java Virtual Machine + * Specification (JVMS). + * + * @see JVMS + * 4.7 + * @see JVMS + * 4.7.3 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Attribute { + + /** The type of this attribute, also called its name in the JVMS. */ + public final String type; + + /** + * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}). + * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not + * included. + */ + private byte[] content; + + /** + * The next attribute in this attribute list (Attribute instances can be linked via this field to + * store a list of class, field, method or Code attributes). May be {@literal null}. + */ + Attribute nextAttribute; + + /** + * Constructs a new empty attribute. + * + * @param type the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns {@literal true} if this type of attribute is unknown. This means that the attribute + * content can't be parsed to extract constant pool references, labels, etc. Instead, the + * attribute content is read as an opaque byte array, and written back as is. This can lead to + * invalid attributes, if the content actually contains constant pool references, labels, or other + * symbolic references that need to be updated when there are changes to the constant pool, the + * method bytecode, etc. The default implementation of this method always returns {@literal true}. + * + * @return {@literal true} if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns {@literal true} if this type of attribute is a Code attribute. + * + * @return {@literal true} if this type of attribute is a Code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not + * a Code attribute that contains labels. + */ + protected Label[] getLabels() { + return new Label[0]; + } + + /** + * Reads a {@link #type} attribute. This method must return a new {@link Attribute} object, + * of type {@link #type}, corresponding to the 'length' bytes starting at 'offset', in the given + * ClassReader. + * + * @param classReader the class that contains the attribute to be read. + * @param offset index of the first byte of the attribute's content in {@link ClassReader}. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to call the ClassReader methods requiring a + * 'charBuffer' parameter. + * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute + * in {@link ClassReader}, or -1 if the attribute to be read is not a Code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param labels the labels of the method's code, or {@literal null} if the attribute to be read + * is not a Code attribute. + * @return a new {@link Attribute} object corresponding to the specified bytes. + */ + protected Attribute read( + final ClassReader classReader, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + Attribute attribute = new Attribute(type); + attribute.content = new byte[length]; + System.arraycopy(classReader.classFileBuffer, offset, attribute.content, 0, length); + return attribute; + } + + /** + * Returns the byte array form of the content of this attribute. The 6 header bytes + * (attribute_name_index and attribute_length) must not be added in the returned + * ByteVector. + * + * @param classWriter the class to which this attribute must be added. This parameter can be used + * to add the items that corresponds to this attribute to the constant pool of this class. + * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null} + * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to this code + * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length' + * field of the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or + * -1 if this attribute is not a Code attribute. + * @param maxLocals the maximum number of local variables of the method corresponding to this code + * attribute, or -1 if this attribute is not a Code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write( + final ClassWriter classWriter, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + return new ByteVector(content); + } + + /** + * Returns the number of attributes of the attribute list that begins with this attribute. + * + * @return the number of attributes of the attribute list that begins with this attribute. + */ + final int getAttributeCount() { + int count = 0; + Attribute attribute = this; + while (attribute != null) { + count += 1; + attribute = attribute.nextAttribute; + } + return count; + } + + /** + * Returns the total size in bytes of all the attributes in the attribute list that begins with + * this attribute. This size includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. Also adds the attribute type names to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @return the size of all the attributes in this attribute list. This size includes the size of + * the attribute headers. + */ + final int computeAttributesSize(final SymbolTable symbolTable) { + final byte[] code = null; + final int codeLength = 0; + final int maxStack = -1; + final int maxLocals = -1; + return computeAttributesSize(symbolTable, code, codeLength, maxStack, maxLocals); + } + + /** + * Returns the total size in bytes of all the attributes in the attribute list that begins with + * this attribute. This size includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. Also adds the attribute type names to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param code the bytecode of the method corresponding to these Code attributes, or {@literal + * null} if they are not Code attributes. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to these code + * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of + * the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or + * -1 if they are not Code attributes. + * @param maxLocals the maximum number of local variables of the method corresponding to these + * Code attributes, or -1 if they are not Code attribute. + * @return the size of all the attributes in this attribute list. This size includes the size of + * the attribute headers. + */ + final int computeAttributesSize( + final SymbolTable symbolTable, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + final ClassWriter classWriter = symbolTable.classWriter; + int size = 0; + Attribute attribute = this; + while (attribute != null) { + symbolTable.addConstantUtf8(attribute.type); + size += 6 + attribute.write(classWriter, code, codeLength, maxStack, maxLocals).length; + attribute = attribute.nextAttribute; + } + return size; + } + + /** + * Returns the total size in bytes of all the attributes that correspond to the given field, + * method or class access flags and signature. This size includes the 6 header bytes + * (attribute_name_index and attribute_length) per attribute. Also adds the attribute type names + * to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param accessFlags some field, method or class access flags. + * @param signatureIndex the constant pool index of a field, method of class signature. + * @return the size of all the attributes in bytes. This size includes the size of the attribute + * headers. + */ + static int computeAttributesSize( + final SymbolTable symbolTable, final int accessFlags, final int signatureIndex) { + int size = 0; + // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 + && symbolTable.getMajorVersion() < Opcodes.V1_5) { + // Synthetic attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + size += 6; + } + if (signatureIndex != 0) { + // Signature attributes always use 8 bytes. + symbolTable.addConstantUtf8(Constants.SIGNATURE); + size += 8; + } + // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead. + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + // Deprecated attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.DEPRECATED); + size += 6; + } + return size; + } + + /** + * Puts all the attributes of the attribute list that begins with this attribute, in the given + * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per + * attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param output where the attributes must be written. + */ + final void putAttributes(final SymbolTable symbolTable, final ByteVector output) { + final byte[] code = null; + final int codeLength = 0; + final int maxStack = -1; + final int maxLocals = -1; + putAttributes(symbolTable, code, codeLength, maxStack, maxLocals, output); + } + + /** + * Puts all the attributes of the attribute list that begins with this attribute, in the given + * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per + * attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param code the bytecode of the method corresponding to these Code attributes, or {@literal + * null} if they are not Code attributes. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to these code + * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of + * the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or + * -1 if they are not Code attributes. + * @param maxLocals the maximum number of local variables of the method corresponding to these + * Code attributes, or -1 if they are not Code attribute. + * @param output where the attributes must be written. + */ + final void putAttributes( + final SymbolTable symbolTable, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals, + final ByteVector output) { + final ClassWriter classWriter = symbolTable.classWriter; + Attribute attribute = this; + while (attribute != null) { + ByteVector attributeContent = + attribute.write(classWriter, code, codeLength, maxStack, maxLocals); + // Put attribute_name_index and attribute_length. + output.putShort(symbolTable.addConstantUtf8(attribute.type)).putInt(attributeContent.length); + output.putByteArray(attributeContent.data, 0, attributeContent.length); + attribute = attribute.nextAttribute; + } + } + + /** + * Puts all the attributes that correspond to the given field, method or class access flags and + * signature, in the given byte vector. This includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param accessFlags some field, method or class access flags. + * @param signatureIndex the constant pool index of a field, method of class signature. + * @param output where the attributes must be written. + */ + static void putAttributes( + final SymbolTable symbolTable, + final int accessFlags, + final int signatureIndex, + final ByteVector output) { + // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 + && symbolTable.getMajorVersion() < Opcodes.V1_5) { + output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + } + + /** A set of attribute prototypes (attributes with the same type are considered equal). */ + static final class Set { + + private static final int SIZE_INCREMENT = 6; + + private int size; + private Attribute[] data = new Attribute[SIZE_INCREMENT]; + + void addAttributes(final Attribute attributeList) { + Attribute attribute = attributeList; + while (attribute != null) { + if (!contains(attribute)) { + add(attribute); + } + attribute = attribute.nextAttribute; + } + } + + Attribute[] toArray() { + Attribute[] result = new Attribute[size]; + System.arraycopy(data, 0, result, 0, size); + return result; + } + + private boolean contains(final Attribute attribute) { + for (int i = 0; i < size; ++i) { + if (data[i].type.equals(attribute.type)) { + return true; + } + } + return false; + } + + private void add(final Attribute attribute) { + if (size >= data.length) { + Attribute[] newData = new Attribute[data.length + SIZE_INCREMENT]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + data[size++] = attribute; + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ByteVector.java b/native/java/jpype.jvm.asm/org/jpype/asm/ByteVector.java new file mode 100644 index 000000000..d1d8eecad --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ByteVector.java @@ -0,0 +1,361 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A dynamically extensible vector of bytes. This class is roughly equivalent to a DataOutputStream + * on top of a ByteArrayOutputStream, but is more efficient. + * + * @author Eric Bruneton + */ +public class ByteVector { + + /** The content of this vector. Only the first {@link #length} bytes contain real data. */ + byte[] data; + + /** The actual number of bytes in this vector. */ + int length; + + /** Constructs a new {@link ByteVector} with a default initial capacity. */ + public ByteVector() { + data = new byte[64]; + } + + /** + * Constructs a new {@link ByteVector} with the given initial capacity. + * + * @param initialCapacity the initial capacity of the byte vector to be constructed. + */ + public ByteVector(final int initialCapacity) { + data = new byte[initialCapacity]; + } + + /** + * Constructs a new {@link ByteVector} from the given initial data. + * + * @param data the initial data of the new byte vector. + */ + ByteVector(final byte[] data) { + this.data = data; + this.length = data.length; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param byteValue a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int byteValue) { + int currentLength = length; + if (currentLength + 1 > data.length) { + enlarge(1); + } + data[currentLength++] = (byte) byteValue; + length = currentLength; + return this; + } + + /** + * Puts two bytes into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param byteValue1 a byte. + * @param byteValue2 another byte. + * @return this byte vector. + */ + final ByteVector put11(final int byteValue1, final int byteValue2) { + int currentLength = length; + if (currentLength + 2 > data.length) { + enlarge(2); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue1; + currentData[currentLength++] = (byte) byteValue2; + length = currentLength; + return this; + } + + /** + * Puts a short into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param shortValue a short. + * @return this byte vector. + */ + public ByteVector putShort(final int shortValue) { + int currentLength = length; + if (currentLength + 2 > data.length) { + enlarge(2); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } + + /** + * Puts a byte and a short into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteValue a byte. + * @param shortValue a short. + * @return this byte vector. + */ + final ByteVector put12(final int byteValue, final int shortValue) { + int currentLength = length; + if (currentLength + 3 > data.length) { + enlarge(3); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } + + /** + * Puts two bytes and a short into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteValue1 a byte. + * @param byteValue2 another byte. + * @param shortValue a short. + * @return this byte vector. + */ + final ByteVector put112(final int byteValue1, final int byteValue2, final int shortValue) { + int currentLength = length; + if (currentLength + 4 > data.length) { + enlarge(4); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue1; + currentData[currentLength++] = (byte) byteValue2; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } + + /** + * Puts an int into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param intValue an int. + * @return this byte vector. + */ + public ByteVector putInt(final int intValue) { + int currentLength = length; + if (currentLength + 4 > data.length) { + enlarge(4); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + length = currentLength; + return this; + } + + /** + * Puts one byte and two shorts into this byte vector. The byte vector is automatically enlarged + * if necessary. + * + * @param byteValue a byte. + * @param shortValue1 a short. + * @param shortValue2 another short. + * @return this byte vector. + */ + final ByteVector put122(final int byteValue, final int shortValue1, final int shortValue2) { + int currentLength = length; + if (currentLength + 5 > data.length) { + enlarge(5); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue; + currentData[currentLength++] = (byte) (shortValue1 >>> 8); + currentData[currentLength++] = (byte) shortValue1; + currentData[currentLength++] = (byte) (shortValue2 >>> 8); + currentData[currentLength++] = (byte) shortValue2; + length = currentLength; + return this; + } + + /** + * Puts a long into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param longValue a long. + * @return this byte vector. + */ + public ByteVector putLong(final long longValue) { + int currentLength = length; + if (currentLength + 8 > data.length) { + enlarge(8); + } + byte[] currentData = data; + int intValue = (int) (longValue >>> 32); + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + intValue = (int) longValue; + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + length = currentLength; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param stringValue a String whose UTF8 encoded length must be less than 65536. + * @return this byte vector. + */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). + public ByteVector putUTF8(final String stringValue) { + int charLength = stringValue.length(); + if (charLength > 65535) { + throw new IllegalArgumentException("UTF8 string too large"); + } + int currentLength = length; + if (currentLength + 2 + charLength > data.length) { + enlarge(2 + charLength); + } + byte[] currentData = data; + // Optimistic algorithm: instead of computing the byte length and then serializing the string + // (which requires two loops), we assume the byte length is equal to char length (which is the + // most frequent case), and we start serializing the string right away. During the + // serialization, if we find that this assumption is wrong, we continue with the general method. + currentData[currentLength++] = (byte) (charLength >>> 8); + currentData[currentLength++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= '\u0001' && charValue <= '\u007F') { + currentData[currentLength++] = (byte) charValue; + } else { + length = currentLength; + return encodeUtf8(stringValue, i, 65535); + } + } + length = currentLength; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if + * necessary. The string length is encoded in two bytes before the encoded characters, if there is + * space for that (i.e. if this.length - offset - 2 >= 0). + * + * @param stringValue the String to encode. + * @param offset the index of the first character to encode. The previous characters are supposed + * to have already been encoded, using only one byte per character. + * @param maxByteLength the maximum byte length of the encoded string, including the already + * encoded characters. + * @return this byte vector. + */ + final ByteVector encodeUtf8(final String stringValue, final int offset, final int maxByteLength) { + int charLength = stringValue.length(); + int byteLength = offset; + for (int i = offset; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= 0x0001 && charValue <= 0x007F) { + byteLength++; + } else if (charValue <= 0x07FF) { + byteLength += 2; + } else { + byteLength += 3; + } + } + if (byteLength > maxByteLength) { + throw new IllegalArgumentException("UTF8 string too large"); + } + // Compute where 'byteLength' must be stored in 'data', and store it at this location. + int byteLengthOffset = length - offset - 2; + if (byteLengthOffset >= 0) { + data[byteLengthOffset] = (byte) (byteLength >>> 8); + data[byteLengthOffset + 1] = (byte) byteLength; + } + if (length + byteLength - offset > data.length) { + enlarge(byteLength - offset); + } + int currentLength = length; + for (int i = offset; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= 0x0001 && charValue <= 0x007F) { + data[currentLength++] = (byte) charValue; + } else if (charValue <= 0x07FF) { + data[currentLength++] = (byte) (0xC0 | charValue >> 6 & 0x1F); + data[currentLength++] = (byte) (0x80 | charValue & 0x3F); + } else { + data[currentLength++] = (byte) (0xE0 | charValue >> 12 & 0xF); + data[currentLength++] = (byte) (0x80 | charValue >> 6 & 0x3F); + data[currentLength++] = (byte) (0x80 | charValue & 0x3F); + } + } + length = currentLength; + return this; + } + + /** + * Puts an array of bytes into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteArrayValue an array of bytes. May be {@literal null} to put {@code byteLength} null + * bytes into this byte vector. + * @param byteOffset index of the first byte of byteArrayValue that must be copied. + * @param byteLength number of bytes of byteArrayValue that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray( + final byte[] byteArrayValue, final int byteOffset, final int byteLength) { + if (length + byteLength > data.length) { + enlarge(byteLength); + } + if (byteArrayValue != null) { + System.arraycopy(byteArrayValue, byteOffset, data, length, byteLength); + } + length += byteLength; + return this; + } + + /** + * Enlarges this byte vector so that it can receive 'size' more bytes. + * + * @param size number of additional bytes that this byte vector should be able to receive. + */ + private void enlarge(final int size) { + int doubleCapacity = 2 * data.length; + int minimalCapacity = length + size; + byte[] newData = new byte[doubleCapacity > minimalCapacity ? doubleCapacity : minimalCapacity]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ClassReader.java b/native/java/jpype.jvm.asm/org/jpype/asm/ClassReader.java new file mode 100644 index 000000000..d3597836e --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ClassReader.java @@ -0,0 +1,3817 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java + * Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the + * appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode + * instruction encountered. + * + * @see JVMS 4 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed + * nor visited. + */ + public static final int SKIP_CODE = 1; + + /** + * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, + * LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set + * these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link + * MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link + * MethodVisitor#visitParameter} are not called). + */ + public static final int SKIP_DEBUG = 2; + + /** + * A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes + * are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag + * is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames + * that will be ignored and recomputed from scratch. + */ + public static final int SKIP_FRAMES = 4; + + /** + * A flag to expand the stack map frames. By default stack map frames are visited in their + * original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed" + * for the other classes). If this flag is set, stack map frames are always visited in expanded + * format (this option adds a decompression/compression step in ClassReader and ClassWriter which + * degrades performance quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode + * instructions. When resolving a forward jump it may happen that the signed 2 bytes offset + * reserved for it is not sufficient to store the bytecode offset. In this case the jump + * instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes + * offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing + * such instructions, in order to replace them with standard instructions. In addition, when this + * flag is used, goto_w and jsr_w are not converted into goto and jsr, to make sure that + * infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a + * goto_w in ClassWriter cannot occur. + */ + static final int EXPAND_ASM_INSNS = 256; + + /** The size of the temporary byte array used to read class input streams chunk by chunk. */ + private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096; + + /** + * A byte array containing the JVMS ClassFile structure to be parsed. + * + * @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will + * eventually be deleted. + */ + @Deprecated + // DontCheck(MemberName): can't be renamed (for backward binary compatibility). + public final byte[] b; + /** The offset in bytes of the ClassFile's access_flags field. */ + public final int header; + /** + * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array + * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally + * not needed by class visitors. + * + *

NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not + * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct + * ClassFile element offsets within this byte array. + */ + final byte[] classFileBuffer; + /** + * The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's + * constant_pool array, plus one. In other words, the offset of constant pool entry i is + * given by cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - + * 1]. + */ + private final int[] cpInfoOffsets; + /** + * The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids + * multiple parsing of a given CONSTANT_Utf8 constant pool item. + */ + private final String[] constantUtf8Values; + /** + * The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This + * cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item. + */ + private final ConstantDynamic[] constantDynamicValues; + /** + * The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array + * (in the BootstrapMethods attribute). + * + * @see JVMS + * 4.7.23 + */ + private final int[] bootstrapMethodOffsets; + /** + * A conservative estimate of the maximum length of the strings contained in the constant pool of + * the class. + */ + private final int maxStringLength; + + // ----------------------------------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFile the JVMS ClassFile structure to be read. + */ + public ClassReader(final byte[] classFile) { + this(classFile, 0, classFile.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param classFileLength the length in bytes of the ClassFile to be read. + */ + public ClassReader( + final byte[] classFileBuffer, + final int classFileOffset, + final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility. + this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true); + } + + /** + * Constructs a new {@link ClassReader} object. This internal constructor must not be exposed + * as a public API. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param checkClassVersion whether to check the class version or not. + */ + ClassReader( + final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) { + this.classFileBuffer = classFileBuffer; + this.b = classFileBuffer; + // Check the class' major_version. This field is after the magic and minor_version fields, which + // use 4 and 2 bytes respectively. + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V16) { + throw new IllegalArgumentException( + "Unsupported class file major version " + readShort(classFileOffset + 6)); + } + // Create the constant pool arrays. The constant_pool_count field is after the magic, + // minor_version and major_version fields, which use 4, 2 and 2 bytes respectively. + int constantPoolCount = readUnsignedShort(classFileOffset + 8); + cpInfoOffsets = new int[constantPoolCount]; + constantUtf8Values = new String[constantPoolCount]; + // Compute the offset of each constant pool entry, as well as a conservative estimate of the + // maximum length of the constant pool strings. The first constant pool entry is after the + // magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2 + // bytes respectively. + int currentCpInfoIndex = 1; + int currentCpInfoOffset = classFileOffset + 10; + int currentMaxStringLength = 0; + boolean hasBootstrapMethods = false; + boolean hasConstantDynamic = false; + // The offset of the other entries depend on the total size of all the previous entries. + while (currentCpInfoIndex < constantPoolCount) { + cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1; + int cpInfoSize; + switch (classFileBuffer[currentCpInfoOffset]) { + case Symbol.CONSTANT_FIELDREF_TAG: + case Symbol.CONSTANT_METHODREF_TAG: + case Symbol.CONSTANT_INTERFACE_METHODREF_TAG: + case Symbol.CONSTANT_INTEGER_TAG: + case Symbol.CONSTANT_FLOAT_TAG: + case Symbol.CONSTANT_NAME_AND_TYPE_TAG: + cpInfoSize = 5; + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + cpInfoSize = 5; + hasBootstrapMethods = true; + hasConstantDynamic = true; + break; + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + cpInfoSize = 5; + hasBootstrapMethods = true; + break; + case Symbol.CONSTANT_LONG_TAG: + case Symbol.CONSTANT_DOUBLE_TAG: + cpInfoSize = 9; + currentCpInfoIndex++; + break; + case Symbol.CONSTANT_UTF8_TAG: + cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1); + if (cpInfoSize > currentMaxStringLength) { + // The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate + // of the length in characters of the corresponding string, and is much cheaper to + // compute than this exact length. + currentMaxStringLength = cpInfoSize; + } + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + cpInfoSize = 4; + break; + case Symbol.CONSTANT_CLASS_TAG: + case Symbol.CONSTANT_STRING_TAG: + case Symbol.CONSTANT_METHOD_TYPE_TAG: + case Symbol.CONSTANT_PACKAGE_TAG: + case Symbol.CONSTANT_MODULE_TAG: + cpInfoSize = 3; + break; + default: + throw new IllegalArgumentException(); + } + currentCpInfoOffset += cpInfoSize; + } + maxStringLength = currentMaxStringLength; + // The Classfile's access_flags field is just after the last constant pool entry. + header = currentCpInfoOffset; + + // Allocate the cache of ConstantDynamic values, if there is at least one. + constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null; + + // Read the BootstrapMethods attribute, if any (only get the offset of each method). + bootstrapMethodOffsets = + hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param inputStream an input stream of the JVMS ClassFile structure to be read. This input + * stream must contain nothing more than the ClassFile structure itself. It is read from its + * current position to its end. + * @throws IOException if a problem occurs during reading. + */ + public ClassReader(final InputStream inputStream) throws IOException { + this(readStream(inputStream, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param className the fully qualified name of the class to be read. The ClassFile structure is + * retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}. + * @throws IOException if an exception occurs during reading. + */ + public ClassReader(final String className) throws IOException { + this( + readStream( + ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true)); + } + + /** + * Reads the given input stream and returns its content as a byte array. + * + * @param inputStream an input stream. + * @param close true to close the input stream after reading. + * @return the content of the given input stream. + * @throws IOException if a problem occurs during reading. + */ + private static byte[] readStream(final InputStream inputStream, final boolean close) + throws IOException { + if (inputStream == null) { + throw new IOException("Class not found"); + } + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE]; + int bytesRead; + while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { + outputStream.write(data, 0, bytesRead); + } + outputStream.flush(); + return outputStream.toByteArray(); + } finally { + if (close) { + inputStream.close(); + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated + * and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes. + * + * @return the class access flags. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see {@link Type#getInternalName()}). + * + * @return the internal class name. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + // this_class is just after the access_flags field (using 2 bytes). + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For + * interfaces, the super class is {@link Object}. + * + * @return the internal name of the super class, or {@literal null} for {@link Object} class. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + // super_class is after the access_flags and this_class fields (2 bytes each). + return readClass(header + 4, new char[maxStringLength]); + } + + /** + * Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}). + * + * @return the internal names of the directly implemented interfaces. Inherited implemented + * interfaces are not returned. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + // interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each). + int currentOffset = header + 6; + int interfacesCount = readUnsignedShort(currentOffset); + String[] interfaces = new String[interfacesCount]; + if (interfacesCount > 0) { + char[] charBuffer = new char[maxStringLength]; + for (int i = 0; i < interfacesCount; ++i) { + currentOffset += 2; + interfaces[i] = readClass(currentOffset, charBuffer); + } + } + return interfaces; + } + + // ----------------------------------------------------------------------------------------------- + // Public methods + // ----------------------------------------------------------------------------------------------- + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept(final ClassVisitor classVisitor, final int parsingOptions) { + accept(classVisitor, new Attribute[0], parsingOptions); + } + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. This may + * corrupt it if this value contains references to the constant pool, or has syntactic or + * semantic links with a class element that has been transformed by a class adapter between + * the reader and the writer. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept( + final ClassVisitor classVisitor, + final Attribute[] attributePrototypes, + final int parsingOptions) { + Context context = new Context(); + context.attributePrototypes = attributePrototypes; + context.parsingOptions = parsingOptions; + context.charBuffer = new char[maxStringLength]; + + // Read the access_flags, this_class, super_class, interface_count and interfaces fields. + char[] charBuffer = context.charBuffer; + int currentOffset = header; + int accessFlags = readUnsignedShort(currentOffset); + String thisClass = readClass(currentOffset + 2, charBuffer); + String superClass = readClass(currentOffset + 4, charBuffer); + String[] interfaces = new String[readUnsignedShort(currentOffset + 6)]; + currentOffset += 8; + for (int i = 0; i < interfaces.length; ++i) { + interfaces[i] = readClass(currentOffset, charBuffer); + currentOffset += 2; + } + + // Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the InnerClasses attribute, or 0. + int innerClassesOffset = 0; + // - The offset of the EnclosingMethod attribute, or 0. + int enclosingMethodOffset = 0; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The string corresponding to the SourceFile attribute, or null. + String sourceFile = null; + // - The string corresponding to the SourceDebugExtension attribute, or null. + String sourceDebugExtension = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the Module attribute, or 0. + int moduleOffset = 0; + // - The offset of the ModulePackages attribute, or 0. + int modulePackagesOffset = 0; + // - The string corresponding to the ModuleMainClass attribute, or null. + String moduleMainClass = null; + // - The string corresponding to the NestHost attribute, or null. + String nestHostClass = null; + // - The offset of the NestMembers attribute, or 0. + int nestMembersOffset = 0; + // - The offset of the PermittedSubclasses attribute, or 0 + int permittedSubclassesOffset = 0; + // - The offset of the Record attribute, or 0. + int recordOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int currentAttributeOffset = getFirstAttributeOffset(); + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SOURCE_FILE.equals(attributeName)) { + sourceFile = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.INNER_CLASSES.equals(attributeName)) { + innerClassesOffset = currentAttributeOffset; + } else if (Constants.ENCLOSING_METHOD.equals(attributeName)) { + enclosingMethodOffset = currentAttributeOffset; + } else if (Constants.NEST_HOST.equals(attributeName)) { + nestHostClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.NEST_MEMBERS.equals(attributeName)) { + nestMembersOffset = currentAttributeOffset; + } else if (Constants.PERMITTED_SUBCLASSES.equals(attributeName)) { + permittedSubclassesOffset = currentAttributeOffset; + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) { + sourceDebugExtension = + readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]); + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RECORD.equals(attributeName)) { + recordOffset = currentAttributeOffset; + accessFlags |= Opcodes.ACC_RECORD; + } else if (Constants.MODULE.equals(attributeName)) { + moduleOffset = currentAttributeOffset; + } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) { + moduleMainClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.MODULE_PACKAGES.equals(attributeName)) { + modulePackagesOffset = currentAttributeOffset; + } else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // The BootstrapMethods attribute is read in the constructor. + Attribute attribute = + readAttribute( + attributePrototypes, + attributeName, + currentAttributeOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentAttributeOffset += attributeLength; + } + + // Visit the class declaration. The minor_version and major_version fields start 6 bytes before + // the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition). + classVisitor.visit( + readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces); + + // Visit the SourceFile and SourceDebugExtenstion attributes. + if ((parsingOptions & SKIP_DEBUG) == 0 + && (sourceFile != null || sourceDebugExtension != null)) { + classVisitor.visitSource(sourceFile, sourceDebugExtension); + } + + // Visit the Module, ModulePackages and ModuleMainClass attributes. + if (moduleOffset != 0) { + readModuleAttributes( + classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass); + } + + // Visit the NestHost attribute. + if (nestHostClass != null) { + classVisitor.visitNestHost(nestHostClass); + } + + // Visit the EnclosingMethod attribute. + if (enclosingMethodOffset != 0) { + String className = readClass(enclosingMethodOffset, charBuffer); + int methodIndex = readUnsignedShort(enclosingMethodOffset + 2); + String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer); + String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer); + classVisitor.visitOuterClass(className, name, type); + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in ClassWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + classVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the NestedMembers attribute. + if (nestMembersOffset != 0) { + int numberOfNestMembers = readUnsignedShort(nestMembersOffset); + int currentNestMemberOffset = nestMembersOffset + 2; + while (numberOfNestMembers-- > 0) { + classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer)); + currentNestMemberOffset += 2; + } + } + + // Visit the PermittedSubclasses attribute. + if (permittedSubclassesOffset != 0) { + int numberOfPermittedSubclasses = readUnsignedShort(permittedSubclassesOffset); + int currentPermittedSubclassesOffset = permittedSubclassesOffset + 2; + while (numberOfPermittedSubclasses-- > 0) { + classVisitor.visitPermittedSubclass( + readClass(currentPermittedSubclassesOffset, charBuffer)); + currentPermittedSubclassesOffset += 2; + } + } + + // Visit the InnerClasses attribute. + if (innerClassesOffset != 0) { + int numberOfClasses = readUnsignedShort(innerClassesOffset); + int currentClassesOffset = innerClassesOffset + 2; + while (numberOfClasses-- > 0) { + classVisitor.visitInnerClass( + readClass(currentClassesOffset, charBuffer), + readClass(currentClassesOffset + 2, charBuffer), + readUTF8(currentClassesOffset + 4, charBuffer), + readUnsignedShort(currentClassesOffset + 6)); + currentClassesOffset += 8; + } + } + + // Visit Record components. + if (recordOffset != 0) { + int recordComponentsCount = readUnsignedShort(recordOffset); + recordOffset += 2; + while (recordComponentsCount-- > 0) { + recordOffset = readRecordComponent(classVisitor, context, recordOffset); + } + } + + // Visit the fields and methods. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (fieldsCount-- > 0) { + currentOffset = readField(classVisitor, context, currentOffset); + } + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + currentOffset = readMethod(classVisitor, context, currentOffset); + } + + // Visit the end of the class. + classVisitor.visitEnd(); + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse modules, fields and methods + // ---------------------------------------------------------------------------------------------- + + /** + * Reads the Module, ModulePackages and ModuleMainClass attributes and visit them. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param moduleOffset the offset of the Module attribute (excluding the attribute_info's + * attribute_name_index and attribute_length fields). + * @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the + * attribute_info's attribute_name_index and attribute_length fields), or 0. + * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or {@literal + * null}. + */ + private void readModuleAttributes( + final ClassVisitor classVisitor, + final Context context, + final int moduleOffset, + final int modulePackagesOffset, + final String moduleMainClass) { + char[] buffer = context.charBuffer; + + // Read the module_name_index, module_flags and module_version_index fields and visit them. + int currentOffset = moduleOffset; + String moduleName = readModule(currentOffset, buffer); + int moduleFlags = readUnsignedShort(currentOffset + 2); + String moduleVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + ModuleVisitor moduleVisitor = classVisitor.visitModule(moduleName, moduleFlags, moduleVersion); + if (moduleVisitor == null) { + return; + } + + // Visit the ModuleMainClass attribute. + if (moduleMainClass != null) { + moduleVisitor.visitMainClass(moduleMainClass); + } + + // Visit the ModulePackages attribute. + if (modulePackagesOffset != 0) { + int packageCount = readUnsignedShort(modulePackagesOffset); + int currentPackageOffset = modulePackagesOffset + 2; + while (packageCount-- > 0) { + moduleVisitor.visitPackage(readPackage(currentPackageOffset, buffer)); + currentPackageOffset += 2; + } + } + + // Read the 'requires_count' and 'requires' fields. + int requiresCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (requiresCount-- > 0) { + // Read the requires_index, requires_flags and requires_version fields and visit them. + String requires = readModule(currentOffset, buffer); + int requiresFlags = readUnsignedShort(currentOffset + 2); + String requiresVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + moduleVisitor.visitRequire(requires, requiresFlags, requiresVersion); + } + + // Read the 'exports_count' and 'exports' fields. + int exportsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exportsCount-- > 0) { + // Read the exports_index, exports_flags, exports_to_count and exports_to_index fields + // and visit them. + String exports = readPackage(currentOffset, buffer); + int exportsFlags = readUnsignedShort(currentOffset + 2); + int exportsToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] exportsTo = null; + if (exportsToCount != 0) { + exportsTo = new String[exportsToCount]; + for (int i = 0; i < exportsToCount; ++i) { + exportsTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; + } + } + moduleVisitor.visitExport(exports, exportsFlags, exportsTo); + } + + // Reads the 'opens_count' and 'opens' fields. + int opensCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (opensCount-- > 0) { + // Read the opens_index, opens_flags, opens_to_count and opens_to_index fields and visit them. + String opens = readPackage(currentOffset, buffer); + int opensFlags = readUnsignedShort(currentOffset + 2); + int opensToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] opensTo = null; + if (opensToCount != 0) { + opensTo = new String[opensToCount]; + for (int i = 0; i < opensToCount; ++i) { + opensTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; + } + } + moduleVisitor.visitOpen(opens, opensFlags, opensTo); + } + + // Read the 'uses_count' and 'uses' fields. + int usesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (usesCount-- > 0) { + moduleVisitor.visitUse(readClass(currentOffset, buffer)); + currentOffset += 2; + } + + // Read the 'provides_count' and 'provides' fields. + int providesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (providesCount-- > 0) { + // Read the provides_index, provides_with_count and provides_with_index fields and visit them. + String provides = readClass(currentOffset, buffer); + int providesWithCount = readUnsignedShort(currentOffset + 2); + currentOffset += 4; + String[] providesWith = new String[providesWithCount]; + for (int i = 0; i < providesWithCount; ++i) { + providesWith[i] = readClass(currentOffset, buffer); + currentOffset += 2; + } + moduleVisitor.visitProvide(provides, providesWith); + } + + // Visit the end of the module attributes. + moduleVisitor.visitEnd(); + } + + /** + * Reads a record component and visit it. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param recordComponentOffset the offset of the current record component. + * @return the offset of the first byte following the record component. + */ + private int readRecordComponent( + final ClassVisitor classVisitor, final Context context, final int recordComponentOffset) { + char[] charBuffer = context.charBuffer; + + int currentOffset = recordComponentOffset; + String name = readUTF8(currentOffset, charBuffer); + String descriptor = readUTF8(currentOffset + 2, charBuffer); + currentOffset += 4; + + // Read the record component attributes (the variables are ordered as in Section 4.7 of the + // JVMS). + + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + RecordComponentVisitor recordComponentVisitor = + classVisitor.visitRecordComponent(name, descriptor, signature); + if (recordComponentVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + recordComponentVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + recordComponentVisitor.visitEnd(); + return currentOffset; + } + + /** + * Reads a JVMS field_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the field. + * @param context information about the class being parsed. + * @param fieldInfoOffset the start offset of the field_info structure. + * @return the offset of the first byte following the field_info structure. + */ + private int readField( + final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = fieldInfoOffset; + int accessFlags = readUnsignedShort(currentOffset); + String name = readUTF8(currentOffset + 2, charBuffer); + String descriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the field attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The value corresponding to the ConstantValue attribute, or null. + Object constantValue = null; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CONSTANT_VALUE.equals(attributeName)) { + int constantvalueIndex = readUnsignedShort(currentOffset); + constantValue = constantvalueIndex == 0 ? null : readConst(constantvalueIndex, charBuffer); + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Visit the field declaration. + FieldVisitor fieldVisitor = + classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue); + if (fieldVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + fieldVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + fieldVisitor.visitEnd(); + return currentOffset; + } + + /** + * Reads a JVMS method_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the method. + * @param context information about the class being parsed. + * @param methodInfoOffset the start offset of the method_info structure. + * @return the offset of the first byte following the method_info structure. + */ + private int readMethod( + final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = methodInfoOffset; + context.currentMethodAccessFlags = readUnsignedShort(currentOffset); + context.currentMethodName = readUTF8(currentOffset + 2, charBuffer); + context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the method attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the Code attribute, or 0. + int codeOffset = 0; + // - The offset of the Exceptions attribute, or 0. + int exceptionsOffset = 0; + // - The strings corresponding to the Exceptions attribute, or null. + String[] exceptions = null; + // - Whether the method has a Synthetic attribute. + boolean synthetic = false; + // - The constant pool index contained in the Signature attribute, or 0. + int signatureIndex = 0; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0. + int runtimeVisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0. + int runtimeInvisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the AnnotationDefault attribute, or 0. + int annotationDefaultOffset = 0; + // - The offset of the MethodParameters attribute, or 0. + int methodParametersOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CODE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_CODE) == 0) { + codeOffset = currentOffset; + } + } else if (Constants.EXCEPTIONS.equals(attributeName)) { + exceptionsOffset = currentOffset; + exceptions = new String[readUnsignedShort(exceptionsOffset)]; + int currentExceptionOffset = exceptionsOffset + 2; + for (int i = 0; i < exceptions.length; ++i) { + exceptions[i] = readClass(currentExceptionOffset, charBuffer); + currentExceptionOffset += 2; + } + } else if (Constants.SIGNATURE.equals(attributeName)) { + signatureIndex = readUnsignedShort(currentOffset); + } else if (Constants.DEPRECATED.equals(attributeName)) { + context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) { + annotationDefaultOffset = currentOffset; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + synthetic = true; + context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.METHOD_PARAMETERS.equals(attributeName)) { + methodParametersOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Visit the method declaration. + MethodVisitor methodVisitor = + classVisitor.visitMethod( + context.currentMethodAccessFlags, + context.currentMethodName, + context.currentMethodDescriptor, + signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer), + exceptions); + if (methodVisitor == null) { + return currentOffset; + } + + // If the returned MethodVisitor is in fact a MethodWriter, it means there is no method + // adapter between the reader and the writer. In this case, it might be possible to copy + // the method attributes directly into the writer. If so, return early without visiting + // the content of these attributes. + if (methodVisitor instanceof MethodWriter) { + MethodWriter methodWriter = (MethodWriter) methodVisitor; + if (methodWriter.canCopyMethodAttributes( + this, + synthetic, + (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0, + readUnsignedShort(methodInfoOffset + 4), + signatureIndex, + exceptionsOffset)) { + methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset); + return currentOffset; + } + } + + // Visit the MethodParameters attribute. + if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { + int parametersCount = readByte(methodParametersOffset); + int currentParameterOffset = methodParametersOffset + 1; + while (parametersCount-- > 0) { + // Read the name_index and access_flags fields and visit them. + methodVisitor.visitParameter( + readUTF8(currentParameterOffset, charBuffer), + readUnsignedShort(currentParameterOffset + 2)); + currentParameterOffset += 4; + } + } + + // Visit the AnnotationDefault attribute. + if (annotationDefaultOffset != 0) { + AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault(); + readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer); + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleParameterAnnotations attribute. + if (runtimeVisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true); + } + + // Visit the RuntimeInvisibleParameterAnnotations attribute. + if (runtimeInvisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, + context, + runtimeInvisibleParameterAnnotationsOffset, + /* visible = */ false); + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the Code attribute. + if (codeOffset != 0) { + methodVisitor.visitCode(); + readCode(methodVisitor, context, codeOffset); + } + + // Visit the end of the method. + methodVisitor.visitEnd(); + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse a Code attribute + // ---------------------------------------------------------------------------------------------- + + /** + * Reads a JVMS 'Code' attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the Code attribute. + * @param context information about the class being parsed. + * @param codeOffset the start offset in {@link #classFileBuffer} of the Code attribute, excluding + * its attribute_name_index and attribute_length fields. + */ + private void readCode( + final MethodVisitor methodVisitor, final Context context, final int codeOffset) { + int currentOffset = codeOffset; + + // Read the max_stack, max_locals and code_length fields. + final byte[] classBuffer = classFileBuffer; + final char[] charBuffer = context.charBuffer; + final int maxStack = readUnsignedShort(currentOffset); + final int maxLocals = readUnsignedShort(currentOffset + 2); + final int codeLength = readInt(currentOffset + 4); + currentOffset += 8; + + // Read the bytecode 'code' array to create a label for each referenced instruction. + final int bytecodeStartOffset = currentOffset; + final int bytecodeEndOffset = currentOffset + codeLength; + final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1]; + while (currentOffset < bytecodeEndOffset) { + final int bytecodeOffset = currentOffset - bytecodeStartOffset; + final int opcode = classBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + currentOffset += 1; + break; + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + createLabel(bytecodeOffset + readShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + createLabel(bytecodeOffset + readUnsignedShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + case Constants.ASM_GOTO_W: + createLabel(bytecodeOffset + readInt(currentOffset + 1), labels); + currentOffset += 5; + break; + case Constants.WIDE: + switch (classBuffer[currentOffset + 1] & 0xFF) { + case Opcodes.ILOAD: + case Opcodes.FLOAD: + case Opcodes.ALOAD: + case Opcodes.LLOAD: + case Opcodes.DLOAD: + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + case Opcodes.LSTORE: + case Opcodes.DSTORE: + case Opcodes.RET: + currentOffset += 4; + break; + case Opcodes.IINC: + currentOffset += 6; + break; + default: + throw new IllegalArgumentException(); + } + break; + case Opcodes.TABLESWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of table entries. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numTableEntries = readInt(currentOffset + 8) - readInt(currentOffset + 4) + 1; + currentOffset += 12; + // Read the table labels. + while (numTableEntries-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset), labels); + currentOffset += 4; + } + break; + case Opcodes.LOOKUPSWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of switch cases. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numSwitchCases = readInt(currentOffset + 4); + currentOffset += 8; + // Read the switch labels. + while (numSwitchCases-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset + 4), labels); + currentOffset += 8; + } + break; + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: + case Opcodes.LDC: + currentOffset += 2; + break; + case Opcodes.SIPUSH: + case Constants.LDC_W: + case Constants.LDC2_W: + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: + case Opcodes.IINC: + currentOffset += 3; + break; + case Opcodes.INVOKEINTERFACE: + case Opcodes.INVOKEDYNAMIC: + currentOffset += 5; + break; + case Opcodes.MULTIANEWARRAY: + currentOffset += 4; + break; + default: + throw new IllegalArgumentException(); + } + } + + // Read the 'exception_table_length' and 'exception_table' field to create a label for each + // referenced instruction, and to make methodVisitor visit the corresponding try catch blocks. + int exceptionTableLength = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exceptionTableLength-- > 0) { + Label start = createLabel(readUnsignedShort(currentOffset), labels); + Label end = createLabel(readUnsignedShort(currentOffset + 2), labels); + Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels); + String catchType = readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer); + currentOffset += 8; + methodVisitor.visitTryCatchBlock(start, end, handler, catchType); + } + + // Read the Code attributes to create a label for each referenced instruction (the variables + // are ordered as in Section 4.7 of the JVMS). Attribute offsets exclude the + // attribute_name_index and attribute_length fields. + // - The offset of the current 'stack_map_frame' in the StackMap[Table] attribute, or 0. + // Initially, this is the offset of the first 'stack_map_frame' entry. Then this offset is + // updated after each stack_map_frame is read. + int stackMapFrameOffset = 0; + // - The end offset of the StackMap[Table] attribute, or 0. + int stackMapTableEndOffset = 0; + // - Whether the stack map frames are compressed (i.e. in a StackMapTable) or not. + boolean compressedFrames = true; + // - The offset of the LocalVariableTable attribute, or 0. + int localVariableTableOffset = 0; + // - The offset of the LocalVariableTypeTable attribute, or 0. + int localVariableTypeTableOffset = 0; + // - The offset of each 'type_annotation' entry in the RuntimeVisibleTypeAnnotations + // attribute, or null. + int[] visibleTypeAnnotationOffsets = null; + // - The offset of each 'type_annotation' entry in the RuntimeInvisibleTypeAnnotations + // attribute, or null. + int[] invisibleTypeAnnotationOffsets = null; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + if (Constants.LOCAL_VARIABLE_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + localVariableTableOffset = currentOffset; + // Parse the attribute to find the corresponding (debug only) labels. + int currentLocalVariableTableOffset = currentOffset; + int localVariableTableLength = readUnsignedShort(currentLocalVariableTableOffset); + currentLocalVariableTableOffset += 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentLocalVariableTableOffset); + createDebugLabel(startPc, labels); + int length = readUnsignedShort(currentLocalVariableTableOffset + 2); + createDebugLabel(startPc + length, labels); + // Skip the name_index, descriptor_index and index fields (2 bytes each). + currentLocalVariableTableOffset += 10; + } + } + } else if (Constants.LOCAL_VARIABLE_TYPE_TABLE.equals(attributeName)) { + localVariableTypeTableOffset = currentOffset; + // Here we do not extract the labels corresponding to the attribute content. We assume they + // are the same or a subset of those of the LocalVariableTable attribute. + } else if (Constants.LINE_NUMBER_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + // Parse the attribute to find the corresponding (debug only) labels. + int currentLineNumberTableOffset = currentOffset; + int lineNumberTableLength = readUnsignedShort(currentLineNumberTableOffset); + currentLineNumberTableOffset += 2; + while (lineNumberTableLength-- > 0) { + int startPc = readUnsignedShort(currentLineNumberTableOffset); + int lineNumber = readUnsignedShort(currentLineNumberTableOffset + 2); + currentLineNumberTableOffset += 4; + createDebugLabel(startPc, labels); + labels[startPc].addLineNumber(lineNumber); + } + } + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + visibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true); + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // type annotation at a time (i.e. after a type annotation has been visited, the next type + // annotation is read), and the labels it contains are also extracted one annotation at a + // time. This assumes that type annotations are ordered by increasing bytecode offset. + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + invisibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false); + // Same comment as above for the RuntimeVisibleTypeAnnotations attribute. + } else if (Constants.STACK_MAP_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; + } + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // frame at a time (i.e. after a frame has been visited, the next frame is read), and the + // labels it contains are also extracted one frame at a time. Thanks to the ordering of + // frames, having only a "one frame lookahead" is not a problem, i.e. it is not possible to + // see an offset smaller than the offset of the current instruction and for which no Label + // exist. Except for UNINITIALIZED type offsets. We solve this by parsing the stack map + // table without a full decoding (see below). + } else if ("StackMap".equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; + compressedFrames = false; + } + // IMPORTANT! Here we assume that the frames are ordered, as in the StackMapTable attribute, + // although this is not guaranteed by the attribute format. This allows an incremental + // extraction of the labels corresponding to this attribute (see the comment above for the + // StackMapTable attribute). + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + codeOffset, + labels); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Initialize the context fields related to stack map frames, and generate the first + // (implicit) stack map frame, if needed. + final boolean expandFrames = (context.parsingOptions & EXPAND_FRAMES) != 0; + if (stackMapFrameOffset != 0) { + // The bytecode offset of the first explicit frame is not offset_delta + 1 but only + // offset_delta. Setting the implicit frame offset to -1 allows us to use of the + // "offset_delta + 1" rule in all cases. + context.currentFrameOffset = -1; + context.currentFrameType = 0; + context.currentFrameLocalCount = 0; + context.currentFrameLocalCountDelta = 0; + context.currentFrameLocalTypes = new Object[maxLocals]; + context.currentFrameStackCount = 0; + context.currentFrameStackTypes = new Object[maxStack]; + if (expandFrames) { + computeImplicitFrame(context); + } + // Find the labels for UNINITIALIZED frame types. Instead of decoding each element of the + // stack map table, we look for 3 consecutive bytes that "look like" an UNINITIALIZED type + // (tag ITEM_Uninitialized, offset within bytecode bounds, NEW instruction at this offset). + // We may find false positives (i.e. not real UNINITIALIZED types), but this should be rare, + // and the only consequence will be the creation of an unneeded label. This is better than + // creating a label for each NEW instruction, and faster than fully decoding the whole stack + // map table. + for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) { + if (classBuffer[offset] == Frame.ITEM_UNINITIALIZED) { + int potentialBytecodeOffset = readUnsignedShort(offset + 1); + if (potentialBytecodeOffset >= 0 + && potentialBytecodeOffset < codeLength + && (classBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF) + == Opcodes.NEW) { + createLabel(potentialBytecodeOffset, labels); + } + } + } + } + if (expandFrames && (context.parsingOptions & EXPAND_ASM_INSNS) != 0) { + // Expanding the ASM specific instructions can introduce F_INSERT frames, even if the method + // does not currently have any frame. These inserted frames must be computed by simulating the + // effect of the bytecode instructions, one by one, starting from the implicit first frame. + // For this, MethodWriter needs to know maxLocals before the first instruction is visited. To + // ensure this, we visit the implicit first frame here (passing only maxLocals - the rest is + // computed in MethodWriter). + methodVisitor.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null); + } + + // Visit the bytecode instructions. First, introduce state variables for the incremental parsing + // of the type annotations. + + // Index of the next runtime visible type annotation to read (in the + // visibleTypeAnnotationOffsets array). + int currentVisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime visible type annotation to read, or -1. + int currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0); + // Index of the next runtime invisible type annotation to read (in the + // invisibleTypeAnnotationOffsets array). + int currentInvisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime invisible type annotation to read, or -1. + int currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0); + + // Whether a F_INSERT stack map frame must be inserted before the current instruction. + boolean insertFrame = false; + + // The delta to subtract from a goto_w or jsr_w opcode to get the corresponding goto or jsr + // opcode, or 0 if goto_w and jsr_w must be left unchanged (i.e. when expanding ASM specific + // instructions). + final int wideJumpOpcodeDelta = + (context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0; + + currentOffset = bytecodeStartOffset; + while (currentOffset < bytecodeEndOffset) { + final int currentBytecodeOffset = currentOffset - bytecodeStartOffset; + + // Visit the label and the line number(s) for this bytecode offset, if any. + Label currentLabel = labels[currentBytecodeOffset]; + if (currentLabel != null) { + currentLabel.accept(methodVisitor, (context.parsingOptions & SKIP_DEBUG) == 0); + } + + // Visit the stack map frame for this bytecode offset, if any. + while (stackMapFrameOffset != 0 + && (context.currentFrameOffset == currentBytecodeOffset + || context.currentFrameOffset == -1)) { + // If there is a stack map frame for this offset, make methodVisitor visit it, and read the + // next stack map frame if there is one. + if (context.currentFrameOffset != -1) { + if (!compressedFrames || expandFrames) { + methodVisitor.visitFrame( + Opcodes.F_NEW, + context.currentFrameLocalCount, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } else { + methodVisitor.visitFrame( + context.currentFrameType, + context.currentFrameLocalCountDelta, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } + // Since there is already a stack map frame for this bytecode offset, there is no need to + // insert a new one. + insertFrame = false; + } + if (stackMapFrameOffset < stackMapTableEndOffset) { + stackMapFrameOffset = + readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context); + } else { + stackMapFrameOffset = 0; + } + } + + // Insert a stack map frame for this bytecode offset, if requested by setting insertFrame to + // true during the previous iteration. The actual frame content is computed in MethodWriter. + if (insertFrame) { + if ((context.parsingOptions & EXPAND_FRAMES) != 0) { + methodVisitor.visitFrame(Constants.F_INSERT, 0, null, 0, null); + } + insertFrame = false; + } + + // Visit the instruction at this bytecode offset. + int opcode = classBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + methodVisitor.visitInsn(opcode); + currentOffset += 1; + break; + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + opcode -= Constants.ILOAD_0; + methodVisitor.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + opcode -= Constants.ISTORE_0; + methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + methodVisitor.visitJumpInsn( + opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + methodVisitor.visitJumpInsn( + opcode - wideJumpOpcodeDelta, + labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + currentOffset += 5; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + { + // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO + // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:..., + // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and + // where designates the instruction just after the GOTO_W. + // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and + // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL. + opcode = + opcode < Constants.ASM_IFNULL + ? opcode - Constants.ASM_OPCODE_DELTA + : opcode - Constants.ASM_IFNULL_OPCODE_DELTA; + Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)]; + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + // Replace GOTO with GOTO_W and JSR with JSR_W. + methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target); + } else { + // Compute the "opposite" of opcode. This can be done by flipping the least + // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ + // (with a pre and post offset by 1). + opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1; + Label endif = createLabel(currentBytecodeOffset + 3, labels); + methodVisitor.visitJumpInsn(opcode, endif); + methodVisitor.visitJumpInsn(Constants.GOTO_W, target); + // endif designates the instruction just after GOTO_W, and is visited as part of the + // next instruction. Since it is a jump target, we need to insert a frame here. + insertFrame = true; + } + currentOffset += 3; + break; + } + case Constants.ASM_GOTO_W: + // Replace ASM_GOTO_W with GOTO_W. + methodVisitor.visitJumpInsn( + Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns + // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame + // here. + insertFrame = true; + currentOffset += 5; + break; + case Constants.WIDE: + opcode = classBuffer[currentOffset + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + methodVisitor.visitIincInsn( + readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4)); + currentOffset += 6; + } else { + methodVisitor.visitVarInsn(opcode, readUnsignedShort(currentOffset + 2)); + currentOffset += 4; + } + break; + case Opcodes.TABLESWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int low = readInt(currentOffset + 4); + int high = readInt(currentOffset + 8); + currentOffset += 12; + Label[] table = new Label[high - low + 1]; + for (int i = 0; i < table.length; ++i) { + table[i] = labels[currentBytecodeOffset + readInt(currentOffset)]; + currentOffset += 4; + } + methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table); + break; + } + case Opcodes.LOOKUPSWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int numPairs = readInt(currentOffset + 4); + currentOffset += 8; + int[] keys = new int[numPairs]; + Label[] values = new Label[numPairs]; + for (int i = 0; i < numPairs; ++i) { + keys[i] = readInt(currentOffset); + values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)]; + currentOffset += 8; + } + methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values); + break; + } + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: + methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF); + currentOffset += 2; + break; + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: + methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]); + currentOffset += 2; + break; + case Opcodes.SIPUSH: + methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1)); + currentOffset += 3; + break; + case Opcodes.LDC: + methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer)); + currentOffset += 2; + break; + case Constants.LDC_W: + case Constants.LDC2_W: + methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer)); + currentOffset += 3; + break; + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String owner = readClass(cpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + if (opcode < Opcodes.INVOKEVIRTUAL) { + methodVisitor.visitFieldInsn(opcode, owner, name, descriptor); + } else { + boolean isInterface = + classBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + if (opcode == Opcodes.INVOKEINTERFACE) { + currentOffset += 5; + } else { + currentOffset += 3; + } + break; + } + case Opcodes.INVOKEDYNAMIC: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = + (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = + new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = + readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; + } + methodVisitor.visitInvokeDynamicInsn( + name, descriptor, handle, bootstrapMethodArguments); + currentOffset += 5; + break; + } + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: + methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer)); + currentOffset += 3; + break; + case Opcodes.IINC: + methodVisitor.visitIincInsn( + classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]); + currentOffset += 3; + break; + case Opcodes.MULTIANEWARRAY: + methodVisitor.visitMultiANewArrayInsn( + readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF); + currentOffset += 4; + break; + default: + throw new AssertionError(); + } + + // Visit the runtime visible instruction annotations, if any. + while (visibleTypeAnnotationOffsets != null + && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length + && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex); + } + + // Visit the runtime invisible instruction annotations, if any. + while (invisibleTypeAnnotationOffsets != null + && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length + && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex); + } + } + if (labels[codeLength] != null) { + methodVisitor.visitLabel(labels[codeLength]); + } + + // Visit LocalVariableTable and LocalVariableTypeTable attributes. + if (localVariableTableOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { + // The (start_pc, index, signature_index) fields of each entry of the LocalVariableTypeTable. + int[] typeTable = null; + if (localVariableTypeTableOffset != 0) { + typeTable = new int[readUnsignedShort(localVariableTypeTableOffset) * 3]; + currentOffset = localVariableTypeTableOffset + 2; + int typeTableIndex = typeTable.length; + while (typeTableIndex > 0) { + // Store the offset of 'signature_index', and the value of 'index' and 'start_pc'. + typeTable[--typeTableIndex] = currentOffset + 6; + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset + 8); + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset); + currentOffset += 10; + } + } + int localVariableTableLength = readUnsignedShort(localVariableTableOffset); + currentOffset = localVariableTableOffset + 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + String name = readUTF8(currentOffset + 4, charBuffer); + String descriptor = readUTF8(currentOffset + 6, charBuffer); + int index = readUnsignedShort(currentOffset + 8); + currentOffset += 10; + String signature = null; + if (typeTable != null) { + for (int i = 0; i < typeTable.length; i += 3) { + if (typeTable[i] == startPc && typeTable[i + 1] == index) { + signature = readUTF8(typeTable[i + 2], charBuffer); + break; + } + } + } + methodVisitor.visitLocalVariable( + name, descriptor, signature, labels[startPc], labels[startPc + length], index); + } + } + + // Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute. + if (visibleTypeAnnotationOffsets != null) { + for (int typeAnnotationOffset : visibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ true), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + // Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute. + if (invisibleTypeAnnotationOffsets != null) { + for (int typeAnnotationOffset : invisibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ false), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the max stack and max locals values. + methodVisitor.visitMaxs(maxStack, maxLocals); + } + + /** + * Returns the label corresponding to the given bytecode offset. The default implementation of + * this method creates a label for the given offset if it has not been already created. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a label already exists + * for bytecodeOffset this method must not create a new one. Otherwise it must store the new + * label in this array. + * @return a non null Label, which must be equal to labels[bytecodeOffset]. + */ + protected Label readLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + labels[bytecodeOffset] = new Label(); + } + return labels[bytecodeOffset]; + } + + /** + * Creates a label without the {@link Label#FLAG_DEBUG_ONLY} flag set, for the given bytecode + * offset. The label is created with a call to {@link #readLabel} and its {@link + * Label#FLAG_DEBUG_ONLY} flag is cleared. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + * @return a Label without the {@link Label#FLAG_DEBUG_ONLY} flag set. + */ + private Label createLabel(final int bytecodeOffset, final Label[] labels) { + Label label = readLabel(bytecodeOffset, labels); + label.flags &= ~Label.FLAG_DEBUG_ONLY; + return label; + } + + /** + * Creates a label with the {@link Label#FLAG_DEBUG_ONLY} flag set, if there is no already + * existing label for the given bytecode offset (otherwise does nothing). The label is created + * with a call to {@link #readLabel}. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + */ + private void createDebugLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + readLabel(bytecodeOffset, labels).flags |= Label.FLAG_DEBUG_ONLY; + } + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse annotations, type annotations and parameter annotations + // ---------------------------------------------------------------------------------------------- + + /** + * Parses a Runtime[In]VisibleTypeAnnotations attribute to find the offset of each type_annotation + * entry it contains, to find the corresponding labels, and to visit the try catch block + * annotations. + * + * @param methodVisitor the method visitor to be used to visit the try catch block annotations. + * @param context information about the class being parsed. + * @param runtimeTypeAnnotationsOffset the start offset of a Runtime[In]VisibleTypeAnnotations + * attribute, excluding the attribute_info's attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleTypeAnnotations attribute, + * false it is a RuntimeInvisibleTypeAnnotations attribute. + * @return the start offset of each entry of the Runtime[In]VisibleTypeAnnotations_attribute's + * 'annotations' array field. + */ + private int[] readTypeAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeTypeAnnotationsOffset, + final boolean visible) { + char[] charBuffer = context.charBuffer; + int currentOffset = runtimeTypeAnnotationsOffset; + // Read the num_annotations field and create an array to store the type_annotation offsets. + int[] typeAnnotationsOffsets = new int[readUnsignedShort(currentOffset)]; + currentOffset += 2; + // Parse the 'annotations' array field. + for (int i = 0; i < typeAnnotationsOffsets.length; ++i) { + typeAnnotationsOffsets[i] = currentOffset; + // Parse the type_annotation's target_type and the target_info fields. The size of the + // target_info field depends on the value of target_type. + int targetType = readInt(currentOffset); + switch (targetType >>> 24) { + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + // A localvar_target has a variable size, which depends on the value of their table_length + // field. It also references bytecode offsets, for which we need labels. + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + while (tableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + // Skip the index field (2 bytes). + currentOffset += 6; + createLabel(startPc, context.currentMethodLabels); + createLabel(startPc + length, context.currentMethodLabels); + } + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + currentOffset += 3; + break; + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + default: + // TypeReference type which can't be used in Code attribute, or which is unknown. + throw new IllegalArgumentException(); + } + // Parse the rest of the type_annotation structure, starting with the target_path structure + // (whose size depends on its path_length field). + int pathLength = readByte(currentOffset); + if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) { + // Parse the target_path structure and create a corresponding TypePath. + TypePath path = pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); + currentOffset += 1 + 2 * pathLength; + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitTryCatchAnnotation( + targetType & 0xFFFFFF00, path, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } else { + // We don't want to visit the other target_type annotations, so we just skip them (which + // requires some parsing because the element_value_pairs array has a variable size). First, + // skip the target_path structure: + currentOffset += 3 + 2 * pathLength; + // Then skip the num_element_value_pairs and element_value_pairs fields (by reading them + // with a null AnnotationVisitor). + currentOffset = + readElementValues( + /* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer); + } + } + return typeAnnotationsOffsets; + } + + /** + * Returns the bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or + * -1 if there is no such type_annotation of if it does not have a bytecode offset. + * + * @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a + * Runtime[In]VisibleTypeAnnotations attribute, or {@literal null}. + * @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets. + * @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1 + * if there is no such type_annotation of if it does not have a bytecode offset. + */ + private int getTypeAnnotationBytecodeOffset( + final int[] typeAnnotationOffsets, final int typeAnnotationIndex) { + if (typeAnnotationOffsets == null + || typeAnnotationIndex >= typeAnnotationOffsets.length + || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) { + return -1; + } + return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1); + } + + /** + * Parses the header of a JVMS type_annotation structure to extract its target_type, target_info + * and target_path (the result is stored in the given context), and returns the start offset of + * the rest of the type_annotation structure. + * + * @param context information about the class being parsed. This is where the extracted + * target_type and target_path must be stored. + * @param typeAnnotationOffset the start offset of a type_annotation structure. + * @return the start offset of the rest of the type_annotation structure. + */ + private int readTypeAnnotationTarget(final Context context, final int typeAnnotationOffset) { + int currentOffset = typeAnnotationOffset; + // Parse and store the target_type structure. + int targetType = readInt(typeAnnotationOffset); + switch (targetType >>> 24) { + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + targetType &= 0xFFFF0000; + currentOffset += 2; + break; + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + targetType &= 0xFF000000; + currentOffset += 1; + break; + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + targetType &= 0xFF000000; + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + context.currentLocalVariableAnnotationRangeStarts = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeEnds = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeIndices = new int[tableLength]; + for (int i = 0; i < tableLength; ++i) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + int index = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + context.currentLocalVariableAnnotationRangeStarts[i] = + createLabel(startPc, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeEnds[i] = + createLabel(startPc + length, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeIndices[i] = index; + } + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + targetType &= 0xFF0000FF; + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + targetType &= 0xFFFFFF00; + currentOffset += 3; + break; + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + targetType &= 0xFF000000; + currentOffset += 3; + break; + default: + throw new IllegalArgumentException(); + } + context.currentTypeAnnotationTarget = targetType; + // Parse and store the target_path structure. + int pathLength = readByte(currentOffset); + context.currentTypeAnnotationTargetPath = + pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); + // Return the start offset of the rest of the type_annotation structure. + return currentOffset + 1 + 2 * pathLength; + } + + /** + * Reads a Runtime[In]VisibleParameterAnnotations attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the parameter annotations. + * @param context information about the class being parsed. + * @param runtimeParameterAnnotationsOffset the start offset of a + * Runtime[In]VisibleParameterAnnotations attribute, excluding the attribute_info's + * attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleParameterAnnotations + * attribute, false it is a RuntimeInvisibleParameterAnnotations attribute. + */ + private void readParameterAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeParameterAnnotationsOffset, + final boolean visible) { + int currentOffset = runtimeParameterAnnotationsOffset; + int numParameters = classFileBuffer[currentOffset++] & 0xFF; + methodVisitor.visitAnnotableParameterCount(numParameters, visible); + char[] charBuffer = context.charBuffer; + for (int i = 0; i < numParameters; ++i) { + int numAnnotations = readUnsignedShort(currentOffset); + currentOffset += 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + /** + * Reads the element values of a JVMS 'annotation' structure and makes the given visitor visit + * them. This method can also be used to read the values of the JVMS 'array_value' field of an + * annotation's 'element_value'. + * + * @param annotationVisitor the visitor that must visit the values. + * @param annotationOffset the start offset of an 'annotation' structure (excluding its type_index + * field) or of an 'array_value' structure. + * @param named if the annotation values are named or not. This should be true to parse the values + * of a JVMS 'annotation' structure, and false to parse the JVMS 'array_value' of an + * annotation's element_value. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'annotation' or 'array_value' structure. + */ + private int readElementValues( + final AnnotationVisitor annotationVisitor, + final int annotationOffset, + final boolean named, + final char[] charBuffer) { + int currentOffset = annotationOffset; + // Read the num_element_value_pairs field (or num_values field for an array_value). + int numElementValuePairs = readUnsignedShort(currentOffset); + currentOffset += 2; + if (named) { + // Parse the element_value_pairs array. + while (numElementValuePairs-- > 0) { + String elementName = readUTF8(currentOffset, charBuffer); + currentOffset = + readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer); + } + } else { + // Parse the array_value array. + while (numElementValuePairs-- > 0) { + currentOffset = + readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer); + } + } + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + return currentOffset; + } + + /** + * Reads a JVMS 'element_value' structure and makes the given visitor visit it. + * + * @param annotationVisitor the visitor that must visit the element_value structure. + * @param elementValueOffset the start offset in {@link #classFileBuffer} of the element_value + * structure to be read. + * @param elementName the name of the element_value structure to be read, or {@literal null}. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'element_value' structure. + */ + private int readElementValue( + final AnnotationVisitor annotationVisitor, + final int elementValueOffset, + final String elementName, + final char[] charBuffer) { + int currentOffset = elementValueOffset; + if (annotationVisitor == null) { + switch (classFileBuffer[currentOffset] & 0xFF) { + case 'e': // enum_const_value + return currentOffset + 5; + case '@': // annotation_value + return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer); + case '[': // array_value + return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer); + default: + return currentOffset + 3; + } + } + switch (classFileBuffer[currentOffset++] & 0xFF) { + case 'B': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'C': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'D': // const_value_index, CONSTANT_Double + case 'F': // const_value_index, CONSTANT_Float + case 'I': // const_value_index, CONSTANT_Integer + case 'J': // const_value_index, CONSTANT_Long + annotationVisitor.visit( + elementName, readConst(readUnsignedShort(currentOffset), charBuffer)); + currentOffset += 2; + break; + case 'S': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + + case 'Z': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, + readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0 + ? Boolean.FALSE + : Boolean.TRUE); + currentOffset += 2; + break; + case 's': // const_value_index, CONSTANT_Utf8 + annotationVisitor.visit(elementName, readUTF8(currentOffset, charBuffer)); + currentOffset += 2; + break; + case 'e': // enum_const_value + annotationVisitor.visitEnum( + elementName, + readUTF8(currentOffset, charBuffer), + readUTF8(currentOffset + 2, charBuffer)); + currentOffset += 4; + break; + case 'c': // class_info + annotationVisitor.visit(elementName, Type.getType(readUTF8(currentOffset, charBuffer))); + currentOffset += 2; + break; + case '@': // annotation_value + currentOffset = + readElementValues( + annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)), + currentOffset + 2, + true, + charBuffer); + break; + case '[': // array_value + int numValues = readUnsignedShort(currentOffset); + currentOffset += 2; + if (numValues == 0) { + return readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); + } + switch (classFileBuffer[currentOffset] & 0xFF) { + case 'B': + byte[] byteValues = new byte[numValues]; + for (int i = 0; i < numValues; i++) { + byteValues[i] = (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, byteValues); + break; + case 'Z': + boolean[] booleanValues = new boolean[numValues]; + for (int i = 0; i < numValues; i++) { + booleanValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]) != 0; + currentOffset += 3; + } + annotationVisitor.visit(elementName, booleanValues); + break; + case 'S': + short[] shortValues = new short[numValues]; + for (int i = 0; i < numValues; i++) { + shortValues[i] = (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, shortValues); + break; + case 'C': + char[] charValues = new char[numValues]; + for (int i = 0; i < numValues; i++) { + charValues[i] = (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, charValues); + break; + case 'I': + int[] intValues = new int[numValues]; + for (int i = 0; i < numValues; i++) { + intValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, intValues); + break; + case 'J': + long[] longValues = new long[numValues]; + for (int i = 0; i < numValues; i++) { + longValues[i] = readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, longValues); + break; + case 'F': + float[] floatValues = new float[numValues]; + for (int i = 0; i < numValues; i++) { + floatValues[i] = + Float.intBitsToFloat( + readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, floatValues); + break; + case 'D': + double[] doubleValues = new double[numValues]; + for (int i = 0; i < numValues; i++) { + doubleValues[i] = + Double.longBitsToDouble( + readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, doubleValues); + break; + default: + currentOffset = + readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); + break; + } + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse stack map frames + // ---------------------------------------------------------------------------------------------- + + /** + * Computes the implicit frame of the method currently being parsed (as defined in the given + * {@link Context}) and stores it in the given context. + * + * @param context information about the class being parsed. + */ + private void computeImplicitFrame(final Context context) { + String methodDescriptor = context.currentMethodDescriptor; + Object[] locals = context.currentFrameLocalTypes; + int numLocal = 0; + if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) { + if ("".equals(context.currentMethodName)) { + locals[numLocal++] = Opcodes.UNINITIALIZED_THIS; + } else { + locals[numLocal++] = readClass(header + 2, context.charBuffer); + } + } + // Parse the method descriptor, one argument type descriptor at each iteration. Start by + // skipping the first method descriptor character, which is always '('. + int currentMethodDescritorOffset = 1; + while (true) { + int currentArgumentDescriptorStartOffset = currentMethodDescritorOffset; + switch (methodDescriptor.charAt(currentMethodDescritorOffset++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + locals[numLocal++] = Opcodes.INTEGER; + break; + case 'F': + locals[numLocal++] = Opcodes.FLOAT; + break; + case 'J': + locals[numLocal++] = Opcodes.LONG; + break; + case 'D': + locals[numLocal++] = Opcodes.DOUBLE; + break; + case '[': + while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') { + ++currentMethodDescritorOffset; + } + if (methodDescriptor.charAt(currentMethodDescritorOffset) == 'L') { + ++currentMethodDescritorOffset; + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; + } + } + locals[numLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset); + break; + case 'L': + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; + } + locals[numLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++); + break; + default: + context.currentFrameLocalCount = numLocal; + return; + } + } + } + + /** + * Reads a JVMS 'stack_map_frame' structure and stores the result in the given {@link Context} + * object. This method can also be used to read a full_frame structure, excluding its frame_type + * field (this is used to parse the legacy StackMap attributes). + * + * @param stackMapFrameOffset the start offset in {@link #classFileBuffer} of the + * stack_map_frame_value structure to be read, or the start offset of a full_frame structure + * (excluding its frame_type field). + * @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame' + * structure without its frame_type field. + * @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}. + * @param context where the parsed stack map frame must be stored. + * @return the end offset of the JVMS 'stack_map_frame' or 'full_frame' structure. + */ + private int readStackMapFrame( + final int stackMapFrameOffset, + final boolean compressed, + final boolean expand, + final Context context) { + int currentOffset = stackMapFrameOffset; + final char[] charBuffer = context.charBuffer; + final Label[] labels = context.currentMethodLabels; + int frameType; + if (compressed) { + // Read the frame_type field. + frameType = classFileBuffer[currentOffset++] & 0xFF; + } else { + frameType = Frame.FULL_FRAME; + context.currentFrameOffset = -1; + } + int offsetDelta; + context.currentFrameLocalCountDelta = 0; + if (frameType < Frame.SAME_LOCALS_1_STACK_ITEM_FRAME) { + offsetDelta = frameType; + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.RESERVED) { + offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME; + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + offsetDelta = readUnsignedShort(currentOffset); + currentOffset += 2; + if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.CHOP_FRAME && frameType < Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_CHOP; + context.currentFrameLocalCountDelta = Frame.SAME_FRAME_EXTENDED - frameType; + context.currentFrameLocalCount -= context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else if (frameType == Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.FULL_FRAME) { + int local = expand ? context.currentFrameLocalCount : 0; + for (int k = frameType - Frame.SAME_FRAME_EXTENDED; k > 0; k--) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels); + } + context.currentFrameType = Opcodes.F_APPEND; + context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED; + context.currentFrameLocalCount += context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else { + final int numberOfLocals = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameType = Opcodes.F_FULL; + context.currentFrameLocalCountDelta = numberOfLocals; + context.currentFrameLocalCount = numberOfLocals; + for (int local = 0; local < numberOfLocals; ++local) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels); + } + final int numberOfStackItems = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameStackCount = numberOfStackItems; + for (int stack = 0; stack < numberOfStackItems; ++stack) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels); + } + } + } else { + throw new IllegalArgumentException(); + } + context.currentFrameOffset += offsetDelta + 1; + createLabel(context.currentFrameOffset, labels); + return currentOffset; + } + + /** + * Reads a JVMS 'verification_type_info' structure and stores it at the given index in the given + * array. + * + * @param verificationTypeInfoOffset the start offset of the 'verification_type_info' structure to + * read. + * @param frame the array where the parsed type must be stored. + * @param index the index in 'frame' where the parsed type must be stored. + * @param charBuffer the buffer used to read strings in the constant pool. + * @param labels the labels of the method currently being parsed, indexed by their offset. If the + * parsed type is an ITEM_Uninitialized, a new label for the corresponding NEW instruction is + * stored in this array if it does not already exist. + * @return the end offset of the JVMS 'verification_type_info' structure. + */ + private int readVerificationTypeInfo( + final int verificationTypeInfoOffset, + final Object[] frame, + final int index, + final char[] charBuffer, + final Label[] labels) { + int currentOffset = verificationTypeInfoOffset; + int tag = classFileBuffer[currentOffset++] & 0xFF; + switch (tag) { + case Frame.ITEM_TOP: + frame[index] = Opcodes.TOP; + break; + case Frame.ITEM_INTEGER: + frame[index] = Opcodes.INTEGER; + break; + case Frame.ITEM_FLOAT: + frame[index] = Opcodes.FLOAT; + break; + case Frame.ITEM_DOUBLE: + frame[index] = Opcodes.DOUBLE; + break; + case Frame.ITEM_LONG: + frame[index] = Opcodes.LONG; + break; + case Frame.ITEM_NULL: + frame[index] = Opcodes.NULL; + break; + case Frame.ITEM_UNINITIALIZED_THIS: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case Frame.ITEM_OBJECT: + frame[index] = readClass(currentOffset, charBuffer); + currentOffset += 2; + break; + case Frame.ITEM_UNINITIALIZED: + frame[index] = createLabel(readUnsignedShort(currentOffset), labels); + currentOffset += 2; + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse attributes + // ---------------------------------------------------------------------------------------------- + + /** + * Returns the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. + * + * @return the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. + */ + final int getFirstAttributeOffset() { + // Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes + // each), as well as the interfaces array field (2 bytes per interface). + int currentOffset = header + 8 + readUnsignedShort(header + 6) * 2; + + // Read the fields_count field. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + // Skip the 'fields' array field. + while (fieldsCount-- > 0) { + // Invariant: currentOffset is the offset of a field_info structure. + // Skip the access_flags, name_index and descriptor_index fields (2 bytes each), and read the + // attributes_count field. + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + // Skip the 'attributes' array field. + while (attributesCount-- > 0) { + // Invariant: currentOffset is the offset of an attribute_info structure. + // Read the attribute_length field (2 bytes after the start of the attribute_info) and skip + // this many bytes, plus 6 for the attribute_name_index and attribute_length fields + // (yielding the total size of the attribute_info structure). + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the methods_count and 'methods' fields, using the same method as above. + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + while (attributesCount-- > 0) { + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the ClassFile's attributes_count field. + return currentOffset + 2; + } + + /** + * Reads the BootstrapMethods attribute to compute the offset of each bootstrap method. + * + * @param maxStringLength a conservative estimate of the maximum length of the strings contained + * in the constant pool of the class. + * @return the offsets of the bootstrap methods. + */ + private int[] readBootstrapMethodsAttribute(final int maxStringLength) { + char[] charBuffer = new char[maxStringLength]; + int currentAttributeOffset = getFirstAttributeOffset(); + int[] currentBootstrapMethodOffsets = null; + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // Read the num_bootstrap_methods field and create an array of this size. + currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)]; + // Compute and store the offset of each 'bootstrap_methods' array field entry. + int currentBootstrapMethodOffset = currentAttributeOffset + 2; + for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) { + currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset; + // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each), + // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2). + currentBootstrapMethodOffset += + 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2; + } + return currentBootstrapMethodOffsets; + } + currentAttributeOffset += attributeLength; + } + throw new IllegalArgumentException(); + } + + /** + * Reads a non standard JVMS 'attribute' structure in {@link #classFileBuffer}. + * + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. + * @param type the type of the attribute. + * @param offset the start offset of the JVMS 'attribute' structure in {@link #classFileBuffer}. + * The 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to read strings in the constant pool. + * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link + * #classFileBuffer}, or -1 if the attribute to be read is not a code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param labels the labels of the method's code, or {@literal null} if the attribute to be read + * is not a code attribute. + * @return the attribute that has been read. + */ + private Attribute readAttribute( + final Attribute[] attributePrototypes, + final String type, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + for (Attribute attributePrototype : attributePrototypes) { + if (attributePrototype.type.equals(type)) { + return attributePrototype.read( + this, offset, length, charBuffer, codeAttributeOffset, labels); + } + } + return new Attribute(type).read(this, offset, length, null, -1, null); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: low level parsing + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the number of entries in the class's constant pool table. + * + * @return the number of entries in the class's constant pool table. + */ + public int getItemCount() { + return cpInfoOffsets.length; + } + + /** + * Returns the start offset in this {@link ClassReader} of a JVMS 'cp_info' structure (i.e. a + * constant pool entry), plus one. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool + * table. + * @return the start offset in this {@link ClassReader} of the corresponding JVMS 'cp_info' + * structure, plus one. + */ + public int getItem(final int constantPoolEntryIndex) { + return cpInfoOffsets[constantPoolEntryIndex]; + } + + /** + * Returns a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + * + * @return a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readByte(final int offset) { + return classFileBuffer[offset] & 0xFF; + } + + /** + * Reads an unsigned short value in this {@link ClassReader}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start index of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readUnsignedShort(final int offset) { + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF); + } + + /** + * Reads a signed short value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public short readShort(final int offset) { + byte[] classBuffer = classFileBuffer; + return (short) (((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF)); + } + + /** + * Reads a signed int value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readInt(final int offset) { + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 24) + | ((classBuffer[offset + 1] & 0xFF) << 16) + | ((classBuffer[offset + 2] & 0xFF) << 8) + | (classBuffer[offset + 3] & 0xFF); + } + + /** + * Reads a signed long value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public long readLong(final int offset) { + long l1 = readInt(offset); + long l0 = readInt(offset + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Utf8 entry in the class's constant pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). + public String readUTF8(final int offset, final char[] charBuffer) { + int constantPoolEntryIndex = readUnsignedShort(offset); + if (offset == 0 || constantPoolEntryIndex == 0) { + return null; + } + return readUtf(constantPoolEntryIndex, charBuffer); + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in {@link #classFileBuffer}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool + * table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) { + String value = constantUtf8Values[constantPoolEntryIndex]; + if (value != null) { + return value; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + return constantUtf8Values[constantPoolEntryIndex] = + readUtf(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer); + } + + /** + * Reads an UTF8 string in {@link #classFileBuffer}. + * + * @param utfOffset the start offset of the UTF8 string to be read. + * @param utfLength the length of the UTF8 string to be read. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUtf(final int utfOffset, final int utfLength, final char[] charBuffer) { + int currentOffset = utfOffset; + int endOffset = currentOffset + utfLength; + int strLength = 0; + byte[] classBuffer = classFileBuffer; + while (currentOffset < endOffset) { + int currentByte = classBuffer[currentOffset++]; + if ((currentByte & 0x80) == 0) { + charBuffer[strLength++] = (char) (currentByte & 0x7F); + } else if ((currentByte & 0xE0) == 0xC0) { + charBuffer[strLength++] = + (char) (((currentByte & 0x1F) << 6) + (classBuffer[currentOffset++] & 0x3F)); + } else { + charBuffer[strLength++] = + (char) + (((currentByte & 0xF) << 12) + + ((classBuffer[currentOffset++] & 0x3F) << 6) + + (classBuffer[currentOffset++] & 0x3F)); + } + } + return new String(charBuffer, 0, strLength); + } + + /** + * Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or + * CONSTANT_Package constant pool entry in {@link #classFileBuffer}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in {@link #classFileBuffer}, whose + * value is the index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_Module or CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified constant pool entry. + */ + private String readStringish(final int offset, final char[] charBuffer) { + // Get the start offset of the cp_info structure (plus one), and read the CONSTANT_Utf8 entry + // designated by the first two bytes of this cp_info. + return readUTF8(cpInfoOffsets[readUnsignedShort(offset)], charBuffer); + } + + /** + * Reads a CONSTANT_Class constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Class entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Class entry. + */ + public String readClass(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Module constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Module entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Module entry. + */ + public String readModule(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Package constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Package entry. + */ + public String readPackage(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Dynamic constant pool entry in {@link #classFileBuffer}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant + * pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry. + */ + private ConstantDynamic readConstantDynamic( + final int constantPoolEntryIndex, final char[] charBuffer) { + ConstantDynamic constantDynamic = constantDynamicValues[constantPoolEntryIndex]; + if (constantDynamic != null) { + return constantDynamic; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; + } + return constantDynamicValues[constantPoolEntryIndex] = + new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments); + } + + /** + * Reads a numeric or string constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, + * CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_MethodHandle or CONSTANT_Dynamic entry in the class's constant pool. + * @param charBuffer the buffer to be used to read strings. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, + * {@link Type}, {@link Handle} or {@link ConstantDynamic} corresponding to the specified + * constant pool entry. + */ + public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) { + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + switch (classFileBuffer[cpInfoOffset - 1]) { + case Symbol.CONSTANT_INTEGER_TAG: + return readInt(cpInfoOffset); + case Symbol.CONSTANT_FLOAT_TAG: + return Float.intBitsToFloat(readInt(cpInfoOffset)); + case Symbol.CONSTANT_LONG_TAG: + return readLong(cpInfoOffset); + case Symbol.CONSTANT_DOUBLE_TAG: + return Double.longBitsToDouble(readLong(cpInfoOffset)); + case Symbol.CONSTANT_CLASS_TAG: + return Type.getObjectType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_STRING_TAG: + return readUTF8(cpInfoOffset, charBuffer); + case Symbol.CONSTANT_METHOD_TYPE_TAG: + return Type.getMethodType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + int referenceKind = readByte(cpInfoOffset); + int referenceCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(referenceCpInfoOffset + 2)]; + String owner = readClass(referenceCpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + boolean isInterface = + classFileBuffer[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + return new Handle(referenceKind, owner, name, descriptor, isInterface); + case Symbol.CONSTANT_DYNAMIC_TAG: + return readConstantDynamic(constantPoolEntryIndex, charBuffer); + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ClassTooLargeException.java b/native/java/jpype.jvm.asm/org/jpype/asm/ClassTooLargeException.java new file mode 100644 index 000000000..adba9971c --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ClassTooLargeException.java @@ -0,0 +1,71 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * Exception thrown when the constant pool of a class produced by a {@link ClassWriter} is too + * large. + * + * @author Jason Zaugg + */ +public final class ClassTooLargeException extends IndexOutOfBoundsException { + private static final long serialVersionUID = 160715609518896765L; + + private final String className; + private final int constantPoolCount; + + /** + * Constructs a new {@link ClassTooLargeException}. + * + * @param className the internal name of the class. + * @param constantPoolCount the number of constant pool items of the class. + */ + public ClassTooLargeException(final String className, final int constantPoolCount) { + super("Class too large: " + className); + this.className = className; + this.constantPoolCount = constantPoolCount; + } + + /** + * Returns the internal name of the class. + * + * @return the internal name of the class. + */ + public String getClassName() { + return className; + } + + /** + * Returns the number of constant pool items of the class. + * + * @return the number of constant pool items of the class. + */ + public int getConstantPoolCount() { + return constantPoolCount; + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ClassVisitor.java b/native/java/jpype.jvm.asm/org/jpype/asm/ClassVisitor.java new file mode 100644 index 000000000..c75f1ff1f --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ClassVisitor.java @@ -0,0 +1,380 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A visitor to visit a Java class. The methods of this class must be called in the following order: + * {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code + * visitPermittedSubclass} ][ {@code visitOuterClass} ] ( {@code visitAnnotation} | {@code + * visitTypeAnnotation} | {@code visitAttribute} )* ( {@code visitNestMember} | {@code + * visitInnerClass} | {@code visitRecordComponent} | {@code visitField} | {@code visitMethod} )* + * {@code visitEnd}. + * + * @author Eric Bruneton + */ +public abstract class ClassVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + protected final int api; + + /** The class visitor to which this visitor must delegate method calls. May be {@literal null}. */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + public ClassVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link + * Opcodes#ASM8} or {@link Opcodes#ASM9}. + * @param classVisitor the class visitor to which this visitor must delegate method calls. May be + * null. + */ + public ClassVisitor(final int api, final ClassVisitor classVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); + } + this.api = api; + this.cv = classVisitor; + } + + /** + * Visits the header of the class. + * + * @param version the class version. The minor version is stored in the 16 most significant bits, + * and the major version in the 16 least significant bits. + * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if + * the class is deprecated {@link Opcodes#ACC_DEPRECATED} or a record {@link + * Opcodes#ACC_RECORD}. + * @param name the internal name of the class (see {@link Type#getInternalName()}). + * @param signature the signature of this class. May be {@literal null} if the class is not a + * generic one, and does not extend or implement generic classes or interfaces. + * @param superName the internal of name of the super class (see {@link Type#getInternalName()}). + * For interfaces, the super class is {@link Object}. May be {@literal null}, but only for the + * {@link Object} class. + * @param interfaces the internal names of the class's interfaces (see {@link + * Type#getInternalName()}). May be {@literal null}. + */ + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) { + throw new UnsupportedOperationException("Records requires ASM8"); + } + if (cv != null) { + cv.visit(version, access, name, signature, superName, interfaces); + } + } + + /** + * Visits the source of the class. + * + * @param source the name of the source file from which the class was compiled. May be {@literal + * null}. + * @param debug additional debug information to compute the correspondence between source and + * compiled elements of the class. May be {@literal null}. + */ + public void visitSource(final String source, final String debug) { + if (cv != null) { + cv.visitSource(source, debug); + } + } + + /** + * Visit the module corresponding to the class. + * + * @param name the fully qualified name (using dots) of the module. + * @param access the module access flags, among {@code ACC_OPEN}, {@code ACC_SYNTHETIC} and {@code + * ACC_MANDATED}. + * @param version the module version, or {@literal null}. + * @return a visitor to visit the module values, or {@literal null} if this visitor is not + * interested in visiting this module. + */ + public ModuleVisitor visitModule(final String name, final int access, final String version) { + if (api < Opcodes.ASM6) { + throw new UnsupportedOperationException("Module requires ASM6"); + } + if (cv != null) { + return cv.visitModule(name, access, version); + } + return null; + } + + /** + * Visits the nest host class of the class. A nest is a set of classes of the same package that + * share access to their private members. One of these classes, called the host, lists the other + * members of the nest, which in turn should link to the host of their nest. This method must be + * called only once and only if the visited class is a non-host member of a nest. A class is + * implicitly its own nest, so it's invalid to call this method with the visited class name as + * argument. + * + * @param nestHost the internal name of the host class of the nest. + */ + public void visitNestHost(final String nestHost) { + if (api < Opcodes.ASM7) { + throw new UnsupportedOperationException("NestHost requires ASM7"); + } + if (cv != null) { + cv.visitNestHost(nestHost); + } + } + + /** + * Visits the enclosing class of the class. This method must be called only if the class has an + * enclosing class. + * + * @param owner internal name of the enclosing class of the class. + * @param name the name of the method that contains the class, or {@literal null} if the class is + * not enclosed in a method of its enclosing class. + * @param descriptor the descriptor of the method that contains the class, or {@literal null} if + * the class is not enclosed in a method of its enclosing class. + */ + public void visitOuterClass(final String owner, final String name, final String descriptor) { + if (cv != null) { + cv.visitOuterClass(owner, name, descriptor); + } + } + + /** + * Visits an annotation of the class. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (cv != null) { + return cv.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the class signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException("TypeAnnotation requires ASM5"); + } + if (cv != null) { + return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the class. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (cv != null) { + cv.visitAttribute(attribute); + } + } + + /** + * Visits a member of the nest. A nest is a set of classes of the same package that share access + * to their private members. One of these classes, called the host, lists the other members of the + * nest, which in turn should link to the host of their nest. This method must be called only if + * the visited class is the host of a nest. A nest host is implicitly a member of its own nest, so + * it's invalid to call this method with the visited class name as argument. + * + * @param nestMember the internal name of a nest member. + */ + public void visitNestMember(final String nestMember) { + if (api < Opcodes.ASM7) { + throw new UnsupportedOperationException("NestMember requires ASM7"); + } + if (cv != null) { + cv.visitNestMember(nestMember); + } + } + + /** + * Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the + * current class. + * + * @param permittedSubclass the internal name of a permitted subclass. + */ + public void visitPermittedSubclass(final String permittedSubclass) { + if (api < Opcodes.ASM9) { + throw new UnsupportedOperationException("PermittedSubclasses requires ASM9"); + } + if (cv != null) { + cv.visitPermittedSubclass(permittedSubclass); + } + } + + /** + * Visits information about an inner class. This inner class is not necessarily a member of the + * class being visited. + * + * @param name the internal name of an inner class (see {@link Type#getInternalName()}). + * @param outerName the internal name of the class to which the inner class belongs (see {@link + * Type#getInternalName()}). May be {@literal null} for not member classes. + * @param innerName the (simple) name of the inner class inside its enclosing class. May be + * {@literal null} for anonymous inner classes. + * @param access the access flags of the inner class as originally declared in the enclosing + * class. + */ + public void visitInnerClass( + final String name, final String outerName, final String innerName, final int access) { + if (cv != null) { + cv.visitInnerClass(name, outerName, innerName, access); + } + } + + /** + * Visits a record component of the class. + * + * @param name the record component name. + * @param descriptor the record component descriptor (see {@link Type}). + * @param signature the record component signature. May be {@literal null} if the record component + * type does not use generic types. + * @return a visitor to visit this record component annotations and attributes, or {@literal null} + * if this class visitor is not interested in visiting these annotations and attributes. + */ + public RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + if (api < Opcodes.ASM8) { + throw new UnsupportedOperationException("Record requires ASM8"); + } + if (cv != null) { + return cv.visitRecordComponent(name, descriptor, signature); + } + return null; + } + + /** + * Visits a field of the class. + * + * @param access the field's access flags (see {@link Opcodes}). This parameter also indicates if + * the field is synthetic and/or deprecated. + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be {@literal null} if the field's type does not use + * generic types. + * @param value the field's initial value. This parameter, which may be {@literal null} if the + * field does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link + * Long}, a {@link Double} or a {@link String} (for {@code int}, {@code float}, {@code long} + * or {@code String} fields respectively). This parameter is only used for static + * fields. Its value is ignored for non static fields, which must be initialized through + * bytecode instructions in constructors or methods. + * @return a visitor to visit field annotations and attributes, or {@literal null} if this class + * visitor is not interested in visiting these annotations and attributes. + */ + public FieldVisitor visitField( + final int access, + final String name, + final String descriptor, + final String signature, + final Object value) { + if (cv != null) { + return cv.visitField(access, name, descriptor, signature, value); + } + return null; + } + + /** + * Visits a method of the class. This method must return a new {@link MethodVisitor} + * instance (or {@literal null}) each time it is called, i.e., it should not return a previously + * returned visitor. + * + * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if + * the method is synthetic and/or deprecated. + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be {@literal null} if the method parameters, + * return type and exceptions do not use generic types. + * @param exceptions the internal names of the method's exception classes (see {@link + * Type#getInternalName()}). May be {@literal null}. + * @return an object to visit the byte code of the method, or {@literal null} if this class + * visitor is not interested in visiting the code of this method. + */ + public MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + if (cv != null) { + return cv.visitMethod(access, name, descriptor, signature, exceptions); + } + return null; + } + + /** + * Visits the end of the class. This method, which is the last one to be called, is used to inform + * the visitor that all the fields and methods of the class have been visited. + */ + public void visitEnd() { + if (cv != null) { + cv.visitEnd(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ClassWriter.java b/native/java/jpype.jvm.asm/org/jpype/asm/ClassWriter.java new file mode 100644 index 000000000..77922a88f --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ClassWriter.java @@ -0,0 +1,1053 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A {@link ClassVisitor} that generates a corresponding ClassFile structure, as defined in the Java + * Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from + * scratch", or with one or more {@link ClassReader} and adapter {@link ClassVisitor} to generate a + * modified class from one or more existing Java classes. + * + * @see JVMS 4 + * @author Eric Bruneton + */ +public class ClassWriter extends ClassVisitor { + + /** + * A flag to automatically compute the maximum stack size and the maximum number of local + * variables of methods. If this flag is set, then the arguments of the {@link + * MethodVisitor#visitMaxs} method of the {@link MethodVisitor} returned by the {@link + * #visitMethod} method will be ignored, and computed automatically from the signature and the + * bytecode of each method. + * + *

Note: for classes whose version is {@link Opcodes#V1_7} of more, this option requires + * valid stack map frames. The maximum stack size is then computed from these frames, and from the + * bytecode instructions in between. If stack map frames are not present or must be recomputed, + * used {@link #COMPUTE_FRAMES} instead. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * A flag to automatically compute the stack map frames of methods from scratch. If this flag is + * set, then the calls to the {@link MethodVisitor#visitFrame} method are ignored, and the stack + * map frames are recomputed from the methods bytecode. The arguments of the {@link + * MethodVisitor#visitMaxs} method are also ignored and recomputed from the bytecode. In other + * words, {@link #COMPUTE_FRAMES} implies {@link #COMPUTE_MAXS}. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + // Note: fields are ordered as in the ClassFile structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The minor_version and major_version fields of the JVMS ClassFile structure. minor_version is + * stored in the 16 most significant bits, and major_version in the 16 least significant bits. + */ + private int version; + + /** The symbol table for this class (contains the constant_pool and the BootstrapMethods). */ + private final SymbolTable symbolTable; + + /** + * The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED} or {}@link Opcodes#ACC_RECORD}, which are + * removed when generating the ClassFile structure. + */ + private int accessFlags; + + /** The this_class field of the JVMS ClassFile structure. */ + private int thisClass; + + /** The super_class field of the JVMS ClassFile structure. */ + private int superClass; + + /** The interface_count field of the JVMS ClassFile structure. */ + private int interfaceCount; + + /** The 'interfaces' array of the JVMS ClassFile structure. */ + private int[] interfaces; + + /** + * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their + * {@link FieldWriter#fv} field. This field stores the first element of this list. + */ + private FieldWriter firstField; + + /** + * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their + * {@link FieldWriter#fv} field. This field stores the last element of this list. + */ + private FieldWriter lastField; + + /** + * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their + * {@link MethodWriter#mv} field. This field stores the first element of this list. + */ + private MethodWriter firstMethod; + + /** + * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their + * {@link MethodWriter#mv} field. This field stores the last element of this list. + */ + private MethodWriter lastMethod; + + /** The number_of_classes field of the InnerClasses attribute, or 0. */ + private int numberOfInnerClasses; + + /** The 'classes' array of the InnerClasses attribute, or {@literal null}. */ + private ByteVector innerClasses; + + /** The class_index field of the EnclosingMethod attribute, or 0. */ + private int enclosingClassIndex; + + /** The method_index field of the EnclosingMethod attribute. */ + private int enclosingMethodIndex; + + /** The signature_index field of the Signature attribute, or 0. */ + private int signatureIndex; + + /** The source_file_index field of the SourceFile attribute, or 0. */ + private int sourceFileIndex; + + /** The debug_extension field of the SourceDebugExtension attribute, or {@literal null}. */ + private ByteVector debugExtension; + + /** + * The last runtime visible annotation of this class. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this class. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this class. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this class. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** The Module attribute of this class, or {@literal null}. */ + private ModuleWriter moduleWriter; + + /** The host_class_index field of the NestHost attribute, or 0. */ + private int nestHostClassIndex; + + /** The number_of_classes field of the NestMembers attribute, or 0. */ + private int numberOfNestMemberClasses; + + /** The 'classes' array of the NestMembers attribute, or {@literal null}. */ + private ByteVector nestMemberClasses; + + /** The number_of_classes field of the PermittedSubclasses attribute, or 0. */ + private int numberOfPermittedSubclasses; + + /** The 'classes' array of the PermittedSubclasses attribute, or {@literal null}. */ + private ByteVector permittedSubclasses; + + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the first + * element of this list. + */ + private RecordComponentWriter firstRecordComponent; + + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the last + * element of this list. + */ + private RecordComponentWriter lastRecordComponent; + + /** + * The first non standard attribute of this class. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #toByteArray} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + /** + * Indicates what must be automatically computed in {@link MethodWriter}. Must be one of {@link + * MethodWriter#COMPUTE_NOTHING}, {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL}, {@link + * MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}. + */ + private int compute; + + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags option flags that can be used to modify the default behavior of this class. Must + * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + this(null, flags); + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for "mostly add" bytecode + * transformations. These optimizations are the following: + * + *

    + *
  • The constant pool and bootstrap methods from the original class are copied as is in the + * new class, which saves time. New constant pool entries and new bootstrap methods will be + * added at the end if necessary, but unused constant pool entries or bootstrap methods + * won't be removed. + *
  • Methods that are not transformed are copied as is in the new class, directly from the + * original class bytecode (i.e. without emitting visit events for all the method + * instructions), which saves a lot of time. Untransformed methods are detected by + * the fact that the {@link ClassReader} receives {@link MethodVisitor} objects that come + * from a {@link ClassWriter} (and not from any other {@link ClassVisitor} instance). + *
+ * + * @param classReader the {@link ClassReader} used to read the original class. It will be used to + * copy the entire constant pool and bootstrap methods from the original class and also to + * copy other fragments of original bytecode where applicable. + * @param flags option flags that can be used to modify the default behavior of this class.Must be + * zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. These option flags do + * not affect methods that are copied as is in the new class. This means that neither the + * maximum stack size nor the stack frames will be computed for these methods. + */ + public ClassWriter(final ClassReader classReader, final int flags) { + super(/* latest api = */ Opcodes.ASM9); + symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); + if ((flags & COMPUTE_FRAMES) != 0) { + this.compute = MethodWriter.COMPUTE_ALL_FRAMES; + } else if ((flags & COMPUTE_MAXS) != 0) { + this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; + } else { + this.compute = MethodWriter.COMPUTE_NOTHING; + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the ClassVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public final void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + this.version = version; + this.accessFlags = access; + this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index; + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index; + } + } + if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) { + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES; + } + } + + @Override + public final void visitSource(final String file, final String debug) { + if (file != null) { + sourceFileIndex = symbolTable.addConstantUtf8(file); + } + if (debug != null) { + debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE); + } + } + + @Override + public final ModuleVisitor visitModule( + final String name, final int access, final String version) { + return moduleWriter = + new ModuleWriter( + symbolTable, + symbolTable.addConstantModule(name).index, + access, + version == null ? 0 : symbolTable.addConstantUtf8(version)); + } + + @Override + public final void visitNestHost(final String nestHost) { + nestHostClassIndex = symbolTable.addConstantClass(nestHost).index; + } + + @Override + public final void visitOuterClass( + final String owner, final String name, final String descriptor) { + enclosingClassIndex = symbolTable.addConstantClass(owner).index; + if (name != null && descriptor != null) { + enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor); + } + } + + @Override + public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public final AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public final void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public final void visitNestMember(final String nestMember) { + if (nestMemberClasses == null) { + nestMemberClasses = new ByteVector(); + } + ++numberOfNestMemberClasses; + nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index); + } + + @Override + public final void visitPermittedSubclass(final String permittedSubclass) { + if (permittedSubclasses == null) { + permittedSubclasses = new ByteVector(); + } + ++numberOfPermittedSubclasses; + permittedSubclasses.putShort(symbolTable.addConstantClass(permittedSubclass).index); + } + + @Override + public final void visitInnerClass( + final String name, final String outerName, final String innerName, final int access) { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + // Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the constant_pool table + // which represents a class or interface C that is not a package member must have exactly one + // corresponding entry in the classes array". To avoid duplicates we keep track in the info + // field of the Symbol of each CONSTANT_Class_info entry C whether an inner class entry has + // already been added for C. If so, we store the index of this inner class entry (plus one) in + // the info field. This trick allows duplicate detection in O(1) time. + Symbol nameSymbol = symbolTable.addConstantClass(name); + if (nameSymbol.info == 0) { + ++numberOfInnerClasses; + innerClasses.putShort(nameSymbol.index); + innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index); + innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName)); + innerClasses.putShort(access); + nameSymbol.info = numberOfInnerClasses; + } + // Else, compare the inner classes entry nameSymbol.info - 1 with the arguments of this method + // and throw an exception if there is a difference? + } + + @Override + public final RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + RecordComponentWriter recordComponentWriter = + new RecordComponentWriter(symbolTable, name, descriptor, signature); + if (firstRecordComponent == null) { + firstRecordComponent = recordComponentWriter; + } else { + lastRecordComponent.delegate = recordComponentWriter; + } + return lastRecordComponent = recordComponentWriter; + } + + @Override + public final FieldVisitor visitField( + final int access, + final String name, + final String descriptor, + final String signature, + final Object value) { + FieldWriter fieldWriter = + new FieldWriter(symbolTable, access, name, descriptor, signature, value); + if (firstField == null) { + firstField = fieldWriter; + } else { + lastField.fv = fieldWriter; + } + return lastField = fieldWriter; + } + + @Override + public final MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + MethodWriter methodWriter = + new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute); + if (firstMethod == null) { + firstMethod = methodWriter; + } else { + lastMethod.mv = methodWriter; + } + return lastMethod = methodWriter; + } + + @Override + public final void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Other public methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the content of the class file that was built by this ClassWriter. + * + * @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter. + * @throws ClassTooLargeException if the constant pool of the class is too large. + * @throws MethodTooLargeException if the Code attribute of a method is too large. + */ + public byte[] toByteArray() { + // First step: compute the size in bytes of the ClassFile structure. + // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version, + // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count, + // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too. + int size = 24 + 2 * interfaceCount; + int fieldsCount = 0; + FieldWriter fieldWriter = firstField; + while (fieldWriter != null) { + ++fieldsCount; + size += fieldWriter.computeFieldInfoSize(); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + int methodsCount = 0; + MethodWriter methodWriter = firstMethod; + while (methodWriter != null) { + ++methodsCount; + size += methodWriter.computeMethodInfoSize(); + methodWriter = (MethodWriter) methodWriter.mv; + } + + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (innerClasses != null) { + ++attributesCount; + size += 8 + innerClasses.length; + symbolTable.addConstantUtf8(Constants.INNER_CLASSES); + } + if (enclosingClassIndex != 0) { + ++attributesCount; + size += 10; + symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD); + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { + ++attributesCount; + size += 6; + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + } + if (signatureIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.SIGNATURE); + } + if (sourceFileIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.SOURCE_FILE); + } + if (debugExtension != null) { + ++attributesCount; + size += 6 + debugExtension.length; + symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributesCount; + size += 6; + symbolTable.addConstantUtf8(Constants.DEPRECATED); + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (symbolTable.computeBootstrapMethodsSize() > 0) { + ++attributesCount; + size += symbolTable.computeBootstrapMethodsSize(); + } + if (moduleWriter != null) { + attributesCount += moduleWriter.getAttributeCount(); + size += moduleWriter.computeAttributesSize(); + } + if (nestHostClassIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.NEST_HOST); + } + if (nestMemberClasses != null) { + ++attributesCount; + size += 8 + nestMemberClasses.length; + symbolTable.addConstantUtf8(Constants.NEST_MEMBERS); + } + if (permittedSubclasses != null) { + ++attributesCount; + size += 8 + permittedSubclasses.length; + symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES); + } + int recordComponentCount = 0; + int recordSize = 0; + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + ++recordComponentCount; + recordSize += recordComponentWriter.computeRecordComponentInfoSize(); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + ++attributesCount; + size += 8 + recordSize; + symbolTable.addConstantUtf8(Constants.RECORD); + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + size += firstAttribute.computeAttributesSize(symbolTable); + } + // IMPORTANT: this must be the last part of the ClassFile size computation, because the previous + // statements can add attribute names to the constant pool, thereby changing its size! + size += symbolTable.getConstantPoolLength(); + int constantPoolCount = symbolTable.getConstantPoolCount(); + if (constantPoolCount > 0xFFFF) { + throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount); + } + + // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in + // dynamic resizes) and fill it with the ClassFile content. + ByteVector result = new ByteVector(size); + result.putInt(0xCAFEBABE).putInt(version); + symbolTable.putConstantPool(result); + int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0; + result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass); + result.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + result.putShort(interfaces[i]); + } + result.putShort(fieldsCount); + fieldWriter = firstField; + while (fieldWriter != null) { + fieldWriter.putFieldInfo(result); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + result.putShort(methodsCount); + boolean hasFrames = false; + boolean hasAsmInstructions = false; + methodWriter = firstMethod; + while (methodWriter != null) { + hasFrames |= methodWriter.hasFrames(); + hasAsmInstructions |= methodWriter.hasAsmInstructions(); + methodWriter.putMethodInfo(result); + methodWriter = (MethodWriter) methodWriter.mv; + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + result.putShort(attributesCount); + if (innerClasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES)) + .putInt(innerClasses.length + 2) + .putShort(numberOfInnerClasses) + .putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (enclosingClassIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD)) + .putInt(4) + .putShort(enclosingClassIndex) + .putShort(enclosingMethodIndex); + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { + result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if (sourceFileIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE)) + .putInt(2) + .putShort(sourceFileIndex); + } + if (debugExtension != null) { + int length = debugExtension.length; + result + .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION)) + .putInt(length) + .putByteArray(debugExtension.data, 0, length); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + result); + symbolTable.putBootstrapMethods(result); + if (moduleWriter != null) { + moduleWriter.putAttributes(result); + } + if (nestHostClassIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST)) + .putInt(2) + .putShort(nestHostClassIndex); + } + if (nestMemberClasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS)) + .putInt(nestMemberClasses.length + 2) + .putShort(numberOfNestMemberClasses) + .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length); + } + if (permittedSubclasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES)) + .putInt(permittedSubclasses.length + 2) + .putShort(numberOfPermittedSubclasses) + .putByteArray(permittedSubclasses.data, 0, permittedSubclasses.length); + } + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.RECORD)) + .putInt(recordSize + 2) + .putShort(recordComponentCount); + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.putRecordComponentInfo(result); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + } + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, result); + } + + // Third step: replace the ASM specific instructions, if any. + if (hasAsmInstructions) { + return replaceAsmInstructions(result.data, hasFrames); + } else { + return result.data; + } + } + + /** + * Returns the equivalent of the given class file, with the ASM specific instructions replaced + * with standard ones. This is done with a ClassReader -> ClassWriter round trip. + * + * @param classFile a class file containing ASM specific instructions, generated by this + * ClassWriter. + * @param hasFrames whether there is at least one stack map frames in 'classFile'. + * @return an equivalent of 'classFile', with the ASM specific instructions replaced with standard + * ones. + */ + private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) { + final Attribute[] attributes = getAttributePrototypes(); + firstField = null; + lastField = null; + firstMethod = null; + lastMethod = null; + lastRuntimeVisibleAnnotation = null; + lastRuntimeInvisibleAnnotation = null; + lastRuntimeVisibleTypeAnnotation = null; + lastRuntimeInvisibleTypeAnnotation = null; + moduleWriter = null; + nestHostClassIndex = 0; + numberOfNestMemberClasses = 0; + nestMemberClasses = null; + numberOfPermittedSubclasses = 0; + permittedSubclasses = null; + firstRecordComponent = null; + lastRecordComponent = null; + firstAttribute = null; + compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING; + new ClassReader(classFile, 0, /* checkClassVersion = */ false) + .accept( + this, + attributes, + (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS); + return toByteArray(); + } + + /** + * Returns the prototypes of the attributes used by this class, its fields and its methods. + * + * @return the prototypes of the attributes used by this class, its fields and its methods. + */ + private Attribute[] getAttributePrototypes() { + Attribute.Set attributePrototypes = new Attribute.Set(); + attributePrototypes.addAttributes(firstAttribute); + FieldWriter fieldWriter = firstField; + while (fieldWriter != null) { + fieldWriter.collectAttributePrototypes(attributePrototypes); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + MethodWriter methodWriter = firstMethod; + while (methodWriter != null) { + methodWriter.collectAttributePrototypes(attributePrototypes); + methodWriter = (MethodWriter) methodWriter.mv; + } + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.collectAttributePrototypes(attributePrototypes); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + return attributePrototypes.toArray(); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: constant pool management for Attribute sub classes + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a number or string constant to the constant pool of the class being build. Does nothing if + * the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param value the value of the constant to be added to the constant pool. This parameter must be + * an {@link Integer}, a {@link Float}, a {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the given value. + */ + public int newConst(final Object value) { + return symbolTable.addConstant(value).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does nothing if the constant + * pool already contains a similar item. This method is intended for {@link Attribute} sub + * classes, and is normally not needed by class generators or adapters. + * + * @param value the String value. + * @return the index of a new or already existing UTF8 item. + */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). + public int newUTF8(final String value) { + return symbolTable.addConstantUtf8(value); + } + + /** + * Adds a class reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param value the internal name of the class. + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return symbolTable.addConstantClass(value).index; + } + + /** + * Adds a method type reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param methodDescriptor method descriptor of the method type. + * @return the index of a new or already existing method type reference item. + */ + public int newMethodType(final String methodDescriptor) { + return symbolTable.addConstantMethodType(methodDescriptor).index; + } + + /** + * Adds a module reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param moduleName name of the module. + * @return the index of a new or already existing module reference item. + */ + public int newModule(final String moduleName) { + return symbolTable.addConstantModule(moduleName).index; + } + + /** + * Adds a package reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param packageName name of the package in its internal form. + * @return the index of a new or already existing module reference item. + */ + public int newPackage(final String packageName) { + return symbolTable.addConstantPackage(packageName).index; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool + * already contains a similar item. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link + * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class. + * @param name the name of the field or method. + * @param descriptor the descriptor of the field or method. + * @return the index of a new or already existing method type reference item. + * @deprecated this method is superseded by {@link #newHandle(int, String, String, String, + * boolean)}. + */ + @Deprecated + public int newHandle( + final int tag, final String owner, final String name, final String descriptor) { + return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool + * already contains a similar item. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link + * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class. + * @param name the name of the field or method. + * @param descriptor the descriptor of the field or method. + * @param isInterface true if the owner is an interface. + * @return the index of a new or already existing method type reference item. + */ + public int newHandle( + final int tag, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index; + } + + /** + * Adds a dynamic constant reference to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param name name of the invoked method. + * @param descriptor field descriptor of the constant type. + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. + * @return the index of a new or already existing dynamic constant reference item. + */ + public int newConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + return symbolTable.addConstantDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) + .index; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being build. Does nothing if + * the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param name name of the invoked method. + * @param descriptor descriptor of the invoke method. + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. + * @return the index of a new or already existing invokedynamic reference item. + */ + public int newInvokeDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + return symbolTable.addConstantInvokeDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) + .index; + } + + /** + * Adds a field reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param owner the internal name of the field's owner class. + * @param name the field's name. + * @param descriptor the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String descriptor) { + return symbolTable.addConstantFieldref(owner, name, descriptor).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param owner the internal name of the method's owner class. + * @param name the method's name. + * @param descriptor the method's descriptor. + * @param isInterface {@literal true} if {@code owner} is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod( + final String owner, final String name, final String descriptor, final boolean isInterface) { + return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param name a name. + * @param descriptor a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String descriptor) { + return symbolTable.addConstantNameAndType(name, descriptor); + } + + // ----------------------------------------------------------------------------------------------- + // Default method to compute common super classes when computing stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the common super type of the two given types. The default implementation of this method + * loads the two given classes and uses the java.lang.Class methods to find the common + * super class. It can be overridden to compute this common super type in other ways, in + * particular without actually loading any class, or to take into account the class that is + * currently being generated by this ClassWriter, which can of course not be loaded since it is + * under construction. + * + * @param type1 the internal name of a class. + * @param type2 the internal name of another class. + * @return the internal name of the common super class of the two given classes. + */ + protected String getCommonSuperClass(final String type1, final String type2) { + ClassLoader classLoader = getClassLoader(); + Class class1; + try { + class1 = Class.forName(type1.replace('/', '.'), false, classLoader); + } catch (ClassNotFoundException e) { + throw new TypeNotPresentException(type1, e); + } + Class class2; + try { + class2 = Class.forName(type2.replace('/', '.'), false, classLoader); + } catch (ClassNotFoundException e) { + throw new TypeNotPresentException(type2, e); + } + if (class1.isAssignableFrom(class2)) { + return type1; + } + if (class2.isAssignableFrom(class1)) { + return type2; + } + if (class1.isInterface() || class2.isInterface()) { + return "java/lang/Object"; + } else { + do { + class1 = class1.getSuperclass(); + } while (!class1.isAssignableFrom(class2)); + return class1.getName().replace('.', '/'); + } + } + + /** + * Returns the {@link ClassLoader} to be used by the default implementation of {@link + * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by + * default. + * + * @return ClassLoader + */ + protected ClassLoader getClassLoader() { + return getClass().getClassLoader(); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ConstantDynamic.java b/native/java/jpype.jvm.asm/org/jpype/asm/ConstantDynamic.java new file mode 100644 index 000000000..4814d0aab --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ConstantDynamic.java @@ -0,0 +1,178 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +import java.util.Arrays; + +/** + * A constant whose value is computed at runtime, with a bootstrap method. + * + * @author Remi Forax + */ +public final class ConstantDynamic { + + /** The constant name (can be arbitrary). */ + private final String name; + + /** The constant type (must be a field descriptor). */ + private final String descriptor; + + /** The bootstrap method to use to compute the constant value at runtime. */ + private final Handle bootstrapMethod; + + /** + * The arguments to pass to the bootstrap method, in order to compute the constant value at + * runtime. + */ + private final Object[] bootstrapMethodArguments; + + /** + * Constructs a new {@link ConstantDynamic}. + * + * @param name the constant name (can be arbitrary). + * @param descriptor the constant type (must be a field descriptor). + * @param bootstrapMethod the bootstrap method to use to compute the constant value at runtime. + * @param bootstrapMethodArguments the arguments to pass to the bootstrap method, in order to + * compute the constant value at runtime. + */ + public ConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethod, + final Object... bootstrapMethodArguments) { + this.name = name; + this.descriptor = descriptor; + this.bootstrapMethod = bootstrapMethod; + this.bootstrapMethodArguments = bootstrapMethodArguments; + } + + /** + * Returns the name of this constant. + * + * @return the name of this constant. + */ + public String getName() { + return name; + } + + /** + * Returns the type of this constant. + * + * @return the type of this constant, as a field descriptor. + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Returns the bootstrap method used to compute the value of this constant. + * + * @return the bootstrap method used to compute the value of this constant. + */ + public Handle getBootstrapMethod() { + return bootstrapMethod; + } + + /** + * Returns the number of arguments passed to the bootstrap method, in order to compute the value + * of this constant. + * + * @return the number of arguments passed to the bootstrap method, in order to compute the value + * of this constant. + */ + public int getBootstrapMethodArgumentCount() { + return bootstrapMethodArguments.length; + } + + /** + * Returns an argument passed to the bootstrap method, in order to compute the value of this + * constant. + * + * @param index an argument index, between 0 and {@link #getBootstrapMethodArgumentCount()} + * (exclusive). + * @return the argument passed to the bootstrap method, with the given index. + */ + public Object getBootstrapMethodArgument(final int index) { + return bootstrapMethodArguments[index]; + } + + /** + * Returns the arguments to pass to the bootstrap method, in order to compute the value of this + * constant. WARNING: this array must not be modified, and must not be returned to the user. + * + * @return the arguments to pass to the bootstrap method, in order to compute the value of this + * constant. + */ + Object[] getBootstrapMethodArgumentsUnsafe() { + return bootstrapMethodArguments; + } + + /** + * Returns the size of this constant. + * + * @return the size of this constant, i.e., 2 for {@code long} and {@code double}, 1 otherwise. + */ + public int getSize() { + char firstCharOfDescriptor = descriptor.charAt(0); + return (firstCharOfDescriptor == 'J' || firstCharOfDescriptor == 'D') ? 2 : 1; + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ConstantDynamic)) { + return false; + } + ConstantDynamic constantDynamic = (ConstantDynamic) object; + return name.equals(constantDynamic.name) + && descriptor.equals(constantDynamic.descriptor) + && bootstrapMethod.equals(constantDynamic.bootstrapMethod) + && Arrays.equals(bootstrapMethodArguments, constantDynamic.bootstrapMethodArguments); + } + + @Override + public int hashCode() { + return name.hashCode() + ^ Integer.rotateLeft(descriptor.hashCode(), 8) + ^ Integer.rotateLeft(bootstrapMethod.hashCode(), 16) + ^ Integer.rotateLeft(Arrays.hashCode(bootstrapMethodArguments), 24); + } + + @Override + public String toString() { + return name + + " : " + + descriptor + + ' ' + + bootstrapMethod + + ' ' + + Arrays.toString(bootstrapMethodArguments); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Constants.java b/native/java/jpype.jvm.asm/org/jpype/asm/Constants.java new file mode 100644 index 000000000..3e75fcfe2 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Constants.java @@ -0,0 +1,221 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Pattern; + +/** + * Defines additional JVM opcodes, access flags and constants which are not part of the ASM public + * API. + * + * @see JVMS 6 + * @author Eric Bruneton + */ +final class Constants { + + // The ClassFile attribute names, in the order they are defined in + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300. + + static final String CONSTANT_VALUE = "ConstantValue"; + static final String CODE = "Code"; + static final String STACK_MAP_TABLE = "StackMapTable"; + static final String EXCEPTIONS = "Exceptions"; + static final String INNER_CLASSES = "InnerClasses"; + static final String ENCLOSING_METHOD = "EnclosingMethod"; + static final String SYNTHETIC = "Synthetic"; + static final String SIGNATURE = "Signature"; + static final String SOURCE_FILE = "SourceFile"; + static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension"; + static final String LINE_NUMBER_TABLE = "LineNumberTable"; + static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable"; + static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; + static final String DEPRECATED = "Deprecated"; + static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"; + static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations"; + static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations"; + static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = + "RuntimeInvisibleParameterAnnotations"; + static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations"; + static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations"; + static final String ANNOTATION_DEFAULT = "AnnotationDefault"; + static final String BOOTSTRAP_METHODS = "BootstrapMethods"; + static final String METHOD_PARAMETERS = "MethodParameters"; + static final String MODULE = "Module"; + static final String MODULE_PACKAGES = "ModulePackages"; + static final String MODULE_MAIN_CLASS = "ModuleMainClass"; + static final String NEST_HOST = "NestHost"; + static final String NEST_MEMBERS = "NestMembers"; + static final String PERMITTED_SUBCLASSES = "PermittedSubclasses"; + static final String RECORD = "Record"; + + // ASM specific access flags. + // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard + // access flags, and also to make sure that these flags are automatically filtered out when + // written in class files (because access flags are stored using 16 bits only). + + static final int ACC_CONSTRUCTOR = 0x40000; // method access flag. + + // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}. + + /** + * A frame inserted between already existing frames. This internal stack map frame type (in + * addition to the ones declared in {@link Opcodes}) can only be used if the frame content can be + * computed from the previous existing frame and from the instructions between this existing frame + * and the inserted one, without any knowledge of the type hierarchy. This kind of frame is only + * used when an unconditional jump is inserted in a method while expanding an ASM specific + * instruction. Keep in sync with Opcodes.java. + */ + static final int F_INSERT = 256; + + // The JVM opcode values which are not part of the ASM public API. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html. + + static final int LDC_W = 19; + static final int LDC2_W = 20; + static final int ILOAD_0 = 26; + static final int ILOAD_1 = 27; + static final int ILOAD_2 = 28; + static final int ILOAD_3 = 29; + static final int LLOAD_0 = 30; + static final int LLOAD_1 = 31; + static final int LLOAD_2 = 32; + static final int LLOAD_3 = 33; + static final int FLOAD_0 = 34; + static final int FLOAD_1 = 35; + static final int FLOAD_2 = 36; + static final int FLOAD_3 = 37; + static final int DLOAD_0 = 38; + static final int DLOAD_1 = 39; + static final int DLOAD_2 = 40; + static final int DLOAD_3 = 41; + static final int ALOAD_0 = 42; + static final int ALOAD_1 = 43; + static final int ALOAD_2 = 44; + static final int ALOAD_3 = 45; + static final int ISTORE_0 = 59; + static final int ISTORE_1 = 60; + static final int ISTORE_2 = 61; + static final int ISTORE_3 = 62; + static final int LSTORE_0 = 63; + static final int LSTORE_1 = 64; + static final int LSTORE_2 = 65; + static final int LSTORE_3 = 66; + static final int FSTORE_0 = 67; + static final int FSTORE_1 = 68; + static final int FSTORE_2 = 69; + static final int FSTORE_3 = 70; + static final int DSTORE_0 = 71; + static final int DSTORE_1 = 72; + static final int DSTORE_2 = 73; + static final int DSTORE_3 = 74; + static final int ASTORE_0 = 75; + static final int ASTORE_1 = 76; + static final int ASTORE_2 = 77; + static final int ASTORE_3 = 78; + static final int WIDE = 196; + static final int GOTO_W = 200; + static final int JSR_W = 201; + + // Constants to convert between normal and wide jump instructions. + + // The delta between the GOTO_W and JSR_W opcodes and GOTO and JUMP. + static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - Opcodes.GOTO; + + // Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa. + + // The delta between the ASM_IFEQ, ..., ASM_IF_ACMPNE, ASM_GOTO and ASM_JSR opcodes + // and IFEQ, ..., IF_ACMPNE, GOTO and JSR. + static final int ASM_OPCODE_DELTA = 49; + + // The delta between the ASM_IFNULL and ASM_IFNONNULL opcodes and IFNULL and IFNONNULL. + static final int ASM_IFNULL_OPCODE_DELTA = 20; + + // ASM specific opcodes, used for long forward jump instructions. + + static final int ASM_IFEQ = Opcodes.IFEQ + ASM_OPCODE_DELTA; + static final int ASM_IFNE = Opcodes.IFNE + ASM_OPCODE_DELTA; + static final int ASM_IFLT = Opcodes.IFLT + ASM_OPCODE_DELTA; + static final int ASM_IFGE = Opcodes.IFGE + ASM_OPCODE_DELTA; + static final int ASM_IFGT = Opcodes.IFGT + ASM_OPCODE_DELTA; + static final int ASM_IFLE = Opcodes.IFLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPEQ = Opcodes.IF_ICMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPNE = Opcodes.IF_ICMPNE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLT = Opcodes.IF_ICMPLT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGE = Opcodes.IF_ICMPGE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGT = Opcodes.IF_ICMPGT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLE = Opcodes.IF_ICMPLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPEQ = Opcodes.IF_ACMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPNE = Opcodes.IF_ACMPNE + ASM_OPCODE_DELTA; + static final int ASM_GOTO = Opcodes.GOTO + ASM_OPCODE_DELTA; + static final int ASM_JSR = Opcodes.JSR + ASM_OPCODE_DELTA; + static final int ASM_IFNULL = Opcodes.IFNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_IFNONNULL = Opcodes.IFNONNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_GOTO_W = 220; + + private Constants() {} + + static void checkAsmExperimental(final Object caller) { + Class callerClass = caller.getClass(); + String internalName = callerClass.getName().replace('.', '/'); + if (!isWhitelisted(internalName)) { + checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class")); + } + } + + static boolean isWhitelisted(final String internalName) { + if (!internalName.startsWith("org/objectweb/asm/")) { + return false; + } + String member = "(Annotation|Class|Field|Method|Module|RecordComponent|Signature)"; + return internalName.contains("Test$") + || Pattern.matches( + "org/objectweb/asm/util/Trace" + member + "Visitor(\\$.*)?", internalName) + || Pattern.matches( + "org/objectweb/asm/util/Check" + member + "Adapter(\\$.*)?", internalName); + } + + static void checkIsPreview(final InputStream classInputStream) { + if (classInputStream == null) { + throw new IllegalStateException("Bytecode not available, can't check class version"); + } + int minorVersion; + try (DataInputStream callerClassStream = new DataInputStream(classInputStream); ) { + callerClassStream.readInt(); + minorVersion = callerClassStream.readUnsignedShort(); + } catch (IOException ioe) { + throw new IllegalStateException("I/O error, can't check class version", ioe); + } + if (minorVersion != 0xFFFF) { + throw new IllegalStateException( + "ASM9_EXPERIMENTAL can only be used by classes compiled with --enable-preview"); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Context.java b/native/java/jpype.jvm.asm/org/jpype/asm/Context.java new file mode 100644 index 000000000..09e648997 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Context.java @@ -0,0 +1,137 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package org.jpype.asm; + +/** + * Information about a class being parsed in a {@link ClassReader}. + * + * @author Eric Bruneton + */ +final class Context { + + /** The prototypes of the attributes that must be parsed in this class. */ + Attribute[] attributePrototypes; + + /** + * The options used to parse this class. One or more of {@link ClassReader#SKIP_CODE}, {@link + * ClassReader#SKIP_DEBUG}, {@link ClassReader#SKIP_FRAMES}, {@link ClassReader#EXPAND_FRAMES} or + * {@link ClassReader#EXPAND_ASM_INSNS}. + */ + int parsingOptions; + + /** The buffer used to read strings in the constant pool. */ + char[] charBuffer; + + // Information about the current method, i.e. the one read in the current (or latest) call + // to {@link ClassReader#readMethod()}. + + /** The access flags of the current method. */ + int currentMethodAccessFlags; + + /** The name of the current method. */ + String currentMethodName; + + /** The descriptor of the current method. */ + String currentMethodDescriptor; + + /** + * The labels of the current method, indexed by bytecode offset (only bytecode offsets for which a + * label is needed have a non null associated Label). + */ + Label[] currentMethodLabels; + + // Information about the current type annotation target, i.e. the one read in the current + // (or latest) call to {@link ClassReader#readAnnotationTarget()}. + + /** + * The target_type and target_info of the current type annotation target, encoded as described in + * {@link TypeReference}. + */ + int currentTypeAnnotationTarget; + + /** The target_path of the current type annotation target. */ + TypePath currentTypeAnnotationTargetPath; + + /** The start of each local variable range in the current local variable annotation. */ + Label[] currentLocalVariableAnnotationRangeStarts; + + /** The end of each local variable range in the current local variable annotation. */ + Label[] currentLocalVariableAnnotationRangeEnds; + + /** + * The local variable index of each local variable range in the current local variable annotation. + */ + int[] currentLocalVariableAnnotationRangeIndices; + + // Information about the current stack map frame, i.e. the one read in the current (or latest) + // call to {@link ClassReader#readFrame()}. + + /** The bytecode offset of the current stack map frame. */ + int currentFrameOffset; + + /** + * The type of the current stack map frame. One of {@link Opcodes#F_FULL}, {@link + * Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or {@link Opcodes#F_SAME1}. + */ + int currentFrameType; + + /** + * The number of local variable types in the current stack map frame. Each type is represented + * with a single array element (even long and double). + */ + int currentFrameLocalCount; + + /** + * The delta number of local variable types in the current stack map frame (each type is + * represented with a single array element - even long and double). This is the number of local + * variable types in this frame, minus the number of local variable types in the previous frame. + */ + int currentFrameLocalCountDelta; + + /** + * The types of the local variables in the current stack map frame. Each type is represented with + * a single array element (even long and double), using the format described in {@link + * MethodVisitor#visitFrame}. Depending on {@link #currentFrameType}, this contains the types of + * all the local variables, or only those of the additional ones (compared to the previous frame). + */ + Object[] currentFrameLocalTypes; + + /** + * The number stack element types in the current stack map frame. Each type is represented with a + * single array element (even long and double). + */ + int currentFrameStackCount; + + /** + * The types of the stack elements in the current stack map frame. Each type is represented with a + * single array element (even long and double), using the format described in {@link + * MethodVisitor#visitFrame}. + */ + Object[] currentFrameStackTypes; +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/CurrentFrame.java b/native/java/jpype.jvm.asm/org/jpype/asm/CurrentFrame.java new file mode 100644 index 000000000..a818e9545 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/CurrentFrame.java @@ -0,0 +1,56 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package org.jpype.asm; + +/** + * Information about the input stack map frame at the "current" instruction of a method. This is + * implemented as a Frame subclass for a "basic block" containing only one instruction. + * + * @author Eric Bruneton + */ +final class CurrentFrame extends Frame { + + CurrentFrame(final Label owner) { + super(owner); + } + + /** + * Sets this CurrentFrame to the input stack map frame of the next "current" instruction, i.e. the + * instruction just after the given one. It is assumed that the value of this object when this + * method is called is the stack map frame status just before the given instruction is executed. + */ + @Override + void execute( + final int opcode, final int arg, final Symbol symbolArg, final SymbolTable symbolTable) { + super.execute(opcode, arg, symbolArg, symbolTable); + Frame successor = new Frame(null); + merge(symbolTable, successor, 0); + copyFrom(successor); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Edge.java b/native/java/jpype.jvm.asm/org/jpype/asm/Edge.java new file mode 100644 index 000000000..8008755b1 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Edge.java @@ -0,0 +1,91 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * An edge in the control flow graph of a method. Each node of this graph is a basic block, + * represented with the Label corresponding to its first instruction. Each edge goes from one node + * to another, i.e. from one basic block to another (called the predecessor and successor blocks, + * respectively). An edge corresponds either to a jump or ret instruction or to an exception + * handler. + * + * @see Label + * @author Eric Bruneton + */ +final class Edge { + + /** + * A control flow graph edge corresponding to a jump or ret instruction. Only used with {@link + * ClassWriter#COMPUTE_FRAMES}. + */ + static final int JUMP = 0; + + /** + * A control flow graph edge corresponding to an exception handler. Only used with {@link + * ClassWriter#COMPUTE_MAXS}. + */ + static final int EXCEPTION = 0x7FFFFFFF; + + /** + * Information about this control flow graph edge. + * + *
    + *
  • If {@link ClassWriter#COMPUTE_MAXS} is used, this field contains either a stack size + * delta (for an edge corresponding to a jump instruction), or the value EXCEPTION (for an + * edge corresponding to an exception handler). The stack size delta is the stack size just + * after the jump instruction, minus the stack size at the beginning of the predecessor + * basic block, i.e. the one containing the jump instruction. + *
  • If {@link ClassWriter#COMPUTE_FRAMES} is used, this field contains either the value JUMP + * (for an edge corresponding to a jump instruction), or the index, in the {@link + * ClassWriter} type table, of the exception type that is handled (for an edge corresponding + * to an exception handler). + *
+ */ + final int info; + + /** The successor block of this control flow graph edge. */ + final Label successor; + + /** + * The next edge in the list of outgoing edges of a basic block. See {@link Label#outgoingEdges}. + */ + Edge nextEdge; + + /** + * Constructs a new Edge. + * + * @param info see {@link #info}. + * @param successor see {@link #successor}. + * @param nextEdge see {@link #nextEdge}. + */ + Edge(final int info, final Label successor, final Edge nextEdge) { + this.info = info; + this.successor = successor; + this.nextEdge = nextEdge; + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/FieldVisitor.java b/native/java/jpype.jvm.asm/org/jpype/asm/FieldVisitor.java new file mode 100644 index 000000000..aa45a290d --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/FieldVisitor.java @@ -0,0 +1,145 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A visitor to visit a Java field. The methods of this class must be called in the following order: + * ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code + * visitEnd}. + * + * @author Eric Bruneton + */ +public abstract class FieldVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link + * Opcodes#ASM8} or {@link Opcodes#ASM9}. + */ + protected final int api; + + /** The field visitor to which this visitor must delegate method calls. May be {@literal null}. */ + protected FieldVisitor fv; + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link + * Opcodes#ASM8} or {@link Opcodes#ASM9}. + */ + public FieldVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7} or {@link + * Opcodes#ASM8}. + * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be + * null. + */ + public FieldVisitor(final int api, final FieldVisitor fieldVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); + } + this.api = api; + this.fv = fieldVisitor; + } + + /** + * Visits an annotation of the field. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (fv != null) { + return fv.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on the type of the field. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#FIELD}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException("This feature requires ASM5"); + } + if (fv != null) { + return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the field. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (fv != null) { + fv.visitAttribute(attribute); + } + } + + /** + * Visits the end of the field. This method, which is the last one to be called, is used to inform + * the visitor that all the annotations and attributes of the field have been visited. + */ + public void visitEnd() { + if (fv != null) { + fv.visitEnd(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/FieldWriter.java b/native/java/jpype.jvm.asm/org/jpype/asm/FieldWriter.java new file mode 100644 index 000000000..640b27bf0 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/FieldWriter.java @@ -0,0 +1,284 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A {@link FieldVisitor} that generates a corresponding 'field_info' structure, as defined in the + * Java Virtual Machine Specification (JVMS). + * + * @see JVMS + * 4.5 + * @author Eric Bruneton + */ +final class FieldWriter extends FieldVisitor { + + /** Where the constants used in this FieldWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the field_info structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The access_flags field of the field_info JVMS structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the + * ClassFile structure. + */ + private final int accessFlags; + + /** The name_index field of the field_info JVMS structure. */ + private final int nameIndex; + + /** The descriptor_index field of the field_info JVMS structure. */ + private final int descriptorIndex; + + /** + * The signature_index field of the Signature attribute of this field_info, or 0 if there is no + * Signature attribute. + */ + private int signatureIndex; + + /** + * The constantvalue_index field of the ConstantValue attribute of this field_info, or 0 if there + * is no ConstantValue attribute. + */ + private int constantValueIndex; + + /** + * The last runtime visible annotation of this field. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this field. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this field. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this field. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of this field. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putFieldInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link FieldWriter}. + * + * @param symbolTable where the constants used in this FieldWriter must be stored. + * @param access the field's access flags (see {@link Opcodes}). + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be {@literal null}. + * @param constantValue the field's constant value. May be {@literal null}. + */ + FieldWriter( + final SymbolTable symbolTable, + final int access, + final String name, + final String descriptor, + final String signature, + final Object constantValue) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.accessFlags = access; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + if (constantValue != null) { + this.constantValueIndex = symbolTable.addConstant(constantValue).index; + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the FieldVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of the field_info JVMS structure generated by this FieldWriter. Also adds the + * names of the attributes of this field in the constant pool. + * + * @return the size in bytes of the field_info JVMS structure. + */ + int computeFieldInfoSize() { + // The access_flags, name_index, descriptor_index and attributes_count fields use 8 bytes. + int size = 8; + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (constantValueIndex != 0) { + // ConstantValue attributes always use 8 bytes. + symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE); + size += 8; + } + size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the field_info JVMS structure generated by this FieldWriter into the given + * ByteVector. + * + * @param output where the field_info structure must be put. + */ + void putFieldInfo(final ByteVector output) { + boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; + // Put the access_flags, name_index and descriptor_index fields. + int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0; + output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); + // Compute and put the attributes_count field. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (constantValueIndex != 0) { + ++attributesCount; + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + ++attributesCount; + } + if (signatureIndex != 0) { + ++attributesCount; + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributesCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + } + output.putShort(attributesCount); + // Put the field_info attributes. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (constantValueIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE)) + .putInt(2) + .putShort(constantValueIndex); + } + Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this field into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Frame.java b/native/java/jpype.jvm.asm/org/jpype/asm/Frame.java new file mode 100644 index 000000000..afc2de981 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Frame.java @@ -0,0 +1,1473 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * The input and output stack map frames of a basic block. + * + *

Stack map frames are computed in two steps: + * + *

    + *
  • During the visit of each instruction in MethodWriter, the state of the frame at the end of + * the current basic block is updated by simulating the action of the instruction on the + * previous state of this so called "output frame". + *
  • After all instructions have been visited, a fix point algorithm is used in MethodWriter to + * compute the "input frame" of each basic block (i.e. the stack map frame at the beginning of + * the basic block). See {@link MethodWriter#computeAllFrames}. + *
+ * + *

Output stack map frames are computed relatively to the input frame of the basic block, which + * is not yet known when output frames are computed. It is therefore necessary to be able to + * represent abstract types such as "the type at position x in the input frame locals" or "the type + * at position x from the top of the input frame stack" or even "the type at position x in the input + * frame, with y more (or less) array dimensions". This explains the rather complicated type format + * used in this class, explained below. + * + *

The local variables and the operand stack of input and output frames contain values called + * "abstract types" hereafter. An abstract type is represented with 4 fields named DIM, KIND, FLAGS + * and VALUE, packed in a single int value for better performance and memory efficiency: + * + *

+ *   =====================================
+ *   |...DIM|KIND|.F|...............VALUE|
+ *   =====================================
+ * 
+ * + *
    + *
  • the DIM field, stored in the 6 most significant bits, is a signed number of array + * dimensions (from -32 to 31, included). It can be retrieved with {@link #DIM_MASK} and a + * right shift of {@link #DIM_SHIFT}. + *
  • the KIND field, stored in 4 bits, indicates the kind of VALUE used. These 4 bits can be + * retrieved with {@link #KIND_MASK} and, without any shift, must be equal to {@link + * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link #LOCAL_KIND} + * or {@link #STACK_KIND}. + *
  • the FLAGS field, stored in 2 bits, contains up to 2 boolean flags. Currently only one flag + * is defined, namely {@link #TOP_IF_LONG_OR_DOUBLE_FLAG}. + *
  • the VALUE field, stored in the remaining 20 bits, contains either + *
      + *
    • one of the constants {@link #ITEM_TOP}, {@link #ITEM_ASM_BOOLEAN}, {@link + * #ITEM_ASM_BYTE}, {@link #ITEM_ASM_CHAR} or {@link #ITEM_ASM_SHORT}, {@link + * #ITEM_INTEGER}, {@link #ITEM_FLOAT}, {@link #ITEM_LONG}, {@link #ITEM_DOUBLE}, {@link + * #ITEM_NULL} or {@link #ITEM_UNINITIALIZED_THIS}, if KIND is equal to {@link + * #CONSTANT_KIND}. + *
    • the index of a {@link Symbol#TYPE_TAG} {@link Symbol} in the type table of a {@link + * SymbolTable}, if KIND is equal to {@link #REFERENCE_KIND}. + *
    • the index of an {@link Symbol#UNINITIALIZED_TYPE_TAG} {@link Symbol} in the type + * table of a SymbolTable, if KIND is equal to {@link #UNINITIALIZED_KIND}. + *
    • the index of a local variable in the input stack frame, if KIND is equal to {@link + * #LOCAL_KIND}. + *
    • a position relatively to the top of the stack of the input stack frame, if KIND is + * equal to {@link #STACK_KIND}, + *
    + *
+ * + *

Output frames can contain abstract types of any kind and with a positive or negative array + * dimension (and even unassigned types, represented by 0 - which does not correspond to any valid + * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND or + * UNINITIALIZED_KIND abstract types of positive or {@literal null} array dimension. In all cases + * the type table contains only internal type names (array type descriptors are forbidden - array + * dimensions must be represented through the DIM field). + * + *

The LONG and DOUBLE types are always represented by using two slots (LONG + TOP or DOUBLE + + * TOP), for local variables as well as in the operand stack. This is necessary to be able to + * simulate DUPx_y instructions, whose effect would be dependent on the concrete types represented + * by the abstract types in the stack (which are not always known). + * + * @author Eric Bruneton + */ +class Frame { + + // Constants used in the StackMapTable attribute. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.4. + + static final int SAME_FRAME = 0; + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; + static final int RESERVED = 128; + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; + static final int CHOP_FRAME = 248; + static final int SAME_FRAME_EXTENDED = 251; + static final int APPEND_FRAME = 252; + static final int FULL_FRAME = 255; + + static final int ITEM_TOP = 0; + static final int ITEM_INTEGER = 1; + static final int ITEM_FLOAT = 2; + static final int ITEM_DOUBLE = 3; + static final int ITEM_LONG = 4; + static final int ITEM_NULL = 5; + static final int ITEM_UNINITIALIZED_THIS = 6; + static final int ITEM_OBJECT = 7; + static final int ITEM_UNINITIALIZED = 8; + // Additional, ASM specific constants used in abstract types below. + private static final int ITEM_ASM_BOOLEAN = 9; + private static final int ITEM_ASM_BYTE = 10; + private static final int ITEM_ASM_CHAR = 11; + private static final int ITEM_ASM_SHORT = 12; + + // The size and offset in bits of each field of an abstract type. + + private static final int DIM_SIZE = 6; + private static final int KIND_SIZE = 4; + private static final int FLAGS_SIZE = 2; + private static final int VALUE_SIZE = 32 - DIM_SIZE - KIND_SIZE - FLAGS_SIZE; + + private static final int DIM_SHIFT = KIND_SIZE + FLAGS_SIZE + VALUE_SIZE; + private static final int KIND_SHIFT = FLAGS_SIZE + VALUE_SIZE; + private static final int FLAGS_SHIFT = VALUE_SIZE; + + // Bitmasks to get each field of an abstract type. + + private static final int DIM_MASK = ((1 << DIM_SIZE) - 1) << DIM_SHIFT; + private static final int KIND_MASK = ((1 << KIND_SIZE) - 1) << KIND_SHIFT; + private static final int VALUE_MASK = (1 << VALUE_SIZE) - 1; + + // Constants to manipulate the DIM field of an abstract type. + + /** The constant to be added to an abstract type to get one with one more array dimension. */ + private static final int ARRAY_OF = +1 << DIM_SHIFT; + + /** The constant to be added to an abstract type to get one with one less array dimension. */ + private static final int ELEMENT_OF = -1 << DIM_SHIFT; + + // Possible values for the KIND field of an abstract type. + + private static final int CONSTANT_KIND = 1 << KIND_SHIFT; + private static final int REFERENCE_KIND = 2 << KIND_SHIFT; + private static final int UNINITIALIZED_KIND = 3 << KIND_SHIFT; + private static final int LOCAL_KIND = 4 << KIND_SHIFT; + private static final int STACK_KIND = 5 << KIND_SHIFT; + + // Possible flags for the FLAGS field of an abstract type. + + /** + * A flag used for LOCAL_KIND and STACK_KIND abstract types, indicating that if the resolved, + * concrete type is LONG or DOUBLE, TOP should be used instead (because the value has been + * partially overridden with an xSTORE instruction). + */ + private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 1 << FLAGS_SHIFT; + + // Useful predefined abstract types (all the possible CONSTANT_KIND types). + + private static final int TOP = CONSTANT_KIND | ITEM_TOP; + private static final int BOOLEAN = CONSTANT_KIND | ITEM_ASM_BOOLEAN; + private static final int BYTE = CONSTANT_KIND | ITEM_ASM_BYTE; + private static final int CHAR = CONSTANT_KIND | ITEM_ASM_CHAR; + private static final int SHORT = CONSTANT_KIND | ITEM_ASM_SHORT; + private static final int INTEGER = CONSTANT_KIND | ITEM_INTEGER; + private static final int FLOAT = CONSTANT_KIND | ITEM_FLOAT; + private static final int LONG = CONSTANT_KIND | ITEM_LONG; + private static final int DOUBLE = CONSTANT_KIND | ITEM_DOUBLE; + private static final int NULL = CONSTANT_KIND | ITEM_NULL; + private static final int UNINITIALIZED_THIS = CONSTANT_KIND | ITEM_UNINITIALIZED_THIS; + + // ----------------------------------------------------------------------------------------------- + // Instance fields + // ----------------------------------------------------------------------------------------------- + + /** The basic block to which these input and output stack map frames correspond. */ + Label owner; + + /** The input stack map frame locals. This is an array of abstract types. */ + private int[] inputLocals; + + /** The input stack map frame stack. This is an array of abstract types. */ + private int[] inputStack; + + /** The output stack map frame locals. This is an array of abstract types. */ + private int[] outputLocals; + + /** The output stack map frame stack. This is an array of abstract types. */ + private int[] outputStack; + + /** + * The start of the output stack, relatively to the input stack. This offset is always negative or + * null. A null offset means that the output stack must be appended to the input stack. A -n + * offset means that the first n output stack elements must replace the top n input stack + * elements, and that the other elements must be appended to the input stack. + */ + private short outputStackStart; + + /** The index of the top stack element in {@link #outputStack}. */ + private short outputStackTop; + + /** The number of types that are initialized in the basic block. See {@link #initializations}. */ + private int initializationCount; + + /** + * The abstract types that are initialized in the basic block. A constructor invocation on an + * UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every occurrence of this + * type in the local variables and in the operand stack. This cannot be done during the first step + * of the algorithm since, during this step, the local variables and the operand stack types are + * still abstract. It is therefore necessary to store the abstract types of the constructors which + * are invoked in the basic block, in order to do this replacement during the second step of the + * algorithm, where the frames are fully computed. Note that this array can contain abstract types + * that are relative to the input locals or to the input stack. + */ + private int[] initializations; + + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new Frame. + * + * @param owner the basic block to which these input and output stack map frames correspond. + */ + Frame(final Label owner) { + this.owner = owner; + } + + /** + * Sets this frame to the value of the given frame. + * + *

WARNING: after this method is called the two frames share the same data structures. It is + * recommended to discard the given frame to avoid unexpected side effects. + * + * @param frame The new frame value. + */ + final void copyFrom(final Frame frame) { + inputLocals = frame.inputLocals; + inputStack = frame.inputStack; + outputStackStart = 0; + outputLocals = frame.outputLocals; + outputStack = frame.outputStack; + outputStackTop = frame.outputStackTop; + initializationCount = frame.initializationCount; + initializations = frame.initializations; + } + + // ----------------------------------------------------------------------------------------------- + // Static methods to get abstract types from other type formats + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the abstract type corresponding to the given public API frame element type. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param type a frame element type described using the same format as in {@link + * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or + * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating + * a NEW instruction (for uninitialized types). + * @return the abstract type corresponding to the given frame element type. + */ + static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Object type) { + if (type instanceof Integer) { + return CONSTANT_KIND | ((Integer) type).intValue(); + } else if (type instanceof String) { + String descriptor = Type.getObjectType((String) type).getDescriptor(); + return getAbstractTypeFromDescriptor(symbolTable, descriptor, 0); + } else { + return UNINITIALIZED_KIND + | symbolTable.addUninitializedType("", ((Label) type).bytecodeOffset); + } + } + + /** + * Returns the abstract type corresponding to the internal name of a class. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param internalName the internal name of a class. This must not be an array type + * descriptor. + * @return the abstract type value corresponding to the given internal name. + */ + static int getAbstractTypeFromInternalName( + final SymbolTable symbolTable, final String internalName) { + return REFERENCE_KIND | symbolTable.addType(internalName); + } + + /** + * Returns the abstract type corresponding to the given type descriptor. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param buffer a string ending with a type descriptor. + * @param offset the start offset of the type descriptor in buffer. + * @return the abstract type corresponding to the given type descriptor. + */ + private static int getAbstractTypeFromDescriptor( + final SymbolTable symbolTable, final String buffer, final int offset) { + String internalName; + switch (buffer.charAt(offset)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + internalName = buffer.substring(offset + 1, buffer.length() - 1); + return REFERENCE_KIND | symbolTable.addType(internalName); + case '[': + int elementDescriptorOffset = offset + 1; + while (buffer.charAt(elementDescriptorOffset) == '[') { + ++elementDescriptorOffset; + } + int typeValue; + switch (buffer.charAt(elementDescriptorOffset)) { + case 'Z': + typeValue = BOOLEAN; + break; + case 'C': + typeValue = CHAR; + break; + case 'B': + typeValue = BYTE; + break; + case 'S': + typeValue = SHORT; + break; + case 'I': + typeValue = INTEGER; + break; + case 'F': + typeValue = FLOAT; + break; + case 'J': + typeValue = LONG; + break; + case 'D': + typeValue = DOUBLE; + break; + case 'L': + internalName = buffer.substring(elementDescriptorOffset + 1, buffer.length() - 1); + typeValue = REFERENCE_KIND | symbolTable.addType(internalName); + break; + default: + throw new IllegalArgumentException(); + } + return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue; + default: + throw new IllegalArgumentException(); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods related to the input frame + // ----------------------------------------------------------------------------------------------- + + /** + * Sets the input frame from the given method description. This method is used to initialize the + * first frame of a method, which is implicit (i.e. not stored explicitly in the StackMapTable + * attribute). + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param access the method's access flags. + * @param descriptor the method descriptor. + * @param maxLocals the maximum number of local variables of the method. + */ + final void setInputFrameFromDescriptor( + final SymbolTable symbolTable, + final int access, + final String descriptor, + final int maxLocals) { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int inputLocalIndex = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & Constants.ACC_CONSTRUCTOR) == 0) { + inputLocals[inputLocalIndex++] = + REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName()); + } else { + inputLocals[inputLocalIndex++] = UNINITIALIZED_THIS; + } + } + for (Type argumentType : Type.getArgumentTypes(descriptor)) { + int abstractType = + getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0); + inputLocals[inputLocalIndex++] = abstractType; + if (abstractType == LONG || abstractType == DOUBLE) { + inputLocals[inputLocalIndex++] = TOP; + } + } + while (inputLocalIndex < maxLocals) { + inputLocals[inputLocalIndex++] = TOP; + } + } + + /** + * Sets the input frame from the given public API frame description. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param numLocal the number of local variables. + * @param local the local variable types, described using the same format as in {@link + * MethodVisitor#visitFrame}. + * @param numStack the number of operand stack elements. + * @param stack the operand stack types, described using the same format as in {@link + * MethodVisitor#visitFrame}. + */ + final void setInputFrameFromApiFormat( + final SymbolTable symbolTable, + final int numLocal, + final Object[] local, + final int numStack, + final Object[] stack) { + int inputLocalIndex = 0; + for (int i = 0; i < numLocal; ++i) { + inputLocals[inputLocalIndex++] = getAbstractTypeFromApiFormat(symbolTable, local[i]); + if (local[i] == Opcodes.LONG || local[i] == Opcodes.DOUBLE) { + inputLocals[inputLocalIndex++] = TOP; + } + } + while (inputLocalIndex < inputLocals.length) { + inputLocals[inputLocalIndex++] = TOP; + } + int numStackTop = 0; + for (int i = 0; i < numStack; ++i) { + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + ++numStackTop; + } + } + inputStack = new int[numStack + numStackTop]; + int inputStackIndex = 0; + for (int i = 0; i < numStack; ++i) { + inputStack[inputStackIndex++] = getAbstractTypeFromApiFormat(symbolTable, stack[i]); + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + inputStack[inputStackIndex++] = TOP; + } + } + outputStackTop = 0; + initializationCount = 0; + } + + final int getInputStackSize() { + return inputStack.length; + } + + // ----------------------------------------------------------------------------------------------- + // Methods related to the output frame + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the abstract type stored at the given local variable index in the output frame. + * + * @param localIndex the index of the local variable whose value must be returned. + * @return the abstract type stored at the given local variable index in the output frame. + */ + private int getLocal(final int localIndex) { + if (outputLocals == null || localIndex >= outputLocals.length) { + // If this local has never been assigned in this basic block, it is still equal to its value + // in the input frame. + return LOCAL_KIND | localIndex; + } else { + int abstractType = outputLocals[localIndex]; + if (abstractType == 0) { + // If this local has never been assigned in this basic block, so it is still equal to its + // value in the input frame. + abstractType = outputLocals[localIndex] = LOCAL_KIND | localIndex; + } + return abstractType; + } + } + + /** + * Replaces the abstract type stored at the given local variable index in the output frame. + * + * @param localIndex the index of the output frame local variable that must be set. + * @param abstractType the value that must be set. + */ + private void setLocal(final int localIndex, final int abstractType) { + // Create and/or resize the output local variables array if necessary. + if (outputLocals == null) { + outputLocals = new int[10]; + } + int outputLocalsLength = outputLocals.length; + if (localIndex >= outputLocalsLength) { + int[] newOutputLocals = new int[Math.max(localIndex + 1, 2 * outputLocalsLength)]; + System.arraycopy(outputLocals, 0, newOutputLocals, 0, outputLocalsLength); + outputLocals = newOutputLocals; + } + // Set the local variable. + outputLocals[localIndex] = abstractType; + } + + /** + * Pushes the given abstract type on the output frame stack. + * + * @param abstractType an abstract type. + */ + private void push(final int abstractType) { + // Create and/or resize the output stack array if necessary. + if (outputStack == null) { + outputStack = new int[10]; + } + int outputStackLength = outputStack.length; + if (outputStackTop >= outputStackLength) { + int[] newOutputStack = new int[Math.max(outputStackTop + 1, 2 * outputStackLength)]; + System.arraycopy(outputStack, 0, newOutputStack, 0, outputStackLength); + outputStack = newOutputStack; + } + // Pushes the abstract type on the output stack. + outputStack[outputStackTop++] = abstractType; + // Updates the maximum size reached by the output stack, if needed (note that this size is + // relative to the input stack size, which is not known yet). + short outputStackSize = (short) (outputStackStart + outputStackTop); + if (outputStackSize > owner.outputStackMax) { + owner.outputStackMax = outputStackSize; + } + } + + /** + * Pushes the abstract type corresponding to the given descriptor on the output frame stack. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param descriptor a type or method descriptor (in which case its return type is pushed). + */ + private void push(final SymbolTable symbolTable, final String descriptor) { + int typeDescriptorOffset = + descriptor.charAt(0) == '(' ? Type.getReturnTypeOffset(descriptor) : 0; + int abstractType = getAbstractTypeFromDescriptor(symbolTable, descriptor, typeDescriptorOffset); + if (abstractType != 0) { + push(abstractType); + if (abstractType == LONG || abstractType == DOUBLE) { + push(TOP); + } + } + } + + /** + * Pops an abstract type from the output frame stack and returns its value. + * + * @return the abstract type that has been popped from the output frame stack. + */ + private int pop() { + if (outputStackTop > 0) { + return outputStack[--outputStackTop]; + } else { + // If the output frame stack is empty, pop from the input stack. + return STACK_KIND | -(--outputStackStart); + } + } + + /** + * Pops the given number of abstract types from the output frame stack. + * + * @param elements the number of abstract types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= elements; + } else { + // If the number of elements to be popped is greater than the number of elements in the output + // stack, clear it, and pop the remaining elements from the input stack. + outputStackStart -= elements - outputStackTop; + outputStackTop = 0; + } + } + + /** + * Pops as many abstract types from the output frame stack as described by the given descriptor. + * + * @param descriptor a type or method descriptor (in which case its argument types are popped). + */ + private void pop(final String descriptor) { + char firstDescriptorChar = descriptor.charAt(0); + if (firstDescriptorChar == '(') { + pop((Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1); + } else if (firstDescriptorChar == 'J' || firstDescriptorChar == 'D') { + pop(2); + } else { + pop(1); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to handle uninitialized types + // ----------------------------------------------------------------------------------------------- + + /** + * Adds an abstract type to the list of types on which a constructor is invoked in the basic + * block. + * + * @param abstractType an abstract type on a which a constructor is invoked. + */ + private void addInitializedType(final int abstractType) { + // Create and/or resize the initializations array if necessary. + if (initializations == null) { + initializations = new int[2]; + } + int initializationsLength = initializations.length; + if (initializationCount >= initializationsLength) { + int[] newInitializations = + new int[Math.max(initializationCount + 1, 2 * initializationsLength)]; + System.arraycopy(initializations, 0, newInitializations, 0, initializationsLength); + initializations = newInitializations; + } + // Store the abstract type. + initializations[initializationCount++] = abstractType; + } + + /** + * Returns the "initialized" abstract type corresponding to the given abstract type. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param abstractType an abstract type. + * @return the REFERENCE_KIND abstract type corresponding to abstractType if it is + * UNINITIALIZED_THIS or an UNINITIALIZED_KIND abstract type for one of the types on which a + * constructor is invoked in the basic block. Otherwise returns abstractType. + */ + private int getInitializedType(final SymbolTable symbolTable, final int abstractType) { + if (abstractType == UNINITIALIZED_THIS + || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND) { + for (int i = 0; i < initializationCount; ++i) { + int initializedType = initializations[i]; + int dim = initializedType & DIM_MASK; + int kind = initializedType & KIND_MASK; + int value = initializedType & VALUE_MASK; + if (kind == LOCAL_KIND) { + initializedType = dim + inputLocals[value]; + } else if (kind == STACK_KIND) { + initializedType = dim + inputStack[inputStack.length - value]; + } + if (abstractType == initializedType) { + if (abstractType == UNINITIALIZED_THIS) { + return REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName()); + } else { + return REFERENCE_KIND + | symbolTable.addType(symbolTable.getType(abstractType & VALUE_MASK).value); + } + } + } + } + return abstractType; + } + + // ----------------------------------------------------------------------------------------------- + // Main method, to simulate the execution of each instruction on the output frame + // ----------------------------------------------------------------------------------------------- + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode the opcode of the instruction. + * @param arg the numeric operand of the instruction, if any. + * @param argSymbol the Symbol operand of the instruction, if any. + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + */ + void execute( + final int opcode, final int arg, final Symbol argSymbol, final SymbolTable symbolTable) { + // Abstract types popped from the stack or read from local variables. + int abstractType1; + int abstractType2; + int abstractType3; + int abstractType4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (argSymbol.tag) { + case Symbol.CONSTANT_INTEGER_TAG: + push(INTEGER); + break; + case Symbol.CONSTANT_LONG_TAG: + push(LONG); + push(TOP); + break; + case Symbol.CONSTANT_FLOAT_TAG: + push(FLOAT); + break; + case Symbol.CONSTANT_DOUBLE_TAG: + push(DOUBLE); + push(TOP); + break; + case Symbol.CONSTANT_CLASS_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/Class")); + break; + case Symbol.CONSTANT_STRING_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/String")); + break; + case Symbol.CONSTANT_METHOD_TYPE_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodType")); + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodHandle")); + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + push(symbolTable, argSymbol.value); + break; + default: + throw new AssertionError(); + } + break; + case Opcodes.ALOAD: + push(getLocal(arg)); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + abstractType1 = pop(); + push(abstractType1 == NULL ? abstractType1 : ELEMENT_OF + abstractType1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + abstractType1 = pop(); + setLocal(arg, abstractType1); + if (arg > 0) { + int previousLocalType = getLocal(arg - 1); + if (previousLocalType == LONG || previousLocalType == DOUBLE) { + setLocal(arg - 1, TOP); + } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND + || (previousLocalType & KIND_MASK) == STACK_KIND) { + // The type of the previous local variable is not known yet, but if it later appears + // to be LONG or DOUBLE, we should then use TOP instead. + setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + abstractType1 = pop(); + setLocal(arg, abstractType1); + setLocal(arg + 1, TOP); + if (arg > 0) { + int previousLocalType = getLocal(arg - 1); + if (previousLocalType == LONG || previousLocalType == DOUBLE) { + setLocal(arg - 1, TOP); + } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND + || (previousLocalType & KIND_MASK) == STACK_KIND) { + // The type of the previous local variable is not known yet, but if it later appears + // to be LONG or DOUBLE, we should then use TOP instead. + setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + abstractType1 = pop(); + push(abstractType1); + push(abstractType1); + break; + case Opcodes.DUP_X1: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType1); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP_X2: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + push(abstractType1); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2_X1: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2_X2: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + abstractType4 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType4); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.SWAP: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType1); + push(abstractType2); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + setLocal(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new IllegalArgumentException("JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(symbolTable, argSymbol.value); + break; + case Opcodes.PUTSTATIC: + pop(argSymbol.value); + break; + case Opcodes.GETFIELD: + pop(1); + push(symbolTable, argSymbol.value); + break; + case Opcodes.PUTFIELD: + pop(argSymbol.value); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(argSymbol.value); + if (opcode != Opcodes.INVOKESTATIC) { + abstractType1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL && argSymbol.name.charAt(0) == '<') { + addInitializedType(abstractType1); + } + } + push(symbolTable, argSymbol.value); + break; + case Opcodes.INVOKEDYNAMIC: + pop(argSymbol.value); + push(symbolTable, argSymbol.value); + break; + case Opcodes.NEW: + push(UNINITIALIZED_KIND | symbolTable.addUninitializedType(argSymbol.value, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); + break; + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); + break; + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); + break; + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); + break; + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); + break; + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); + break; + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); + break; + case Opcodes.T_LONG: + push(ARRAY_OF | LONG); + break; + default: + throw new IllegalArgumentException(); + } + break; + case Opcodes.ANEWARRAY: + String arrayElementType = argSymbol.value; + pop(); + if (arrayElementType.charAt(0) == '[') { + push(symbolTable, '[' + arrayElementType); + } else { + push(ARRAY_OF | REFERENCE_KIND | symbolTable.addType(arrayElementType)); + } + break; + case Opcodes.CHECKCAST: + String castType = argSymbol.value; + pop(); + if (castType.charAt(0) == '[') { + push(symbolTable, castType); + } else { + push(REFERENCE_KIND | symbolTable.addType(castType)); + } + break; + case Opcodes.MULTIANEWARRAY: + pop(arg); + push(symbolTable, argSymbol.value); + break; + default: + throw new IllegalArgumentException(); + } + } + + // ----------------------------------------------------------------------------------------------- + // Frame merging methods, used in the second step of the stack map frame computation algorithm + // ----------------------------------------------------------------------------------------------- + + /** + * Computes the concrete output type corresponding to a given abstract output type. + * + * @param abstractOutputType an abstract output type. + * @param numStack the size of the input stack, used to resolve abstract output types of + * STACK_KIND kind. + * @return the concrete output type corresponding to 'abstractOutputType'. + */ + private int getConcreteOutputType(final int abstractOutputType, final int numStack) { + int dim = abstractOutputType & DIM_MASK; + int kind = abstractOutputType & KIND_MASK; + if (kind == LOCAL_KIND) { + // By definition, a LOCAL_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + int concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; + } + return concreteOutputType; + } else if (kind == STACK_KIND) { + // By definition, a STACK_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + int concreteOutputType = dim + inputStack[numStack - (abstractOutputType & VALUE_MASK)]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; + } + return concreteOutputType; + } else { + return abstractOutputType; + } + } + + /** + * Merges the input frame of the given {@link Frame} with the input and output frames of this + * {@link Frame}. Returns {@literal true} if the given frame has been changed by this operation + * (the input and output frames of this {@link Frame} are never changed). + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param dstFrame the {@link Frame} whose input frame must be updated. This should be the frame + * of a successor, in the control flow graph, of the basic block corresponding to this frame. + * @param catchTypeIndex if 'frame' corresponds to an exception handler basic block, the type + * table index of the caught exception type, otherwise 0. + * @return {@literal true} if the input frame of 'frame' has been changed by this operation. + */ + final boolean merge( + final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) { + boolean frameChanged = false; + + // Compute the concrete types of the local variables at the end of the basic block corresponding + // to this frame, by resolving its abstract output types, and merge these concrete types with + // those of the local variables in the input frame of dstFrame. + int numLocal = inputLocals.length; + int numStack = inputStack.length; + if (dstFrame.inputLocals == null) { + dstFrame.inputLocals = new int[numLocal]; + frameChanged = true; + } + for (int i = 0; i < numLocal; ++i) { + int concreteOutputType; + if (outputLocals != null && i < outputLocals.length) { + int abstractOutputType = outputLocals[i]; + if (abstractOutputType == 0) { + // If the local variable has never been assigned in this basic block, it is equal to its + // value at the beginning of the block. + concreteOutputType = inputLocals[i]; + } else { + concreteOutputType = getConcreteOutputType(abstractOutputType, numStack); + } + } else { + // If the local variable has never been assigned in this basic block, it is equal to its + // value at the beginning of the block. + concreteOutputType = inputLocals[i]; + } + // concreteOutputType might be an uninitialized type from the input locals or from the input + // stack. However, if a constructor has been called for this class type in the basic block, + // then this type is no longer uninitialized at the end of basic block. + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputLocals, i); + } + + // If dstFrame is an exception handler block, it can be reached from any instruction of the + // basic block corresponding to this frame, in particular from the first one. Therefore, the + // input locals of dstFrame should be compatible (i.e. merged) with the input locals of this + // frame (and the input stack of dstFrame should be compatible, i.e. merged, with a one + // element stack containing the caught exception type). + if (catchTypeIndex > 0) { + for (int i = 0; i < numLocal; ++i) { + frameChanged |= merge(symbolTable, inputLocals[i], dstFrame.inputLocals, i); + } + if (dstFrame.inputStack == null) { + dstFrame.inputStack = new int[1]; + frameChanged = true; + } + frameChanged |= merge(symbolTable, catchTypeIndex, dstFrame.inputStack, 0); + return frameChanged; + } + + // Compute the concrete types of the stack operands at the end of the basic block corresponding + // to this frame, by resolving its abstract output types, and merge these concrete types with + // those of the stack operands in the input frame of dstFrame. + int numInputStack = inputStack.length + outputStackStart; + if (dstFrame.inputStack == null) { + dstFrame.inputStack = new int[numInputStack + outputStackTop]; + frameChanged = true; + } + // First, do this for the stack operands that have not been popped in the basic block + // corresponding to this frame, and which are therefore equal to their value in the input + // frame (except for uninitialized types, which may have been initialized). + for (int i = 0; i < numInputStack; ++i) { + int concreteOutputType = inputStack[i]; + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, i); + } + // Then, do this for the stack operands that have pushed in the basic block (this code is the + // same as the one above for local variables). + for (int i = 0; i < outputStackTop; ++i) { + int abstractOutputType = outputStack[i]; + int concreteOutputType = getConcreteOutputType(abstractOutputType, numStack); + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= + merge(symbolTable, concreteOutputType, dstFrame.inputStack, numInputStack + i); + } + return frameChanged; + } + + /** + * Merges the type at the given index in the given abstract type array with the given type. + * Returns {@literal true} if the type array has been modified by this operation. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param sourceType the abstract type with which the abstract type array element must be merged. + * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND} or {@link + * #UNINITIALIZED_KIND} kind, with positive or {@literal null} array dimensions. + * @param dstTypes an array of abstract types. These types should be of {@link #CONSTANT_KIND}, + * {@link #REFERENCE_KIND} or {@link #UNINITIALIZED_KIND} kind, with positive or {@literal + * null} array dimensions. + * @param dstIndex the index of the type that must be merged in dstTypes. + * @return {@literal true} if the type array has been modified by this operation. + */ + private static boolean merge( + final SymbolTable symbolTable, + final int sourceType, + final int[] dstTypes, + final int dstIndex) { + int dstType = dstTypes[dstIndex]; + if (dstType == sourceType) { + // If the types are equal, merge(sourceType, dstType) = dstType, so there is no change. + return false; + } + int srcType = sourceType; + if ((sourceType & ~DIM_MASK) == NULL) { + if (dstType == NULL) { + return false; + } + srcType = NULL; + } + if (dstType == 0) { + // If dstTypes[dstIndex] has never been assigned, merge(srcType, dstType) = srcType. + dstTypes[dstIndex] = srcType; + return true; + } + int mergedType; + if ((dstType & DIM_MASK) != 0 || (dstType & KIND_MASK) == REFERENCE_KIND) { + // If dstType is a reference type of any array dimension. + if (srcType == NULL) { + // If srcType is the NULL type, merge(srcType, dstType) = dstType, so there is no change. + return false; + } else if ((srcType & (DIM_MASK | KIND_MASK)) == (dstType & (DIM_MASK | KIND_MASK))) { + // If srcType has the same array dimension and the same kind as dstType. + if ((dstType & KIND_MASK) == REFERENCE_KIND) { + // If srcType and dstType are reference types with the same array dimension, + // merge(srcType, dstType) = dim(srcType) | common super class of srcType and dstType. + mergedType = + (srcType & DIM_MASK) + | REFERENCE_KIND + | symbolTable.addMergedType(srcType & VALUE_MASK, dstType & VALUE_MASK); + } else { + // If srcType and dstType are array types of equal dimension but different element types, + // merge(srcType, dstType) = dim(srcType) - 1 | java/lang/Object. + int mergedDim = ELEMENT_OF + (srcType & DIM_MASK); + mergedType = mergedDim | REFERENCE_KIND | symbolTable.addType("java/lang/Object"); + } + } else if ((srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND) { + // If srcType is any other reference or array type, + // merge(srcType, dstType) = min(srcDdim, dstDim) | java/lang/Object + // where srcDim is the array dimension of srcType, minus 1 if srcType is an array type + // with a non reference element type (and similarly for dstDim). + int srcDim = srcType & DIM_MASK; + if (srcDim != 0 && (srcType & KIND_MASK) != REFERENCE_KIND) { + srcDim = ELEMENT_OF + srcDim; + } + int dstDim = dstType & DIM_MASK; + if (dstDim != 0 && (dstType & KIND_MASK) != REFERENCE_KIND) { + dstDim = ELEMENT_OF + dstDim; + } + mergedType = + Math.min(srcDim, dstDim) | REFERENCE_KIND | symbolTable.addType("java/lang/Object"); + } else { + // If srcType is any other type, merge(srcType, dstType) = TOP. + mergedType = TOP; + } + } else if (dstType == NULL) { + // If dstType is the NULL type, merge(srcType, dstType) = srcType, or TOP if srcType is not a + // an array type or a reference type. + mergedType = + (srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND ? srcType : TOP; + } else { + // If dstType is any other type, merge(srcType, dstType) = TOP whatever srcType. + mergedType = TOP; + } + if (mergedType != dstType) { + dstTypes[dstIndex] = mergedType; + return true; + } + return false; + } + + // ----------------------------------------------------------------------------------------------- + // Frame output methods, to generate StackMapFrame attributes + // ----------------------------------------------------------------------------------------------- + + /** + * Makes the given {@link MethodWriter} visit the input frame of this {@link Frame}. The visit is + * done with the {@link MethodWriter#visitFrameStart}, {@link MethodWriter#visitAbstractType} and + * {@link MethodWriter#visitFrameEnd} methods. + * + * @param methodWriter the {@link MethodWriter} that should visit the input frame of this {@link + * Frame}. + */ + final void accept(final MethodWriter methodWriter) { + // Compute the number of locals, ignoring TOP types that are just after a LONG or a DOUBLE, and + // all trailing TOP types. + int[] localTypes = inputLocals; + int numLocal = 0; + int numTrailingTop = 0; + int i = 0; + while (i < localTypes.length) { + int localType = localTypes[i]; + i += (localType == LONG || localType == DOUBLE) ? 2 : 1; + if (localType == TOP) { + numTrailingTop++; + } else { + numLocal += numTrailingTop + 1; + numTrailingTop = 0; + } + } + // Compute the stack size, ignoring TOP types that are just after a LONG or a DOUBLE. + int[] stackTypes = inputStack; + int numStack = 0; + i = 0; + while (i < stackTypes.length) { + int stackType = stackTypes[i]; + i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; + numStack++; + } + // Visit the frame and its content. + int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, numLocal, numStack); + i = 0; + while (numLocal-- > 0) { + int localType = localTypes[i]; + i += (localType == LONG || localType == DOUBLE) ? 2 : 1; + methodWriter.visitAbstractType(frameIndex++, localType); + } + i = 0; + while (numStack-- > 0) { + int stackType = stackTypes[i]; + i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; + methodWriter.visitAbstractType(frameIndex++, stackType); + } + methodWriter.visitFrameEnd(); + } + + /** + * Put the given abstract type in the given ByteVector, using the JVMS verification_type_info + * format used in StackMapTable attributes. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param abstractType an abstract type, restricted to {@link Frame#CONSTANT_KIND}, {@link + * Frame#REFERENCE_KIND} or {@link Frame#UNINITIALIZED_KIND} types. + * @param output where the abstract type must be put. + * @see JVMS + * 4.7.4 + */ + static void putAbstractType( + final SymbolTable symbolTable, final int abstractType, final ByteVector output) { + int arrayDimensions = (abstractType & Frame.DIM_MASK) >> DIM_SHIFT; + if (arrayDimensions == 0) { + int typeValue = abstractType & VALUE_MASK; + switch (abstractType & KIND_MASK) { + case CONSTANT_KIND: + output.putByte(typeValue); + break; + case REFERENCE_KIND: + output + .putByte(ITEM_OBJECT) + .putShort(symbolTable.addConstantClass(symbolTable.getType(typeValue).value).index); + break; + case UNINITIALIZED_KIND: + output.putByte(ITEM_UNINITIALIZED).putShort((int) symbolTable.getType(typeValue).data); + break; + default: + throw new AssertionError(); + } + } else { + // Case of an array type, we need to build its descriptor first. + StringBuilder typeDescriptor = new StringBuilder(); + while (arrayDimensions-- > 0) { + typeDescriptor.append('['); + } + if ((abstractType & KIND_MASK) == REFERENCE_KIND) { + typeDescriptor + .append('L') + .append(symbolTable.getType(abstractType & VALUE_MASK).value) + .append(';'); + } else { + switch (abstractType & VALUE_MASK) { + case Frame.ITEM_ASM_BOOLEAN: + typeDescriptor.append('Z'); + break; + case Frame.ITEM_ASM_BYTE: + typeDescriptor.append('B'); + break; + case Frame.ITEM_ASM_CHAR: + typeDescriptor.append('C'); + break; + case Frame.ITEM_ASM_SHORT: + typeDescriptor.append('S'); + break; + case Frame.ITEM_INTEGER: + typeDescriptor.append('I'); + break; + case Frame.ITEM_FLOAT: + typeDescriptor.append('F'); + break; + case Frame.ITEM_LONG: + typeDescriptor.append('J'); + break; + case Frame.ITEM_DOUBLE: + typeDescriptor.append('D'); + break; + default: + throw new AssertionError(); + } + } + output + .putByte(ITEM_OBJECT) + .putShort(symbolTable.addConstantClass(typeDescriptor.toString()).index); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Handle.java b/native/java/jpype.jvm.asm/org/jpype/asm/Handle.java new file mode 100644 index 000000000..cf7b267c1 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Handle.java @@ -0,0 +1,189 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package org.jpype.asm; + +/** + * A reference to a field or a method. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public final class Handle { + + /** + * The kind of field or method designated by this Handle. Should be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + */ + private final int tag; + + /** The internal name of the class that owns the field or method designated by this handle. */ + private final String owner; + + /** The name of the field or method designated by this handle. */ + private final String name; + + /** The descriptor of the field or method designated by this handle. */ + private final String descriptor; + + /** Whether the owner is an interface or not. */ + private final boolean isInterface; + + /** + * Constructs a new field or method handle. + * + * @param tag the kind of field or method designated by this Handle. Must be {@link + * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link + * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link + * Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the class that owns the field or method designated by this + * handle. + * @param name the name of the field or method designated by this handle. + * @param descriptor the descriptor of the field or method designated by this handle. + * @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String, + * boolean)}. + */ + @Deprecated + public Handle(final int tag, final String owner, final String name, final String descriptor) { + this(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Constructs a new field or method handle. + * + * @param tag the kind of field or method designated by this Handle. Must be {@link + * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link + * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link + * Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the class that owns the field or method designated by this + * handle. + * @param name the name of the field or method designated by this handle. + * @param descriptor the descriptor of the field or method designated by this handle. + * @param isInterface whether the owner is an interface or not. + */ + public Handle( + final int tag, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + this.tag = tag; + this.owner = owner; + this.name = name; + this.descriptor = descriptor; + this.isInterface = isInterface; + } + + /** + * Returns the kind of field or method designated by this handle. + * + * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + */ + public int getTag() { + return tag; + } + + /** + * Returns the internal name of the class that owns the field or method designated by this handle. + * + * @return the internal name of the class that owns the field or method designated by this handle. + */ + public String getOwner() { + return owner; + } + + /** + * Returns the name of the field or method designated by this handle. + * + * @return the name of the field or method designated by this handle. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the field or method designated by this handle. + * + * @return the descriptor of the field or method designated by this handle. + */ + public String getDesc() { + return descriptor; + } + + /** + * Returns true if the owner of the field or method designated by this handle is an interface. + * + * @return true if the owner of the field or method designated by this handle is an interface. + */ + public boolean isInterface() { + return isInterface; + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Handle)) { + return false; + } + Handle handle = (Handle) object; + return tag == handle.tag + && isInterface == handle.isInterface + && owner.equals(handle.owner) + && name.equals(handle.name) + && descriptor.equals(handle.descriptor); + } + + @Override + public int hashCode() { + return tag + + (isInterface ? 64 : 0) + + owner.hashCode() * name.hashCode() * descriptor.hashCode(); + } + + /** + * Returns the textual representation of this handle. The textual representation is: + * + *

    + *
  • for a reference to a class: owner "." name descriptor " (" tag ")", + *
  • for a reference to an interface: owner "." name descriptor " (" tag " itf)". + *
+ */ + @Override + public String toString() { + return owner + '.' + name + descriptor + " (" + tag + (isInterface ? " itf" : "") + ')'; + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Handler.java b/native/java/jpype.jvm.asm/org/jpype/asm/Handler.java new file mode 100644 index 000000000..1c04c25c9 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Handler.java @@ -0,0 +1,198 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * Information about an exception handler. Corresponds to an element of the exception_table array of + * a Code attribute, as defined in the Java Virtual Machine Specification (JVMS). Handler instances + * can be chained together, with their {@link #nextHandler} field, to describe a full JVMS + * exception_table array. + * + * @see JVMS + * 4.7.3 + * @author Eric Bruneton + */ +final class Handler { + + /** + * The start_pc field of this JVMS exception_table entry. Corresponds to the beginning of the + * exception handler's scope (inclusive). + */ + final Label startPc; + + /** + * The end_pc field of this JVMS exception_table entry. Corresponds to the end of the exception + * handler's scope (exclusive). + */ + final Label endPc; + + /** + * The handler_pc field of this JVMS exception_table entry. Corresponding to the beginning of the + * exception handler's code. + */ + final Label handlerPc; + + /** + * The catch_type field of this JVMS exception_table entry. This is the constant pool index of the + * internal name of the type of exceptions handled by this handler, or 0 to catch any exceptions. + */ + final int catchType; + + /** + * The internal name of the type of exceptions handled by this handler, or {@literal null} to + * catch any exceptions. + */ + final String catchTypeDescriptor; + + /** The next exception handler. */ + Handler nextHandler; + + /** + * Constructs a new Handler. + * + * @param startPc the start_pc field of this JVMS exception_table entry. + * @param endPc the end_pc field of this JVMS exception_table entry. + * @param handlerPc the handler_pc field of this JVMS exception_table entry. + * @param catchType The catch_type field of this JVMS exception_table entry. + * @param catchTypeDescriptor The internal name of the type of exceptions handled by this handler, + * or {@literal null} to catch any exceptions. + */ + Handler( + final Label startPc, + final Label endPc, + final Label handlerPc, + final int catchType, + final String catchTypeDescriptor) { + this.startPc = startPc; + this.endPc = endPc; + this.handlerPc = handlerPc; + this.catchType = catchType; + this.catchTypeDescriptor = catchTypeDescriptor; + } + + /** + * Constructs a new Handler from the given one, with a different scope. + * + * @param handler an existing Handler. + * @param startPc the start_pc field of this JVMS exception_table entry. + * @param endPc the end_pc field of this JVMS exception_table entry. + */ + Handler(final Handler handler, final Label startPc, final Label endPc) { + this(startPc, endPc, handler.handlerPc, handler.catchType, handler.catchTypeDescriptor); + this.nextHandler = handler.nextHandler; + } + + /** + * Removes the range between start and end from the Handler list that begins with the given + * element. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @param start the start of the range to be removed. + * @param end the end of the range to be removed. Maybe {@literal null}. + * @return the exception handler list with the start-end range removed. + */ + static Handler removeRange(final Handler firstHandler, final Label start, final Label end) { + if (firstHandler == null) { + return null; + } else { + firstHandler.nextHandler = removeRange(firstHandler.nextHandler, start, end); + } + int handlerStart = firstHandler.startPc.bytecodeOffset; + int handlerEnd = firstHandler.endPc.bytecodeOffset; + int rangeStart = start.bytecodeOffset; + int rangeEnd = end == null ? Integer.MAX_VALUE : end.bytecodeOffset; + // Return early if [handlerStart,handlerEnd[ and [rangeStart,rangeEnd[ don't intersect. + if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) { + return firstHandler; + } + if (rangeStart <= handlerStart) { + if (rangeEnd >= handlerEnd) { + // If [handlerStart,handlerEnd[ is included in [rangeStart,rangeEnd[, remove firstHandler. + return firstHandler.nextHandler; + } else { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [rangeEnd,handlerEnd[ + return new Handler(firstHandler, end, firstHandler.endPc); + } + } else if (rangeEnd >= handlerEnd) { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [handlerStart,rangeStart[ + return new Handler(firstHandler, firstHandler.startPc, start); + } else { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = + // [handlerStart,rangeStart[ + [rangeEnd,handerEnd[ + firstHandler.nextHandler = new Handler(firstHandler, end, firstHandler.endPc); + return new Handler(firstHandler, firstHandler.startPc, start); + } + } + + /** + * Returns the number of elements of the Handler list that begins with the given element. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @return the number of elements of the Handler list that begins with 'handler'. + */ + static int getExceptionTableLength(final Handler firstHandler) { + int length = 0; + Handler handler = firstHandler; + while (handler != null) { + length++; + handler = handler.nextHandler; + } + return length; + } + + /** + * Returns the size in bytes of the JVMS exception_table corresponding to the Handler list that + * begins with the given element. This includes the exception_table_length field. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @return the size in bytes of the exception_table_length and exception_table structures. + */ + static int getExceptionTableSize(final Handler firstHandler) { + return 2 + 8 * getExceptionTableLength(firstHandler); + } + + /** + * Puts the JVMS exception_table corresponding to the Handler list that begins with the given + * element. This includes the exception_table_length field. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @param output where the exception_table_length and exception_table structures must be put. + */ + static void putExceptionTable(final Handler firstHandler, final ByteVector output) { + output.putShort(getExceptionTableLength(firstHandler)); + Handler handler = firstHandler; + while (handler != null) { + output + .putShort(handler.startPc.bytecodeOffset) + .putShort(handler.endPc.bytecodeOffset) + .putShort(handler.handlerPc.bytecodeOffset) + .putShort(handler.catchType); + handler = handler.nextHandler; + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Label.java b/native/java/jpype.jvm.asm/org/jpype/asm/Label.java new file mode 100644 index 000000000..1ed4656a3 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Label.java @@ -0,0 +1,622 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A position in the bytecode of a method. Labels are used for jump, goto, and switch instructions, + * and for try catch blocks. A label designates the instruction that is just after. Note + * however that there can be other elements between a label and the instruction it designates (such + * as other labels, stack map frames, line numbers, etc.). + * + * @author Eric Bruneton + */ +public class Label { + + /** + * A flag indicating that a label is only used for debug attributes. Such a label is not the start + * of a basic block, the target of a jump instruction, or an exception handler. It can be safely + * ignored in control flow graph analysis algorithms (for optimization purposes). + */ + static final int FLAG_DEBUG_ONLY = 1; + + /** + * A flag indicating that a label is the target of a jump instruction, or the start of an + * exception handler. + */ + static final int FLAG_JUMP_TARGET = 2; + + /** A flag indicating that the bytecode offset of a label is known. */ + static final int FLAG_RESOLVED = 4; + + /** A flag indicating that a label corresponds to a reachable basic block. */ + static final int FLAG_REACHABLE = 8; + + /** + * A flag indicating that the basic block corresponding to a label ends with a subroutine call. By + * construction in {@link MethodWriter#visitJumpInsn}, labels with this flag set have at least two + * outgoing edges: + * + *
    + *
  • the first one corresponds to the instruction that follows the jsr instruction in the + * bytecode, i.e. where execution continues when it returns from the jsr call. This is a + * virtual control flow edge, since execution never goes directly from the jsr to the next + * instruction. Instead, it goes to the subroutine and eventually returns to the instruction + * following the jsr. This virtual edge is used to compute the real outgoing edges of the + * basic blocks ending with a ret instruction, in {@link #addSubroutineRetSuccessors}. + *
  • the second one corresponds to the target of the jsr instruction, + *
+ */ + static final int FLAG_SUBROUTINE_CALLER = 16; + + /** + * A flag indicating that the basic block corresponding to a label is the start of a subroutine. + */ + static final int FLAG_SUBROUTINE_START = 32; + + /** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */ + static final int FLAG_SUBROUTINE_END = 64; + + /** + * The number of elements to add to the {@link #otherLineNumbers} array when it needs to be + * resized to store a new source line number. + */ + static final int LINE_NUMBERS_CAPACITY_INCREMENT = 4; + + /** + * The number of elements to add to the {@link #forwardReferences} array when it needs to be + * resized to store a new forward reference. + */ + static final int FORWARD_REFERENCES_CAPACITY_INCREMENT = 6; + + /** + * The bit mask to extract the type of a forward reference to this label. The extracted type is + * either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link #FORWARD_REFERENCE_TYPE_WIDE}. + * + * @see #forwardReferences + */ + static final int FORWARD_REFERENCE_TYPE_MASK = 0xF0000000; + + /** + * The type of forward references stored with two bytes in the bytecode. This is the case, for + * instance, of a forward reference from an ifnull instruction. + */ + static final int FORWARD_REFERENCE_TYPE_SHORT = 0x10000000; + + /** + * The type of forward references stored in four bytes in the bytecode. This is the case, for + * instance, of a forward reference from a lookupswitch instruction. + */ + static final int FORWARD_REFERENCE_TYPE_WIDE = 0x20000000; + + /** + * The bit mask to extract the 'handle' of a forward reference to this label. The extracted handle + * is the bytecode offset where the forward reference value is stored (using either 2 or 4 bytes, + * as indicated by the {@link #FORWARD_REFERENCE_TYPE_MASK}). + * + * @see #forwardReferences + */ + static final int FORWARD_REFERENCE_HANDLE_MASK = 0x0FFFFFFF; + + /** + * A sentinel element used to indicate the end of a list of labels. + * + * @see #nextListElement + */ + static final Label EMPTY_LIST = new Label(); + + /** + * A user managed state associated with this label. Warning: this field is used by the ASM tree + * package. In order to use it with the ASM tree package you must override the getLabelNode method + * in MethodNode. + */ + public Object info; + + /** + * The type and status of this label or its corresponding basic block. Must be zero or more of + * {@link #FLAG_DEBUG_ONLY}, {@link #FLAG_JUMP_TARGET}, {@link #FLAG_RESOLVED}, {@link + * #FLAG_REACHABLE}, {@link #FLAG_SUBROUTINE_CALLER}, {@link #FLAG_SUBROUTINE_START}, {@link + * #FLAG_SUBROUTINE_END}. + */ + short flags; + + /** + * The source line number corresponding to this label, or 0. If there are several source line + * numbers corresponding to this label, the first one is stored in this field, and the remaining + * ones are stored in {@link #otherLineNumbers}. + */ + private short lineNumber; + + /** + * The source line numbers corresponding to this label, in addition to {@link #lineNumber}, or + * null. The first element of this array is the number n of source line numbers it contains, which + * are stored between indices 1 and n (inclusive). + */ + private int[] otherLineNumbers; + + /** + * The offset of this label in the bytecode of its method, in bytes. This value is set if and only + * if the {@link #FLAG_RESOLVED} flag is set. + */ + int bytecodeOffset; + + /** + * The forward references to this label. The first element is the number of forward references, + * times 2 (this corresponds to the index of the last element actually used in this array). Then, + * each forward reference is described with two consecutive integers noted + * 'sourceInsnBytecodeOffset' and 'reference': + * + *
    + *
  • 'sourceInsnBytecodeOffset' is the bytecode offset of the instruction that contains the + * forward reference, + *
  • 'reference' contains the type and the offset in the bytecode where the forward reference + * value must be stored, which can be extracted with {@link #FORWARD_REFERENCE_TYPE_MASK} + * and {@link #FORWARD_REFERENCE_HANDLE_MASK}. + *
+ * + *

For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is + * equal to x, and 'reference' is of type {@link #FORWARD_REFERENCE_TYPE_SHORT} with value x + 1 + * (because the ifnull instruction uses a 2 bytes bytecode offset operand stored one byte after + * the start of the instruction itself). For the default case of a lookupswitch instruction at + * bytecode offset x, 'sourceInsnBytecodeOffset' is equal to x, and 'reference' is of type {@link + * #FORWARD_REFERENCE_TYPE_WIDE} with value between x + 1 and x + 4 (because the lookupswitch + * instruction uses a 4 bytes bytecode offset operand stored one to four bytes after the start of + * the instruction itself). + */ + private int[] forwardReferences; + + // ----------------------------------------------------------------------------------------------- + + // Fields for the control flow and data flow graph analysis algorithms (used to compute the + // maximum stack size or the stack map frames). A control flow graph contains one node per "basic + // block", and one edge per "jump" from one basic block to another. Each node (i.e., each basic + // block) is represented with the Label object that corresponds to the first instruction of this + // basic block. Each node also stores the list of its successors in the graph, as a linked list of + // Edge objects. + // + // The control flow analysis algorithms used to compute the maximum stack size or the stack map + // frames are similar and use two steps. The first step, during the visit of each instruction, + // builds information about the state of the local variables and the operand stack at the end of + // each basic block, called the "output frame", relatively to the frame state at the + // beginning of the basic block, which is called the "input frame", and which is unknown + // during this step. The second step, in {@link MethodWriter#computeAllFrames} and {@link + // MethodWriter#computeMaxStackAndLocal}, is a fix point algorithm + // that computes information about the input frame of each basic block, from the input state of + // the first basic block (known from the method signature), and by the using the previously + // computed relative output frames. + // + // The algorithm used to compute the maximum stack size only computes the relative output and + // absolute input stack heights, while the algorithm used to compute stack map frames computes + // relative output frames and absolute input frames. + + /** + * The number of elements in the input stack of the basic block corresponding to this label. This + * field is computed in {@link MethodWriter#computeMaxStackAndLocal}. + */ + short inputStackSize; + + /** + * The number of elements in the output stack, at the end of the basic block corresponding to this + * label. This field is only computed for basic blocks that end with a RET instruction. + */ + short outputStackSize; + + /** + * The maximum height reached by the output stack, relatively to the top of the input stack, in + * the basic block corresponding to this label. This maximum is always positive or {@literal + * null}. + */ + short outputStackMax; + + /** + * The id of the subroutine to which this basic block belongs, or 0. If the basic block belongs to + * several subroutines, this is the id of the "oldest" subroutine that contains it (with the + * convention that a subroutine calling another one is "older" than the callee). This field is + * computed in {@link MethodWriter#computeMaxStackAndLocal}, if the method contains JSR + * instructions. + */ + short subroutineId; + + /** + * The input and output stack map frames of the basic block corresponding to this label. This + * field is only used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} or {@link + * MethodWriter#COMPUTE_INSERTED_FRAMES} option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited in {@link MethodVisitor#visitLabel}. + * This linked list does not include labels used for debug info only. If the {@link + * MethodWriter#COMPUTE_ALL_FRAMES} or {@link MethodWriter#COMPUTE_INSERTED_FRAMES} option is used + * then it does not contain either successive labels that denote the same bytecode offset (in this + * case only the first label appears in this list). + */ + Label nextBasicBlock; + + /** + * The outgoing edges of the basic block corresponding to this label, in the control flow graph of + * its method. These edges are stored in a linked list of {@link Edge} objects, linked to each + * other by their {@link Edge#nextEdge} field. + */ + Edge outgoingEdges; + + /** + * The next element in the list of labels to which this label belongs, or {@literal null} if it + * does not belong to any list. All lists of labels must end with the {@link #EMPTY_LIST} + * sentinel, in order to ensure that this field is null if and only if this label does not belong + * to a list of labels. Note that there can be several lists of labels at the same time, but that + * a label can belong to at most one list at a time (unless some lists share a common tail, but + * this is not used in practice). + * + *

List of labels are used in {@link MethodWriter#computeAllFrames} and {@link + * MethodWriter#computeMaxStackAndLocal} to compute stack map frames and the maximum stack size, + * respectively, as well as in {@link #markSubroutine} and {@link #addSubroutineRetSuccessors} to + * compute the basic blocks belonging to subroutines and their outgoing edges. Outside of these + * methods, this field should be null (this property is a precondition and a postcondition of + * these methods). + */ + Label nextListElement; + + // ----------------------------------------------------------------------------------------------- + // Constructor and accessors + // ----------------------------------------------------------------------------------------------- + + /** Constructs a new label. */ + public Label() { + // Nothing to do. + } + + /** + * Returns the bytecode offset corresponding to this label. This offset is computed from the start + * of the method's bytecode. This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @return the bytecode offset corresponding to this label. + * @throws IllegalStateException if this label is not resolved yet. + */ + public int getOffset() { + if ((flags & FLAG_RESOLVED) == 0) { + throw new IllegalStateException("Label offset position has not been resolved yet"); + } + return bytecodeOffset; + } + + /** + * Returns the "canonical" {@link Label} instance corresponding to this label's bytecode offset, + * if known, otherwise the label itself. The canonical instance is the first label (in the order + * of their visit by {@link MethodVisitor#visitLabel}) corresponding to this bytecode offset. It + * cannot be known for labels which have not been visited yet. + * + *

This method should only be used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} option + * is used. + * + * @return the label itself if {@link #frame} is null, otherwise the Label's frame owner. This + * corresponds to the "canonical" label instance described above thanks to the way the label + * frame is set in {@link MethodWriter#visitLabel}. + */ + final Label getCanonicalInstance() { + return frame == null ? this : frame.owner; + } + + // ----------------------------------------------------------------------------------------------- + // Methods to manage line numbers + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a source line number corresponding to this label. + * + * @param lineNumber a source line number (which should be strictly positive). + */ + final void addLineNumber(final int lineNumber) { + if (this.lineNumber == 0) { + this.lineNumber = (short) lineNumber; + } else { + if (otherLineNumbers == null) { + otherLineNumbers = new int[LINE_NUMBERS_CAPACITY_INCREMENT]; + } + int otherLineNumberIndex = ++otherLineNumbers[0]; + if (otherLineNumberIndex >= otherLineNumbers.length) { + int[] newLineNumbers = new int[otherLineNumbers.length + LINE_NUMBERS_CAPACITY_INCREMENT]; + System.arraycopy(otherLineNumbers, 0, newLineNumbers, 0, otherLineNumbers.length); + otherLineNumbers = newLineNumbers; + } + otherLineNumbers[otherLineNumberIndex] = lineNumber; + } + } + + /** + * Makes the given visitor visit this label and its source line numbers, if applicable. + * + * @param methodVisitor a method visitor. + * @param visitLineNumbers whether to visit of the label's source line numbers, if any. + */ + final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) { + methodVisitor.visitLabel(this); + if (visitLineNumbers && lineNumber != 0) { + methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this); + if (otherLineNumbers != null) { + for (int i = 1; i <= otherLineNumbers[0]; ++i) { + methodVisitor.visitLineNumber(otherLineNumbers[i], this); + } + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to compute offsets and to manage forward references + // ----------------------------------------------------------------------------------------------- + + /** + * Puts a reference to this label in the bytecode of a method. If the bytecode offset of the label + * is known, the relative bytecode offset between the label and the instruction referencing it is + * computed and written directly. Otherwise, a null relative offset is written and a new forward + * reference is declared for this label. + * + * @param code the bytecode of the method. This is where the reference is appended. + * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the + * reference to be appended. + * @param wideReference whether the reference must be stored in 4 bytes (instead of 2 bytes). + */ + final void put( + final ByteVector code, final int sourceInsnBytecodeOffset, final boolean wideReference) { + if ((flags & FLAG_RESOLVED) == 0) { + if (wideReference) { + addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_WIDE, code.length); + code.putInt(-1); + } else { + addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_SHORT, code.length); + code.putShort(-1); + } + } else { + if (wideReference) { + code.putInt(bytecodeOffset - sourceInsnBytecodeOffset); + } else { + code.putShort(bytecodeOffset - sourceInsnBytecodeOffset); + } + } + } + + /** + * Adds a forward reference to this label. This method must be called only for a true forward + * reference, i.e. only if this label is not resolved yet. For backward references, the relative + * bytecode offset of the reference can be, and must be, computed and stored directly. + * + * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the + * reference stored at referenceHandle. + * @param referenceType either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link + * #FORWARD_REFERENCE_TYPE_WIDE}. + * @param referenceHandle the offset in the bytecode where the forward reference value must be + * stored. + */ + private void addForwardReference( + final int sourceInsnBytecodeOffset, final int referenceType, final int referenceHandle) { + if (forwardReferences == null) { + forwardReferences = new int[FORWARD_REFERENCES_CAPACITY_INCREMENT]; + } + int lastElementIndex = forwardReferences[0]; + if (lastElementIndex + 2 >= forwardReferences.length) { + int[] newValues = new int[forwardReferences.length + FORWARD_REFERENCES_CAPACITY_INCREMENT]; + System.arraycopy(forwardReferences, 0, newValues, 0, forwardReferences.length); + forwardReferences = newValues; + } + forwardReferences[++lastElementIndex] = sourceInsnBytecodeOffset; + forwardReferences[++lastElementIndex] = referenceType | referenceHandle; + forwardReferences[0] = lastElementIndex; + } + + /** + * Sets the bytecode offset of this label to the given value and resolves the forward references + * to this label, if any. This method must be called when this label is added to the bytecode of + * the method, i.e. when its bytecode offset becomes known. This method fills in the blanks that + * where left in the bytecode by each forward reference previously added to this label. + * + * @param code the bytecode of the method. + * @param bytecodeOffset the bytecode offset of this label. + * @return {@literal true} if a blank that was left for this label was too small to store the + * offset. In such a case the corresponding jump instruction is replaced with an equivalent + * ASM specific instruction using an unsigned two bytes offset. These ASM specific + * instructions are later replaced with standard bytecode instructions with wider offsets (4 + * bytes instead of 2), in ClassReader. + */ + final boolean resolve(final byte[] code, final int bytecodeOffset) { + this.flags |= FLAG_RESOLVED; + this.bytecodeOffset = bytecodeOffset; + if (forwardReferences == null) { + return false; + } + boolean hasAsmInstructions = false; + for (int i = forwardReferences[0]; i > 0; i -= 2) { + final int sourceInsnBytecodeOffset = forwardReferences[i - 1]; + final int reference = forwardReferences[i]; + final int relativeOffset = bytecodeOffset - sourceInsnBytecodeOffset; + int handle = reference & FORWARD_REFERENCE_HANDLE_MASK; + if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_SHORT) { + if (relativeOffset < Short.MIN_VALUE || relativeOffset > Short.MAX_VALUE) { + // Change the opcode of the jump instruction, in order to be able to find it later in + // ClassReader. These ASM specific opcodes are similar to jump instruction opcodes, except + // that the 2 bytes offset is unsigned (and can therefore represent values from 0 to + // 65535, which is sufficient since the size of a method is limited to 65535 bytes). + int opcode = code[sourceInsnBytecodeOffset] & 0xFF; + if (opcode < Opcodes.IFNULL) { + // Change IFEQ ... JSR to ASM_IFEQ ... ASM_JSR. + code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_OPCODE_DELTA); + } else { + // Change IFNULL and IFNONNULL to ASM_IFNULL and ASM_IFNONNULL. + code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_IFNULL_OPCODE_DELTA); + } + hasAsmInstructions = true; + } + code[handle++] = (byte) (relativeOffset >>> 8); + code[handle] = (byte) relativeOffset; + } else { + code[handle++] = (byte) (relativeOffset >>> 24); + code[handle++] = (byte) (relativeOffset >>> 16); + code[handle++] = (byte) (relativeOffset >>> 8); + code[handle] = (byte) relativeOffset; + } + } + return hasAsmInstructions; + } + + // ----------------------------------------------------------------------------------------------- + // Methods related to subroutines + // ----------------------------------------------------------------------------------------------- + + /** + * Finds the basic blocks that belong to the subroutine starting with the basic block + * corresponding to this label, and marks these blocks as belonging to this subroutine. This + * method follows the control flow graph to find all the blocks that are reachable from the + * current basic block WITHOUT following any jsr target. + * + *

Note: a precondition and postcondition of this method is that all labels must have a null + * {@link #nextListElement}. + * + * @param subroutineId the id of the subroutine starting with the basic block corresponding to + * this label. + */ + final void markSubroutine(final short subroutineId) { + // Data flow algorithm: put this basic block in a list of blocks to process (which are blocks + // belonging to subroutine subroutineId) and, while there are blocks to process, remove one from + // the list, mark it as belonging to the subroutine, and add its successor basic blocks in the + // control flow graph to the list of blocks to process (if not already done). + Label listOfBlocksToProcess = this; + listOfBlocksToProcess.nextListElement = EMPTY_LIST; + while (listOfBlocksToProcess != EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + basicBlock.nextListElement = null; + + // If it is not already marked as belonging to a subroutine, mark it as belonging to + // subroutineId and add its successors to the list of blocks to process (unless already done). + if (basicBlock.subroutineId == 0) { + basicBlock.subroutineId = subroutineId; + listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess); + } + } + } + + /** + * Finds the basic blocks that end a subroutine starting with the basic block corresponding to + * this label and, for each one of them, adds an outgoing edge to the basic block following the + * given subroutine call. In other words, completes the control flow graph by adding the edges + * corresponding to the return from this subroutine, when called from the given caller basic + * block. + * + *

Note: a precondition and postcondition of this method is that all labels must have a null + * {@link #nextListElement}. + * + * @param subroutineCaller a basic block that ends with a jsr to the basic block corresponding to + * this label. This label is supposed to correspond to the start of a subroutine. + */ + final void addSubroutineRetSuccessors(final Label subroutineCaller) { + // Data flow algorithm: put this basic block in a list blocks to process (which are blocks + // belonging to a subroutine starting with this label) and, while there are blocks to process, + // remove one from the list, put it in a list of blocks that have been processed, add a return + // edge to the successor of subroutineCaller if applicable, and add its successor basic blocks + // in the control flow graph to the list of blocks to process (if not already done). + Label listOfProcessedBlocks = EMPTY_LIST; + Label listOfBlocksToProcess = this; + listOfBlocksToProcess.nextListElement = EMPTY_LIST; + while (listOfBlocksToProcess != EMPTY_LIST) { + // Move a basic block from the list of blocks to process to the list of processed blocks. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = basicBlock.nextListElement; + basicBlock.nextListElement = listOfProcessedBlocks; + listOfProcessedBlocks = basicBlock; + + // Add an edge from this block to the successor of the caller basic block, if this block is + // the end of a subroutine and if this block and subroutineCaller do not belong to the same + // subroutine. + if ((basicBlock.flags & FLAG_SUBROUTINE_END) != 0 + && basicBlock.subroutineId != subroutineCaller.subroutineId) { + basicBlock.outgoingEdges = + new Edge( + basicBlock.outputStackSize, + // By construction, the first outgoing edge of a basic block that ends with a jsr + // instruction leads to the jsr continuation block, i.e. where execution continues + // when ret is called (see {@link #FLAG_SUBROUTINE_CALLER}). + subroutineCaller.outgoingEdges.successor, + basicBlock.outgoingEdges); + } + // Add its successors to the list of blocks to process. Note that {@link #pushSuccessors} does + // not push basic blocks which are already in a list. Here this means either in the list of + // blocks to process, or in the list of already processed blocks. This second list is + // important to make sure we don't reprocess an already processed block. + listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess); + } + // Reset the {@link #nextListElement} of all the basic blocks that have been processed to null, + // so that this method can be called again with a different subroutine or subroutine caller. + while (listOfProcessedBlocks != EMPTY_LIST) { + Label newListOfProcessedBlocks = listOfProcessedBlocks.nextListElement; + listOfProcessedBlocks.nextListElement = null; + listOfProcessedBlocks = newListOfProcessedBlocks; + } + } + + /** + * Adds the successors of this label in the method's control flow graph (except those + * corresponding to a jsr target, and those already in a list of labels) to the given list of + * blocks to process, and returns the new list. + * + * @param listOfLabelsToProcess a list of basic blocks to process, linked together with their + * {@link #nextListElement} field. + * @return the new list of blocks to process. + */ + private Label pushSuccessors(final Label listOfLabelsToProcess) { + Label newListOfLabelsToProcess = listOfLabelsToProcess; + Edge outgoingEdge = outgoingEdges; + while (outgoingEdge != null) { + // By construction, the second outgoing edge of a basic block that ends with a jsr instruction + // leads to the jsr target (see {@link #FLAG_SUBROUTINE_CALLER}). + boolean isJsrTarget = + (flags & Label.FLAG_SUBROUTINE_CALLER) != 0 && outgoingEdge == outgoingEdges.nextEdge; + if (!isJsrTarget && outgoingEdge.successor.nextListElement == null) { + // Add this successor to the list of blocks to process, if it does not already belong to a + // list of labels. + outgoingEdge.successor.nextListElement = newListOfLabelsToProcess; + newListOfLabelsToProcess = outgoingEdge.successor; + } + outgoingEdge = outgoingEdge.nextEdge; + } + return newListOfLabelsToProcess; + } + + // ----------------------------------------------------------------------------------------------- + // Overridden Object methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/MethodTooLargeException.java b/native/java/jpype.jvm.asm/org/jpype/asm/MethodTooLargeException.java new file mode 100644 index 000000000..34282b1e9 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/MethodTooLargeException.java @@ -0,0 +1,99 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * Exception thrown when the Code attribute of a method produced by a {@link ClassWriter} is too + * large. + * + * @author Jason Zaugg + */ +public final class MethodTooLargeException extends IndexOutOfBoundsException { + private static final long serialVersionUID = 6807380416709738314L; + + private final String className; + private final String methodName; + private final String descriptor; + private final int codeSize; + + /** + * Constructs a new {@link MethodTooLargeException}. + * + * @param className the internal name of the owner class. + * @param methodName the name of the method. + * @param descriptor the descriptor of the method. + * @param codeSize the size of the method's Code attribute, in bytes. + */ + public MethodTooLargeException( + final String className, + final String methodName, + final String descriptor, + final int codeSize) { + super("Method too large: " + className + "." + methodName + " " + descriptor); + this.className = className; + this.methodName = methodName; + this.descriptor = descriptor; + this.codeSize = codeSize; + } + + /** + * Returns the internal name of the owner class. + * + * @return the internal name of the owner class. + */ + public String getClassName() { + return className; + } + + /** + * Returns the name of the method. + * + * @return the name of the method. + */ + public String getMethodName() { + return methodName; + } + + /** + * Returns the descriptor of the method. + * + * @return the descriptor of the method. + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Returns the size of the method's Code attribute, in bytes. + * + * @return the size of the method's Code attribute, in bytes. + */ + public int getCodeSize() { + return codeSize; + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/MethodVisitor.java b/native/java/jpype.jvm.asm/org/jpype/asm/MethodVisitor.java new file mode 100644 index 000000000..e7c74271c --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/MethodVisitor.java @@ -0,0 +1,786 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A visitor to visit a Java method. The methods of this class must be called in the following + * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} | + * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} {@code + * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} | + * {@code visitXInsn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code + * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code + * visitLocalVariableAnnotation} | {@code visitLineNumber} )* {@code visitMaxs} ] {@code visitEnd}. + * In addition, the {@code visitXInsn} and {@code visitLabel} methods must be called in the + * sequential order of the bytecode instructions of the visited code, {@code visitInsnAnnotation} + * must be called after the annotated instruction, {@code visitTryCatchBlock} must be called + * before the labels passed as arguments have been visited, {@code + * visitTryCatchBlockAnnotation} must be called after the corresponding try catch block has + * been visited, and the {@code visitLocalVariable}, {@code visitLocalVariableAnnotation} and {@code + * visitLineNumber} methods must be called after the labels passed as arguments have been + * visited. + * + * @author Eric Bruneton + */ +public abstract class MethodVisitor { + + private static final String REQUIRES_ASM5 = "This feature requires ASM5"; + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + protected final int api; + + /** + * The method visitor to which this visitor must delegate method calls. May be {@literal null}. + */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + public MethodVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param methodVisitor the method visitor to which this visitor must delegate method calls. May + * be null. + */ + public MethodVisitor(final int api, final MethodVisitor methodVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); + } + this.api = api; + this.mv = methodVisitor; + } + + // ----------------------------------------------------------------------------------------------- + // Parameters, annotations and non standard attributes + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a parameter of this method. + * + * @param name parameter name or {@literal null} if none is provided. + * @param access the parameter's access flags, only {@code ACC_FINAL}, {@code ACC_SYNTHETIC} + * or/and {@code ACC_MANDATED} are allowed (see {@link Opcodes}). + */ + public void visitParameter(final String name, final int access) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + mv.visitParameter(name, access); + } + } + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this annotation interface method, or + * {@literal null} if this visitor is not interested in visiting this default value. The + * 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover, + * exacly one visit method must be called on this annotation visitor, followed by visitEnd. + */ + public AnnotationVisitor visitAnnotationDefault() { + if (mv != null) { + return mv.visitAnnotationDefault(); + } + return null; + } + + /** + * Visits an annotation of this method. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (mv != null) { + return mv.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the method signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#METHOD_TYPE_PARAMETER}, {@link + * TypeReference#METHOD_TYPE_PARAMETER_BOUND}, {@link TypeReference#METHOD_RETURN}, {@link + * TypeReference#METHOD_RECEIVER}, {@link TypeReference#METHOD_FORMAL_PARAMETER} or {@link + * TypeReference#THROWS}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits the number of method parameters that can have annotations. By default (i.e. when this + * method is not called), all the method parameters defined by the method descriptor can have + * annotations. + * + * @param parameterCount the number of method parameters than can have annotations. This number + * must be less or equal than the number of parameter types in the method descriptor. It can + * be strictly less when a method has synthetic parameters and when these parameters are + * ignored when computing parameter indices for the purpose of parameter annotations (see + * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). + * @param visible {@literal true} to define the number of method parameters that can have + * annotations visible at runtime, {@literal false} to define the number of method parameters + * that can have annotations invisible at runtime. + */ + public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) { + if (mv != null) { + mv.visitAnnotableParameterCount(parameterCount, visible); + } + } + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter the parameter index. This index must be strictly smaller than the number of + * parameters in the method descriptor, and strictly smaller than the parameter count + * specified in {@link #visitAnnotableParameterCount}. Important note: a parameter index i + * is not required to correspond to the i'th parameter descriptor in the method + * descriptor, in particular in case of synthetic parameters (see + * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitParameterAnnotation( + final int parameter, final String descriptor, final boolean visible) { + if (mv != null) { + return mv.visitParameterAnnotation(parameter, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of this method. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (mv != null) { + mv.visitAttribute(attribute); + } + } + + /** Starts the visit of the method's code, if any (i.e. non abstract method). */ + public void visitCode() { + if (mv != null) { + mv.visitCode(); + } + } + + /** + * Visits the current state of the local variables and operand stack elements. This method must(*) + * be called just before any instruction i that follows an unconditional branch + * instruction such as GOTO or THROW, that is the target of a jump instruction, or that starts an + * exception handler block. The visited types must describe the values of the local variables and + * of the operand stack elements just before i is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or equal to {@link + * Opcodes#V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in compressed form (all frames + * must use the same format, i.e. you must not mix expanded and compressed frames within a single + * method): + * + *

    + *
  • In expanded form, all frames must have the F_NEW type. + *
  • In compressed form, frames are basically "deltas" from the state of the previous frame: + *
      + *
    • {@link Opcodes#F_SAME} representing frame with exactly the same locals as the + * previous frame and with the empty stack. + *
    • {@link Opcodes#F_SAME1} representing frame with exactly the same locals as the + * previous frame and with single value on the stack ( numStack is 1 and + * stack[0] contains value for the type of the stack item). + *
    • {@link Opcodes#F_APPEND} representing frame with current locals are the same as the + * locals in the previous frame, except that additional locals are defined ( + * numLocal is 1, 2 or 3 and local elements contains values + * representing added types). + *
    • {@link Opcodes#F_CHOP} representing frame with current locals are the same as the + * locals in the previous frame, except that the last 1-3 locals are absent and with + * the empty stack (numLocal is 1, 2 or 3). + *
    • {@link Opcodes#F_FULL} representing complete frame data. + *
    + *
+ * + *
+ * In both cases the first frame, corresponding to the method's parameters and access flags, is + * implicit and must not be visited. Also, it is illegal to visit two or more frames for the same + * code location (i.e., at least one instruction must be visited between two calls to visitFrame). + * + * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded + * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link + * Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames. + * @param numLocal the number of local variables in the visited frame. + * @param local the local variable types in this frame. This array must not be modified. Primitive + * types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element). + * Reference types are represented by String objects (representing internal names), and + * uninitialized types by Label objects (this label designates the NEW instruction that + * created this uninitialized value). + * @param numStack the number of operand stack elements in the visited frame. + * @param stack the operand stack types in this frame. This array must not be modified. Its + * content has the same format as the "local" array. + * @throws IllegalStateException if a frame is visited just after another one, without any + * instruction between the two (unless this frame is a Opcodes#F_SAME frame, in which case it + * is silently ignored). + */ + public void visitFrame( + final int type, + final int numLocal, + final Object[] local, + final int numStack, + final Object[] stack) { + if (mv != null) { + mv.visitFrame(type, numLocal, local, numStack, stack); + } + } + + // ----------------------------------------------------------------------------------------------- + // Normal instructions + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode the opcode of the instruction to be visited. This opcode is either NOP, + * ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, + * LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, + * FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, + * AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, + * SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, + * FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, + * LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, + * D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, + * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT. + */ + public void visitInsn(final int opcode) { + if (mv != null) { + mv.visitInsn(opcode); + } + } + + /** + * Visits an instruction with a single int operand. + * + * @param opcode the opcode of the instruction to be visited. This opcode is either BIPUSH, SIPUSH + * or NEWARRAY. + * @param operand the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between Byte.MIN_VALUE and Byte.MAX_VALUE. + *
+ * When opcode is SIPUSH, operand value should be between Short.MIN_VALUE and Short.MAX_VALUE. + *
+ * When opcode is NEWARRAY, operand value should be one of {@link Opcodes#T_BOOLEAN}, {@link + * Opcodes#T_CHAR}, {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, {@link Opcodes#T_BYTE}, + * {@link Opcodes#T_SHORT}, {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + public void visitIntInsn(final int opcode, final int operand) { + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + /** + * Visits a local variable instruction. A local variable instruction is an instruction that loads + * or stores the value of a local variable. + * + * @param opcode the opcode of the local variable instruction to be visited. This opcode is either + * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var the operand of the instruction to be visited. This operand is the index of a local + * variable. + */ + public void visitVarInsn(final int opcode, final int var) { + if (mv != null) { + mv.visitVarInsn(opcode, var); + } + } + + /** + * Visits a type instruction. A type instruction is an instruction that takes the internal name of + * a class as parameter. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW, + * ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type the operand of the instruction to be visited. This operand must be the internal + * name of an object or array class (see {@link Type#getInternalName()}). + */ + public void visitTypeInsn(final int opcode, final String type) { + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + /** + * Visits a field instruction. A field instruction is an instruction that loads or stores the + * value of a field of an object. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}). + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + */ + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, descriptor); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead. + */ + @Deprecated + public void visitMethodInsn( + final int opcode, final String owner, final String name, final String descriptor) { + int opcodeAndSource = opcode | (api < Opcodes.ASM5 ? Opcodes.SOURCE_DEPRECATED : 0); + visitMethodInsn(opcodeAndSource, owner, name, descriptor, opcode == Opcodes.INVOKEINTERFACE); + } + + /** + * Visits a method instruction. A method instruction is an instruction that invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param isInterface if the method's owner class is an interface. + */ + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + if (api < Opcodes.ASM5 && (opcode & Opcodes.SOURCE_DEPRECATED) == 0) { + if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new UnsupportedOperationException("INVOKESPECIAL/STATIC on interfaces requires ASM5"); + } + visitMethodInsn(opcode, owner, name, descriptor); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode & ~Opcodes.SOURCE_MASK, owner, name, descriptor, isInterface); + } + } + + /** + * Visits an invokedynamic instruction. + * + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, {@link + * Type}, {@link Handle} or {@link ConstantDynamic} value. This method is allowed to modify + * the content of the array so a caller should expect that this array may change. + */ + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + } + + /** + * Visits a jump instruction. A jump instruction is an instruction that may jump to another + * instruction. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either IFEQ, + * IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, + * IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label the operand of the instruction to be visited. This operand is a label that + * designates the instruction to which the jump instruction may jump. + */ + public void visitJumpInsn(final int opcode, final Label label) { + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + /** + * Visits a label. A label designates the instruction that will be visited just after it. + * + * @param label a {@link Label} object. + */ + public void visitLabel(final Label label) { + if (mv != null) { + mv.visitLabel(label); + } + } + + // ----------------------------------------------------------------------------------------------- + // Special instructions + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. Note that new constant types may be added in future versions of the + * Java Virtual Machine. To easily detect new constant types, implementations of this method + * should check for unexpected constant types, like this: + * + *
+   * if (cst instanceof Integer) {
+   *     // ...
+   * } else if (cst instanceof Float) {
+   *     // ...
+   * } else if (cst instanceof Long) {
+   *     // ...
+   * } else if (cst instanceof Double) {
+   *     // ...
+   * } else if (cst instanceof String) {
+   *     // ...
+   * } else if (cst instanceof Type) {
+   *     int sort = ((Type) cst).getSort();
+   *     if (sort == Type.OBJECT) {
+   *         // ...
+   *     } else if (sort == Type.ARRAY) {
+   *         // ...
+   *     } else if (sort == Type.METHOD) {
+   *         // ...
+   *     } else {
+   *         // throw an exception
+   *     }
+   * } else if (cst instanceof Handle) {
+   *     // ...
+   * } else if (cst instanceof ConstantDynamic) {
+   *     // ...
+   * } else {
+   *     // throw an exception
+   * }
+   * 
+ * + * @param value the constant to be loaded on the stack. This parameter must be a non null {@link + * Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link + * Type} of OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is + * 49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle + * constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant + * dynamic for classes whose version is 55. + */ + public void visitLdcInsn(final Object value) { + if (api < Opcodes.ASM5 + && (value instanceof Handle + || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) { + throw new UnsupportedOperationException("This feature requires ASM7"); + } + if (mv != null) { + mv.visitLdcInsn(value); + } + } + + /** + * Visits an IINC instruction. + * + * @param var index of the local variable to be incremented. + * @param increment amount to increment the local variable by. + */ + public void visitIincInsn(final int var, final int increment) { + if (mv != null) { + mv.visitIincInsn(var, increment); + } + } + + /** + * Visits a TABLESWITCH instruction. + * + * @param min the minimum key value. + * @param max the maximum key value. + * @param dflt beginning of the default handler block. + * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the + * handler block for the {@code min + i} key. + */ + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt beginning of the default handler block. + * @param keys the values of the keys. + * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the + * handler block for the {@code keys[i]} key. + */ + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param descriptor an array type descriptor (see {@link Type}). + * @param numDimensions the number of dimensions of the array to allocate. + */ + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + if (mv != null) { + mv.visitMultiANewArrayInsn(descriptor, numDimensions); + } + } + + /** + * Visits an annotation on an instruction. This method must be called just after the + * annotated instruction. It can be called several times for the same instruction. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#INSTANCEOF}, {@link TypeReference#NEW}, {@link + * TypeReference#CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE}, {@link + * TypeReference#CAST}, {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link + * TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link + * TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitInsnAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitInsnAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + // ----------------------------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start the beginning of the exception handler's scope (inclusive). + * @param end the end of the exception handler's scope (exclusive). + * @param handler the beginning of the exception handler's code. + * @param type the internal name of the type of exceptions handled by the handler, or {@literal + * null} to catch any exceptions (for "finally" blocks). + * @throws IllegalArgumentException if one of the labels has already been visited by this visitor + * (by the {@link #visitLabel} method). + */ + public void visitTryCatchBlock( + final Label start, final Label end, final Label handler, final String type) { + if (mv != null) { + mv.visitTryCatchBlock(start, end, handler, type); + } + } + + /** + * Visits an annotation on an exception handler type. This method must be called after the + * {@link #visitTryCatchBlock} for the annotated exception handler. It can be called several times + * for the same exception handler. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#EXCEPTION_PARAMETER}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTryCatchAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a local variable declaration. + * + * @param name the name of a local variable. + * @param descriptor the type descriptor of this local variable. + * @param signature the type signature of this local variable. May be {@literal null} if the local + * variable type does not use generic types. + * @param start the first instruction corresponding to the scope of this local variable + * (inclusive). + * @param end the last instruction corresponding to the scope of this local variable (exclusive). + * @param index the local variable's index. + * @throws IllegalArgumentException if one of the labels has not already been visited by this + * visitor (by the {@link #visitLabel} method). + */ + public void visitLocalVariable( + final String name, + final String descriptor, + final String signature, + final Label start, + final Label end, + final int index) { + if (mv != null) { + mv.visitLocalVariable(name, descriptor, signature, start, end, index); + } + } + + /** + * Visits an annotation on a local variable type. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE}. See {@link + * TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param start the fist instructions corresponding to the continuous ranges that make the scope + * of this local variable (inclusive). + * @param end the last instructions corresponding to the continuous ranges that make the scope of + * this local variable (exclusive). This array must have the same size as the 'start' array. + * @param index the local variable's index in each range. This array must have the same size as + * the 'start' array. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitLocalVariableAnnotation( + final int typeRef, + final TypePath typePath, + final Label[] start, + final Label[] end, + final int[] index, + final String descriptor, + final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitLocalVariableAnnotation( + typeRef, typePath, start, end, index, descriptor, visible); + } + return null; + } + + /** + * Visits a line number declaration. + * + * @param line a line number. This number refers to the source file from which the class was + * compiled. + * @param start the first instruction corresponding to this line number. + * @throws IllegalArgumentException if {@code start} has not already been visited by this visitor + * (by the {@link #visitLabel} method). + */ + public void visitLineNumber(final int line, final Label start) { + if (mv != null) { + mv.visitLineNumber(line, start); + } + } + + /** + * Visits the maximum stack size and the maximum number of local variables of the method. + * + * @param maxStack maximum stack size of the method. + * @param maxLocals maximum number of local variables for the method. + */ + public void visitMaxs(final int maxStack, final int maxLocals) { + if (mv != null) { + mv.visitMaxs(maxStack, maxLocals); + } + } + + /** + * Visits the end of the method. This method, which is the last one to be called, is used to + * inform the visitor that all the annotations and attributes of the method have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/MethodWriter.java b/native/java/jpype.jvm.asm/org/jpype/asm/MethodWriter.java new file mode 100644 index 000000000..3331d5672 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/MethodWriter.java @@ -0,0 +1,2393 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A {@link MethodVisitor} that generates a corresponding 'method_info' structure, as defined in the + * Java Virtual Machine Specification (JVMS). + * + * @see JVMS + * 4.6 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class MethodWriter extends MethodVisitor { + + /** Indicates that nothing must be computed. */ + static final int COMPUTE_NOTHING = 0; + + /** + * Indicates that the maximum stack size and the maximum number of local variables must be + * computed, from scratch. + */ + static final int COMPUTE_MAX_STACK_AND_LOCAL = 1; + + /** + * Indicates that the maximum stack size and the maximum number of local variables must be + * computed, from the existing stack map frames. This can be done more efficiently than with the + * control flow graph algorithm used for {@link #COMPUTE_MAX_STACK_AND_LOCAL}, by using a linear + * scan of the bytecode instructions. + */ + static final int COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES = 2; + + /** + * Indicates that the stack map frames of type F_INSERT must be computed. The other frames are not + * computed. They should all be of type F_NEW and should be sufficient to compute the content of + * the F_INSERT frames, together with the bytecode instructions between a F_NEW and a F_INSERT + * frame - and without any knowledge of the type hierarchy (by definition of F_INSERT). + */ + static final int COMPUTE_INSERTED_FRAMES = 3; + + /** + * Indicates that all the stack map frames must be computed. In this case the maximum stack size + * and the maximum number of local variables is also computed. + */ + static final int COMPUTE_ALL_FRAMES = 4; + + /** Indicates that {@link #STACK_SIZE_DELTA} is not applicable (not constant or never used). */ + private static final int NA = 0; + + /** + * The stack size variation corresponding to each JVM opcode. The stack size variation for opcode + * 'o' is given by the array element at index 'o'. + * + * @see JVMS 6 + */ + private static final int[] STACK_SIZE_DELTA = { + 0, // nop = 0 (0x0) + 1, // aconst_null = 1 (0x1) + 1, // iconst_m1 = 2 (0x2) + 1, // iconst_0 = 3 (0x3) + 1, // iconst_1 = 4 (0x4) + 1, // iconst_2 = 5 (0x5) + 1, // iconst_3 = 6 (0x6) + 1, // iconst_4 = 7 (0x7) + 1, // iconst_5 = 8 (0x8) + 2, // lconst_0 = 9 (0x9) + 2, // lconst_1 = 10 (0xa) + 1, // fconst_0 = 11 (0xb) + 1, // fconst_1 = 12 (0xc) + 1, // fconst_2 = 13 (0xd) + 2, // dconst_0 = 14 (0xe) + 2, // dconst_1 = 15 (0xf) + 1, // bipush = 16 (0x10) + 1, // sipush = 17 (0x11) + 1, // ldc = 18 (0x12) + NA, // ldc_w = 19 (0x13) + NA, // ldc2_w = 20 (0x14) + 1, // iload = 21 (0x15) + 2, // lload = 22 (0x16) + 1, // fload = 23 (0x17) + 2, // dload = 24 (0x18) + 1, // aload = 25 (0x19) + NA, // iload_0 = 26 (0x1a) + NA, // iload_1 = 27 (0x1b) + NA, // iload_2 = 28 (0x1c) + NA, // iload_3 = 29 (0x1d) + NA, // lload_0 = 30 (0x1e) + NA, // lload_1 = 31 (0x1f) + NA, // lload_2 = 32 (0x20) + NA, // lload_3 = 33 (0x21) + NA, // fload_0 = 34 (0x22) + NA, // fload_1 = 35 (0x23) + NA, // fload_2 = 36 (0x24) + NA, // fload_3 = 37 (0x25) + NA, // dload_0 = 38 (0x26) + NA, // dload_1 = 39 (0x27) + NA, // dload_2 = 40 (0x28) + NA, // dload_3 = 41 (0x29) + NA, // aload_0 = 42 (0x2a) + NA, // aload_1 = 43 (0x2b) + NA, // aload_2 = 44 (0x2c) + NA, // aload_3 = 45 (0x2d) + -1, // iaload = 46 (0x2e) + 0, // laload = 47 (0x2f) + -1, // faload = 48 (0x30) + 0, // daload = 49 (0x31) + -1, // aaload = 50 (0x32) + -1, // baload = 51 (0x33) + -1, // caload = 52 (0x34) + -1, // saload = 53 (0x35) + -1, // istore = 54 (0x36) + -2, // lstore = 55 (0x37) + -1, // fstore = 56 (0x38) + -2, // dstore = 57 (0x39) + -1, // astore = 58 (0x3a) + NA, // istore_0 = 59 (0x3b) + NA, // istore_1 = 60 (0x3c) + NA, // istore_2 = 61 (0x3d) + NA, // istore_3 = 62 (0x3e) + NA, // lstore_0 = 63 (0x3f) + NA, // lstore_1 = 64 (0x40) + NA, // lstore_2 = 65 (0x41) + NA, // lstore_3 = 66 (0x42) + NA, // fstore_0 = 67 (0x43) + NA, // fstore_1 = 68 (0x44) + NA, // fstore_2 = 69 (0x45) + NA, // fstore_3 = 70 (0x46) + NA, // dstore_0 = 71 (0x47) + NA, // dstore_1 = 72 (0x48) + NA, // dstore_2 = 73 (0x49) + NA, // dstore_3 = 74 (0x4a) + NA, // astore_0 = 75 (0x4b) + NA, // astore_1 = 76 (0x4c) + NA, // astore_2 = 77 (0x4d) + NA, // astore_3 = 78 (0x4e) + -3, // iastore = 79 (0x4f) + -4, // lastore = 80 (0x50) + -3, // fastore = 81 (0x51) + -4, // dastore = 82 (0x52) + -3, // aastore = 83 (0x53) + -3, // bastore = 84 (0x54) + -3, // castore = 85 (0x55) + -3, // sastore = 86 (0x56) + -1, // pop = 87 (0x57) + -2, // pop2 = 88 (0x58) + 1, // dup = 89 (0x59) + 1, // dup_x1 = 90 (0x5a) + 1, // dup_x2 = 91 (0x5b) + 2, // dup2 = 92 (0x5c) + 2, // dup2_x1 = 93 (0x5d) + 2, // dup2_x2 = 94 (0x5e) + 0, // swap = 95 (0x5f) + -1, // iadd = 96 (0x60) + -2, // ladd = 97 (0x61) + -1, // fadd = 98 (0x62) + -2, // dadd = 99 (0x63) + -1, // isub = 100 (0x64) + -2, // lsub = 101 (0x65) + -1, // fsub = 102 (0x66) + -2, // dsub = 103 (0x67) + -1, // imul = 104 (0x68) + -2, // lmul = 105 (0x69) + -1, // fmul = 106 (0x6a) + -2, // dmul = 107 (0x6b) + -1, // idiv = 108 (0x6c) + -2, // ldiv = 109 (0x6d) + -1, // fdiv = 110 (0x6e) + -2, // ddiv = 111 (0x6f) + -1, // irem = 112 (0x70) + -2, // lrem = 113 (0x71) + -1, // frem = 114 (0x72) + -2, // drem = 115 (0x73) + 0, // ineg = 116 (0x74) + 0, // lneg = 117 (0x75) + 0, // fneg = 118 (0x76) + 0, // dneg = 119 (0x77) + -1, // ishl = 120 (0x78) + -1, // lshl = 121 (0x79) + -1, // ishr = 122 (0x7a) + -1, // lshr = 123 (0x7b) + -1, // iushr = 124 (0x7c) + -1, // lushr = 125 (0x7d) + -1, // iand = 126 (0x7e) + -2, // land = 127 (0x7f) + -1, // ior = 128 (0x80) + -2, // lor = 129 (0x81) + -1, // ixor = 130 (0x82) + -2, // lxor = 131 (0x83) + 0, // iinc = 132 (0x84) + 1, // i2l = 133 (0x85) + 0, // i2f = 134 (0x86) + 1, // i2d = 135 (0x87) + -1, // l2i = 136 (0x88) + -1, // l2f = 137 (0x89) + 0, // l2d = 138 (0x8a) + 0, // f2i = 139 (0x8b) + 1, // f2l = 140 (0x8c) + 1, // f2d = 141 (0x8d) + -1, // d2i = 142 (0x8e) + 0, // d2l = 143 (0x8f) + -1, // d2f = 144 (0x90) + 0, // i2b = 145 (0x91) + 0, // i2c = 146 (0x92) + 0, // i2s = 147 (0x93) + -3, // lcmp = 148 (0x94) + -1, // fcmpl = 149 (0x95) + -1, // fcmpg = 150 (0x96) + -3, // dcmpl = 151 (0x97) + -3, // dcmpg = 152 (0x98) + -1, // ifeq = 153 (0x99) + -1, // ifne = 154 (0x9a) + -1, // iflt = 155 (0x9b) + -1, // ifge = 156 (0x9c) + -1, // ifgt = 157 (0x9d) + -1, // ifle = 158 (0x9e) + -2, // if_icmpeq = 159 (0x9f) + -2, // if_icmpne = 160 (0xa0) + -2, // if_icmplt = 161 (0xa1) + -2, // if_icmpge = 162 (0xa2) + -2, // if_icmpgt = 163 (0xa3) + -2, // if_icmple = 164 (0xa4) + -2, // if_acmpeq = 165 (0xa5) + -2, // if_acmpne = 166 (0xa6) + 0, // goto = 167 (0xa7) + 1, // jsr = 168 (0xa8) + 0, // ret = 169 (0xa9) + -1, // tableswitch = 170 (0xaa) + -1, // lookupswitch = 171 (0xab) + -1, // ireturn = 172 (0xac) + -2, // lreturn = 173 (0xad) + -1, // freturn = 174 (0xae) + -2, // dreturn = 175 (0xaf) + -1, // areturn = 176 (0xb0) + 0, // return = 177 (0xb1) + NA, // getstatic = 178 (0xb2) + NA, // putstatic = 179 (0xb3) + NA, // getfield = 180 (0xb4) + NA, // putfield = 181 (0xb5) + NA, // invokevirtual = 182 (0xb6) + NA, // invokespecial = 183 (0xb7) + NA, // invokestatic = 184 (0xb8) + NA, // invokeinterface = 185 (0xb9) + NA, // invokedynamic = 186 (0xba) + 1, // new = 187 (0xbb) + 0, // newarray = 188 (0xbc) + 0, // anewarray = 189 (0xbd) + 0, // arraylength = 190 (0xbe) + NA, // athrow = 191 (0xbf) + 0, // checkcast = 192 (0xc0) + 0, // instanceof = 193 (0xc1) + -1, // monitorenter = 194 (0xc2) + -1, // monitorexit = 195 (0xc3) + NA, // wide = 196 (0xc4) + NA, // multianewarray = 197 (0xc5) + -1, // ifnull = 198 (0xc6) + -1, // ifnonnull = 199 (0xc7) + NA, // goto_w = 200 (0xc8) + NA // jsr_w = 201 (0xc9) + }; + + /** Where the constants used in this MethodWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the method_info structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The access_flags field of the method_info JVMS structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the + * ClassFile structure. + */ + private final int accessFlags; + + /** The name_index field of the method_info JVMS structure. */ + private final int nameIndex; + + /** The name of this method. */ + private final String name; + + /** The descriptor_index field of the method_info JVMS structure. */ + private final int descriptorIndex; + + /** The descriptor of this method. */ + private final String descriptor; + + // Code attribute fields and sub attributes: + + /** The max_stack field of the Code attribute. */ + private int maxStack; + + /** The max_locals field of the Code attribute. */ + private int maxLocals; + + /** The 'code' field of the Code attribute. */ + private final ByteVector code = new ByteVector(); + + /** + * The first element in the exception handler list (used to generate the exception_table of the + * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May + * be {@literal null}. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list (used to generate the exception_table of the + * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May + * be {@literal null}. + */ + private Handler lastHandler; + + /** The line_number_table_length field of the LineNumberTable code attribute. */ + private int lineNumberTableLength; + + /** The line_number_table array of the LineNumberTable code attribute, or {@literal null}. */ + private ByteVector lineNumberTable; + + /** The local_variable_table_length field of the LocalVariableTable code attribute. */ + private int localVariableTableLength; + + /** + * The local_variable_table array of the LocalVariableTable code attribute, or {@literal null}. + */ + private ByteVector localVariableTable; + + /** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */ + private int localVariableTypeTableLength; + + /** + * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or {@literal + * null}. + */ + private ByteVector localVariableTypeTable; + + /** The number_of_entries field of the StackMapTable code attribute. */ + private int stackMapTableNumberOfEntries; + + /** The 'entries' array of the StackMapTable code attribute. */ + private ByteVector stackMapTableEntries; + + /** + * The last runtime visible type annotation of the Code attribute. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of the Code attribute. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of the Code attribute. The next ones can be accessed with the + * {@link Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstCodeAttribute; + + // Other method_info attributes: + + /** The number_of_exceptions field of the Exceptions attribute. */ + private final int numberOfExceptions; + + /** The exception_index_table array of the Exceptions attribute, or {@literal null}. */ + private final int[] exceptionIndexTable; + + /** The signature_index field of the Signature attribute. */ + private final int signatureIndex; + + /** + * The last runtime visible annotation of this method. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this method. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** The number of method parameters that can have runtime visible annotations, or 0. */ + private int visibleAnnotableParameterCount; + + /** + * The runtime visible parameter annotations of this method. Each array element contains the last + * annotation of a parameter (which can be {@literal null} - the previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}. + */ + private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations; + + /** The number of method parameters that can have runtime visible annotations, or 0. */ + private int invisibleAnnotableParameterCount; + + /** + * The runtime invisible parameter annotations of this method. Each array element contains the + * last annotation of a parameter (which can be {@literal null} - the previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}. + */ + private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations; + + /** + * The last runtime visible type annotation of this method. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this method. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** The default_value field of the AnnotationDefault attribute, or {@literal null}. */ + private ByteVector defaultValue; + + /** The parameters_count field of the MethodParameters attribute. */ + private int parametersCount; + + /** The 'parameters' array of the MethodParameters attribute, or {@literal null}. */ + private ByteVector parameters; + + /** + * The first non standard attribute of this method. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + // ----------------------------------------------------------------------------------------------- + // Fields used to compute the maximum stack size and number of locals, and the stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link + * #COMPUTE_INSERTED_FRAMES}, {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}. + */ + private final int compute; + + /** + * The first basic block of the method. The next ones (in bytecode offset order) can be accessed + * with the {@link Label#nextBasicBlock} field. + */ + private Label firstBasicBlock; + + /** + * The last basic block of the method (in bytecode offset order). This field is updated each time + * a basic block is encountered, and is used to append it at the end of the basic block list. + */ + private Label lastBasicBlock; + + /** + * The current basic block, i.e. the basic block of the last visited instruction. When {@link + * #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this + * field is {@literal null} for unreachable code. When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays + * unchanged throughout the whole method (i.e. the whole code is seen as a single basic block; + * indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame - + * and the maximum stack size as well - without using any control flow graph). + */ + private Label currentBasicBlock; + + /** + * The relative stack size after the last visited instruction. This size is relative to the + * beginning of {@link #currentBasicBlock}, i.e. the true stack size after the last visited + * instruction is equal to the {@link Label#inputStackSize} of the current basic block plus {@link + * #relativeStackSize}. When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of + * the method, so this relative size is also equal to the absolute stack size after the last + * visited instruction. + */ + private int relativeStackSize; + + /** + * The maximum relative stack size after the last visited instruction. This size is relative to + * the beginning of {@link #currentBasicBlock}, i.e. the true maximum stack size after the last + * visited instruction is equal to the {@link Label#inputStackSize} of the current basic block + * plus {@link #maxRelativeStackSize}.When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of + * the method, so this relative size is also equal to the absolute maximum stack size after the + * last visited instruction. + */ + private int maxRelativeStackSize; + + /** The number of local variables in the last visited stack map frame. */ + private int currentLocals; + + /** The bytecode offset of the last frame that was written in {@link #stackMapTableEntries}. */ + private int previousFrameOffset; + + /** + * The last frame that was written in {@link #stackMapTableEntries}. This field has the same + * format as {@link #currentFrame}. + */ + private int[] previousFrame; + + /** + * The current stack map frame. The first element contains the bytecode offset of the instruction + * to which the frame corresponds, the second element is the number of locals and the third one is + * the number of stack elements. The local variables start at index 3 and are followed by the + * operand stack elements. In summary frame[0] = offset, frame[1] = numLocal, frame[2] = numStack. + * Local variables and operand stack entries contain abstract types, as defined in {@link Frame}, + * but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND} or {@link + * Frame#UNINITIALIZED_KIND} abstract types. Long and double types use only one array entry. + */ + private int[] currentFrame; + + /** Whether this method contains subroutines. */ + private boolean hasSubroutines; + + // ----------------------------------------------------------------------------------------------- + // Other miscellaneous status fields + // ----------------------------------------------------------------------------------------------- + + /** Whether the bytecode of this method contains ASM specific instructions. */ + private boolean hasAsmInstructions; + + /** + * The start offset of the last visited instruction. Used to set the offset field of type + * annotations of type 'offset_target' (see JVMS + * 4.7.20.1). + */ + private int lastBytecodeOffset; + + /** + * The offset in bytes in {@link SymbolTable#getSource} from which the method_info for this method + * (excluding its first 6 bytes) must be copied, or 0. + */ + private int sourceOffset; + + /** + * The length in bytes in {@link SymbolTable#getSource} which must be copied to get the + * method_info for this method (excluding its first 6 bytes for access_flags, name_index and + * descriptor_index). + */ + private int sourceLength; + + // ----------------------------------------------------------------------------------------------- + // Constructor and accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link MethodWriter}. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param access the method's access flags (see {@link Opcodes}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be {@literal null}. + * @param exceptions the internal names of the method's exceptions. May be {@literal null}. + * @param compute indicates what must be computed (see #compute). + */ + MethodWriter( + final SymbolTable symbolTable, + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions, + final int compute) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.name = name; + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + this.descriptor = descriptor; + this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature); + if (exceptions != null && exceptions.length > 0) { + numberOfExceptions = exceptions.length; + this.exceptionIndexTable = new int[numberOfExceptions]; + for (int i = 0; i < numberOfExceptions; ++i) { + this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index; + } + } else { + numberOfExceptions = 0; + this.exceptionIndexTable = null; + } + this.compute = compute; + if (compute != COMPUTE_NOTHING) { + // Update maxLocals and currentLocals. + int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --argumentsSize; + } + maxLocals = argumentsSize; + currentLocals = argumentsSize; + // Create and visit the label for the first basic block. + firstBasicBlock = new Label(); + visitLabel(firstBasicBlock); + } + } + + boolean hasFrames() { + return stackMapTableNumberOfEntries > 0; + } + + boolean hasAsmInstructions() { + return hasAsmInstructions; + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the MethodVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public void visitParameter(final String name, final int access) { + if (parameters == null) { + parameters = new ByteVector(); + } + ++parametersCount; + parameters.putShort((name == null) ? 0 : symbolTable.addConstantUtf8(name)).putShort(access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + defaultValue = new ByteVector(); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null); + } + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) { + if (visible) { + visibleAnnotableParameterCount = parameterCount; + } else { + invisibleAnnotableParameterCount = parameterCount; + } + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, final String annotationDescriptor, final boolean visible) { + if (visible) { + if (lastRuntimeVisibleParameterAnnotations == null) { + lastRuntimeVisibleParameterAnnotations = + new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + return lastRuntimeVisibleParameterAnnotations[parameter] = + AnnotationWriter.create( + symbolTable, annotationDescriptor, lastRuntimeVisibleParameterAnnotations[parameter]); + } else { + if (lastRuntimeInvisibleParameterAnnotations == null) { + lastRuntimeInvisibleParameterAnnotations = + new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + return lastRuntimeInvisibleParameterAnnotations[parameter] = + AnnotationWriter.create( + symbolTable, + annotationDescriptor, + lastRuntimeInvisibleParameterAnnotations[parameter]); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + if (attribute.isCodeAttribute()) { + attribute.nextAttribute = firstCodeAttribute; + firstCodeAttribute = attribute; + } else { + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + } + + @Override + public void visitCode() { + // Nothing to do. + } + + @Override + public void visitFrame( + final int type, + final int numLocal, + final Object[] local, + final int numStack, + final Object[] stack) { + if (compute == COMPUTE_ALL_FRAMES) { + return; + } + + if (compute == COMPUTE_INSERTED_FRAMES) { + if (currentBasicBlock.frame == null) { + // This should happen only once, for the implicit first frame (which is explicitly visited + // in ClassReader if the EXPAND_ASM_INSNS option is used - and COMPUTE_INSERTED_FRAMES + // can't be set if EXPAND_ASM_INSNS is not used). + currentBasicBlock.frame = new CurrentFrame(currentBasicBlock); + currentBasicBlock.frame.setInputFrameFromDescriptor( + symbolTable, accessFlags, descriptor, numLocal); + currentBasicBlock.frame.accept(this); + } else { + if (type == Opcodes.F_NEW) { + currentBasicBlock.frame.setInputFrameFromApiFormat( + symbolTable, numLocal, local, numStack, stack); + } + // If type is not F_NEW then it is F_INSERT by hypothesis, and currentBlock.frame contains + // the stack map frame at the current instruction, computed from the last F_NEW frame and + // the bytecode instructions in between (via calls to CurrentFrame#execute). + currentBasicBlock.frame.accept(this); + } + } else if (type == Opcodes.F_NEW) { + if (previousFrame == null) { + int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + Frame implicitFirstFrame = new Frame(new Label()); + implicitFirstFrame.setInputFrameFromDescriptor( + symbolTable, accessFlags, descriptor, argumentsSize); + implicitFirstFrame.accept(this); + } + currentLocals = numLocal; + int frameIndex = visitFrameStart(code.length, numLocal, numStack); + for (int i = 0; i < numLocal; ++i) { + currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]); + } + for (int i = 0; i < numStack; ++i) { + currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]); + } + visitFrameEnd(); + } else { + if (symbolTable.getMajorVersion() < Opcodes.V1_6) { + throw new IllegalArgumentException("Class versions V1_5 or less must use F_NEW frames."); + } + int offsetDelta; + if (stackMapTableEntries == null) { + stackMapTableEntries = new ByteVector(); + offsetDelta = code.length; + } else { + offsetDelta = code.length - previousFrameOffset - 1; + if (offsetDelta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + currentLocals = numLocal; + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal); + for (int i = 0; i < numLocal; ++i) { + putFrameType(local[i]); + } + stackMapTableEntries.putShort(numStack); + for (int i = 0; i < numStack; ++i) { + putFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + currentLocals += numLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + numLocal).putShort(offsetDelta); + for (int i = 0; i < numLocal; ++i) { + putFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + currentLocals -= numLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - numLocal).putShort(offsetDelta); + break; + case Opcodes.F_SAME: + if (offsetDelta < 64) { + stackMapTableEntries.putByte(offsetDelta); + } else { + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta); + } + break; + case Opcodes.F_SAME1: + if (offsetDelta < 64) { + stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta); + } else { + stackMapTableEntries + .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(offsetDelta); + } + putFrameType(stack[0]); + break; + default: + throw new IllegalArgumentException(); + } + + previousFrameOffset = code.length; + ++stackMapTableNumberOfEntries; + } + + if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + relativeStackSize = numStack; + for (int i = 0; i < numStack; ++i) { + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + relativeStackSize++; + } + } + if (relativeStackSize > maxRelativeStackSize) { + maxRelativeStackSize = relativeStackSize; + } + } + + maxStack = Math.max(maxStack, numStack); + maxLocals = Math.max(maxLocals, currentLocals); + } + + @Override + public void visitInsn(final int opcode) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(opcode); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, null, null); + } else { + int size = relativeStackSize + STACK_SIZE_DELTA[opcode]; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) { + endCurrentBasicBlockWithNoSuccessor(); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // The stack size delta is 1 for BIPUSH or SIPUSH, and 0 for NEWARRAY. + int size = relativeStackSize + 1; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if (var < 4 && opcode != Opcodes.RET) { + int optimizedOpcode; + if (opcode < Opcodes.ISTORE) { + optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var; + } else { + optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var; + } + code.putByte(optimizedOpcode); + } else if (var >= 256) { + code.putByte(Constants.WIDE).put12(opcode, var); + } else { + code.put11(opcode, var); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, var, null, null); + } else { + if (opcode == Opcodes.RET) { + // No stack size delta. + currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_END; + currentBasicBlock.outputStackSize = (short) relativeStackSize; + endCurrentBasicBlockWithNoSuccessor(); + } else { // xLOAD or xSTORE + int size = relativeStackSize + STACK_SIZE_DELTA[opcode]; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + if (compute != COMPUTE_NOTHING) { + int currentMaxLocals; + if (opcode == Opcodes.LLOAD + || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE + || opcode == Opcodes.DSTORE) { + currentMaxLocals = var + 2; + } else { + currentMaxLocals = var + 1; + } + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + if (opcode >= Opcodes.ISTORE && compute == COMPUTE_ALL_FRAMES && firstHandler != null) { + // If there are exception handler blocks, each instruction within a handler range is, in + // theory, a basic block (since execution can jump from this instruction to the exception + // handler). As a consequence, the local variable types at the beginning of the handler + // block should be the merge of the local variable types at all the instructions within the + // handler range. However, instead of creating a basic block for each instruction, we can + // get the same result in a more efficient way. Namely, by starting a new basic block after + // each xSTORE instruction, which is what we do here. + visitLabel(new Label()); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol typeSymbol = symbolTable.addConstantClass(type); + code.put12(opcode, typeSymbol.index); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, lastBytecodeOffset, typeSymbol, symbolTable); + } else if (opcode == Opcodes.NEW) { + // The stack size delta is 1 for NEW, and 0 for ANEWARRAY, CHECKCAST, or INSTANCEOF. + int size = relativeStackSize + 1; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol fieldrefSymbol = symbolTable.addConstantFieldref(owner, name, descriptor); + code.put12(opcode, fieldrefSymbol.index); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, fieldrefSymbol, symbolTable); + } else { + int size; + char firstDescChar = descriptor.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 1 : 0); + break; + case Opcodes.PUTFIELD: + default: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -3 : -2); + break; + } + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol methodrefSymbol = symbolTable.addConstantMethodref(owner, name, descriptor, isInterface); + if (opcode == Opcodes.INVOKEINTERFACE) { + code.put12(Opcodes.INVOKEINTERFACE, methodrefSymbol.index) + .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0); + } else { + code.put12(opcode, methodrefSymbol.index); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, methodrefSymbol, symbolTable); + } else { + int argumentsAndReturnSize = methodrefSymbol.getArgumentsAndReturnSizes(); + int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2); + int size; + if (opcode == Opcodes.INVOKESTATIC) { + size = relativeStackSize + stackSizeDelta + 1; + } else { + size = relativeStackSize + stackSizeDelta; + } + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol invokeDynamicSymbol = + symbolTable.addConstantInvokeDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + code.put12(Opcodes.INVOKEDYNAMIC, invokeDynamicSymbol.index); + code.putShort(0); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, invokeDynamicSymbol, symbolTable); + } else { + int argumentsAndReturnSize = invokeDynamicSymbol.getArgumentsAndReturnSizes(); + int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2) + 1; + int size = relativeStackSize + stackSizeDelta; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + // Compute the 'base' opcode, i.e. GOTO or JSR if opcode is GOTO_W or JSR_W, otherwise opcode. + int baseOpcode = + opcode >= Constants.GOTO_W ? opcode - Constants.WIDE_JUMP_OPCODE_DELTA : opcode; + boolean nextInsnIsJumpTarget = false; + if ((label.flags & Label.FLAG_RESOLVED) != 0 + && label.bytecodeOffset - code.length < Short.MIN_VALUE) { + // Case of a backward jump with an offset < -32768. In this case we automatically replace GOTO + // with GOTO_W, JSR with JSR_W and IFxxx with IFNOTxxx GOTO_W L:..., where + // IFNOTxxx is the "opposite" opcode of IFxxx (e.g. IFNE for IFEQ) and where designates + // the instruction just after the GOTO_W. + if (baseOpcode == Opcodes.GOTO) { + code.putByte(Constants.GOTO_W); + } else if (baseOpcode == Opcodes.JSR) { + code.putByte(Constants.JSR_W); + } else { + // Put the "opposite" opcode of baseOpcode. This can be done by flipping the least + // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ (with a + // pre and post offset by 1). The jump offset is 8 bytes (3 for IFNOTxxx, 5 for GOTO_W). + code.putByte(baseOpcode >= Opcodes.IFNULL ? baseOpcode ^ 1 : ((baseOpcode + 1) ^ 1) - 1); + code.putShort(8); + // Here we could put a GOTO_W in theory, but if ASM specific instructions are used in this + // method or another one, and if the class has frames, we will need to insert a frame after + // this GOTO_W during the additional ClassReader -> ClassWriter round trip to remove the ASM + // specific instructions. To not miss this additional frame, we need to use an ASM_GOTO_W + // here, which has the unfortunate effect of forcing this additional round trip (which in + // some case would not have been really necessary, but we can't know this at this point). + code.putByte(Constants.ASM_GOTO_W); + hasAsmInstructions = true; + // The instruction after the GOTO_W becomes the target of the IFNOT instruction. + nextInsnIsJumpTarget = true; + } + label.put(code, code.length - 1, true); + } else if (baseOpcode != opcode) { + // Case of a GOTO_W or JSR_W specified by the user (normally ClassReader when used to remove + // ASM specific instructions). In this case we keep the original instruction. + code.putByte(opcode); + label.put(code, code.length - 1, true); + } else { + // Case of a jump with an offset >= -32768, or of a jump with an unknown offset. In these + // cases we store the offset in 2 bytes (which will be increased via a ClassReader -> + // ClassWriter round trip if it turns out that 2 bytes are not sufficient). + code.putByte(baseOpcode); + label.put(code, code.length - 1, false); + } + + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + Label nextBasicBlock = null; + if (compute == COMPUTE_ALL_FRAMES) { + currentBasicBlock.frame.execute(baseOpcode, 0, null, null); + // Record the fact that 'label' is the target of a jump instruction. + label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + // Add 'label' as a successor of the current basic block. + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + if (baseOpcode != Opcodes.GOTO) { + // The next instruction starts a new basic block (except for GOTO: by default the code + // following a goto is unreachable - unless there is an explicit label for it - and we + // should not compute stack frame types for its instructions). + nextBasicBlock = new Label(); + } + } else if (compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(baseOpcode, 0, null, null); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += STACK_SIZE_DELTA[baseOpcode]; + } else { + if (baseOpcode == Opcodes.JSR) { + // Record the fact that 'label' designates a subroutine, if not already done. + if ((label.flags & Label.FLAG_SUBROUTINE_START) == 0) { + label.flags |= Label.FLAG_SUBROUTINE_START; + hasSubroutines = true; + } + currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_CALLER; + // Note that, by construction in this method, a block which calls a subroutine has at + // least two successors in the control flow graph: the first one (added below) leads to + // the instruction after the JSR, while the second one (added here) leads to the JSR + // target. Note that the first successor is virtual (it does not correspond to a possible + // execution path): it is only used to compute the successors of the basic blocks ending + // with a ret, in {@link Label#addSubroutineRetSuccessors}. + addSuccessorToCurrentBasicBlock(relativeStackSize + 1, label); + // The instruction after the JSR starts a new basic block. + nextBasicBlock = new Label(); + } else { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += STACK_SIZE_DELTA[baseOpcode]; + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + } + // If the next instruction starts a new basic block, call visitLabel to add the label of this + // instruction as a successor of the current block, and to start a new basic block. + if (nextBasicBlock != null) { + if (nextInsnIsJumpTarget) { + nextBasicBlock.flags |= Label.FLAG_JUMP_TARGET; + } + visitLabel(nextBasicBlock); + } + if (baseOpcode == Opcodes.GOTO) { + endCurrentBasicBlockWithNoSuccessor(); + } + } + } + + @Override + public void visitLabel(final Label label) { + // Resolve the forward references to this label, if any. + hasAsmInstructions |= label.resolve(code.data, code.length); + // visitLabel starts a new basic block (except for debug only labels), so we need to update the + // previous and current block references and list of successors. + if ((label.flags & Label.FLAG_DEBUG_ONLY) != 0) { + return; + } + if (compute == COMPUTE_ALL_FRAMES) { + if (currentBasicBlock != null) { + if (label.bytecodeOffset == currentBasicBlock.bytecodeOffset) { + // We use {@link Label#getCanonicalInstance} to store the state of a basic block in only + // one place, but this does not work for labels which have not been visited yet. + // Therefore, when we detect here two labels having the same bytecode offset, we need to + // - consolidate the state scattered in these two instances into the canonical instance: + currentBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET); + // - make sure the two instances share the same Frame instance (the implementation of + // {@link Label#getCanonicalInstance} relies on this property; here label.frame should be + // null): + label.frame = currentBasicBlock.frame; + // - and make sure to NOT assign 'label' into 'currentBasicBlock' or 'lastBasicBlock', so + // that they still refer to the canonical instance for this bytecode offset. + return; + } + // End the current basic block (with one new successor). + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + } + // Append 'label' at the end of the basic block list. + if (lastBasicBlock != null) { + if (label.bytecodeOffset == lastBasicBlock.bytecodeOffset) { + // Same comment as above. + lastBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET); + // Here label.frame should be null. + label.frame = lastBasicBlock.frame; + currentBasicBlock = lastBasicBlock; + return; + } + lastBasicBlock.nextBasicBlock = label; + } + lastBasicBlock = label; + // Make it the new current basic block. + currentBasicBlock = label; + // Here label.frame should be null. + label.frame = new Frame(label); + } else if (compute == COMPUTE_INSERTED_FRAMES) { + if (currentBasicBlock == null) { + // This case should happen only once, for the visitLabel call in the constructor. Indeed, if + // compute is equal to COMPUTE_INSERTED_FRAMES, currentBasicBlock stays unchanged. + currentBasicBlock = label; + } else { + // Update the frame owner so that a correct frame offset is computed in Frame.accept(). + currentBasicBlock.frame.owner = label; + } + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + if (currentBasicBlock != null) { + // End the current basic block (with one new successor). + currentBasicBlock.outputStackMax = (short) maxRelativeStackSize; + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + // Start a new current basic block, and reset the current and maximum relative stack sizes. + currentBasicBlock = label; + relativeStackSize = 0; + maxRelativeStackSize = 0; + // Append the new basic block at the end of the basic block list. + if (lastBasicBlock != null) { + lastBasicBlock.nextBasicBlock = label; + } + lastBasicBlock = label; + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES && currentBasicBlock == null) { + // This case should happen only once, for the visitLabel call in the constructor. Indeed, if + // compute is equal to COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES, currentBasicBlock stays + // unchanged. + currentBasicBlock = label; + } + } + + @Override + public void visitLdcInsn(final Object value) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol constantSymbol = symbolTable.addConstant(value); + int constantIndex = constantSymbol.index; + char firstDescriptorChar; + boolean isLongOrDouble = + constantSymbol.tag == Symbol.CONSTANT_LONG_TAG + || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG + || (constantSymbol.tag == Symbol.CONSTANT_DYNAMIC_TAG + && ((firstDescriptorChar = constantSymbol.value.charAt(0)) == 'J' + || firstDescriptorChar == 'D')); + if (isLongOrDouble) { + code.put12(Constants.LDC2_W, constantIndex); + } else if (constantIndex >= 256) { + code.put12(Constants.LDC_W, constantIndex); + } else { + code.put11(Opcodes.LDC, constantIndex); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.LDC, 0, constantSymbol, symbolTable); + } else { + int size = relativeStackSize + (isLongOrDouble ? 2 : 1); + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitIincInsn(final int var, final int increment) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if ((var > 255) || (increment > 127) || (increment < -128)) { + code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(var, increment); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null + && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) { + currentBasicBlock.frame.execute(Opcodes.IINC, var, null, null); + } + if (compute != COMPUTE_NOTHING) { + int currentMaxLocals = var + 1; + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + } + + @Override + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(Opcodes.TABLESWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(code, lastBytecodeOffset, true); + code.putInt(min).putInt(max); + for (Label label : labels) { + label.put(code, lastBytecodeOffset, true); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + visitSwitchInsn(dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(Opcodes.LOOKUPSWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(code, lastBytecodeOffset, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(code, lastBytecodeOffset, true); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // Add all the labels as successors of the current basic block. + addSuccessorToCurrentBasicBlock(Edge.JUMP, dflt); + dflt.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + for (Label label : labels) { + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + } + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + --relativeStackSize; + // Add all the labels as successors of the current basic block. + addSuccessorToCurrentBasicBlock(relativeStackSize, dflt); + for (Label label : labels) { + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + } + // End the current basic block. + endCurrentBasicBlockWithNoSuccessor(); + } + } + + @Override + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol descSymbol = symbolTable.addConstantClass(descriptor); + code.put12(Opcodes.MULTIANEWARRAY, descSymbol.index).putByte(numDimensions); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute( + Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable); + } else { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += 1 - numDimensions; + } + } + } + + @Override + public AnnotationVisitor visitInsnAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, + (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), + typePath, + descriptor, + lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, + (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), + typePath, + descriptor, + lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitTryCatchBlock( + final Label start, final Label end, final Label handler, final String type) { + Handler newHandler = + new Handler( + start, end, handler, type != null ? symbolTable.addConstantClass(type).index : 0, type); + if (firstHandler == null) { + firstHandler = newHandler; + } else { + lastHandler.nextHandler = newHandler; + } + lastHandler = newHandler; + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitLocalVariable( + final String name, + final String descriptor, + final String signature, + final Label start, + final Label end, + final int index) { + if (signature != null) { + if (localVariableTypeTable == null) { + localVariableTypeTable = new ByteVector(); + } + ++localVariableTypeTableLength; + localVariableTypeTable + .putShort(start.bytecodeOffset) + .putShort(end.bytecodeOffset - start.bytecodeOffset) + .putShort(symbolTable.addConstantUtf8(name)) + .putShort(symbolTable.addConstantUtf8(signature)) + .putShort(index); + } + if (localVariableTable == null) { + localVariableTable = new ByteVector(); + } + ++localVariableTableLength; + localVariableTable + .putShort(start.bytecodeOffset) + .putShort(end.bytecodeOffset - start.bytecodeOffset) + .putShort(symbolTable.addConstantUtf8(name)) + .putShort(symbolTable.addConstantUtf8(descriptor)) + .putShort(index); + if (compute != COMPUTE_NOTHING) { + char firstDescChar = descriptor.charAt(0); + int currentMaxLocals = index + (firstDescChar == 'J' || firstDescChar == 'D' ? 2 : 1); + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation( + final int typeRef, + final TypePath typePath, + final Label[] start, + final Label[] end, + final int[] index, + final String descriptor, + final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + typeAnnotation.putByte(typeRef >>> 24).putShort(start.length); + for (int i = 0; i < start.length; ++i) { + typeAnnotation + .putShort(start[i].bytecodeOffset) + .putShort(end[i].bytecodeOffset - start[i].bytecodeOffset) + .putShort(index[i]); + } + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + new AnnotationWriter( + symbolTable, + /* useNamedValues = */ true, + typeAnnotation, + lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + new AnnotationWriter( + symbolTable, + /* useNamedValues = */ true, + typeAnnotation, + lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitLineNumber(final int line, final Label start) { + if (lineNumberTable == null) { + lineNumberTable = new ByteVector(); + } + ++lineNumberTableLength; + lineNumberTable.putShort(start.bytecodeOffset); + lineNumberTable.putShort(line); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (compute == COMPUTE_ALL_FRAMES) { + computeAllFrames(); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + computeMaxStackAndLocal(); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + this.maxStack = maxRelativeStackSize; + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + /** Computes all the stack map frames of the method, from scratch. */ + private void computeAllFrames() { + // Complete the control flow graph with exception handler blocks. + Handler handler = firstHandler; + while (handler != null) { + String catchTypeDescriptor = + handler.catchTypeDescriptor == null ? "java/lang/Throwable" : handler.catchTypeDescriptor; + int catchType = Frame.getAbstractTypeFromInternalName(symbolTable, catchTypeDescriptor); + // Mark handlerBlock as an exception handler. + Label handlerBlock = handler.handlerPc.getCanonicalInstance(); + handlerBlock.flags |= Label.FLAG_JUMP_TARGET; + // Add handlerBlock as a successor of all the basic blocks in the exception handler range. + Label handlerRangeBlock = handler.startPc.getCanonicalInstance(); + Label handlerRangeEnd = handler.endPc.getCanonicalInstance(); + while (handlerRangeBlock != handlerRangeEnd) { + handlerRangeBlock.outgoingEdges = + new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges); + handlerRangeBlock = handlerRangeBlock.nextBasicBlock; + } + handler = handler.nextHandler; + } + + // Create and visit the first (implicit) frame. + Frame firstFrame = firstBasicBlock.frame; + firstFrame.setInputFrameFromDescriptor(symbolTable, accessFlags, descriptor, this.maxLocals); + firstFrame.accept(this); + + // Fix point algorithm: add the first basic block to a list of blocks to process (i.e. blocks + // whose stack map frame has changed) and, while there are blocks to process, remove one from + // the list and update the stack map frames of its successor blocks in the control flow graph + // (which might change them, in which case these blocks must be processed too, and are thus + // added to the list of blocks to process). Also compute the maximum stack size of the method, + // as a by-product. + Label listOfBlocksToProcess = firstBasicBlock; + listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST; + int maxStackSize = 0; + while (listOfBlocksToProcess != Label.EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + basicBlock.nextListElement = null; + // By definition, basicBlock is reachable. + basicBlock.flags |= Label.FLAG_REACHABLE; + // Update the (absolute) maximum stack size. + int maxBlockStackSize = basicBlock.frame.getInputStackSize() + basicBlock.outputStackMax; + if (maxBlockStackSize > maxStackSize) { + maxStackSize = maxBlockStackSize; + } + // Update the successor blocks of basicBlock in the control flow graph. + Edge outgoingEdge = basicBlock.outgoingEdges; + while (outgoingEdge != null) { + Label successorBlock = outgoingEdge.successor.getCanonicalInstance(); + boolean successorBlockChanged = + basicBlock.frame.merge(symbolTable, successorBlock.frame, outgoingEdge.info); + if (successorBlockChanged && successorBlock.nextListElement == null) { + // If successorBlock has changed it must be processed. Thus, if it is not already in the + // list of blocks to process, add it to this list. + successorBlock.nextListElement = listOfBlocksToProcess; + listOfBlocksToProcess = successorBlock; + } + outgoingEdge = outgoingEdge.nextEdge; + } + } + + // Loop over all the basic blocks and visit the stack map frames that must be stored in the + // StackMapTable attribute. Also replace unreachable code with NOP* ATHROW, and remove it from + // exception handler ranges. + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) + == (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) { + basicBlock.frame.accept(this); + } + if ((basicBlock.flags & Label.FLAG_REACHABLE) == 0) { + // Find the start and end bytecode offsets of this unreachable block. + Label nextBasicBlock = basicBlock.nextBasicBlock; + int startOffset = basicBlock.bytecodeOffset; + int endOffset = (nextBasicBlock == null ? code.length : nextBasicBlock.bytecodeOffset) - 1; + if (endOffset >= startOffset) { + // Replace its instructions with NOP ... NOP ATHROW. + for (int i = startOffset; i < endOffset; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[endOffset] = (byte) Opcodes.ATHROW; + // Emit a frame for this unreachable block, with no local and a Throwable on the stack + // (so that the ATHROW could consume this Throwable if it were reachable). + int frameIndex = visitFrameStart(startOffset, /* numLocal = */ 0, /* numStack = */ 1); + currentFrame[frameIndex] = + Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable"); + visitFrameEnd(); + // Remove this unreachable basic block from the exception handler ranges. + firstHandler = Handler.removeRange(firstHandler, basicBlock, nextBasicBlock); + // The maximum stack size is now at least one, because of the Throwable declared above. + maxStackSize = Math.max(maxStackSize, 1); + } + } + basicBlock = basicBlock.nextBasicBlock; + } + + this.maxStack = maxStackSize; + } + + /** Computes the maximum stack size of the method. */ + private void computeMaxStackAndLocal() { + // Complete the control flow graph with exception handler blocks. + Handler handler = firstHandler; + while (handler != null) { + Label handlerBlock = handler.handlerPc; + Label handlerRangeBlock = handler.startPc; + Label handlerRangeEnd = handler.endPc; + // Add handlerBlock as a successor of all the basic blocks in the exception handler range. + while (handlerRangeBlock != handlerRangeEnd) { + if ((handlerRangeBlock.flags & Label.FLAG_SUBROUTINE_CALLER) == 0) { + handlerRangeBlock.outgoingEdges = + new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges); + } else { + // If handlerRangeBlock is a JSR block, add handlerBlock after the first two outgoing + // edges to preserve the hypothesis about JSR block successors order (see + // {@link #visitJumpInsn}). + handlerRangeBlock.outgoingEdges.nextEdge.nextEdge = + new Edge( + Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge); + } + handlerRangeBlock = handlerRangeBlock.nextBasicBlock; + } + handler = handler.nextHandler; + } + + // Complete the control flow graph with the successor blocks of subroutines, if needed. + if (hasSubroutines) { + // First step: find the subroutines. This step determines, for each basic block, to which + // subroutine(s) it belongs. Start with the main "subroutine": + short numSubroutines = 1; + firstBasicBlock.markSubroutine(numSubroutines); + // Then, mark the subroutines called by the main subroutine, then the subroutines called by + // those called by the main subroutine, etc. + for (short currentSubroutine = 1; currentSubroutine <= numSubroutines; ++currentSubroutine) { + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0 + && basicBlock.subroutineId == currentSubroutine) { + Label jsrTarget = basicBlock.outgoingEdges.nextEdge.successor; + if (jsrTarget.subroutineId == 0) { + // If this subroutine has not been marked yet, find its basic blocks. + jsrTarget.markSubroutine(++numSubroutines); + } + } + basicBlock = basicBlock.nextBasicBlock; + } + } + // Second step: find the successors in the control flow graph of each subroutine basic block + // 'r' ending with a RET instruction. These successors are the virtual successors of the basic + // blocks ending with JSR instructions (see {@link #visitJumpInsn)} that can reach 'r'. + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) { + // By construction, jsr targets are stored in the second outgoing edge of basic blocks + // that ends with a jsr instruction (see {@link #FLAG_SUBROUTINE_CALLER}). + Label subroutine = basicBlock.outgoingEdges.nextEdge.successor; + subroutine.addSubroutineRetSuccessors(basicBlock); + } + basicBlock = basicBlock.nextBasicBlock; + } + } + + // Data flow algorithm: put the first basic block in a list of blocks to process (i.e. blocks + // whose input stack size has changed) and, while there are blocks to process, remove one + // from the list, update the input stack size of its successor blocks in the control flow + // graph, and add these blocks to the list of blocks to process (if not already done). + Label listOfBlocksToProcess = firstBasicBlock; + listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST; + int maxStackSize = maxStack; + while (listOfBlocksToProcess != Label.EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. Note that we don't reset + // basicBlock.nextListElement to null on purpose, to make sure we don't reprocess already + // processed basic blocks. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + // Compute the (absolute) input stack size and maximum stack size of this block. + int inputStackTop = basicBlock.inputStackSize; + int maxBlockStackSize = inputStackTop + basicBlock.outputStackMax; + // Update the absolute maximum stack size of the method. + if (maxBlockStackSize > maxStackSize) { + maxStackSize = maxBlockStackSize; + } + // Update the input stack size of the successor blocks of basicBlock in the control flow + // graph, and add these blocks to the list of blocks to process, if not already done. + Edge outgoingEdge = basicBlock.outgoingEdges; + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) { + // Ignore the first outgoing edge of the basic blocks ending with a jsr: these are virtual + // edges which lead to the instruction just after the jsr, and do not correspond to a + // possible execution path (see {@link #visitJumpInsn} and + // {@link Label#FLAG_SUBROUTINE_CALLER}). + outgoingEdge = outgoingEdge.nextEdge; + } + while (outgoingEdge != null) { + Label successorBlock = outgoingEdge.successor; + if (successorBlock.nextListElement == null) { + successorBlock.inputStackSize = + (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info); + successorBlock.nextListElement = listOfBlocksToProcess; + listOfBlocksToProcess = successorBlock; + } + outgoingEdge = outgoingEdge.nextEdge; + } + } + this.maxStack = maxStackSize; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: control flow analysis algorithm + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a successor to {@link #currentBasicBlock} in the control flow graph. + * + * @param info information about the control flow edge to be added. + * @param successor the successor block to be added to the current basic block. + */ + private void addSuccessorToCurrentBasicBlock(final int info, final Label successor) { + currentBasicBlock.outgoingEdges = new Edge(info, successor, currentBasicBlock.outgoingEdges); + } + + /** + * Ends the current basic block. This method must be used in the case where the current basic + * block does not have any successor. + * + *

WARNING: this method must be called after the currently visited instruction has been put in + * {@link #code} (if frames are computed, this method inserts a new Label to start a new basic + * block after the current instruction). + */ + private void endCurrentBasicBlockWithNoSuccessor() { + if (compute == COMPUTE_ALL_FRAMES) { + Label nextBasicBlock = new Label(); + nextBasicBlock.frame = new Frame(nextBasicBlock); + nextBasicBlock.resolve(code.data, code.length); + lastBasicBlock.nextBasicBlock = nextBasicBlock; + lastBasicBlock = nextBasicBlock; + currentBasicBlock = null; + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + currentBasicBlock.outputStackMax = (short) maxRelativeStackSize; + currentBasicBlock = null; + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Starts the visit of a new stack map frame, stored in {@link #currentFrame}. + * + * @param offset the bytecode offset of the instruction to which the frame corresponds. + * @param numLocal the number of local variables in the frame. + * @param numStack the number of stack elements in the frame. + * @return the index of the next element to be written in this frame. + */ + int visitFrameStart(final int offset, final int numLocal, final int numStack) { + int frameLength = 3 + numLocal + numStack; + if (currentFrame == null || currentFrame.length < frameLength) { + currentFrame = new int[frameLength]; + } + currentFrame[0] = offset; + currentFrame[1] = numLocal; + currentFrame[2] = numStack; + return 3; + } + + /** + * Sets an abstract type in {@link #currentFrame}. + * + * @param frameIndex the index of the element to be set in {@link #currentFrame}. + * @param abstractType an abstract type. + */ + void visitAbstractType(final int frameIndex, final int abstractType) { + currentFrame[frameIndex] = abstractType; + } + + /** + * Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by + * updating the StackMapTable number_of_entries (except if the current frame is the first one, + * which is implicit in StackMapTable). Then resets {@link #currentFrame} to {@literal null}. + */ + void visitFrameEnd() { + if (previousFrame != null) { + if (stackMapTableEntries == null) { + stackMapTableEntries = new ByteVector(); + } + putFrame(); + ++stackMapTableNumberOfEntries; + } + previousFrame = currentFrame; + currentFrame = null; + } + + /** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */ + private void putFrame() { + final int numLocal = currentFrame[1]; + final int numStack = currentFrame[2]; + if (symbolTable.getMajorVersion() < Opcodes.V1_6) { + // Generate a StackMap attribute entry, which are always uncompressed. + stackMapTableEntries.putShort(currentFrame[0]).putShort(numLocal); + putAbstractTypes(3, 3 + numLocal); + stackMapTableEntries.putShort(numStack); + putAbstractTypes(3 + numLocal, 3 + numLocal + numStack); + return; + } + final int offsetDelta = + stackMapTableNumberOfEntries == 0 + ? currentFrame[0] + : currentFrame[0] - previousFrame[0] - 1; + final int previousNumlocal = previousFrame[1]; + final int numLocalDelta = numLocal - previousNumlocal; + int type = Frame.FULL_FRAME; + if (numStack == 0) { + switch (numLocalDelta) { + case -3: + case -2: + case -1: + type = Frame.CHOP_FRAME; + break; + case 0: + type = offsetDelta < 64 ? Frame.SAME_FRAME : Frame.SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = Frame.APPEND_FRAME; + break; + default: + // Keep the FULL_FRAME type. + break; + } + } else if (numLocalDelta == 0 && numStack == 1) { + type = + offsetDelta < 63 + ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; + } + if (type != Frame.FULL_FRAME) { + // Verify if locals are the same as in the previous frame. + int frameIndex = 3; + for (int i = 0; i < previousNumlocal && i < numLocal; i++) { + if (currentFrame[frameIndex] != previousFrame[frameIndex]) { + type = Frame.FULL_FRAME; + break; + } + frameIndex++; + } + } + switch (type) { + case Frame.SAME_FRAME: + stackMapTableEntries.putByte(offsetDelta); + break; + case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta); + putAbstractTypes(3 + numLocal, 4 + numLocal); + break; + case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMapTableEntries + .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(offsetDelta); + putAbstractTypes(3 + numLocal, 4 + numLocal); + break; + case Frame.SAME_FRAME_EXTENDED: + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta); + break; + case Frame.CHOP_FRAME: + stackMapTableEntries + .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta) + .putShort(offsetDelta); + break; + case Frame.APPEND_FRAME: + stackMapTableEntries + .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta) + .putShort(offsetDelta); + putAbstractTypes(3 + previousNumlocal, 3 + numLocal); + break; + case Frame.FULL_FRAME: + default: + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal); + putAbstractTypes(3, 3 + numLocal); + stackMapTableEntries.putShort(numStack); + putAbstractTypes(3 + numLocal, 3 + numLocal + numStack); + break; + } + } + + /** + * Puts some abstract types of {@link #currentFrame} in {@link #stackMapTableEntries} , using the + * JVMS verification_type_info format used in StackMapTable attributes. + * + * @param start index of the first type in {@link #currentFrame} to write. + * @param end index of last type in {@link #currentFrame} to write (exclusive). + */ + private void putAbstractTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + Frame.putAbstractType(symbolTable, currentFrame[i], stackMapTableEntries); + } + } + + /** + * Puts the given public API frame element type in {@link #stackMapTableEntries} , using the JVMS + * verification_type_info format used in StackMapTable attributes. + * + * @param type a frame element type described using the same format as in {@link + * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or + * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating + * a NEW instruction (for uninitialized types). + */ + private void putFrameType(final Object type) { + if (type instanceof Integer) { + stackMapTableEntries.putByte(((Integer) type).intValue()); + } else if (type instanceof String) { + stackMapTableEntries + .putByte(Frame.ITEM_OBJECT) + .putShort(symbolTable.addConstantClass((String) type).index); + } else { + stackMapTableEntries + .putByte(Frame.ITEM_UNINITIALIZED) + .putShort(((Label) type).bytecodeOffset); + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns whether the attributes of this method can be copied from the attributes of the given + * method (assuming there is no method visitor between the given ClassReader and this + * MethodWriter). This method should only be called just after this MethodWriter has been created, + * and before any content is visited. It returns true if the attributes corresponding to the + * constructor arguments (at most a Signature, an Exception, a Deprecated and a Synthetic + * attribute) are the same as the corresponding attributes in the given method. + * + * @param source the source ClassReader from which the attributes of this method might be copied. + * @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes + * of this method might be copied contains a Synthetic attribute. + * @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes + * of this method might be copied contains a Deprecated attribute. + * @param descriptorIndex the descriptor_index field of the method_info JVMS structure from which + * the attributes of this method might be copied. + * @param signatureIndex the constant pool index contained in the Signature attribute of the + * method_info JVMS structure from which the attributes of this method might be copied, or 0. + * @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info + * JVMS structure from which the attributes of this method might be copied, or 0. + * @return whether the attributes of this method can be copied from the attributes of the + * method_info JVMS structure in 'source.b', between 'methodInfoOffset' and 'methodInfoOffset' + * + 'methodInfoLength'. + */ + boolean canCopyMethodAttributes( + final ClassReader source, + final boolean hasSyntheticAttribute, + final boolean hasDeprecatedAttribute, + final int descriptorIndex, + final int signatureIndex, + final int exceptionsOffset) { + // If the method descriptor has changed, with more locals than the max_locals field of the + // original Code attribute, if any, then the original method attributes can't be copied. A + // conservative check on the descriptor changes alone ensures this (being more precise is not + // worth the additional complexity, because these cases should be rare -- if a transform changes + // a method descriptor, most of the time it needs to change the method's code too). + if (source != symbolTable.getSource() + || descriptorIndex != this.descriptorIndex + || signatureIndex != this.signatureIndex + || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) { + return false; + } + boolean needSyntheticAttribute = + symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0; + if (hasSyntheticAttribute != needSyntheticAttribute) { + return false; + } + if (exceptionsOffset == 0) { + if (numberOfExceptions != 0) { + return false; + } + } else if (source.readUnsignedShort(exceptionsOffset) == numberOfExceptions) { + int currentExceptionOffset = exceptionsOffset + 2; + for (int i = 0; i < numberOfExceptions; ++i) { + if (source.readUnsignedShort(currentExceptionOffset) != exceptionIndexTable[i]) { + return false; + } + currentExceptionOffset += 2; + } + } + return true; + } + + /** + * Sets the source from which the attributes of this method will be copied. + * + * @param methodInfoOffset the offset in 'symbolTable.getSource()' of the method_info JVMS + * structure from which the attributes of this method will be copied. + * @param methodInfoLength the length in 'symbolTable.getSource()' of the method_info JVMS + * structure from which the attributes of this method will be copied. + */ + void setMethodAttributesSource(final int methodInfoOffset, final int methodInfoLength) { + // Don't copy the attributes yet, instead store their location in the source class reader so + // they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes + // of the method_info JVMS structure. + this.sourceOffset = methodInfoOffset + 6; + this.sourceLength = methodInfoLength - 6; + } + + /** + * Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the + * names of the attributes of this method in the constant pool. + * + * @return the size in bytes of the method_info JVMS structure. + */ + int computeMethodInfoSize() { + // If this method_info must be copied from an existing one, the size computation is trivial. + if (sourceOffset != 0) { + // sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index. + return 6 + sourceLength; + } + // 2 bytes each for access_flags, name_index, descriptor_index and attributes_count. + int size = 8; + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (code.length > 0) { + if (code.length > 65535) { + throw new MethodTooLargeException( + symbolTable.getClassName(), name, descriptor, code.length); + } + symbolTable.addConstantUtf8(Constants.CODE); + // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack, + // max_locals, code_length and attributes_count, plus the bytecode and the exception table. + size += 16 + code.length + Handler.getExceptionTableSize(firstHandler); + if (stackMapTableEntries != null) { + boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6; + symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap"); + // 6 header bytes and 2 bytes for number_of_entries. + size += 8 + stackMapTableEntries.length; + } + if (lineNumberTable != null) { + symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE); + // 6 header bytes and 2 bytes for line_number_table_length. + size += 8 + lineNumberTable.length; + } + if (localVariableTable != null) { + symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE); + // 6 header bytes and 2 bytes for local_variable_table_length. + size += 8 + localVariableTable.length; + } + if (localVariableTypeTable != null) { + symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE); + // 6 header bytes and 2 bytes for local_variable_type_table_length. + size += 8 + localVariableTypeTable.length; + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + size += + lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + size += + lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (firstCodeAttribute != null) { + size += + firstCodeAttribute.computeAttributesSize( + symbolTable, code.data, code.length, maxStack, maxLocals); + } + } + if (numberOfExceptions > 0) { + symbolTable.addConstantUtf8(Constants.EXCEPTIONS); + size += 8 + 2 * numberOfExceptions; + } + size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); + if (lastRuntimeVisibleParameterAnnotations != null) { + size += + AnnotationWriter.computeParameterAnnotationsSize( + Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, + lastRuntimeVisibleParameterAnnotations, + visibleAnnotableParameterCount == 0 + ? lastRuntimeVisibleParameterAnnotations.length + : visibleAnnotableParameterCount); + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + size += + AnnotationWriter.computeParameterAnnotationsSize( + Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, + lastRuntimeInvisibleParameterAnnotations, + invisibleAnnotableParameterCount == 0 + ? lastRuntimeInvisibleParameterAnnotations.length + : invisibleAnnotableParameterCount); + } + if (defaultValue != null) { + symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT); + size += 6 + defaultValue.length; + } + if (parameters != null) { + symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS); + // 6 header bytes and 1 byte for parameters_count. + size += 7 + parameters.length; + } + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the method_info JVMS structure generated by this MethodWriter into the + * given ByteVector. + * + * @param output where the method_info structure must be put. + */ + void putMethodInfo(final ByteVector output) { + boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; + int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0; + output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); + // If this method_info must be copied from an existing one, copy it now and return early. + if (sourceOffset != 0) { + output.putByteArray(symbolTable.getSource().classFileBuffer, sourceOffset, sourceLength); + return; + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (numberOfExceptions > 0) { + ++attributeCount; + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + ++attributeCount; + } + if (signatureIndex != 0) { + ++attributeCount; + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeVisibleParameterAnnotations != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + ++attributeCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributeCount; + } + if (defaultValue != null) { + ++attributeCount; + } + if (parameters != null) { + ++attributeCount; + } + if (firstAttribute != null) { + attributeCount += firstAttribute.getAttributeCount(); + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + output.putShort(attributeCount); + if (code.length > 0) { + // 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and + // attributes_count, plus the bytecode and the exception table. + int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler); + int codeAttributeCount = 0; + if (stackMapTableEntries != null) { + // 6 header bytes and 2 bytes for number_of_entries. + size += 8 + stackMapTableEntries.length; + ++codeAttributeCount; + } + if (lineNumberTable != null) { + // 6 header bytes and 2 bytes for line_number_table_length. + size += 8 + lineNumberTable.length; + ++codeAttributeCount; + } + if (localVariableTable != null) { + // 6 header bytes and 2 bytes for local_variable_table_length. + size += 8 + localVariableTable.length; + ++codeAttributeCount; + } + if (localVariableTypeTable != null) { + // 6 header bytes and 2 bytes for local_variable_type_table_length. + size += 8 + localVariableTypeTable.length; + ++codeAttributeCount; + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + size += + lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + ++codeAttributeCount; + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + size += + lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + ++codeAttributeCount; + } + if (firstCodeAttribute != null) { + size += + firstCodeAttribute.computeAttributesSize( + symbolTable, code.data, code.length, maxStack, maxLocals); + codeAttributeCount += firstCodeAttribute.getAttributeCount(); + } + output + .putShort(symbolTable.addConstantUtf8(Constants.CODE)) + .putInt(size) + .putShort(maxStack) + .putShort(maxLocals) + .putInt(code.length) + .putByteArray(code.data, 0, code.length); + Handler.putExceptionTable(firstHandler, output); + output.putShort(codeAttributeCount); + if (stackMapTableEntries != null) { + boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6; + output + .putShort( + symbolTable.addConstantUtf8( + useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap")) + .putInt(2 + stackMapTableEntries.length) + .putShort(stackMapTableNumberOfEntries) + .putByteArray(stackMapTableEntries.data, 0, stackMapTableEntries.length); + } + if (lineNumberTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE)) + .putInt(2 + lineNumberTable.length) + .putShort(lineNumberTableLength) + .putByteArray(lineNumberTable.data, 0, lineNumberTable.length); + } + if (localVariableTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE)) + .putInt(2 + localVariableTable.length) + .putShort(localVariableTableLength) + .putByteArray(localVariableTable.data, 0, localVariableTable.length); + } + if (localVariableTypeTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE)) + .putInt(2 + localVariableTypeTable.length) + .putShort(localVariableTypeTableLength) + .putByteArray(localVariableTypeTable.data, 0, localVariableTypeTable.length); + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + lastCodeRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + if (firstCodeAttribute != null) { + firstCodeAttribute.putAttributes( + symbolTable, code.data, code.length, maxStack, maxLocals, output); + } + } + if (numberOfExceptions > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS)) + .putInt(2 + 2 * numberOfExceptions) + .putShort(numberOfExceptions); + for (int exceptionIndex : exceptionIndexTable) { + output.putShort(exceptionIndex); + } + } + Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); + if (lastRuntimeVisibleParameterAnnotations != null) { + AnnotationWriter.putParameterAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS), + lastRuntimeVisibleParameterAnnotations, + visibleAnnotableParameterCount == 0 + ? lastRuntimeVisibleParameterAnnotations.length + : visibleAnnotableParameterCount, + output); + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + AnnotationWriter.putParameterAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS), + lastRuntimeInvisibleParameterAnnotations, + invisibleAnnotableParameterCount == 0 + ? lastRuntimeInvisibleParameterAnnotations.length + : invisibleAnnotableParameterCount, + output); + } + if (defaultValue != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT)) + .putInt(defaultValue.length) + .putByteArray(defaultValue.data, 0, defaultValue.length); + } + if (parameters != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS)) + .putInt(1 + parameters.length) + .putByte(parametersCount) + .putByteArray(parameters.data, 0, parameters.length); + } + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this method into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + attributePrototypes.addAttributes(firstCodeAttribute); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ModuleVisitor.java b/native/java/jpype.jvm.asm/org/jpype/asm/ModuleVisitor.java new file mode 100644 index 000000000..68f832745 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ModuleVisitor.java @@ -0,0 +1,185 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A visitor to visit a Java module. The methods of this class must be called in the following + * order: ( {@code visitMainClass} | ( {@code visitPackage} | {@code visitRequire} | {@code + * visitExport} | {@code visitOpen} | {@code visitUse} | {@code visitProvide} )* ) {@code visitEnd}. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public abstract class ModuleVisitor { + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + protected final int api; + + /** + * The module visitor to which this visitor must delegate method calls. May be {@literal null}. + */ + protected ModuleVisitor mv; + + /** + * Constructs a new {@link ModuleVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} + * or {@link Opcodes#ASM7}. + */ + public ModuleVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ModuleVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} + * or {@link Opcodes#ASM7}. + * @param moduleVisitor the module visitor to which this visitor must delegate method calls. May + * be null. + */ + public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); + } + this.api = api; + this.mv = moduleVisitor; + } + + /** + * Visit the main class of the current module. + * + * @param mainClass the internal name of the main class of the current module. + */ + public void visitMainClass(final String mainClass) { + if (mv != null) { + mv.visitMainClass(mainClass); + } + } + + /** + * Visit a package of the current module. + * + * @param packaze the internal name of a package. + */ + public void visitPackage(final String packaze) { + if (mv != null) { + mv.visitPackage(packaze); + } + } + + /** + * Visits a dependence of the current module. + * + * @param module the fully qualified name (using dots) of the dependence. + * @param access the access flag of the dependence among {@code ACC_TRANSITIVE}, {@code + * ACC_STATIC_PHASE}, {@code ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param version the module version at compile time, or {@literal null}. + */ + public void visitRequire(final String module, final int access, final String version) { + if (mv != null) { + mv.visitRequire(module, access, version); + } + } + + /** + * Visit an exported package of the current module. + * + * @param packaze the internal name of the exported package. + * @param access the access flag of the exported package, valid values are among {@code + * ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param modules the fully qualified names (using dots) of the modules that can access the public + * classes of the exported package, or {@literal null}. + */ + public void visitExport(final String packaze, final int access, final String... modules) { + if (mv != null) { + mv.visitExport(packaze, access, modules); + } + } + + /** + * Visit an open package of the current module. + * + * @param packaze the internal name of the opened package. + * @param access the access flag of the opened package, valid values are among {@code + * ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param modules the fully qualified names (using dots) of the modules that can use deep + * reflection to the classes of the open package, or {@literal null}. + */ + public void visitOpen(final String packaze, final int access, final String... modules) { + if (mv != null) { + mv.visitOpen(packaze, access, modules); + } + } + + /** + * Visit a service used by the current module. The name must be the internal name of an interface + * or a class. + * + * @param service the internal name of the service. + */ + public void visitUse(final String service) { + if (mv != null) { + mv.visitUse(service); + } + } + + /** + * Visit an implementation of a service. + * + * @param service the internal name of the service. + * @param providers the internal names of the implementations of the service (there is at least + * one provider). + */ + public void visitProvide(final String service, final String... providers) { + if (mv != null) { + mv.visitProvide(service, providers); + } + } + + /** + * Visits the end of the module. This method, which is the last one to be called, is used to + * inform the visitor that everything have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/ModuleWriter.java b/native/java/jpype.jvm.asm/org/jpype/asm/ModuleWriter.java new file mode 100644 index 000000000..b3d803927 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/ModuleWriter.java @@ -0,0 +1,253 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A {@link ModuleVisitor} that generates the corresponding Module, ModulePackages and + * ModuleMainClass attributes, as defined in the Java Virtual Machine Specification (JVMS). + * + * @see JVMS + * 4.7.25 + * @see JVMS + * 4.7.26 + * @see JVMS + * 4.7.27 + * @author Remi Forax + * @author Eric Bruneton + */ +final class ModuleWriter extends ModuleVisitor { + + /** Where the constants used in this AnnotationWriter must be stored. */ + private final SymbolTable symbolTable; + + /** The module_name_index field of the JVMS Module attribute. */ + private final int moduleNameIndex; + + /** The module_flags field of the JVMS Module attribute. */ + private final int moduleFlags; + + /** The module_version_index field of the JVMS Module attribute. */ + private final int moduleVersionIndex; + + /** The requires_count field of the JVMS Module attribute. */ + private int requiresCount; + + /** The binary content of the 'requires' array of the JVMS Module attribute. */ + private final ByteVector requires; + + /** The exports_count field of the JVMS Module attribute. */ + private int exportsCount; + + /** The binary content of the 'exports' array of the JVMS Module attribute. */ + private final ByteVector exports; + + /** The opens_count field of the JVMS Module attribute. */ + private int opensCount; + + /** The binary content of the 'opens' array of the JVMS Module attribute. */ + private final ByteVector opens; + + /** The uses_count field of the JVMS Module attribute. */ + private int usesCount; + + /** The binary content of the 'uses_index' array of the JVMS Module attribute. */ + private final ByteVector usesIndex; + + /** The provides_count field of the JVMS Module attribute. */ + private int providesCount; + + /** The binary content of the 'provides' array of the JVMS Module attribute. */ + private final ByteVector provides; + + /** The provides_count field of the JVMS ModulePackages attribute. */ + private int packageCount; + + /** The binary content of the 'package_index' array of the JVMS ModulePackages attribute. */ + private final ByteVector packageIndex; + + /** The main_class_index field of the JVMS ModuleMainClass attribute, or 0. */ + private int mainClassIndex; + + ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.moduleNameIndex = name; + this.moduleFlags = access; + this.moduleVersionIndex = version; + this.requires = new ByteVector(); + this.exports = new ByteVector(); + this.opens = new ByteVector(); + this.usesIndex = new ByteVector(); + this.provides = new ByteVector(); + this.packageIndex = new ByteVector(); + } + + @Override + public void visitMainClass(final String mainClass) { + this.mainClassIndex = symbolTable.addConstantClass(mainClass).index; + } + + @Override + public void visitPackage(final String packaze) { + packageIndex.putShort(symbolTable.addConstantPackage(packaze).index); + packageCount++; + } + + @Override + public void visitRequire(final String module, final int access, final String version) { + requires + .putShort(symbolTable.addConstantModule(module).index) + .putShort(access) + .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version)); + requiresCount++; + } + + @Override + public void visitExport(final String packaze, final int access, final String... modules) { + exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access); + if (modules == null) { + exports.putShort(0); + } else { + exports.putShort(modules.length); + for (String module : modules) { + exports.putShort(symbolTable.addConstantModule(module).index); + } + } + exportsCount++; + } + + @Override + public void visitOpen(final String packaze, final int access, final String... modules) { + opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access); + if (modules == null) { + opens.putShort(0); + } else { + opens.putShort(modules.length); + for (String module : modules) { + opens.putShort(symbolTable.addConstantModule(module).index); + } + } + opensCount++; + } + + @Override + public void visitUse(final String service) { + usesIndex.putShort(symbolTable.addConstantClass(service).index); + usesCount++; + } + + @Override + public void visitProvide(final String service, final String... providers) { + provides.putShort(symbolTable.addConstantClass(service).index); + provides.putShort(providers.length); + for (String provider : providers) { + provides.putShort(symbolTable.addConstantClass(provider).index); + } + providesCount++; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + /** + * Returns the number of Module, ModulePackages and ModuleMainClass attributes generated by this + * ModuleWriter. + * + * @return the number of Module, ModulePackages and ModuleMainClass attributes (between 1 and 3). + */ + int getAttributeCount() { + return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0); + } + + /** + * Returns the size of the Module, ModulePackages and ModuleMainClass attributes generated by this + * ModuleWriter. Also add the names of these attributes in the constant pool. + * + * @return the size in bytes of the Module, ModulePackages and ModuleMainClass attributes. + */ + int computeAttributesSize() { + symbolTable.addConstantUtf8(Constants.MODULE); + // 6 attribute header bytes, 6 bytes for name, flags and version, and 5 * 2 bytes for counts. + int size = + 22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length; + if (packageCount > 0) { + symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES); + // 6 attribute header bytes, and 2 bytes for package_count. + size += 8 + packageIndex.length; + } + if (mainClassIndex > 0) { + symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS); + // 6 attribute header bytes, and 2 bytes for main_class_index. + size += 8; + } + return size; + } + + /** + * Puts the Module, ModulePackages and ModuleMainClass attributes generated by this ModuleWriter + * in the given ByteVector. + * + * @param output where the attributes must be put. + */ + void putAttributes(final ByteVector output) { + // 6 bytes for name, flags and version, and 5 * 2 bytes for counts. + int moduleAttributeLength = + 16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length; + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE)) + .putInt(moduleAttributeLength) + .putShort(moduleNameIndex) + .putShort(moduleFlags) + .putShort(moduleVersionIndex) + .putShort(requiresCount) + .putByteArray(requires.data, 0, requires.length) + .putShort(exportsCount) + .putByteArray(exports.data, 0, exports.length) + .putShort(opensCount) + .putByteArray(opens.data, 0, opens.length) + .putShort(usesCount) + .putByteArray(usesIndex.data, 0, usesIndex.length) + .putShort(providesCount) + .putByteArray(provides.data, 0, provides.length); + if (packageCount > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES)) + .putInt(2 + packageIndex.length) + .putShort(packageCount) + .putByteArray(packageIndex.data, 0, packageIndex.length); + } + if (mainClassIndex > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS)) + .putInt(2) + .putShort(mainClassIndex); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Opcodes.java b/native/java/jpype.jvm.asm/org/jpype/asm/Opcodes.java new file mode 100644 index 000000000..f01d97cd5 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Opcodes.java @@ -0,0 +1,559 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * The JVM opcodes, access flags and array type codes. This interface does not define all the JVM + * opcodes because some opcodes are automatically handled. For example, the xLOAD and xSTORE opcodes + * are automatically replaced by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and + * xSTORE_n opcodes are therefore not defined in this interface. Likewise for LDC, automatically + * replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and JSR_W. + * + * @see JVMS 6 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +// DontCheck(InterfaceIsType): can't be fixed (for backward binary compatibility). +public interface Opcodes { + + // ASM API versions. + + int ASM4 = 4 << 16 | 0 << 8; + int ASM5 = 5 << 16 | 0 << 8; + int ASM6 = 6 << 16 | 0 << 8; + int ASM7 = 7 << 16 | 0 << 8; + int ASM8 = 8 << 16 | 0 << 8; + int ASM9 = 9 << 16 | 0 << 8; + + /** + * Experimental, use at your own risk. This field will be renamed when it becomes stable, this + * will break existing code using it. Only code compiled with --enable-preview can use this. + * + * @deprecated This API is experimental. + */ + @Deprecated int ASM10_EXPERIMENTAL = 1 << 24 | 10 << 16 | 0 << 8; + + /* + * Internal flags used to redirect calls to deprecated methods. For instance, if a visitOldStuff + * method in API_OLD is deprecated and replaced with visitNewStuff in API_NEW, then the + * redirection should be done as follows: + * + *

+   * public class StuffVisitor {
+   *   ...
+   *
+   *   @Deprecated public void visitOldStuff(int arg, ...) {
+   *     // SOURCE_DEPRECATED means "a call from a deprecated method using the old 'api' value".
+   *     visitNewStuf(arg | (api < API_NEW ? SOURCE_DEPRECATED : 0), ...);
+   *   }
+   *
+   *   public void visitNewStuff(int argAndSource, ...) {
+   *     if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+   *       visitOldStuff(argAndSource, ...);
+   *     } else {
+   *       int arg = argAndSource & ~SOURCE_MASK;
+   *       [ do stuff ]
+   *     }
+   *   }
+   * }
+   * 
+ * + *

If 'api' is equal to API_NEW, there are two cases: + * + *

    + *
  • call visitNewStuff: the redirection test is skipped and 'do stuff' is executed directly. + *
  • call visitOldSuff: the source is not set to SOURCE_DEPRECATED before calling + * visitNewStuff, but the redirection test is skipped anyway in visitNewStuff, which + * directly executes 'do stuff'. + *
+ * + *

If 'api' is equal to API_OLD, there are two cases: + * + *

    + *
  • call visitOldSuff: the source is set to SOURCE_DEPRECATED before calling visitNewStuff. + * Because of this visitNewStuff does not redirect back to visitOldStuff, and instead + * executes 'do stuff'. + *
  • call visitNewStuff: the call is redirected to visitOldStuff because the source is 0. + * visitOldStuff now sets the source to SOURCE_DEPRECATED and calls visitNewStuff back. This + * time visitNewStuff does not redirect the call, and instead executes 'do stuff'. + *
+ * + *

User subclasses

+ * + *

If a user subclass overrides one of these methods, there are only two cases: either 'api' is + * API_OLD and visitOldStuff is overridden (and visitNewStuff is not), or 'api' is API_NEW or + * more, and visitNewStuff is overridden (and visitOldStuff is not). Any other case is a user + * programming error. + * + *

If 'api' is equal to API_NEW, the class hierarchy is equivalent to + * + *

+   * public class StuffVisitor {
+   *   @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+   *   public void visitNewStuff(int arg, ...) { [ do stuff ] }
+   * }
+   * class UserStuffVisitor extends StuffVisitor {
+   *   @Override public void visitNewStuff(int arg, ...) {
+   *     super.visitNewStuff(int arg, ...); // optional
+   *     [ do user stuff ]
+   *   }
+   * }
+   * 
+ * + *

It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff' and 'do + * user stuff' will be executed, in this order. + * + *

If 'api' is equal to API_OLD, the class hierarchy is equivalent to + * + *

+   * public class StuffVisitor {
+   *   @Deprecated public void visitOldStuff(int arg, ...) {
+   *     visitNewStuf(arg | SOURCE_DEPRECATED, ...);
+   *   }
+   *   public void visitNewStuff(int argAndSource...) {
+   *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+   *       visitOldStuff(argAndSource, ...);
+   *     } else {
+   *       int arg = argAndSource & ~SOURCE_MASK;
+   *       [ do stuff ]
+   *     }
+   *   }
+   * }
+   * class UserStuffVisitor extends StuffVisitor {
+   *   @Override public void visitOldStuff(int arg, ...) {
+   *     super.visitOldStuff(int arg, ...); // optional
+   *     [ do user stuff ]
+   *   }
+   * }
+   * 
+ * + *

and there are two cases: + * + *

    + *
  • call visitOldSuff: in the call to super.visitOldStuff, the source is set to + * SOURCE_DEPRECATED and visitNewStuff is called. Here 'do stuff' is run because the source + * was previously set to SOURCE_DEPRECATED, and execution eventually returns to + * UserStuffVisitor.visitOldStuff, where 'do user stuff' is run. + *
  • call visitNewStuff: the call is redirected to UserStuffVisitor.visitOldStuff because the + * source is 0. Execution continues as in the previous case, resulting in 'do stuff' and 'do + * user stuff' being executed, in this order. + *
+ * + *

ASM subclasses

+ * + *

In ASM packages, subclasses of StuffVisitor can typically be sub classed again by the user, + * and can be used with API_OLD or API_NEW. Because of this, if such a subclass must override + * visitNewStuff, it must do so in the following way (and must not override visitOldStuff): + * + *

+   * public class AsmStuffVisitor extends StuffVisitor {
+   *   @Override public void visitNewStuff(int argAndSource, ...) {
+   *     if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+   *       super.visitNewStuff(argAndSource, ...);
+   *       return;
+   *     }
+   *     super.visitNewStuff(argAndSource, ...); // optional
+   *     int arg = argAndSource & ~SOURCE_MASK;
+   *     [ do other stuff ]
+   *   }
+   * }
+   * 
+ * + *

If a user class extends this with 'api' equal to API_NEW, the class hierarchy is equivalent + * to + * + *

+   * public class StuffVisitor {
+   *   @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+   *   public void visitNewStuff(int arg, ...) { [ do stuff ] }
+   * }
+   * public class AsmStuffVisitor extends StuffVisitor {
+   *   @Override public void visitNewStuff(int arg, ...) {
+   *     super.visitNewStuff(arg, ...);
+   *     [ do other stuff ]
+   *   }
+   * }
+   * class UserStuffVisitor extends StuffVisitor {
+   *   @Override public void visitNewStuff(int arg, ...) {
+   *     super.visitNewStuff(int arg, ...);
+   *     [ do user stuff ]
+   *   }
+   * }
+   * 
+ * + *

It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do + * other stuff' and 'do user stuff' will be executed, in this order. If, on the other hand, a user + * class extends AsmStuffVisitor with 'api' equal to API_OLD, the class hierarchy is equivalent to + * + *

+   * public class StuffVisitor {
+   *   @Deprecated public void visitOldStuff(int arg, ...) {
+   *     visitNewStuf(arg | SOURCE_DEPRECATED, ...);
+   *   }
+   *   public void visitNewStuff(int argAndSource, ...) {
+   *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+   *       visitOldStuff(argAndSource, ...);
+   *     } else {
+   *       int arg = argAndSource & ~SOURCE_MASK;
+   *       [ do stuff ]
+   *     }
+   *   }
+   * }
+   * public class AsmStuffVisitor extends StuffVisitor {
+   *   @Override public void visitNewStuff(int argAndSource, ...) {
+   *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+   *       super.visitNewStuff(argAndSource, ...);
+   *       return;
+   *     }
+   *     super.visitNewStuff(argAndSource, ...); // optional
+   *     int arg = argAndSource & ~SOURCE_MASK;
+   *     [ do other stuff ]
+   *   }
+   * }
+   * class UserStuffVisitor extends StuffVisitor {
+   *   @Override public void visitOldStuff(int arg, ...) {
+   *     super.visitOldStuff(arg, ...);
+   *     [ do user stuff ]
+   *   }
+   * }
+   * 
+ * + *

and, here again, whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do other + * stuff' and 'do user stuff' will be executed, in this order (exercise left to the reader). + * + *

Notes

+ * + *
    + *
  • the SOURCE_DEPRECATED flag is set only if 'api' is API_OLD, just before calling + * visitNewStuff. By hypothesis, this method is not overridden by the user. Therefore, user + * classes can never see this flag. Only ASM subclasses must take care of extracting the + * actual argument value by clearing the source flags. + *
  • because the SOURCE_DEPRECATED flag is immediately cleared in the caller, the caller can + * call visitOldStuff or visitNewStuff (in 'do stuff' and 'do user stuff') on a delegate + * visitor without any risks (breaking the redirection logic, "leaking" the flag, etc). + *
  • all the scenarios discussed above are unit tested in MethodVisitorTest. + *
+ */ + + int SOURCE_DEPRECATED = 0x100; + int SOURCE_MASK = SOURCE_DEPRECATED; + + // Java ClassFile versions (the minor version is stored in the 16 most significant bits, and the + // major version in the 16 least significant bits). + + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + int V1_8 = 0 << 16 | 52; + int V9 = 0 << 16 | 53; + int V10 = 0 << 16 | 54; + int V11 = 0 << 16 | 55; + int V12 = 0 << 16 | 56; + int V13 = 0 << 16 | 57; + int V14 = 0 << 16 | 58; + int V15 = 0 << 16 | 59; + int V16 = 0 << 16 | 60; + + /** + * Version flag indicating that the class is using 'preview' features. + * + *

{@code version & V_PREVIEW == V_PREVIEW} tests if a version is flagged with {@code + * V_PREVIEW}. + */ + int V_PREVIEW = 0xFFFF0000; + + // Access flags values, defined in + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.5-200-A.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.6-200-A.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.25 + + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method, parameter + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_OPEN = 0x0020; // module + int ACC_TRANSITIVE = 0x0020; // module requires + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_STATIC_PHASE = 0x0040; // module requires + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module * + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + int ACC_MANDATED = 0x8000; // field, method, parameter, module, module * + int ACC_MODULE = 0x8000; // class + + // ASM specific access flags. + // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard + // access flags, and also to make sure that these flags are automatically filtered out when + // written in class files (because access flags are stored using 16 bits only). + + int ACC_RECORD = 0x10000; // class + int ACC_DEPRECATED = 0x20000; // class, field, method + + // Possible values for the type operand of the NEWARRAY instruction. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.newarray. + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + // Possible values for the reference_kind field of CONSTANT_MethodHandle_info structures. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4.8. + + int H_GETFIELD = 1; + int H_GETSTATIC = 2; + int H_PUTFIELD = 3; + int H_PUTSTATIC = 4; + int H_INVOKEVIRTUAL = 5; + int H_INVOKESTATIC = 6; + int H_INVOKESPECIAL = 7; + int H_NEWINVOKESPECIAL = 8; + int H_INVOKEINTERFACE = 9; + + // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}. + + /** An expanded frame. See {@link ClassReader#EXPAND_FRAMES}. */ + int F_NEW = -1; + + /** A compressed frame with complete frame data. */ + int F_FULL = 0; + + /** + * A compressed frame where locals are the same as the locals in the previous frame, except that + * additional 1-3 locals are defined, and with an empty stack. + */ + int F_APPEND = 1; + + /** + * A compressed frame where locals are the same as the locals in the previous frame, except that + * the last 1-3 locals are absent and with an empty stack. + */ + int F_CHOP = 2; + + /** + * A compressed frame with exactly the same locals as the previous frame and with an empty stack. + */ + int F_SAME = 3; + + /** + * A compressed frame with exactly the same locals as the previous frame and with a single value + * on the stack. + */ + int F_SAME1 = 4; + + // Standard stack map frame element types, used in {@link ClassVisitor#visitFrame}. + + Integer TOP = Frame.ITEM_TOP; + Integer INTEGER = Frame.ITEM_INTEGER; + Integer FLOAT = Frame.ITEM_FLOAT; + Integer DOUBLE = Frame.ITEM_DOUBLE; + Integer LONG = Frame.ITEM_LONG; + Integer NULL = Frame.ITEM_NULL; + Integer UNINITIALIZED_THIS = Frame.ITEM_UNINITIALIZED_THIS; + + // The JVM opcode values (with the MethodVisitor method name used to visit them in comment, and + // where '-' means 'same method name as on the previous line'). + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html. + + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/RecordComponentVisitor.java b/native/java/jpype.jvm.asm/org/jpype/asm/RecordComponentVisitor.java new file mode 100644 index 000000000..973d6a85f --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/RecordComponentVisitor.java @@ -0,0 +1,152 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * A visitor to visit a record component. The methods of this class must be called in the following + * order: ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code + * visitEnd}. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public abstract class RecordComponentVisitor { + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM8} or {@link Opcodes#ASM9}. + */ + protected final int api; + + /** + * The record visitor to which this visitor must delegate method calls. May be {@literal null}. + */ + /*package-private*/ RecordComponentVisitor delegate; + + /** + * Constructs a new {@link RecordComponentVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM8} + * or {@link Opcodes#ASM9}. + */ + public RecordComponentVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link RecordComponentVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be {@link Opcodes#ASM8}. + * @param recordComponentVisitor the record component visitor to which this visitor must delegate + * method calls. May be null. + */ + public RecordComponentVisitor( + final int api, final RecordComponentVisitor recordComponentVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); + } + this.api = api; + this.delegate = recordComponentVisitor; + } + + /** + * The record visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the record visitor to which this visitor must delegate method calls or {@literal null}. + */ + public RecordComponentVisitor getDelegate() { + return delegate; + } + + /** + * Visits an annotation of the record component. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (delegate != null) { + return delegate.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the record component signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (delegate != null) { + return delegate.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the record component. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (delegate != null) { + delegate.visitAttribute(attribute); + } + } + + /** + * Visits the end of the record component. This method, which is the last one to be called, is + * used to inform the visitor that everything have been visited. + */ + public void visitEnd() { + if (delegate != null) { + delegate.visitEnd(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/RecordComponentWriter.java b/native/java/jpype.jvm.asm/org/jpype/asm/RecordComponentWriter.java new file mode 100644 index 000000000..7d5d60378 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/RecordComponentWriter.java @@ -0,0 +1,225 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +final class RecordComponentWriter extends RecordComponentVisitor { + /** Where the constants used in this RecordComponentWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the record_component_info structure, and those related to + // attributes are ordered as in Section 4.7 of the JVMS. + + /** The name_index field of the Record attribute. */ + private final int nameIndex; + + /** The descriptor_index field of the the Record attribute. */ + private final int descriptorIndex; + + /** + * The signature_index field of the Signature attribute of this record component, or 0 if there is + * no Signature attribute. + */ + private int signatureIndex; + + /** + * The last runtime visible annotation of this record component. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of this record component. The next ones can be accessed with + * the {@link Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute(Attribute)}. + * The {@link #putRecordComponentInfo(ByteVector)} method writes the attributes in the order + * defined by this list, i.e. in the reverse order specified by the user. + */ + private Attribute firstAttribute; + + /** + * Constructs a new {@link RecordComponentWriter}. + * + * @param symbolTable where the constants used in this RecordComponentWriter must be stored. + * @param name the record component name. + * @param descriptor the record component descriptor (see {@link Type}). + * @param signature the record component signature. May be {@literal null}. + */ + RecordComponentWriter( + final SymbolTable symbolTable, + final String name, + final String descriptor, + final String signature) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the FieldVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of the record component JVMS structure generated by this + * RecordComponentWriter. Also adds the names of the attributes of this record component in the + * constant pool. + * + * @return the size in bytes of the record_component_info of the Record attribute. + */ + int computeRecordComponentInfoSize() { + // name_index, descriptor_index and attributes_count fields use 6 bytes. + int size = 6; + size += Attribute.computeAttributesSize(symbolTable, 0, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the record component generated by this RecordComponentWriter into the given + * ByteVector. + * + * @param output where the record_component_info structure must be put. + */ + void putRecordComponentInfo(final ByteVector output) { + output.putShort(nameIndex).putShort(descriptorIndex); + // Compute and put the attributes_count field. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (signatureIndex != 0) { + ++attributesCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + } + output.putShort(attributesCount); + Attribute.putAttributes(symbolTable, 0, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this record component into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Symbol.java b/native/java/jpype.jvm.asm/org/jpype/asm/Symbol.java new file mode 100644 index 000000000..661d2ccbc --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Symbol.java @@ -0,0 +1,243 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * An entry of the constant pool, of the BootstrapMethods attribute, or of the (ASM specific) type + * table of a class. + * + * @see JVMS + * 4.4 + * @see JVMS + * 4.7.23 + * @author Eric Bruneton + */ +abstract class Symbol { + + // Tag values for the constant pool entries (using the same order as in the JVMS). + + /** The tag value of CONSTANT_Class_info JVMS structures. */ + static final int CONSTANT_CLASS_TAG = 7; + + /** The tag value of CONSTANT_Fieldref_info JVMS structures. */ + static final int CONSTANT_FIELDREF_TAG = 9; + + /** The tag value of CONSTANT_Methodref_info JVMS structures. */ + static final int CONSTANT_METHODREF_TAG = 10; + + /** The tag value of CONSTANT_InterfaceMethodref_info JVMS structures. */ + static final int CONSTANT_INTERFACE_METHODREF_TAG = 11; + + /** The tag value of CONSTANT_String_info JVMS structures. */ + static final int CONSTANT_STRING_TAG = 8; + + /** The tag value of CONSTANT_Integer_info JVMS structures. */ + static final int CONSTANT_INTEGER_TAG = 3; + + /** The tag value of CONSTANT_Float_info JVMS structures. */ + static final int CONSTANT_FLOAT_TAG = 4; + + /** The tag value of CONSTANT_Long_info JVMS structures. */ + static final int CONSTANT_LONG_TAG = 5; + + /** The tag value of CONSTANT_Double_info JVMS structures. */ + static final int CONSTANT_DOUBLE_TAG = 6; + + /** The tag value of CONSTANT_NameAndType_info JVMS structures. */ + static final int CONSTANT_NAME_AND_TYPE_TAG = 12; + + /** The tag value of CONSTANT_Utf8_info JVMS structures. */ + static final int CONSTANT_UTF8_TAG = 1; + + /** The tag value of CONSTANT_MethodHandle_info JVMS structures. */ + static final int CONSTANT_METHOD_HANDLE_TAG = 15; + + /** The tag value of CONSTANT_MethodType_info JVMS structures. */ + static final int CONSTANT_METHOD_TYPE_TAG = 16; + + /** The tag value of CONSTANT_Dynamic_info JVMS structures. */ + static final int CONSTANT_DYNAMIC_TAG = 17; + + /** The tag value of CONSTANT_InvokeDynamic_info JVMS structures. */ + static final int CONSTANT_INVOKE_DYNAMIC_TAG = 18; + + /** The tag value of CONSTANT_Module_info JVMS structures. */ + static final int CONSTANT_MODULE_TAG = 19; + + /** The tag value of CONSTANT_Package_info JVMS structures. */ + static final int CONSTANT_PACKAGE_TAG = 20; + + // Tag values for the BootstrapMethods attribute entries (ASM specific tag). + + /** The tag value of the BootstrapMethods attribute entries. */ + static final int BOOTSTRAP_METHOD_TAG = 64; + + // Tag values for the type table entries (ASM specific tags). + + /** The tag value of a normal type entry in the (ASM specific) type table of a class. */ + static final int TYPE_TAG = 128; + + /** + * The tag value of an {@link Frame#ITEM_UNINITIALIZED} type entry in the type table of a class. + */ + static final int UNINITIALIZED_TYPE_TAG = 129; + + /** The tag value of a merged type entry in the (ASM specific) type table of a class. */ + static final int MERGED_TYPE_TAG = 130; + + // Instance fields. + + /** + * The index of this symbol in the constant pool, in the BootstrapMethods attribute, or in the + * (ASM specific) type table of a class (depending on the {@link #tag} value). + */ + final int index; + + /** + * A tag indicating the type of this symbol. Must be one of the static tag values defined in this + * class. + */ + final int tag; + + /** + * The internal name of the owner class of this symbol. Only used for {@link + * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link + * #CONSTANT_INTERFACE_METHODREF_TAG}, and {@link #CONSTANT_METHOD_HANDLE_TAG} symbols. + */ + final String owner; + + /** + * The name of the class field or method corresponding to this symbol. Only used for {@link + * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link + * #CONSTANT_INTERFACE_METHODREF_TAG}, {@link #CONSTANT_NAME_AND_TYPE_TAG}, {@link + * #CONSTANT_METHOD_HANDLE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols. + */ + final String name; + + /** + * The string value of this symbol. This is: + * + *

    + *
  • a field or method descriptor for {@link #CONSTANT_FIELDREF_TAG}, {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG}, {@link + * #CONSTANT_NAME_AND_TYPE_TAG}, {@link #CONSTANT_METHOD_HANDLE_TAG}, {@link + * #CONSTANT_METHOD_TYPE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • an arbitrary string for {@link #CONSTANT_UTF8_TAG} and {@link #CONSTANT_STRING_TAG} + * symbols, + *
  • an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG} and {@link + * #UNINITIALIZED_TYPE_TAG} symbols, + *
  • {@literal null} for the other types of symbol. + *
+ */ + final String value; + + /** + * The numeric value of this symbol. This is: + * + *
    + *
  • the symbol's value for {@link #CONSTANT_INTEGER_TAG},{@link #CONSTANT_FLOAT_TAG}, {@link + * #CONSTANT_LONG_TAG}, {@link #CONSTANT_DOUBLE_TAG}, + *
  • the CONSTANT_MethodHandle_info reference_kind field value for {@link + * #CONSTANT_METHOD_HANDLE_TAG} symbols, + *
  • the CONSTANT_InvokeDynamic_info bootstrap_method_attr_index field value for {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • the offset of a bootstrap method in the BootstrapMethods boostrap_methods array, for + * {@link #CONSTANT_DYNAMIC_TAG} or {@link #BOOTSTRAP_METHOD_TAG} symbols, + *
  • the bytecode offset of the NEW instruction that created an {@link + * Frame#ITEM_UNINITIALIZED} type for {@link #UNINITIALIZED_TYPE_TAG} symbols, + *
  • the indices (in the class' type table) of two {@link #TYPE_TAG} source types for {@link + * #MERGED_TYPE_TAG} symbols, + *
  • 0 for the other types of symbol. + *
+ */ + final long data; + + /** + * Additional information about this symbol, generally computed lazily. Warning: the value of + * this field is ignored when comparing Symbol instances (to avoid duplicate entries in a + * SymbolTable). Therefore, this field should only contain data that can be computed from the + * other fields of this class. It contains: + * + *
    + *
  • the {@link Type#getArgumentsAndReturnSizes} of the symbol's method descriptor for {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • the index in the InnerClasses_attribute 'classes' array (plus one) corresponding to this + * class, for {@link #CONSTANT_CLASS_TAG} symbols, + *
  • the index (in the class' type table) of the merged type of the two source types for + * {@link #MERGED_TYPE_TAG} symbols, + *
  • 0 for the other types of symbol, or if this field has not been computed yet. + *
+ */ + int info; + + /** + * Constructs a new Symbol. This constructor can't be used directly because the Symbol class is + * abstract. Instead, use the factory methods of the {@link SymbolTable} class. + * + * @param index the symbol index in the constant pool, in the BootstrapMethods attribute, or in + * the (ASM specific) type table of a class (depending on 'tag'). + * @param tag the symbol type. Must be one of the static tag values defined in this class. + * @param owner The internal name of the symbol's owner class. Maybe {@literal null}. + * @param name The name of the symbol's corresponding class field or method. Maybe {@literal + * null}. + * @param value The string value of this symbol. Maybe {@literal null}. + * @param data The numeric value of this symbol. + */ + Symbol( + final int index, + final int tag, + final String owner, + final String name, + final String value, + final long data) { + this.index = index; + this.tag = tag; + this.owner = owner; + this.name = name; + this.value = value; + this.data = data; + } + + /** + * Returns the result {@link Type#getArgumentsAndReturnSizes} on {@link #value}. + * + * @return the result {@link Type#getArgumentsAndReturnSizes} on {@link #value} (memoized in + * {@link #info} for efficiency). This should only be used for {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols. + */ + int getArgumentsAndReturnSizes() { + if (info == 0) { + info = Type.getArgumentsAndReturnSizes(value); + } + return info; + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/SymbolTable.java b/native/java/jpype.jvm.asm/org/jpype/asm/SymbolTable.java new file mode 100644 index 000000000..b597b3b23 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/SymbolTable.java @@ -0,0 +1,1322 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +/** + * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type + * table entries of a class. + * + * @author Eric Bruneton + * @see JVMS + * 4.4 + * @see JVMS + * 4.7.23 + */ +final class SymbolTable { + + /** + * The ClassWriter to which this SymbolTable belongs. This is only used to get access to {@link + * ClassWriter#getCommonSuperClass} and to serialize custom attributes with {@link + * Attribute#write}. + */ + final ClassWriter classWriter; + + /** + * The ClassReader from which this SymbolTable was constructed, or {@literal null} if it was + * constructed from scratch. + */ + private final ClassReader sourceClassReader; + + /** The major version number of the class to which this symbol table belongs. */ + private int majorVersion; + + /** The internal name of the class to which this symbol table belongs. */ + private String className; + + /** + * The total number of {@link Entry} instances in {@link #entries}. This includes entries that are + * accessible (recursively) via {@link Entry#next}. + */ + private int entryCount; + + /** + * A hash set of all the entries in this SymbolTable (this includes the constant pool entries, the + * bootstrap method entries and the type table entries). Each {@link Entry} instance is stored at + * the array index given by its hash code modulo the array size. If several entries must be stored + * at the same array index, they are linked together via their {@link Entry#next} field. The + * factory methods of this class make sure that this table does not contain duplicated entries. + */ + private Entry[] entries; + + /** + * The number of constant pool items in {@link #constantPool}, plus 1. The first constant pool + * item has index 1, and long and double items count for two items. + */ + private int constantPoolCount; + + /** + * The content of the ClassFile's constant_pool JVMS structure corresponding to this SymbolTable. + * The ClassFile's constant_pool_count field is not included. + */ + private ByteVector constantPool; + + /** + * The number of bootstrap methods in {@link #bootstrapMethods}. Corresponds to the + * BootstrapMethods_attribute's num_bootstrap_methods field value. + */ + private int bootstrapMethodCount; + + /** + * The content of the BootstrapMethods attribute 'bootstrap_methods' array corresponding to this + * SymbolTable. Note that the first 6 bytes of the BootstrapMethods_attribute, and its + * num_bootstrap_methods field, are not included. + */ + private ByteVector bootstrapMethods; + + /** + * The actual number of elements in {@link #typeTable}. These elements are stored from index 0 to + * typeCount (excluded). The other array entries are empty. + */ + private int typeCount; + + /** + * An ASM specific type table used to temporarily store internal names that will not necessarily + * be stored in the constant pool. This type table is used by the control flow and data flow + * analysis algorithm used to compute stack map frames from scratch. This array stores {@link + * Symbol#TYPE_TAG} and {@link Symbol#UNINITIALIZED_TYPE_TAG}) Symbol. The type symbol at index + * {@code i} has its {@link Symbol#index} equal to {@code i} (and vice versa). + */ + private Entry[] typeTable; + + /** + * Constructs a new, empty SymbolTable for the given ClassWriter. + * + * @param classWriter a ClassWriter. + */ + SymbolTable(final ClassWriter classWriter) { + this.classWriter = classWriter; + this.sourceClassReader = null; + this.entries = new Entry[256]; + this.constantPoolCount = 1; + this.constantPool = new ByteVector(); + } + + /** + * Constructs a new SymbolTable for the given ClassWriter, initialized with the constant pool and + * bootstrap methods of the given ClassReader. + * + * @param classWriter a ClassWriter. + * @param classReader the ClassReader whose constant pool and bootstrap methods must be copied to + * initialize the SymbolTable. + */ + SymbolTable(final ClassWriter classWriter, final ClassReader classReader) { + this.classWriter = classWriter; + this.sourceClassReader = classReader; + + // Copy the constant pool binary content. + byte[] inputBytes = classReader.classFileBuffer; + int constantPoolOffset = classReader.getItem(1) - 1; + int constantPoolLength = classReader.header - constantPoolOffset; + constantPoolCount = classReader.getItemCount(); + constantPool = new ByteVector(constantPoolLength); + constantPool.putByteArray(inputBytes, constantPoolOffset, constantPoolLength); + + // Add the constant pool items in the symbol table entries. Reserve enough space in 'entries' to + // avoid too many hash set collisions (entries is not dynamically resized by the addConstant* + // method calls below), and to account for bootstrap method entries. + entries = new Entry[constantPoolCount * 2]; + char[] charBuffer = new char[classReader.getMaxStringLength()]; + boolean hasBootstrapMethods = false; + int itemIndex = 1; + while (itemIndex < constantPoolCount) { + int itemOffset = classReader.getItem(itemIndex); + int itemTag = inputBytes[itemOffset - 1]; + int nameAndTypeItemOffset; + switch (itemTag) { + case Symbol.CONSTANT_FIELDREF_TAG: + case Symbol.CONSTANT_METHODREF_TAG: + case Symbol.CONSTANT_INTERFACE_METHODREF_TAG: + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 2)); + addConstantMemberReference( + itemIndex, + itemTag, + classReader.readClass(itemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_INTEGER_TAG: + case Symbol.CONSTANT_FLOAT_TAG: + addConstantIntegerOrFloat(itemIndex, itemTag, classReader.readInt(itemOffset)); + break; + case Symbol.CONSTANT_NAME_AND_TYPE_TAG: + addConstantNameAndType( + itemIndex, + classReader.readUTF8(itemOffset, charBuffer), + classReader.readUTF8(itemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_LONG_TAG: + case Symbol.CONSTANT_DOUBLE_TAG: + addConstantLongOrDouble(itemIndex, itemTag, classReader.readLong(itemOffset)); + break; + case Symbol.CONSTANT_UTF8_TAG: + addConstantUtf8(itemIndex, classReader.readUtf(itemIndex, charBuffer)); + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + int memberRefItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 1)); + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2)); + addConstantMethodHandle( + itemIndex, + classReader.readByte(itemOffset), + classReader.readClass(memberRefItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + hasBootstrapMethods = true; + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 2)); + addConstantDynamicOrInvokeDynamicReference( + itemTag, + itemIndex, + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer), + classReader.readUnsignedShort(itemOffset)); + break; + case Symbol.CONSTANT_STRING_TAG: + case Symbol.CONSTANT_CLASS_TAG: + case Symbol.CONSTANT_METHOD_TYPE_TAG: + case Symbol.CONSTANT_MODULE_TAG: + case Symbol.CONSTANT_PACKAGE_TAG: + addConstantUtf8Reference( + itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer)); + break; + default: + throw new IllegalArgumentException(); + } + itemIndex += + (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1; + } + + // Copy the BootstrapMethods, if any. + if (hasBootstrapMethods) { + copyBootstrapMethods(classReader, charBuffer); + } + } + + /** + * Read the BootstrapMethods 'bootstrap_methods' array binary content and add them as entries of + * the SymbolTable. + * + * @param classReader the ClassReader whose bootstrap methods must be copied to initialize the + * SymbolTable. + * @param charBuffer a buffer used to read strings in the constant pool. + */ + private void copyBootstrapMethods(final ClassReader classReader, final char[] charBuffer) { + // Find attributOffset of the 'bootstrap_methods' array. + byte[] inputBytes = classReader.classFileBuffer; + int currentAttributeOffset = classReader.getFirstAttributeOffset(); + for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer); + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + bootstrapMethodCount = classReader.readUnsignedShort(currentAttributeOffset + 6); + break; + } + currentAttributeOffset += 6 + classReader.readInt(currentAttributeOffset + 2); + } + if (bootstrapMethodCount > 0) { + // Compute the offset and the length of the BootstrapMethods 'bootstrap_methods' array. + int bootstrapMethodsOffset = currentAttributeOffset + 8; + int bootstrapMethodsLength = classReader.readInt(currentAttributeOffset + 2) - 2; + bootstrapMethods = new ByteVector(bootstrapMethodsLength); + bootstrapMethods.putByteArray(inputBytes, bootstrapMethodsOffset, bootstrapMethodsLength); + + // Add each bootstrap method in the symbol table entries. + int currentOffset = bootstrapMethodsOffset; + for (int i = 0; i < bootstrapMethodCount; i++) { + int offset = currentOffset - bootstrapMethodsOffset; + int bootstrapMethodRef = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + int numBootstrapArguments = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + int hashCode = classReader.readConst(bootstrapMethodRef, charBuffer).hashCode(); + while (numBootstrapArguments-- > 0) { + int bootstrapArgument = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + hashCode ^= classReader.readConst(bootstrapArgument, charBuffer).hashCode(); + } + add(new Entry(i, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode & 0x7FFFFFFF)); + } + } + } + + /** + * Returns the ClassReader from which this SymbolTable was constructed. + * + * @return the ClassReader from which this SymbolTable was constructed, or {@literal null} if it + * was constructed from scratch. + */ + ClassReader getSource() { + return sourceClassReader; + } + + /** + * Returns the major version of the class to which this symbol table belongs. + * + * @return the major version of the class to which this symbol table belongs. + */ + int getMajorVersion() { + return majorVersion; + } + + /** + * Returns the internal name of the class to which this symbol table belongs. + * + * @return the internal name of the class to which this symbol table belongs. + */ + String getClassName() { + return className; + } + + /** + * Sets the major version and the name of the class to which this symbol table belongs. Also adds + * the class name to the constant pool. + * + * @param majorVersion a major ClassFile version number. + * @param className an internal class name. + * @return the constant pool index of a new or already existing Symbol with the given class name. + */ + int setMajorVersionAndClassName(final int majorVersion, final String className) { + this.majorVersion = majorVersion; + this.className = className; + return addConstantClass(className).index; + } + + /** + * Returns the number of items in this symbol table's constant_pool array (plus 1). + * + * @return the number of items in this symbol table's constant_pool array (plus 1). + */ + int getConstantPoolCount() { + return constantPoolCount; + } + + /** + * Returns the length in bytes of this symbol table's constant_pool array. + * + * @return the length in bytes of this symbol table's constant_pool array. + */ + int getConstantPoolLength() { + return constantPool.length; + } + + /** + * Puts this symbol table's constant_pool array in the given ByteVector, preceded by the + * constant_pool_count value. + * + * @param output where the JVMS ClassFile's constant_pool array must be put. + */ + void putConstantPool(final ByteVector output) { + output.putShort(constantPoolCount).putByteArray(constantPool.data, 0, constantPool.length); + } + + /** + * Returns the size in bytes of this symbol table's BootstrapMethods attribute. Also adds the + * attribute name in the constant pool. + * + * @return the size in bytes of this symbol table's BootstrapMethods attribute. + */ + int computeBootstrapMethodsSize() { + if (bootstrapMethods != null) { + addConstantUtf8(Constants.BOOTSTRAP_METHODS); + return 8 + bootstrapMethods.length; + } else { + return 0; + } + } + + /** + * Puts this symbol table's BootstrapMethods attribute in the given ByteVector. This includes the + * 6 attribute header bytes and the num_bootstrap_methods value. + * + * @param output where the JVMS BootstrapMethods attribute must be put. + */ + void putBootstrapMethods(final ByteVector output) { + if (bootstrapMethods != null) { + output + .putShort(addConstantUtf8(Constants.BOOTSTRAP_METHODS)) + .putInt(bootstrapMethods.length + 2) + .putShort(bootstrapMethodCount) + .putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); + } + } + + // ----------------------------------------------------------------------------------------------- + // Generic symbol table entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the list of entries which can potentially have the given hash code. + * + * @param hashCode a {@link Entry#hashCode} value. + * @return the list of entries which can potentially have the given hash code. The list is stored + * via the {@link Entry#next} field. + */ + private Entry get(final int hashCode) { + return entries[hashCode % entries.length]; + } + + /** + * Puts the given entry in the {@link #entries} hash set. This method does not check + * whether {@link #entries} already contains a similar entry or not. {@link #entries} is resized + * if necessary to avoid hash collisions (multiple entries needing to be stored at the same {@link + * #entries} array index) as much as possible, with reasonable memory usage. + * + * @param entry an Entry (which must not already be contained in {@link #entries}). + * @return the given entry + */ + private Entry put(final Entry entry) { + if (entryCount > (entries.length * 3) / 4) { + int currentCapacity = entries.length; + int newCapacity = currentCapacity * 2 + 1; + Entry[] newEntries = new Entry[newCapacity]; + for (int i = currentCapacity - 1; i >= 0; --i) { + Entry currentEntry = entries[i]; + while (currentEntry != null) { + int newCurrentEntryIndex = currentEntry.hashCode % newCapacity; + Entry nextEntry = currentEntry.next; + currentEntry.next = newEntries[newCurrentEntryIndex]; + newEntries[newCurrentEntryIndex] = currentEntry; + currentEntry = nextEntry; + } + } + entries = newEntries; + } + entryCount++; + int index = entry.hashCode % entries.length; + entry.next = entries[index]; + return entries[index] = entry; + } + + /** + * Adds the given entry in the {@link #entries} hash set. This method does not check + * whether {@link #entries} already contains a similar entry or not, and does not resize + * {@link #entries} if necessary. + * + * @param entry an Entry (which must not already be contained in {@link #entries}). + */ + private void add(final Entry entry) { + entryCount++; + int index = entry.hashCode % entries.length; + entry.next = entries[index]; + entries[index] = entry; + } + + // ----------------------------------------------------------------------------------------------- + // Constant pool entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a number or string constant to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value the value of the constant to be added to the constant pool. This parameter must be + * an {@link Integer}, {@link Byte}, {@link Character}, {@link Short}, {@link Boolean}, {@link + * Float}, {@link Long}, {@link Double}, {@link String}, {@link Type} or {@link Handle}. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstant(final Object value) { + if (value instanceof Integer) { + return addConstantInteger(((Integer) value).intValue()); + } else if (value instanceof Byte) { + return addConstantInteger(((Byte) value).intValue()); + } else if (value instanceof Character) { + return addConstantInteger(((Character) value).charValue()); + } else if (value instanceof Short) { + return addConstantInteger(((Short) value).intValue()); + } else if (value instanceof Boolean) { + return addConstantInteger(((Boolean) value).booleanValue() ? 1 : 0); + } else if (value instanceof Float) { + return addConstantFloat(((Float) value).floatValue()); + } else if (value instanceof Long) { + return addConstantLong(((Long) value).longValue()); + } else if (value instanceof Double) { + return addConstantDouble(((Double) value).doubleValue()); + } else if (value instanceof String) { + return addConstantString((String) value); + } else if (value instanceof Type) { + Type type = (Type) value; + int typeSort = type.getSort(); + if (typeSort == Type.OBJECT) { + return addConstantClass(type.getInternalName()); + } else if (typeSort == Type.METHOD) { + return addConstantMethodType(type.getDescriptor()); + } else { // type is a primitive or array type. + return addConstantClass(type.getDescriptor()); + } + } else if (value instanceof Handle) { + Handle handle = (Handle) value; + return addConstantMethodHandle( + handle.getTag(), + handle.getOwner(), + handle.getName(), + handle.getDesc(), + handle.isInterface()); + } else if (value instanceof ConstantDynamic) { + ConstantDynamic constantDynamic = (ConstantDynamic) value; + return addConstantDynamic( + constantDynamic.getName(), + constantDynamic.getDescriptor(), + constantDynamic.getBootstrapMethod(), + constantDynamic.getBootstrapMethodArgumentsUnsafe()); + } else { + throw new IllegalArgumentException("value " + value); + } + } + + /** + * Adds a CONSTANT_Class_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value the internal name of a class. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantClass(final String value) { + return addConstantUtf8Reference(Symbol.CONSTANT_CLASS_TAG, value); + } + + /** + * Adds a CONSTANT_Fieldref_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param owner the internal name of a class. + * @param name a field name. + * @param descriptor a field descriptor. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantFieldref(final String owner, final String name, final String descriptor) { + return addConstantMemberReference(Symbol.CONSTANT_FIELDREF_TAG, owner, name, descriptor); + } + + /** + * Adds a CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to the constant pool of this + * symbol table. Does nothing if the constant pool already contains a similar item. + * + * @param owner the internal name of a class. + * @param name a method name. + * @param descriptor a method descriptor. + * @param isInterface whether owner is an interface or not. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodref( + final String owner, final String name, final String descriptor, final boolean isInterface) { + int tag = isInterface ? Symbol.CONSTANT_INTERFACE_METHODREF_TAG : Symbol.CONSTANT_METHODREF_TAG; + return addConstantMemberReference(tag, owner, name, descriptor); + } + + /** + * Adds a CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to + * the constant pool of this symbol table. Does nothing if the constant pool already contains a + * similar item. + * + * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG} + * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}. + * @param owner the internal name of a class. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @return a new or already existing Symbol with the given value. + */ + private Entry addConstantMemberReference( + final int tag, final String owner, final String name, final String descriptor) { + int hashCode = hash(tag, owner, name, descriptor); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.owner.equals(owner) + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + constantPool.put122( + tag, addConstantClass(owner).index, addConstantNameAndType(name, descriptor)); + return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, 0, hashCode)); + } + + /** + * Adds a new CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info + * to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG} + * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}. + * @param owner the internal name of a class. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantMemberReference( + final int index, + final int tag, + final String owner, + final String name, + final String descriptor) { + add(new Entry(index, tag, owner, name, descriptor, 0, hash(tag, owner, name, descriptor))); + } + + /** + * Adds a CONSTANT_String_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a string. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantString(final String value) { + return addConstantUtf8Reference(Symbol.CONSTANT_STRING_TAG, value); + } + + /** + * Adds a CONSTANT_Integer_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value an int. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantInteger(final int value) { + return addConstantIntegerOrFloat(Symbol.CONSTANT_INTEGER_TAG, value); + } + + /** + * Adds a CONSTANT_Float_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a float. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantFloat(final float value) { + return addConstantIntegerOrFloat(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value)); + } + + /** + * Adds a CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol table. + * Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}. + * @param value an int or float. + * @return a constant pool constant with the given tag and primitive values. + */ + private Symbol addConstantIntegerOrFloat(final int tag, final int value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) { + return entry; + } + entry = entry.next; + } + constantPool.putByte(tag).putInt(value); + return put(new Entry(constantPoolCount++, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol + * table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}. + * @param value an int or float. + */ + private void addConstantIntegerOrFloat(final int index, final int tag, final int value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + /** + * Adds a CONSTANT_Long_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a long. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantLong(final long value) { + return addConstantLongOrDouble(Symbol.CONSTANT_LONG_TAG, value); + } + + /** + * Adds a CONSTANT_Double_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a double. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantDouble(final double value) { + return addConstantLongOrDouble(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value)); + } + + /** + * Adds a CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol table. + * Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}. + * @param value a long or double. + * @return a constant pool constant with the given tag and primitive values. + */ + private Symbol addConstantLongOrDouble(final int tag, final long value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) { + return entry; + } + entry = entry.next; + } + int index = constantPoolCount; + constantPool.putByte(tag).putLong(value); + constantPoolCount += 2; + return put(new Entry(index, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol + * table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}. + * @param value a long or double. + */ + private void addConstantLongOrDouble(final int index, final int tag, final long value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + /** + * Adds a CONSTANT_NameAndType_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @return a new or already existing Symbol with the given value. + */ + int addConstantNameAndType(final String name, final String descriptor) { + final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG; + int hashCode = hash(tag, name, descriptor); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry.index; + } + entry = entry.next; + } + constantPool.put122(tag, addConstantUtf8(name), addConstantUtf8(descriptor)); + return put(new Entry(constantPoolCount++, tag, name, descriptor, hashCode)).index; + } + + /** + * Adds a new CONSTANT_NameAndType_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantNameAndType(final int index, final String name, final String descriptor) { + final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG; + add(new Entry(index, tag, name, descriptor, hash(tag, name, descriptor))); + } + + /** + * Adds a CONSTANT_Utf8_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a string. + * @return a new or already existing Symbol with the given value. + */ + int addConstantUtf8(final String value) { + int hashCode = hash(Symbol.CONSTANT_UTF8_TAG, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.CONSTANT_UTF8_TAG + && entry.hashCode == hashCode + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + constantPool.putByte(Symbol.CONSTANT_UTF8_TAG).putUTF8(value); + return put(new Entry(constantPoolCount++, Symbol.CONSTANT_UTF8_TAG, value, hashCode)).index; + } + + /** + * Adds a new CONSTANT_String_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param value a string. + */ + private void addConstantUtf8(final int index, final String value) { + add(new Entry(index, Symbol.CONSTANT_UTF8_TAG, value, hash(Symbol.CONSTANT_UTF8_TAG, value))); + } + + /** + * Adds a CONSTANT_MethodHandle_info to the constant pool of this symbol table. Does nothing if + * the constant pool already contains a similar item. + * + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of a class of interface. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @param isInterface whether owner is an interface or not. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodHandle( + final int referenceKind, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + // Note that we don't need to include isInterface in the hash computation, because it is + // redundant with owner (we can't have the same owner with different isInterface values). + int hashCode = hash(tag, owner, name, descriptor, referenceKind); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.data == referenceKind + && entry.owner.equals(owner) + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + if (referenceKind <= Opcodes.H_PUTSTATIC) { + constantPool.put112(tag, referenceKind, addConstantFieldref(owner, name, descriptor).index); + } else { + constantPool.put112( + tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index); + } + return put( + new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode)); + } + + /** + * Adds a new CONSTANT_MethodHandle_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of a class of interface. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantMethodHandle( + final int index, + final int referenceKind, + final String owner, + final String name, + final String descriptor) { + final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + int hashCode = hash(tag, owner, name, descriptor, referenceKind); + add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode)); + } + + /** + * Adds a CONSTANT_MethodType_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param methodDescriptor a method descriptor. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodType(final String methodDescriptor) { + return addConstantUtf8Reference(Symbol.CONSTANT_METHOD_TYPE_TAG, methodDescriptor); + } + + /** + * Adds a CONSTANT_Dynamic_info to the constant pool of this symbol table. Also adds the related + * bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the constant + * pool already contains a similar item. + * + * @param name a method name. + * @param descriptor a field descriptor. + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments); + return addConstantDynamicOrInvokeDynamicReference( + Symbol.CONSTANT_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index); + } + + /** + * Adds a CONSTANT_InvokeDynamic_info to the constant pool of this symbol table. Also adds the + * related bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param name a method name. + * @param descriptor a method descriptor. + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantInvokeDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments); + return addConstantDynamicOrInvokeDynamicReference( + Symbol.CONSTANT_INVOKE_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index); + } + + /** + * Adds a CONSTANT_Dynamic or a CONSTANT_InvokeDynamic_info to the constant pool of this symbol + * table. Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link + * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}. + * @param name a method name. + * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG) or a method descriptor for + * CONSTANT_INVOKE_DYNAMIC_TAG. + * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addConstantDynamicOrInvokeDynamicReference( + final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) { + int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.data == bootstrapMethodIndex + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor)); + return put( + new Entry( + constantPoolCount++, tag, null, name, descriptor, bootstrapMethodIndex, hashCode)); + } + + /** + * Adds a new CONSTANT_Dynamic_info or CONSTANT_InvokeDynamic_info to the constant pool of this + * symbol table. + * + * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link + * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}. + * @param index the constant pool index of the new Symbol. + * @param name a method name. + * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG or a method descriptor for + * CONSTANT_INVOKE_DYNAMIC_TAG. + * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute. + */ + private void addConstantDynamicOrInvokeDynamicReference( + final int tag, + final int index, + final String name, + final String descriptor, + final int bootstrapMethodIndex) { + int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex); + add(new Entry(index, tag, null, name, descriptor, bootstrapMethodIndex, hashCode)); + } + + /** + * Adds a CONSTANT_Module_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param moduleName a fully qualified name (using dots) of a module. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantModule(final String moduleName) { + return addConstantUtf8Reference(Symbol.CONSTANT_MODULE_TAG, moduleName); + } + + /** + * Adds a CONSTANT_Package_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param packageName the internal name of a package. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantPackage(final String packageName) { + return addConstantUtf8Reference(Symbol.CONSTANT_PACKAGE_TAG, packageName); + } + + /** + * Adds a CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info, + * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. Does + * nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link + * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link + * Symbol#CONSTANT_PACKAGE_TAG}. + * @param value an internal class name, an arbitrary string, a method descriptor, a module or a + * package name, depending on tag. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addConstantUtf8Reference(final int tag, final String value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.value.equals(value)) { + return entry; + } + entry = entry.next; + } + constantPool.put12(tag, addConstantUtf8(value)); + return put(new Entry(constantPoolCount++, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info, + * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link + * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link + * Symbol#CONSTANT_PACKAGE_TAG}. + * @param value an internal class name, an arbitrary string, a method descriptor, a module or a + * package name, depending on tag. + */ + private void addConstantUtf8Reference(final int index, final int tag, final String value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + // ----------------------------------------------------------------------------------------------- + // Bootstrap method entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if + * the BootstrapMethods already contains a similar bootstrap method. + * + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addBootstrapMethod( + final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) { + ByteVector bootstrapMethodsAttribute = bootstrapMethods; + if (bootstrapMethodsAttribute == null) { + bootstrapMethodsAttribute = bootstrapMethods = new ByteVector(); + } + + // The bootstrap method arguments can be Constant_Dynamic values, which reference other + // bootstrap methods. We must therefore add the bootstrap method arguments to the constant pool + // and BootstrapMethods attribute first, so that the BootstrapMethods attribute is not modified + // while adding the given bootstrap method to it, in the rest of this method. + int numBootstrapArguments = bootstrapMethodArguments.length; + int[] bootstrapMethodArgumentIndexes = new int[numBootstrapArguments]; + for (int i = 0; i < numBootstrapArguments; i++) { + bootstrapMethodArgumentIndexes[i] = addConstant(bootstrapMethodArguments[i]).index; + } + + // Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to + // compare it with existing ones, and will be reverted below if there is already a similar + // bootstrap method. + int bootstrapMethodOffset = bootstrapMethodsAttribute.length; + bootstrapMethodsAttribute.putShort( + addConstantMethodHandle( + bootstrapMethodHandle.getTag(), + bootstrapMethodHandle.getOwner(), + bootstrapMethodHandle.getName(), + bootstrapMethodHandle.getDesc(), + bootstrapMethodHandle.isInterface()) + .index); + + bootstrapMethodsAttribute.putShort(numBootstrapArguments); + for (int i = 0; i < numBootstrapArguments; i++) { + bootstrapMethodsAttribute.putShort(bootstrapMethodArgumentIndexes[i]); + } + + // Compute the length and the hash code of the bootstrap method. + int bootstrapMethodlength = bootstrapMethodsAttribute.length - bootstrapMethodOffset; + int hashCode = bootstrapMethodHandle.hashCode(); + for (Object bootstrapMethodArgument : bootstrapMethodArguments) { + hashCode ^= bootstrapMethodArgument.hashCode(); + } + hashCode &= 0x7FFFFFFF; + + // Add the bootstrap method to the symbol table or revert the above changes. + return addBootstrapMethod(bootstrapMethodOffset, bootstrapMethodlength, hashCode); + } + + /** + * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if + * the BootstrapMethods already contains a similar bootstrap method (more precisely, reverts the + * content of {@link #bootstrapMethods} to remove the last, duplicate bootstrap method). + * + * @param offset the offset of the last bootstrap method in {@link #bootstrapMethods}, in bytes. + * @param length the length of this bootstrap method in {@link #bootstrapMethods}, in bytes. + * @param hashCode the hash code of this bootstrap method. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) { + final byte[] bootstrapMethodsData = bootstrapMethods.data; + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) { + int otherOffset = (int) entry.data; + boolean isSameBootstrapMethod = true; + for (int i = 0; i < length; ++i) { + if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) { + isSameBootstrapMethod = false; + break; + } + } + if (isSameBootstrapMethod) { + bootstrapMethods.length = offset; // Revert to old position. + return entry; + } + } + entry = entry.next; + } + return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode)); + } + + // ----------------------------------------------------------------------------------------------- + // Type table entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the type table element whose index is given. + * + * @param typeIndex a type table index. + * @return the type table element whose index is given. + */ + Symbol getType(final int typeIndex) { + return typeTable[typeIndex]; + } + + /** + * Adds a type in the type table of this symbol table. Does nothing if the type table already + * contains a similar type. + * + * @param value an internal class name. + * @return the index of a new or already existing type Symbol with the given value. + */ + int addType(final String value) { + int hashCode = hash(Symbol.TYPE_TAG, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.TYPE_TAG && entry.hashCode == hashCode && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addTypeInternal(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode)); + } + + /** + * Adds an {@link Frame#ITEM_UNINITIALIZED} type in the type table of this symbol table. Does + * nothing if the type table already contains a similar type. + * + * @param value an internal class name. + * @param bytecodeOffset the bytecode offset of the NEW instruction that created this {@link + * Frame#ITEM_UNINITIALIZED} type value. + * @return the index of a new or already existing type Symbol with the given value. + */ + int addUninitializedType(final String value, final int bytecodeOffset) { + int hashCode = hash(Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.UNINITIALIZED_TYPE_TAG + && entry.hashCode == hashCode + && entry.data == bytecodeOffset + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addTypeInternal( + new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode)); + } + + /** + * Adds a merged type in the type table of this symbol table. Does nothing if the type table + * already contains a similar type. + * + * @param typeTableIndex1 a {@link Symbol#TYPE_TAG} type, specified by its index in the type + * table. + * @param typeTableIndex2 another {@link Symbol#TYPE_TAG} type, specified by its index in the type + * table. + * @return the index of a new or already existing {@link Symbol#TYPE_TAG} type Symbol, + * corresponding to the common super class of the given types. + */ + int addMergedType(final int typeTableIndex1, final int typeTableIndex2) { + long data = + typeTableIndex1 < typeTableIndex2 + ? typeTableIndex1 | (((long) typeTableIndex2) << 32) + : typeTableIndex2 | (((long) typeTableIndex1) << 32); + int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.MERGED_TYPE_TAG && entry.hashCode == hashCode && entry.data == data) { + return entry.info; + } + entry = entry.next; + } + String type1 = typeTable[typeTableIndex1].value; + String type2 = typeTable[typeTableIndex2].value; + int commonSuperTypeIndex = addType(classWriter.getCommonSuperClass(type1, type2)); + put(new Entry(typeCount, Symbol.MERGED_TYPE_TAG, data, hashCode)).info = commonSuperTypeIndex; + return commonSuperTypeIndex; + } + + /** + * Adds the given type Symbol to {@link #typeTable}. + * + * @param entry a {@link Symbol#TYPE_TAG} or {@link Symbol#UNINITIALIZED_TYPE_TAG} type symbol. + * The index of this Symbol must be equal to the current value of {@link #typeCount}. + * @return the index in {@link #typeTable} where the given type was added, which is also equal to + * entry's index by hypothesis. + */ + private int addTypeInternal(final Entry entry) { + if (typeTable == null) { + typeTable = new Entry[16]; + } + if (typeCount == typeTable.length) { + Entry[] newTypeTable = new Entry[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTypeTable, 0, typeTable.length); + typeTable = newTypeTable; + } + typeTable[typeCount++] = entry; + return put(entry).index; + } + + // ----------------------------------------------------------------------------------------------- + // Static helper methods to compute hash codes. + // ----------------------------------------------------------------------------------------------- + + private static int hash(final int tag, final int value) { + return 0x7FFFFFFF & (tag + value); + } + + private static int hash(final int tag, final long value) { + return 0x7FFFFFFF & (tag + (int) value + (int) (value >>> 32)); + } + + private static int hash(final int tag, final String value) { + return 0x7FFFFFFF & (tag + value.hashCode()); + } + + private static int hash(final int tag, final String value1, final int value2) { + return 0x7FFFFFFF & (tag + value1.hashCode() + value2); + } + + private static int hash(final int tag, final String value1, final String value2) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode()); + } + + private static int hash( + final int tag, final String value1, final String value2, final int value3) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * (value3 + 1)); + } + + private static int hash( + final int tag, final String value1, final String value2, final String value3) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode()); + } + + private static int hash( + final int tag, + final String value1, + final String value2, + final String value3, + final int value4) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4); + } + + /** + * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields + * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid + * duplicate symbols). See {@link #entries}. + * + * @author Eric Bruneton + */ + private static class Entry extends Symbol { + + /** The hash code of this entry. */ + final int hashCode; + + /** + * Another entry (and so on recursively) having the same hash code (modulo the size of {@link + * #entries}) as this one. + */ + Entry next; + + Entry( + final int index, + final int tag, + final String owner, + final String name, + final String value, + final long data, + final int hashCode) { + super(index, tag, owner, name, value, data); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, value, /* data = */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final long data, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, value, data); + this.hashCode = hashCode; + } + + Entry( + final int index, final int tag, final String name, final String value, final int hashCode) { + super(index, tag, /* owner = */ null, name, value, /* data = */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final long data, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, /* value = */ null, data); + this.hashCode = hashCode; + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/Type.java b/native/java/jpype.jvm.asm/org/jpype/asm/Type.java new file mode 100644 index 000000000..811d501d6 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/Type.java @@ -0,0 +1,895 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * A Java field or method type. This class can be used to make it easier to manipulate type and + * method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ +public final class Type { + + /** The sort of the {@code void} type. See {@link #getSort}. */ + public static final int VOID = 0; + + /** The sort of the {@code boolean} type. See {@link #getSort}. */ + public static final int BOOLEAN = 1; + + /** The sort of the {@code char} type. See {@link #getSort}. */ + public static final int CHAR = 2; + + /** The sort of the {@code byte} type. See {@link #getSort}. */ + public static final int BYTE = 3; + + /** The sort of the {@code short} type. See {@link #getSort}. */ + public static final int SHORT = 4; + + /** The sort of the {@code int} type. See {@link #getSort}. */ + public static final int INT = 5; + + /** The sort of the {@code float} type. See {@link #getSort}. */ + public static final int FLOAT = 6; + + /** The sort of the {@code long} type. See {@link #getSort}. */ + public static final int LONG = 7; + + /** The sort of the {@code double} type. See {@link #getSort}. */ + public static final int DOUBLE = 8; + + /** The sort of array reference types. See {@link #getSort}. */ + public static final int ARRAY = 9; + + /** The sort of object reference types. See {@link #getSort}. */ + public static final int OBJECT = 10; + + /** The sort of method types. See {@link #getSort}. */ + public static final int METHOD = 11; + + /** The (private) sort of object reference types represented with an internal name. */ + private static final int INTERNAL = 12; + + /** The descriptors of the primitive types. */ + private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD"; + + /** The {@code void} type. */ + public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1); + + /** The {@code boolean} type. */ + public static final Type BOOLEAN_TYPE = + new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1); + + /** The {@code char} type. */ + public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1); + + /** The {@code byte} type. */ + public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1); + + /** The {@code short} type. */ + public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1); + + /** The {@code int} type. */ + public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1); + + /** The {@code float} type. */ + public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1); + + /** The {@code long} type. */ + public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1); + + /** The {@code double} type. */ + public static final Type DOUBLE_TYPE = + new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1); + + // ----------------------------------------------------------------------------------------------- + // Fields + // ----------------------------------------------------------------------------------------------- + + /** + * The sort of this type. Either {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, + * {@link #SHORT}, {@link #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, + * {@link #OBJECT}, {@link #METHOD} or {@link #INTERNAL}. + */ + private final int sort; + + /** + * A buffer containing the value of this field or method type. This value is an internal name for + * {@link #OBJECT} and {@link #INTERNAL} types, and a field or method descriptor in the other + * cases. + * + *

For {@link #OBJECT} types, this field also contains the descriptor: the characters in + * [{@link #valueBegin},{@link #valueEnd}) contain the internal name, and those in [{@link + * #valueBegin} - 1, {@link #valueEnd} + 1) contain the descriptor. + */ + private final String valueBuffer; + + /** + * The beginning index, inclusive, of the value of this Java field or method type in {@link + * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types, + * and a field or method descriptor in the other cases. + */ + private final int valueBegin; + + /** + * The end index, exclusive, of the value of this Java field or method type in {@link + * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types, + * and a field or method descriptor in the other cases. + */ + private final int valueEnd; + + /** + * Constructs a reference type. + * + * @param sort the sort of this type, see {@link #sort}. + * @param valueBuffer a buffer containing the value of this field or method type. + * @param valueBegin the beginning index, inclusive, of the value of this field or method type in + * valueBuffer. + * @param valueEnd the end index, exclusive, of the value of this field or method type in + * valueBuffer. + */ + private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) { + this.sort = sort; + this.valueBuffer = valueBuffer; + this.valueBegin = valueBegin; + this.valueEnd = valueEnd; + } + + // ----------------------------------------------------------------------------------------------- + // Methods to get Type(s) from a descriptor, a reflected Method or Constructor, other types, etc. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the {@link Type} corresponding to the given type descriptor. + * + * @param typeDescriptor a field or method type descriptor. + * @return the {@link Type} corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getTypeInternal(typeDescriptor, 0, typeDescriptor.length()); + } + + /** + * Returns the {@link Type} corresponding to the given class. + * + * @param clazz a class. + * @return the {@link Type} corresponding to the given class. + */ + public static Type getType(final Class clazz) { + if (clazz.isPrimitive()) { + if (clazz == Integer.TYPE) { + return INT_TYPE; + } else if (clazz == Void.TYPE) { + return VOID_TYPE; + } else if (clazz == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (clazz == Byte.TYPE) { + return BYTE_TYPE; + } else if (clazz == Character.TYPE) { + return CHAR_TYPE; + } else if (clazz == Short.TYPE) { + return SHORT_TYPE; + } else if (clazz == Double.TYPE) { + return DOUBLE_TYPE; + } else if (clazz == Float.TYPE) { + return FLOAT_TYPE; + } else if (clazz == Long.TYPE) { + return LONG_TYPE; + } else { + throw new AssertionError(); + } + } else { + return getType(getDescriptor(clazz)); + } + } + + /** + * Returns the method {@link Type} corresponding to the given constructor. + * + * @param constructor a {@link Constructor} object. + * @return the method {@link Type} corresponding to the given constructor. + */ + public static Type getType(final Constructor constructor) { + return getType(getConstructorDescriptor(constructor)); + } + + /** + * Returns the method {@link Type} corresponding to the given method. + * + * @param method a {@link Method} object. + * @return the method {@link Type} corresponding to the given method. + */ + public static Type getType(final Method method) { + return getType(getMethodDescriptor(method)); + } + + /** + * Returns the type of the elements of this array type. This method should only be used for an + * array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + final int numDimensions = getDimensions(); + return getTypeInternal(valueBuffer, valueBegin + numDimensions, valueEnd); + } + + /** + * Returns the {@link Type} corresponding to the given internal name. + * + * @param internalName an internal name. + * @return the {@link Type} corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + return new Type( + internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length()); + } + + /** + * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to + * Type.getType(methodDescriptor). + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length()); + } + + /** + * Returns the method {@link Type} corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the method {@link Type} corresponding to the given argument and return types. + */ + public static Type getMethodType(final Type returnType, final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the argument types of methods of this type. This method should only be used for method + * types. + * + * @return the argument types of methods of this type. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + + /** + * Returns the {@link Type} values corresponding to the argument types of the given method + * descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} values corresponding to the argument types of the given method + * descriptor. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + // First step: compute the number of argument types in methodDescriptor. + int numArgumentTypes = 0; + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Parse the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + ++numArgumentTypes; + } + + // Second step: create a Type instance for each argument type. + Type[] argumentTypes = new Type[numArgumentTypes]; + // Skip the first character, which is always a '('. + currentOffset = 1; + // Parse and create the argument types, one at each loop iteration. + int currentArgumentTypeIndex = 0; + while (methodDescriptor.charAt(currentOffset) != ')') { + final int currentArgumentTypeOffset = currentOffset; + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + argumentTypes[currentArgumentTypeIndex++] = + getTypeInternal(methodDescriptor, currentArgumentTypeOffset, currentOffset); + } + return argumentTypes; + } + + /** + * Returns the {@link Type} values corresponding to the argument types of the given method. + * + * @param method a method. + * @return the {@link Type} values corresponding to the argument types of the given method. + */ + public static Type[] getArgumentTypes(final Method method) { + Class[] classes = method.getParameterTypes(); + Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * Returns the return type of methods of this type. This method should only be used for method + * types. + * + * @return the return type of methods of this type. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + + /** + * Returns the {@link Type} corresponding to the return type of the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} corresponding to the return type of the given method descriptor. + */ + public static Type getReturnType(final String methodDescriptor) { + return getTypeInternal( + methodDescriptor, getReturnTypeOffset(methodDescriptor), methodDescriptor.length()); + } + + /** + * Returns the {@link Type} corresponding to the return type of the given method. + * + * @param method a method. + * @return the {@link Type} corresponding to the return type of the given method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Returns the start index of the return type of the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the start index of the return type of the given method descriptor. + */ + static int getReturnTypeOffset(final String methodDescriptor) { + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Skip the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + } + return currentOffset + 1; + } + + /** + * Returns the {@link Type} corresponding to the given field or method descriptor. + * + * @param descriptorBuffer a buffer containing the field or method descriptor. + * @param descriptorBegin the beginning index, inclusive, of the field or method descriptor in + * descriptorBuffer. + * @param descriptorEnd the end index, exclusive, of the field or method descriptor in + * descriptorBuffer. + * @return the {@link Type} corresponding to the given type descriptor. + */ + private static Type getTypeInternal( + final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) { + switch (descriptorBuffer.charAt(descriptorBegin)) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + return new Type(ARRAY, descriptorBuffer, descriptorBegin, descriptorEnd); + case 'L': + return new Type(OBJECT, descriptorBuffer, descriptorBegin + 1, descriptorEnd - 1); + case '(': + return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd); + default: + throw new IllegalArgumentException(); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to get class names, internal names or descriptors. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the binary name of the class corresponding to this type. This method must not be used + * on method types. + * + * @return the binary name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuilder stringBuilder = new StringBuilder(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + stringBuilder.append("[]"); + } + return stringBuilder.toString(); + case OBJECT: + case INTERNAL: + return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.'); + default: + throw new AssertionError(); + } + } + + /** + * Returns the internal name of the class corresponding to this object or array type. The internal + * name of a class is its fully qualified name (as returned by Class.getName(), where '.' are + * replaced by '/'). This method should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + */ + public String getInternalName() { + return valueBuffer.substring(valueBegin, valueEnd); + } + + /** + * Returns the internal name of the given class. The internal name of a class is its fully + * qualified name, as returned by Class.getName(), where '.' are replaced by '/'. + * + * @param clazz an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class clazz) { + return clazz.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to this type. + * + * @return the descriptor corresponding to this type. + */ + public String getDescriptor() { + if (sort == OBJECT) { + return valueBuffer.substring(valueBegin - 1, valueEnd + 1); + } else if (sort == INTERNAL) { + return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';'; + } else { + return valueBuffer.substring(valueBegin, valueEnd); + } + } + + /** + * Returns the descriptor corresponding to the given class. + * + * @param clazz an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class clazz) { + StringBuilder stringBuilder = new StringBuilder(); + appendDescriptor(clazz, stringBuilder); + return stringBuilder.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param constructor a {@link Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor constructor) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + Class[] parameters = constructor.getParameterTypes(); + for (Class parameter : parameters) { + appendDescriptor(parameter, stringBuilder); + } + return stringBuilder.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the descriptor corresponding to the given argument and return types. + */ + public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + for (Type argumentType : argumentTypes) { + argumentType.appendDescriptor(stringBuilder); + } + stringBuilder.append(')'); + returnType.appendDescriptor(stringBuilder); + return stringBuilder.toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param method a {@link Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method method) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + Class[] parameters = method.getParameterTypes(); + for (Class parameter : parameters) { + appendDescriptor(parameter, stringBuilder); + } + stringBuilder.append(')'); + appendDescriptor(method.getReturnType(), stringBuilder); + return stringBuilder.toString(); + } + + /** + * Appends the descriptor corresponding to this type to the given string buffer. + * + * @param stringBuilder the string builder to which the descriptor must be appended. + */ + private void appendDescriptor(final StringBuilder stringBuilder) { + if (sort == OBJECT) { + stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1); + } else if (sort == INTERNAL) { + stringBuilder.append('L').append(valueBuffer, valueBegin, valueEnd).append(';'); + } else { + stringBuilder.append(valueBuffer, valueBegin, valueEnd); + } + } + + /** + * Appends the descriptor of the given class to the given string builder. + * + * @param clazz the class whose descriptor must be computed. + * @param stringBuilder the string builder to which the descriptor must be appended. + */ + private static void appendDescriptor(final Class clazz, final StringBuilder stringBuilder) { + Class currentClass = clazz; + while (currentClass.isArray()) { + stringBuilder.append('['); + currentClass = currentClass.getComponentType(); + } + if (currentClass.isPrimitive()) { + char descriptor; + if (currentClass == Integer.TYPE) { + descriptor = 'I'; + } else if (currentClass == Void.TYPE) { + descriptor = 'V'; + } else if (currentClass == Boolean.TYPE) { + descriptor = 'Z'; + } else if (currentClass == Byte.TYPE) { + descriptor = 'B'; + } else if (currentClass == Character.TYPE) { + descriptor = 'C'; + } else if (currentClass == Short.TYPE) { + descriptor = 'S'; + } else if (currentClass == Double.TYPE) { + descriptor = 'D'; + } else if (currentClass == Float.TYPE) { + descriptor = 'F'; + } else if (currentClass == Long.TYPE) { + descriptor = 'J'; + } else { + throw new AssertionError(); + } + stringBuilder.append(descriptor); + } else { + stringBuilder.append('L').append(getInternalName(currentClass)).append(';'); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to get the sort, dimension, size, and opcodes corresponding to a Type or descriptor. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the sort of this type. + * + * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link + * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or + * {@link #METHOD}. + */ + public int getSort() { + return sort == INTERNAL ? OBJECT : sort; + } + + /** + * Returns the number of dimensions of this array type. This method should only be used for an + * array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int numDimensions = 1; + while (valueBuffer.charAt(valueBegin + numDimensions) == '[') { + numDimensions++; + } + return numDimensions; + } + + /** + * Returns the size of values of this type. This method must not be used for method types. + * + * @return the size of values of this type, i.e., 2 for {@code long} and {@code double}, 0 for + * {@code void} and 1 otherwise. + */ + public int getSize() { + switch (sort) { + case VOID: + return 0; + case BOOLEAN: + case CHAR: + case BYTE: + case SHORT: + case INT: + case FLOAT: + case ARRAY: + case OBJECT: + case INTERNAL: + return 1; + case LONG: + case DOUBLE: + return 2; + default: + throw new AssertionError(); + } + } + + /** + * Returns the size of the arguments and of the return value of methods of this type. This method + * should only be used for method types. + * + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code + * i >> 2}, and returnSize to {@code i & 0x03}). + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param methodDescriptor a method descriptor. + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code + * i >> 2}, and returnSize to {@code i & 0x03}). + */ + public static int getArgumentsAndReturnSizes(final String methodDescriptor) { + int argumentsSize = 1; + // Skip the first character, which is always a '('. + int currentOffset = 1; + int currentChar = methodDescriptor.charAt(currentOffset); + // Parse the argument types and compute their size, one at a each loop iteration. + while (currentChar != ')') { + if (currentChar == 'J' || currentChar == 'D') { + currentOffset++; + argumentsSize += 2; + } else { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + argumentsSize += 1; + } + currentChar = methodDescriptor.charAt(currentOffset); + } + currentChar = methodDescriptor.charAt(currentOffset + 1); + if (currentChar == 'V') { + return argumentsSize << 2; + } else { + int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1; + return argumentsSize << 2 | returnSize; + } + } + + /** + * Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for + * method types. + * + * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD, + * IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and + * IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to this {@link Type}. For + * example, if this type is {@code float} and {@code opcode} is IRETURN, this method returns + * FRETURN. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + switch (sort) { + case BOOLEAN: + case BYTE: + return opcode + (Opcodes.BALOAD - Opcodes.IALOAD); + case CHAR: + return opcode + (Opcodes.CALOAD - Opcodes.IALOAD); + case SHORT: + return opcode + (Opcodes.SALOAD - Opcodes.IALOAD); + case INT: + return opcode; + case FLOAT: + return opcode + (Opcodes.FALOAD - Opcodes.IALOAD); + case LONG: + return opcode + (Opcodes.LALOAD - Opcodes.IALOAD); + case DOUBLE: + return opcode + (Opcodes.DALOAD - Opcodes.IALOAD); + case ARRAY: + case OBJECT: + case INTERNAL: + return opcode + (Opcodes.AALOAD - Opcodes.IALOAD); + case METHOD: + case VOID: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); + } + } else { + switch (sort) { + case VOID: + if (opcode != Opcodes.IRETURN) { + throw new UnsupportedOperationException(); + } + return Opcodes.RETURN; + case BOOLEAN: + case BYTE: + case CHAR: + case SHORT: + case INT: + return opcode; + case FLOAT: + return opcode + (Opcodes.FRETURN - Opcodes.IRETURN); + case LONG: + return opcode + (Opcodes.LRETURN - Opcodes.IRETURN); + case DOUBLE: + return opcode + (Opcodes.DRETURN - Opcodes.IRETURN); + case ARRAY: + case OBJECT: + case INTERNAL: + if (opcode != Opcodes.ILOAD && opcode != Opcodes.ISTORE && opcode != Opcodes.IRETURN) { + throw new UnsupportedOperationException(); + } + return opcode + (Opcodes.ARETURN - Opcodes.IRETURN); + case METHOD: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Equals, hashCode and toString. + // ----------------------------------------------------------------------------------------------- + + /** + * Tests if the given object is equal to this type. + * + * @param object the object to be compared to this type. + * @return {@literal true} if the given object is equal to this type. + */ + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof Type)) { + return false; + } + Type other = (Type) object; + if ((sort == INTERNAL ? OBJECT : sort) != (other.sort == INTERNAL ? OBJECT : other.sort)) { + return false; + } + int begin = valueBegin; + int end = valueEnd; + int otherBegin = other.valueBegin; + int otherEnd = other.valueEnd; + // Compare the values. + if (end - begin != otherEnd - otherBegin) { + return false; + } + for (int i = begin, j = otherBegin; i < end; i++, j++) { + if (valueBuffer.charAt(i) != other.valueBuffer.charAt(j)) { + return false; + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hashCode = 13 * (sort == INTERNAL ? OBJECT : sort); + if (sort >= ARRAY) { + for (int i = valueBegin, end = valueEnd; i < end; i++) { + hashCode = 17 * (hashCode + valueBuffer.charAt(i)); + } + } + return hashCode; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/TypePath.java b/native/java/jpype.jvm.asm/org/jpype/asm/TypePath.java new file mode 100644 index 000000000..f0023d379 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/TypePath.java @@ -0,0 +1,201 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package org.jpype.asm; + +/** + * The path to a type argument, wildcard bound, array element type, or static inner type within an + * enclosing type. + * + * @author Eric Bruneton + */ +public final class TypePath { + + /** A type path step that steps into the element type of an array type. See {@link #getStep}. */ + public static final int ARRAY_ELEMENT = 0; + + /** A type path step that steps into the nested type of a class type. See {@link #getStep}. */ + public static final int INNER_TYPE = 1; + + /** A type path step that steps into the bound of a wildcard type. See {@link #getStep}. */ + public static final int WILDCARD_BOUND = 2; + + /** A type path step that steps into a type argument of a generic type. See {@link #getStep}. */ + public static final int TYPE_ARGUMENT = 3; + + /** + * The byte array where the 'type_path' structure - as defined in the Java Virtual Machine + * Specification (JVMS) - corresponding to this TypePath is stored. The first byte of the + * structure in this array is given by {@link #typePathOffset}. + * + * @see JVMS + * 4.7.20.2 + */ + private final byte[] typePathContainer; + + /** The offset of the first byte of the type_path JVMS structure in {@link #typePathContainer}. */ + private final int typePathOffset; + + /** + * Constructs a new TypePath. + * + * @param typePathContainer a byte array containing a type_path JVMS structure. + * @param typePathOffset the offset of the first byte of the type_path structure in + * typePathContainer. + */ + TypePath(final byte[] typePathContainer, final int typePathOffset) { + this.typePathContainer = typePathContainer; + this.typePathOffset = typePathOffset; + } + + /** + * Returns the length of this path, i.e. its number of steps. + * + * @return the length of this path. + */ + public int getLength() { + // path_length is stored in the first byte of a type_path. + return typePathContainer[typePathOffset]; + } + + /** + * Returns the value of the given step of this path. + * + * @param index an index between 0 and {@link #getLength()}, exclusive. + * @return one of {@link #ARRAY_ELEMENT}, {@link #INNER_TYPE}, {@link #WILDCARD_BOUND}, or {@link + * #TYPE_ARGUMENT}. + */ + public int getStep(final int index) { + // Returns the type_path_kind of the path element of the given index. + return typePathContainer[typePathOffset + 2 * index + 1]; + } + + /** + * Returns the index of the type argument that the given step is stepping into. This method should + * only be used for steps whose value is {@link #TYPE_ARGUMENT}. + * + * @param index an index between 0 and {@link #getLength()}, exclusive. + * @return the index of the type argument that the given step is stepping into. + */ + public int getStepArgument(final int index) { + // Returns the type_argument_index of the path element of the given index. + return typePathContainer[typePathOffset + 2 * index + 2]; + } + + /** + * Converts a type path in string form, in the format used by {@link #toString()}, into a TypePath + * object. + * + * @param typePath a type path in string form, in the format used by {@link #toString()}. May be + * {@literal null} or empty. + * @return the corresponding TypePath object, or {@literal null} if the path is empty. + */ + public static TypePath fromString(final String typePath) { + if (typePath == null || typePath.length() == 0) { + return null; + } + int typePathLength = typePath.length(); + ByteVector output = new ByteVector(typePathLength); + output.putByte(0); + int typePathIndex = 0; + while (typePathIndex < typePathLength) { + char c = typePath.charAt(typePathIndex++); + if (c == '[') { + output.put11(ARRAY_ELEMENT, 0); + } else if (c == '.') { + output.put11(INNER_TYPE, 0); + } else if (c == '*') { + output.put11(WILDCARD_BOUND, 0); + } else if (c >= '0' && c <= '9') { + int typeArg = c - '0'; + while (typePathIndex < typePathLength) { + c = typePath.charAt(typePathIndex++); + if (c >= '0' && c <= '9') { + typeArg = typeArg * 10 + c - '0'; + } else if (c == ';') { + break; + } else { + throw new IllegalArgumentException(); + } + } + output.put11(TYPE_ARGUMENT, typeArg); + } else { + throw new IllegalArgumentException(); + } + } + output.data[0] = (byte) (output.length / 2); + return new TypePath(output.data, 0); + } + + /** + * Returns a string representation of this type path. {@link #ARRAY_ELEMENT} steps are represented + * with '[', {@link #INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND} steps with '*' and {@link + * #TYPE_ARGUMENT} steps with their type argument index in decimal form followed by ';'. + */ + @Override + public String toString() { + int length = getLength(); + StringBuilder result = new StringBuilder(length * 2); + for (int i = 0; i < length; ++i) { + switch (getStep(i)) { + case ARRAY_ELEMENT: + result.append('['); + break; + case INNER_TYPE: + result.append('.'); + break; + case WILDCARD_BOUND: + result.append('*'); + break; + case TYPE_ARGUMENT: + result.append(getStepArgument(i)).append(';'); + break; + default: + throw new AssertionError(); + } + } + return result.toString(); + } + + /** + * Puts the type_path JVMS structure corresponding to the given TypePath into the given + * ByteVector. + * + * @param typePath a TypePath instance, or {@literal null} for empty paths. + * @param output where the type path must be put. + */ + static void put(final TypePath typePath, final ByteVector output) { + if (typePath == null) { + output.putByte(0); + } else { + int length = typePath.typePathContainer[typePath.typePathOffset] * 2 + 1; + output.putByteArray(typePath.typePathContainer, typePath.typePathOffset, length); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/TypeReference.java b/native/java/jpype.jvm.asm/org/jpype/asm/TypeReference.java new file mode 100644 index 000000000..f24669dc1 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/TypeReference.java @@ -0,0 +1,436 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package org.jpype.asm; + +/** + * A reference to a type appearing in a class, field or method declaration, or on an instruction. + * Such a reference designates the part of the class where the referenced type is appearing (e.g. an + * 'extends', 'implements' or 'throws' clause, a 'new' instruction, a 'catch' clause, a type cast, a + * local variable declaration, etc). + * + * @author Eric Bruneton + */ +public class TypeReference { + + /** + * The sort of type references that target a type parameter of a generic class. See {@link + * #getSort}. + */ + public static final int CLASS_TYPE_PARAMETER = 0x00; + + /** + * The sort of type references that target a type parameter of a generic method. See {@link + * #getSort}. + */ + public static final int METHOD_TYPE_PARAMETER = 0x01; + + /** + * The sort of type references that target the super class of a class or one of the interfaces it + * implements. See {@link #getSort}. + */ + public static final int CLASS_EXTENDS = 0x10; + + /** + * The sort of type references that target a bound of a type parameter of a generic class. See + * {@link #getSort}. + */ + public static final int CLASS_TYPE_PARAMETER_BOUND = 0x11; + + /** + * The sort of type references that target a bound of a type parameter of a generic method. See + * {@link #getSort}. + */ + public static final int METHOD_TYPE_PARAMETER_BOUND = 0x12; + + /** The sort of type references that target the type of a field. See {@link #getSort}. */ + public static final int FIELD = 0x13; + + /** The sort of type references that target the return type of a method. See {@link #getSort}. */ + public static final int METHOD_RETURN = 0x14; + + /** + * The sort of type references that target the receiver type of a method. See {@link #getSort}. + */ + public static final int METHOD_RECEIVER = 0x15; + + /** + * The sort of type references that target the type of a formal parameter of a method. See {@link + * #getSort}. + */ + public static final int METHOD_FORMAL_PARAMETER = 0x16; + + /** + * The sort of type references that target the type of an exception declared in the throws clause + * of a method. See {@link #getSort}. + */ + public static final int THROWS = 0x17; + + /** + * The sort of type references that target the type of a local variable in a method. See {@link + * #getSort}. + */ + public static final int LOCAL_VARIABLE = 0x40; + + /** + * The sort of type references that target the type of a resource variable in a method. See {@link + * #getSort}. + */ + public static final int RESOURCE_VARIABLE = 0x41; + + /** + * The sort of type references that target the type of the exception of a 'catch' clause in a + * method. See {@link #getSort}. + */ + public static final int EXCEPTION_PARAMETER = 0x42; + + /** + * The sort of type references that target the type declared in an 'instanceof' instruction. See + * {@link #getSort}. + */ + public static final int INSTANCEOF = 0x43; + + /** + * The sort of type references that target the type of the object created by a 'new' instruction. + * See {@link #getSort}. + */ + public static final int NEW = 0x44; + + /** + * The sort of type references that target the receiver type of a constructor reference. See + * {@link #getSort}. + */ + public static final int CONSTRUCTOR_REFERENCE = 0x45; + + /** + * The sort of type references that target the receiver type of a method reference. See {@link + * #getSort}. + */ + public static final int METHOD_REFERENCE = 0x46; + + /** + * The sort of type references that target the type declared in an explicit or implicit cast + * instruction. See {@link #getSort}. + */ + public static final int CAST = 0x47; + + /** + * The sort of type references that target a type parameter of a generic constructor in a + * constructor call. See {@link #getSort}. + */ + public static final int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48; + + /** + * The sort of type references that target a type parameter of a generic method in a method call. + * See {@link #getSort}. + */ + public static final int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49; + + /** + * The sort of type references that target a type parameter of a generic constructor in a + * constructor reference. See {@link #getSort}. + */ + public static final int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A; + + /** + * The sort of type references that target a type parameter of a generic method in a method + * reference. See {@link #getSort}. + */ + public static final int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B; + + /** + * The target_type and target_info structures - as defined in the Java Virtual Machine + * Specification (JVMS) - corresponding to this type reference. target_type uses one byte, and all + * the target_info union fields use up to 3 bytes (except localvar_target, handled with the + * specific method {@link MethodVisitor#visitLocalVariableAnnotation}). Thus, both structures can + * be stored in an int. + * + *

This int field stores target_type (called the TypeReference 'sort' in the public API of this + * class) in its most significant byte, followed by the target_info fields. Depending on + * target_type, 1, 2 or even 3 least significant bytes of this field are unused. target_info + * fields which reference bytecode offsets are set to 0 (these offsets are ignored in ClassReader, + * and recomputed in MethodWriter). + * + * @see JVMS + * 4.7.20 + * @see JVMS + * 4.7.20.1 + */ + private final int targetTypeAndInfo; + + /** + * Constructs a new TypeReference. + * + * @param typeRef the int encoded value of the type reference, as received in a visit method + * related to type annotations, such as {@link ClassVisitor#visitTypeAnnotation}. + */ + public TypeReference(final int typeRef) { + this.targetTypeAndInfo = typeRef; + } + + /** + * Returns a type reference of the given sort. + * + * @param sort one of {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link + * #LOCAL_VARIABLE}, {@link #RESOURCE_VARIABLE}, {@link #INSTANCEOF}, {@link #NEW}, {@link + * #CONSTRUCTOR_REFERENCE}, or {@link #METHOD_REFERENCE}. + * @return a type reference of the given sort. + */ + public static TypeReference newTypeReference(final int sort) { + return new TypeReference(sort << 24); + } + + /** + * Returns a reference to a type parameter of a generic class or method. + * + * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}. + * @param paramIndex the type parameter index. + * @return a reference to the given generic class or method type parameter. + */ + public static TypeReference newTypeParameterReference(final int sort, final int paramIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to a type parameter bound of a generic class or method. + * + * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}. + * @param paramIndex the type parameter index. + * @param boundIndex the type bound index within the above type parameters. + * @return a reference to the given generic class or method type parameter bound. + */ + public static TypeReference newTypeParameterBoundReference( + final int sort, final int paramIndex, final int boundIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16) | (boundIndex << 8)); + } + + /** + * Returns a reference to the super class or to an interface of the 'implements' clause of a + * class. + * + * @param itfIndex the index of an interface in the 'implements' clause of a class, or -1 to + * reference the super class of the class. + * @return a reference to the given super type of a class. + */ + public static TypeReference newSuperTypeReference(final int itfIndex) { + return new TypeReference((CLASS_EXTENDS << 24) | ((itfIndex & 0xFFFF) << 8)); + } + + /** + * Returns a reference to the type of a formal parameter of a method. + * + * @param paramIndex the formal parameter index. + * @return a reference to the type of the given method formal parameter. + */ + public static TypeReference newFormalParameterReference(final int paramIndex) { + return new TypeReference((METHOD_FORMAL_PARAMETER << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to the type of an exception, in a 'throws' clause of a method. + * + * @param exceptionIndex the index of an exception in a 'throws' clause of a method. + * @return a reference to the type of the given exception. + */ + public static TypeReference newExceptionReference(final int exceptionIndex) { + return new TypeReference((THROWS << 24) | (exceptionIndex << 8)); + } + + /** + * Returns a reference to the type of the exception declared in a 'catch' clause of a method. + * + * @param tryCatchBlockIndex the index of a try catch block (using the order in which they are + * visited with visitTryCatchBlock). + * @return a reference to the type of the given exception. + */ + public static TypeReference newTryCatchReference(final int tryCatchBlockIndex) { + return new TypeReference((EXCEPTION_PARAMETER << 24) | (tryCatchBlockIndex << 8)); + } + + /** + * Returns a reference to the type of a type argument in a constructor or method call or + * reference. + * + * @param sort one of {@link #CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link + * #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link + * #METHOD_REFERENCE_TYPE_ARGUMENT}. + * @param argIndex the type argument index. + * @return a reference to the type of the given type argument. + */ + public static TypeReference newTypeArgumentReference(final int sort, final int argIndex) { + return new TypeReference((sort << 24) | argIndex); + } + + /** + * Returns the sort of this type reference. + * + * @return one of {@link #CLASS_TYPE_PARAMETER}, {@link #METHOD_TYPE_PARAMETER}, {@link + * #CLASS_EXTENDS}, {@link #CLASS_TYPE_PARAMETER_BOUND}, {@link #METHOD_TYPE_PARAMETER_BOUND}, + * {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link + * #METHOD_FORMAL_PARAMETER}, {@link #THROWS}, {@link #LOCAL_VARIABLE}, {@link + * #RESOURCE_VARIABLE}, {@link #EXCEPTION_PARAMETER}, {@link #INSTANCEOF}, {@link #NEW}, + * {@link #CONSTRUCTOR_REFERENCE}, {@link #METHOD_REFERENCE}, {@link #CAST}, {@link + * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}. + */ + public int getSort() { + return targetTypeAndInfo >>> 24; + } + + /** + * Returns the index of the type parameter referenced by this type reference. This method must + * only be used for type references whose sort is {@link #CLASS_TYPE_PARAMETER}, {@link + * #METHOD_TYPE_PARAMETER}, {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link + * #METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter index. + */ + public int getTypeParameterIndex() { + return (targetTypeAndInfo & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the type parameter bound, within the type parameter {@link + * #getTypeParameterIndex}, referenced by this type reference. This method must only be used for + * type references whose sort is {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link + * #METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter bound index. + */ + public int getTypeParameterBoundIndex() { + return (targetTypeAndInfo & 0x0000FF00) >> 8; + } + + /** + * Returns the index of the "super type" of a class that is referenced by this type reference. + * This method must only be used for type references whose sort is {@link #CLASS_EXTENDS}. + * + * @return the index of an interface in the 'implements' clause of a class, or -1 if this type + * reference references the type of the super class. + */ + public int getSuperTypeIndex() { + return (short) ((targetTypeAndInfo & 0x00FFFF00) >> 8); + } + + /** + * Returns the index of the formal parameter whose type is referenced by this type reference. This + * method must only be used for type references whose sort is {@link #METHOD_FORMAL_PARAMETER}. + * + * @return a formal parameter index. + */ + public int getFormalParameterIndex() { + return (targetTypeAndInfo & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the exception, in a 'throws' clause of a method, whose type is referenced + * by this type reference. This method must only be used for type references whose sort is {@link + * #THROWS}. + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getExceptionIndex() { + return (targetTypeAndInfo & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the try catch block (using the order in which they are visited with + * visitTryCatchBlock), whose 'catch' type is referenced by this type reference. This method must + * only be used for type references whose sort is {@link #EXCEPTION_PARAMETER} . + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getTryCatchBlockIndex() { + return (targetTypeAndInfo & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the type argument referenced by this type reference. This method must only + * be used for type references whose sort is {@link #CAST}, {@link + * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}. + * + * @return a type parameter index. + */ + public int getTypeArgumentIndex() { + return targetTypeAndInfo & 0xFF; + } + + /** + * Returns the int encoded value of this type reference, suitable for use in visit methods related + * to type annotations, like visitTypeAnnotation. + * + * @return the int encoded value of this type reference. + */ + public int getValue() { + return targetTypeAndInfo; + } + + /** + * Puts the given target_type and target_info JVMS structures into the given ByteVector. + * + * @param targetTypeAndInfo a target_type and a target_info structures encoded as in {@link + * #targetTypeAndInfo}. LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported. + * @param output where the type reference must be put. + */ + static void putTarget(final int targetTypeAndInfo, final ByteVector output) { + switch (targetTypeAndInfo >>> 24) { + case CLASS_TYPE_PARAMETER: + case METHOD_TYPE_PARAMETER: + case METHOD_FORMAL_PARAMETER: + output.putShort(targetTypeAndInfo >>> 16); + break; + case FIELD: + case METHOD_RETURN: + case METHOD_RECEIVER: + output.putByte(targetTypeAndInfo >>> 24); + break; + case CAST: + case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case METHOD_INVOCATION_TYPE_ARGUMENT: + case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case METHOD_REFERENCE_TYPE_ARGUMENT: + output.putInt(targetTypeAndInfo); + break; + case CLASS_EXTENDS: + case CLASS_TYPE_PARAMETER_BOUND: + case METHOD_TYPE_PARAMETER_BOUND: + case THROWS: + case EXCEPTION_PARAMETER: + case INSTANCEOF: + case NEW: + case CONSTRUCTOR_REFERENCE: + case METHOD_REFERENCE: + output.put12(targetTypeAndInfo >>> 24, (targetTypeAndInfo & 0xFFFF00) >> 8); + break; + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/package.html b/native/java/jpype.jvm.asm/org/jpype/asm/package.html new file mode 100644 index 000000000..3ccd4a6d8 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/package.html @@ -0,0 +1,78 @@ + + + + + Package org.jpype.asm + + +Provides a small and fast bytecode manipulation framework. + +

+The ASM framework is organized +around the {@link org.jpype.asm.ClassVisitor ClassVisitor}, +{@link org.jpype.asm.FieldVisitor FieldVisitor}, +{@link org.jpype.asm.MethodVisitor MethodVisitor} and +{@link org.jpype.asm.AnnotationVisitor AnnotationVisitor} abstract classes, +which allow one to visit the fields, methods and annotations of a class, +including the bytecode instructions of each method. + +

+In addition to these main abstract classes, ASM provides a {@link +org.jpype.asm.ClassReader ClassReader} class, that can parse an +existing class and make a given visitor visit it. ASM also provides +a {@link org.jpype.asm.ClassWriter ClassWriter} class, which is +a visitor that generates Java class files. + +

+In order to generate a class from scratch, only the {@link +org.jpype.asm.ClassWriter ClassWriter} class is necessary. Indeed, +in order to generate a class, one must just call its visitXxx +methods with the appropriate arguments to generate the desired fields +and methods. + +

+In order to modify existing classes, one must use a {@link +org.jpype.asm.ClassReader ClassReader} class to analyze +the original class, a class modifier, and a {@link org.jpype.asm.ClassWriter +ClassWriter} to construct the modified class. The class modifier +is just a {@link org.jpype.asm.ClassVisitor ClassVisitor} +that delegates most of the work to another {@link org.jpype.asm.ClassVisitor +ClassVisitor}, but that sometimes changes some parameter values, +or call additional methods, in order to implement the desired +modification process. In order to make it easier to implement such +class modifiers, the {@link org.jpype.asm.ClassVisitor +ClassVisitor} and {@link org.jpype.asm.MethodVisitor MethodVisitor} +classes delegate by default all the method calls they receive to an +optional visitor. + +@since ASM 1.3 + + diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureReader.java b/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureReader.java new file mode 100644 index 000000000..59f1f56f9 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureReader.java @@ -0,0 +1,252 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm.signature; + +/** + * A parser for signature literals, as defined in the Java Virtual Machine Specification (JVMS), to + * visit them with a SignatureVisitor. + * + * @see JVMS + * 4.7.9.1 + * @author Thomas Hallgren + * @author Eric Bruneton + */ +public class SignatureReader { + + /** The JVMS signature to be read. */ + private final String signatureValue; + + /** + * Constructs a {@link SignatureReader} for the given signature. + * + * @param signature A JavaTypeSignature, ClassSignature or MethodSignature. + */ + public SignatureReader(final String signature) { + this.signatureValue = signature; + } + + /** + * Makes the given visitor visit the signature of this {@link SignatureReader}. This signature is + * the one specified in the constructor (see {@link #SignatureReader}). This method is intended to + * be called on a {@link SignatureReader} that was created using a ClassSignature (such as + * the signature parameter of the {@link org.objectweb.asm.ClassVisitor#visit} + * method) or a MethodSignature (such as the signature parameter of the {@link + * org.objectweb.asm.ClassVisitor#visitMethod} method). + * + * @param signatureVistor the visitor that must visit this signature. + */ + public void accept(final SignatureVisitor signatureVistor) { + String signature = this.signatureValue; + int length = signature.length(); + int offset; // Current offset in the parsed signature (parsed from left to right). + char currentChar; // The signature character at 'offset', or just before. + + // If the signature starts with '<', it starts with TypeParameters, i.e. a formal type parameter + // identifier, followed by one or more pair ':',ReferenceTypeSignature (for its class bound and + // interface bounds). + if (signature.charAt(0) == '<') { + // Invariant: offset points to the second character of a formal type parameter name at the + // beginning of each iteration of the loop below. + offset = 2; + do { + // The formal type parameter name is everything between offset - 1 and the first ':'. + int classBoundStartOffset = signature.indexOf(':', offset); + signatureVistor.visitFormalTypeParameter( + signature.substring(offset - 1, classBoundStartOffset)); + + // If the character after the ':' class bound marker is not the start of a + // ReferenceTypeSignature, it means the class bound is empty (which is a valid case). + offset = classBoundStartOffset + 1; + currentChar = signature.charAt(offset); + if (currentChar == 'L' || currentChar == '[' || currentChar == 'T') { + offset = parseType(signature, offset, signatureVistor.visitClassBound()); + } + + // While the character after the class bound or after the last parsed interface bound + // is ':', we need to parse another interface bound. + while ((currentChar = signature.charAt(offset++)) == ':') { + offset = parseType(signature, offset, signatureVistor.visitInterfaceBound()); + } + + // At this point a TypeParameter has been fully parsed, and we need to parse the next one + // (note that currentChar is now the first character of the next TypeParameter, and that + // offset points to the second character), unless the character just after this + // TypeParameter signals the end of the TypeParameters. + } while (currentChar != '>'); + } else { + offset = 0; + } + + // If the (optional) TypeParameters is followed by '(' this means we are parsing a + // MethodSignature, which has JavaTypeSignature type inside parentheses, followed by a Result + // type and optional ThrowsSignature types. + if (signature.charAt(offset) == '(') { + offset++; + while (signature.charAt(offset) != ')') { + offset = parseType(signature, offset, signatureVistor.visitParameterType()); + } + // Use offset + 1 to skip ')'. + offset = parseType(signature, offset + 1, signatureVistor.visitReturnType()); + while (offset < length) { + // Use offset + 1 to skip the first character of a ThrowsSignature, i.e. '^'. + offset = parseType(signature, offset + 1, signatureVistor.visitExceptionType()); + } + } else { + // Otherwise we are parsing a ClassSignature (by hypothesis on the method input), which has + // one or more ClassTypeSignature for the super class and the implemented interfaces. + offset = parseType(signature, offset, signatureVistor.visitSuperclass()); + while (offset < length) { + offset = parseType(signature, offset, signatureVistor.visitInterface()); + } + } + } + + /** + * Makes the given visitor visit the signature of this {@link SignatureReader}. This signature is + * the one specified in the constructor (see {@link #SignatureReader}). This method is intended to + * be called on a {@link SignatureReader} that was created using a JavaTypeSignature, such + * as the signature parameter of the {@link + * org.objectweb.asm.ClassVisitor#visitField} or {@link + * org.objectweb.asm.MethodVisitor#visitLocalVariable} methods. + * + * @param signatureVisitor the visitor that must visit this signature. + */ + public void acceptType(final SignatureVisitor signatureVisitor) { + parseType(signatureValue, 0, signatureVisitor); + } + + /** + * Parses a JavaTypeSignature and makes the given visitor visit it. + * + * @param signature a string containing the signature that must be parsed. + * @param startOffset index of the first character of the signature to parsed. + * @param signatureVisitor the visitor that must visit this signature. + * @return the index of the first character after the parsed signature. + */ + private static int parseType( + final String signature, final int startOffset, final SignatureVisitor signatureVisitor) { + int offset = startOffset; // Current offset in the parsed signature. + char currentChar = signature.charAt(offset++); // The signature character at 'offset'. + + // Switch based on the first character of the JavaTypeSignature, which indicates its kind. + switch (currentChar) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + case 'F': + case 'J': + case 'D': + case 'V': + // Case of a BaseType or a VoidDescriptor. + signatureVisitor.visitBaseType(currentChar); + return offset; + + case '[': + // Case of an ArrayTypeSignature, a '[' followed by a JavaTypeSignature. + return parseType(signature, offset, signatureVisitor.visitArrayType()); + + case 'T': + // Case of TypeVariableSignature, an identifier between 'T' and ';'. + int endOffset = signature.indexOf(';', offset); + signatureVisitor.visitTypeVariable(signature.substring(offset, endOffset)); + return endOffset + 1; + + case 'L': + // Case of a ClassTypeSignature, which ends with ';'. + // These signatures have a main class type followed by zero or more inner class types + // (separated by '.'). Each can have type arguments, inside '<' and '>'. + int start = offset; // The start offset of the currently parsed main or inner class name. + boolean visited = false; // Whether the currently parsed class name has been visited. + boolean inner = false; // Whether we are currently parsing an inner class type. + // Parses the signature, one character at a time. + while (true) { + currentChar = signature.charAt(offset++); + if (currentChar == '.' || currentChar == ';') { + // If a '.' or ';' is encountered, this means we have fully parsed the main class name + // or an inner class name. This name may already have been visited it is was followed by + // type arguments between '<' and '>'. If not, we need to visit it here. + if (!visited) { + String name = signature.substring(start, offset - 1); + if (inner) { + signatureVisitor.visitInnerClassType(name); + } else { + signatureVisitor.visitClassType(name); + } + } + // If we reached the end of the ClassTypeSignature return, otherwise start the parsing + // of a new class name, which is necessarily an inner class name. + if (currentChar == ';') { + signatureVisitor.visitEnd(); + break; + } + start = offset; + visited = false; + inner = true; + } else if (currentChar == '<') { + // If a '<' is encountered, this means we have fully parsed the main class name or an + // inner class name, and that we now need to parse TypeArguments. First, we need to + // visit the parsed class name. + String name = signature.substring(start, offset - 1); + if (inner) { + signatureVisitor.visitInnerClassType(name); + } else { + signatureVisitor.visitClassType(name); + } + visited = true; + // Now, parse the TypeArgument(s), one at a time. + while ((currentChar = signature.charAt(offset)) != '>') { + switch (currentChar) { + case '*': + // Unbounded TypeArgument. + ++offset; + signatureVisitor.visitTypeArgument(); + break; + case '+': + case '-': + // Extends or Super TypeArgument. Use offset + 1 to skip the '+' or '-'. + offset = + parseType( + signature, offset + 1, signatureVisitor.visitTypeArgument(currentChar)); + break; + default: + // Instanceof TypeArgument. The '=' is implicit. + offset = parseType(signature, offset, signatureVisitor.visitTypeArgument('=')); + break; + } + } + } + } + return offset; + + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureVisitor.java b/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureVisitor.java new file mode 100644 index 000000000..270f1d902 --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureVisitor.java @@ -0,0 +1,209 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm.signature; + +import org.jpype.asm.Opcodes; + +/** + * A visitor to visit a generic signature. The methods of this interface must be called in one of + * the three following orders (the last one is the only valid order for a {@link SignatureVisitor} + * that is returned by a method of this interface): + * + *

    + *
  • ClassSignature = ( {@code visitFormalTypeParameter} {@code visitClassBound}? {@code + * visitInterfaceBound}* )* ({@code visitSuperclass} {@code visitInterface}* ) + *
  • MethodSignature = ( {@code visitFormalTypeParameter} {@code visitClassBound}? {@code + * visitInterfaceBound}* )* ({@code visitParameterType}* {@code visitReturnType} {@code + * visitExceptionType}* ) + *
  • TypeSignature = {@code visitBaseType} | {@code visitTypeVariable} | {@code + * visitArrayType} | ( {@code visitClassType} {@code visitTypeArgument}* ( {@code + * visitInnerClassType} {@code visitTypeArgument}* )* {@code visitEnd} ) ) + *
+ * + * @author Thomas Hallgren + * @author Eric Bruneton + */ +public abstract class SignatureVisitor { + + /** Wildcard for an "extends" type argument. */ + public static final char EXTENDS = '+'; + + /** Wildcard for a "super" type argument. */ + public static final char SUPER = '-'; + + /** Wildcard for a normal type argument. */ + public static final char INSTANCEOF = '='; + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + protected final int api; + + /** + * Constructs a new {@link SignatureVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link + * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + public SignatureVisitor(final int api) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + this.api = api; + } + + /** + * Visits a formal type parameter. + * + * @param name the name of the formal parameter. + */ + public void visitFormalTypeParameter(final String name) {} + + /** + * Visits the class bound of the last visited formal type parameter. + * + * @return a non null visitor to visit the signature of the class bound. + */ + public SignatureVisitor visitClassBound() { + return this; + } + + /** + * Visits an interface bound of the last visited formal type parameter. + * + * @return a non null visitor to visit the signature of the interface bound. + */ + public SignatureVisitor visitInterfaceBound() { + return this; + } + + /** + * Visits the type of the super class. + * + * @return a non null visitor to visit the signature of the super class type. + */ + public SignatureVisitor visitSuperclass() { + return this; + } + + /** + * Visits the type of an interface implemented by the class. + * + * @return a non null visitor to visit the signature of the interface type. + */ + public SignatureVisitor visitInterface() { + return this; + } + + /** + * Visits the type of a method parameter. + * + * @return a non null visitor to visit the signature of the parameter type. + */ + public SignatureVisitor visitParameterType() { + return this; + } + + /** + * Visits the return type of the method. + * + * @return a non null visitor to visit the signature of the return type. + */ + public SignatureVisitor visitReturnType() { + return this; + } + + /** + * Visits the type of a method exception. + * + * @return a non null visitor to visit the signature of the exception type. + */ + public SignatureVisitor visitExceptionType() { + return this; + } + + /** + * Visits a signature corresponding to a primitive type. + * + * @param descriptor the descriptor of the primitive type, or 'V' for {@code void} . + */ + public void visitBaseType(final char descriptor) {} + + /** + * Visits a signature corresponding to a type variable. + * + * @param name the name of the type variable. + */ + public void visitTypeVariable(final String name) {} + + /** + * Visits a signature corresponding to an array type. + * + * @return a non null visitor to visit the signature of the array element type. + */ + public SignatureVisitor visitArrayType() { + return this; + } + + /** + * Starts the visit of a signature corresponding to a class or interface type. + * + * @param name the internal name of the class or interface. + */ + public void visitClassType(final String name) {} + + /** + * Visits an inner class. + * + * @param name the local name of the inner class in its enclosing class. + */ + public void visitInnerClassType(final String name) {} + + /** Visits an unbounded type argument of the last visited class or inner class type. */ + public void visitTypeArgument() {} + + /** + * Visits a type argument of the last visited class or inner class type. + * + * @param wildcard '+', '-' or '='. + * @return a non null visitor to visit the signature of the type argument. + */ + public SignatureVisitor visitTypeArgument(final char wildcard) { + return this; + } + + /** Ends the visit of a signature corresponding to a class or interface type. */ + public void visitEnd() {} +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureWriter.java b/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureWriter.java new file mode 100644 index 000000000..75982e69c --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/signature/SignatureWriter.java @@ -0,0 +1,240 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.jpype.asm.signature; + +import org.jpype.asm.Opcodes; + +/** + * A SignatureVisitor that generates signature literals, as defined in the Java Virtual Machine + * Specification (JVMS). + * + * @see JVMS + * 4.7.9.1 + * @author Thomas Hallgren + * @author Eric Bruneton + */ +public class SignatureWriter extends SignatureVisitor { + + /** The builder used to construct the visited signature. */ + private final StringBuilder stringBuilder = new StringBuilder(); + + /** Whether the visited signature contains formal type parameters. */ + private boolean hasFormals; + + /** Whether the visited signature contains method parameter types. */ + private boolean hasParameters; + + /** + * The stack used to keep track of class types that have arguments. Each element of this stack is + * a boolean encoded in one bit. The top of the stack is the least significant bit. Pushing false + * = *2, pushing true = *2+1, popping = /2. + * + *

Class type arguments must be surrounded with '<' and '>' and, because + * + *

    + *
  1. class types can be nested (because type arguments can themselves be class types), + *
  2. SignatureWriter always returns 'this' in each visit* method (to avoid allocating new + * SignatureWriter instances), + *
+ * + *

we need a stack to properly balance these 'parentheses'. A new element is pushed on this + * stack for each new visited type, and popped when the visit of this type ends (either is + * visitEnd, or because visitInnerClassType is called). + */ + private int argumentStack; + + /** Constructs a new {@link SignatureWriter}. */ + public SignatureWriter() { + super(/* latest api =*/ Opcodes.ASM9); + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the SignatureVisitor interface + // ----------------------------------------------------------------------------------------------- + + @Override + public void visitFormalTypeParameter(final String name) { + if (!hasFormals) { + hasFormals = true; + stringBuilder.append('<'); + } + stringBuilder.append(name); + stringBuilder.append(':'); + } + + @Override + public SignatureVisitor visitClassBound() { + return this; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + stringBuilder.append(':'); + return this; + } + + @Override + public SignatureVisitor visitSuperclass() { + endFormals(); + return this; + } + + @Override + public SignatureVisitor visitInterface() { + return this; + } + + @Override + public SignatureVisitor visitParameterType() { + endFormals(); + if (!hasParameters) { + hasParameters = true; + stringBuilder.append('('); + } + return this; + } + + @Override + public SignatureVisitor visitReturnType() { + endFormals(); + if (!hasParameters) { + stringBuilder.append('('); + } + stringBuilder.append(')'); + return this; + } + + @Override + public SignatureVisitor visitExceptionType() { + stringBuilder.append('^'); + return this; + } + + @Override + public void visitBaseType(final char descriptor) { + stringBuilder.append(descriptor); + } + + @Override + public void visitTypeVariable(final String name) { + stringBuilder.append('T'); + stringBuilder.append(name); + stringBuilder.append(';'); + } + + @Override + public SignatureVisitor visitArrayType() { + stringBuilder.append('['); + return this; + } + + @Override + public void visitClassType(final String name) { + stringBuilder.append('L'); + stringBuilder.append(name); + // Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as + // we can tell at this point). + argumentStack *= 2; + } + + @Override + public void visitInnerClassType(final String name) { + endArguments(); + stringBuilder.append('.'); + stringBuilder.append(name); + // Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as + // we can tell at this point). + argumentStack *= 2; + } + + @Override + public void visitTypeArgument() { + // If the top of the stack is 'false', this means we are visiting the first type argument of the + // currently visited type. We therefore need to append a '<', and to replace the top stack + // element with 'true' (meaning that the current type does have type arguments). + if (argumentStack % 2 == 0) { + argumentStack |= 1; + stringBuilder.append('<'); + } + stringBuilder.append('*'); + } + + @Override + public SignatureVisitor visitTypeArgument(final char wildcard) { + // If the top of the stack is 'false', this means we are visiting the first type argument of the + // currently visited type. We therefore need to append a '<', and to replace the top stack + // element with 'true' (meaning that the current type does have type arguments). + if (argumentStack % 2 == 0) { + argumentStack |= 1; + stringBuilder.append('<'); + } + if (wildcard != '=') { + stringBuilder.append(wildcard); + } + return this; + } + + @Override + public void visitEnd() { + endArguments(); + stringBuilder.append(';'); + } + + /** + * Returns the signature that was built by this signature writer. + * + * @return the signature that was built by this signature writer. + */ + @Override + public String toString() { + return stringBuilder.toString(); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** Ends the formal type parameters section of the signature. */ + private void endFormals() { + if (hasFormals) { + hasFormals = false; + stringBuilder.append('>'); + } + } + + /** Ends the type arguments of a class or inner class type. */ + private void endArguments() { + // If the top of the stack is 'true', this means that some type arguments have been visited for + // the type whose visit is now ending. We therefore need to append a '>', and to pop one element + // from the stack. + if (argumentStack % 2 == 1) { + stringBuilder.append('>'); + } + argumentStack /= 2; + } +} diff --git a/native/java/jpype.jvm.asm/org/jpype/asm/signature/package.html b/native/java/jpype.jvm.asm/org/jpype/asm/signature/package.html new file mode 100644 index 000000000..d1657d41a --- /dev/null +++ b/native/java/jpype.jvm.asm/org/jpype/asm/signature/package.html @@ -0,0 +1,40 @@ + + + + + Package org.objectweb.asm.signature + + +Provides support for type signatures. + +@since ASM 2.0 + + diff --git a/native/java/org/jpype/classloader/DynamicClassLoader.java b/native/java/jpype.jvm/org/jpype/classloader/DynamicClassLoader.java similarity index 100% rename from native/java/org/jpype/classloader/DynamicClassLoader.java rename to native/java/jpype.jvm/org/jpype/classloader/DynamicClassLoader.java diff --git a/native/java/org/jpype/classloader/JPypeClassLoader.java b/native/java/jpype.jvm/org/jpype/classloader/JPypeClassLoader.java similarity index 100% rename from native/java/org/jpype/classloader/JPypeClassLoader.java rename to native/java/jpype.jvm/org/jpype/classloader/JPypeClassLoader.java diff --git a/native/java/org/jpype/JPypeSignal.java b/native/java/jpype.jvm/org/jpype/jvm/JPypeSignalImpl.java similarity index 84% rename from native/java/org/jpype/JPypeSignal.java rename to native/java/jpype.jvm/org/jpype/jvm/JPypeSignalImpl.java index be8f1d476..74ea301c8 100644 --- a/native/java/org/jpype/JPypeSignal.java +++ b/native/java/jpype.jvm/org/jpype/jvm/JPypeSignalImpl.java @@ -1,3 +1,5 @@ +package org.jpype.jvm; + /* **************************************************************************** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,8 +15,9 @@ See NOTICE file for details. **************************************************************************** */ -package org.jpype; + +import org.jpype.JPypeSignal; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -26,18 +29,18 @@ * Thus the have warnings against it that cannot be disabled. So we will skin * this cat another way. */ -public class JPypeSignal +public class JPypeSignalImpl implements JPypeSignal { static Thread main; - static void installHandlers() + @Override + public void installHandlers() { try { Class Signal = Class.forName("sun.misc.Signal"); Class SignalHandler = Class.forName("sun.misc.SignalHandler"); - main = Thread.currentThread(); Method method = Signal.getMethod("handle", Signal, SignalHandler); Object handler = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] @@ -60,7 +63,19 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl // If we don't get the signal handler run without it. (ANDROID) } } + + @Override + public void interruptPy() + { + _interruptPy(); + } + + @Override + public void acknowledgePy() + { + _acknowledgePy(); + } - native static void interruptPy(); - native static void acknowledgePy(); + native static void _interruptPy(); + native static void _acknowledgePy(); } diff --git a/native/java/org/jpype/pickle/ByteBufferInputStream.java b/native/java/jpype.jvm/org/jpype/pickle/ByteBufferInputStream.java similarity index 100% rename from native/java/org/jpype/pickle/ByteBufferInputStream.java rename to native/java/jpype.jvm/org/jpype/pickle/ByteBufferInputStream.java diff --git a/native/java/org/jpype/pickle/Decoder.java b/native/java/jpype.jvm/org/jpype/pickle/Decoder.java similarity index 100% rename from native/java/org/jpype/pickle/Decoder.java rename to native/java/jpype.jvm/org/jpype/pickle/Decoder.java diff --git a/native/java/org/jpype/pickle/Encoder.java b/native/java/jpype.jvm/org/jpype/pickle/Encoder.java similarity index 100% rename from native/java/org/jpype/pickle/Encoder.java rename to native/java/jpype.jvm/org/jpype/pickle/Encoder.java diff --git a/native/java/jpype.python/org/jpype/python/Engine.java b/native/java/jpype.python/org/jpype/python/Engine.java new file mode 100644 index 000000000..6ef347da6 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/Engine.java @@ -0,0 +1,43 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +/** + * + * @author nelson85 + */ +public interface Engine +{ + + /** + * Create a context for executing Python commands. + * + * Each context is an independent scope with its own symbols. Modules from + * different contexts are shared as there is only one Python interpreter. + * + * @param name + * @return a new interpreter context. + */ + Scope newScope(String name); + + default Scope newScope() + { + return Engine.this.newScope("__main__"); + } + + // FIXME we need a way to create code objects that can then be executed in a scope. + +} diff --git a/native/java/jpype.python/org/jpype/python/EngineFactory.java b/native/java/jpype.python/org/jpype/python/EngineFactory.java new file mode 100644 index 000000000..071a475a0 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/EngineFactory.java @@ -0,0 +1,56 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +/** + * + * @author nelson85 + */ +public interface EngineFactory +{ + + /** + * Get the instance of the EngineFactory. + * + * @return + */ + static EngineFactory getInstance() + { + return Statics.getEngineFactory(); + } + + /** + * Set a property for the engine. + * + * Properties should be set before starting the engine. After the engine is + * started most properties have no effect. + * + * @param key + * @param value + * @throws IllegalStateException if the engine is already started. + */ + void setProperty(String key, Object value) throws IllegalStateException; + + /** + * Create a Python instance. + * + * This can be only used once. + * + * @return + * @throws IllegalStateException if the Python engine has already been started. + */ + Engine create() throws IllegalStateException; +} diff --git a/native/java/jpype.python/org/jpype/python/EngineFactoryImpl.java b/native/java/jpype.python/org/jpype/python/EngineFactoryImpl.java new file mode 100644 index 000000000..f1d0c3558 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/EngineFactoryImpl.java @@ -0,0 +1,144 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Paths; +import org.jpype.python.internal.Native; +import org.jpype.python.internal.PyFrameStatic; + +/** + * + * @author nelson85 + */ +class EngineFactoryImpl implements EngineFactory +{ + + String pythonExec = "python"; + String jpypeLibrary = null; + String pythonLibrary = null; + + boolean started = false; + + @Override + public void setProperty(String key, Object value) + { + if (started) + throw new IllegalStateException(); + + if (key.equals("python.exec")) + { + pythonExec = (String) value; + return; + } + if (key.equals("python.lib")) + { + pythonLibrary = Paths.get((String) value).toAbsolutePath().toString(); + return; + } + if (key.equals("jpype.lib")) + { + jpypeLibrary = Paths.get((String) value).toAbsolutePath().toString(); + return; + } + throw new UnsupportedOperationException("Unknown property " + key); + } + + @Override + public Engine create() + { + // We can only create one engine as all have shared instances + if (started) + throw new IllegalStateException(); + + // Get the _jpype extension library + resolveLibraries(); + if (jpypeLibrary == null || pythonLibrary == null) + { + throw new RuntimeException("Unable to find _jpype module"); + } + + // Load libraries in Java so they are available for native calls. + if (Paths.get(pythonLibrary).isAbsolute()) + System.load(pythonLibrary); + else + System.loadLibrary(pythonLibrary); + System.load(jpypeLibrary); + + // Add to FFI name lookup table + Native.addLibrary(pythonLibrary); + Native.addLibrary(jpypeLibrary); + + // Start the Python + Native.start(); + started = true; + + // Connect up the natives + Statics.FRAME_STATIC = PyTypeManager.getInstance().createStaticInstance(PyFrameStatic.class); + return new EngineImpl(); + } + +// + /** + * Get the shared library parameters. + * + * @return + */ + public void resolveLibraries() + { + // System properties dub compiled in paths + this.jpypeLibrary = System.getProperty("jpype.lib", jpypeLibrary); + this.pythonLibrary = System.getProperty("python.lib", pythonLibrary); + + // No need to do a probe + if (this.jpypeLibrary != null && this.pythonLibrary != null) + return; + + try + { + String python = pythonExec; + String[] cmd = + { + python, "-c", + "import importlib\n" + + "import sysconfig\n" + + "import os\n" + + "gcv = sysconfig.get_config_var\n" + + "print(importlib.util.find_spec('_jpype').origin)\n" + + "print(os.path.join(gcv('LIBDIR')+gcv('multiarchsubdir'),gcv('LDLIBRARY')))" + }; + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.redirectOutput(ProcessBuilder.Redirect.PIPE); + Process process = pb.start(); + BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream())); + process.waitFor(); + String a = out.readLine(); + String b = out.readLine(); + if (jpypeLibrary == null) + jpypeLibrary = a; + if (pythonLibrary == null) + pythonLibrary = b; + + } catch (IOException | InterruptedException ex) + { + ex.printStackTrace(); + } + } + +// +} diff --git a/native/java/jpype.python/org/jpype/python/EngineImpl.java b/native/java/jpype.python/org/jpype/python/EngineImpl.java new file mode 100644 index 000000000..5feb33098 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/EngineImpl.java @@ -0,0 +1,47 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import python.lang.PyBuiltins; +import python.lang.PyDict; +import python.lang.PyString; +import static python.lang.PyBuiltins.*; + +/** + * Python execution engine. + */ +class EngineImpl implements Engine +{ + + @Override + public ScopeImpl newScope(String name) + { + PyDict globals = new PyDict(); + // Set up the minimum global dictionary required to function + globals.put("__spec__", None); + globals.put("__dict__", None); + globals.put("__package__", None); + globals.put("__name__", new PyString(name)); + globals.put("__builtins__", PyBuiltins.builtins()); + + // We will add JPype module and types so the environment is usable + ScopeImpl frame = new ScopeImpl(this, globals, globals); + frame.importModule("jpype"); + frame.importFrom("jpype.types", "*"); + return frame; + } + +} diff --git a/native/java/jpype.python/org/jpype/python/PyTypeBuilder.java b/native/java/jpype.python/org/jpype/python/PyTypeBuilder.java new file mode 100644 index 000000000..da6f28f79 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/PyTypeBuilder.java @@ -0,0 +1,465 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import org.jpype.asm.ClassReader; +import org.jpype.asm.ClassVisitor; +import org.jpype.asm.ClassWriter; +import org.jpype.asm.FieldVisitor; +import org.jpype.asm.MethodVisitor; +import static org.jpype.asm.Opcodes.*; +import org.jpype.asm.Type; +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.enums.PyInvocation; +import org.jpype.python.internal.Native; +import org.jpype.python.internal.PyBaseExtension; +import org.jpype.python.internal.PyBaseObject; +import org.jpype.python.internal.PyBaseStatic; +import org.jpype.python.internal.PyInvoker; + +/** + * Builder to create dynamic classes for use with Python. + * + * This uses the ASM library to dynamically compile classes. They must be loaded + * into a custom class loader to be activated. + */ +class PyTypeBuilder +{ + + String invoker = Type.getInternalName(PyInvoker.class); + + public byte[] newClass(String name, Class concrete, Class... interfaces) + { + + if (concrete != null) + { + if (!PyBaseObject.class.isAssignableFrom(concrete) + && concrete.getSuperclass().equals(Object.class)) + throw new RuntimeException(concrete + " is missing base."); + } else + { + concrete = PyBaseObject.class; + if (interfaces.length == 1) + { + PyTypeInfo info = (PyTypeInfo) interfaces[0].getAnnotation(PyTypeInfo.class); + if (info == null) + concrete = Object.class; + } + } + + // Find a unique set of methods + HashSet set = new HashSet<>(); + ArrayList entries = new ArrayList<>(); + ArrayList methods = new ArrayList<>(); + Class internal = null; + + try + { + for (Class cls : interfaces) + { + PyTypeInfo info = (PyTypeInfo) cls.getAnnotation(PyTypeInfo.class); + collectMethods(cls, set, entries, methods); + if (info != null && info.internal() != PyTypeInfo.class) + { + collectMethods(info.internal(), set, entries, methods); + if (internal == null) + internal = info.internal(); + } + } + } catch (Throwable th) + { + th.printStackTrace(); + } + + byte[] out = generateClass(name, concrete, internal, interfaces, methods, entries); + + // Write the class to disk for inspection with javap -c + try ( OutputStream os = Files.newOutputStream(Paths.get(name + ".class"))) + { + os.write(out); + } catch (IOException ex) + { + throw new RuntimeException(ex); + } + return out; + } + + /** + * Search for methods to be implemented. + */ + private void collectMethods(Class cls, Set set, List entries, List methods) + { + for (Method method : cls.getMethods()) + { + try + { + PyMethodInfo info = method.getAnnotation(PyMethodInfo.class); + if (info == null) + continue; + StringBuilder sb = new StringBuilder(); + sb.append(method.getName()).append(":"); + sb.append(Type.getMethodDescriptor(method)); + String sig = sb.toString(); + if (set.contains(sig)) + continue; + set.add(sig); + entries.add(getMethod(info.name())); + methods.add(method); + } catch (NoSuchMethodException ex) + { + throw new RuntimeException(ex); + } + } + } + + static public long getMethod(String signature) throws NoSuchMethodException + { + long l = Native.getSymbol(signature); + if (l == 0) + { + throw new NoSuchMethodException(signature); + } + return l; + } + +// + /** + * Construct a class based on the requested interfaces. + * + * Declares the class, implements any methods that have PyMethodInfo + * annotation, copies in private implementation methods, and copies in base + * object methods. + * + * @param name + * @param concrete + * @param internal + * @param interfaces + * @param methods + * @param entries + * @return + */ + private byte[] generateClass(String name, + Class concrete, + Class internal, + Class[] interfaces, + List methods, + List entries) + { + String[] interfaceNames = Stream.of(interfaces).map(p -> Type.getInternalName(p)).toArray(String[]::new); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + ClassSpecification target = new ClassSpecification(cw, name, concrete, internal, interfaces); + cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, name, null, Type.getInternalName(concrete), interfaceNames); + + // Add methods + int index = 0; + for (Method method : methods) + { + PyMethodInfo info = method.getAnnotation(PyMethodInfo.class); + generateMethod(target, method, info, entries.get(index)); + index++; + } + if (internal != null) + { + copyClass(target, internal); + } + if (concrete != Object.class) + copyClass(target, PyBaseExtension.class); + else + copyClass(target, PyBaseStatic.class); + cw.visitEnd(); + return cw.toByteArray(); + } + + /** + * Implements a method. + * + * All methods marked with PyMethodInfo will be implemented as native. As we + * can't create C function stubs on the fly easily we will proxy through + * PyInvoker. These methods are simple. We look up the Java context, get the + * entry point from the method cache, copy the arguments onto the call stack, + * call invoker, and then match the return against the requested return type. + * + * @param target + * @param method + * @param info + * @param entry + */ + private void generateMethod(ClassSpecification target, Method method, + PyMethodInfo info, Long entry) + { + ClassWriter cw = target.cw; + PyInvocation invocation = info.invoke(); + Method imethod = invocation.getMethod(); + + // Check if this is already implemented + String name = method.getName(); + String descriptor = Type.getMethodDescriptor(method); + if (target.checkCache(name + descriptor)) + return; + + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, + descriptor, null, null); + mv.visitCode(); + + // First thing we need the entry point + mv.visitLdcInsn(entry); + + // If there are special flags they go next + mv.visitLdcInsn(info.flags()); + + // Pass the parameters + Class[] params = imethod.getParameterTypes(); + int pcount = 0; + + // If we are not a method, then skip this. + if (!info.method()) + pcount++; + int lparams = params.length; + if (info.op() != -1) + lparams--; + + // The 2 in the next line is how many invoker parameters to skip + for (int i = 2; i < lparams; ++i) + { + Class param = params[i]; + if (param == Integer.TYPE) + mv.visitVarInsn(ILOAD, pcount++); + else if (param == Long.TYPE) + { // Long counts as 2 slots + mv.visitVarInsn(LLOAD, pcount++); + pcount++; + } else if (param == Float.TYPE) + mv.visitVarInsn(FLOAD, pcount++); + else if (param == Double.TYPE) + { // Double counts as 2 slots + mv.visitVarInsn(DLOAD, pcount++); + pcount++; + } else + mv.visitVarInsn(ALOAD, pcount++); + } + + // Add a fixed opcode if applicable. (for richcompare) + if (info.op() != -1) + mv.visitLdcInsn(info.op()); + + // Call the invoke method + mv.visitMethodInsn(INVOKESTATIC, invoker, imethod.getName(), + Type.getMethodDescriptor(imethod), false); + + // Handle the return + Class retType = method.getReturnType(); + if (retType == Void.TYPE) + mv.visitInsn(RETURN); + else if (retType == Boolean.TYPE) + mv.visitInsn(IRETURN); + else if (retType == Integer.TYPE) + { + if (imethod.getReturnType() == Long.TYPE) + mv.visitInsn(L2I); + mv.visitInsn(IRETURN); + } else if (retType == Long.TYPE) + mv.visitInsn(LRETURN); + else if (retType == Float.TYPE) + mv.visitInsn(FRETURN); + else if (retType == Double.TYPE) + mv.visitInsn(DRETURN); + else if (retType == Object.class) + mv.visitInsn(ARETURN); + else + { + // Everything else needs a cast + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(retType)); + mv.visitInsn(ARETURN); + + } + mv.visitMaxs(0, 0); + mv.visitEnd(); + } +// +// + + /** + * Structure to hold all the variables for a new class wrapper during + * construction. + */ + private static class ClassSpecification + { + + private final ClassWriter cw; + private final String name; + private final HashSet implemented_ = new HashSet<>(); + Class concrete; + Class internal; + Class[] interfaces; + + ClassSpecification(ClassWriter cw, String targetName, Class concrete, + Class internal, + Class[] interfaces) + { + this.cw = cw; + this.name = targetName; + this.concrete = concrete; + this.interfaces = interfaces; + this.internal = internal; + } + + /** + * Cache to see what has been implemented. + * + * Classes can only implement fields and methods once. This cache is ensures + * we skip any replication. If two classes have a conflict it will be + * determined when the class is loaded. + * + * @param key + * @return + */ + private boolean checkCache(String key) + { + if (implemented_.contains(key)) + return true; + implemented_.add(key); + return false; + } + } +// +// + + void copyClass(ClassSpecification target, Class source) + { + try ( InputStream is = source.getClassLoader().getResourceAsStream(Type.getInternalName(source) + ".class")) + { + ClassReader cr = new ClassReader(is); + ClassCopy cc = new ClassCopy(target, source, source.getSuperclass()); + // Delegate to the copy method. + cr.accept(cc, 0); + } catch (IOException ex) + { + throw new RuntimeException("Unable to find " + Type.getInternalName(source), ex); + } + } + + static class ClassCopy extends ClassVisitor + { + + private String sourceName; + private final ClassSpecification target; + private final Class sourceClass; + private final Class superClass; + + public ClassCopy(ClassSpecification target, Class sourceClass, Class superClass) + { + super(ASM8); + this.target = target; + this.sourceClass = sourceClass; + this.superClass = superClass; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) + { + this.sourceName = name; + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) + { + if (target.checkCache(name)) + return null; + target.cw.visitField(access, name, descriptor, signature, value); + return null; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) + { + // Don't copy abstract + if ((access & ACC_ABSTRACT) == ACC_ABSTRACT) + return null; + + if (target.checkCache(name + descriptor)) + return null; + + String superName = null; + if (superClass != null) + superName = Type.getInternalName(superClass); + + return new MethodCopy(target.cw.visitMethod(access, name, descriptor, signature, exceptions), + name, target, sourceName, superName); + } + } + + static class MethodCopy extends MethodVisitor + { + + private final ClassSpecification target; + private final String sourceName; + private final String methodName; + private final String superName; + + private MethodCopy(MethodVisitor visitMethod, String methodName, ClassSpecification target, + String sourceName, String superName) + { + super(ASM8, visitMethod); + this.methodName = methodName; + this.target = target; + this.sourceName = sourceName; + this.superName = superName; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) + { + if (owner.equals(sourceName)) + mv.visitFieldInsn(opcode, target.name, name, descriptor); + else + mv.visitFieldInsn(opcode, owner, name, descriptor); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) + { + if (methodName.equals("") && opcode == INVOKESPECIAL + && target.concrete != null && owner.equals(superName)) + owner = Type.getInternalName(target.concrete); + if (owner.equals(sourceName)) + mv.visitMethodInsn(opcode, target.name, name, descriptor, isInterface); + else + mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + @Override + public void visitTypeInsn(int opcode, String type) + { + if (type.equals(sourceName)) + mv.visitTypeInsn(opcode, target.name); + else + mv.visitTypeInsn(opcode, type); + } + } +// +} diff --git a/native/java/jpype.python/org/jpype/python/PyTypeLoader.java b/native/java/jpype.python/org/jpype/python/PyTypeLoader.java new file mode 100644 index 000000000..d8f0042da --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/PyTypeLoader.java @@ -0,0 +1,66 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import java.util.HashMap; +import org.jpype.JPypeContext; + +/** + * Class load for dynamic classes. + * + * @author nelson85 + */ +class PyTypeLoader extends ClassLoader +{ + + final HashMap cache = new HashMap<>(); + final PyTypeBuilder builder; + + PyTypeLoader(PyTypeBuilder builder) + { + super(JPypeContext.getInstance().getClassLoader()); + this.builder = builder; + } + + /** + * Find a class with the specified interfaces. + * + * @param name + * @param concrete + * @param interfaces + * @return + */ + public Class findClass(String name, Class concrete, Class[] interfaces) + { + int hash = 0; + for (int i = 0; i < interfaces.length; i++) + { + hash = hash * 0xa2060073 + interfaces[i].getName().hashCode(); + } + if (concrete != null) + hash = hash * 0xa2060073 + concrete.getName().hashCode(); + Class cls = cache.get(hash); + if (cls == null) + { + String className = String.format("%s$%08x", name, hash); + byte[] byteCode = builder.newClass(className, concrete, interfaces); + cls = defineClass(className, byteCode, 0, byteCode.length); + cache.put(hash, cls); + } + return cls; + } + +} diff --git a/native/java/jpype.python/org/jpype/python/PyTypeManager.java b/native/java/jpype.python/org/jpype/python/PyTypeManager.java new file mode 100644 index 000000000..0233845c0 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/PyTypeManager.java @@ -0,0 +1,287 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import org.jpype.JPypeContext; +import org.jpype.manager.ClassDescriptor; +import org.jpype.manager.ModifierCode; +import org.jpype.manager.TypeManager; +import org.jpype.manager.TypeManagerExtension; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBaseObject; +import org.jpype.python.internal.PyModuleIndex; +import python.lang.PyDict; +import python.lang.PyFloat; +import python.lang.PyLong; +import python.lang.PyObject; +import python.lang.PyString; +import python.lang.PyTuple; +import python.lang.exc.PyBaseException; + +/** + * TypeManager holds all of the wrapper classes that have been created. + */ +class PyTypeManager implements TypeManagerExtension +{ + + static final private PyTypeManager instance = new PyTypeManager(); + final PyTypeLoader loader; + + static public PyTypeManager getInstance() + { + if (JPypeContext.getInstance() == null) + throw new RuntimeException("Python environment is not started"); + if (instance == null) + throw new RuntimeException("Initialization error"); + return instance; + } + + private PyTypeManager() + { + // Make sure that all native entry points are loaded. + // This happens during the bootup sequence and we don't yet have the + // ability to print stacktraces in Python, so we have to do it here. + try + { + Class.forName("org.jpype.python.enums.PyInvocation", true, + JPypeContext.getInstance().getClassLoader()); + } catch (ClassNotFoundException ex) + { + throw new RuntimeException(ex); + } + + PyTypeBuilder builder = new PyTypeBuilder(); + loader = new PyTypeLoader(builder); + } + + void initialize() + { + + // Install wrappers in C++ layer + TypeManager typeManager = JPypeContext.getInstance().getTypeManager(); + // Note that order is very important when creating these initial wrapper + // types. If something inherits from another type then the super class + // will be created without the special flag and the type system won't + // be able to handle the duplicate type properly. + Class[] cls = + { + PyBaseObject.class, PyBaseException.class, + PyString.class, PyLong.class, PyFloat.class, + PyDict.class, PyTuple.class + }; + for (Class c : cls) + { + createClass(typeManager, c); + } + + // Register this as part of the typemanager + typeManager.registerExtension(this); + } + + private void collectBases(ArrayList interfaces, Class cls) + { + if (cls.getAnnotation(PyTypeInfo.class) != null) + { + if (interfaces.contains(cls)) + return; + interfaces.add(cls); + return; + } + + for (Class intf : cls.getInterfaces()) + { + collectBases(interfaces, intf); + } + } + + /** + * Get the wrapper for a class. + * + * This operates to create the customized wrapper for a Python class. First it + * checks the index to see if there is a defined wrapper type. If the wrapper + * type is complete then its work is done. Otherwise, it consults the dynamic + * classloader to construct a new class wrapper. + * + * @param moduleName is the name of the module in Python. + * @param className is the name of the class in Python. + * @param bases is a list of protocols that apply to this object. + * @return + */ + public Class getWrapper(String moduleName, String className, Class[] bases) + { + try + { + ArrayList interfaces = new ArrayList<>(); + if (bases == null) + bases = new Class[0]; + + interfaces.ensureCapacity(bases.length + 5); + Class wrapper; + Class concrete = null; + try + { + // Search the index for the best fit wrapper + Class module = Class.forName("python.__modules__." + moduleName); + PyModuleIndex index = (PyModuleIndex) module.getConstructor().newInstance(); + wrapper = index.getWrapper(className); + + // If we get a wrapper then see how it is to be used. + if (wrapper != null) + { + // If it is concrete then we should include its interfaces + if (!Modifier.isAbstract(wrapper.getModifiers())) + { + concrete = wrapper; + interfaces.addAll(Arrays.asList(wrapper.getInterfaces())); + } else + interfaces.add(wrapper); + PyTypeInfo annotation = (PyTypeInfo) wrapper.getAnnotation(PyTypeInfo.class); + if (annotation.exact()) + return wrapper; + } + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) + { + } + + for (Class base : bases) + { + collectBases(interfaces, base); + } + + // Pass 1 find a concrete type if it exists + Iterator iter = interfaces.iterator(); + while (iter.hasNext()) + { + Class base = iter.next(); + if (!base.isInterface()) + { + if (concrete != null && concrete != base && !base.isAssignableFrom(concrete)) + { + + throw new RuntimeException(String.format("Base conflict between '%s' and '%s' while creating wrapper for %s", + concrete.getName(), base.getName(), className)); + } + concrete = base; + iter.remove(); + } + } + + // Pass 2 remove an interfaces already implemented by the concrete type + if (concrete != null) + { + iter = interfaces.iterator(); + while (iter.hasNext()) + { + Class base = iter.next(); + if (base.isAssignableFrom(concrete)) + iter.remove(); + } + } + + // Construct the class using the loader + Class out = loader.findClass(className, concrete, interfaces.toArray(Class[]::new)); + TypeManager typeManager = JPypeContext.getInstance().getTypeManager(); + createClass(typeManager, out); + return out; + } catch (Exception ex) + { + ex.printStackTrace(); + throw ex; + } + } + + /** + * + * @param + * @param cls + * @return + */ + public T createStaticInstance(Class cls) + { + try + { + Class c = loader.findClass(cls.getSimpleName(), null, new Class[] + { + cls + }); + return c.getConstructor().newInstance(); + } catch (NoSuchMethodException | SecurityException | InstantiationException + | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) + { + // There is no error recovery as this is called from static initializer + ex.printStackTrace(); + throw new RuntimeException(ex); + } + } + +// + /** + * Extension to TypeManager to handle Python class instances. + * + * @param typeManager + * @param cls + * @return + */ + @Override + public ClassDescriptor createClass(TypeManager typeManager, Class cls) + { + // Figure out the base class to apply + ClassDescriptor out = null; + Class base = null; + + // These are the concrete base classes for Java. + if (PyBaseObject.class.isAssignableFrom(cls)) + base = PyBaseObject.class; + else if (PyBaseException.class.isAssignableFrom(cls)) + base = PyBaseException.class; + else if (PyString.class.isAssignableFrom(cls)) + base = PyString.class; + else if (PyLong.class.isAssignableFrom(cls)) + base = PyLong.class; + else if (PyFloat.class.isAssignableFrom(cls)) + base = PyFloat.class; + else + throw new RuntimeException("No known base for " + cls.getName() + " " + cls.getSuperclass()); + + // Check if the same wrapper already exists + out = typeManager.classMap.get(base); + if (out != null) + { + // This class will shared the same object wrapper. + typeManager.classMap.put(cls, out); + return out; + } + + // Python classes created as special classes so they unwrap back to + // Python when passed from Java. + out = typeManager.defineClass(base, false, ModifierCode.PYTHON.value); + typeManager.classMap.put(cls, out); + return out; + } + + @Override + public boolean handles(Class cls) + { + System.out.println("Test "+cls +" " +PyObject.class.isAssignableFrom(cls)); + return PyObject.class.isAssignableFrom(cls); + } +// +} diff --git a/native/java/jpype.python/org/jpype/python/Scope.java b/native/java/jpype.python/org/jpype/python/Scope.java new file mode 100644 index 000000000..c598c2d8b --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/Scope.java @@ -0,0 +1,63 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import python.lang.PyDict; + +/** + * + * @author nelson85 + */ +public interface Scope +{ + + /** + * Execute a statement in this context. + * + * @param s + * @return + */ + Object eval(String s); + + /** + * Start interactive mode in this context. + */ + void interactive(); + +// + void set(String name, Object obj); + + Object get(String name); + + /** + * @return the globals + */ + PyDict getGlobals(); + + /** + * @return the locals + */ + PyDict getLocals(); + +// +// + Object importFrom(String module, String symbol); + + Object importModule(String module); + + Object importModule(String module, String as); +// +} diff --git a/native/java/jpype.python/org/jpype/python/ScopeImpl.java b/native/java/jpype.python/org/jpype/python/ScopeImpl.java new file mode 100644 index 000000000..69a9c284b --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/ScopeImpl.java @@ -0,0 +1,113 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import static org.jpype.python.Statics.FRAME_STATIC; +import python.lang.PyDict; + +/** + * Execution Frame for holding local and global variables. + * + * A scripting context holds the global and local dictionaries. + */ +class ScopeImpl implements Scope +{ + private final Engine engine; + private final PyDict globals; + private final PyDict locals; + + ScopeImpl(Engine engine, PyDict globals, PyDict locals) + { + this.engine = engine; + this.globals = (PyDict) globals; + this.locals = (PyDict) locals; + } + + public Engine getEngine() + { + return engine; + } + + /** + * Execute a statement in this context. + * + * @param s + * @return + */ + @Override + public Object eval(String s) + { + return FRAME_STATIC.runString(s, globals, locals); + } + + /** + * Start interactive mode in this context. + */ + @Override + public void interactive() + { + FRAME_STATIC.interactive(globals, locals); + } + + @Override + public void set(String name, Object obj) + { + globals.put(obj, obj); + } + + @Override + public Object get(String name) + { + return globals.get(name); + } + + /** + * @return the globals + */ + @Override + public PyDict getGlobals() + { + return globals; + } + + /** + * @return the locals + */ + @Override + public PyDict getLocals() + { + return locals; + } + + @Override + public Object importModule(String module) + { + return FRAME_STATIC.runString("import " + module, globals, locals); + } + + @Override + public Object importModule(String module, String as) + { + return FRAME_STATIC.runString("import " + module + " as " + as, globals, locals); + } + + @Override + public Object importFrom(String module, String symbol) + { + return FRAME_STATIC.runString("from " + module + " import " + symbol, globals, locals); + } + +} diff --git a/native/java/jpype.python/org/jpype/python/Statics.java b/native/java/jpype.python/org/jpype/python/Statics.java new file mode 100644 index 000000000..0cc1d9b88 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/Statics.java @@ -0,0 +1,38 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +import org.jpype.python.internal.PyFrameStatic; + +/** + * + * @author nelson85 + */ +public class Statics +{ + static EngineFactory ENGINE_FACTORY; + static PyFrameStatic FRAME_STATIC; + + static EngineFactory getEngineFactory() + { + if (ENGINE_FACTORY== null) + ENGINE_FACTORY = new EngineFactoryImpl(); + return ENGINE_FACTORY; + } + + + +} diff --git a/native/java/jpype.python/org/jpype/python/Types.java b/native/java/jpype.python/org/jpype/python/Types.java new file mode 100644 index 000000000..ab2bce9ba --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/Types.java @@ -0,0 +1,37 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +/** + * + * This is an internal class used to convert stubs into instances. + * + * @author nelson85 + */ +public class Types +{ + /** + * Creates a static instance from a stub class. + * + * @param + * @param c + * @return + */ + public static T newInstance(Class c) + { + return PyTypeManager.getInstance().createStaticInstance(c); + } +} diff --git a/native/java/jpype.python/org/jpype/python/annotation/PyMethodInfo.java b/native/java/jpype.python/org/jpype/python/annotation/PyMethodInfo.java new file mode 100644 index 000000000..def379c6d --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/annotation/PyMethodInfo.java @@ -0,0 +1,68 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.jpype.python.enums.PyInvocation; + +/** + * Annotation to direct PyTypeBuilder when creating a method wrapper. + * + * @author nelson85 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface PyMethodInfo +{ + + public static final int ACCEPT = 1; + public static final int BORROWED = 2; + + /** + * Name of the Python method. + */ + String name(); + + /** + * Indicates how to call this wrapper in C. + * + * @return + */ + PyInvocation invoke(); + + /** + * Used for richcompare + */ + int op() default -1; + + /** + * Skip this argument. + * + * Used to create instance methods. + * + * @return + */ + boolean method(); + + /** + * Accept null returns. + * + * Prevents a throw if the argument is null. + * + * @return + */ + int flags() default 0; +} diff --git a/native/java/jpype.python/org/jpype/python/annotation/PyTypeInfo.java b/native/java/jpype.python/org/jpype/python/annotation/PyTypeInfo.java new file mode 100644 index 000000000..d29493a40 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/annotation/PyTypeInfo.java @@ -0,0 +1,50 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation to define how to apply wrappers to Python objects. + * + * @author nelson85 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface PyTypeInfo +{ + + /** + * The name of the Python class. + * + * @return + */ + String name(); + + /** + * Indicates the wrapper should only be applied to exact type matches. + * + * @return + */ + boolean exact() default false; + + /** + * Indicates that private methods will be implement + * + * @return + */ + Class internal() default PyTypeInfo.class; +} diff --git a/native/java/jpype.python/org/jpype/python/enums/PyInput.java b/native/java/jpype.python/org/jpype/python/enums/PyInput.java new file mode 100644 index 000000000..1e1f46546 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/enums/PyInput.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.enums; + +/** + * Enums for Run/Compile/Eval in Python. + */ +public enum PyInput +{ + Single, File, Eval; +} diff --git a/native/java/jpype.python/org/jpype/python/enums/PyInvocation.java b/native/java/jpype.python/org/jpype/python/enums/PyInvocation.java new file mode 100644 index 000000000..3b62d984b --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/enums/PyInvocation.java @@ -0,0 +1,152 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.enums; + +import java.lang.reflect.Method; +import org.jpype.python.internal.PyInvoker; + +/** + * Enums for dispatching to Python. + * + * This is an FFI interface for accessing Python methods from Java. + * + */ +public enum PyInvocation +{ + /** + * Object func(Object) + */ + NoArgs(getMethod("invokeNone")), + FromInt(getMethod("invokeFromInt")), + FromLong(getMethod("invokeFromLong")), + FromDouble(getMethod("invokeFromDouble")), + FromDouble2(getMethod("invokeFromDouble2")), + FromJObject(getMethod("invokeFromJObject")), + /** + * Object func(Object) + */ + Unary(getMethod("invokeUnary")), + /** + * int func(Object) + */ + AsBoolean(getMethod("invokeAsBoolean")), + /** + * int func(Object) + */ + AsInt(getMethod("invokeAsInt")), + /** + * long func(Object) + */ + AsLong(getMethod("invokeAsLong")), + /** + * float func(Object) + */ + AsFloat(getMethod("invokeAsFloat")), + /** + * double func(Object) + */ + AsDouble(getMethod("invokeAsDouble")), + /** + * Object func(Object, Object) + */ + Binary(getMethod("invokeBinary")), + /** + * Object func(Object, int) + */ + BinaryInt(getMethod("invokeBinaryInt")), + /** + * int func(Object, Object) + */ + BinaryToInt(getMethod("invokeBinaryToInt")), + /** + * long func(Object, Object) + */ + BinaryToLong(getMethod("invokeBinaryToLong")), + /** + * Object func(Object, Object, Object) + */ + Ternary(getMethod("invokeTernary")), + /** + * Object func(Object, int, int) + */ + GetSlice(getMethod("invokeGetSlice")), + /** + * Object func(Object, int, int, Object) + */ + SetSlice(getMethod("invokeSetSlice")), + /** + * int func(Object, int, int) + */ + DelSlice(getMethod("invokeDelSlice")), + /** + * int func(Object, str) + */ + DelStr(getMethod("invokeDelStr")), + /** + * Object func(Object, str) + */ + GetStr(getMethod("invokeGetStr")), + /** + * int func(Object, Object, Object) + */ + SetObj(getMethod("invokeSetObj")), + /** + * int func(Object, String, Object) + */ + SetStr(getMethod("invokeSetStr")), + /** + * int func(Object, int, Object) + */ + SetInt(getMethod("invokeSetInt")), + /** + * int func(Object, int, Object) + */ + SetIntToObj(getMethod("invokeSetIntToObj")), + /** + * int func(Object, int) + */ + IntOperator1(getMethod("invokeIntOperator1")), + /** + * int func(Object, Object, int) + */ + IntOperator2(getMethod("invokeIntOperator2")), + /** + * Object func(Object[]) + */ + Array(getMethod("invokeArray")); + + PyInvocation(Method method) + { + this.method = method; + } + + public Method getMethod() + { + return method; + } + + private static Method getMethod(String name) + { + for (Method m : PyInvoker.class.getMethods()) + { + if (m.getName().equals(name)) + return m; + } + throw new RuntimeException("Method " + name + " not found."); + } + + Method method; +} diff --git a/native/java/jpype.python/org/jpype/python/internal/Native.java b/native/java/jpype.python/org/jpype/python/internal/Native.java new file mode 100644 index 000000000..dd857b45b --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/Native.java @@ -0,0 +1,49 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +/** + * This is the home for native methods that call into the module. + * + * @author nelson85 + */ +public class Native +{ + + public static native void start(); + + /** + * (internal) Find a C language symbol in the existing library. + * + * @param str + * @return + */ + public static native long getSymbol(String str); + + + /** + * (internal) Adds a shared library to the search path for symbols. + * + * This is mainly an internal call, though some extension modules may require + * it. + * + * FIXME This should take a Path object. + * + * @param str + */ + public static native void addLibrary(String str); + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyBaseExtension.java b/native/java/jpype.python/org/jpype/python/internal/PyBaseExtension.java new file mode 100644 index 000000000..e62b15430 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyBaseExtension.java @@ -0,0 +1,38 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +/** + * Template for objects that derive from a concrete object + * + * @author nelson85 + */ +public class PyBaseExtension extends PyBaseObject +{ + + public PyBaseExtension(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyBaseExtension(ALLOCATOR, inst); + } + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyBaseObject.java b/native/java/jpype.python/org/jpype/python/internal/PyBaseObject.java new file mode 100644 index 000000000..72b6450df --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyBaseObject.java @@ -0,0 +1,76 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; +import python.lang.PyBuiltins; +import python.lang.PyObject; + +/** + * Base implementation. + */ +public class PyBaseObject implements PyObject +{ + long _self; + + @SuppressWarnings("LeakingThisInConstructor") + public PyBaseObject(PyConstructor key, long instance) + { + this._self = instance; + key.link(this, instance); + } + + @Override + public String toString() + { + CharSequence c = PyBuiltins.str(this); + return c.toString(); + } + + @Override + public int hashCode() + { + long l = PyBuiltins.hash(this); + int u = (int) (l >> 32); + return u ^ ((int) l); + } + + // Stub + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + return PyBuiltins.eq(this, obj); + } + + static Object _allocate(long inst) + { + return new PyBaseObject(ALLOCATOR, inst); + } + + /** + * Get the internal pointer to PyObject. + * + * @param o + * @return + */ + public static long _getSelf(PyBaseObject o) + { + return o._self; + } + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyBaseStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyBaseStatic.java new file mode 100644 index 000000000..75575a1b4 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyBaseStatic.java @@ -0,0 +1,27 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +/** + * + * @author nelson85 + */ +public class PyBaseStatic +{ + public PyBaseStatic() + { + } +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyBoolPrivate.java b/native/java/jpype.python/org/jpype/python/internal/PyBoolPrivate.java new file mode 100644 index 000000000..f9d856322 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyBoolPrivate.java @@ -0,0 +1,35 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +/** + * + * @author nelson85 + */ +public class PyBoolPrivate +{ + + static long _True; + + public static Object _allocate(long instance) + { + // First call defines TRUE during bootup. + if (_True == 0) + _True = instance; + return instance == _True; + } + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyBuiltinStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyBuiltinStatic.java new file mode 100644 index 000000000..d3fd7d1f3 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyBuiltinStatic.java @@ -0,0 +1,249 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.Types; +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.PyByteArray; +import python.lang.PyBytes; +import python.lang.PyDict; +import python.lang.PyLong; +import python.lang.PyMemoryView; +import python.lang.PyMethod; +import python.lang.PyObject; +import python.lang.PySet; +import python.lang.PySlice; +import python.lang.PyString; +import python.lang.PyTuple; +import python.lang.PyType; +import python.lang.exc.PyTypeError; +import python.lang.protocol.PyNumber; + +/** + * + * @author nelson85 + */ +public interface PyBuiltinStatic +{ + + final static PyBuiltinStatic INSTANCE = Types.newInstance(PyBuiltinStatic.class); + +// + /** + * Equivalent to the Python expression 'len(o)'. + * + * @return + */ + @PyMethodInfo(name = "PyObject_Length", invoke = PyInvocation.AsLong, method = false) + int len(Object o); + + @PyMethodInfo(name = "PyObject_LengthHint", invoke = PyInvocation.IntOperator1, method = false) + int lengthHint(Object o, int defaultValue); + + /** + * Equivalent of the Python statement 'o[key]'. + * + * @param key + * @return + */ + @PyMethodInfo(name = "PyObject_GetItem", invoke = PyInvocation.Binary, method = false) + Object getItem(Object o, Object key); + + /** + * Equivalent of the Python statement 'o[key] = v'. + * + * @param key + * @param v + */ + @PyMethodInfo(name = "PyObject_SetItem", invoke = PyInvocation.Ternary, method = false) + void setItem(Object o, Object key, Object v); + + /** + * Equivalent to the Python statement 'del o[key]'. + * + * @param key + */ + @PyMethodInfo(name = "PyObject_DelItem", invoke = PyInvocation.BinaryToInt, method = false) + void delItem(Object o, Object key); + + /** + * Equivalent to the Python expression `iter(o)`. + * + * It returns a new iterator for the object argument, or the object itself if + * the object is already an iterator. + * + * @return + * @throws PyTypeError if the object cannot be iterated. + */ + @PyMethodInfo(name = "PyObject_GetIter", invoke = PyInvocation.Unary, method = false) + Object iter(Object o) throws PyTypeError; + + // + @PyMethodInfo(name = "PyObject_Type", invoke = PyInvocation.Unary, method = false) + PyType type(Object o); + + @PyMethodInfo(name = "PyObject_Hash", invoke = PyInvocation.AsLong, method = false) + long hash(Object o); + + @PyMethodInfo(name = "PyObject_Str", invoke = PyInvocation.Unary, method = false) + PyString str(Object o); + + @PyMethodInfo(name = "PyObject_Repr", invoke = PyInvocation.Unary, method = false) + PyString repr(Object o); + + /** + * Equivalent to the Python expression 'dir(o)'. + * + * @param o + * @return a (possibly empty) list of strings appropriate for the object + * argument, or throw if there was an error. + */ + @PyMethodInfo(name = "PyObject_Dir", invoke = PyInvocation.Unary, method = false) + PyObject dir(Object o); + + /** + * Equivalent to the Python expression `not o`. + * + * @param o + * @return + */ + @PyMethodInfo(name = "PyObject_Not", invoke = PyInvocation.AsBoolean, method = false) + boolean not(Object o); + + /** + * Equivalent to the Python expression `not not o`. + * + * @param o + * @return + */ + @PyMethodInfo(name = "PyObject_IsTrue", invoke = PyInvocation.AsBoolean, method = false) + boolean isTrue(Object o); + + @PyMethodInfo(name = "PyObject_RichCompareBool", invoke = PyInvocation.IntOperator2, op = 2, method = false) + boolean eq(Object a, Object b); + + @PyMethodInfo(name = "PyObject_RichCompareBool", invoke = PyInvocation.IntOperator2, op = 3, method = false) + boolean ne(Object a, Object b); + + @PyMethodInfo(name = "PyObject_RichCompareBool", invoke = PyInvocation.IntOperator2, op = 4, method = false) + boolean gt(Object a, Object b); + + @PyMethodInfo(name = "PyObject_RichCompareBool", invoke = PyInvocation.IntOperator2, op = 0, method = false) + boolean lt(Object a, Object b); + + @PyMethodInfo(name = "PyObject_RichCompareBool", invoke = PyInvocation.IntOperator2, op = 5, method = false) + boolean ge(Object a, Object b); + + @PyMethodInfo(name = "PyObject_RichCompareBool", invoke = PyInvocation.IntOperator2, op = 1, method = false) + boolean le(Object a, Object b); + + @PyMethodInfo(name = "PyObject_HasAttrString", invoke = PyInvocation.DelStr, method = false) + boolean hasAttrString(Object o, String name); + + @PyMethodInfo(name = "PyObject_HasAttr", invoke = PyInvocation.BinaryToInt, method = false) + boolean hasAttrObject(Object o, Object name); + + @PyMethodInfo(name = "PyObject_GetAttrString", invoke = PyInvocation.GetStr, method = false) + Object getAttrString(Object o, String name); + + @PyMethodInfo(name = "PyObject_GetAttrString", invoke = PyInvocation.Binary, method = false) + Object getAttr(Object o, Object name); + + @PyMethodInfo(name = "PyObject_SetAttr", invoke = PyInvocation.Ternary, method = false) + void setAttrObject(Object o, Object name, Object value); + + @PyMethodInfo(name = "PyObject_SetAttrString", invoke = PyInvocation.SetStr, method = false) + void setAttrString(Object o, String name, Object value); + + @PyMethodInfo(name = "PyObject_DelAttrE", invoke = PyInvocation.Binary, method = false) + void delAttrObject(Object o, Object name); + + @PyMethodInfo(name = "PyObject_DelAttrStringE", invoke = PyInvocation.GetStr, method = false) + void delAttrString(Object o, String name); + + @PyMethodInfo(name = "PyObject_GetAttr", invoke = PyInvocation.Binary, method = false) + Object getAttrObject(Object o, Object string); + + @PyMethodInfo(name = "PyDict_New", invoke = PyInvocation.NoArgs, method = false) + PyDict newDict(); + + @PyMethodInfo(name = "PySet_New", invoke = PyInvocation.Unary, method = false) + PySet newSet(Iterable s); + + @PyMethodInfo(name = "PyFrozenSet_New", invoke = PyInvocation.Unary, method = false) + PySet newFrozenSet(Iterable s); + + @PyMethodInfo(name = "PyBytes_FromStringAndSizeE", invoke = PyInvocation.FromJObject, method = false) + PyBytes newBytes(Object bytes); + + @PyMethodInfo(name = "PyByteArray_FromObject", invoke = PyInvocation.Unary, method = false) + PyByteArray newByteArray(Object bytes); + + @PyMethodInfo(name = "PyFloat_FromDouble", invoke = PyInvocation.FromDouble, method = false) + PyNumber newFloat(double d); + + @PyMethodInfo(name = "PySlice_New", invoke = PyInvocation.Ternary, method = false) + PySlice newSlice(Object start, Object end, Object step); + + @PyMethodInfo(name = "PyMemoryView_FromObject", invoke = PyInvocation.Unary, method = false) + PyMemoryView newMemoryView(Object obj); + + @PyMethodInfo(name = "PyUnicode_FromOrdinal", invoke = PyInvocation.FromInt, method = false) + Object chr(int obj); + + @PyMethodInfo(name = "PyObject_Format", invoke = PyInvocation.Binary, method = false) + Object format(Object value, Object spec); + + @PyMethodInfo(name = "PyNumber_Divmod", invoke = PyInvocation.Binary, method = false) + Object divmod(Object a, Object b); + + @PyMethodInfo(name = "PyLong_FromVoidPtr", invoke = PyInvocation.Unary, method = false) + PyLong id(Object o); + + @PyMethodInfo(name = "PyNumber_ToBase", invoke = PyInvocation.BinaryInt, method = false) + Object toBase(Object o, int i); + + @PyMethodInfo(name = "PyCallable_Check", invoke = PyInvocation.AsInt, method = false) + boolean callable(Object o); + + @PyMethodInfo(name = "PyEval_GetBuiltins", invoke = PyInvocation.NoArgs, method = false, flags = PyMethodInfo.BORROWED) + PyDict builtins(); + + @PyMethodInfo(name = "PyNumber_Power", invoke = PyInvocation.Ternary, method = false) + Object pow(Object a, Object b, Object object); + + @PyMethodInfo(name = "PyObject_IsInstance", invoke = PyInvocation.BinaryToInt, method = false) + boolean isinstance(Object o, Object t); + + @PyMethodInfo(name = "PyObject_IsSubclass", invoke = PyInvocation.BinaryToInt, method = false) + boolean issubclass(Object o, Object t); + + @PyMethodInfo(name = "PyMethod_New", invoke = PyInvocation.Binary, method = false) + PyMethod newMethod(Object func, Object self); + + @PyMethodInfo(name = "PyObject_Call", invoke = PyInvocation.Ternary, method = false) + Object call(Object self, PyTuple args, PyDict kwargs); + + @PyMethodInfo(name = "PyIter_Next", invoke = PyInvocation.Unary, method = false, flags=PyMethodInfo.ACCEPT) + Object next(Object self); + + @PyMethodInfo(name = "PyComplex_FromDoubles", invoke = PyInvocation.FromDouble2, method = false) + Object newComplex(double doubleValue, double doubleValue0); + +// @PyMethodInfo() +// Object map(Object func, Object o); +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyConstructor.java b/native/java/jpype.python/org/jpype/python/internal/PyConstructor.java new file mode 100644 index 000000000..f64fef262 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyConstructor.java @@ -0,0 +1,65 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.ref.JPypeReferenceQueue; + +public class PyConstructor +{ + + private static JPypeReferenceQueue referenceQueue = JPypeReferenceQueue.getInstance(); + public static PyConstructor ALLOCATOR = new PyConstructor(true); + public static PyConstructor CONSTRUCTOR = new PyConstructor(false); + + static long cleanup = 0; + private final boolean shouldReference; + + PyConstructor(boolean reference) + { + this.shouldReference = reference; + } + + /** + * Create a link between a Java object and Python object such that the Python + * object may not be destroyed. + * + * This command is dangerous because it will increment the memory pointed to + * by the long. + * + * @param javaObject + * @param pyObject + */ + public void link(Object javaObject, long pyObject) + { + if (pyObject == 0) + return; + if (shouldReference) + incref(pyObject); + if (cleanup == 0) + cleanup = init(); + referenceQueue.registerRef(javaObject, pyObject, cleanup); + } + + /** + * Get the resources needed for this object + */ + native static long init(); + + /** + * Increment the reference counter + */ + native static void incref(long l); +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyDictStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyDictStatic.java new file mode 100644 index 000000000..2ffe00c79 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyDictStatic.java @@ -0,0 +1,89 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.PyDict; +import python.lang.PyList; +import python.lang.exc.PyException; + +/** + * + * @author nelson85 + */ +public interface PyDictStatic +{ + + @PyMethodInfo(name = "PyDict_SetItemString", invoke = PyInvocation.SetStr, method = false) + public void setItemString(Object self, String key, Object value); + + @PyMethodInfo(name = "PyDict_SetItem", invoke = PyInvocation.SetObj, method = false) + public void setItem(Object self, Object key, Object value); + + @PyMethodInfo(name = "PyDict_GetItem", invoke = PyInvocation.Binary, + method = false, flags = PyMethodInfo.ACCEPT|PyMethodInfo.BORROWED) + Object getItem(Object self, Object key); + + @PyMethodInfo(name = "PyDict_GetItemString", invoke = PyInvocation.GetStr, + method = false, flags = PyMethodInfo.ACCEPT|PyMethodInfo.BORROWED) + Object getItemString(Object self, String key); + + @PyMethodInfo(name = "PyDict_DelItem", invoke = PyInvocation.BinaryToInt, method = false) + void delItem(Object self, Object key) throws PyException; + + @PyMethodInfo(name = "PyDict_DelItemString", invoke = PyInvocation.DelStr, method = false) + void delItemString(Object self, String key) throws PyException; + + @PyMethodInfo(name = "PyDictProxy_New", invoke = PyInvocation.Unary, method = false) + PyDict asReadOnly(Object self); + + @PyMethodInfo(name = "PyDict_Clear", invoke = PyInvocation.Unary, method = false) + void clear(Object self); + + @PyMethodInfo(name = "PyDict_Contains", invoke = PyInvocation.BinaryToInt, method = false) + boolean contains(Object self, Object key) throws PyException; + + @PyMethodInfo(name = "PyDict_Copy", invoke = PyInvocation.Unary, method = false) + PyDict clone(Object self); + + @PyMethodInfo(name = "PyDict_GetItemWithError", invoke = PyInvocation.Binary, method = false, flags = PyMethodInfo.BORROWED) + Object getItemWithError(Object self, Object key) throws PyException; + + @PyMethodInfo(name = "PyDict_SetDefault", invoke = PyInvocation.Ternary, method = false) + Object setDefault(Object self, Object key, Object defaultobj); + + @PyMethodInfo(name = "PyDict_Items", invoke = PyInvocation.Unary, method = false) + PyList items(Object self); + + @PyMethodInfo(name = "PyDict_Keys", invoke = PyInvocation.Unary, method = false) + PyList keys(Object self); + + @PyMethodInfo(name = "PyDict_Values", invoke = PyInvocation.Unary, method = false) + PyList values(Object self); + + @PyMethodInfo(name = "PyDict_Size", invoke = PyInvocation.AsInt, method = false) + int size(Object self); + + @PyMethodInfo(name = "PyDict_Merge", invoke = PyInvocation.IntOperator2, method = false) + void merge(Object self, Object dict, boolean override) throws PyException; + + @PyMethodInfo(name = "PyDict_Update", invoke = PyInvocation.Binary, method = false) + void update(Object self, Object b) throws PyException; + + @PyMethodInfo(name = "PyDict_MergeFromSeq2", invoke = PyInvocation.IntOperator2, method = false) + void mergeFromSeq2(Object self, Object seq2, boolean override) throws PyException; +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyEllipsisPrivate.java b/native/java/jpype.python/org/jpype/python/internal/PyEllipsisPrivate.java new file mode 100644 index 000000000..a0540d167 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyEllipsisPrivate.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import python.lang.PyBuiltins; +import python.lang.PyEllipsis; +import python.lang.PyNone; + +/** + * + * @author nelson85 + */ +public class PyEllipsisPrivate extends PyBaseObject +{ + public PyEllipsisPrivate(PyConstructor key, long instance) + { + super(key, instance); + } + + protected static Object _allocate(long instance) + { + // No need to reference as PyNone is immortal + if (PyBuiltins.Ellipsis == null) + PyBuiltins.Ellipsis = (PyEllipsis) new PyEllipsisPrivate(PyConstructor.CONSTRUCTOR, instance); + return PyBuiltins.Ellipsis; + } + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyFrameStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyFrameStatic.java new file mode 100644 index 000000000..c9fdacfb2 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyFrameStatic.java @@ -0,0 +1,35 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.PyDict; + +/** + * + * @author nelson85 + */ +public interface PyFrameStatic +{ + + @PyMethodInfo(name = "PyFrame_RunString", invoke = PyInvocation.Ternary, method = false) + public Object runString(Object s, Object globals, Object locals); + + @PyMethodInfo(name = "PyFrame_Interactive", invoke = PyInvocation.Binary, method = false) + public Object interactive(PyDict globals, PyDict locals); + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyHandleImpl.java b/native/java/jpype.python/org/jpype/python/internal/PyHandleImpl.java new file mode 100644 index 000000000..3fac3243d --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyHandleImpl.java @@ -0,0 +1,40 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import python.lang.protocol.PyHandle; + +/** + * + * @author nelson85 + */ +public class PyHandleImpl implements PyHandle +{ + long _self; + + @SuppressWarnings("LeakingThisInConstructor") + public PyHandleImpl(PyConstructor key, long instance) + { + this._self = instance; + key.link(this, instance); + } + + @Override + public long _id() + { + return _self; + } +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyInvoker.java b/native/java/jpype.python/org/jpype/python/internal/PyInvoker.java new file mode 100644 index 000000000..ffc5ef6b3 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyInvoker.java @@ -0,0 +1,97 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +/** + * Class to invoke methods in Python. + * + * Invocation rules. + *

    + *
  • Methods must not steal a reference from the arguments. + *
  • Methods must return a new reference (not borrowed). + *
  • Methods that return null but do not set an error will get null. + *
  • Methods that set an error will throw. + *
  • Methods must match the Java signature if there is a conflict or be + * renamed with a trailing underscore. + *
+ * + * If a Python method is unable to implement this, it will be wrapped and a + * suffix will be added so that the default behavior and the required behavior + * can distinguished. + * + */ +public class PyInvoker +{ + + public static native Object invokeNone(long entry, int flags); + + public static native Object invokeFromInt(long entry, int flags, int i); + + public static native Object invokeFromLong(long entry, int flags, long i); + + public static native Object invokeFromDouble(long entry, int flags, double i); + + public static native Object invokeFromDouble2(long entry, int flags, double i, double j); + + public static native Object invokeFromJObject(long entry, int flags, Object i); + + public static native Object invokeUnary(long entry, int flags, Object a0); + + public static native boolean invokeAsBoolean(long entry, int flags, Object a0); + + public static native int invokeAsInt(long entry, int flags, Object a0); + + public static native long invokeAsLong(long entry, int flags, Object a0); + + public static native float invokeAsFloat(long entry, int flags, Object a0); + + public static native double invokeAsDouble(long entry, int flags, Object a0); + + public static native Object invokeBinary(long entry, int flags, Object a0, Object a1); + + public static native Object invokeBinaryInt(long entry, int flags, Object a0, int a1); + + public static native int invokeBinaryToInt(long entry, int flags, Object a0, Object a1); + + public static native long invokeBinaryToLong(long entry, int flags, Object a0, Object a1); + + public static native Object invokeTernary(long entry, int flags, Object a0, Object a1, Object a2); + + public static native int invokeDelSlice(long entry, int flags, Object a0, int a1, int a2); + + public static native Object invokeGetSlice(long entry, int flags, Object a0, int a1, int a2); + + public static native int invokeSetSlice(long entry, int flags, Object a0, int a1, int a2, Object a3); + + public static native int invokeDelStr(long entry, int flags, Object a0, String a1); + + public static native Object invokeGetStr(long entry, int flags, Object a0, String a1); + + public static native int invokeSetObj(long entry, int flags, Object a0, Object a1, Object a2); + + public static native int invokeSetStr(long entry, int flags, Object a0, String a1, Object a2); + + public static native int invokeSetInt(long entry, int flags, Object a0, int a1, Object a2); + + public static native Object invokeSetIntToObj(long entry, int flags, Object a0, int a1, Object a2); + + public static native int invokeIntOperator1(long entry, int flags, Object a0, int op); + + public static native int invokeIntOperator2(long entry, int flags, Object a0, Object a1, int op); + + public static native Object invokeArray(long entry, int flags, Object[] a1); + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyIteratorPrivate.java b/native/java/jpype.python/org/jpype/python/internal/PyIteratorPrivate.java new file mode 100644 index 000000000..c82bdfc99 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyIteratorPrivate.java @@ -0,0 +1,53 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Customizer for Iterator to have Java semantics. + * + * @author nelson85 + */ +public abstract class PyIteratorPrivate implements Iterator +{ + + public Object element_ = null; + + @Override + public boolean hasNext() + { + if (element_ == null) + element_ = PyBuiltinStatic.INSTANCE.next(this); + return element_ != null; + } + + @Override + public Object next() + { + if (element_ == null) + element_ = PyBuiltinStatic.INSTANCE.next(this); + if (element_ == null) + { + throw new NoSuchElementException(); + } + Object out = element_; + element_ = null; + return out; + } + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyListStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyListStatic.java new file mode 100644 index 000000000..09218bc91 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyListStatic.java @@ -0,0 +1,54 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.PyTuple; +import python.lang.exc.PyException; +import python.lang.exc.PyIndexError; + +public interface PyListStatic +{ + + @PyMethodInfo(name = "PyList_GetItem", invoke = PyInvocation.BinaryInt, method = false, flags = PyMethodInfo.BORROWED) + Object getItem(Object o, int index) throws PyIndexError; + + @PyMethodInfo(name = "PyList_SetItemS", invoke = PyInvocation.SetInt, method = false) + void setItem(Object o, int index, Object item) throws PyIndexError; + + @PyMethodInfo(name = "PyList_Insert", invoke = PyInvocation.SetInt, method = false) + void insert(Object o, int index, Object item) throws PyException; + + @PyMethodInfo(name = "PyList_Append", invoke = PyInvocation.BinaryToInt, method = false) + void append(Object o, Object item) throws PyException; + + @PyMethodInfo(name = "PyList_GetSlice", invoke = PyInvocation.GetSlice, method = false) + Object getSlice(Object o, int low, int high) throws PyException; + + @PyMethodInfo(name = "PyList_SetSlice", invoke = PyInvocation.SetSlice, method = false) + void setSlice(Object o, int low, int high, Object item) throws PyException; + + @PyMethodInfo(name = "PyList_Sort", invoke = PyInvocation.AsInt, method = false) + void sort(Object o) throws PyException; + + @PyMethodInfo(name = "PyList_Reverse", invoke = PyInvocation.AsInt, method = false) + void reverse(Object o) throws PyException; + + @PyMethodInfo(name = "PyList_AsTuple", invoke = PyInvocation.Unary, method = false) + PyTuple asTuple(Object o); + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyMappingStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyMappingStatic.java new file mode 100644 index 000000000..39bdd9a14 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyMappingStatic.java @@ -0,0 +1,64 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.exc.PyException; + +/** + * + * @author nelson85 + */ +public interface PyMappingStatic +{ + + @PyMethodInfo(name = "PyMapping_SetItemString", invoke = PyInvocation.SetStr, method = false) + void setItemString(Object self, String key, Object value); + + @PyMethodInfo(name = "PyObject_SetItem", invoke = PyInvocation.SetObj, method = false) + void setItem(Object self, Object key, Object value); + + @PyMethodInfo(name = "PyMapping_GetItemString", invoke = PyInvocation.Binary, method = false) + Object getItemString(Object self, String key); + + @PyMethodInfo(name = "PyMapping_GetItemString", invoke = PyInvocation.Binary, method = false) + Object getItem(Object self, Object key); + + @PyMethodInfo(name = "PyObject_DelItem", invoke = PyInvocation.BinaryToInt, method = false) + void delItem(Object self, Object key) throws PyException; + + @PyMethodInfo(name = "PyObject_DelItemString", invoke = PyInvocation.BinaryToInt, method = false) + void delItemString(Object self, String key) throws PyException; + + @PyMethodInfo(name = "PyMapping_HasKey", invoke = PyInvocation.BinaryToInt, method = false) + boolean hasKey(Object self, Object key); + + @PyMethodInfo(name = "PyMapping_HasKeyString", invoke = PyInvocation.DelStr, method = false) + boolean hasKeyString(Object self, String key); + + @PyMethodInfo(name = "PyMapping_Keys", invoke = PyInvocation.Unary, method = false) + Object keys(Object self); + + @PyMethodInfo(name = "PyMapping_Values", invoke = PyInvocation.Unary, method = false) + Object values(Object self); + + @PyMethodInfo(name = "PyMapping_Items", invoke = PyInvocation.Unary, method = false) + Object items(Object self); + + @PyMethodInfo(name = "PyMapping_Size", invoke = PyInvocation.AsLong, method = false) + int size(Object self); +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyModuleDef.java b/native/java/jpype.python/org/jpype/python/internal/PyModuleDef.java new file mode 100644 index 000000000..1a281aedc --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyModuleDef.java @@ -0,0 +1,61 @@ +/** *************************************************************************** + * 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. + * + * See NOTICE file for details. + **************************************************************************** */ +package org.jpype.python.internal; + +import java.util.HashMap; +import python.lang.PyModule; + +/** + * + * @author nelson85 + */ +public class PyModuleDef +{ + + static HashMap definitions = new HashMap<>(); + long moduleDef; + + final static PyBuiltinStatic BUILTIN_STATIC = PyBuiltinStatic.INSTANCE; + + private PyModuleDef(long moduleDef) + { + this.moduleDef = moduleDef; + + // Unpack the methods list + definitions.put(moduleDef, this); + } + + /** + * Get the definition for a module. + * + * @param module + * @return the module definition or null if the module is not internally + * defined. + */ + public PyModuleDef getDefinition(PyModule module) + { + long ptr = _find(module); + if (ptr == 0) + return null; + if (definitions.containsKey(ptr)) + return definitions.get(ptr); + return new PyModuleDef(ptr); + } + + native static long _find(Object o); + native static String _getName(long ptr); + native static Object[][] _getMethods(long ptr); +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyModuleIndex.java b/native/java/jpype.python/org/jpype/python/internal/PyModuleIndex.java new file mode 100644 index 000000000..839a4714d --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyModuleIndex.java @@ -0,0 +1,65 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import org.jpype.JPypeContext; +import org.jpype.pkg.JPypePackageManager; +import org.jpype.python.annotation.PyTypeInfo; + +/** + * Index used to define the interfaces for a Python Type. + *

+ * Implementations of this class must have the package python.__modules__. + * + * @author nelson85 + */ +public interface PyModuleIndex +{ + + Class getWrapper(String name); + + /** + * Function for constructing an index of wrappers by scanning a Python package + * for annotations. + * + * @param pkg + * @return + */ + public static Map scanPackage(String pkg) + { + HashMap out = new HashMap<>(); + ClassLoader cl = JPypeContext.getInstance().getClassLoader(); + Map content = JPypePackageManager.getContentMap(pkg); + for (Map.Entry entry : content.entrySet()) + { + Class c; + try + { + c = Class.forName(pkg + "." + entry.getKey(), false, cl); + PyTypeInfo ti = (PyTypeInfo) c.getAnnotation(PyTypeInfo.class); + if (ti == null) + continue; + out.put(ti.name(), c); + } catch (ClassNotFoundException ex) + { + } + } + return out; + } +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyNonePrivate.java b/native/java/jpype.python/org/jpype/python/internal/PyNonePrivate.java new file mode 100644 index 000000000..7d3f9836c --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyNonePrivate.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import python.lang.PyBuiltins; +import python.lang.PyNone; + +/** + * + * @author nelson85 + */ +public class PyNonePrivate extends PyBaseObject +{ + + public PyNonePrivate(PyConstructor key, long instance) + { + super(key, instance); + } + + protected static Object _allocate(long instance) + { + // No need to reference as PyNone is immortal + if (PyBuiltins.None == null) + PyBuiltins.None = (PyNone) new PyNonePrivate(PyConstructor.CONSTRUCTOR, instance); + return PyBuiltins.None; + } + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyNumberStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyNumberStatic.java new file mode 100644 index 000000000..9e2b30dd2 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyNumberStatic.java @@ -0,0 +1,145 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.Types; +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.PyString; +import python.lang.exc.PyBaseException; + +public interface PyNumberStatic +{ + public final static PyNumberStatic INSTANCE = Types.newInstance(PyNumberStatic.class); + + @PyMethodInfo(name = "PyNumber_Add", invoke = PyInvocation.Binary, method = false) + Object add(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Subtract", invoke = PyInvocation.Binary, method = false) + Object subtract(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Multiply", invoke = PyInvocation.Binary, method = false) + Object multiply(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_MatrixMultiply", invoke = PyInvocation.Binary, method = false) + Object matrixMultiply(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_FloorDivide", invoke = PyInvocation.Binary, method = false) + Object floorDivide(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_TrueDivide", invoke = PyInvocation.Binary, method = false) + Object trueDivide(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Remainder", invoke = PyInvocation.Binary, method = false) + Object remainder(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Divmod", invoke = PyInvocation.Binary, method = false) + Object divmod(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Power", invoke = PyInvocation.Ternary, method = false) + Object power(Object self, Object o2, Object o3); + + @PyMethodInfo(name = "PyNumber_Power2", invoke = PyInvocation.Binary, method = false) + Object power(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Negative", invoke = PyInvocation.Unary, method = false) + Object negative(Object self); + + @PyMethodInfo(name = "PyNumber_Positive", invoke = PyInvocation.Unary, method = false) + Object positive(Object self); + + @PyMethodInfo(name = "PyNumber_Absolute", invoke = PyInvocation.Unary, method = false) + Object absolute(Object self); + + @PyMethodInfo(name = "PyNumber_Invert", invoke = PyInvocation.Unary, method = false) + Object invert(Object self); + + @PyMethodInfo(name = "PyNumber_Lshift", invoke = PyInvocation.Binary, method = false) + Object leftShift(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Rshift", invoke = PyInvocation.Binary, method = false) + Object rightShift(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_And", invoke = PyInvocation.Binary, method = false) + Object and(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Xor", invoke = PyInvocation.Binary, method = false) + Object xor(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_Or", invoke = PyInvocation.Binary, method = false) + Object or(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceAdd", invoke = PyInvocation.Binary, method = false) + Object addAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceSubtract", invoke = PyInvocation.Binary, method = false) + Object subtractAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceMultiply", invoke = PyInvocation.Binary, method = false) + Object multiplyAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceMatrixMultiply", invoke = PyInvocation.Binary, method = false) + Object matrixMultiplyAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceFloorDivide", invoke = PyInvocation.Binary, method = false) + Object floorDivideAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceTrueDivide", invoke = PyInvocation.Binary, method = false) + Object trueDivideAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceRemainder", invoke = PyInvocation.Binary, method = false) + Object remainderAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlacePower2", invoke = PyInvocation.Binary, method = false) + Object powerAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlacePower", invoke = PyInvocation.Ternary, method = false) + Object powerAssign(Object self, Object o2, Object o3); + + @PyMethodInfo(name = "PyNumber_InPlaceLshift", invoke = PyInvocation.Binary, method = false) + Object leftShiftAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceRshift", invoke = PyInvocation.Binary, method = false) + Object rightShiftAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceAnd", invoke = PyInvocation.Binary, method = false) + Object andAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceXor", invoke = PyInvocation.Binary, method = false) + Object xorAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_InPlaceOr", invoke = PyInvocation.Binary, method = false) + Object orAssign(Object self, Object o2); + + @PyMethodInfo(name = "PyNumber_ToBase", invoke = PyInvocation.BinaryInt, method = false) + PyString toBase(Object self, int base); + + @PyMethodInfo(name = "PyNumber_AsSsize_t", invoke = PyInvocation.BinaryToLong, method = false) + long asSize(Object self, PyBaseException exc); + + @PyMethodInfo(name = "PyNumber_IntValue", invoke = PyInvocation.AsInt, method = false) + int intValue(Object self); + + @PyMethodInfo(name = "PyNumber_LongValue", invoke = PyInvocation.AsLong, method = false) + long longValue(Object self); + + @PyMethodInfo(name = "PyNumber_FloatValue", invoke = PyInvocation.AsFloat, method = false) + float floatValue(Object self); + + @PyMethodInfo(name = "PyNumber_DoubleValue", invoke = PyInvocation.AsDouble, method = false) + double doubleValue(Object self); + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PySequenceStatic.java b/native/java/jpype.python/org/jpype/python/internal/PySequenceStatic.java new file mode 100644 index 000000000..8c0056a6a --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PySequenceStatic.java @@ -0,0 +1,72 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.PyList; +import python.lang.PyTuple; +import python.lang.exc.PyException; + +public interface PySequenceStatic +{ + + @PyMethodInfo(name = "PySequence_Concat", invoke = PyInvocation.Binary, method = false) + Object concat(Object self, Object o2); + + @PyMethodInfo(name = "PySequence_Repeat", invoke = PyInvocation.BinaryInt, method = false) + Object repeat(Object self, int count); + + @PyMethodInfo(name = "PySequence_InPlaceConcat", invoke = PyInvocation.Binary, method = false) + Object assignConcat(Object self, Object o2); + + @PyMethodInfo(name = "PySequence_InPlaceRepeat", invoke = PyInvocation.BinaryInt, method = false) + Object assignRepeat(Object self, int count); + + @PyMethodInfo(name = "PySequence_GetItem", invoke = PyInvocation.BinaryInt, method = false) + Object get(Object self, int index) throws PyException; + + @PyMethodInfo(name = "PySequence_GetSlice", invoke = PyInvocation.GetSlice, method = false) + Object getSlice(Object self, int i1, int i2) throws PyException; + + @PyMethodInfo(name = "PySequence_SetItem", invoke = PyInvocation.SetInt, method = false) + void setItem(Object self, int index, Object value) throws PyException; + + @PyMethodInfo(name = "PySequence_DelItem", invoke = PyInvocation.IntOperator1, method = false) + void delItem(Object self, int index) throws PyException; + + @PyMethodInfo(name = "PySequence_SetSlice", invoke = PyInvocation.SetSlice, method = false) + void setSlice(Object self, int start, int end, Object value); + + @PyMethodInfo(name = "PySequence_DelSlice", invoke = PyInvocation.DelSlice, method = false) + void delSlice(Object self, int start, int end) throws PyException; + + @PyMethodInfo(name = "PySequence_Count", invoke = PyInvocation.BinaryToInt, method = false) + int count(Object self, Object value) throws PyException; + + @PyMethodInfo(name = "PySequence_Contains", invoke = PyInvocation.BinaryToInt, method = false) + boolean contains(Object self, Object value) throws PyException; + + @PyMethodInfo(name = "PySequence_Index", invoke = PyInvocation.BinaryToInt, method = false) + int indexOf(Object self, Object value) throws PyException; + + @PyMethodInfo(name = "PySequence_List", invoke = PyInvocation.Unary, method = false) + PyList asList(Object self) throws PyException; + + @PyMethodInfo(name = "PySequence_Tuple", invoke = PyInvocation.Unary, method = false) + PyTuple asTuple(Object self); + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PySetStatic.java b/native/java/jpype.python/org/jpype/python/internal/PySetStatic.java new file mode 100644 index 000000000..e4331aa7f --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PySetStatic.java @@ -0,0 +1,45 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.exc.PyKeyError; +import python.lang.exc.PyMemoryError; +import python.lang.exc.PySystemError; +import python.lang.exc.PyTypeError; + +public interface PySetStatic +{ + + @PyMethodInfo(name = "PyObject_Size", invoke = PyInvocation.AsLong, method = false) + int size(Object self); + + @PyMethodInfo(name = "PySet_Contains", invoke = PyInvocation.AsInt, method = false) + boolean contains(Object self, Object key); + + @PyMethodInfo(name = "PySet_Add", invoke = PyInvocation.Binary, method = false) + void addItem(Object self, Object key) throws PyTypeError, PyMemoryError, PySystemError; + + @PyMethodInfo(name = "PySet_Discard", invoke = PyInvocation.BinaryToInt, method = false) + boolean remove(Object self, Object key) throws PySystemError; + + @PyMethodInfo(name = "PySet_Pop", invoke = PyInvocation.Unary, method = false) + Object pop(Object self) throws PyKeyError, PySystemError; + + @PyMethodInfo(name = "PySet_Clear", invoke = PyInvocation.Unary, method = false) + void clear(Object self); +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyStringStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyStringStatic.java new file mode 100644 index 000000000..e1be6540c --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyStringStatic.java @@ -0,0 +1,21 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +public interface PyStringStatic +{ + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/PyTupleStatic.java b/native/java/jpype.python/org/jpype/python/internal/PyTupleStatic.java new file mode 100644 index 000000000..44b64f4ca --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/PyTupleStatic.java @@ -0,0 +1,46 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.exc.PyException; +import python.lang.exc.PyIndexError; + +/** + * + * @author nelson85 + */ +public interface PyTupleStatic +{ + + @PyMethodInfo(name = "PyTuple_Size", + invoke = PyInvocation.AsInt, + method = false) + int size(Object self); + + @PyMethodInfo(name = "PyTuple_GetItem", + invoke = PyInvocation.BinaryInt, + method = false, + flags = PyMethodInfo.BORROWED) + Object get(Object self, int i) throws PyIndexError; + + @PyMethodInfo(name = "PyTuple_GetSlice", + invoke = PyInvocation.GetSlice, + method = false) + Object getSlice(Object self, int i1, int i2) throws PyException; + +} diff --git a/native/java/jpype.python/org/jpype/python/internal/package-info.java b/native/java/jpype.python/org/jpype/python/internal/package-info.java new file mode 100644 index 000000000..3b35cc424 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/internal/package-info.java @@ -0,0 +1,40 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python.internal; + +/** + * These classes act as customizers for Python classes to make it easier to add + * methods to the wrapper implementation. + *

+ * All fields and public methods are copied to the resulting wrapper. The fields + * all must be initialized to null. Methods which are abstract and have + * PyMethodInfo are implement by the PyTypeBuilder. All others are copied to the + * corresponding wrapper class. + *

+ * They should be concrete classes so that they can have all types of methods + * (static, public, private). These classes will not appear in the public type + * tree as they are merely used as templates. Any public method which is exposed + * should be declared in the interface. They cannot override methods defined in + * the interfaces using PyMethodInfo. + *

+ * The only difficulty is how to implement static methods in the interfaces to + * be delegated to here. These templates can't be instantiated as the Python + * methods don't exist yet and interfaces refer to the private class instance + * directly. Once we are on Java 9 or later, this will get a bit easier as Java + * 9 permits more method types in interface classes. For now we will have to + * implement those methods we need directly using the invoker. + * + */ diff --git a/native/java/jpype.python/org/jpype/python/package-info.java b/native/java/jpype.python/org/jpype/python/package-info.java new file mode 100644 index 000000000..737001020 --- /dev/null +++ b/native/java/jpype.python/org/jpype/python/package-info.java @@ -0,0 +1,42 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package org.jpype.python; + +/** + * Package for JPype Python script engine. + * + * This is the "native" interface which presents the Python script engine. It + * does not currently conform to Graalvm Polyglot nor a Java ScriptEngine. + * + * Graalvm polyglot requires the language to be specified each time a script it + * interpreted, rather than allowing a language context which can then be acted + * on so it doesn't really apply. + * + * The Javax ScriptEngine assumes one script engine with a set of variable + * bindings and input/output called a Context, the variable binding itself as + * Bindings, and a ScriptEngineFactory that produces the ScriptEngine. This + * again is not a great match. Although Python can have multiple interpreters, + * most modules to not support a second interpreter so the the concept to more + * than one interpreter is not great. Python has two types of Bindings (globals + * and locals). We could drop locals to make it more similar. It is also unclear + * how we can tie stdin and stdout for one scope as Python has a global concept + * of sys.stdin and sys.stdout. + * + * As locals and globals are the same, we could just drop locals from + * consideration. But Javax also has the concept of ENGINE_SCOPE which contains + * all those things that are shared amongst all bindings. + * + */ diff --git a/native/java/python.lang/python/__modules__/builtins.java b/native/java/python.lang/python/__modules__/builtins.java new file mode 100644 index 000000000..8156e09f8 --- /dev/null +++ b/native/java/python.lang/python/__modules__/builtins.java @@ -0,0 +1,37 @@ +package python.__modules__; + +import java.util.HashMap; +import java.util.Map; +import org.jpype.python.internal.PyModuleIndex; + +/** + * Index for Python builtins module. + * + * This scans for defined wrappers for Python objects. Other modules can have + * their own wrapper interfaces if they implement an index in + * python.__modules__. Otherwise, they will fall back to the builtin wrappers. + * + * Currently this looks for wrappers in python.lang, python.lang.exc, and + * python.lang.protocol. + * + * @author nelson85 + */ +public class builtins implements PyModuleIndex +{ + + final static Map ENTRIES = new HashMap<>(); + + static + { + ENTRIES.putAll(PyModuleIndex.scanPackage("python.lang")); + ENTRIES.putAll(PyModuleIndex.scanPackage("python.lang.exc")); + ENTRIES.putAll(PyModuleIndex.scanPackage("python.lang.protocol")); + } + + @Override + public Class getWrapper(String name) + { + return ENTRIES.get(name); + } + +} diff --git a/native/java/python.lang/python/__modules__/package-info.java b/native/java/python.lang/python/__modules__/package-info.java new file mode 100644 index 000000000..881a1a629 --- /dev/null +++ b/native/java/python.lang/python/__modules__/package-info.java @@ -0,0 +1,10 @@ +package python.__modules__; + +/** + * Modules is a directory search for the index to any module. + *

+ * Most modules will use the generic wrappers for all types. But in some cases a + * module like numpy may require additional wrappers to improve functionality. + *

+ * In those cases the Java package would create a new index here. + */ diff --git a/native/java/python.lang/python/lang/PyArguments.java b/native/java/python.lang/python/lang/PyArguments.java new file mode 100644 index 000000000..52499be78 --- /dev/null +++ b/native/java/python.lang/python/lang/PyArguments.java @@ -0,0 +1,46 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +/** + * PyArguments is a special form of a Python tuple used with a PyCallable + * object. + * + * PyArguments will expand into an argument list when passed to the call method. + * + * @author nelson85 + */ +public class PyArguments +{ + + private final PyTuple tuple; + + private PyArguments(PyTuple tuple) + { + this.tuple = tuple; + } + + public static PyArguments use(PyTuple args) + { + return new PyArguments(args); + } + + public PyTuple toTuple() + { + return this.tuple; + } + +} diff --git a/native/java/python.lang/python/lang/PyBool.java b/native/java/python.lang/python/lang/PyBool.java new file mode 100644 index 000000000..c19245152 --- /dev/null +++ b/native/java/python.lang/python/lang/PyBool.java @@ -0,0 +1,30 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.internal.PyBoolPrivate; +import org.jpype.python.annotation.PyTypeInfo; + +/** + * Python Boolean type. + * + * This is a dummy wrapper as we will always translate the Python instance True + * and False to Java TRUE and Java FALSE. + */ +@PyTypeInfo(name = "bool", internal = PyBoolPrivate.class) +public interface PyBool extends PyObject +{ +} diff --git a/native/java/python.lang/python/lang/PyBuiltins.java b/native/java/python.lang/python/lang/PyBuiltins.java new file mode 100644 index 000000000..6ec9b1fbb --- /dev/null +++ b/native/java/python.lang/python/lang/PyBuiltins.java @@ -0,0 +1,509 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import python.lang.exc.PyTypeError; +import org.jpype.python.internal.PyBuiltinStatic; + +/** + * + * @author nelson85 + */ +public class PyBuiltins +{ + + final static PyBuiltinStatic BUILTIN_STATIC = PyBuiltinStatic.INSTANCE; + + //public static PyNone None; + public static PyEllipsis Ellipsis; + public static Boolean True = Boolean.TRUE; + public static Boolean False = Boolean.FALSE; + public static PyNone None = null; + +// + public static boolean hasattr(Object o, CharSequence attr) + { + if (attr == null) + return false; + if (attr instanceof PyString) + return BUILTIN_STATIC.hasAttrObject(o, (String) attr); + return BUILTIN_STATIC.hasAttrString(o, attr.toString()); + } + + public static void delattr(Object o, CharSequence attr) + { + if (attr == null) + return; + if (attr instanceof PyString) + BUILTIN_STATIC.delAttrObject(o, (String) attr); + BUILTIN_STATIC.delAttrString(o, attr.toString()); + } + + public static Object getattr(Object o, CharSequence attr) + { + if (attr == null) + return null; + if (attr instanceof PyString) + return BUILTIN_STATIC.getAttrObject(o, attr); + return BUILTIN_STATIC.getAttrString(o, attr.toString()); + } + + public static void setattr(Object o, CharSequence attr, Object value) + { + if (attr == null) + throw new NullPointerException("attr may not be null"); + if (attr instanceof PyString) + BUILTIN_STATIC.setAttrObject(o, (String) attr, value); + BUILTIN_STATIC.setAttrString(o, attr.toString(), value); + } +// +// + + static Object chr(int obj) + { + return BUILTIN_STATIC.chr(obj); + } + + static Object ord(Object o) + { + throw new UnsupportedOperationException(); + } +// +// + + static boolean all(Object o) + { + throw new UnsupportedOperationException(); + } + + static boolean any(Object o) + { + throw new UnsupportedOperationException(); + } + + /** + * Equivalent to the Python expression `not o`. + * + * @param o + * @return + */ + static public boolean not(PyObject o) + { + return BUILTIN_STATIC.not(o); + } + + /** + * Equivalent to the Python expression `not not o`. + * + * @param o + * @return + */ + static public boolean isTrue(PyObject o) + { + return BUILTIN_STATIC.isTrue(o); + } + + static public boolean eq(Object a, Object b) + { + return BUILTIN_STATIC.ge(a, b); + } + + static public boolean ne(Object a, Object b) + { + return BUILTIN_STATIC.le(a, b); + } + + static public boolean gt(Object a, Object b) + { + return BUILTIN_STATIC.gt(a, b); + } + + static public boolean lt(Object a, Object b) + { + return BUILTIN_STATIC.lt(a, b); + } + + static public boolean ge(Object a, Object b) + { + return BUILTIN_STATIC.ge(a, b); + } + + static public boolean le(Object a, Object b) + { + return BUILTIN_STATIC.le(a, b); + } +// +// + + static Object bin(Object o) + { + return BUILTIN_STATIC.toBase(o, 2); + } + + static Object hex(Object o) + { + return BUILTIN_STATIC.toBase(o, 16); + } + + static Object oct(Object o) + { + return BUILTIN_STATIC.toBase(o, 8); + } + +// +// + static Object abs(Object o) + { + throw new UnsupportedOperationException(); + } + + static Object max(Object... o) + { + throw new UnsupportedOperationException(); + } + + static Object round(Object o) + { + throw new UnsupportedOperationException(); + } + + static Object pow(Object a, Object b) + { + return BUILTIN_STATIC.pow(a, b, null); + } + + static Object sum(Object o) + { + throw new UnsupportedOperationException(); + } + + public static Object divmod(Object a, Object b) + { + return BUILTIN_STATIC.divmod(a, b); + } + +// +// + public static boolean callable(Object o) + { + return BUILTIN_STATIC.callable(o); + } + + /** + * Equivalent to the Python expression 'dir(o)'. + * + * @param o + * @return a (possibly empty) list of strings appropriate for the object + * argument, or throw if there was an error. + */ + public static PyObject dir(PyObject o) + { + return BUILTIN_STATIC.dir(o); + } + + public static long hash(Object o) + { + return BUILTIN_STATIC.hash(o); + } + + public static Object help(Object o) + { + throw new UnsupportedOperationException(); + } + + public static boolean isinstance(Object o, Object t) + { + return BUILTIN_STATIC.isinstance(o, t); + } + + public static boolean issubclass(Object o, Object t) + { + return BUILTIN_STATIC.issubclass(o, t); + } + + public static Object ascii(Object o) + { + throw new UnsupportedOperationException(); + } + + public static PyString repr(Object o) + { + // The arguement should not be restricted to PyObject + return BUILTIN_STATIC.repr(o); + } + + public static PyString str(Object o) + { + // The arguement should not be restricted to PyObject + return BUILTIN_STATIC.str(o); + } + + public static PyType type(PyObject o) + { + return BUILTIN_STATIC.type(o); + } + + public static PyLong id(Object o) + { + return BUILTIN_STATIC.id(o); + } + +// +// + + public static PyDict builtins() + { + return BUILTIN_STATIC.builtins(); + } + +// static Object vars() +// { +// throw new UnsupportedOperationException(); +// } +// +// static eval(Object o) +// { +// throw new UnsupportedOperationException(); +// } +// static Object compile(Object o) +// { +// throw new UnsupportedOperationException(); +// } +// +// exec() +// { +// throw new UnsupportedOperationException(); +// } +// +// + /** + * Equivalent to the Python expression 'len(o)'. + * + * @param o + * @return + */ + static public int len(PyObject o) + { + return BUILTIN_STATIC.len(o); + } + + static public int lengthHint(PyObject o, int defaultValue) + { + return BUILTIN_STATIC.lengthHint(o, defaultValue); + } + + /** + * Equivalent of the Python statement 'o[key]'. + * + * @param o + * @param key + * @return + */ + static public Object getItem(PyObject o, Object key) + { + return BUILTIN_STATIC.getItem(o, key); + } + + /** + * Equivalent of the Python statement 'o[key] = v'. + * + * @param o + * @param key + * @param v + */ + static public void setItem(PyObject o, Object key, Object v) + { + BUILTIN_STATIC.setItem(o, key, v); + } + + /** + * Equivalent to the Python statement 'del o[key]'. + * + * @param o + * @param key + */ + static public void delItem(PyObject o, Object key) + { + BUILTIN_STATIC.delItem(o, key); + } + + /** + * Equivalent to the Python expression `iter(o)`. + *

+ * It returns a new iterator for the object argument, or the object itself if + * the object is already an iterator. + * + * @param o + * @return + * @throws PyTypeError if the object cannot be iterated. + */ + static public Iterator iter(PyObject o) throws PyTypeError + { + return (Iterator) BUILTIN_STATIC.iter(o); + } + +// static Object map(Object func, Object o) +// { +// return BUILTIN_STATIC.map(func, o); +// } +// +// static Object filter(Object func, Object iterable) +// { +// return BUILTIN_STATIC.filter(func, iterable); +// } + + static Object next(Object o) + { + throw new UnsupportedOperationException(); + } + + static Object range(Object o) + { + throw new UnsupportedOperationException(); + } + + static Object reversed(Object o) + { + throw new UnsupportedOperationException(); + } + + static Object enumerate(Object o) + { + throw new UnsupportedOperationException(); + } + + static Object sorted(Object o) + { + throw new UnsupportedOperationException(); + } + + static Object zip(Object... iterable) + { + throw new UnsupportedOperationException(); + } + +// +// + public static boolean bool(Object o) + { + return BUILTIN_STATIC.isTrue(o); + } + + public static Object bytes(ByteBuffer bytes) + { + if (bytes.hasArray()) + return PyBuiltins.BUILTIN_STATIC.newBytes(bytes.array()); + return PyBuiltins.BUILTIN_STATIC.newBytes(bytes); + } + + public static PyBytes bytes(byte[] bytes) + { + return PyBuiltins.BUILTIN_STATIC.newBytes(bytes); + } + + public static Object bytearray(Buffer bb) + { + return PyBuiltins.BUILTIN_STATIC.newByteArray(bb); + } + + public static Object complex(Number real, Number img) + { + return PyBuiltins.BUILTIN_STATIC.newComplex(real.doubleValue(), img.doubleValue()); + } + + public static PyDict dict() + { + return PyBuiltins.BUILTIN_STATIC.newDict(); + } + + public static PySet frozenset(Iterable s) + { + return PyBuiltins.BUILTIN_STATIC.newFrozenSet(s); + } + + public static PyList list(Collection e) + { + return PyList.of(e.toArray()); + } + + public static PyMemoryView memoryview(Object o) + { + return BUILTIN_STATIC.newMemoryView(o); + } + + /** + * Create a new set containing objects returned by the iterable. + *

+ * The iterable may be NULL to create a new empty set. The constructor is also + * useful for copying a set (c=set(s)). + * + * @param s + * @return the new set on success. + * @throws PyTypeError if the iterable is not actual iterable. + */ + public static PySet set(Iterable s) + { + return PyBuiltins.BUILTIN_STATIC.newSet(s); + } + + public static PySet set(K... keys) + { + return PyBuiltins.BUILTIN_STATIC.newSet(Arrays.asList(keys)); + } + + public static PySlice slice(Object start, Object end) + { + return PyBuiltins.BUILTIN_STATIC.newSlice(start, end, None); + } + + public static PySlice slice(Object start, Object end, Object step) + { + return PyBuiltins.BUILTIN_STATIC.newSlice(start, end, step); + } + + public static PyTuple tuple(E... e) + { + return PyTuple.of(e); + } + + public static Object object() + { + throw new UnsupportedOperationException(); + } + +// +// +// Likely never used +// classmethod() +// property() +// super_() +// breakpoint() +// print() +// input() +// staticmethod(Object o) +// open(Object o, Object o) +// + static Object format(Object value, Object spec) + { + return BUILTIN_STATIC.format(value, spec); + } + +} diff --git a/native/java/python.lang/python/lang/PyByteArray.java b/native/java/python.lang/python/lang/PyByteArray.java new file mode 100644 index 000000000..83856684f --- /dev/null +++ b/native/java/python.lang/python/lang/PyByteArray.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "bytearray") +public interface PyByteArray extends PyObject +{ + +} diff --git a/native/java/python.lang/python/lang/PyBytes.java b/native/java/python.lang/python/lang/PyBytes.java new file mode 100644 index 000000000..8321d3e96 --- /dev/null +++ b/native/java/python.lang/python/lang/PyBytes.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "bytes") +public interface PyBytes extends PyObject +{ + +} diff --git a/native/java/python.lang/python/lang/PyCode.java b/native/java/python.lang/python/lang/PyCode.java new file mode 100644 index 000000000..b76abe8d9 --- /dev/null +++ b/native/java/python.lang/python/lang/PyCode.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "code") +public interface PyCode extends PyObject +{ + +} diff --git a/native/java/python.lang/python/lang/PyDict.java b/native/java/python.lang/python/lang/PyDict.java new file mode 100644 index 000000000..c6396420d --- /dev/null +++ b/native/java/python.lang/python/lang/PyDict.java @@ -0,0 +1,559 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import python.lang.protocol.PyMapping; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import org.jpype.python.Types; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBaseObject; +import org.jpype.python.internal.PyConstructor; +import org.jpype.python.internal.PyDictStatic; +import python.lang.exc.PyException; +import python.lang.exc.PyTypeError; + +@PyTypeInfo(name = "dict") +public class PyDict extends PyBaseObject implements Map, PyMapping +{ + + final static PyDictStatic DICT_STATIC = Types.newInstance(PyDictStatic.class); + + protected PyDict(PyConstructor key, long instance) + { + super(key, instance); + HashMap h; + } + + public PyDict() + { + super(PyConstructor.CONSTRUCTOR, _ctor(null)); + } + + public PyDict(Map m) + { + super(PyConstructor.CONSTRUCTOR, _ctor(m)); + } + + static Object _allocate(long instance) + { + return new PyDict(PyConstructor.ALLOCATOR, instance); + } + + /** + * MappingProxyType object for a mapping which enforces read-only behavior. + *

+ * This is normally used to create a view to prevent modification of the + * dictionary for non-dynamic class types. + * + * @return + */ + public PyDict asReadOnly() + { + return DICT_STATIC.asReadOnly(this); + } + + /** + * Empty an existing dictionary of all key-value pairs. + * + */ + @Override + public void clear() + { + DICT_STATIC.clear(this); + } + + /** + * Determine if dictionary p contains key.If an item in p is matches key, + * return true, otherwise return false. + * + * + * @param key + * @return + * @throws PyException on error. + */ + @Override + public boolean containsKey(Object key) throws PyException + { + return DICT_STATIC.contains(this, key); + } + + @Override + public boolean containsValue(Object value) + { + return this.values().contains(value); + } + + /** + * + * Return a new dictionary that contains the same key-value pairs as p. + * + * @return a copy of the dictionary. + */ + @Override + public PyDict clone() throws CloneNotSupportedException + { + return DICT_STATIC.clone(this); + } + + /** + * Insert val into the dictionary p with a key of key. + * + * @param key + * @param value + * @return the previous value associated with {@code key}, or {@code null} if + * there was no mapping for {@code key}. (A {@code null} return can also + * indicate that the map previously associated {@code null} with {@code key}, + * if the implementation supports {@code null} values.) + * @throws PyTypeError if the key is not hashable. + */ + @Override + public V put(K key, V value) throws PyTypeError + { + V out = get(key); + setItem(key, value); + return out; + } + + /** + * Insert val into the dictionary p using key as a key. + *

+ * The key object is created using PyUnicode_FromString(key). + * + * Return 0 on success or -1 on failure. This function does not steal a + * reference to val. + * + * @param key + * @param value + */ + @Override + public void setItem(K key, V value) throws PyTypeError + { + if (key instanceof String) + DICT_STATIC.setItemString(this, (String) key, value); + else + DICT_STATIC.setItem(this, key, value); + } + + /** + * Remove the entry in dictionary p with key key. + * + * @param key + * @throws PyTypeError if the key is not hashable. + */ + @Override + public void delItem(Object key) throws PyTypeError + { + if (key instanceof String) + DICT_STATIC.delItemString(this, (String) key); + else + DICT_STATIC.delItem(this, key); + } + + @Override + public V remove(Object key) + { + V out = get(key); + delItem(key); + return out; + } + + /** + * Return the object from dictionary p which has a key key. + *

+ * This does not set an exception if the key is not found. + *

+ * Note that exceptions which occur while calling __hash__() and __eq__() + * methods will get suppressed. + * + * To get error reporting use PyDict_GetItemWithError() instead. + * + * @param key is the key to match in the dictionary. + * @return the value associated with the key or null if key is not present. + */ + @Override + public V get(Object key) + { + if (key instanceof String) + return (V) DICT_STATIC.getItemString(this, (String) key); + else + return (V) DICT_STATIC.getItem(this, (String) key); + } + + /** + * Variant of PyDict_GetItem() that does not suppress exceptions. + * + * @param key is the key to match in the dictionary. + * @return the value associated with the key. + * @throws PyException is the key is not found. + */ + public Object getItemWithError(Object key) throws PyException + { + return DICT_STATIC.getItemWithError(this, key); + } + + /** + * This is the same as the Python-level dict.setdefault(). + * + * If present, it returns the value corresponding to key from the dictionary + * p. If the key is not in the dict, it is inserted with value defaultobj and + * defaultobj is returned. This function evaluates the hash function of key + * only once, instead of evaluating it independently for the lookup and the + * insertion. + * + * New in version 3.4. + * + * @param key + * @param defaultobj + * @return + */ + public V setDefault(K key, V defaultobj) + { + return (V) DICT_STATIC.setDefault(this, key, defaultobj); + } + + /** + * Get a list containing all the items from the dictionary. + * + * @return + */ + @Override + public PyList items() + { + return DICT_STATIC.items(this); + } + + @Override + public Set> entrySet() + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + /** + * Get a list containing all the keys from the dictionary. + * + * @return + */ + @Override + public PyList keys() + { + return DICT_STATIC.keys(this); + } + + @Override + public Set keySet() // FIXME this should be a view + { + return new PyDictKeySet(); + } + + /** + * Return a PyList containing all the values from the dictionary p. + * + * @return a new list containing the items the dictionary. + */ + @Override + public PyList values() + { + return DICT_STATIC.values(this); + } + + /** + * Return the number of items in the dictionary. + *

+ * This is equivalent to len(p) on a dictionary. + * + * @return the number of items in the dictionary. + */ + @Override + public int size() + { + return DICT_STATIC.size(this); + } + + /** + * Iterate over mapping object b adding key-value pairs to dictionary. + *

+ * dict may be a dictionary, or any object supporting PyMapping_Keys() and + * PyObject_GetItem(). + *

+ * If override is true, existing pairs in a will be replaced if a matching key + * is found in b, otherwise pairs will only be added if there is not a + * matching key in a. + * + * Return 0 on success or -1 if an exception was raised. + * + * @param dict + * @param override + */ + public void merge(Object dict, boolean override) throws PyException + { + DICT_STATIC.merge(this, dict, override); + } + + @Override + public void putAll(Map m) + { + merge(m, true); + } + + /** + * Update a dict. + * + * This is the same as PyDict_Merge(a, b, 1) in C, and is similar to + * a.update(b) in Python except that PyDict_Update() doesn’t fall back to the + * iterating over a sequence of key value pairs if the second argument has no + * “keys” attribute. + * + * @param b + * @throws PyException is the key is not found. + */ + public void update(Object b) throws PyException + { + DICT_STATIC.update(this, b); + } + + /** + * Update or merge into dictionary a, from the key-value pairs in seq2. + *

+ * seq2 must be an iterable object producing iterable objects of length 2, + * viewed as key-value pairs. + * + * In case of duplicate keys, the last wins if override is true, else the + * first wins. + * + * @throws PyException is the key is not found. + */ + public void mergeFromSeq2(Object seq2, boolean override) throws PyException + { + DICT_STATIC.mergeFromSeq2(this, seq2, override); + } + + @Override + public boolean isEmpty() + { + return size() == 0; + } + + class PyDictKeySet implements Set + { + + @Override + public int size() + { + return PyDict.this.size(); + } + + @Override + public boolean isEmpty() + { + return PyDict.this.isEmpty(); + } + + @Override + public boolean contains(Object o) + { + return PyDict.this.containsKey(o); + } + + @Override + public Iterator iterator() + { + return PyBuiltins.iter(PyDict.this); + } + + @Override + public Object[] toArray() + { + Object[] out = new Object[size()]; + int i = 0; + for (Object s : PyDict.this.keys()) + { + out[i++] = s; + } + return out; + } + + @Override + public T[] toArray(T[] a) + { + int n = this.size(); + if (a.length != n) + try + { + a = (T[]) a.getClass().getConstructor(int.class).newInstance(n); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) + { + throw new RuntimeException(ex); + } + int i = 0; + for (Object s : PyDict.this.keys()) + { + a[i++] = (T) s; + } + return a; + } + + @Override + public boolean add(K e) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean remove(Object o) + { + return PyDict.this.remove(o) != null; + } + + @Override + public boolean containsAll(Collection c) + { + for (Object u : c) + { + if (!PyDict.this.containsKey(u)) + return false; + } + return true; + } + + @Override + public boolean addAll(Collection c) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean retainAll(Collection c) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean removeAll(Collection c) + { + boolean changed = false; + for (Object u : c) + { + if (remove(u)) + changed = true; + } + return changed; + } + + @Override + public void clear() + { + PyDict.this.clear(); + } + + } + + class PyDictEntrySet implements Set> + { + + @Override + public int size() + { + return PyDict.this.size(); + } + + @Override + public boolean isEmpty() + { + return PyDict.this.isEmpty(); + } + + @Override + public boolean contains(Object o) + { + return false; + } + + @Override + public Iterator> iterator() + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public Object[] toArray() + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public T[] toArray(T[] a) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean add(Entry e) + { + return PyDict.this.put((K) e.getKey(), (V) e.getValue()) != null; + } + + @Override + public boolean remove(Object o) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean containsAll(Collection c) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean addAll(Collection> c) + { + boolean changed = false; + for (Entry e : c) + { + if (this.add(e)) + changed = true; + } + return changed; + } + + @Override + public boolean retainAll(Collection c) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean removeAll(Collection c) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void clear() + { + PyDict.this.clear(); + } + + } + + private static native long _ctor(Object o); + +} diff --git a/native/java/python.lang/python/lang/PyEllipsis.java b/native/java/python.lang/python/lang/PyEllipsis.java new file mode 100644 index 000000000..831dfa9dd --- /dev/null +++ b/native/java/python.lang/python/lang/PyEllipsis.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.internal.PyEllipsisPrivate; +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "ellipsis", internal = PyEllipsisPrivate.class) +public interface PyEllipsis extends PyObject +{ +} diff --git a/native/java/python.lang/python/lang/PyFloat.java b/native/java/python.lang/python/lang/PyFloat.java new file mode 100644 index 000000000..4e9f4b426 --- /dev/null +++ b/native/java/python.lang/python/lang/PyFloat.java @@ -0,0 +1,80 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import python.lang.protocol.PyNumber; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; + +@PyTypeInfo(name = "float") +public class PyFloat extends Number implements PyNumber, PyObject +{ + + final long _self; + + protected PyFloat() + { + this._self = 0; + } + + protected PyFloat(PyConstructor key, long instance) + { + this._self = instance; + key.link(this, instance); + } + + public PyFloat(double v) + { + this(PyConstructor.CONSTRUCTOR, _ctor(v)); + } + + static Object _allocate(long inst) + { + return new PyFloat(PyConstructor.ALLOCATOR, inst); + } + + public static PyFloat of(long d) + { + return (PyFloat) PyBuiltins.BUILTIN_STATIC.newFloat(d); + } + + @Override + public int intValue() + { + return NUMBER_STATIC.intValue(this); + } + + @Override + public long longValue() + { + return NUMBER_STATIC.longValue(this); + } + + @Override + public float floatValue() + { + return NUMBER_STATIC.floatValue(this); + } + + @Override + public double doubleValue() + { + return NUMBER_STATIC.doubleValue(this); + } + + private native static long _ctor(double d); + +} diff --git a/native/java/python.lang/python/lang/PyFunction.java b/native/java/python.lang/python/lang/PyFunction.java new file mode 100644 index 000000000..849264afe --- /dev/null +++ b/native/java/python.lang/python/lang/PyFunction.java @@ -0,0 +1,110 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.enums.PyInvocation; +import python.lang.exc.PyException; +import python.lang.exc.PySystemError; + +@PyTypeInfo(name = "function") +public interface PyFunction extends PyObject +{ + + /** + * Return the code object associated with the function object op. + * + * @return + */ + @PyMethodInfo(name = "PyFunction_GetCode", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getCode(); + + /** + * Return the globals dictionary associated with the function object op. + * + * @return + */ + @PyMethodInfo(name = "PyFunction_GetGlobals", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getGlobals(); + + /** + * Return the __module__ attribute of the function object op. + *

+ * This is normally a string containing the module name, but can be set to any + * other object by Python code. + * + * @return + */ + @PyMethodInfo(name = "PyFunction_GetModule", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getModule(); + + /** + * Return the argument default values of the function object op. + *

+ * This can be a tuple of arguments or NULL. + * + * @return + */ + @PyMethodInfo(name = "PyFunction_GetDefaults", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getDefaults(); + + /** + * Set the argument default values for the function object op. + * + * @param defaults must be Py_None or a tuple. + * @throws PySystemError on failure. + */ + @PyMethodInfo(name = "PyFunction_SetDefaults", invoke = PyInvocation.BinaryToInt, method = true) + void setDefaults(Object defaults) throws PySystemError; + + /** + * Return the closure associated with the function object op. + *

+ * @return null or a tuple of cell objects. + */ + @PyMethodInfo(name = "PyFunction_GetClosure", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getClosure(); + + /** + * Set the closure associated with the function object op. + *

+ * @param closure must be Py_None or a tuple of cell objects. + * @throws PySystemError on failure. + */ + @PyMethodInfo(name = "PyFunction_SetClosure", invoke = PyInvocation.Binary, method = true) + void setClosure(Object closure) throws PySystemError; + + /** + * Get the annotations of the function object. + *

+ * This can be a mutable dictionary or NULL. + * + * @return + */ + @PyMethodInfo(name = "PyFunction_GetAnnotations", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getAnnotations(); + + /** + * Set the annotations for the function object op. + * + * @param annotations must be a dictionary or Py_None. + * @throws PyException on failure. + */ + @PyMethodInfo(name = "PyFunction_SetAnnotations", invoke = PyInvocation.Binary, method = true) + void setAnnotations(Object annotations) throws PyException; + +} diff --git a/native/java/python.lang/python/lang/PyKeywords.java b/native/java/python.lang/python/lang/PyKeywords.java new file mode 100644 index 000000000..c670df5d5 --- /dev/null +++ b/native/java/python.lang/python/lang/PyKeywords.java @@ -0,0 +1,99 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import python.lang.exc.PyTypeError; + +/** + * + * @author nelson85 + */ +public class PyKeywords +{ + + PyDict contents; + + private PyKeywords(PyDict dict) + { + this.contents = dict; + } + + /** + * Create a new keyword arguments. + */ + public PyKeywords() + { + this(new PyDict()); + } + + /** + * Use an existing dictionary as a keywords argument. + * + * This is the equivalent of {@code **kwargs}. + * + * @param dict + * @return + */ + public static PyKeywords use(PyDict dict) + { + return new PyKeywords(dict); + } + + /** + * Construct a set of keywords from a list of key/value pairs. + * + * Every other argument should be a string type. There should be an even + * number of arguments. + * + * @param contents + * @return + */ + public static PyKeywords of(Object... contents) + { + PyKeywords out = new PyKeywords(new PyDict()); + for (int i = 0; i < contents.length; i += 2) + { + if (contents[i] instanceof CharSequence) + { + out.put((CharSequence) contents[i], contents[i + 1]); + } else + { + throw new PyTypeError("Arguments must be char type"); + } + } + return out; + } + + /** + * Add a value to a keyword arguments. + * + * This can be chained to set up multiple values. + * + * @param key + * @param value + * @return + */ + public PyKeywords put(CharSequence key, Object value) + { + this.contents.put(new PyString(key), value); + return this; + } + + public PyDict toDict() + { + return this.contents; + } +} diff --git a/native/java/python.lang/python/lang/PyList.java b/native/java/python.lang/python/lang/PyList.java new file mode 100644 index 000000000..2bae68a73 --- /dev/null +++ b/native/java/python.lang/python/lang/PyList.java @@ -0,0 +1,192 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import python.lang.protocol.PySequence; +import java.util.Collection; +import java.util.List; +import org.jpype.python.Types; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBaseObject; +import org.jpype.python.internal.PyConstructor; +import org.jpype.python.internal.PyListStatic; +import python.lang.exc.PyException; +import python.lang.exc.PyIndexError; + +@PyTypeInfo(name = "list") +public class PyList extends PyBaseObject implements PySequence, List +{ + + final static PyListStatic LIST_STATIC = Types.newInstance(PyListStatic.class); + + protected PyList(PyConstructor key, long instance) + { + super(key, instance); + } + + public PyList() + { + super(PyConstructor.CONSTRUCTOR, _ctor(null)); + } + + public PyList(Collection c) + { + super(PyConstructor.CONSTRUCTOR, _ctor(c.toArray())); + } + + static PyList of(Object... elements) + { + return new PyList(PyConstructor.CONSTRUCTOR, _ctor(elements)); + } + + /** + * Return the object at position index in the list pointed to by list. + *

+ * The position must be non-negative; indexing from the end of the list is not + * supported. + * + * @param index + * @return + * @throws PyIndexError if index is out of bounds. + * + */ + @Override + public E get(int index) throws PyIndexError + { + return (E) LIST_STATIC.getItem(this, index); + } + + @Override + public void setItem(int index, E item) throws PyIndexError + { + if (item == null) + throw new NullPointerException(); + LIST_STATIC.setItem(this, index, item); + } + + /** + * Analogous to list.insert(index, item). + *

+ * Insert the item item into list list in front of index index. + * + * @param index + * @param item + * @throws PyException on failure. + */ + public void insert(int index, Object item) throws PyException + { + if (item == null) + throw new NullPointerException(); + LIST_STATIC.insert(this, index, item); + } + + /** + * Append the object item at the end of list. + *

+ * Analogous to list.append(item). + * + * @param item + * @throws PyException on failure. + */ + public void append(E item) throws PyException + { + if (item == null) + throw new NullPointerException(); + LIST_STATIC.append(this, item); + } + + /** + * Return a list of the objects in list containing the objects between low and + * high. + *

+ * Analogous to list[low:high]. Indexing from the end of the list is not + * supported + * + * @param low + * @param high + * @return + * @throws PyException on failure. + */ + @Override + public Object getSlice(int low, int high) throws PyException + { + return LIST_STATIC.getSlice(this, low, high); + } + + /** + * Set the slice of list between low and high to the contents of itemlist. + *

+ * Analogous to list[low:high] = itemlist. The itemlist may be NULL, + * indicating the assignment of an empty list (slice deletion). + *

+ * Indexing from the end of the list is not supported. + * + * @param low + * @param high + * @param item + * @throws PyException on failure. + */ + @Override + public void setSlice(int low, int high, Object item) throws PyException + { + LIST_STATIC.setSlice(this, low, high, item); + } + + /** + * Sort the items of list in place. + *

+ * This is equivalent to list.sort(). + * + * @throws PyException on failure. + */ + public void sort() throws PyException + { + LIST_STATIC.sort(this); + } + + /** + * This is the equivalent of list.reverse(). + * + * Reverse the items of list in place. + * + * @throws PyException on failure. + */ + public void reverse() throws PyException + { + LIST_STATIC.reverse(this); + } + + /** + * Return a new tuple object containing the contents of list. + *

+ * Equivalent to tuple(list). + * + * @return + */ + @Override + public PyTuple asTuple() + { + return LIST_STATIC.asTuple(this); + } + + private native static long _ctor(Object[] o); + + static Object _allocate(long instance) + { + return new PyList(null, instance); + } + +} diff --git a/native/java/python.lang/python/lang/PyLong.java b/native/java/python.lang/python/lang/PyLong.java new file mode 100644 index 000000000..bf36dfd9c --- /dev/null +++ b/native/java/python.lang/python/lang/PyLong.java @@ -0,0 +1,97 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import python.lang.protocol.PyNumber; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; + +@PyTypeInfo(name = "int") +public class PyLong extends Number implements PyNumber, PyObject +{ + + final long _self; + + protected PyLong(PyConstructor key, long instance) + { + this._self = instance; + key.link(this, instance); + } + + public PyLong(long v) + { + this(PyConstructor.CONSTRUCTOR, _ctor(v)); + } + + @Override + public int intValue() + { + return NUMBER_STATIC.intValue(this); + } + + @Override + public long longValue() + { + return NUMBER_STATIC.longValue(this); + } + + @Override + public float floatValue() + { + return NUMBER_STATIC.floatValue(this); + } + + @Override + public double doubleValue() + { + return NUMBER_STATIC.doubleValue(this); + } + + @Override + public String toString() + { + CharSequence c = PyBuiltins.str(this); + return c.toString(); + } + + @Override + public int hashCode() + { + long l = PyBuiltins.hash(this); + int u = (int) (l >> 32); + return u ^ ((int) l); + } + + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof PyObject)) + { + return false; + } + if (this == obj) + return true; + return PyBuiltins.eq(this, obj); + } + + static Object _allocate(long inst) + { + return new PyLong(PyConstructor.ALLOCATOR, inst); + } + + private static native long _ctor(long v); + +} diff --git a/native/java/python.lang/python/lang/PyMemoryView.java b/native/java/python.lang/python/lang/PyMemoryView.java new file mode 100644 index 000000000..511964b0a --- /dev/null +++ b/native/java/python.lang/python/lang/PyMemoryView.java @@ -0,0 +1,28 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "memoryview") +public interface PyMemoryView extends PyObject +{ + + static PyMemoryView of(Object obj) + { + return PyBuiltins.BUILTIN_STATIC.newMemoryView(obj); + } +} diff --git a/native/java/python.lang/python/lang/PyMethod.java b/native/java/python.lang/python/lang/PyMethod.java new file mode 100644 index 000000000..ec4b90b47 --- /dev/null +++ b/native/java/python.lang/python/lang/PyMethod.java @@ -0,0 +1,55 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.enums.PyInvocation; + +@PyTypeInfo(name = "method") +public interface PyMethod extends PyObject +{ + + /** + * Return a new method object, with func being any callable object and self + * the instance the method should be bound . + * + * @param func is the function that will be called when the method is called. + * @param self must not be null. + * @return + */ + static PyMethod of(Object func, Object self) + { + return PyBuiltins.BUILTIN_STATIC.newMethod(func, self); + } + + /** + * Get the function object associated with the method meth. + * + * @return + */ + @PyMethodInfo(name = "PyMethod_Function", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getFunction(); + + /** + * Get the instance associated with the method meth. + * + * @return + */ + @PyMethodInfo(name = "PyMethod_Self", invoke = PyInvocation.Unary, method = true, flags = PyMethodInfo.BORROWED) + Object getSelf(); + +} diff --git a/native/java/python.lang/python/lang/PyModule.java b/native/java/python.lang/python/lang/PyModule.java new file mode 100644 index 000000000..5c2a51cec --- /dev/null +++ b/native/java/python.lang/python/lang/PyModule.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "module") +public interface PyModule extends PyObject +{ + +} diff --git a/native/java/python.lang/python/lang/PyNone.java b/native/java/python.lang/python/lang/PyNone.java new file mode 100644 index 000000000..36eed2c7b --- /dev/null +++ b/native/java/python.lang/python/lang/PyNone.java @@ -0,0 +1,29 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.internal.PyNonePrivate; +import org.jpype.python.annotation.PyTypeInfo; + +/** + * Python None type. + * + * This object is a singleton. + */ +@PyTypeInfo(name = "NoneType", internal = PyNonePrivate.class) +public interface PyNone extends PyObject +{ +} diff --git a/native/java/python.lang/python/lang/PyObject.java b/native/java/python.lang/python/lang/PyObject.java new file mode 100644 index 000000000..8c694c90b --- /dev/null +++ b/native/java/python.lang/python/lang/PyObject.java @@ -0,0 +1,75 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyTypeInfo; + +/** + * Python Object protocol. + * + * If it isn't here then it needs to be cast to the appropriate type to access. + * Use 'obj instanceOf type` for type checks. + */ +@PyTypeInfo(name = "object") +public interface PyObject +{ + + /** + * Equivalent to the Python expression 'hasattr(o, attr_name)'. + * + * This function always succeeds. + * + * @param name + * @return + */ + default boolean hasAttr(String name) + { + return PyBuiltins.hasattr(this, name); + } + + /** + * Equivalent of the Python expression 'o.attr_name'. + * + * @param name + * @return + */ + default Object getAttr(CharSequence name) + { + return PyBuiltins.getattr(this, name); + } + + /** + * Equivalent of the Python statement 'o.attr_name = v'. + * + * @param name + * @param value + */ + default void setAttr(CharSequence name, Object value) + { + PyBuiltins.setattr(this, name, value); + } + + /** + * Equivalent of the Python statement 'del o'. + * + * @param name + */ + default void delAttr(CharSequence name) + { + PyBuiltins.delattr(this, name); + } + +} diff --git a/native/java/python.lang/python/lang/PySet.java b/native/java/python.lang/python/lang/PySet.java new file mode 100644 index 000000000..7735a0c01 --- /dev/null +++ b/native/java/python.lang/python/lang/PySet.java @@ -0,0 +1,227 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.internal.PySetStatic; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.Set; +import org.jpype.python.Types; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBaseObject; +import org.jpype.python.internal.PyConstructor; +import org.jpype.python.internal.PyNumberStatic; +import python.lang.exc.PyKeyError; +import python.lang.exc.PyMemoryError; +import python.lang.exc.PySystemError; +import python.lang.exc.PyTypeError; +import python.lang.protocol.PyCollection; +import python.lang.protocol.PyIterator; + +@PyTypeInfo(name = "set") +public class PySet extends PyBaseObject implements Set, PyCollection +{ + + final static PySetStatic SET_STATIC = Types.newInstance(PySetStatic.class); + + public PySet() + { + super(PyConstructor.CONSTRUCTOR, _ctor(null)); + } + + public PySet(Collection c) + { + super(PyConstructor.CONSTRUCTOR, _ctor(c.toArray())); + } + + protected PySet(PyConstructor key, long instance) + { + super(key, instance); + } + + @Override + public PyIterator iterator() + { + return (PyIterator) PyBuiltins.iter(this); + } + + @Override + public int size() + { + return SET_STATIC.size(this); + } + + @Override + public boolean isEmpty() + { + return size() == 0; + } + + /** + * Returns true if this set contains the specified element. + *

+ * Unlike the Python __contains__() method, this function does not + * automatically convert unhashable sets into temporary frozensets. + * + * @param key + * @throws PyTypeError if the key is unhashable. + * @throws PySystemError if anyset is not a set, frozenset, or an instance of + * a subtype. + */ + @Override + public boolean contains(Object key) + { + return SET_STATIC.contains(this, key); + } + + /** + * Add key to a set instance. + *

+ * Also works with frozenset instances (like PyTuple_SetItem() it can be used + * to fill-in the values of brand new frozensets before they are exposed to + * other code). Return 0 on success or -1 on failure. + * + * @param key + * @throws PyTypeError if the key is unhashable. + * @throws PyMemoryError if there is no room to grow. + * @throws PySystemError if set is not an instance of set or its subtype. + * + * + */ + @Override + public boolean add(E key) throws PyTypeError, PyMemoryError, PySystemError + { + boolean b = this.contains(key); + addItem(key); + return b; + } + + public void addItem(E key) throws PyTypeError, PyMemoryError, PySystemError + { + SET_STATIC.addItem(this, key); + } + + /** + * Remove a key from the set. + * + *

+ * Does not raise KeyError for missing keys. Unlike the Python discard() + * method, this function does not automatically convert unhashable sets into + * temporary frozensets. + * + * @param key + * @return true if found and removed, 0 if not found (no action taken). + * @throws PyTypeError if the key is unhashable. + * @throws PySystemError if set is not an instance of set or its subtype. + * + */ + @Override + public boolean remove(Object key) throws PySystemError + { + return SET_STATIC.remove(this, key); + } + + /** + * Return a new reference to an arbitrary object in the set, and removes the + * object from the set. + * + *

+ * @return the object removed. + * @throws PyKeyError if the set is empty. + * @throws PySystemError if set is not an instance of set or its subtype. + * + */ + public E pop() throws PyKeyError, PySystemError + { + return (E) SET_STATIC.pop(this); + } + + /** + * Empty an existing set of all elements. + */ + @Override + public void clear() + { + SET_STATIC.clear(this); + } + + @Override + public Object[] toArray() + { + Object[] o = new Object[this.size()]; + int i = 0; + for (E item : this) + { + o[i++] = item; + } + return o; + } + + @Override + public T[] toArray(T[] a) + { + int n = this.size(); + if (a.length != n) + try + { + a = (T[]) a.getClass().getConstructor(int.class).newInstance(n); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) + { + throw new RuntimeException(ex); + } + int i = 0; + for (E item : this) + { + a[i++] = (T) item; + } + return a; + } + + @Override + public boolean containsAll(Collection c) + { + PySet out = (PySet) PyNumberStatic.INSTANCE.and(this, c); + return out.size() == c.size(); + } + + @Override + public boolean addAll(Collection c) + { + PyNumberStatic.INSTANCE.orAssign(this, c); + return true; // We assume something was changed. + } + + @Override + public boolean retainAll(Collection c) + { + PyNumberStatic.INSTANCE.andAssign(this, c); + return true; // We assume something was changed. + } + + @Override + public boolean removeAll(Collection c) + { + PyNumberStatic.INSTANCE.subtractAssign(this, c); + return true; // We assume something was changed + } + + static Object _allocate(long instance) + { + return new PySet(PyConstructor.ALLOCATOR, instance); + } + + private native static long _ctor(Object[] o); +} diff --git a/native/java/python.lang/python/lang/PySlice.java b/native/java/python.lang/python/lang/PySlice.java new file mode 100644 index 000000000..5e92f0b7b --- /dev/null +++ b/native/java/python.lang/python/lang/PySlice.java @@ -0,0 +1,34 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "slice") +public interface PySlice extends PyObject +{ + + static PySlice of(Object start, Object end) + { +// return PyBuiltins.BUILTIN_STATIC.newSlice(start, end, None); + return null; + } + + static PySlice of(Object start, Object end, Object step) + { + return PyBuiltins.BUILTIN_STATIC.newSlice(start, end, step); + } +} diff --git a/native/java/python.lang/python/lang/PyString.java b/native/java/python.lang/python/lang/PyString.java new file mode 100644 index 000000000..8261ca288 --- /dev/null +++ b/native/java/python.lang/python/lang/PyString.java @@ -0,0 +1,121 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.Types; +import python.lang.protocol.PySequence; +import org.jpype.python.internal.PyStringStatic; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBaseObject; +import org.jpype.python.internal.PyConstructor; + +/** + * Representation of a Python String. + * + * FIXME should this be named PyUnicode to match the Python CAPI or PyString? + * python.lang.PyString is more similar to java.lang.String. + * + * @author nelson85 + */ +@PyTypeInfo(name = "str") +public class PyString extends PyBaseObject implements CharSequence, PySequence +{ + + final static PyStringStatic STRING_STATIC = Types.newInstance(PyStringStatic.class); + + protected PyString(PyConstructor key, long instance) + { + super(key, instance); + } + + /** + * Convert to a Python String representation. + * + * The actions taken depend on the type of the incoming argument. Existing + * Python strings are simply referenced again. All other types are converted + * to a new concrete Python string representation. + * + * @param s + */ + public PyString(CharSequence s) + { + super(PyConstructor.CONSTRUCTOR, _fromType(s)); + } + + @Override + public int length() + { + return PyBuiltins.len(this); + } + + @Override + public int size() + { + return PyBuiltins.len(this); + } + + @Override + public char charAt(int index) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public CharSequence subSequence(int start, int end) + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + static Object _allocate(long instance) + { + return new PyString(PyConstructor.ALLOCATOR, instance); + } + + /** + * Delegator based on the incoming type. + * + * @param s + * @return + */ + private static long _fromType(CharSequence s) + { + if (s == null) + throw new NullPointerException("PyString instances may not null"); + + // Give back the reference to the existing object. + if (s instanceof PyString) + { + return PyBaseObject._getSelf((PyBaseObject) s); + } + + if (s instanceof String) + { + return _ctor((String) s); + } + + // Otherwise convert to String first + return _ctor(s.toString()); + } + + @Override + public String toString() + { + return _toString(this); + } + + private native static String _toString(Object self); + private native static long _ctor(String s); +} diff --git a/native/java/python.lang/python/lang/PyTuple.java b/native/java/python.lang/python/lang/PyTuple.java new file mode 100644 index 000000000..fe2b701e9 --- /dev/null +++ b/native/java/python.lang/python/lang/PyTuple.java @@ -0,0 +1,126 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import java.util.Collection; +import python.lang.protocol.PySequence; +import org.jpype.python.Types; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBaseObject; +import org.jpype.python.internal.PyConstructor; +import org.jpype.python.internal.PyTupleStatic; +import python.lang.exc.PyException; +import python.lang.exc.PyIndexError; + +/** + * Tuples are read only sequences of items. + * + * They may not be modified after being created. + * + * @author nelson85 + * @param + */ +@PyTypeInfo(name = "tuple") +public class PyTuple extends PyBaseObject implements PySequence +{ + + final static PyTupleStatic TUPLE_STATIC = Types.newInstance(PyTupleStatic.class); + + protected PyTuple(PyConstructor key, long instance) + { + super(key, instance); + } + + public PyTuple(Collection c) + { + super(PyConstructor.CONSTRUCTOR, _ctor(c.toArray(), 0, c.size())); + } + + public static PyTuple of(E... values) + { + return new PyTuple(PyConstructor.CONSTRUCTOR, _ctor(values, 0, values.length)); + } + + /** + * Construct a tuple from a range of an array. + * + * @param + * @param values + * @param start + * @param end + * @return + */ + public static PyTuple ofRange(E[] values, int start, int end) + { + return new PyTuple(PyConstructor.CONSTRUCTOR, _ctor(values, start, end)); + } + + /** + * This is equivalent to the Python expression len(o). + * + * @return the number of objects in sequence o on success. + * @throws PyException on failure. + */ + @Override + public int size() + { + return TUPLE_STATIC.size(this); + } + + /** + * Return the object at position pos in the tuple pointed to by p. + * + * @param i is the index of the item. + * @return the item at the index. + * @throws PyIndexError if the position is out of bounds. + */ + @Override + public E get(int i) throws PyIndexError + { + return (E) TUPLE_STATIC.get(this, i); + } + + /** + * This is the equivalent of the Python expression p[low:high]. + *

+ * Indexing from the end of the list is not supported. + * + * @param i1 + * @param i2 + * @return the slice of the tuple pointed to by p between low and high. + * @throws PyException on failure. + * + */ + @Override + public Object getSlice(int i1, int i2) throws PyException + { + return TUPLE_STATIC.getSlice(this, i1, i2); + } + + @Override + public void setItem(int i, E v) + { + throw new UnsupportedOperationException(); + } + + static Object _allocate(long instance) + { + return new PyTuple(PyConstructor.ALLOCATOR, instance); + } + + private native static long _ctor(Object[] o, int start, int end); + +} diff --git a/native/java/python.lang/python/lang/PyType.java b/native/java/python.lang/python/lang/PyType.java new file mode 100644 index 000000000..af2828c0d --- /dev/null +++ b/native/java/python.lang/python/lang/PyType.java @@ -0,0 +1,33 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.enums.PyInvocation; + +@PyTypeInfo(name = "type") +public interface PyType extends PyObject +{ + + @PyMethodInfo(name = "PyType_Name", invoke = PyInvocation.Unary, method = true) + public String getName(); + + // FIXME there should be a bunch of things here. + // FIXME we should be able to implement a Python type with recognized + // interfaces from within Java if needed. We can already implement a class + // generically as Java classes appear as Python. +} diff --git a/native/java/python.lang/python/lang/exc/PyArithmeticError.java b/native/java/python.lang/python/lang/exc/PyArithmeticError.java new file mode 100644 index 000000000..842a37786 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyArithmeticError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "ArithmeticError", exact = true) +public class PyArithmeticError extends PyException +{ + protected PyArithmeticError() + { + super(); + } + + protected PyArithmeticError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyArithmeticError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyAssertionError.java b/native/java/python.lang/python/lang/exc/PyAssertionError.java new file mode 100644 index 000000000..5839e8077 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyAssertionError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "AssertionError", exact = true) +public class PyAssertionError extends PyException +{ + protected PyAssertionError() + { + super(); + } + + protected PyAssertionError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyAssertionError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyAttributeError.java b/native/java/python.lang/python/lang/exc/PyAttributeError.java new file mode 100644 index 000000000..4fed0d7f1 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyAttributeError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "AttributeError", exact = true) +public class PyAttributeError extends PyException +{ + protected PyAttributeError() + { + super(); + } + + protected PyAttributeError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyAttributeError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyBaseException.java b/native/java/python.lang/python/lang/exc/PyBaseException.java new file mode 100644 index 000000000..7dc03a56d --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyBaseException.java @@ -0,0 +1,59 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.manager.TypeInfo; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; +import python.lang.PyBuiltins; +import python.lang.PyObject; + +/** + * A container for holding Python exceptions. + * + * Java does not have an interface for throwable, so we have to throw a wrapper + * class that holds the actual Python Exception. + */ +@PyTypeInfo(name = "BaseException", exact = true) +public class PyBaseException extends RuntimeException implements PyObject +{ + long _self; + + public PyBaseException() + { + super(); + } + + PyBaseException(PyConstructor key, long instance) + { + this._self = instance; + key.link(this, instance); + } + + static Object _allocate(long inst) + { + return new PyBaseException(ALLOCATOR, inst); + } + + @Override + public String getMessage() + { + System.out.println("Get message " + PyBuiltins.str(this)); + return "IMPLEMENT ME"; //PyBuiltins.str(this).toString(); + } + +} diff --git a/native/java/python.lang/python/lang/exc/PyEOFError.java b/native/java/python.lang/python/lang/exc/PyEOFError.java new file mode 100644 index 000000000..6bd553b8f --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyEOFError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "EOFError", exact = true) +public class PyEOFError extends PyException +{ + protected PyEOFError() + { + super(); + } + + protected PyEOFError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyEOFError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyException.java b/native/java/python.lang/python/lang/exc/PyException.java new file mode 100644 index 000000000..7a514d3b6 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyException.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "Exception", exact = true) +public class PyException extends PyBaseException +{ + protected PyException() + { + super(); + } + + protected PyException(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyException(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyFloatingPointError.java b/native/java/python.lang/python/lang/exc/PyFloatingPointError.java new file mode 100644 index 000000000..2912ec511 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyFloatingPointError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "FloatingPointError", exact = true) +public class PyFloatingPointError extends PyArithmeticError +{ + protected PyFloatingPointError() + { + super(); + } + + protected PyFloatingPointError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyFloatingPointError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyIOError.java b/native/java/python.lang/python/lang/exc/PyIOError.java new file mode 100644 index 000000000..e58715911 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyIOError.java @@ -0,0 +1,27 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "IOError", exact = true) +public class PyIOError extends PyBaseException +{ + protected PyIOError() + { + } + +} diff --git a/native/java/python.lang/python/lang/exc/PyImportError.java b/native/java/python.lang/python/lang/exc/PyImportError.java new file mode 100644 index 000000000..3aadc95c0 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyImportError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "ImportError", exact = true) +public class PyImportError extends PyException +{ + protected PyImportError() + { + super(); + } + + protected PyImportError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyImportError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyIndexError.java b/native/java/python.lang/python/lang/exc/PyIndexError.java new file mode 100644 index 000000000..a7b08e088 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyIndexError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "IndexError", exact = true) +public class PyIndexError extends PyLookupError +{ + protected PyIndexError() + { + super(); + } + + protected PyIndexError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyIndexError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyKeyError.java b/native/java/python.lang/python/lang/exc/PyKeyError.java new file mode 100644 index 000000000..4137d6629 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyKeyError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "KeyError", exact = true) +public class PyKeyError extends PyLookupError +{ + protected PyKeyError() + { + super(); + } + + protected PyKeyError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyKeyError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyKeyboardInterrupt.java b/native/java/python.lang/python/lang/exc/PyKeyboardInterrupt.java new file mode 100644 index 000000000..be139c51d --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyKeyboardInterrupt.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "KeyboardInterrupt", exact = true) +public class PyKeyboardInterrupt extends PyBaseException +{ + protected PyKeyboardInterrupt() + { + super(); + } + + protected PyKeyboardInterrupt(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyKeyboardInterrupt(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyLookupError.java b/native/java/python.lang/python/lang/exc/PyLookupError.java new file mode 100644 index 000000000..12f9661b2 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyLookupError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "LookupError", exact = true) +public class PyLookupError extends PyException +{ + protected PyLookupError() + { + super(); + } + + protected PyLookupError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyLookupError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyMemoryError.java b/native/java/python.lang/python/lang/exc/PyMemoryError.java new file mode 100644 index 000000000..96127ff9e --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyMemoryError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "MemoryError", exact = true) +public class PyMemoryError extends PyException +{ + protected PyMemoryError() + { + super(); + } + + protected PyMemoryError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyMemoryError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyNameError.java b/native/java/python.lang/python/lang/exc/PyNameError.java new file mode 100644 index 000000000..4f2f011c1 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyNameError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "NameError", exact = true) +public class PyNameError extends PyException +{ + protected PyNameError() + { + super(); + } + + protected PyNameError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyNameError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyNotImplementedError.java b/native/java/python.lang/python/lang/exc/PyNotImplementedError.java new file mode 100644 index 000000000..9c10834bd --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyNotImplementedError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "NotImplementedError", exact = true) +public class PyNotImplementedError extends PyRuntimeError +{ + protected PyNotImplementedError() + { + super(); + } + + protected PyNotImplementedError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyNotImplementedError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyOSError.java b/native/java/python.lang/python/lang/exc/PyOSError.java new file mode 100644 index 000000000..c2019712f --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyOSError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "OSError", exact = true) +public class PyOSError extends PyException +{ + protected PyOSError() + { + super(); + } + + protected PyOSError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyOSError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyOverflowError.java b/native/java/python.lang/python/lang/exc/PyOverflowError.java new file mode 100644 index 000000000..9f87e3fda --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyOverflowError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "OverflowError", exact = true) +public class PyOverflowError extends PyArithmeticError +{ + protected PyOverflowError() + { + super(); + } + + protected PyOverflowError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyOverflowError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyReferenceError.java b/native/java/python.lang/python/lang/exc/PyReferenceError.java new file mode 100644 index 000000000..b1e4b90f7 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyReferenceError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "ReferenceError", exact = true) +public class PyReferenceError extends PyException +{ + protected PyReferenceError() + { + super(); + } + + protected PyReferenceError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyReferenceError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyRuntimeError.java b/native/java/python.lang/python/lang/exc/PyRuntimeError.java new file mode 100644 index 000000000..74f8aa6b3 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyRuntimeError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "RuntimeError", exact = true) +public class PyRuntimeError extends PyException +{ + protected PyRuntimeError() + { + super(); + } + + protected PyRuntimeError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyRuntimeError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PySyntaxError.java b/native/java/python.lang/python/lang/exc/PySyntaxError.java new file mode 100644 index 000000000..8c6a496f4 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PySyntaxError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "SyntaxError", exact = true) +public class PySyntaxError extends PyException +{ + protected PySyntaxError() + { + super(); + } + + protected PySyntaxError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PySyntaxError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PySystemError.java b/native/java/python.lang/python/lang/exc/PySystemError.java new file mode 100644 index 000000000..c7b90e012 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PySystemError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "SystemError", exact = true) +public class PySystemError extends PyException +{ + protected PySystemError() + { + super(); + } + + protected PySystemError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PySystemError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PySystemExit.java b/native/java/python.lang/python/lang/exc/PySystemExit.java new file mode 100644 index 000000000..a07f9ab2d --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PySystemExit.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "SystemExit", exact = true) +public class PySystemExit extends PyBaseException +{ + protected PySystemExit() + { + super(); + } + + protected PySystemExit(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PySystemExit(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyTypeError.java b/native/java/python.lang/python/lang/exc/PyTypeError.java new file mode 100644 index 000000000..db6b4e18e --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyTypeError.java @@ -0,0 +1,47 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "TypeError", exact = true) +public class PyTypeError extends PyException +{ + protected PyTypeError() + { + super(); + } + + protected PyTypeError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyTypeError(ALLOCATOR, inst); + } + + public PyTypeError(String value) + { + // FIXME this needs to go to the PyException ctor + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyValueError.java b/native/java/python.lang/python/lang/exc/PyValueError.java new file mode 100644 index 000000000..f00e4b343 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyValueError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "ValueError", exact = true) +public class PyValueError extends PyException +{ + protected PyValueError() + { + super(); + } + + protected PyValueError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyValueError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/PyZeroDivisionError.java b/native/java/python.lang/python/lang/exc/PyZeroDivisionError.java new file mode 100644 index 000000000..7b706ba69 --- /dev/null +++ b/native/java/python.lang/python/lang/exc/PyZeroDivisionError.java @@ -0,0 +1,41 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.exc; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyConstructor; +import static org.jpype.python.internal.PyConstructor.ALLOCATOR; + +@PyTypeInfo(name = "ZeroDivisionError", exact = true) +public class PyZeroDivisionError extends PyArithmeticError +{ + protected PyZeroDivisionError() + { + super(); + } + + protected PyZeroDivisionError(PyConstructor key, long instance) + { + super(key, instance); + } + + static Object _allocate(long inst) + { + return new PyZeroDivisionError(ALLOCATOR, inst); + } + + +} diff --git a/native/java/python.lang/python/lang/exc/cogen.py b/native/java/python.lang/python/lang/exc/cogen.py new file mode 100644 index 000000000..c3ba4345d --- /dev/null +++ b/native/java/python.lang/python/lang/exc/cogen.py @@ -0,0 +1,43 @@ +exc = [ArithmeticError, AssertionError, AttributeError, EOFError, Exception, + FloatingPointError, IOError, ImportError, IndexError, KeyError, KeyboardInterrupt, + LookupError, MemoryError, NameError, NotImplementedError, OSError, OverflowError, + ReferenceError, RuntimeError, SyntaxError, SystemError, SystemExit, + TypeError, ValueError, ZeroDivisionError, ] + + +def makeClass(exc, base): + pName = exc.__name__ + eName = "Py" + exc.__name__ + bName = "Py" + base.__name__ + with open(eName + ".java", "w") as fd: + print("\n".join([ + "package python.lang.exc;", + "", + "import org.jpype.python.annotation.PyTypeInfo;", + "import org.jpype.python.internal.PyConstructor;", + "import static org.jpype.python.internal.PyConstructor.ALLOCATOR;", + "", + f"@PyTypeInfo(name = \"{pName}\", exact = true)", + f"public class {eName} extends {bName}", + "{", + f" protected {eName}()", + " {", + " super();", + " }", + "", + f" protected {eName}(PyConstructor key, long instance)", + " {", + " super(key, instance);", + " }", + "", + " static Object _allocate(long inst)", + " {", + f" return new {eName}(ALLOCATOR, inst);", + " }", + "", + "", + "}"]), file=fd) + + +for e in exc: + makeClass(e, e.__mro__[1]) diff --git a/native/java/python.lang/python/lang/package-info.java b/native/java/python.lang/python/lang/package-info.java new file mode 100644 index 000000000..c315b5bad --- /dev/null +++ b/native/java/python.lang/python/lang/package-info.java @@ -0,0 +1,36 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +/** + * Package holding basic types for Python. + * + * These are the concrete (or stubs for concrete) types that Python implements. + * + * The type system will also make its best guess as to the most appropriate type + * to use for a wrapper. If if can't find an appropriate type it will use an + * protocols as a mixin to represent the capabilities of the class. Even if the + * wrapper is not exactly the correct type, all functionality is available + * through the generic object interface though the attributes and call + * mechanisms. + * + * Naming of classes is Py followed by the name as it appears in Python a few + * exceptions. + * + * Builtin functions are held in the PyBuiltin class which has static methods + * for each of the types. + * + */ diff --git a/native/java/python.lang/python/lang/protocol/PyBuffer.java b/native/java/python.lang/python/lang/protocol/PyBuffer.java new file mode 100644 index 000000000..240e11850 --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyBuffer.java @@ -0,0 +1,25 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; +import python.lang.PyObject; + +@PyTypeInfo(name = "protocol.buffer", exact = true) +public interface PyBuffer extends PyObject +{ + // FIXME allow conversion to direct byte buffer +} diff --git a/native/java/python.lang/python/lang/protocol/PyCallable.java b/native/java/python.lang/python/lang/protocol/PyCallable.java new file mode 100644 index 000000000..268529afb --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyCallable.java @@ -0,0 +1,91 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBuiltinStatic; +import python.lang.PyArguments; +import python.lang.PyDict; +import python.lang.PyKeywords; +import python.lang.PyTuple; + +@PyTypeInfo(name = "protocol.callable", exact = true) +public interface PyCallable +{ + + /** + * Call a Python object as a callable. + * + * Normally the argument list is converted into a PyTuple an passed to the + * Python call. + * + * This method has interactions with PyArguments and PyKeywords types. If the + * last argument is type PyKeywords, then it will be used to expand to list of + * keywords. If the keywords are proceeded by a PyArguments type or the last + * argument is PyArguments, then that object will expand into a list of + * arguments. This can be used to express the {@code *args} and + * {@code **kwargs} formulations to a Python call. + * + * If the only argument is to be None use {@code call(PyBuiltins.None) rather + * than {@code call(null)} as the latter is likely to be misinterpreted as an + * empty argument list without a cast. + * + * A PyDict can be converted into a PyKeywords and a PyTyple can be converted + * into a PyArguments. + * + * @param args + * @return an object or null if the return is None. + */ + default Object call(Object... args) + { + PyTuple tuple; + PyDict kwargs = null; + + // Passing a null to the arguments + if (args == null || args.length == 0) + { + return PyBuiltinStatic.INSTANCE.call(this, null, null); + } + + int n = args.length; + + // Handle PyKeywords + if (n > 0 && args[n - 1] instanceof PyKeywords) + { + kwargs = ((PyKeywords) args[n - 1]).toDict(); + n--; // Strip the last argument from the arguments + } + + // Handle PyArguments + if (n > 0 && args[n - 1] instanceof PyArguments) + { + // Extract the tuple backing it. + tuple = ((PyArguments) args[n - 1]).toTuple(); + + // If we have both regular and PyArguments then we have to merge them + if (n > 1) + tuple = PyProtocolInternal.merge(args, 0, n - 1, tuple); + } else + { + // Nothing special just pack the arguments in a tuple. + tuple = PyTuple.ofRange(args, 0, n); + } + + // Call the native method + return PyBuiltinStatic.INSTANCE.call(this, tuple, kwargs); + } + +} \ No newline at end of file diff --git a/native/java/python.lang/python/lang/protocol/PyCollection.java b/native/java/python.lang/python/lang/protocol/PyCollection.java new file mode 100644 index 000000000..9493c4ecf --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyCollection.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "protocol.collection", exact = true) +public interface PyCollection extends PySized, PyIterable, PyContainer +{ + +} diff --git a/native/java/python.lang/python/lang/protocol/PyContainer.java b/native/java/python.lang/python/lang/protocol/PyContainer.java new file mode 100644 index 000000000..56bcc96ca --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyContainer.java @@ -0,0 +1,29 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; +import static python.lang.protocol.PySequence.SEQUENCE_STATIC; + +@PyTypeInfo(name = "protocol.container", exact = true) +public interface PyContainer +{ + + default boolean contains(Object o) + { + return SEQUENCE_STATIC.contains(this, o); + } +} diff --git a/native/java/python.lang/python/lang/protocol/PyDescriptor.java b/native/java/python.lang/python/lang/protocol/PyDescriptor.java new file mode 100644 index 000000000..3ba20cf5b --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyDescriptor.java @@ -0,0 +1,24 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; + +@PyTypeInfo(name = "protocol.descriptor", exact = true) +public interface PyDescriptor +{ + +} diff --git a/native/java/python.lang/python/lang/protocol/PyHandle.java b/native/java/python.lang/python/lang/protocol/PyHandle.java new file mode 100644 index 000000000..7bb7e76e9 --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyHandle.java @@ -0,0 +1,30 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +/** + * + * @author nelson85 + */ +public interface PyHandle +{ + /** + * Get the unique memory location for this object. + * + * @return + */ + long _id(); +} diff --git a/native/java/python.lang/python/lang/protocol/PyIterable.java b/native/java/python.lang/python/lang/protocol/PyIterable.java new file mode 100644 index 000000000..a00a805bf --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyIterable.java @@ -0,0 +1,33 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBuiltinStatic; + +/** + * + * @param + */ +@PyTypeInfo(name = "protocol.iterable", exact = true) +public interface PyIterable extends Iterable +{ + @Override + default PyIterator iterator() + { + return (PyIterator) PyBuiltinStatic.INSTANCE.iter(this); + } +} diff --git a/native/java/python.lang/python/lang/protocol/PyIterator.java b/native/java/python.lang/python/lang/protocol/PyIterator.java new file mode 100644 index 000000000..575856f60 --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyIterator.java @@ -0,0 +1,37 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.internal.PyIteratorPrivate; +import java.util.Iterator; +import org.jpype.python.annotation.PyTypeInfo; +import python.lang.PyObject; + +/** + * + * @author nelson85 + * @param + */ +@PyTypeInfo(name = "protocol.iterator", exact = true, internal = PyIteratorPrivate.class) +public interface PyIterator extends PyObject, Iterator +{ + + @Override + boolean hasNext(); + + @Override + E next(); +} diff --git a/native/java/python.lang/python/lang/protocol/PyMapping.java b/native/java/python.lang/python/lang/protocol/PyMapping.java new file mode 100644 index 000000000..4965b788a --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyMapping.java @@ -0,0 +1,162 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.Types; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyMappingStatic; +import python.lang.PyObject; +import python.lang.exc.PyException; +import python.lang.exc.PyTypeError; + +/** + * PyMapping is less than a Map and implemented by classes such as PyString and + * PySequence. + * + * @author nelson85 + * @param + * @param + */ +@PyTypeInfo(name = "protocol.mapping", exact = true) +public interface PyMapping extends PySized, PyObject +{ + final static PyMappingStatic MAPPING_STATIC = Types.newInstance(PyMappingStatic.class); + + @Override + default int size() + { + return MAPPING_STATIC.size(this); + } + + /** + * Return element of o corresponding to the string key or NULL on failure.This + * is the equivalent of the Python expression o[key]. + * + * See also PyObject_GetItem(). + * + * @param key + * @return the corresponding value or null if not found. + */ + default V getItem(K key) + { + if (key instanceof String) + return (V) MAPPING_STATIC.getItemString(this, (String) key); + else + return (V) MAPPING_STATIC.getItem(this, (String) key); + } + + /** + * This is the equivalent of the Python statement o[key] = v. + *

+ * Map the string key to the value v in object o. + * + * See also PyObject_SetItem(). + * + * @param key + * @param value + * @throws PyException on failure. + */ + default void setItem(K key, V value) throws PyTypeError + { + if (key instanceof String) + MAPPING_STATIC.setItemString(this, (String) key, value); + else + MAPPING_STATIC.setItem(this, key, value); + } + + /** + * This is equivalent to the Python statement del o[key]. + *

+ * Remove the mapping for the object key from the object o. + * + * This is an alias of PyObject_DelItem(). + * + * @param key specifies the entry to be deleted. + * @throws PyException on failure. + */ + default void delItem(Object key) throws PyTypeError + { + if (key instanceof String) + MAPPING_STATIC.delItemString(this, (String) key); + else + MAPPING_STATIC.delItem(this, key); + } + + /** + * Return 1 if the mapping object has the key key and 0 otherwise. This is + * equivalent to the Python expression key in o. This function always + * succeeds. + * + * Note that exceptions which occur while calling the __getitem__() method + * will get suppressed. To get error reporting use PyObject_GetItem() instead. + * + * @param key + * @return + */ + default boolean hasKey(Object key) + { + if (key instanceof String) + return MAPPING_STATIC.hasKeyString(this, (String) key); + return MAPPING_STATIC.hasKey(this, key); + } + + /** + * Return value: New reference. + * + * On success, return a list of the keys in object o. On failure, return NULL. + * + * Changed in version 3.7: Previously, the function returned a list or a + * tuple. + * + * @return + */ + default PySequence keys() + { + return (PySequence) MAPPING_STATIC.keys(this); + } + + /** + * Return value: New reference.On success, return a list of the values in + * object o. + * + * On failure, return NULL. + * + * Changed in version 3.7: Previously, the function returned a list or a + * tuple. + * + * @return + */ + default PySequence values() + { + return (PySequence) MAPPING_STATIC.values(this); + } + + /** + * Return value: New reference.On success, return a list of the items in + * object o, where each item is a tuple containing a key-value pair. + * + * On failure, return NULL. + * + * Changed in version 3.7: Previously, the function returned a list or a tuple + * + * @return + */ + default PySequence items() + { + return (PySequence) MAPPING_STATIC.items(this); + } + +} diff --git a/native/java/python.lang/python/lang/protocol/PyNumber.java b/native/java/python.lang/python/lang/protocol/PyNumber.java new file mode 100644 index 000000000..0350d16c1 --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyNumber.java @@ -0,0 +1,396 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyNumberStatic; +import python.lang.PyObject; +import python.lang.PyString; +import python.lang.exc.PyBaseException; + +@PyTypeInfo(name = "protocol.number", exact = true) +public interface PyNumber extends PyObject +{ + final static PyNumberStatic NUMBER_STATIC = PyNumberStatic.INSTANCE; + + /** + * This is the equivalent of the Python expression o1 + o2. + * + * Return value: New reference.Returns the result of adding o1 and o2, or NULL + * on failure. + * + * + * @param o2 + * @return + */ + default Object add(Object o2) + { + return NUMBER_STATIC.add(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 - o2'. + * + * @param o2 + * @return + */ + default Object subtract(Object o2) + { + return NUMBER_STATIC.subtract(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 * o2'. + * + * @param o2 + * @return + */ + default Object multiply(Object o2) + { + return NUMBER_STATIC.multiply(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 @ o2'. + * + * @param o2 + * @return + */ + default Object matrixMultiply(Object o2) + { + return NUMBER_STATIC.matrixMultiply(this, o2); + } + + /** + * Equivalent to the “classic” division of integers. + * @param o2 + * @return + */ + default Object floorDivide(Object o2) + { + return NUMBER_STATIC.floorDivide(this, o2); + } + + default Object trueDivide(Object o2) + { + return NUMBER_STATIC.trueDivide(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 % o2'. + * + * @param o2 + * @return + */ + default Object remainder(Object o2) + { + return NUMBER_STATIC.remainder(this, o2); + } + + /** + * Equivalent of the Python expression 'divmod(o1,o2)'. + * + * @param o2 + * @return + */ + default Object divmod(Object o2) + { + return NUMBER_STATIC.divmod(this, o2); + } + + /** + * Equivalent of the Python expression 'pow(o1, o2, o3)'. + * + * @param o2 + * @param o3 + * @return + */ + default Object power(Object o2, Object o3) + { + return NUMBER_STATIC.power(this, o2, o3); + } + + default Object power(Object o2) + { + return NUMBER_STATIC.power(this, o2); + } + + /** + * Equivalent of the Python expression '-o'. + * + * @return + */ + default Object negative() + { + return NUMBER_STATIC.negative(this); + } + + /** + * Equivalent of the Python expression '+o'. + * + * @return + */ + default Object positive() + { + return NUMBER_STATIC.positive(this); + } + + /** + * Equivalent of the Python expression 'abs(o)'. + * + * @return + */ + default Object absolute() + { + return NUMBER_STATIC.absolute(this); + } + + /** + * Equivalent of the Python expression '~o'. + * + * @return + */ + default Object invert() + { + return NUMBER_STATIC.invert(this); + } + + /** + * Equivalent of the Python expression 'o1 << o2'. + * + * @param o2 + * @return + */ + default Object leftShift(Object o2) + { + return NUMBER_STATIC.leftShift(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 >> o2'. + * + * @param o2 + * @return + */ + default Object rightShift(Object o2) + { + return NUMBER_STATIC.rightShift(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 & o2'. + * + * @param o2 + * @return + */ + default Object and(Object o2) + { + return NUMBER_STATIC.and(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 ^ o2'. + * + * @param o2 + * @return + */ + default Object xor(Object o2) + { + return NUMBER_STATIC.xor(this, o2); + } + + /** + * Equivalent of the Python expression 'o1 | o2'. + * + * @param o2 + * @return + */ + default Object or(Object o2) + { + return NUMBER_STATIC.or(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 += o2'. + * + * @param o2 + * @return + */ + default Object addAssign(Object o2) + { + return NUMBER_STATIC.addAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 -= o2'. + * + * @param o2 + * @return + */ + default Object subtractAssign(Object o2) + { + return NUMBER_STATIC.subtractAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 *= o2'. + * + * @param o2 + * @return + */ + default Object multiplyAssign(Object o2) + { + return NUMBER_STATIC.multiplyAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 @= o2'. + * + * @param o2 + * @return + */ + default Object matrixMultiplyAssign(Object o2) + { + return NUMBER_STATIC.matrixMultiplyAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 //= o2'. + * + * @param o2 + * @return + */ + default Object floorDivideAssign(Object o2) + { + return NUMBER_STATIC.floorDivideAssign(this, o2); + } + + default Object trueDivideAssign(Object o2) + { + return NUMBER_STATIC.trueDivideAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 %= o2'. + * + * @param o2 + * @return + */ + default Object remainderAssign(Object o2) + { + return NUMBER_STATIC.remainderAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 **= o2'. + * + * @param o2 + * @return + */ + default Object powerAssign(Object o2) + { + return NUMBER_STATIC.powerAssign(this, o2); + } + + default Object powerAssign(Object o2, Object o3) + { + return NUMBER_STATIC.powerAssign(this, o2, o3); + } + + /** + * Equivalent of the Python statement 'o1 <<= o2'. + * + * @param o2 + * @return + */ + default Object leftShiftAssign(Object o2) + { + return NUMBER_STATIC.leftShiftAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 >>= o2'. + * + * @param o2 + * @return + */ + default Object rightShiftAssign(Object o2) + { + return NUMBER_STATIC.rightShiftAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 &= o2'. + * + * @param o2 + * @return + */ + default Object andAssign(Object o2) + { + return NUMBER_STATIC.andAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 ^= o2'. + * + * @param o2 + * @return + */ + default Object xorAssign(Object o2) + { + return NUMBER_STATIC.xorAssign(this, o2); + } + + /** + * Equivalent of the Python statement 'o1 |= o2'. + * + * @param o2 + * @return + */ + default Object orAssign(Object o2) + { + return NUMBER_STATIC.orAssign(this, o2); + } + + /** + * Convert an integer n converted to base base as a string. + * + * The base argument must be one of 2, 8, 10, or 16.For base 2, 8, or 16, the + * returned string is prefixed with a base marker of '0b', '0o', or '0x', + * respectively. + * + * If n is not a Python int, it is converted with asIndex() first. + * + * @param base + * @return + */ + default PyString toBase(int base) + { + return NUMBER_STATIC.toBase(this, base); + } + + /** + * Convert to long or raise the exception. + * + * @param exc + * @return + */ + default long asSize(PyBaseException exc) + { + return NUMBER_STATIC.asSize(this, exc); + } + +} diff --git a/native/java/python.lang/python/lang/protocol/PyProtocolInternal.java b/native/java/python.lang/python/lang/protocol/PyProtocolInternal.java new file mode 100644 index 000000000..3c0403f9c --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PyProtocolInternal.java @@ -0,0 +1,36 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.internal.PyBaseObject; +import python.lang.PyTuple; + +/** + * Private class with support methods for Protocol. + * + * @author nelson85 + */ +class PyProtocolInternal +{ + + static PyTuple merge(Object[] args, int i, int i0, PyTuple tuple) + { + return (PyTuple) _mergeTuple(args, i, i0, PyBaseObject._getSelf(tuple)); + } + + native static Object _mergeTuple(Object[] args, int i, int i0, long tuple); + +} diff --git a/native/java/python.lang/python/lang/protocol/PySequence.java b/native/java/python.lang/python/lang/protocol/PySequence.java new file mode 100644 index 000000000..c60cb70ff --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PySequence.java @@ -0,0 +1,423 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; +import org.jpype.python.Types; +import org.jpype.python.annotation.PyMethodInfo; +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.enums.PyInvocation; +import org.jpype.python.internal.PyBuiltinStatic; +import org.jpype.python.internal.PySequenceStatic; +import python.lang.PyList; +import python.lang.PyTuple; +import python.lang.exc.PyException; + +@PyTypeInfo(name = "protocol.sequence", exact = true) +public interface PySequence extends PyCollection, List, RandomAccess +{ + + final static PySequenceStatic SEQUENCE_STATIC = Types.newInstance(PySequenceStatic.class); + + /** + * Get the length of the sequence. + *

+ * If both sequence and mapping are implemented, then sequence size is used. + */ + @PyMethodInfo(name = "PyObject_Size", invoke = PyInvocation.AsLong, method = true) + @Override + default int size() + { + // PyMethodInfo is used to ensure that derived classes will override if both + // sequence and mapping are inherited. + return PyBuiltinStatic.INSTANCE.len(this); + } + + @Override + default boolean isEmpty() + { + return size() == 0; + } + + @Override + default PyIterator iterator() + { + return (PyIterator) PyBuiltinStatic.INSTANCE.iter(this); + } + + /** + * Get the concatenation of o1 and o2 on success, and NULL on failure. + *

+ * This is the equivalent of the Python expression o1 + o2. + * + * @param o2 + * @return + */ + default Object concat(Object o2) + { + return SEQUENCE_STATIC.concat(this, o2); + } + + /** + * Get the result of repeating sequence object o count times . + *

+ * This is the equivalent of the Python expression o * count. + * + * @param count + * @return + */ + default Object repeat(int count) + { + return SEQUENCE_STATIC.repeat(this, count); + } + + /** + * + * Get the concatenation of o1 and o2 on success. + *

+ * The operation is done in-place when supported. This is the equivalent of + * the Python expression o1 += o2. + * + * @param o2 + * @return + */ + default Object assignConcat(Object o2) + { + return SEQUENCE_STATIC.assignConcat(this, o2); + } + + /** + * Get the result of repeating sequence object o count times. + *

+ * The operation is done in-place when o supports it. This is the equivalent + * of the Python expression o *= count. + * + * @param count + * @return + */ + default Object assignRepeat(int count) + { + return SEQUENCE_STATIC.assignRepeat(this, count); + } + + /** + * Get an element of o. + * + *

+ * This is the equivalent of the Python expression o[i]. + * + * @throws PyException on failure. + */ + @Override + default E get(int index) throws PyException + { + return (E) SEQUENCE_STATIC.get(this, index); + } + + /** + * Get the slice of sequence object o between i1 and i2. + *

+ * This is the equivalent of the Python expression o[i1:i2]. + * + * @param i1 + * @param i2 + * @return + * @throws PyException on failure. + */ + default Object getSlice(int i1, int i2) throws PyException + { + return SEQUENCE_STATIC.getSlice(this, i1, i2); + } + + /** + * Assign a value to a position. + *

+ * This is the equivalent of the Python statement o[i] = v. + * + * @param index + * @param value + * @throws PyException on failure. + */ + @Override + default E set(int index, E value) throws PyException + { + E out = get(index); + setItem(index, value); + return out; + } + + default void setItem(int index, E value) throws PyException + { + SEQUENCE_STATIC.setItem(this, index, value); + } + + /** + * Delete the ith element. + *

+ * This is the equivalent of the Python statement del o[i]. + * + * @param index + * @throws PyException on failure. + */ + default void delItem(int index) throws PyException + { + SEQUENCE_STATIC.delItem(this, index); + } + + /** + * Assign the sequence object v to the slice in sequence object o from i1 to + * i2. + *

+ * This is the equivalent of the Python statement o[i1:i2] = v. + * + * @param start is the start of the assignment. + * @param end is the end of the assignment (exclusive). + * @param value + * @throws PyException on failure. + * + */ + default void setSlice(int start, int end, Object value) + { + SEQUENCE_STATIC.setSlice(this, start, end, value); + } + + /** + * Delete the slice in sequence. + *

+ * This is the equivalent of the Python statement del o[i1:i2]. + * + * @param start is the start of the deletion. + * @param end is the end of the deletion (exclusive). + * @throws PyException on failure. + * + */ + default void delSlice(int start, int end) throws PyException + { + SEQUENCE_STATIC.delSlice(this, start, end); + } + + /** + * Count the number of occurrences of a value. + *

+ * This is equivalent to the Python expression o.count(value). + * + * @param value + * @return the number of keys for which o[key] == value. + * @throws PyException on failure. + * + */ + default int count(E value) throws PyException + { + return SEQUENCE_STATIC.count(this, value); + } + + /** + * Determine if o contains value. + *

+ * This is equivalent to the Python expression value in o. + * + * @param value + * @return true if an item in o is equal to value, otherwise false. + * @throws PyException on failure. + */ + @Override + default boolean contains(Object value) throws PyException + { + return SEQUENCE_STATIC.contains(this, value); + } + + /** + * Get the index the first index i for which o[i] == value. + * + * @param value + * @return the index or -1 if not found. + * @throws PyException on failure. + */ + @Override + default int indexOf(Object value) throws PyException + { + return SEQUENCE_STATIC.indexOf(this, value); + } + + /** + * + * Get a list object with the same contents as the sequence or iterable. + *

+ * The returned list is guaranteed to be new. + *

+ * This is equivalent to the Python expression list(o). + * + * @return + * @throws PyException on failure. + */ + default PyList asList() throws PyException + { + return SEQUENCE_STATIC.asList(this); + } + + /** + * + * Get a tuple object with the same contents as the sequence or iterable. + *

+ * If this is a tuple, a new reference will be returned, otherwise a tuple + * will be constructed with the appropriate contents. + *

+ * This is equivalent to the Python expression tuple(o). + * + * @return a tuple with the same values. + */ + default PyTuple asTuple() + { + return SEQUENCE_STATIC.asTuple(this); + } + + @Override + default Object[] toArray() + { + int n = this.size(); + Object[] out = new Object[this.size()]; + for (int i = 0; i < n; ++i) + out[i] = this.get(i); + return out; + } + + @Override + default T[] toArray(T[] a) + { + int n = this.size(); + if (a.length != n) + try + { + a = (T[]) a.getClass().getConstructor(int.class).newInstance(n); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) + { + throw new RuntimeException(ex); + } + for (int i = 0; i < n; ++i) + a[i] = (T) this.get(i); + return a; + } + + @Override + default boolean add(E e) + { + int size = this.size(); + SEQUENCE_STATIC.setSlice(this, size, size, PyTuple.of(e)); + return true; + } + + @Override + default boolean remove(Object o) + { + int i = this.indexOf(o); + if (i==-1) + return false; + SEQUENCE_STATIC.delItem(o, i); + return true; + } + + @Override + default boolean containsAll(Collection c) + { + for (Object v : c) + { + if (!this.contains(v)) + return false; + } + return true; + } + + @Override + default boolean addAll(Collection c) + { + SEQUENCE_STATIC.assignConcat(this, c); + return true; + } + + @Override + default boolean addAll(int index, Collection c) + { + if (c.isEmpty()) + return false; + int size = this.size(); + SEQUENCE_STATIC.setSlice(this, size, size, new PyTuple(c)); + return true; + } + + @Override + @SuppressWarnings("element-type-mismatch") + default boolean removeAll(Collection c) + { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + default boolean retainAll(Collection c) + { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + default void clear() + { + SEQUENCE_STATIC.delSlice(this, 0, this.size()); + } + + @Override + default void add(int index, E element) + { + SEQUENCE_STATIC.setSlice(this, index, index, PyTuple.of(element)); + } + + @Override + default E remove(int index) + { + Object out = SEQUENCE_STATIC.get(this, index); + SEQUENCE_STATIC.delItem(this, index); + return (E) out; + } + + @Override + default int lastIndexOf(Object o) + { + return SEQUENCE_STATIC.indexOf(this, o); + } + + @Override + default ListIterator listIterator() + { + return new PySequenceListIterator<>(this, 0); + } + + @Override + default ListIterator listIterator(int index) + { + return new PySequenceListIterator<>(this, index); + } + + @Override + default List subList(int fromIndex, int toIndex) + { + // It is not clear if this will actually work because the slice may + // be a view or copy. We may have to reimplement in Java. + return (PySequence) SEQUENCE_STATIC.getSlice(this, fromIndex, toIndex); + } + +} diff --git a/native/java/python.lang/python/lang/protocol/PySequenceListIterator.java b/native/java/python.lang/python/lang/protocol/PySequenceListIterator.java new file mode 100644 index 000000000..71a2487a6 --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PySequenceListIterator.java @@ -0,0 +1,103 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import java.util.ListIterator; + +/** + * + * @author nelson85 + */ +public class PySequenceListIterator implements ListIterator +{ + + PySequence outer; + int index; + int last = -1; + + PySequenceListIterator(PySequence outer, int index) + { + this.outer = outer; + this.index = index; + } + + @Override + public boolean hasNext() + { + return index < outer.size(); + } + + @Override + public E next() + { + E out = outer.get(index); + last = index; + index++; + return out; + } + + @Override + public boolean hasPrevious() + { + return (index > 0); + } + + @Override + public E previous() + { + index--; + last = index; + E out = outer.get(index); + return out; + } + + @Override + public int nextIndex() + { + return index; + } + + @Override + public int previousIndex() + { + return index - 1; + } + + @Override + public void remove() + { + if (last == -1) + throw new IllegalStateException(); + outer.remove(last); + last = -1; + } + + @Override + public void set(E arg0) + { + if (last == -1) + throw new IllegalStateException(); + outer.set(last, arg0); + last = -1; + } + + @Override + public void add(E arg0) + { + outer.add(index, arg0); + } + +} diff --git a/native/java/python.lang/python/lang/protocol/PySized.java b/native/java/python.lang/python/lang/protocol/PySized.java new file mode 100644 index 000000000..c2f56756a --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/PySized.java @@ -0,0 +1,29 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +import org.jpype.python.annotation.PyTypeInfo; +import org.jpype.python.internal.PyBuiltinStatic; + +@PyTypeInfo(name = "protocol.sized", exact = true) +public interface PySized +{ + + default int size() + { + return PyBuiltinStatic.INSTANCE.len(this); + } +} diff --git a/native/java/python.lang/python/lang/protocol/package-info.java b/native/java/python.lang/python/lang/protocol/package-info.java new file mode 100644 index 000000000..da61a5b7e --- /dev/null +++ b/native/java/python.lang/python/lang/protocol/package-info.java @@ -0,0 +1,47 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang.protocol; + +/** + * Package holding protocols for Python. + * + * The Python type system is a bit like a buffet. You start by grabbing a plate + * (concrete type) and walking down the line sampling food (protocols) until you + * have your meal. Python looks like it has multiple inheritance, but in fact it + * is purely single inheritance as if you try to mix two types that have + * different concrete types it will fail. We have represented that here as a set + * of concrete types and protocols (Java interfaces). When retrieving an entity + * from Python, it may either a Python object defined as a concrete type plus a + * set of protocols, or as a Java type. Therefore, most methods return a generic + * Object which must be cast to the proper type to use. Methods similarly take + * any generic Object rather than a specific type unless the specific type is + * required. There is no difference between Python types that have the same + * protocols regardless of how it was derived. They will all share the same Java + * class as they all implement the same interface. + * + * The type system will also make its best guess as to the most appropriate type + * to use for a wrapper. If if can't find an appropriate type it will use an + * protocols as a mixin to represent the capabilities of the class. Even if the + * wrapper is not exactly the correct type, all functionality is available + * through the generic object interface though the attributes and call + * mechanisms. + * + * In some cases the same slot is shared for multiple concepts. Is something + * with __getitem__, __len__, and __iter__ a sequence, a mapping, or a string? + * In such a case, we check with Python typing to see what Python would consider + * it to be. + * + */ diff --git a/native/python/include/jp_pythontypes.h b/native/python/include/jp_pythontypes.h index b69ebd690..0ec38c767 100755 --- a/native/python/include/jp_pythontypes.h +++ b/native/python/include/jp_pythontypes.h @@ -94,10 +94,18 @@ class JPPyObject * This policy is used when we are given a new reference that we must * destroy. This will steal a reference. * - * claim reference, and decremented when done. Clears errors if NULL. + * claim reference, and decremented when done. */ static JPPyObject accept(PyObject* obj); + /** + * This policy is used when we are given a new reference that we must + * destroy. This will steal a reference. + * + * claim reference, and decremented when done. Clears errors if NULL. + */ + static JPPyObject acceptClear(PyObject* obj); + /** * This policy is used when we are given a new reference that we must * destroy. This will steal a reference. diff --git a/native/python/include/pyjp.h b/native/python/include/pyjp.h index c9645843d..262173510 100755 --- a/native/python/include/pyjp.h +++ b/native/python/include/pyjp.h @@ -153,7 +153,8 @@ extern PyObject *_JMethodAnnotations; extern PyObject *_JMethodCode; extern PyObject *_JObjectKey; extern PyObject *_JVMNotRunning; -extern PyObject* PyJPClassMagic; +extern PyObject *_JExtension; +extern PyObject *PyJPClassMagic; extern JPContext* JPContext_global; diff --git a/native/python/jp_pythontypes.cpp b/native/python/jp_pythontypes.cpp index e532955cc..df20d091d 100644 --- a/native/python/jp_pythontypes.cpp +++ b/native/python/jp_pythontypes.cpp @@ -57,9 +57,21 @@ JPPyObject JPPyObject::use(PyObject* obj) * This policy is used when we are given a new reference that we must * destroy. This will steal a reference. * - * claim reference, and decremented when done. + * claim reference, clear error, and decremented when done. */ JPPyObject JPPyObject::accept(PyObject* obj) +{ + JP_TRACE_PY("pyref new(accept)", obj); + return JPPyObject(obj, 1); +} + +/** + * This policy is used when we are given a new reference that we must + * destroy. This will steal a reference. + * + * claim reference, clear error, and decremented when done. + */ +JPPyObject JPPyObject::acceptClear(PyObject* obj) { JP_TRACE_PY("pyref new(accept)", obj); if (obj == nullptr) @@ -375,9 +387,9 @@ bool JPPyErr::fetch(JPPyObject& exceptionClass, JPPyObject& exceptionValue, JPPy PyErr_Fetch(&v1, &v2, &v3); if (v1 == nullptr && v2 == nullptr && v3 == nullptr) return false; - exceptionClass = JPPyObject::accept(v1); - exceptionValue = JPPyObject::accept(v2); - exceptionTrace = JPPyObject::accept(v3); + exceptionClass = JPPyObject::acceptClear(v1); + exceptionValue = JPPyObject::acceptClear(v2); + exceptionTrace = JPPyObject::acceptClear(v3); return true; } diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp index fc633e11c..111fc2055 100644 --- a/native/python/pyjp_class.cpp +++ b/native/python/pyjp_class.cpp @@ -32,13 +32,12 @@ struct PyJPClass PyHeapTypeObject ht_type; JPClass *m_Class; PyObject *m_Doc; -} ; +}; PyObject* PyJPClassMagic = nullptr; #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif int PyJPClass_Check(PyObject* obj) @@ -89,8 +88,8 @@ PyObject *PyJPClass_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) } if (magic == 0) { - PyErr_Format(PyExc_TypeError, "Java classes cannot be extended in Python"); - return nullptr; + // Redirect to the class builder + return PyObject_Call(_JExtension, args, kwargs); } auto *typenew = (PyTypeObject*) PyType_Type.tp_new(type, args, kwargs); @@ -466,7 +465,7 @@ int PyJPClass_setattro(PyObject *self, PyObject *attr_name, PyObject *v) if (PyUnicode_GetLength(attr_name) && PyUnicode_ReadChar(attr_name, 0) == '_') return PyType_Type.tp_setattro(self, attr_name, v); - JPPyObject f = JPPyObject::accept(PyJP_GetAttrDescriptor((PyTypeObject*) self, attr_name)); + JPPyObject f = JPPyObject::acceptClear(PyJP_GetAttrDescriptor((PyTypeObject*) self, attr_name)); if (f.isNull()) { const char *name_str = PyUnicode_AsUTF8(attr_name); diff --git a/native/python/pyjp_module.cpp b/native/python/pyjp_module.cpp index 628694b69..f2f3f147e 100644 --- a/native/python/pyjp_module.cpp +++ b/native/python/pyjp_module.cpp @@ -15,6 +15,7 @@ *****************************************************************************/ #include "jpype.h" #include "pyjp.h" +#include "epypj.h" #include "jp_arrayclass.h" #include "jp_primitive_accessor.h" #include "jp_gc.h" @@ -120,11 +121,14 @@ void PyJPModule_loadResources(PyObject* module) _JMethodCode = PyObject_GetAttrString(module, "getMethodCode"); JP_PY_CHECK(); Py_INCREF(_JMethodCode); - + _JExtension = PyObject_GetAttrString(module, "_JExtension"); + JP_PY_CHECK(); + Py_INCREF(_JExtension); _JObjectKey = PyCapsule_New(module, "constructor key", nullptr); } catch (JPypeException&) // GCOVR_EXCL_LINE { + printf("Resource not found\n"); // GCOVR_EXCL_START PyJP_SetStringWithCause(PyExc_RuntimeError, "JPype resource is missing"); JP_RAISE_PYTHON(); @@ -133,8 +137,7 @@ void PyJPModule_loadResources(PyObject* module) } #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif @@ -575,6 +578,43 @@ static PyObject* PyJPModule_isPackage(PyObject *module, PyObject *pkg) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } +static PyObject* PyJPModule_getJavaType(PyObject *module, PyTypeObject *type) +{ + JP_PY_TRY("getJavaType"); + if (EJP_HasPyType(type)) + Py_RETURN_NONE; + + if (!PyType_Check((PyObject*) type)) + { + PyErr_SetString(PyExc_TypeError, "Bad type"); + return NULL; + } + + JPContext *context = PyJPModule_getContext(); + JPJavaFrame frame = JPJavaFrame::outer(context); + EJP_GetClass(frame, type); + Py_RETURN_NONE; + JP_PY_CATCH(NULL); +} + +static PyObject* PyJPModule_toJavaHandle(PyObject *module, PyTypeObject *type) +{ + JP_PY_TRY("getJavaType"); + if (EJP_HasPyType(type)) + Py_RETURN_NONE; + + if (!PyType_Check((PyObject*) type)) + { + PyErr_SetString(PyExc_TypeError, "Bad type"); + return NULL; + } + + JPContext *context = PyJPModule_getContext(); + JPJavaFrame frame = JPJavaFrame::outer(context); + EJP_GetClass(frame, type); + Py_RETURN_NONE; + JP_PY_CATCH(NULL); +} #if 1 // GCOVR_EXCL_START @@ -668,6 +708,19 @@ static PyObject *PyJPModule_bootstrap(PyObject *module) } #endif +PyObject* test() +{ + PyObject *builtins = PyImport_AddModule("builtins"); + PyModuleDef *moduleDef = PyModule_GetDef(builtins); + PyMethodDef *methods = moduleDef->m_methods; + while (methods->ml_name != NULL) + { + printf("%s\n", methods->ml_name); + methods++; + } + Py_RETURN_NONE; +} + static PyMethodDef moduleMethods[] = { // Startup and initialization {"isStarted", (PyCFunction) PyJPModule_isStarted, METH_NOARGS, ""}, @@ -698,10 +751,15 @@ static PyMethodDef moduleMethods[] = { {"arrayFromBuffer", (PyCFunction) PyJPModule_arrayFromBuffer, METH_VARARGS, ""}, {"enableStacktraces", (PyCFunction) PyJPModule_enableStacktraces, METH_O, ""}, {"isPackage", (PyCFunction) PyJPModule_isPackage, METH_O, ""}, + + {"getJavaType", (PyCFunction) PyJPModule_getJavaType, METH_O, ""}, + {"toJavaHandle", (PyCFunction) PyJPModule_toJavaHandle, METH_O, ""}, + {"trace", (PyCFunction) PyJPModule_trace, METH_O, ""}, #ifdef JP_INSTRUMENTATION {"fault", (PyCFunction) PyJPModule_fault, METH_O, ""}, #endif + {"test", (PyCFunction) test, METH_O, ""}, {"examine", (PyCFunction) examine, METH_O, ""}, // sentinel diff --git a/native/python/pyjp_package.cpp b/native/python/pyjp_package.cpp index 67cd17e3a..95a7d369a 100644 --- a/native/python/pyjp_package.cpp +++ b/native/python/pyjp_package.cpp @@ -155,7 +155,7 @@ static PyObject *PyJPPackage_getattro(PyObject *self, PyObject *attr) obj = frame.getPackageObject(pkg, attrName); } catch (JPypeException& ex) { - JPPyObject h = JPPyObject::accept(PyObject_GetAttrString(self, "_handler")); + JPPyObject h = JPPyObject::acceptClear(PyObject_GetAttrString(self, "_handler")); // If something fails, we need to go to a handler if (!h.isNull()) { diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 1deecaa92..32758bbb9 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -212,7 +212,7 @@ PyObject* PyJPValue_str(PyObject* self) if (cls == context->_java_lang_String) { PyObject *cache; - JPPyObject dict = JPPyObject::accept(PyObject_GenericGetDict(self, nullptr)); + JPPyObject dict = JPPyObject::acceptClear(PyObject_GenericGetDict(self, nullptr)); if (!dict.isNull()) { cache = PyDict_GetItemString(dict.get(), "_jstr"); @@ -250,7 +250,7 @@ PyObject *PyJPValue_getattro(PyObject *obj, PyObject *name) PyObject* pyattr = PyBaseObject_Type.tp_getattro(obj, name); if (pyattr == nullptr) return nullptr; - JPPyObject attr = JPPyObject::accept(pyattr); + JPPyObject attr = JPPyObject::acceptClear(pyattr); // Private members go regardless if (PyUnicode_GetLength(name) && PyUnicode_ReadChar(name, 0) == '_') @@ -276,7 +276,7 @@ int PyJPValue_setattro(PyObject *self, PyObject *name, PyObject *value) // Private members are accessed directly if (PyUnicode_GetLength(name) && PyUnicode_ReadChar(name, 0) == '_') return PyObject_GenericSetAttr(self, name, value); - JPPyObject f = JPPyObject::accept(PyJP_GetAttrDescriptor(Py_TYPE(self), name)); + JPPyObject f = JPPyObject::acceptClear(PyJP_GetAttrDescriptor(Py_TYPE(self), name)); if (f.isNull()) { PyErr_Format(PyExc_AttributeError, "Field '%U' is not found", name); @@ -334,6 +334,6 @@ bool PyJPValue_isSetJavaSlot(PyObject* self) Py_ssize_t offset = PyJPValue_getJavaSlotOffset(self); if (offset == 0) return false; // GCOVR_EXCL_LINE - auto* slot = (JPValue*) (((char*) self) + offset); + JPValue* slot = (JPValue*) (((char*) self) + offset); return slot->getClass() != nullptr; } diff --git a/project/epype_java/build.xml b/project/epype_java/build.xml new file mode 100644 index 000000000..9b2cef607 --- /dev/null +++ b/project/epype_java/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project epype_java. + + + diff --git a/project/epype_java/nbproject/build-impl.xml b/project/epype_java/nbproject/build-impl.xml new file mode 100644 index 000000000..e356b75e7 --- /dev/null +++ b/project/epype_java/nbproject/build-impl.xml @@ -0,0 +1,1797 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.jpype.python.dir + Must set src.python.lang.dir + Must set test.epypj.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/epype_java/nbproject/genfiles.properties b/project/epype_java/nbproject/genfiles.properties new file mode 100644 index 000000000..c06f73669 --- /dev/null +++ b/project/epype_java/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=c4b10944 +build.xml.script.CRC32=e7c13c5e +build.xml.stylesheet.CRC32=f85dc8f2@1.95.0.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=c4b10944 +nbproject/build-impl.xml.script.CRC32=a7cf536b +nbproject/build-impl.xml.stylesheet.CRC32=f89f7d21@1.95.0.48 diff --git a/project/epype_java/nbproject/licenseheader.txt b/project/epype_java/nbproject/licenseheader.txt new file mode 100644 index 000000000..a922e019b --- /dev/null +++ b/project/epype_java/nbproject/licenseheader.txt @@ -0,0 +1,15 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ \ No newline at end of file diff --git a/project/epype_java/nbproject/project.properties b/project/epype_java/nbproject/project.properties new file mode 100644 index 000000000..16a1251c4 --- /dev/null +++ b/project/epype_java/nbproject/project.properties @@ -0,0 +1,106 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=epype_java +application.vendor=nelson85 +auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/epype_java.jar +dist.javadoc.dir=${dist.dir}/javadoc +dist.jlink.dir=${dist.dir}/jlink +dist.jlink.output=${dist.jlink.dir}/epype_java +endorsed.classpath= +excludes= +file.reference.java-jpype.python=../../native/java/jpype.python +file.reference.java-python.lang=../../native/java/python.lang +file.reference.testng-epypj=../../test/testng/epypj +includes=** +jar.compress=false +javac.classpath=\ + ${reference.jpype_java.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=true +javac.modulepath= +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=11 +javac.target=11 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.testng.classpath} +javac.test.modulepath=\ + ${javac.modulepath} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.html5=false +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +# The jlink additional root modules to resolve +jlink.additionalmodules= +# The jlink additional command line parameters +jlink.additionalparam= +jlink.launcher=true +jlink.launcher.name=epype_java +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=true +no.dependencies=true +platform.active=default_platform +project.jpype_java=../jpype_java +project.licensePath=./nbproject/licenseheader.txt +reference.jpype_java.jar=${project.jpype_java}/dist/org.jpype.jar +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.modulepath=\ + ${javac.modulepath} +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +run.test.modulepath=\ + ${javac.test.modulepath} +source.encoding=UTF-8 +src.jpype.python.dir=${file.reference.java-jpype.python} +src.python.lang.dir=${file.reference.java-python.lang} +test.epypj.dir=${file.reference.testng-epypj} diff --git a/project/epype_java/nbproject/project.xml b/project/epype_java/nbproject/project.xml new file mode 100644 index 000000000..b331aa1bc --- /dev/null +++ b/project/epype_java/nbproject/project.xml @@ -0,0 +1,26 @@ + + + org.netbeans.modules.java.j2seproject + + + epype_java + + + + + + + + + + + jpype_java + jar + + jar + clean + jar + + + + diff --git a/project/jpype_cpython/nbproject/configurations.xml b/project/jpype_cpython/nbproject/configurations.xml index 158b734f0..3cc9238c8 100755 --- a/project/jpype_cpython/nbproject/configurations.xml +++ b/project/jpype_cpython/nbproject/configurations.xml @@ -40,6 +40,7 @@ ../../native/common/include/jp_primitive_accessor.h ../../native/common/include/jp_primitivetype.h ../../native/common/include/jp_proxy.h + ../../native/common/include/jp_pyobjecttype.h ../../native/common/include/jp_reference_queue.h ../../native/common/include/jp_shorttype.h ../../native/common/include/jp_stringtype.h @@ -50,6 +51,9 @@ ../../native/common/include/jpype.h + + ../../native/embedded/include/epypj.h + ../../native/python/include/jp_pythontypes.h @@ -57,10 +61,7 @@ - + ../../jpype/__init__.py ../../jpype/_classpath.py @@ -172,10 +173,6 @@ ../../test/jpypetest/test_zzz.py - - @@ -197,6 +194,7 @@ ../../native/common/jp_doubletype.cpp ../../native/common/jp_encoding.cpp ../../native/common/jp_exception.cpp + ../../native/common/jp_extension.cpp ../../native/common/jp_field.cpp ../../native/common/jp_floattype.cpp ../../native/common/jp_functional.cpp @@ -212,6 +210,7 @@ ../../native/common/jp_platform.cpp ../../native/common/jp_primitivetype.cpp ../../native/common/jp_proxy.cpp + ../../native/common/jp_pyobjecttype.cpp ../../native/common/jp_reference_queue.cpp ../../native/common/jp_shorttype.cpp ../../native/common/jp_stringtype.cpp @@ -221,6 +220,15 @@ ../../native/common/jp_value.cpp ../../native/common/jp_voidtype.cpp + + ../../native/embedded/ejp_call.cpp + ../../native/embedded/ejp_context.cpp + ../../native/embedded/ejp_ctor.cpp + ../../native/embedded/ejp_engine.cpp + ../../native/embedded/ejp_invoker.cpp + ../../native/common/ejp_module.cpp + ../../native/embedded/ejp_typemanager.cpp + ../../native/python/jp_pythontypes.cpp ../../native/python/pyjp_array.cpp @@ -252,6 +260,7 @@ ../../native/common + ../../native/embedded ../../native/python ../../jpype ../../test/jpypetest @@ -267,6 +276,12 @@ 5 + + ../../native/jni_include + ../../native/python/include + ../../native/common/include + ../../native/embedded/include + 5 @@ -274,6 +289,7 @@ ../../native/jni_include ../../native/python/include ../../native/common/include + ../../native/embedded/include @@ -335,6 +351,8 @@ + + + + + + + + + + + + + + + + + + + + + + ../../native/common + ../../native/embedded ../../native/python ../../jpype ../../test/jpypetest diff --git a/project/jpype_java/nbproject/project.properties b/project/jpype_java/nbproject/project.properties index 76c7151ac..52816e0be 100755 --- a/project/jpype_java/nbproject/project.properties +++ b/project/jpype_java/nbproject/project.properties @@ -3,7 +3,7 @@ annotation.processing.enabled.in.editor=false annotation.processing.processors.list= annotation.processing.run.all.processors=true annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output -application.title=jpype_java +application.title=org.jpype application.vendor= auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=true auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=4 @@ -54,11 +54,16 @@ dist.jar=${dist.dir}/org.jpype.jar dist.javadoc.dir=${dist.dir}/javadoc endorsed.classpath= excludes= -file.reference.native-java=../../native/java +file.reference.asm-8.0.1.jar=..\\..\\lib\\asm-8.0.1.jar +file.reference.java-jpype.core=../../native/java/jpype.core +file.reference.java-jpype.doc=../../native/java/jpype.doc +file.reference.java-jpype.jvm=../../native/java/jpype.jvm +file.reference.java-jpype.jvm.asm=../../native/java/jpype.jvm.asm file.reference.test-harness=../../test/harness includes=** jar.compress=false -javac.classpath= +javac.classpath=\ + ${file.reference.asm-8.0.1.jar} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false @@ -110,6 +115,9 @@ run.test.classpath=\ run.test.modulepath=\ ${javac.test.modulepath} source.encoding=UTF-8 -src.java.dir=${file.reference.native-java} +src.jpype.core.dir=${file.reference.java-jpype.core} +src.jpype.doc.dir=${file.reference.java-jpype.doc} +src.jpype.jvm.asm.dir=${file.reference.java-jpype.jvm.asm} +src.jpype.jvm.dir=${file.reference.java-jpype.jvm} test.harness.dir=${file.reference.test-harness} test.src.dir=test diff --git a/project/jpype_java/nbproject/project.xml b/project/jpype_java/nbproject/project.xml index 74ce17fc9..c72e4319e 100755 --- a/project/jpype_java/nbproject/project.xml +++ b/project/jpype_java/nbproject/project.xml @@ -5,7 +5,10 @@ jpype_java - + + + + diff --git a/project/utility/extractMethodInfo.py b/project/utility/extractMethodInfo.py new file mode 100644 index 000000000..b8dcf0313 --- /dev/null +++ b/project/utility/extractMethodInfo.py @@ -0,0 +1,26 @@ +import glob +import re + +methods = {} +for i in glob.iglob("**/*.java", recursive=True): + with open(i, 'r') as fd: + lines = fd.readlines() + for l in lines: + if l.strip().startswith("//"): + continue + if not "@PyMethodInfo" in l: + continue + + d = {} + for q in l.split("(")[1].split(")")[0].split(","): + m = re.search(r'name = "(.*)"', q) + if m is not None: + d['name'] = m.group(1) + m = re.search(r'invoke = (.*)', q) + if m is not None: + d['invoke'] = m.group(1) + methods[d['name']] = d + +for m in sorted(methods.keys()): + u = methods[m] + print('REGISTER_CALL("%s", %s); // %s' % (u['name'], u['name'], u['invoke'])) diff --git a/resolve.sh b/resolve.sh index 2a385a001..961e2bcc0 100644 --- a/resolve.sh +++ b/resolve.sh @@ -2,5 +2,5 @@ # This is used to pull dependencies needed for this package # drill has a bunch of dependencies, so we are going to use ivy to collect it all at once. -wget -nc "https://repo1.maven.org/maven2/org/apache/ivy/ivy/2.5.0/ivy-2.5.0.jar" -P lib -java -jar lib/ivy-2.5.0.jar -ivy ivy.xml -retrieve 'lib/[artifact]-[revision](-[classifier]).[ext]' +wget -nc "https://repo1.maven.org/maven2/org/apache/ivy/ivy/2.5.0/ivy-2.5.0.jar" -P build/lib +java -jar build/lib/ivy-2.5.0.jar -ivy ivy.xml -retrieve 'lib/[artifact]-[revision](-[classifier]).[ext]' diff --git a/runTestsNG.sh b/runTestsNG.sh new file mode 100644 index 000000000..f30f6da98 --- /dev/null +++ b/runTestsNG.sh @@ -0,0 +1,2 @@ +(cd test/testng; sh build.sh) +java -cp 'project/jpype_java/dist/org.jpype.jar:project/epype_java/dist/epype_java.jar:lib/*:test/testng/classes' org.testng.TestNG test/testng/testng.xml diff --git a/setup.py b/setup.py index c48875b49..a17b34184 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,6 @@ libraries=["lib/asm-8.0.1.jar"] ) - setup( name='JPype1', version='1.5.0_dev0', diff --git a/setupext/__init__.py b/setupext/__init__.py index 39e85b481..86553df8e 100644 --- a/setupext/__init__.py +++ b/setupext/__init__.py @@ -16,10 +16,10 @@ # # ***************************************************************************** +from . import pytester +from . import sdist +from . import test_java from . import utils from . import dist from . import platform from . import build_ext -from . import test_java -from . import sdist -from . import pytester diff --git a/setupext/build_ext.py b/setupext/build_ext.py index 7d42e9960..70a14d099 100644 --- a/setupext/build_ext.py +++ b/setupext/build_ext.py @@ -175,6 +175,7 @@ class BuildExtCommand(build_ext): ('android', None, 'configure for android'), ('makefile', None, 'Build a makefile for extensions'), ('jar', None, 'Build the jar only'), + ('headers', None, 'Build the jar headers'), ] def initialize_options(self, *args): @@ -182,6 +183,7 @@ def initialize_options(self, *args): self.android = False self.makefile = False self.jar = False + self.headers = False import distutils.sysconfig cfg_vars = distutils.sysconfig.get_config_vars() @@ -306,8 +308,12 @@ def build_java_ext(self, ext): build_dir = os.path.join(self.build_temp, ext.name, "classes") os.makedirs(build_dir, exist_ok=True) os.makedirs(dirname, exist_ok=True) - cmd1 = shlex.split('%s -cp "%s" -d "%s" -g:none -source %s -target %s -encoding UTF-8' % - (javac, classpath, build_dir, target_version, target_version)) + + headers = "" + if self.headers: + headers = "-h headers" + cmd1 = shlex.split('%s -cp "%s" -d "%s" -g:none -source %s -target %s %s -encoding UTF-8' % + (javac, classpath, build_dir, target_version, target_version, headers)) cmd1.extend(ext.sources) debug = "-g:none" if coverage: diff --git a/setupext/platform.py b/setupext/platform.py index 28ebd85ec..1c4df9e57 100644 --- a/setupext/platform.py +++ b/setupext/platform.py @@ -122,8 +122,8 @@ def Platform(include_dirs=None, sources=None, platform=sys.platform): # This code is used to include python library in the build when starting Python from # within Java. It will be used in the future, but is not currently required. -# if static and sysconfig.get_config_var('BLDLIBRARY') is not None: -# platform_specific['extra_link_args'].append(sysconfig.get_config_var('BLDLIBRARY')) + if static and sysconfig.get_config_var('BLDLIBRARY') is not None: + platform_specific['extra_link_args'].append(sysconfig.get_config_var('BLDLIBRARY')) if found_jni: distutils.log.info("Add JNI directory %s" % os.path.join(java_home, 'include', jni_md_platform)) diff --git a/test/jpypetest/test_fault.py b/test/jpypetest/test_fault.py index 441aa5466..164e7e234 100644 --- a/test/jpypetest/test_fault.py +++ b/test/jpypetest/test_fault.py @@ -285,6 +285,7 @@ def f(): # pyjp_module.cpp: JP_PY_TRY("examine"); # pyjp_module.cpp: JP_PY_TRY("PyInit__jpype"); + @common.requireInstrumentation def testJPMonitor_init(self): jo = JClass("java.lang.Object")() @@ -423,6 +424,7 @@ def accept(self, d): @common.requireInstrumentation def testJPProxy_box_return(self): q = None + @JImplements("java.util.function.Supplier") class f(object): @JOverride diff --git a/test/jpypetest/test_jedi.py b/test/jpypetest/test_jedi.py index 0afcde208..dc78c3da1 100644 --- a/test/jpypetest/test_jedi.py +++ b/test/jpypetest/test_jedi.py @@ -33,6 +33,7 @@ # FIXME: some jedi version is causing an issue jpype-project/jpype#920 so we pretend not to have jedi, until it is resolved. have_jedi = False + class JediTestCase(common.JPypeTestCase): """Test tab completion on JPype objects """ diff --git a/test/jpypetest/test_sql_h2.py b/test/jpypetest/test_sql_h2.py index e8471ca73..2c169d93c 100644 --- a/test/jpypetest/test_sql_h2.py +++ b/test/jpypetest/test_sql_h2.py @@ -25,7 +25,7 @@ def setUp(self): common.JPypeTestCase.setUp(self) if common.fast: raise common.unittest.SkipTest("fast") - + def testConnect(self): cx = dbapi2.connect(db_name) self.assertIsInstance(cx, dbapi2.Connection) diff --git a/test/testng/build.sh b/test/testng/build.sh new file mode 100644 index 000000000..462a09f7d --- /dev/null +++ b/test/testng/build.sh @@ -0,0 +1 @@ +javac --release 8 -d classes -cp ../../project/epype_java/dist/epype_java.jar:../../lib/* epypj/python/lang/*.java diff --git a/test/testng/epypj/README b/test/testng/epypj/README new file mode 100644 index 000000000..4e0facfe5 --- /dev/null +++ b/test/testng/epypj/README @@ -0,0 +1 @@ +This is the home of unittests for reverse bridge diff --git a/test/testng/epypj/python/lang/PyArgumentsNGTest.java b/test/testng/epypj/python/lang/PyArgumentsNGTest.java new file mode 100644 index 000000000..0ccfe2248 --- /dev/null +++ b/test/testng/epypj/python/lang/PyArgumentsNGTest.java @@ -0,0 +1,88 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyArgumentsNGTest +{ + + public PyArgumentsNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of use method, of class PyArguments. + */ + @Test + public void testUse() + { + System.out.println("use"); + PyTuple args = null; + PyArguments expResult = null; + PyArguments result = PyArguments.use(args); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of toTuple method, of class PyArguments. + */ + @Test + public void testToTuple() + { + System.out.println("toTuple"); + PyArguments instance = null; + PyTuple expResult = null; + PyTuple result = instance.toTuple(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + +} diff --git a/test/testng/epypj/python/lang/PyBuiltinsNGTest.java b/test/testng/epypj/python/lang/PyBuiltinsNGTest.java new file mode 100644 index 000000000..48cf9057a --- /dev/null +++ b/test/testng/epypj/python/lang/PyBuiltinsNGTest.java @@ -0,0 +1,714 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyBuiltinsNGTest +{ + + public PyBuiltinsNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of hasattr method, of class PyBuiltins. + */ + @Test + public void testHasattr() + { + System.out.println("hasattr"); + Object o = null; + CharSequence attr = null; + boolean expResult = false; + boolean result = PyBuiltins.hasattr(o, attr); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of delattr method, of class PyBuiltins. + */ + @Test + public void testDelattr() + { + System.out.println("delattr"); + Object o = null; + CharSequence attr = null; + PyBuiltins.delattr(o, attr); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of getattr method, of class PyBuiltins. + */ + @Test + public void testGetattr() + { + System.out.println("getattr"); + Object o = null; + CharSequence attr = null; + Object expResult = null; + Object result = PyBuiltins.getattr(o, attr); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of setattr method, of class PyBuiltins. + */ + @Test + public void testSetattr() + { + System.out.println("setattr"); + Object o = null; + CharSequence attr = null; + Object value = null; + PyBuiltins.setattr(o, attr, value); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of not method, of class PyBuiltins. + */ + @Test + public void testNot() + { + System.out.println("not"); + PyObject o = null; + boolean expResult = false; + boolean result = PyBuiltins.not(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of isTrue method, of class PyBuiltins. + */ + @Test + public void testIsTrue() + { + System.out.println("isTrue"); + PyObject o = null; + boolean expResult = false; + boolean result = PyBuiltins.isTrue(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of eq method, of class PyBuiltins. + */ + @Test + public void testEq() + { + System.out.println("eq"); + Object a = null; + Object b = null; + boolean expResult = false; + boolean result = PyBuiltins.eq(a, b); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of ne method, of class PyBuiltins. + */ + @Test + public void testNe() + { + System.out.println("ne"); + Object a = null; + Object b = null; + boolean expResult = false; + boolean result = PyBuiltins.ne(a, b); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of gt method, of class PyBuiltins. + */ + @Test + public void testGt() + { + System.out.println("gt"); + Object a = null; + Object b = null; + boolean expResult = false; + boolean result = PyBuiltins.gt(a, b); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of lt method, of class PyBuiltins. + */ + @Test + public void testLt() + { + System.out.println("lt"); + Object a = null; + Object b = null; + boolean expResult = false; + boolean result = PyBuiltins.lt(a, b); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of ge method, of class PyBuiltins. + */ + @Test + public void testGe() + { + System.out.println("ge"); + Object a = null; + Object b = null; + boolean expResult = false; + boolean result = PyBuiltins.ge(a, b); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of le method, of class PyBuiltins. + */ + @Test + public void testLe() + { + System.out.println("le"); + Object a = null; + Object b = null; + boolean expResult = false; + boolean result = PyBuiltins.le(a, b); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of divmod method, of class PyBuiltins. + */ + @Test + public void testDivmod() + { + System.out.println("divmod"); + Object a = null; + Object b = null; + Object expResult = null; + Object result = PyBuiltins.divmod(a, b); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of callable method, of class PyBuiltins. + */ + @Test + public void testCallable() + { + System.out.println("callable"); + Object o = null; + boolean expResult = false; + boolean result = PyBuiltins.callable(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of dir method, of class PyBuiltins. + */ + @Test + public void testDir() + { + System.out.println("dir"); + PyObject o = null; + PyObject expResult = null; + PyObject result = PyBuiltins.dir(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of hash method, of class PyBuiltins. + */ + @Test + public void testHash() + { + System.out.println("hash"); + Object o = null; + long expResult = 0L; + long result = PyBuiltins.hash(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of help method, of class PyBuiltins. + */ + @Test + public void testHelp() + { + System.out.println("help"); + Object o = null; + Object expResult = null; + Object result = PyBuiltins.help(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of isinstance method, of class PyBuiltins. + */ + @Test + public void testIsinstance() + { + System.out.println("isinstance"); + Object o = null; + Object t = null; + boolean expResult = false; + boolean result = PyBuiltins.isinstance(o, t); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of issubclass method, of class PyBuiltins. + */ + @Test + public void testIssubclass() + { + System.out.println("issubclass"); + Object o = null; + Object t = null; + boolean expResult = false; + boolean result = PyBuiltins.issubclass(o, t); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of ascii method, of class PyBuiltins. + */ + @Test + public void testAscii() + { + System.out.println("ascii"); + Object o = null; + Object expResult = null; + Object result = PyBuiltins.ascii(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of repr method, of class PyBuiltins. + */ + @Test + public void testRepr() + { + System.out.println("repr"); + Object o = null; + PyString expResult = null; + PyString result = PyBuiltins.repr(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of str method, of class PyBuiltins. + */ + @Test + public void testStr() + { + System.out.println("str"); + Object o = null; + PyString expResult = null; + PyString result = PyBuiltins.str(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of type method, of class PyBuiltins. + */ + @Test + public void testType() + { + System.out.println("type"); + PyObject o = null; + PyType expResult = null; + PyType result = PyBuiltins.type(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of id method, of class PyBuiltins. + */ + @Test + public void testId() + { + System.out.println("id"); + Object o = null; + PyLong expResult = null; + PyLong result = PyBuiltins.id(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of builtins method, of class PyBuiltins. + */ + @Test + public void testBuiltins() + { + System.out.println("builtins"); + PyDict expResult = null; + PyDict result = PyBuiltins.builtins(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of len method, of class PyBuiltins. + */ + @Test + public void testLen() + { + System.out.println("len"); + PyObject o = null; + int expResult = 0; + int result = PyBuiltins.len(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of lengthHint method, of class PyBuiltins. + */ + @Test + public void testLengthHint() + { + System.out.println("lengthHint"); + PyObject o = null; + int defaultValue = 0; + int expResult = 0; + int result = PyBuiltins.lengthHint(o, defaultValue); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of getItem method, of class PyBuiltins. + */ + @Test + public void testGetItem() + { + System.out.println("getItem"); + PyObject o = null; + Object key = null; + Object expResult = null; + Object result = PyBuiltins.getItem(o, key); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of setItem method, of class PyBuiltins. + */ + @Test + public void testSetItem() + { + System.out.println("setItem"); + PyObject o = null; + Object key = null; + Object v = null; + PyBuiltins.setItem(o, key, v); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of delItem method, of class PyBuiltins. + */ + @Test + public void testDelItem() + { + System.out.println("delItem"); + PyObject o = null; + Object key = null; + PyBuiltins.delItem(o, key); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of iter method, of class PyBuiltins. + */ + @Test + public void testIter() + { + System.out.println("iter"); + PyObject o = null; + Iterator expResult = null; + Iterator result = PyBuiltins.iter(o); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of bool method, of class PyBuiltins. + */ + @Test + public void testBool() + { + System.out.println("bool"); + boolean result = PyBuiltins.bool(new PyLong(0)); + assertEquals(result, false); + result = PyBuiltins.bool(new PyLong(1)); + assertEquals(result, true); + } + + /** + * Test of bytes method, of class PyBuiltins. + */ + @Test + public void testBytes_ByteBuffer() + { + System.out.println("bytes"); + Object result = PyBuiltins.bytes(ByteBuffer.wrap(new byte[] + { + 1, 2, 3 + })); + } + + /** + * Test of bytes method, of class PyBuiltins. + */ + @Test + public void testBytes_byteArr() + { + System.out.println("bytes"); + Object result = PyBuiltins.bytes(new byte[] + { + 1, 2, 3 + }); + } + + /** + * Test of bytearray method, of class PyBuiltins. + */ + @Test + public void testBytearray() + { + System.out.println("bytearray"); + Object result = PyBuiltins.bytearray(ByteBuffer.wrap(new byte[] + { + 1, 2, 3 + })); + } + + /** + * Test of complex method, of class PyBuiltins. + */ + @Test + public void testComplex() + { + System.out.println("complex"); + Object result = PyBuiltins.complex(1, 2); + assertEquals(result, null); + } + + /** + * Test of dict method, of class PyBuiltins. + */ + @Test + public void testDict() + { + System.out.println("dict"); + PyDict result = PyBuiltins.dict(); + assertEquals(result.size(), 0); + } + + /** + * Test of frozenset method, of class PyBuiltins. + */ + @Test + public void testFrozenset() + { + System.out.println("frozenset"); + Iterable s = Arrays.asList("A", "B", "C"); + PySet result = PyBuiltins.frozenset(s); + // FIXME + } + + /** + * Test of list method, of class PyBuiltins. + */ + @Test + public void testList() + { + System.out.println("list"); + Collection e = Arrays.asList("A", "B", "C"); + PyList result = PyBuiltins.list(e); + assertEquals(result.get(0), "A"); + assertEquals(result.get(1), "B"); + assertEquals(result.get(2), "C"); + } + + /** + * Test of memoryview method, of class PyBuiltins. + */ + @Test + public void testMemoryview() + { + System.out.println("memoryview"); + Object o = new PyString("ABC"); + PyMemoryView result = PyBuiltins.memoryview(o); + // FIXME + } + + /** + * Test of set method, of class PyBuiltins. + */ + @Test + public void testSet_Iterable() + { + System.out.println("set"); + Iterable s = Arrays.asList("A", "B", "A"); + PySet result = PyBuiltins.set(s); + assertEquals(result.size(), 2); + } + + /** + * Test of set method, of class PyBuiltins. + */ + @Test + public void testSet_GenericType() + { + System.out.println("set"); + PySet result = PyBuiltins.set("A", "B", "A"); + assertEquals(result.size(), 2); + } + + /** + * Test of slice method, of class PyBuiltins. + */ + @Test + public void testSlice_Object_Object() + { + PySlice result = PyBuiltins.slice(0, 10); + // FIXME + } + + /** + * Test of slice method, of class PyBuiltins. + */ + @Test + public void testSlice_3args() + { + PySlice result = PyBuiltins.slice(0, 10, 2); + // FIXME + } + + /** + * Test of tuple method, of class PyBuiltins. + */ + @Test + public void testTuple() + { + System.out.println("tuple"); + PyTuple result = PyBuiltins.tuple("A", "B", "C"); + assertEquals(result, PyTuple.of("A", "B", "C")); + } + + /** + * Test of object method, of class PyBuiltins. + */ + @Test + public void testObject() + { + System.out.println("object"); + Object result = PyBuiltins.object(); + assertNotEquals(result, null); + } + +} diff --git a/test/testng/epypj/python/lang/PyDictNGTest.java b/test/testng/epypj/python/lang/PyDictNGTest.java new file mode 100644 index 000000000..792d2e16b --- /dev/null +++ b/test/testng/epypj/python/lang/PyDictNGTest.java @@ -0,0 +1,420 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.jpype.python.Engine; +import org.jpype.python.Scope; +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyDictNGTest +{ + + public PyDictNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + Engine engine = PythonTest.getEngine(); + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of asReadOnly method, of class PyDict. + */ + @Test + public void testAsReadOnly() + { + System.out.println("asReadOnly"); + PyDict instance = new PyDict(); + // This should succeed + instance.put("A", "B"); + + // This should fail + PyDict result = instance.asReadOnly(); + result.put("A", "B"); + } + + /** + * Test of clear method, of class PyDict. + */ + @Test + public void testClear() + { + System.out.println("clear"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + assertEquals(instance.size(), 2); + // instance.clear(); + assertEquals(instance.size(), 0); + } + + /** + * Test of containsKey method, of class PyDict. + */ + @Test + public void testContainsKey() + { + System.out.println("containsKey"); + Object key = null; + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + assertEquals(instance.containsKey("C"), false); + assertEquals(instance.containsKey("A"), true); + } + + /** + * Test of containsValue method, of class PyDict. + */ + @Test + public void testContainsValue() + { + System.out.println("containsValue"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + assertEquals(instance.containsValue(3), false); + assertEquals(instance.containsKey(1), true); + } + + /** + * Test of clone method, of class PyDict. + */ + @Test + public void testClone() throws Exception + { + System.out.println("clone"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + PyDict result = instance.clone(); + assertFalse(result == instance); + assertTrue(result.equals(instance)); + } + + /** + * Test of put method, of class PyDict. + */ + @Test + public void testPut() + { + System.out.println("put"); + PyDict instance = new PyDict(); + assertEquals(instance.put("A", 1), null); + assertEquals(instance.put("A", 2), 1); + } + + /** + * Test of setItem method, of class PyDict. + */ + @Test + public void testSetItem() + { + System.out.println("setItem"); + PyDict instance = new PyDict(); + instance.setItem("A", 1); + instance.setItem(2, 2); + } + + /** + * Test of delItem method, of class PyDict. + */ + @Test + public void testDelItem() + { + System.out.println("delItem"); + Object key = null; + PyDict instance = new PyDict(); + instance.setItem("A", 1); + assertTrue(instance.containsKey("A")); + instance.delItem("A"); + assertFalse(instance.containsKey("A")); + } + + /** + * Test of remove method, of class PyDict. + */ + @Test + public void testRemove() + { + System.out.println("remove"); + Object key = null; + PyDict instance = new PyDict(); + instance.setItem("A", 1); + assertTrue(instance.containsKey("A")); + assertEquals(instance.remove("A"), 1); + assertFalse(instance.containsKey("A")); + } + + /** + * Test of get method, of class PyDict. + */ + @Test + public void testGet() + { + System.out.println("get"); + PyDict instance = new PyDict(); + instance.setItem("A", 1); + assertEquals(instance.get("A"), 1); + assertEquals(instance.get("B"), null); + } + + /** + * Test of getItemWithError method, of class PyDict. + */ + @Test + public void testGetItemWithError() + { + System.out.println("getItemWithError"); + Object key = null; + PyDict instance = new PyDict(); + instance.setItem("A", 1); + assertEquals(instance.getItemWithError("A"), 1); + assertEquals(instance.getItemWithError("B"), null); + } + + /** + * Test of setDefault method, of class PyDict. + */ + @Test + public void testSetDefault() + { + System.out.println("setDefault"); + Object key = null; + Object defaultobj = null; + PyDict instance = new PyDict(); + Object expResult = null; + Object result = instance.setDefault(key, defaultobj); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of items method, of class PyDict. + */ + @Test + public void testItems() + { + System.out.println("items"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 1); + PyList result = instance.items(); + assertEquals(result.size(), 2); + } + + /** + * Test of entrySet method, of class PyDict. + */ + @Test + public void testEntrySet() + { + System.out.println("entrySet"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + Set result = instance.entrySet(); + Map.Entry first = result.iterator().next(); + assertEquals(first.getKey(), "A"); + assertEquals(first.getValue(), 1); + } + + /** + * Test of keys method, of class PyDict. + */ + @Test + public void testKeys() + { + System.out.println("keys"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + PyList result = instance.keys(); + assertEquals(result.size(), 2); + String[] expected = + { + "A", "B" + }; + for (int i = 0; i < 2; i++) + assertEquals(result.get(i), expected[i]); + } + + /** + * Test of keySet method, of class PyDict. + */ + @Test + public void testKeySet() + { + System.out.println("keySet"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + Set result = instance.keySet(); + String[] expected = + { + "A", "B" + }; + for (int i = 0; i < 2; i++) + assertTrue(result.contains(expected[i])); + } + + /** + * Test of values method, of class PyDict. + */ + @Test + public void testValues() + { + System.out.println("values"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + PyList result = instance.values(); + Object[] expected = + { + 1, 2 + }; + for (int i = 0; i < 2; i++) + assertEquals(result.get(i), expected[i]); + } + + /** + * Test of size method, of class PyDict. + */ + @Test + public void testSize() + { + System.out.println("size"); + PyDict instance = new PyDict(); + instance.put("A", 1); + instance.put("B", 2); + assertEquals(instance.size(), 2); + } + + /** + * Test of merge method, of class PyDict. + */ + @Test + public void testMerge() + { + System.out.println("merge"); + boolean override = false; + PyDict instance = new PyDict(); + PyDict dict = new PyDict(); + dict.put("A", 1); + dict.put("B", 2); + instance.merge(dict, override); + PyList result = instance.keys(); + String[] expected = + { + "A", "B" + }; + for (int i = 0; i < 2; i++) + assertTrue(result.contains(expected[i])); + } + + /** + * Test of putAll method, of class PyDict. + */ + @Test + public void testPutAll() + { + System.out.println("putAll"); + Map dict = new HashMap<>(); + PyDict instance = new PyDict(); + dict.put("A", 1); + dict.put("B", 2); + instance.putAll(dict); + PyList result = instance.keys(); + String[] expected = + { + "A", "B" + }; + for (int i = 0; i < 2; i++) + assertTrue(result.contains(expected[i])); + } + + /** + * Test of update method, of class PyDict. + */ + @Test + public void testUpdate() + { + System.out.println("update"); +// Object b = null; +// PyDict instance = new PyDict(); +// instance.update(b); + } + + /** + * Test of mergeFromSeq2 method, of class PyDict. + */ + @Test + public void testMergeFromSeq2() + { + System.out.println("mergeFromSeq2"); +// Object seq2 = null; +// boolean override = false; +// PyDict instance = new PyDict(); +// instance.mergeFromSeq2(seq2, override); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); + } + + /** + * Test of isEmpty method, of class PyDict. + */ + @Test + public void testIsEmpty() + { + System.out.println("isEmpty"); + PyDict instance = new PyDict(); + assertTrue(instance.isEmpty()); + instance.put("A", 1); + assertFalse(instance.isEmpty()); + } + +} diff --git a/test/testng/epypj/python/lang/PyFloatNGTest.java b/test/testng/epypj/python/lang/PyFloatNGTest.java new file mode 100644 index 000000000..a8b72be07 --- /dev/null +++ b/test/testng/epypj/python/lang/PyFloatNGTest.java @@ -0,0 +1,133 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyFloatNGTest +{ + + public PyFloatNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of of method, of class PyFloat. + */ + @Test + public void testOf() + { + System.out.println("of"); + long d = 0L; + PyFloat expResult = null; + PyFloat result = PyFloat.of(d); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of intValue method, of class PyFloat. + */ + @Test + public void testIntValue() + { + System.out.println("intValue"); + PyFloat instance = new PyFloat(); + int expResult = 0; + int result = instance.intValue(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of longValue method, of class PyFloat. + */ + @Test + public void testLongValue() + { + System.out.println("longValue"); + PyFloat instance = new PyFloat(); + long expResult = 0L; + long result = instance.longValue(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of floatValue method, of class PyFloat. + */ + @Test + public void testFloatValue() + { + System.out.println("floatValue"); + PyFloat instance = new PyFloat(); + float expResult = 0.0F; + float result = instance.floatValue(); + assertEquals(result, expResult, 0.0); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of doubleValue method, of class PyFloat. + */ + @Test + public void testDoubleValue() + { + System.out.println("doubleValue"); + PyFloat instance = new PyFloat(); + double expResult = 0.0; + double result = instance.doubleValue(); + assertEquals(result, expResult, 0.0); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + +} diff --git a/test/testng/epypj/python/lang/PyKeywordsNGTest.java b/test/testng/epypj/python/lang/PyKeywordsNGTest.java new file mode 100644 index 000000000..57880f3a4 --- /dev/null +++ b/test/testng/epypj/python/lang/PyKeywordsNGTest.java @@ -0,0 +1,120 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyKeywordsNGTest +{ + + public PyKeywordsNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of use method, of class PyKeywords. + */ + @Test + public void testUse() + { + System.out.println("use"); + PyDict dict = null; + PyKeywords expResult = null; + PyKeywords result = PyKeywords.use(dict); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of of method, of class PyKeywords. + */ + @Test + public void testOf() + { + System.out.println("of"); + Object[] contents = null; + PyKeywords expResult = null; + PyKeywords result = PyKeywords.of(contents); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of put method, of class PyKeywords. + */ + @Test + public void testPut() + { + System.out.println("put"); + CharSequence key = null; + Object value = null; + PyKeywords instance = new PyKeywords(); + PyKeywords expResult = null; + PyKeywords result = instance.put(key, value); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of toDict method, of class PyKeywords. + */ + @Test + public void testToDict() + { + System.out.println("toDict"); + PyKeywords instance = new PyKeywords(); + PyDict expResult = null; + PyDict result = instance.toDict(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + +} diff --git a/test/testng/epypj/python/lang/PyListNGTest.java b/test/testng/epypj/python/lang/PyListNGTest.java new file mode 100644 index 000000000..84b360bc6 --- /dev/null +++ b/test/testng/epypj/python/lang/PyListNGTest.java @@ -0,0 +1,189 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import org.jpype.python.Engine; +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyListNGTest +{ + + public PyListNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of get method, of class PyList. + */ + @Test + public void testGet() + { + System.out.println("get"); + PyList instance = new PyList(); + instance.add("A"); + instance.add("B"); + assertEquals(instance.get(0), "A"); + assertEquals(instance.get(1), "B"); + } + + /** + * Test of setItem method, of class PyList. + */ + @Test + public void testSetItem() + { + System.out.println("setItem"); + PyList instance = new PyList(); + instance.add("A"); + instance.add("B"); + assertEquals(instance.get(0), "A"); + assertEquals(instance.get(1), "B"); + instance.setItem(0, "C"); + assertEquals(instance.get(0), "C"); + } + + /** + * Test of insert method, of class PyList. + */ + @Test + public void testInsert() + { + System.out.println("insert"); + PyList instance = new PyList(); + instance.add("A"); + instance.add("B"); + assertEquals(instance.get(0), "A"); + assertEquals(instance.get(1), "B"); + instance.insert(0, "C"); + assertEquals(instance.get(0), "C"); + assertEquals(instance.get(1), "A"); + } + + /** + * Test of append method, of class PyList. + */ + @Test + public void testAppend() + { + System.out.println("append"); + PyList instance = new PyList(); + instance.append("A"); + instance.append("B"); + assertEquals(instance.get(0), "A"); + assertEquals(instance.get(1), "B"); + } + + /** + * Test of getSlice method, of class PyList. + */ + @Test + public void testGetSlice() + { + System.out.println("getSlice"); + PyList instance = new PyList(); + instance.append("A"); + instance.append("B"); + instance.getSlice(0, 1); + } + + /** + * Test of setSlice method, of class PyList. + */ + @Test + public void testSetSlice() + { + System.out.println("setSlice"); + int low = 0; + int high = 0; + Object item = null; + PyList instance = new PyList(); + instance.setSlice(low, high, item); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of sort method, of class PyList. + */ + @Test + public void testSort() + { + System.out.println("sort"); + PyList instance = new PyList(); + instance.sort(); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of reverse method, of class PyList. + */ + @Test + public void testReverse() + { + System.out.println("reverse"); + PyList instance = new PyList(); + instance.reverse(); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of asTuple method, of class PyList. + */ + @Test + public void testAsTuple() + { + System.out.println("asTuple"); + PyList instance = new PyList(); + PyTuple expResult = null; + PyTuple result = instance.asTuple(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + +} diff --git a/test/testng/epypj/python/lang/PyLongNGTest.java b/test/testng/epypj/python/lang/PyLongNGTest.java new file mode 100644 index 000000000..23509a6b8 --- /dev/null +++ b/test/testng/epypj/python/lang/PyLongNGTest.java @@ -0,0 +1,164 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyLongNGTest +{ + + public PyLongNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of intValue method, of class PyLong. + */ + @Test + public void testIntValue() + { + System.out.println("intValue"); + PyLong instance = null; + int expResult = 0; + int result = instance.intValue(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of longValue method, of class PyLong. + */ + @Test + public void testLongValue() + { + System.out.println("longValue"); + PyLong instance = null; + long expResult = 0L; + long result = instance.longValue(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of floatValue method, of class PyLong. + */ + @Test + public void testFloatValue() + { + System.out.println("floatValue"); + PyLong instance = null; + float expResult = 0.0F; + float result = instance.floatValue(); + assertEquals(result, expResult, 0.0); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of doubleValue method, of class PyLong. + */ + @Test + public void testDoubleValue() + { + System.out.println("doubleValue"); + PyLong instance = null; + double expResult = 0.0; + double result = instance.doubleValue(); + assertEquals(result, expResult, 0.0); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of toString method, of class PyLong. + */ + @Test + public void testToString() + { + System.out.println("toString"); + PyLong instance = null; + String expResult = ""; + String result = instance.toString(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of hashCode method, of class PyLong. + */ + @Test + public void testHashCode() + { + System.out.println("hashCode"); + PyLong instance = null; + int expResult = 0; + int result = instance.hashCode(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of equals method, of class PyLong. + */ + @Test + public void testEquals() + { + System.out.println("equals"); + Object obj = null; + PyLong instance = null; + boolean expResult = false; + boolean result = instance.equals(obj); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + +} diff --git a/test/testng/epypj/python/lang/PySetNGTest.java b/test/testng/epypj/python/lang/PySetNGTest.java new file mode 100644 index 000000000..7c1db2f29 --- /dev/null +++ b/test/testng/epypj/python/lang/PySetNGTest.java @@ -0,0 +1,293 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import java.util.ArrayList; +import java.util.Collection; +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import python.lang.protocol.PyIterator; + +/** + * + * @author nelson85 + */ +public class PySetNGTest +{ + + public PySetNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of iterator method, of class PySet. + */ + @Test + public void testIterator() + { + System.out.println("iterator"); + PySet instance = new PySet(); + PyIterator expResult = null; + PyIterator result = instance.iterator(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of size method, of class PySet. + */ + @Test + public void testSize() + { + System.out.println("size"); + PySet instance = new PySet(); + int expResult = 0; + int result = instance.size(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of isEmpty method, of class PySet. + */ + @Test + public void testIsEmpty() + { + System.out.println("isEmpty"); + PySet instance = new PySet(); + boolean expResult = false; + boolean result = instance.isEmpty(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of contains method, of class PySet. + */ + @Test + public void testContains() + { + System.out.println("contains"); + Object key = null; + PySet instance = new PySet(); + boolean expResult = false; + boolean result = instance.contains(key); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of add method, of class PySet. + */ + @Test + public void testAdd() + { + System.out.println("add"); + Object key = null; + PySet instance = new PySet(); + boolean expResult = false; + boolean result = instance.add(key); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of addItem method, of class PySet. + */ + @Test + public void testAddItem() + { + System.out.println("addItem"); + Object key = null; + PySet instance = new PySet(); + instance.addItem(key); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of remove method, of class PySet. + */ + @Test + public void testRemove() + { + System.out.println("remove"); + Object key = null; + PySet instance = new PySet(); + boolean expResult = false; + boolean result = instance.remove(key); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of pop method, of class PySet. + */ + @Test + public void testPop() + { + System.out.println("pop"); + PySet instance = new PySet(); + Object expResult = null; + Object result = instance.pop(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of clear method, of class PySet. + */ + @Test + public void testClear() + { + System.out.println("clear"); + PySet instance = new PySet(); + instance.clear(); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of toArray method, of class PySet. + */ + @Test + public void testToArray_0args() + { + System.out.println("toArray"); + PySet instance = new PySet(); + Object[] expResult = null; + Object[] result = instance.toArray(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of toArray method, of class PySet. + */ + @Test + public void testToArray_GenericType() + { + System.out.println("toArray"); + PySet instance = new PySet(); + Object[] expResult = null; + Object[] result = instance.toArray(new Object[0]); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of containsAll method, of class PySet. + */ + @Test + public void testContainsAll() + { + System.out.println("containsAll"); + Collection c = null; + PySet instance = new PySet(); + boolean expResult = false; + boolean result = instance.containsAll(c); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of addAll method, of class PySet. + */ + @Test + public void testAddAll() + { + System.out.println("addAll"); + Collection c = new ArrayList<>(); + c.add("A"); + c.add("B"); + c.add("A"); + PySet instance = new PySet(); + boolean result = instance.addAll(c); + assertEquals(result, true); + assertEquals(instance.size(), 2); + assertTrue(instance.contains("A")); + assertTrue(instance.contains("B")); + } + + /** + * Test of retainAll method, of class PySet. + */ + @Test + public void testRetainAll() + { + System.out.println("retainAll"); + Collection c = null; + PySet instance = new PySet(); + boolean expResult = false; + boolean result = instance.retainAll(c); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of removeAll method, of class PySet. + */ + @Test + public void testRemoveAll() + { + System.out.println("removeAll"); + Collection c = null; + PySet instance = new PySet(); + boolean expResult = false; + boolean result = instance.removeAll(c); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + +} diff --git a/test/testng/epypj/python/lang/PyStringNGTest.java b/test/testng/epypj/python/lang/PyStringNGTest.java new file mode 100644 index 000000000..0fb473091 --- /dev/null +++ b/test/testng/epypj/python/lang/PyStringNGTest.java @@ -0,0 +1,121 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyStringNGTest +{ + + public PyStringNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of length method, of class PyString. + */ + @Test + public void testLength() + { + System.out.println("length"); + PyString instance = null; + int expResult = 0; + int result = instance.length(); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of size method, of class PyString. + */ + @Test + public void testSize() + { + System.out.println("size"); + PyString instance = new PyString("ABC"); + assertEquals(instance.size(), 3); + } + + /** + * Test of charAt method, of class PyString. + */ + @Test + public void testCharAt() + { + System.out.println("charAt"); + PyString instance = new PyString("ABC"); + assertEquals(instance.charAt(0), "A"); + } + + /** + * Test of subSequence method, of class PyString. + */ + @Test + public void testSubSequence() + { + System.out.println("subSequence"); + int start = 0; + int end = 0; + PyString instance = new PyString("ABC"); + CharSequence result = instance.subSequence(1, 3); + assertEquals(result, "BC"); + } + + /** + * Test of toString method, of class PyString. + */ + @Test + public void testToString() + { + System.out.println("toString"); + PyString instance = new PyString("ABC"); + String result = instance.toString(); + assertEquals(result, "ABC"); + } + +} diff --git a/test/testng/epypj/python/lang/PyTupleNGTest.java b/test/testng/epypj/python/lang/PyTupleNGTest.java new file mode 100644 index 000000000..1432cbdea --- /dev/null +++ b/test/testng/epypj/python/lang/PyTupleNGTest.java @@ -0,0 +1,152 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author nelson85 + */ +public class PyTupleNGTest +{ + + public PyTupleNGTest() + { + } + + @BeforeClass + public static void setUpClass() throws Exception + { + PythonTest.getEngine(); + } + + @AfterClass + public static void tearDownClass() throws Exception + { + } + + @BeforeMethod + public void setUpMethod() throws Exception + { + } + + @AfterMethod + public void tearDownMethod() throws Exception + { + } + + /** + * Test of of method, of class PyTuple. + */ + @Test + public void testOf() + { + System.out.println("of"); + PyTuple result = PyTuple.of("A", "B", "C"); + assertEquals(result.size(), 3); + assertEquals(result.get(0), "A"); + assertEquals(result.get(1), "B"); + assertEquals(result.get(2), "C"); + } + + /** + * Test of ofRange method, of class PyTuple. + */ + @Test + public void testOfRange() + { + System.out.println("ofRange"); + PyTuple result = PyTuple.ofRange(new Object[] + { + "A", "B", "C" + }, 0, 2); + assertEquals(result.size(), 2); + assertEquals(result.get(0), "A"); + assertEquals(result.get(1), "B"); + } + + /** + * Test of size method, of class PyTuple. + */ + @Test + public void testSize() + { + System.out.println("size"); + PyTuple instance = PyTuple.of("A"); + assertEquals(instance.size(), 1); + instance = PyTuple.of("A", "B"); + assertEquals(instance.size(), 2); + instance = PyTuple.of("A", "B", "C"); + assertEquals(instance.size(), 3); + } + + /** + * Test of get method, of class PyTuple. + */ + @Test + public void testGet() + { + System.out.println("get"); + int i = 0; + PyTuple instance = PyTuple.of("A", "B", "C"); + assertEquals(instance.get(0), "A"); + assertEquals(instance.get(1), "B"); + assertEquals(instance.get(2), "C"); + + Object expResult = null; + Object result = instance.get(i); + assertEquals(result, expResult); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of getSlice method, of class PyTuple. + */ + @Test + public void testGetSlice() + { + System.out.println("getSlice"); + PyTuple instance = PyTuple.of("A", "B", "C"); + Object result = instance.getSlice(1, 3); + + // FIXME + } + + /** + * Test of setItem method, of class PyTuple. + */ + @Test + public void testSetItem() + { + System.out.println("setItem"); + PyTuple instance = PyTuple.of("A"); + try + { + instance.setItem(0, "A"); + fail("Must throw"); + } catch (UnsupportedOperationException a) + { + } + } + +} diff --git a/test/testng/epypj/python/lang/PythonTest.java b/test/testng/epypj/python/lang/PythonTest.java new file mode 100644 index 000000000..a52127a1a --- /dev/null +++ b/test/testng/epypj/python/lang/PythonTest.java @@ -0,0 +1,58 @@ +/* **************************************************************************** + 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. + + See NOTICE file for details. +**************************************************************************** */ +package python.lang; + +import java.nio.file.Paths; +import org.jpype.python.Engine; +import org.jpype.python.EngineFactory; +import org.jpype.python.Scope; + +/** + * + * @author nelson85 + */ +public class PythonTest +{ + + static Engine engine; + static Scope scope; + + static Engine getEngine() + { + if (engine != null) + return engine; + + System.out.println(Paths.get(".").toAbsolutePath()); + + EngineFactory main = EngineFactory.getInstance(); + main.setProperty("python.exec", "python3"); + //main.setProperty("jpype.lib","_jpype.cpython-36m-x86_64-linux-gnu.so"); + //main.setProperty("python.lib","/usr/lib/x86_64-linux-gnu/libpython3.6m.so"); + System.out.println("Start"); + engine = main.create(); + System.out.println("Run"); + scope = engine.newScope(); + return engine; + } + + Scope getScope() + { + if (scope == null) + getEngine(); + return scope; + } + +} diff --git a/test/testng/jpype/README b/test/testng/jpype/README new file mode 100644 index 000000000..ae0efff77 --- /dev/null +++ b/test/testng/jpype/README @@ -0,0 +1 @@ +This is the home for unit testing of the jpype internal module diff --git a/test/testng/testng.xml b/test/testng/testng.xml new file mode 100644 index 000000000..f0feb97e4 --- /dev/null +++ b/test/testng/testng.xml @@ -0,0 +1,8 @@ + + + + + + + +