diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e9304fc8d..5c69ae19c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,16 +1,16 @@ [bumpversion] -current_version = 1.5.2.dev0 +current_version = 2.0.0_dev0 commit = True tag = False -parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? -serialize = - {major}.{minor}.{patch}.{release}{build} +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\_(?P[a-z]+)(?P\d+))? +serialize = + {major}.{minor}.{patch}_{release}{build} {major}.{minor}.{patch} [bumpversion:part:release] first_value = dev optional_value = prod -values = +values = dev prod diff --git a/.gitignore b/.gitignore index 171de2920..1f37e0452 100755 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,15 @@ jacoco/ wheelhouse/ vc*.pdb *.class +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake +*.project +*.ninja* +_jpype.* +.clangd +.classpath +compile_commands.json +*.gradle +*.settings +*.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..659c5ffec --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required (VERSION 3.30) + +project("_jpype" LANGUAGES CXX) + +set (Python_FIND_ABI "ANY" "ANY" "ANY" "ON") +set(Python_Interpreter_GIL_DISABLED ON) +find_package(Python3 REQUIRED COMPONENTS Interpreter Development) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_SHARED_MODULE_SUFFIX ".cp313t-win_amd64.pyd") + +set(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_ROOT}) + +set(D_SRC_COMMON ${PROJECT_ROOT}/native/common) +set(D_SRC_PYTHON ${PROJECT_ROOT}/native/python) + +file(GLOB SrcFiles ${D_SRC_COMMON}/*.cpp ${D_SRC_PYTHON}/*.cpp) + +add_compile_options( +"$<$:-Od;>" +) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DPy_GIL_DISABLED=1 /W3 /GL") +#target_sources(${PROJECT_NAME} MODULE ${SrcFiles}) +add_library(${PROJECT_NAME} MODULE ${SrcFiles}) +target_include_directories(${PROJECT_NAME} PRIVATE "C:\\Program Files\\Python313\\include" ${PROJECT_ROOT}/native/jni_include ${D_SRC_COMMON}/include ${D_SRC_PYTHON}/include) +#target_link_libraries(${PROJECT_NAME} PRIVATE Python3::Module) +target_link_directories(${PROJECT_NAME} PRIVATE "C:\\Program Files\\Python313\\libs") diff --git a/LICENSE b/LICENSE index 261eeb9e9..20652dbd0 100644 --- a/LICENSE +++ b/LICENSE @@ -199,3 +199,35 @@ 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. + +============== +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/_jpype-stubs/__init__.pyi b/_jpype-stubs/__init__.pyi new file mode 100644 index 000000000..7baefe4b1 --- /dev/null +++ b/_jpype-stubs/__init__.pyi @@ -0,0 +1,215 @@ +import types +import typing +import jpype +from java.lang import Class # type: ignore + +__version__: str + +class _JArray(_JObject): + length: jpype.JInt + def __init__(self, *args, **kwargs) -> None: ... + def __buffer__(self, *args, **kwargs): ... + def __delitem__(self, other) -> None: ... + def __getitem__(self, index): ... + def __len__(self) -> int: ... + def __release_buffer__(self, *args, **kwargs): ... + def __setitem__(self, index, object) -> None: ... + +class _JArrayPrimitive(_JArray): + def __buffer__(self, *args, **kwargs): ... + def __release_buffer__(self, *args, **kwargs): ... + +class _JBoolean(int, _JObject): + @classmethod + def __init__(cls, *args, **kwargs) -> None: ... + def __delattr__(self, name): ... + def __eq__(self, other: object) -> bool: ... + def __float__(self) -> float: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + @classmethod + def __init_subclass__(cls, *args, internal: bool, **kwargs): ... + def __int__(self) -> int: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __setattr__(self, name, value): ... + +class _JBuffer(_JObject): + def __buffer__(self, *args, **kwargs): ... + def __release_buffer__(self, *args, **kwargs): ... + +class _JChar(str, _JObject): + @classmethod + def __init__(cls, *args, **kwargs) -> None: ... + def __abs__(self): ... + def __add__(self, other): ... + def __and__(self, other): ... + def __bool__(self) -> bool: ... + def __delattr__(self, name): ... + def __divmod__(self, other): ... + def __eq__(self, other: object) -> bool: ... + def __float__(self) -> float: ... + def __floordiv__(self, other): ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __invert__(self): ... + def __le__(self, other: object) -> bool: ... + def __len__(self) -> int: ... + def __lshift__(self, other): ... + def __lt__(self, other: object) -> bool: ... + def __mul__(self, other): ... + def __ne__(self, other: object) -> bool: ... + def __neg__(self): ... + def __or__(self, other): ... + def __pos__(self): ... + def __radd__(self, other): ... + def __rand__(self, other): ... + def __rdivmod__(self, other): ... + def __rfloordiv__(self, other): ... + def __rlshift__(self, other): ... + def __rmul__(self, other): ... + def __ror__(self, other): ... + def __rrshift__(self, other): ... + def __rshift__(self, other): ... + def __rsub__(self, other): ... + def __rxor__(self, other): ... + def __setattr__(self, name, value): ... + def __sub__(self, other): ... + def __xor__(self, other): ... + @classmethod + def __init_subclass__(cls, *args, internal: bool, **kwargs): ... + +class _JClass(type): + class_: Class + def __init__(self, *args, **kwargs) -> None: ... + def mro(self, *args, **kwargs): ... + def __del__(self, *args, **kwargs) -> None: ... + def __delattr__(self, name): ... + def __getitem__(self, index): ... + def __imatmul__(self, *args, **kwargs): ... + def __instancecheck__(self, *args, **kwargs): ... + def __matmul__(self, *args, **kwargs): ... + @classmethod + def __prepare__(cls, *args, **kwargs): ... + def __rmatmul__(self, *args, **kwargs): ... + def __setattr__(self, name, value): ... + def __subclasscheck__(self, *args, **kwargs): ... + +class _JClassHints: + def __init__(self, *args, **kwargs) -> None: ... + +class _JComparable(_JObject): + def __eq__(self, other: object) -> bool: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + +class _JException(Exception, _JObject): + def __init__(self, *args, **kwargs) -> None: ... + def __delattr__(self, name): ... + def __setattr__(self, name, value): ... + +class _JField: + def __delete__(self, *args, **kwargs): ... + def __get__(self, instance, owner): ... + def __set__(self, instance, value): ... + +class _JMethod(function): + def matchReport(self, *args, **kwargs): ... + def __call__(self, *args, **kwargs): ... + def __get__(self, instance, owner): ... + +class _JMonitor: + def __init__(self, *args, **kwargs) -> None: ... + def __enter__(self): ... + def __exit__(self, type: type[BaseException] | None, value: BaseException | None, traceback: types.TracebackType | None): ... + +class _JNumberFloat(float, _JObject): + @classmethod + def __init__(cls, *args, **kwargs) -> None: ... + def __delattr__(self, name): ... + def __eq__(self, other: object) -> bool: ... + def __float__(self) -> float: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + @classmethod + def __init_subclass__(cls, *args, internal: bool, **kwargs): ... + def __int__(self) -> int: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __setattr__(self, name, value): ... + +class _JNumberLong(int, _JObject): + @classmethod + def __init__(cls, *args, **kwargs) -> None: ... + def __delattr__(self, name): ... + def __eq__(self, other: object) -> bool: ... + def __float__(self) -> float: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + @classmethod + def __init_subclass__(cls, *args, internal: bool, **kwargs): ... + def __int__(self) -> int: ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __setattr__(self, name, value): ... + +class _JObject: + @classmethod + def __init__(cls, *args, **kwargs) -> None: ... + def __del__(self, *args, **kwargs) -> None: ... + def __delattr__(self, name): ... + def __eq__(self, other: object) -> bool: ... + def __ge__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + @classmethod + def __init_subclass__(cls, *args, **kwargs): ... + def __le__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __setattr__(self, name, value): ... + +class _JPackage(types.ModuleType): + @classmethod + def __init__(cls, *args, **kwargs) -> None: ... + def __call__(self, *args, **kwargs): ... + def __dir__(self): ... + def __imatmul__(self, *args, **kwargs): ... + def __matmul__(self, *args, **kwargs): ... + def __rmatmul__(self, *args, **kwargs): ... + +class _JProxy: + __javaclass__: jpype.JClass + __javainst__: typing.Any + @classmethod + def __init__(cls, *args, **kwargs) -> None: ... + def equals(self, *args, **kwargs): ... + def hashCode(self, *args, **kwargs): ... + def toString(self, *args, **kwargs): ... + +def arrayFromBuffer(*args, **kwargs): ... +def attachThreadAsDaemon(*args, **kwargs): ... +def attachThreadToJVM(*args, **kwargs): ... +def convertToDirectBuffer(*args, **kwargs): ... +def detachThreadFromJVM(*args, **kwargs): ... +def enableStacktraces(*args, **kwargs): ... +def examine(*args, **kwargs): ... +def gcStats(*args, **kwargs): ... +def isPackage(*args, **kwargs): ... +def isStarted(*args, **kwargs): ... +def isThreadAttachedToJVM(*args, **kwargs): ... +def shutdown(*args, **kwargs): ... +def startup(*args, **kwargs): ... +def trace(*args, **kwargs): ... diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..5b2811345 --- /dev/null +++ b/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'java-library' +apply plugin: 'eclipse' + +sourceCompatibility = 8 +targetCompatibility = 8 + +sourceSets { + main { + java { + srcDirs = ['native/java'] + } + } +} + +jar { + destinationDirectory.set(file(project.rootDir)) + archiveFileName.set('org.jpype.jar') + manifest { + attributes( + 'Premain-Class': 'org.jpype.agent.JPypeAgent' + ) + } +} diff --git a/doc/extension.rst b/doc/extension.rst new file mode 100644 index 000000000..f7085a697 --- /dev/null +++ b/doc/extension.rst @@ -0,0 +1,117 @@ +This information is a draft for the user guide. + +Java Extensions +=============== + +JPype can extend Java classes from within Python. Java Extensions are defining +using Python class definitions but they have a number of restrictions. Here is +a summary of the key differences. + +- The JVM must be running to define a Java extension type. + +- Java extensions use decorators to mark up Python elements for use by Java. + Annotations include ``@JPublic``, ``@JProtected``, ``@JPrivate``, + ``@JOverride`` and ``@JThrows``. The concepts of ``final`` and ``static`` + are not currently supported. + +- ``@JPublic``, ``@JProtected``, or ``@JPrivate`` are considered accessors + decorators. They are used without the ``@`` when declaring fields. + +- Java extensions must have all Java base types. + +- Java base types must not be final or sealed. + +- Java bases can have at most one concrete or abstract base type, but they can + have multiple interfaces. + +- If no concrete type is included then JObject will be added as the concrete + base. + +- All Java extensions are private classes without a package. Thus, they cannot + be annotated with ``@JPublic``, ``@JProtected``, or ``@JPrivate``. + +- Java extensions will have a backing Java classes and therefore all exposed Java + methods and fields must be strongly typed. + +- All methods and fields must be annotated with an accessor decorator. + +- Methods can be annotated with ``@JOverride``. Methods with ``@JOverride`` + must have a signature that matches a method in the parent. + +- Java methods must take ``self``, ``cls`` or ``this`` as the first argument. + This is a Java handle with privilaged access to private methods and fields. + The ``cls`` argument for static methods will be the class which defined the + static method. It will not work as a Python ``classmethod`` normally would. + +- Extension classes have the same access restrictions to package, protected + and private visibile methods and fields as they would have if the class was + written in Java. + +- Java methods must have a return annotation if they have a return type. + The return type can be ommitted or None if the method returns ``void``. + The return type must be a Java class with the exception of None. + +- Java methods must have all parameters specified with the exception of the + ``self``, ``cls`` or ``this``. + +- Java methods may not accept keyword arguments. + +- Java methods may not have default values. + +- The Python decorator ``@classmethod`` may be used instead of ``@JStatic``. However, + as previously mentioned, it will not behave in the same fashion as a Python classmethod. + +- The Python decorator ``@staticmethod`` are not currently supported. + +- Java methods can be overloaded so long as the signatures are not conflicting. + +- Variadic arguements are not currently supported. + +- Java classes are closed so all fields must be defined in advance before being + used. Use ``JPublic``, ``JProtected``, ``JPrivate`` and ``JStatic`` to declare + those slots in advance. + +- Java fields are specified using ``JPublic``, ``JProtected``, ``JPrivate``, ``JFinal`` + and ``JStatic`` as type annotations using ``typing.Annotated``. + +- Field names must not be Java keywords. + +- The constructor is specified with ``__init__``. Overloading of constructors + is allowed. + +- The first call to the Java class must be to the base class initializer. + Accessing any other field or method will produce a ``ValueError``. + +- The singleton pattern is not supported. Supporting this would require + instantiating the Python class before it is defined. + +- Nested classes, enums, records and throwables are not supported. + +- Extension classes may be subclassed. However, at least one Java visible constructor + must always be defined. + +- Extension classes and Java methods cannot be abstract. + +- Extension classes may have Python methods and members. A non Java visible ``__init__`` + may be defined. Note however, that this ``__init__`` will always be called **after** + the Java constructors and with no arguments. Attempting to use ``super().__init__()`` + will have no effect because the Python implementations are called from within the + Java constructor in the JVM. + + +Mechanism +--------- + +When a Java class is used as a base type, control is transfered a class builder +by the meta class JClassMeta. The meta class probes all elements in the Python +prototype for decorated elements. Those decorated elements are turned into a +class description which is passed to Java. Java ASM is then used to construct +the requested classes which is loaded dynamically. The resulting class is then +passed back to Python to finish creating a specialized Python class wrapper. + +The majority of the errors are should be caught when building the class +description. Errors detected at this stage will produce the using +``TypeError`` exceptions. However, in some cases errors in the class +description may result in errors in the class generation or loading phase. +The exceptions from these stages currently produce Java exceptions. + diff --git a/doc/userguide.rst b/doc/userguide.rst index abd9b17b8..819ba0c94 100644 --- a/doc/userguide.rst +++ b/doc/userguide.rst @@ -295,7 +295,7 @@ design goals. The goal of bridge is to open up places and not to restrict flow. - Keep the design as simple as possible. Mixing languages is already complex - enough so don't required the user to learn a huge arsenal of unique methods. + enough so don't require the user to learn a huge arsenal of unique methods. Instead keep it simple with well defined rules and reuse these concepts. For example, all array types originate from JArray, and thus using one can also use isinstance to check if a class is an array @@ -738,7 +738,7 @@ in parentheses in front of the object to be cast. Python does not directly support Java casting syntax. To request an explicit conversion an object must be "cast" using a cast operator @. Overloaded methods with an explicit argument will not be matched. After applying an explicit cast, the match -quality can improve to exact or derived depending on the cast type. +quality can improve to exact or derived depending on the cast type. Not every conversion is possible between Java types. Types that cannot be converted are considerer to be conversion type "none". @@ -751,7 +751,7 @@ Details on the standard conversions provided by JPype are given in the section Java casting ------------ -To access a casting operation we use the casting ``JObject`` wrapper. +To access a casting operation we use the casting ``JObject`` wrapper. For example, ``JObject(object, Type)`` would produce a copy with specificed type. The first argument is the object to convert and the second is the type to cast to. The second argument should always be a Java @@ -784,7 +784,7 @@ changes the resolution type for the object. This can be very useful when trying to call a specific method overload. For example, if we have a Java ``a=String("hello")`` and there were an overload of the method ``foo`` between ``String`` and ``Object`` we would need to select the overload with -``foo(java.lang.Object@a)``. +``foo(java.lang.Object@a)``. .. _JObject: @@ -1117,9 +1117,9 @@ any element cannot be converted a ``TypeError`` will be raised. As a shortcut the ``[]`` operator can be used to specify an array type or an array instance. For example, ``JInt[5]`` will allocate an array instance -of Java ints with length 5. ``JInt[:]`` will create a type instance with +of Java ints with length 5. ``JInt[:]`` will create a type instance with an unspecific length which can be used for the casting operator. To create -an array instance with multiple dimensions we would use ``JInt[5,10]`` +an array instance with multiple dimensions we would use ``JInt[5,10]`` which would create a rectangular array which was 5 by 10. To create a jagged array we would substitute ``:`` for the final dimensions. So ``JInt[5,:]`` is a length 5 array of an array of ``int[]``. Multidimensional @@ -1195,7 +1195,7 @@ additional mathematical operations at this time. Creating a Java array is also required when pass by reference syntax is required. For example, if a Java function takes an array, modifies it and we want to retrieve those values. In Java, all parameters are pass by value, but the contents -of a container like an array can be modified which gives the appearance of +of a container like an array can be modified which gives the appearance of pass by reference. For example. .. code-block:: java @@ -1518,6 +1518,54 @@ following differences: received from Java code can be used without problem. +Annotations +----------- + +Python classes which extend a Java class may apply an annotation to the class, +fields, methods and method parameters. All annotations **must** have runtime +retention ie ``RetentionPolicy.RUNTIME``. If it does not, a ``TypeError`` will +be raised. Applying an annotation is slightly different depending on what it +is being applied to. The following list shows how to apply an annotation in +each use case, followed by an example: + +- A class has annotations applied to the special ``__jannotations__`` member. + Use a ``tuple`` to apply multiple annotations. Using a decorator here would + be a much more pythonic approach. Unfortunately, when a decorator is applied + to a class, the class is evaluated before the decorator is applied. Since the + backing Java class will have already been created, it would be too late to add + an annotation. + +- A field has annotations applied by assigning the result of calling the annotation + to the field. To apply multiple annotations, use a ``tuple``. To set a default + value for the field when annotations are used, use the ``default`` keyword argument + in the first annotation. Since ``default`` is a Java keyword in the context of an + annotation, it is not possible for it to conflict with any annotation elements. + Using a decorator here would be a much more pythonic approach. Unfortunately, Python + does not allow applying a decorator to a class attribute and using ``typing.Annotated`` + would quickly become messy. + +- A method has annotations applied by using the annotation class as a decorator. + +- A method parameter has annotations applied by using the ``JParameterAnnotation`` + on the method with the parameter name as the first argument followed by the + annotations to be applied. + + +.. code-block:: python + + class MyObject(java.lang.Object): + __jannotations__ = (Annotation1(4), Annotation2) + + field1: typing.Annotated[JInt, JPublic] = (Annotation1(default=4, 2), Annotation2) + + @Annotation1(4) + @JParameterAnnotation("value", Annotation2) + @Annotation2 + @JPublic + def method1(self, value: JInt): + ... + + .. _import: Importing Java classes @@ -2622,7 +2670,7 @@ way that a thread would not be attached is if it has never called a Java method. The downside of automatic attachment is that each attachment allocates a small amount of resources in the JVM. For applications that spawn frequent dynamically allocated threads, these threads will need to be detached prior -to completing the thread with ``java.lang.Thread.detach()``. When +to completing the thread with ``java.lang.Thread.detach()``. When implementing dynamic threading, one can detach the thread whenever Java is no longer needed. The thread will automatically reattach if Java is needed again. There is a performance penalty each time a thread is @@ -2775,7 +2823,7 @@ Javadoc JPype can display javadoc in ReStructured Text as part of the Python documentation. To access the javadoc, the javadoc package must be located on -the classpath. This includes the JDK package documentation. +the classpath. This includes the JDK package documentation. For example to get the documentation for ``java.lang.Class``, we start the JVM with the JDK documentation zip file on the classpath. @@ -3187,12 +3235,13 @@ Annotations Some frameworks such as Spring use Java annotations to indicate specific actions. These may be either runtime annotations or compile time annotations. Occasionally while using JPype someone would like to add a Java annotation to a -JProxy method so that a framework like Spring can pick up that annotation. +JProxy method so that a framework like Spring can pick up that annotation. With +the exception of Java classes extended from Python, this is not possible. JPype uses the Java supplied ``Proxy`` to implement an interface. That API does not support addition of a runtime annotation to a method or class. Thus, all methods and classes when probed with reflection that are implemented in -Python will come back with no annotations. +Python will come back with no annotations. Further, the majority of annotation magic within Java is actually performed at compile time. This is accomplished using an annotation processor. When a @@ -3200,15 +3249,7 @@ class or method is annotated, the compiler checks to see if there is an annotation processor which then can produce new code or modify the class annotations. As this is a compile time process, even if annotations were added by Python to a class they would still not be active as the corresponding -compilation phase would not have been executed. - -This is a limitation of the implementation of annotations by the Java virtual -machine. It is technically possible though the use of specialized code -generation with the ASM library or other code generation to add a runtime -annotation. Or through exploits of the Java virtual machine annotation -implementation one can add annotation to existing Java classes. But these -annotations are unlikely to be useful. As such JPype will not be able to -support class or method annotations. +compilation phase would not have been executed. Restarting the JVM diff --git a/jpype/__init__.py b/jpype/__init__.py index 165c60f87..c5457fb4c 100644 --- a/jpype/__init__.py +++ b/jpype/__init__.py @@ -41,6 +41,8 @@ from . import protocol # lgtm [py/import-own-module] from . import _jthread # lgtm [py/import-own-module] +from ._jannotation import * + __all__ = ['java', 'javax'] __all__.extend(_jinit.__all__) # type: ignore[name-defined] __all__.extend(_core.__all__) @@ -51,8 +53,9 @@ __all__.extend(_jclass.__all__) # type: ignore[name-defined] __all__.extend(_jcustomizer.__all__) # type: ignore[name-defined] __all__.extend(_gui.__all__) # type: ignore[name-defined] +__all__.extend(_jannotation.__all__) -__version__ = "1.5.2.dev0" +__version__ = "2.0.0_dev0" __version_info__ = __version__.split('.') diff --git a/jpype/_core.py b/jpype/_core.py index 096c7a461..ef9aa5153 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -52,8 +52,8 @@ class JVMNotRunning(RuntimeError): # Activate jedi tab completion try: - from jedi import __version__ as _jedi_version - import jedi.access as _jedi_access + from jedi import __version__ as _jedi_version # type: ignore + import jedi.access as _jedi_access # type: ignore _jedi_access.ALLOWED_DESCRIPTOR_ACCESS += _jpype._JMethod, _jpype._JField except ModuleNotFoundError: pass diff --git a/jpype/_jannotation.py b/jpype/_jannotation.py new file mode 100644 index 000000000..1eafd3060 --- /dev/null +++ b/jpype/_jannotation.py @@ -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. +# +# ***************************************************************************** +import _jpype +from jpype import JClass, JString + + +__all__ = ['JAnnotation', 'JParameterAnnotation'] + + +def _get_retention_policy(cls: JClass): + retention = cls.class_.getAnnotation(JClass("java.lang.annotation.Retention")) + if retention: + return retention.value() + + +class _JAnnotationBase: + + __slots__ = ('_obj',) + + def __init__(self): + self._obj = None + + def __call__(self, obj): + self._obj = obj + annotations = getattr(obj, "__jannotations__", None) + if annotations is None: + annotations = [] + setattr(obj, "__jannotations__", annotations) + annotations.append(self) + return obj + + +class JAnnotation(_JAnnotationBase): + + __slots__ = ('_decl', '_default') + + def __new__(cls, jcls, *args, **kwargs): + """Object for all Java annotation instances. + + To specify annotations on a field, use a tuple. Use the "default" kwarg + on the first annotation to specify the constand field value. + In the context of a Java annotation, default is a keyword and therefore it + cannot cause a conflict with an annotation's element. + + Returns: + JAnnotation: a new wrapper for a Java annotation + + Raises: + TypeError: if the component class is not an annotation. + TypeError: if the annotation class does not have RUNTIME retention. + """ + if not jcls.class_.isAnnotation(): + raise TypeError("%s is not an annotation" % jcls) + RetentionPolicy = JClass("java.lang.annotation.RetentionPolicy") + policy = _get_retention_policy(jcls) + if policy != RetentionPolicy.RUNTIME: + # I don't think class file retention is necessary because + # something would have to read the class file data to know + # if the annotation is present. There is no reason we can't + # support it, I just don't see a reason to. + raise TypeError("%s does not have runtime retention" % jcls) + return super().__new__(JAnnotation) + + def __init__(self, cls, *args, **kwargs): + super().__init__() + AnnotationDecl = JClass("org.jpype.extension.AnnotationDecl") + self._decl = AnnotationDecl(cls) + IllegalArgumentException = JClass("java.lang.IllegalArgumentException") + UnsupportedOperationException = JClass("java.lang.UnsupportedOperationException") + self._default = kwargs.pop("default", None) + if args: + if callable(args[0]): + # marker annotation on a function + # no further checking required + self._obj = args[0] + return + elements = {JString("value") : args[0]} + else: + elements = {JString(k) : v for k, v in kwargs.items()} + try: + self._decl.addElements(elements) + except UnsupportedOperationException as e: + fmt_args = (cls.class_.getSimpleName(), e.getMessage()) + raise KeyError("%s has no element '%s'", fmt_args) + except IllegalArgumentException as e: + fmt_args = (cls.class_.getSimpleName(), e.getMessage()) + raise KeyError("%s is missing a default value for the element '%s'" % fmt_args) + + +class JParameterAnnotation(_JAnnotationBase): + """Helper for Java method parameter annotations""" + + __slots__ = ('_name', '_annotations') + + def __init__(self, name: str, *annotations): + super().__init__() + self._name = name + self._annotations = annotations + if not name: + raise ValueError("The name of the parameter to annotate must be provided") + if not annotations: + raise ValueError("No annotations provided") + + +_jpype.JAnnotation = JAnnotation diff --git a/jpype/_jclass.py b/jpype/_jclass.py index e971bb907..827e86b35 100644 --- a/jpype/_jclass.py +++ b/jpype/_jclass.py @@ -18,11 +18,156 @@ import _jpype from ._pykeywords import pysafe from . import _jcustomizer +import inspect +import enum +import sys +import weakref +import types +import typing -__all__ = ['JClass', 'JInterface', 'JOverride'] +__all__ = ['JClass', 'JFinal', 'JInterface', 'JOverride', 'JPublic', 'JProtected', + 'JPrivate', 'JStatic', 'JThrows'] -def JOverride(*args, **kwargs): +_extension_classloaders = weakref.WeakKeyDictionary() + + +def _get_annotations(target, globals, locals): + # this isn't as straightforward as you might expect since our class hasn't been created yet + if isinstance(target, _JClassTable): + ann = target.get("__annotations__", {}) + else: + ann = getattr(target, "__annotations__", {}) + if not ann: + return ann + return { + key: value if not isinstance(value, str) else eval(value, globals, locals) + for key, value in ann.items() + } + + +class _JFieldDecl(object): + + def __init__(self, cls, modifiers): + self.cls = cls + self.modifiers = modifiers + + def __repr__(self): + return "Field(%s,%s)" % (self.cls.__name__, self.name) + + +def _JMemberDecl(nonlocals, target, strict, modifiers, locals, globals, **kwargs): + """Generic annotation to pass to the code generator. + """ + if not "__jspec__" in nonlocals: + nonlocals["__jspec__"] = set() + jspec: set = nonlocals["__jspec__"] + + if isinstance(target, type): + if not isinstance(target, _jpype.JClass): + raise TypeError("Fields must be Java classes") + prim = issubclass(target, (_jpype._JBoolean, _jpype._JNumberLong, _jpype._JChar, _jpype._JNumberFloat)) + out = [] + for p, v in kwargs.items(): + if not prim and v is not None: + raise ValueError("Initial value must be None") + if prim: + v = _jpype.JObject(v, target) # box it + var = _JFieldDecl(target, p, v, modifiers) + jspec.add(var) + out.append(var) + return out + + if isinstance(target, classmethod): + target = target.__func__ + + if isinstance(target, type(_JMemberDecl)): + annotations = _get_annotations(target, globals, locals) + args = inspect.getfullargspec(target).args + + # Verify the requirements for arguments are met + # Must have a this argument first + if strict: + if len(args) < 1: + raise TypeError("Methods require this argument") + if args[0] not in ("self", "cls", "this"): + raise TypeError("Methods first argument must be this") + + # All other arguments must be annotated as JClass types + for i in range(1, len(args)): + if not args[i] in annotations: + raise TypeError("Methods types must have specifications") + if not isinstance(annotations[args[i]], _jpype.JClass): + raise TypeError("Method arguments must be Java classes") + + if target.__name__ != "__init__": + if not isinstance(annotations.get("return", None), (_jpype.JClass, type(None))): + raise TypeError("Return type must be Java type") + + # Place in the Java spec list + for p, v in kwargs.items(): + object.__setattr__(target, p, v) + if modifiers is not None: + jspec.add(target) + object.__setattr__(target, '__jmodifiers__', modifiers) + return target + + raise TypeError("Unknown Java specification '%s'" % type(target)) + + +@enum.global_enum +class _JModifier(enum.IntFlag): + + JPublic = 1 + JPrivate = 2 + JProtected = 4 + JStatic = 8 + JFinal = 16 + + def __call__(self, target, **kwargs): + modifier = int(self) + if hasattr(target, '__jmodifiers__'): + target.__jmodifiers__ |= modifier + return target + elif isinstance(target, classmethod): + target = target.__func__ + modifier |= JStatic + + nonlocals = inspect.stack()[1][0].f_locals + frame = inspect.stack()[2][0] + locals = frame.f_locals + globals = frame.f_globals + return _JMemberDecl(nonlocals, target, True, modifier, locals, globals, **kwargs) + + def __class_getitem__(cls, key): + if isinstance(key, _JFieldDecl): + key.modifiers |= cls.modifier + return key + return _JFieldDecl(key, cls.modifier) + + +JPublic = _JModifier.JPublic +JPrivate = _JModifier.JPrivate +JProtected = _JModifier.JProtected +JStatic = _JModifier.JStatic +JFinal = _JModifier.JFinal + + +def JThrows(*args): + for arg in args: + if not isinstance(arg, _jpype.JException): + raise TypeError("JThrows requires Java exception arguments") + + def deferred(target): + throws = getattr(target, '__jthrows__', tuple()) + # decorators are processed lifo + # preserve the order in which they were added + object.__setattr__(target, '__jthrows__', (*args, *throws)) + return target + return deferred + + +def JOverride(*target, sticky=False, rename=None, **kwargs): """Annotation to denote a method as overriding a Java method. This annotation applies to customizers, proxies, and extensions @@ -34,16 +179,25 @@ def JOverride(*args, **kwargs): sticky=bool: Applies a customizer method to all derived classes. """ - # Check if called bare - if len(args) == 1 and callable(args[0]): - object.__setattr__(args[0], "__joverride__", {}) - return args[0] - # Otherwise apply arguments as needed - - def modifier(method): - object.__setattr__(method, "__joverride__", kwargs) - return method - return modifier + nonlocals = inspect.stack()[1][0].f_locals + frame = inspect.stack()[2][0] + locals = frame.f_locals + globals = frame.f_globals + if len(target) == 0: + overrides = {} + if kwargs: + overrides.update(kwargs) + if sticky: + overrides["sticky"] = True + if rename is not None: + overrides["rename"] = rename + + def deferred(method): + return _JMemberDecl(nonlocals, method, False, None, locals, globals, __joverride__=overrides) + return deferred + if len(target) == 1: + return _JMemberDecl(nonlocals, *target, False, None, locals, globals, __joverride__={}) + raise TypeError("JOverride can only have one argument") class JClassMeta(type): @@ -97,7 +251,7 @@ def __new__(cls, jc, loader=None, initialize=True): # Pass to class factory to create the type return _jpype._getClass(jc) - + def __class_getitem__(cls, index): # enables JClass[1] to get a Class[] return JClass("java.lang.Class")[index] @@ -246,9 +400,158 @@ def _jclassDoc(cls): return "\n".join(out) +class _JClassTable(dict): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + frame = inspect.stack()[1][0] + self.locals = frame.f_locals + self.globals = frame.f_globals + self.field_annotations = {} + self.annotations = tuple() + + def __setitem__(self, key, value): + if key == "__jannotations__": + if isinstance(value, JClass): + self.annotations = (_jpype.JAnnotation(value),) + elif isinstance(value, _jpype.JAnnotation): + self.annotations = (value,) + else: + self.annotations = value + if not hasattr(value, "__jmodifiers__"): + if isinstance(value, tuple): + if value and isinstance(value[0], _jpype.JAnnotation): + self.field_annotations[key] = value + value = value[0]._default + elif isinstance(value, _jpype.JAnnotation): + self.field_annotations[key] = (value,) + value = value._default + elif isinstance(value, JClass) and value.class_.isAnnotation(): + self.field_annotations[key] = (_jpype.JAnnotation(value),) + value = None + dict.__setitem__(self, key, value) + + @property + def loader(self): + return self.globals['__loader__'] + + @property + def module(self): + return self['__module__'] + + +def _get_classloader(members: _JClassTable): + if members.module in sys.modules: + if members.loader is sys.modules[members.module].__loader__: + # we don't have the module object at this point + # so we check for reference equality of the loader + # any module loaded "normally" (via import) + # will use the "builtin" extension classloader + # and will live for the life of the program + # just like every other JClass. + return None + classloader = _extension_classloaders.get(members.loader) + if classloader is None: + classloader = _jpype.JClass('org.jpype.extension.Factory').getNewExtensionClassLoader() + _extension_classloaders[members.loader] = classloader + finalizer = weakref.finalize(members.loader, type(classloader).cleanup, classloader) + finalizer.atexit = False + return classloader + + +def _add_annotations(member, java_annotations): + # this is gross but I couldn't think of a better way + # importing normally would cause a circular import + from jpype import JParameterAnnotation + args = JClass("java.util.ArrayList")() + for annotation in java_annotations: + if isinstance(annotation, JParameterAnnotation): + param = member.getParameter(annotation._name) + _add_annotations(param, annotation._annotations) + elif isinstance(annotation, JClass) and annotation.class_.isAnnotation(): + args.add(annotation()._decl) + else: + args.add(annotation._decl) + member.setAnnotations(args) + + +def _prepare_methods(cls, members: _JClassTable): + jspec = members['__jspec__'] + + functions = [] + for i in jspec: + if isinstance(i, type(_JExtension)): + exceptions = getattr(i, '__jthrows__', None) + mspec = inspect.getfullargspec(i) + annotations = _get_annotations(i, members.globals, members.locals) + if i.__name__ == "__init__": + names = mspec.args[1:] + args = [annotations[j] for j in names] + fun = cls.addCtor(args, names, exceptions, i.__jmodifiers__) # type: ignore[attr-defined] + functions.append(i) + else: + names = mspec.args[1:] + args = [annotations[j] for j in names] + ret = annotations.get("return", None) + fun = cls.addMethod(i.__name__, ret, args, names, exceptions, i.__jmodifiers__) # type: ignore[attr-defined] + functions.append(i) + java_annotations = getattr(i, "__jannotations__", None) + if java_annotations: + _add_annotations(fun, java_annotations) + try: + members.pop(i.__name__) + except KeyError: + pass + else: + raise TypeError("Unknown member %s" % type(i)) + + return functions + + +def _prepare_fields(cls, members: _JClassTable): + def compute_modifiers(mods: tuple[type[_JModifier]]): + res = 0 + for mod in mods: + res |= mod + return res + + for k, v in _get_annotations(members, members.globals, members.locals).items(): + if hasattr(v, "__metadata__"): + modifiers = compute_modifiers(v.__metadata__) + field = cls.addField(v.__origin__, k, members.pop(k, None), modifiers) + java_annotations = members.field_annotations.get(k) + if java_annotations: + _add_annotations(field, java_annotations) + + +def _JExtension(_, bases, members: _JClassTable): + if "__jspec__" not in members: + raise TypeError("Java classes cannot be extended in Python") + + Factory = _jpype.JClass('org.jpype.extension.Factory') + ldr = _get_classloader(members) + cls = Factory.newClass(members["__qualname__"], bases, ldr) + + java_annotations = members.annotations + if java_annotations: + _add_annotations(cls, java_annotations) + + functions = _prepare_methods(cls, members) + _prepare_fields(cls, members) + + overrides = [] + res = Factory.loadClass(cls) + for i, method in enumerate(cls.getMethods()): + overrides.append((method.retId, method.parametersId, functions[i])) + + return (res, overrides) + + # Install module hooks _jpype.JClass = JClass _jpype.JInterface = JInterface _jpype._jclassDoc = _jclassDoc _jpype._jclassPre = _jclassPre _jpype._jclassPost = _jclassPost +_jpype._JExtension = _JExtension +_jpype._JClassTable = _JClassTable diff --git a/jpype/types.py b/jpype/types.py index bcebce92d..0d42f773f 100644 --- a/jpype/types.py +++ b/jpype/types.py @@ -56,35 +56,35 @@ ] -class JBoolean(_jpype._JBoolean, internal=True): # type: ignore[call-arg] +class JBoolean(_jpype._JBoolean, internal=True): pass -class JByte(_jpype._JNumberLong, internal=True): # type: ignore[call-arg] +class JByte(_jpype._JNumberLong, internal=True): pass -class JChar(_jpype._JChar, internal=True): # type: ignore[call-arg] +class JChar(_jpype._JChar, internal=True): pass -class JInt(_jpype._JNumberLong, internal=True): # type: ignore[call-arg] +class JInt(_jpype._JNumberLong, internal=True): pass -class JShort(_jpype._JNumberLong, internal=True): # type: ignore[call-arg] +class JShort(_jpype._JNumberLong, internal=True): pass -class JLong(_jpype._JNumberLong, internal=True): # type: ignore[call-arg] +class JLong(_jpype._JNumberLong, internal=True): pass -class JFloat(_jpype._JNumberFloat, internal=True): # type: ignore[call-arg] +class JFloat(_jpype._JNumberFloat, internal=True): pass -class JDouble(_jpype._JNumberFloat, internal=True): # type: ignore[call-arg] +class JDouble(_jpype._JNumberFloat, internal=True): pass diff --git a/native/common/include/jp_array.h b/native/common/include/jp_array.h index 2e654f7e4..6809724b9 100644 --- a/native/common/include/jp_array.h +++ b/native/common/include/jp_array.h @@ -16,9 +16,17 @@ #ifndef _JPARRAY_H_ #define _JPARRAY_H_ +#pragma once + +#include "jp_context.h" #include "jp_javaframe.h" +#include "jp_pythontypes.h" class JPArray; +class JPArrayClass; +class JPValue; + +using JPArrayRef = JPRef; class JPArrayView { diff --git a/native/common/include/jp_arrayclass.h b/native/common/include/jp_arrayclass.h index 167f582fe..379d817c2 100644 --- a/native/common/include/jp_arrayclass.h +++ b/native/common/include/jp_arrayclass.h @@ -16,6 +16,10 @@ #ifndef _JPARRAYCLASS_H_ #define _JPARRAYCLASS_H_ +#pragma once + +#include "jp_class.h" + /** * Class to wrap Java Class and provide low-level behavior */ diff --git a/native/common/include/jp_booleantype.h b/native/common/include/jp_booleantype.h index cdc4b6d6a..f12313f80 100755 --- a/native/common/include/jp_booleantype.h +++ b/native/common/include/jp_booleantype.h @@ -16,6 +16,11 @@ #ifndef _JP_BOOLEAN_TYPE_H_ #define _JP_BOOLEAN_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + + class JPBooleanType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_boxedtype.h b/native/common/include/jp_boxedtype.h index c61a79e72..31c324228 100644 --- a/native/common/include/jp_boxedtype.h +++ b/native/common/include/jp_boxedtype.h @@ -16,6 +16,10 @@ #ifndef _JPBOXEDCLASS_H_ #define _JPBOXEDCLASS_H_ +#pragma once + +#include "jp_class.h" + // Boxed types have special conversion rules so that they can convert // from python primitives. This code specializes the class wrappers // to make that happen. @@ -61,4 +65,4 @@ class JPBoxedType : public JPClass jmethodID m_CharValueID; } ; -#endif // _JPBOXEDCLASS_H_ \ No newline at end of file +#endif // _JPBOXEDCLASS_H_ diff --git a/native/common/include/jp_buffer.h b/native/common/include/jp_buffer.h index 4916d1d23..4859fd114 100644 --- a/native/common/include/jp_buffer.h +++ b/native/common/include/jp_buffer.h @@ -16,9 +16,15 @@ #ifndef _JPBUFFER_H_ #define _JPBUFFER_H_ -#include "jp_javaframe.h" +#pragma once + +#include "jni.h" +#include "jp_context.h" +#include + class JPBufferType; +class JPValue; /** * Class to wrap Java Class and provide low-level behavior @@ -54,4 +60,4 @@ class JPBuffer char m_Format[3]{}; } ; -#endif // _JPBUFFER_H_ \ No newline at end of file +#endif // _JPBUFFER_H_ diff --git a/native/common/include/jp_buffertype.h b/native/common/include/jp_buffertype.h index e93393e5f..c27635dec 100644 --- a/native/common/include/jp_buffertype.h +++ b/native/common/include/jp_buffertype.h @@ -16,6 +16,10 @@ #ifndef _JPBUFFERTYPE_H_ #define _JPBUFFERTYPE_H_ +#pragma once + +#include "jp_class.h" + /** * Class to wrap Java Class and provide low-level behavior */ @@ -42,4 +46,4 @@ class JPBufferType : public JPClass int m_Size; } ; -#endif // _JPBUFFERCLASS_H_ \ No newline at end of file +#endif // _JPBUFFERCLASS_H_ diff --git a/native/common/include/jp_bytetype.h b/native/common/include/jp_bytetype.h index aceee617b..10f15937b 100755 --- a/native/common/include/jp_bytetype.h +++ b/native/common/include/jp_bytetype.h @@ -16,6 +16,10 @@ #ifndef _JPBYTE_TYPE_H_ #define _JPBYTE_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + class JPByteType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_chartype.h b/native/common/include/jp_chartype.h index 0b7f17e8a..73999c51b 100755 --- a/native/common/include/jp_chartype.h +++ b/native/common/include/jp_chartype.h @@ -16,6 +16,10 @@ #ifndef _JP_CHAR_TYPE_H_ #define _JP_CHAR_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + class JPCharType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_class.h b/native/common/include/jp_class.h index dfeaa3000..daa2369e7 100644 --- a/native/common/include/jp_class.h +++ b/native/common/include/jp_class.h @@ -16,19 +16,45 @@ #ifndef _JP_CLASS_H_ #define _JP_CLASS_H_ +#pragma once + +#include "jp_pythontypes.h" + +#include "jni.h" +#include "jp_javaframe.h" +#include "jp_resource.h" +#include "jp_match.h" +#include "jp_context.h" #include "jp_modifier.h" +#include +#include + +using std::string; +using std::vector; + +class JPClass; +using JPClassList = vector; + +class JPField; +using JPFieldList = vector; + +class JPMethodDispatch; +using JPMethodDispatchList = vector; + +struct JPConversionInfo; + class JPClass : public JPResource { public: // Special entry point for JVM independent entities - JPClass(const string& name, jint modifiers); + JPClass(const string& name, jlong modifiers); JPClass(JPJavaFrame& context, jclass clss, const string& name, JPClass* super, const JPClassList& interfaces, - jint modifiers); + jlong modifiers); ~JPClass() override; void setHost(PyObject* host); @@ -56,7 +82,7 @@ class JPClass : public JPResource string toString() const; - string getCanonicalName() const + const std::string &getCanonicalName() const { return m_CanonicalName; } @@ -68,6 +94,11 @@ class JPClass : public JPResource return JPModifier::isAbstract(m_Modifiers); } + bool isAnonymous() const + { + return JPModifier::isAnonymous(m_Modifiers); + } + bool isFinal() const { return JPModifier::isFinal(m_Modifiers); @@ -83,6 +114,21 @@ class JPClass : public JPResource return JPModifier::isInterface(m_Modifiers); } + bool isAnnotation() const + { + return JPModifier::isAnnotation(m_Modifiers); + } + + bool isExtension() const + { + return JPModifier::isExtension(m_Modifiers); + } + + bool isExtensionBase() const + { + return JPModifier::isExtensionBase(m_Modifiers); + } + virtual bool isArray() const { return false; @@ -185,7 +231,9 @@ class JPClass : public JPResource /** * Expose IsAssignableFrom to python. */ - virtual bool isAssignableFrom(JPJavaFrame& frame, JPClass* o); + bool isAssignableFrom(JPJavaFrame& frame, const JPClass* o) const { + return frame.IsAssignableFrom(m_Class.get(), o->getJavaClass()) != 0; + } // Object properties @@ -212,9 +260,9 @@ class JPClass : public JPResource JPMethodDispatchList m_Methods; JPFieldList m_Fields; string m_CanonicalName; - jint m_Modifiers; + jlong m_Modifiers; JPPyObject m_Host; JPPyObject m_Hints; } ; -#endif // _JPPOBJECTTYPE_H_ \ No newline at end of file +#endif // _JPPOBJECTTYPE_H_ diff --git a/native/common/include/jp_classhints.h b/native/common/include/jp_classhints.h index b7599e869..e4c7abd1e 100644 --- a/native/common/include/jp_classhints.h +++ b/native/common/include/jp_classhints.h @@ -16,6 +16,10 @@ #ifndef JP_CLASSHINTS_H #define JP_CLASSHINTS_H +#pragma once + +#include "jp_class.h" + class JPConversion { public: @@ -121,4 +125,4 @@ extern JPConversion *boxDoubleConversion; extern JPConversion *unboxConversion; extern JPConversion *proxyConversion; -#endif /* JP_CLASSHINTS_H */ \ No newline at end of file +#endif /* JP_CLASSHINTS_H */ diff --git a/native/common/include/jp_classloader.h b/native/common/include/jp_classloader.h index 526879f42..4e7964497 100644 --- a/native/common/include/jp_classloader.h +++ b/native/common/include/jp_classloader.h @@ -16,6 +16,8 @@ #ifndef _JPCLASSLOADER_H_ #define _JPCLASSLOADER_H_ +#pragma once + #include "jp_class.h" /** @@ -53,4 +55,4 @@ class JPClassLoader jmethodID m_ForNameID; } ; -#endif // _JPCLASSLOADER_H_ \ No newline at end of file +#endif // _JPCLASSLOADER_H_ diff --git a/native/common/include/jp_classtype.h b/native/common/include/jp_classtype.h index 2c864766c..68f93fc47 100644 --- a/native/common/include/jp_classtype.h +++ b/native/common/include/jp_classtype.h @@ -16,6 +16,10 @@ #ifndef _JPCLASSTYPE_H_ #define _JPCLASSTYPE_H_ +#pragma once + +#include "jp_class.h" + /** * Wrapper for Class * @@ -42,4 +46,4 @@ class JPClassType : public JPClass } ; -#endif // _JPCLASSTYPE_H_ \ No newline at end of file +#endif // _JPCLASSTYPE_H_ diff --git a/native/common/include/jp_context.h b/native/common/include/jp_context.h index 0b357f786..24b3e10f3 100644 --- a/native/common/include/jp_context.h +++ b/native/common/include/jp_context.h @@ -15,8 +15,27 @@ *****************************************************************************/ #ifndef JP_CONTEXT_H #define JP_CONTEXT_H -#include + +#pragma once + #include +#include +#include + +using std::string; +using std::vector; + +#include "jp_javaframe.h" + +class JPContext; +class JPTypeManager; +class JPClassLoader; +class JPPrimitiveType; +class JPBoxedType; +class JPStringType; +class JPResource; + +using StringVector = vector; /** JPClass is a bit heavy when we just need to hold a * class reference. It causes issues during bootstrap. Thus we @@ -55,11 +74,16 @@ class JPRef m_Ref = (jref) frame.NewGlobalRef((jobject) obj); } - JPRef(const JPRef& other); + JPRef(const JPRef& rhs); + JPRef(JPRef &&rhs) noexcept : m_Context(rhs.m_Context), m_Ref(rhs.m_Ref) { + rhs.m_Context = nullptr; + rhs.m_Ref = 0; + } ~JPRef(); - JPRef& operator=(const JPRef& other); + JPRef& operator=(const JPRef& rhs); + JPRef& operator=(JPRef &&rhs) noexcept; jref get() const { @@ -270,23 +294,20 @@ class JPContext std::list m_Resources; } ; -extern void JPRef_failed(); +[[noreturn]] extern void JPRef_failed(); // GCOVR_EXCL_START // Not currently used template -JPRef::JPRef(const JPRef& other) +JPRef::JPRef(const JPRef& rhs) { - m_Context = other.m_Context; - if (m_Context != nullptr) - { - JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv()); - m_Ref = (jref) frame.NewGlobalRef((jobject) other.m_Ref); - } else - { + if (rhs.m_Context == nullptr) { JPRef_failed(); } + m_Context = rhs.m_Context; + JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv()); + m_Ref = (jref) frame.NewGlobalRef((jobject) rhs.m_Ref); } // GCOVR_EXCL_STOP @@ -300,9 +321,9 @@ JPRef::~JPRef() } template -JPRef& JPRef::operator=(const JPRef& other) +JPRef& JPRef::operator=(const JPRef& rhs) // NOLINT(bugprone-unhandled-self-assignment) it is handled { - if (other.m_Ref == m_Ref) + if (rhs.m_Ref == m_Ref) return *this; // m_Context may or may not be set up here, so we need to use a // different frame for unreferencing and referencing @@ -313,8 +334,8 @@ JPRef& JPRef::operator=(const JPRef& other) if (m_Ref != 0) frame.DeleteGlobalRef((jobject) m_Ref); } // GCOVR_EXCL_STOP - m_Context = other.m_Context; - m_Ref = other.m_Ref; + m_Context = rhs.m_Context; + m_Ref = rhs.m_Ref; if (m_Context != nullptr && m_Ref != 0) { JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv()); @@ -323,4 +344,23 @@ JPRef& JPRef::operator=(const JPRef& other) return *this; } +template +JPRef& JPRef::operator=(JPRef &&rhs) noexcept { + // m_Context may or may not be set up here, so we need to use a + // different frame for unreferencing and referencing + if (m_Context != nullptr && m_Ref != 0) + { // GCOVR_EXCL_START + // This code is not currently used. + JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv()); + if (m_Ref != 0) { + frame.DeleteGlobalRef((jobject) m_Ref); + } + } // GCOVR_EXCL_STOP + m_Context = rhs.m_Context; + m_Ref = rhs.m_Ref; + rhs.m_Context = nullptr; + rhs.m_Ref = 0; + return *this; +} + #endif /* JP_CONTEXT_H */ diff --git a/native/common/include/jp_doubletype.h b/native/common/include/jp_doubletype.h index 2024b328c..70fd36aa3 100755 --- a/native/common/include/jp_doubletype.h +++ b/native/common/include/jp_doubletype.h @@ -16,6 +16,10 @@ #ifndef _JP_DOUBLE_TYPE_H_ #define _JP_DOUBLE_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + class JPDoubleType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_encoding.h b/native/common/include/jp_encoding.h index 3cbf302e8..aed9c3e8b 100644 --- a/native/common/include/jp_encoding.h +++ b/native/common/include/jp_encoding.h @@ -16,9 +16,10 @@ #ifndef _JP_ENCODING_H_ #define _JP_ENCODING_H_ +#pragma once + #include #include -#include class JPEncoding { @@ -61,4 +62,4 @@ std::string transcribe(const char* in, size_t len, const JPEncoding& sourceEncoding, const JPEncoding& targetEncoding); -#endif // _JP_ENCODING_H_ \ No newline at end of file +#endif // _JP_ENCODING_H_ diff --git a/native/common/include/jp_exception.h b/native/common/include/jp_exception.h index f23b9dea6..eeb077802 100644 --- a/native/common/include/jp_exception.h +++ b/native/common/include/jp_exception.h @@ -16,6 +16,10 @@ #ifndef _JP_EXCEPTION_H_ #define _JP_EXCEPTION_H_ +#pragma once + +#include "jp_context.h" + /* All exception are passed as JPypeException. The type of the exception * is specified at creation. Exceptions may be of type * - _java_error - exception generated from within java. @@ -51,7 +55,7 @@ /** * This is the type of the exception to issue. */ -enum JPError +enum JPError : uint8_t { _java_error, _python_error, diff --git a/native/common/include/jp_extension.hpp b/native/common/include/jp_extension.hpp new file mode 100644 index 000000000..b47ddb405 --- /dev/null +++ b/native/common/include/jp_extension.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "jni.h" +#include "jp_class.h" +#include "jp_pythontypes.h" +#include "jp_value.h" +#include "pyjp.h" + +#include + + +struct JPMethodOverride { + JPClass *returnType; + std::vector paramTypes; + JPPyObject function; +}; + +using JPMethodOverrideList = std::vector; + + +class JPExtensionType final : public JPClass { + +public: + using JPClass::JPClass; + virtual ~JPExtensionType() = default; + + JPPyObject convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override; + const JPMethodOverrideList &getOverrides() const { + return m_Overrides; + } + void setOverrides(JPJavaFrame& frame, PyObject *args); + bool operator==(jobject obj) const { + return m_Context->getEnv()->IsSameObject(obj, getJavaClass()); + } + JPValue newInstance(JPJavaFrame& frame, JPPyObjectVector& args) override; + PyObject *getPythonObject(JPJavaFrame& frame, JPValue &jv) const { + return (PyObject *)frame.GetLongField(jv.getValue().l, m_Instance); + } + void reset(JPJavaFrame& frame); + void clearHost() { + PyJPClass_clearJPClass(m_Host.get()); + m_Host = {}; + } + bool wasReloaded() const { + // to be checked before setting overrides + // if this is true, then we are done in PyJPClass_init + return m_Instance != nullptr; + } +private: + JPMethodOverrideList m_Overrides{}; + jfieldID m_Instance{}; +}; diff --git a/native/common/include/jp_field.h b/native/common/include/jp_field.h index 11fa0a21c..98f5bfec6 100644 --- a/native/common/include/jp_field.h +++ b/native/common/include/jp_field.h @@ -16,6 +16,18 @@ #ifndef _JPFIELD_H_ #define _JPFIELD_H_ +#pragma once + +#include "jni.h" + +#include +using std::vector; + +class JPField; +using JPFieldList = vector; + +#include "jp_class.h" + /** * Field object */ @@ -52,7 +64,7 @@ class JPField return m_Class->getContext(); } - const string& getName() const + const std::string &getName() const { return m_Name; } @@ -73,6 +85,18 @@ class JPField return JPModifier::isStatic(m_Modifiers); } + bool isPublic() const { + return JPModifier::isPublic(m_Modifiers); + } + + bool isProtected() const { + return JPModifier::isProtected(m_Modifiers); + } + + bool isPrivate() const { + return JPModifier::isPrivate(m_Modifiers); + } + JPClass *getClass() const { return m_Class; @@ -87,4 +111,4 @@ class JPField jint m_Modifiers; } ; -#endif // _JPFIELD_H_ \ No newline at end of file +#endif // _JPFIELD_H_ diff --git a/native/common/include/jp_floattype.h b/native/common/include/jp_floattype.h index ea29530cd..ab8d9b92a 100755 --- a/native/common/include/jp_floattype.h +++ b/native/common/include/jp_floattype.h @@ -16,6 +16,10 @@ #ifndef _JP_FLOAT_TYPE_H_ #define _JP_FLOAT_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + class JPFloatType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_functional.h b/native/common/include/jp_functional.h index 4fbc9fb0d..eaeaee73f 100644 --- a/native/common/include/jp_functional.h +++ b/native/common/include/jp_functional.h @@ -16,6 +16,10 @@ #ifndef JP_FUNCTIONAL_H #define JP_FUNCTIONAL_H +#pragma once + +#include "jp_class.h" + class JPFunctional : public JPClass { public: @@ -38,4 +42,4 @@ class JPFunctional : public JPClass string m_Method; } ; -#endif /* JP_FUNCTIONAL_H */ \ No newline at end of file +#endif /* JP_FUNCTIONAL_H */ diff --git a/native/common/include/jp_gc.h b/native/common/include/jp_gc.h index cad182122..6ce579ca8 100644 --- a/native/common/include/jp_gc.h +++ b/native/common/include/jp_gc.h @@ -16,6 +16,15 @@ #ifndef JP_GC_H #define JP_GC_H +#pragma once + +#include "jni.h" +#include + + +class JPContext; +class JPJavaFrame; + struct JPGCStats { long long python_rss; diff --git a/native/common/include/jp_inttype.h b/native/common/include/jp_inttype.h index 7716a5279..3fa2d7404 100755 --- a/native/common/include/jp_inttype.h +++ b/native/common/include/jp_inttype.h @@ -16,6 +16,11 @@ #ifndef _JP_INT_TYPE_H_ #define _JP_INT_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" +#include "jpype.h" + class JPIntType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_javaframe.h b/native/common/include/jp_javaframe.h index a2ebbe25f..66594f0b4 100644 --- a/native/common/include/jp_javaframe.h +++ b/native/common/include/jp_javaframe.h @@ -16,6 +16,18 @@ #ifndef _JP_JAVA_FRAME_H_ #define _JP_JAVA_FRAME_H_ +#pragma once + +#include "jni.h" + +#include +#include + +using std::string; + + +extern "C" using JCleanupHook = void (*)(void *) ; + /** A Java Frame represents a memory managed scope in which * java objects can be manipulated. * @@ -44,6 +56,7 @@ static const int LOCAL_FRAME_DEFAULT = 8; class JPContext; +class JPClass; class JPJavaFrame { @@ -185,15 +198,18 @@ class JPJavaFrame * @param str * @return */ - jstring fromStringUTF8(const string& str); + jstring fromStringUTF8(const std::string_view& str); jobject callMethod(jobject method, jobject obj, jobject args); jobject toCharArray(jstring jstr); string getFunctional(jclass c); JPClass *findClass(jclass obj); - JPClass *findClassByName(const string& name); + JPClass *findClassByName(const std::string_view& name); JPClass *findClassForObject(jobject obj); + // gets and initializes the class if not already + JPClass *getClassByName(const std::string_view& name); + // not implemented JPJavaFrame& operator= (const JPJavaFrame& frame) = delete; @@ -394,6 +410,10 @@ class JPJavaFrame void clearInterrupt(bool throws); + bool isSameObject(jobject ref1, jobject ref2) const { + return m_Env->IsSameObject(ref1, ref2); + } + } ; #endif // _JP_JAVA_FRAME_H_ diff --git a/native/common/include/jp_longtype.h b/native/common/include/jp_longtype.h index baedcb743..c66781830 100755 --- a/native/common/include/jp_longtype.h +++ b/native/common/include/jp_longtype.h @@ -16,6 +16,10 @@ #ifndef _JP_LONG_TYPE_H_ #define _JP_LONG_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + class JPLongType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_match.h b/native/common/include/jp_match.h index a436201ae..d742e1d0e 100644 --- a/native/common/include/jp_match.h +++ b/native/common/include/jp_match.h @@ -16,7 +16,16 @@ #ifndef JP_MATCH_H #define JP_MATCH_H +#pragma once + +#include + class JPConversion; +class JPMethod; +class JPPyObjectVector; +class JPValue; + +#include "jp_javaframe.h" class JPMatch { diff --git a/native/common/include/jp_method.h b/native/common/include/jp_method.h index ab6789ee0..73d7b915b 100644 --- a/native/common/include/jp_method.h +++ b/native/common/include/jp_method.h @@ -15,8 +15,22 @@ *****************************************************************************/ #ifndef _JPMETHOD_H_ #define _JPMETHOD_H_ + +#pragma once + #include "jp_modifier.h" +#include "jp_resource.h" +#include "jp_javaframe.h" + +#include + +using std::vector; + class JPMethod; +using JPMethodList = vector; + +#include "jp_class.h" + class JPMethod : public JPResource { @@ -27,6 +41,7 @@ class JPMethod : public JPResource JPClass* claz, const string& name, jobject mth, + jclass declaringClass, jmethodID mid, JPMethodList& moreSpecific, jint modifiers); @@ -87,6 +102,22 @@ class JPMethod : public JPResource return JPModifier::isCallerSensitive(m_Modifiers); } + bool isPublic() const { + return JPModifier::isPublic(m_Modifiers); + } + + bool isProtected() const { + return JPModifier::isProtected(m_Modifiers); + } + + bool isPrivate() const { + return JPModifier::isPrivate(m_Modifiers); + } + + JPClass *getClass() const { + return m_Class; + } + string toString() const; string matchReport(JPPyObjectVector& args); @@ -109,6 +140,7 @@ class JPMethod : public JPResource JPClass* m_Class{}; string m_Name; JPObjectRef m_Method; + JPClassRef m_DeclaringClass; jmethodID m_MethodID{}; JPClass* m_ReturnType{}; JPClassList m_ParameterTypes; @@ -116,4 +148,4 @@ class JPMethod : public JPResource jint m_Modifiers{}; } ; -#endif // _JPMETHOD_H_ \ No newline at end of file +#endif // _JPMETHOD_H_ diff --git a/native/common/include/jp_methoddispatch.h b/native/common/include/jp_methoddispatch.h index db524f62b..93ea747e7 100644 --- a/native/common/include/jp_methoddispatch.h +++ b/native/common/include/jp_methoddispatch.h @@ -16,6 +16,19 @@ #ifndef _JPMETHODDISPATCH_H_ #define _JPMETHODDISPATCH_H_ +#pragma once + +#include + +using std::vector; + +class JPMethodDispatch; +using JPMethodDispatchList = vector; + +class JPMethod; +using JPMethodList = vector; + +#include "jp_match.h" #include "jp_class.h" class JPMethodDispatch : public JPResource @@ -30,7 +43,7 @@ class JPMethodDispatch : public JPResource JPMethodList& overloads, jint modifiers); - ~JPMethodDispatch() override; + ~JPMethodDispatch() override = default; JPMethodDispatch(const JPMethodDispatch& method) = delete; JPMethodDispatch& operator=(const JPMethodDispatch& method) = delete; @@ -46,7 +59,10 @@ class JPMethodDispatch : public JPResource return m_Class->getContext(); } - const string& getName() const; + const std::string &getName() const + { + return m_Name; + } bool hasStatic() const { @@ -89,4 +105,4 @@ class JPMethodDispatch : public JPResource JPMethodCache m_LastCache{}; } ; -#endif // _JPMETHODDISPATCH_H_ \ No newline at end of file +#endif // _JPMETHODDISPATCH_H_ diff --git a/native/common/include/jp_modifier.h b/native/common/include/jp_modifier.h index e715e8093..f48baf4fb 100644 --- a/native/common/include/jp_modifier.h +++ b/native/common/include/jp_modifier.h @@ -16,10 +16,7 @@ #ifndef JP_MODIFIER_H #define JP_MODIFIER_H -#ifdef __cplusplus -extern "C" -{ -#endif +#include "jni.h" namespace JPModifier { @@ -180,9 +177,15 @@ inline bool isBeanMutator(jlong modifier) { return (modifier & 0x40000000) == 0x40000000; } + +inline bool isExtension(jlong modifier) +{ + return (modifier & 0x80000000) == 0x80000000; } -#ifdef __cplusplus +inline bool isExtensionBase(jlong modifier) +{ + return (modifier & 0x100000000) == 0x100000000; +} } -#endif -#endif /* JP_MODIFIER_H */ \ No newline at end of file +#endif /* JP_MODIFIER_H */ diff --git a/native/common/include/jp_monitor.h b/native/common/include/jp_monitor.h index 233aa94a5..367c25188 100644 --- a/native/common/include/jp_monitor.h +++ b/native/common/include/jp_monitor.h @@ -16,6 +16,10 @@ #ifndef _JPMONITOR_H_ #define _JPMONITOR_H_ +#pragma once + +#include "jp_context.h" + class JPMonitor { public: @@ -35,4 +39,4 @@ class JPMonitor JPObjectRef m_Value; } ; -#endif // _JPMONITOR_H_ \ No newline at end of file +#endif // _JPMONITOR_H_ diff --git a/native/common/include/jp_numbertype.h b/native/common/include/jp_numbertype.h index e0ab0785c..19773b709 100644 --- a/native/common/include/jp_numbertype.h +++ b/native/common/include/jp_numbertype.h @@ -16,6 +16,10 @@ #ifndef _JPNUMBERTYPE_H_ #define _JPNUMBERTYPE_H_ +#pragma once + +#include "jp_class.h" + /** * Wrapper for Class */ @@ -34,4 +38,4 @@ class JPNumberType : public JPClass void getConversionInfo(JPConversionInfo &info) override; } ; -#endif // _JPNUMBERTYPE_H_ \ No newline at end of file +#endif // _JPNUMBERTYPE_H_ diff --git a/native/common/include/jp_objecttype.h b/native/common/include/jp_objecttype.h index 83d81a50d..bb7c9be53 100644 --- a/native/common/include/jp_objecttype.h +++ b/native/common/include/jp_objecttype.h @@ -16,6 +16,10 @@ #ifndef _JPOBJECTTYPE_H_ #define _JPOBJECTTYPE_H_ +#pragma once + +#include "jp_class.h" + /** * Wrapper for Class * @@ -40,4 +44,4 @@ class JPObjectType : public JPClass void getConversionInfo(JPConversionInfo &info) override; } ; -#endif // _JPOBJECTTYPE_H_ \ No newline at end of file +#endif // _JPOBJECTTYPE_H_ diff --git a/native/common/include/jp_platform.h b/native/common/include/jp_platform.h index ec3f911cf..b69955e49 100644 --- a/native/common/include/jp_platform.h +++ b/native/common/include/jp_platform.h @@ -16,6 +16,8 @@ #ifndef _JPENV_H_ #define _JPENV_H_ +#pragma once + /** * the platform adapter's implementation is chosen by the JPYPE_??? macros */ @@ -30,4 +32,4 @@ class JPPlatformAdapter static JPPlatformAdapter* getAdapter(); } ; -#endif // _JPENV_H_ \ No newline at end of file +#endif // _JPENV_H_ diff --git a/native/common/include/jp_primitive_accessor.h b/native/common/include/jp_primitive_accessor.h index 130179b7b..a6fa07364 100644 --- a/native/common/include/jp_primitive_accessor.h +++ b/native/common/include/jp_primitive_accessor.h @@ -15,10 +15,12 @@ *****************************************************************************/ #ifndef JP_PRIMITIVE_ACCESSOR_H #define JP_PRIMITIVE_ACCESSOR_H -#include -#include "jp_exception.h" -#include "jp_javaframe.h" -#include "jp_match.h" + +#pragma once + +#include "jpype.h" +#include "pyjp.h" + template class JPPrimitiveArrayAccessor diff --git a/native/common/include/jp_primitivetype.h b/native/common/include/jp_primitivetype.h index 35d3e5e87..7a6bce5f4 100644 --- a/native/common/include/jp_primitivetype.h +++ b/native/common/include/jp_primitivetype.h @@ -15,7 +15,13 @@ *****************************************************************************/ #ifndef _JPPRIMITIVETYPE_H_ #define _JPPRIMITIVETYPE_H_ -#include "jp_boxedtype.h" + +#pragma once + +#include "jp_class.h" +#include "jp_boxedtype.h" // IWYU pragma: export + +class JPArrayView; class JPPrimitiveType : public JPClass { diff --git a/native/common/include/jp_proxy.h b/native/common/include/jp_proxy.h index 6739be12f..2b9550f94 100644 --- a/native/common/include/jp_proxy.h +++ b/native/common/include/jp_proxy.h @@ -16,6 +16,10 @@ #ifndef _JPPROXY_H_ #define _JPPROXY_H_ +#pragma once + +#include "jp_class.h" + struct PyJPProxy; class JPProxy; class JPFunctional; @@ -98,4 +102,4 @@ class JPProxyType : public JPClass jfieldID m_InstanceID; } ; -#endif // JPPROXY_H \ No newline at end of file +#endif // JPPROXY_H diff --git a/native/common/include/jp_reference_queue.h b/native/common/include/jp_reference_queue.h index df1e10ded..98322a697 100644 --- a/native/common/include/jp_reference_queue.h +++ b/native/common/include/jp_reference_queue.h @@ -15,6 +15,9 @@ *****************************************************************************/ #ifndef JP_REFERENCE_QUEUE_H__ #define JP_REFERENCE_QUEUE_H__ + +#pragma once + #include namespace JPReferenceQueue @@ -23,4 +26,4 @@ void registerRef(JPJavaFrame &frame, jobject obj, PyObject* targetRef); void registerRef(JPJavaFrame &frame, jobject obj, void* host, JCleanupHook func); } ; // end of namespace JPReferenceQueue -#endif \ No newline at end of file +#endif diff --git a/native/common/include/jp_resource.h b/native/common/include/jp_resource.h new file mode 100644 index 000000000..875eefa00 --- /dev/null +++ b/native/common/include/jp_resource.h @@ -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. + *****************************************************************************/ +#ifndef _JP_RESOURCE_H_ +#define _JP_RESOURCE_H_ + +#pragma once + +class JPResource +{ +public: + virtual ~JPResource() = 0; +} ; + +#endif diff --git a/native/common/include/jp_shorttype.h b/native/common/include/jp_shorttype.h index f60c8d29c..ce048e389 100755 --- a/native/common/include/jp_shorttype.h +++ b/native/common/include/jp_shorttype.h @@ -16,6 +16,10 @@ #ifndef _JP_SHORT_TYPE_H_ #define _JP_SHORT_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + class JPShortType : public JPPrimitiveType { public: diff --git a/native/common/include/jp_stringtype.h b/native/common/include/jp_stringtype.h index ce634d17e..e0ea146be 100644 --- a/native/common/include/jp_stringtype.h +++ b/native/common/include/jp_stringtype.h @@ -16,6 +16,10 @@ #ifndef JP_STRINGCLASS_H #define JP_STRINGCLASS_H +#pragma once + +#include "jp_class.h" + class JPStringType : public JPClass { public: @@ -35,4 +39,4 @@ class JPStringType : public JPClass JPValue newInstance(JPJavaFrame& frame, JPPyObjectVector& args) override; } ; -#endif /* JP_STRINGTYPE_H */ \ No newline at end of file +#endif /* JP_STRINGTYPE_H */ diff --git a/native/common/include/jp_tracer.h b/native/common/include/jp_tracer.h index 735e905bd..1e1632c79 100644 --- a/native/common/include/jp_tracer.h +++ b/native/common/include/jp_tracer.h @@ -15,9 +15,16 @@ *****************************************************************************/ #ifndef _JP_TRACER_H__ #define _JP_TRACER_H__ + +#pragma once + #include #include +using std::string; + +#include "jp_exception.h" + // GCOVR_EXCL_START #ifdef JP_TRACING_ENABLE @@ -49,6 +56,8 @@ // Enable this option to get all the py referencing information #define JP_ENABLE_TRACE_PY +class JPStackInfo; + class JPypeTracer { private: @@ -78,7 +87,7 @@ class JPypeTracer static void tracePythonObject(const char *msg, PyObject *ref); static void traceLocks(const string& msg, void *ref); - static void trace1(const char *src, const char *msg); + static void trace1(const char *source, const char *msg); static void trace2(const char *msg1, const char *msg2); private: static void traceIn(const char *msg, void *ref); @@ -156,4 +165,4 @@ inline void trace(const T1& msg1, const T2& msg2, const T3& msg3, const T4& msg4 // GCOVR_EXCL_STOP -#endif // _JP_TRACER_H__ \ No newline at end of file +#endif // _JP_TRACER_H__ diff --git a/native/common/include/jp_typemanager.h b/native/common/include/jp_typemanager.h index abb34e2e6..e8f37a1a2 100644 --- a/native/common/include/jp_typemanager.h +++ b/native/common/include/jp_typemanager.h @@ -15,6 +15,19 @@ *****************************************************************************/ #ifndef _JPTYPE_MANAGER_H_ #define _JPTYPE_MANAGER_H_ +#pragma once + +#include "jni.h" +#include "jp_context.h" + +#include + +using std::string; + +class JPJavaFrame; +class JPClass; + +using JPObjectRef = JPRef; /** * These functions will manage the cache of found type, be it primitive types, class types or the "magic" types. @@ -37,7 +50,8 @@ class JPTypeManager * The pointer returned is NOT owned by the caller */ JPClass* findClass(jclass cls); - JPClass* findClassByName(const string& str); + JPClass* findExtensionBaseClass(jclass cls); + JPClass* findClassByName(const std::string_view& str); JPClass* findClassForObject(jobject obj); void populateMethod(void* method, jobject obj); void populateMembers(JPClass* cls); @@ -47,6 +61,7 @@ class JPTypeManager JPContext* m_Context; JPObjectRef m_JavaTypeManager; jmethodID m_FindClass; + jmethodID m_FindExtensionBaseClass; jmethodID m_FindClassByName; jmethodID m_FindClassForObject; jmethodID m_PopulateMethod; @@ -54,4 +69,4 @@ class JPTypeManager jmethodID m_InterfaceParameterCount; } ; -#endif // _JPCLASS_H_ \ No newline at end of file +#endif // _JPCLASS_H_ diff --git a/native/common/include/jp_value.h b/native/common/include/jp_value.h index 4a3b994ee..e75759202 100644 --- a/native/common/include/jp_value.h +++ b/native/common/include/jp_value.h @@ -16,7 +16,9 @@ #ifndef _JPVALUE_H_ #define _JPVALUE_H_ -#include "jp_class.h" +#pragma once + +#include "jni.h" #include "jp_javaframe.h" /** Lightweight representative of a jvalue with its corresponding class. diff --git a/native/common/include/jp_voidtype.h b/native/common/include/jp_voidtype.h index f22382652..b49ab3d61 100755 --- a/native/common/include/jp_voidtype.h +++ b/native/common/include/jp_voidtype.h @@ -16,6 +16,10 @@ #ifndef _JP_VOID_TYPE_H_ #define _JP_VOID_TYPE_H_ +#pragma once + +#include "jp_primitivetype.h" + class JPVoidType : public JPPrimitiveType { public: diff --git a/native/common/include/jpype.h b/native/common/include/jpype.h index c88e57ad3..dab8c1d05 100644 --- a/native/common/include/jpype.h +++ b/native/common/include/jpype.h @@ -16,6 +16,8 @@ #ifndef _JPYPE_H_ #define _JPYPE_H_ +#pragma once + #ifdef __GNUC__ // Python requires char* but C++ string constants are const char* #pragma GCC diagnostic ignored "-Wwrite-strings" @@ -150,16 +152,8 @@ class JPPyObject; extern "C" using JCleanupHook = void (*)(void *) ; extern "C" struct JPConversionInfo; -using JPClassList = vector; -using JPFieldList = vector; -using JPMethodDispatchList = vector; -using JPMethodList = vector; - -class JPResource -{ -public: - virtual ~JPResource() = 0; -} ; +// IWYU pragma: begin_exports +#include "jp_resource.h" // Macros for raising an exception with jpype // These must be macros so that we can update the pattern and @@ -201,4 +195,6 @@ static inline JPPyObject JPPyTuple_Pack(T... args) { // Primitives classes #include "jp_primitivetype.h" +// IWYU pragma: end_exports + #endif // _JPYPE_H_ diff --git a/native/common/jp_booleantype.cpp b/native/common/jp_booleantype.cpp index 703317bab..be47cc123 100644 --- a/native/common/jp_booleantype.cpp +++ b/native/common/jp_booleantype.cpp @@ -309,7 +309,7 @@ void JPBooleanType::getView(JPArrayView& view) JPJavaFrame frame = JPJavaFrame::outer(view.getContext()); view.m_Memory = (void*) frame.GetBooleanArrayElements( (jbooleanArray) view.m_Array->getJava(), &view.m_IsCopy); - view.m_Buffer.format = "?"; + view.m_Buffer.format = const_cast("?"); view.m_Buffer.itemsize = sizeof (jboolean); } diff --git a/native/common/jp_buffer.cpp b/native/common/jp_buffer.cpp index 597469926..d0ea76dbd 100644 --- a/native/common/jp_buffer.cpp +++ b/native/common/jp_buffer.cpp @@ -14,9 +14,7 @@ See NOTICE file for details. *****************************************************************************/ #include "jpype.h" -#include "pyjp.h" #include "jp_buffer.h" -#include "jp_primitive_accessor.h" #include "jp_buffertype.h" JPBuffer::JPBuffer(const JPValue &value) diff --git a/native/common/jp_chartype.cpp b/native/common/jp_chartype.cpp index f079cb9c7..5575fa29a 100644 --- a/native/common/jp_chartype.cpp +++ b/native/common/jp_chartype.cpp @@ -37,7 +37,7 @@ JPValue JPCharType::newInstance(JPJavaFrame& frame, JPPyObjectVector& args) // This is a cast so we must not fail int overflow; - jv.c = PyLong_AsLongAndOverflow(args[0], &overflow); + jv.c = (jchar) PyLong_AsLongAndOverflow(args[0], &overflow); return JPValue(this, jv); } @@ -250,7 +250,7 @@ void JPCharType::getView(JPArrayView& view) JPJavaFrame frame = JPJavaFrame::outer(view.getContext()); view.m_Memory = (void*) frame.GetCharArrayElements( (jcharArray) view.m_Array->getJava(), &view.m_IsCopy); - view.m_Buffer.format = "H"; + view.m_Buffer.format = const_cast("H"); view.m_Buffer.itemsize = sizeof (jchar); } diff --git a/native/common/jp_class.cpp b/native/common/jp_class.cpp index fb8ab324e..debc11e53 100644 --- a/native/common/jp_class.cpp +++ b/native/common/jp_class.cpp @@ -17,11 +17,10 @@ #include "pyjp.h" #include "jp_field.h" #include "jp_methoddispatch.h" -#include "jp_method.h" JPClass::JPClass( const string& name, - jint modifiers) + jlong modifiers) { m_Context = nullptr; m_CanonicalName = name; @@ -35,7 +34,7 @@ JPClass::JPClass(JPJavaFrame& frame, const string& name, JPClass* super, const JPClassList& interfaces, - jint modifiers) + jlong modifiers) : m_Class(frame, clss) { m_Context = frame.getContext(); @@ -61,8 +60,13 @@ jclass JPClass::getJavaClass() const { jclass cls = m_Class.get(); // This sanity check should not be possible to exercise - if (cls == nullptr) + if (cls == nullptr) { + if (isExtension()) { + PyErr_Format(PyExc_TypeError, "%s has been queued for deletion", m_CanonicalName.c_str()); + JP_RAISE_PYTHON(); + } JP_RAISE(PyExc_RuntimeError, "Class is null"); // GCOVR_EXCL_LINE + } return cls; } @@ -327,7 +331,7 @@ JPValue JPClass::getValueFromObject(const JPValue& obj) JP_TRACE_OUT; } -JPPyObject JPClass::convertToPythonObject(JPJavaFrame& frame, jvalue value, bool cast) +JPPyObject JPClass::convertToPythonObject(JPJavaFrame& frame, jvalue value, bool cast) // NOLINT(misc-no-recursion) { JP_TRACE_IN("JPClass::convertToPythonObject"); JPClass *cls = this; @@ -422,11 +426,3 @@ void JPClass::getConversionInfo(JPConversionInfo &info) } // -// - -bool JPClass::isAssignableFrom(JPJavaFrame& frame, JPClass* o) -{ - return frame.IsAssignableFrom(m_Class.get(), o->getJavaClass()) != 0; -} - -// diff --git a/native/common/jp_classhints.cpp b/native/common/jp_classhints.cpp index 7435f8b76..ac996ac36 100644 --- a/native/common/jp_classhints.cpp +++ b/native/common/jp_classhints.cpp @@ -20,7 +20,7 @@ #include "jp_arrayclass.h" #include "jp_classhints.h" #include "jp_proxy.h" -#include "jp_stringtype.h" +#include "jp_stringtype.h" // IWYU pragma: keep #include "pyjp.h" @@ -80,7 +80,7 @@ JPMethodMatch::JPMethodMatch(JPJavaFrame &frame, JPPyObjectVector& args, bool ca // is only a speed cost if there is a collision, so we don't need to // prove this is a perfect hash function. m_Hash *= 0x10523C01; - m_Hash += (long) (Py_TYPE(arg)); + m_Hash += (long) ((intptr_t)Py_TYPE(arg)); } } diff --git a/native/common/jp_context.cpp b/native/common/jp_context.cpp index d6e25cb2f..ce00438c8 100644 --- a/native/common/jp_context.cpp +++ b/native/common/jp_context.cpp @@ -16,9 +16,7 @@ #include "jpype.h" #include "pyjp.h" #include "jp_typemanager.h" -#include "jp_stringtype.h" #include "jp_classloader.h" -#include "jp_proxy.h" #include "jp_platform.h" #include "jp_gc.h" @@ -27,7 +25,7 @@ JPResource::~JPResource() = default; #define USE_JNI_VERSION JNI_VERSION_1_4 -void JPRef_failed() +[[noreturn]] void JPRef_failed() { JP_RAISE(PyExc_SystemError, "NULL context in JPRef()"); } @@ -290,7 +288,7 @@ void JPContext::initializeResources(JNIEnv* env, bool interrupt) m_GC->init(frame); - _java_nio_ByteBuffer = this->getTypeManager()->findClassByName("java.nio.ByteBuffer"); + _java_nio_ByteBuffer = this->getTypeManager()->findClassByName("java.nio.ByteBuffer"sv); // Testing code to make sure C++ exceptions are handled. // FIXME find a way to call this from instrumentation. @@ -411,26 +409,28 @@ JNIEnv* JPContext::getEnv() extern "C" JNIEXPORT void JNICALL Java_org_jpype_JPypeContext_onShutdown (JNIEnv *env, jobject obj, jlong contextPtr) { + (void) env; + (void) obj; ((JPContext*) contextPtr)->onShutdown(); } /********************************************************************** - * Interrupts are complex. Both Java and Python want to handle the - * interrupt, but only one can be in control. Java starts later and + * Interrupts are complex. Both Java and Python want to handle the + * interrupt, but only one can be in control. Java starts later and * installs its handler over Python as a chain. If Java handles it then * the JVM will terminate which leaves Python with a bunch of bad * references which tends to lead to segfaults. So we need to disable - * the Java one by routing it back to Python. But if we do so then + * the Java one by routing it back to Python. But if we do so then * Java wont respect Ctrl+C. So we need to handle the interrupt, convert - * it to a wait interrupt so that Java can break at the next I/O and + * it to a wait interrupt so that Java can break at the next I/O and * then trip Python signal handler so the Python gets the interrupt. * * But this leads to a few race conditions. * - * If the control is in Java then it will get the interrupt next time + * If the control is in Java then it will get the interrupt next time * it hits Python code when the returned object is checked resulting * InterruptedException. Now we have two exceptions on the stack, - * the one from Java and the one from Python. We check to see if + * the one from Java and the one from Python. We check to see if * Python has a pending interrupt and eat the Java one. * * If the control is in Java and it hits an I/O call. This generates @@ -448,6 +448,8 @@ static int interruptState = 0; extern "C" JNIEXPORT void JNICALL Java_org_jpype_JPypeSignal_interruptPy (JNIEnv *env, jclass cls) { + (void) env; + (void) cls; interruptState = 1; PyErr_SetInterrupt(); } @@ -455,6 +457,8 @@ extern "C" JNIEXPORT void JNICALL Java_org_jpype_JPypeSignal_interruptPy extern "C" JNIEXPORT void JNICALL Java_org_jpype_JPypeSignal_acknowledgePy (JNIEnv *env, jclass cls) { + (void) env; + (void) cls; interruptState = 0; } diff --git a/native/common/jp_convert.cpp b/native/common/jp_convert.cpp index 5d288e044..ebff1944f 100644 --- a/native/common/jp_convert.cpp +++ b/native/common/jp_convert.cpp @@ -41,7 +41,7 @@ class Half frac = frac | (frac >> 2); frac = frac | (frac >> 4); frac = frac | (frac >> 8); - int zeros = std::bitset<32>(~frac).count(); + int zeros = (int)std::bitset<32>((size_t)((uint32_t)~frac)).count(); man = 127-zeros+7; man <<= 23; frac <<= zeros-8; @@ -62,7 +62,7 @@ class Half // to infinity and beyond! if (frac == 0) k |= 0x7f800000; - else + else k |= 0x7f800001 | ((frac&0x200)<<12); } return func(&k); diff --git a/native/common/jp_encoding.cpp b/native/common/jp_encoding.cpp index bb3af6ccd..17cdfc6de 100644 --- a/native/common/jp_encoding.cpp +++ b/native/common/jp_encoding.cpp @@ -15,6 +15,8 @@ *****************************************************************************/ #include "jp_encoding.h" +#include + // These encoders handle all the codes expected to be passed between // Java and Python assuming they both generate compliant codings. However, // this code does not handle miscodings very well. The current behavior diff --git a/native/common/jp_exception.cpp b/native/common/jp_exception.cpp index f56342648..383dde4f3 100644 --- a/native/common/jp_exception.cpp +++ b/native/common/jp_exception.cpp @@ -23,7 +23,7 @@ static_assert(std::is_nothrow_copy_constructible::value, "S must be nothrow copy constructible"); -PyObject* PyTrace_FromJPStackTrace(JPStackTrace& trace); +static PyObject* PyTrace_FromJPStackTrace(JPStackTrace& trace); JPypeException::JPypeException(JPJavaFrame &frame, jthrowable th, const JPStackInfo& stackInfo) : std::runtime_error(frame.toString(th)), @@ -122,7 +122,7 @@ string JPypeException::getMessage() // GCOVR_EXCL_STOP }*/ -bool isJavaThrowable(PyObject* exceptionClass) +static bool isJavaThrowable(PyObject* exceptionClass) { JPClass* cls = PyJPClass_getJPClass(exceptionClass); if (cls == nullptr) @@ -470,8 +470,8 @@ void JPypeException::toJava(JPContext *context) JP_TRACE_OUT; // GCOVR_EXCL_LINE } -PyObject *tb_create( - PyObject *last_traceback, +static PyObject *tb_create( + PyObject *, PyObject *dict, const char* filename, const char* funcname, diff --git a/native/common/jp_extension.cpp b/native/common/jp_extension.cpp new file mode 100755 index 000000000..1d79dbf2d --- /dev/null +++ b/native/common/jp_extension.cpp @@ -0,0 +1,146 @@ +/***************************************************************************** + 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/jp_class.h" +#include "include/jp_exception.h" +#include "jni.h" +#include "jpype.h" +#include "jp_extension.hpp" // IWYU pragma: keep + +static JPPyObject packArgs(JPExtensionType &cls, const JPMethodOverride &method, jobjectArray args) +{ + JP_TRACE_IN("JProxy::getArgs"); + JPJavaFrame frame = JPJavaFrame::outer(cls.getContext()); + const Py_ssize_t argLen = (Py_ssize_t)method.paramTypes.size() + 1; + JPPyObject pyargs = JPPyObject::call(PyTuple_New(argLen)); + + // NOTE: we will always have at least one argument (cls or self) + + jobject obj = frame.GetObjectArrayElement(args, 0); + if (cls == obj) { + PyTuple_SetItem(pyargs.get(), 0, JPPyObject::use((PyObject*)cls.getHost()).keep()); + } else { + JPValue val{&cls, obj}; + PyTuple_SetItem(pyargs.get(), 0, cls.convertToPythonObject(frame, val, false).keep()); + } + + for (Py_ssize_t i = 1; i < argLen; i++) { + jobject obj = frame.GetObjectArrayElement(args, (jsize)i); + JPClass *type = method.paramTypes[i-1]; + 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__1call( + JNIEnv *env, + jclass, + jlong contextPtr, + jlong functionId, + jobjectArray args + ) +{ + JPExtensionType *cls = (JPExtensionType *) contextPtr; + JPContext* context = cls->getContext(); + JPJavaFrame frame = JPJavaFrame::external(context, env); + + // We need the resources to be held for the full duration of the proxy. + JPPyCallAcquire callback; + try { + JP_TRACE_IN("JPype_InvocationHandler_hostInvoke"); + JP_TRACE("context", context); + try + { + const JPMethodOverride &method = cls->getOverrides()[functionId]; + + // Find the return type + JPClass* returnClass = method.returnType; + JP_TRACE("Get return type", returnClass->getCanonicalName()); + + // convert the arguments into a python list + JP_TRACE("Convert arguments"); + JPPyObject pyargs = packArgs(*cls, method, args); + + JP_TRACE("Call Python"); + JPPyObject returnValue = JPPyObject::call(PyObject_Call( + method.function.get(), + 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 + } + catch (...) // JP_TRACE_OUT implies a throw but that is not allowed. + {} + return NULL; +} + +extern "C" JNIEXPORT void JNICALL Java_org_jpype_extension_ExtensionClassLoader_clearHost( + JNIEnv *, + jclass, + jlong cls + ) +{ + ((JPExtensionType *)cls)->clearHost(); +} diff --git a/native/common/jp_field.cpp b/native/common/jp_field.cpp index d291e5f71..6bf58fe88 100644 --- a/native/common/jp_field.cpp +++ b/native/common/jp_field.cpp @@ -13,7 +13,10 @@ See NOTICE file for details. *****************************************************************************/ +#include "jp_class.h" +#include "jp_member.hpp" #include "jpype.h" +#include #include "jp_field.h" JPField::JPField(JPJavaFrame& frame, @@ -39,6 +42,7 @@ JPPyObject JPField::getStaticField() { JP_TRACE_IN("JPField::getStaticAttribute"); JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext()); + checkAccess(frame, *this); return m_Type->getStaticField(frame, m_Class->getJavaClass(), m_FieldID); JP_TRACE_OUT; } @@ -47,6 +51,7 @@ void JPField::setStaticField(PyObject *pyobj) { JP_TRACE_IN("JPField::setStaticAttribute"); JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext()); + checkAccess(frame, *this); m_Type->setStaticField(frame, m_Class->getJavaClass(), m_FieldID, pyobj); JP_TRACE_OUT; } @@ -57,6 +62,7 @@ JPPyObject JPField::getField(jobject inst) JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext()); ASSERT_NOT_NULL(m_Type); JP_TRACE("field type", m_Type->getCanonicalName()); + checkAccess(frame, *this); return m_Type->getField(frame, inst, m_FieldID); JP_TRACE_OUT; } @@ -65,6 +71,7 @@ void JPField::setField(jobject inst, PyObject *pyobj) { JP_TRACE_IN("JPField::setAttribute"); JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext()); + checkAccess(frame, *this); m_Type->setField(frame, inst, m_FieldID, pyobj); JP_TRACE_OUT; } diff --git a/native/common/jp_javaframe.cpp b/native/common/jp_javaframe.cpp index 62bfd2626..721dacf69 100644 --- a/native/common/jp_javaframe.cpp +++ b/native/common/jp_javaframe.cpp @@ -1089,12 +1089,12 @@ string JPJavaFrame::toStringUTF8(jstring str) #endif } -jstring JPJavaFrame::fromStringUTF8(const string& str) +jstring JPJavaFrame::fromStringUTF8(const std::string_view& str) { #ifdef ANDROID return (jstring) NewStringUTF(str.c_str()); #else - string mstr = transcribe(str.c_str(), str.size(), JPEncodingUTF8(), JPEncodingJavaUTF8()); + string mstr = transcribe(str.data(), str.size(), JPEncodingUTF8(), JPEncodingJavaUTF8()); return (jstring) NewStringUTF(mstr.c_str()); #endif } @@ -1180,7 +1180,7 @@ JPClass *JPJavaFrame::findClass(jclass obj) return m_Context->getTypeManager()->findClass(obj); } -JPClass *JPJavaFrame::findClassByName(const string& name) +JPClass *JPJavaFrame::findClassByName(const std::string_view& name) { return m_Context->getTypeManager()->findClassByName(name); } @@ -1190,6 +1190,15 @@ JPClass *JPJavaFrame::findClassForObject(jobject obj) return m_Context->getTypeManager()->findClassForObject(obj); } +JPClass *JPJavaFrame::getClassByName(const std::string_view& name) +{ + JPClass *cls = findClassByName(name); + if (cls != nullptr && cls->getHost() == nullptr) { + newWrapper(cls); + } + return cls; +} + jint JPJavaFrame::compareTo(jobject obj, jobject obj2) { jvalue v; diff --git a/native/common/jp_member.hpp b/native/common/jp_member.hpp new file mode 100644 index 000000000..92ccffa03 --- /dev/null +++ b/native/common/jp_member.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "jp_field.h" +#include "jp_javaframe.h" +#include "jp_method.h" +#include "pyjp.h" + +#include +#include + +/* +#include + +template +concept JavaMember = requires(T t) { + std::same_as || std::same_as; + { t.isPublic() } -> std::same_as; + { t.isProtected() } -> std::same_as; + { t.isPrivate() } -> std::same_as; + { t.isStatic() } -> std::same_as; + { t.getClass() } -> std::same_as; +}; + +template +bool canAccess(JPJavaFrame &jframe, const T &member) { ... } +*/ + +template +using IsJavaMember = std::enable_if_t, std::is_same>, bool>; + +namespace { + +template = true> +bool canAccess(JPJavaFrame &jframe, const T &member) { + if (member.isPublic()) { + return true; + } + + PyObject *locals = PyEval_GetLocals(); + if (locals == nullptr) { + // access denied + return false; + } + + JPPyObject obj = JPPyObject::call(PyMapping_GetItemString(locals, "self")); + if (obj.get() != nullptr) { + obj = JPPyObject::use((PyObject *) Py_TYPE(obj.get())); + } else { + obj = JPPyObject::call(PyMapping_GetItemString(locals, "cls")); + } + + if (obj.isNull()) { + return false; + } + + JPClass *cls = PyJPClass_getJPClass(obj.get()); + if (cls == nullptr) { + return false; + } + + if (member.isPrivate()) { + return cls == member.getClass(); + } + + if (member.isProtected()) { + return cls->isAssignableFrom(jframe, member.getClass()); + } + + // package visibility not supported + return false; +} + +} + +template = true> +void checkAccess(JPJavaFrame &jframe, const T &member) { + if (canAccess(jframe, member)) { + return; + } + if constexpr(std::is_same_v) { + JP_RAISE(PyExc_AttributeError, "Field is not visible"); + } + JP_RAISE(PyExc_AttributeError, "Method is not visible"); +} diff --git a/native/common/jp_method.cpp b/native/common/jp_method.cpp index debc9557d..f9fdd14b8 100644 --- a/native/common/jp_method.cpp +++ b/native/common/jp_method.cpp @@ -13,6 +13,8 @@ See NOTICE file for details. *****************************************************************************/ +#include "jp_member.hpp" +#include "jp_modifier.h" #include "jpype.h" #include "jp_arrayclass.h" #include "jp_method.h" @@ -22,10 +24,11 @@ JPMethod::JPMethod(JPJavaFrame& frame, JPClass* claz, const string& name, jobject mth, + jclass declaringClass, jmethodID mid, JPMethodList& moreSpecific, jint modifiers) -: m_Method(frame, mth) +: m_Method(frame, mth), m_DeclaringClass(frame, declaringClass) { m_Class = claz; m_Name = name; @@ -51,9 +54,10 @@ string JPMethod::toString() const return m_Name; } -JPMatch::Type matchVars(JPJavaFrame &frame, JPMethodMatch& match, JPPyObjectVector &arg, size_t start, JPClass *vartype) +static JPMatch::Type matchVars(JPJavaFrame &frame, JPMethodMatch& match, JPPyObjectVector &arg, size_t start, JPClass *vartype) { JP_TRACE_IN("JPMethod::matchVars"); + (void)frame; auto *arraytype = dynamic_cast( vartype); JPClass *type = arraytype->getComponentType(); size_t len = arg.size(); @@ -194,7 +198,7 @@ void JPMethod::packArgs(JPJavaFrame &frame, JPMethodMatch &match, } JP_TRACE("Pack fixed total=", len - match.m_Offset); - for (size_t i = match.m_Skip; i < len; i++) + for (size_t i = (unsigned char)match.m_Skip; i < len; i++) { v[i - match.m_Skip] = match.m_Arguments[i].convert(); } @@ -240,6 +244,12 @@ JPPyObject JPMethod::invoke(JPJavaFrame& frame, JPMethodMatch& match, JPPyObject { clazz = m_Class->getJavaClass(); JP_TRACE("invoke nonvirtual", m_Name); + } else if (m_Class->isExtension() || m_Class->isExtensionBase()) + { + // extensions always use nonvirtual to allow use of super + checkAccess(frame, *this); + clazz = m_Class->getJavaClass(); + JP_TRACE("invoke nonvirtual", m_Name); } else { JP_TRACE("invoke virtual", m_Name); @@ -252,6 +262,7 @@ JPPyObject JPMethod::invoke(JPJavaFrame& frame, JPMethodMatch& match, JPPyObject JPPyObject JPMethod::invokeCallerSensitive(JPMethodMatch& match, JPPyObjectVector& arg, bool instance) { JP_TRACE_IN("JPMethod::invokeCallerSensitive"); + (void)instance; JPContext *context = m_Class->getContext(); size_t alen = m_ParameterTypes.size(); JPJavaFrame frame = JPJavaFrame::outer(context, (int) (8 + alen)); @@ -378,7 +389,7 @@ string JPMethod::matchReport(JPPyObjectVector& args) break; } // GCOVR_EXCL_STOP - res << std::endl; + res << '\n'; return res.str(); } diff --git a/native/common/jp_methoddispatch.cpp b/native/common/jp_methoddispatch.cpp index 35627af44..d179cedf7 100644 --- a/native/common/jp_methoddispatch.cpp +++ b/native/common/jp_methoddispatch.cpp @@ -13,8 +13,6 @@ See NOTICE file for details. *****************************************************************************/ -#include -#include #include "jpype.h" #include "jp_method.h" #include "jp_methoddispatch.h" @@ -31,14 +29,6 @@ JPMethodDispatch::JPMethodDispatch(JPClass* clazz, m_LastCache.m_Hash = -1; } -JPMethodDispatch::~JPMethodDispatch() -= default; - -const string& JPMethodDispatch::getName() const -{ - return m_Name; -} - bool JPMethodDispatch::findOverload(JPJavaFrame& frame, JPMethodMatch &bestMatch, JPPyObjectVector& arg, bool callInstance, bool raise) { diff --git a/native/common/jp_primitivetype.cpp b/native/common/jp_primitivetype.cpp index 743ee33b7..99579a685 100644 --- a/native/common/jp_primitivetype.cpp +++ b/native/common/jp_primitivetype.cpp @@ -56,9 +56,9 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) #else // 3.12 completely does away with ob_size field and repurposes it - + // Determine the number of digits to copy - int n = (tmp->long_value.lv_tag >> 3); + int n = (int)(tmp->long_value.lv_tag >> 3); PyLongObject *newobj = (PyLongObject *) wrapper->tp_alloc(wrapper, n); if (newobj == NULL) diff --git a/native/common/jp_proxy.cpp b/native/common/jp_proxy.cpp index 432554e2b..ffd899e1e 100644 --- a/native/common/jp_proxy.cpp +++ b/native/common/jp_proxy.cpp @@ -16,13 +16,11 @@ #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" -JPPyObject getArgs(JPContext* context, jlongArray parameterTypePtrs, +static JPPyObject getArgs(JPContext* context, jlongArray parameterTypePtrs, jobjectArray args) { JP_TRACE_IN("JProxy::getArgs"); @@ -47,7 +45,7 @@ JPPyObject getArgs(JPContext* context, jlongArray parameterTypePtrs, } extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke( - JNIEnv *env, jclass clazz, + JNIEnv *env, jclass, jlong contextPtr, jstring name, jlong hostObj, jlong returnTypePtr, @@ -149,7 +147,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke( return nullptr; JP_TRACE_OUT; // GCOVR_EXCL_LINE } - catch (...) // JP_TRACE_OUT implies a throw but that is not allowed. + catch (...) // JP_TRACE_OUT implies a throw but that is not allowed. {} return NULL; } @@ -166,7 +164,7 @@ JPProxy::JPProxy(JPContext* context, PyJPProxy* inst, JPClassList& intf) m_Context->_java_lang_Class->getJavaClass(), nullptr); for (unsigned int i = 0; i < intf.size(); i++) { - frame.SetObjectArrayElement(ar, i, intf[i]->getJavaClass()); + frame.SetObjectArrayElement(ar, (jsize)i, intf[i]->getJavaClass()); } jvalue v[4]; v[0].l = m_Context->getJavaContext(); @@ -190,7 +188,7 @@ JPProxy::~JPProxy() { m_Context->getEnv()->DeleteWeakGlobalRef(m_Ref); } - } catch (JPypeException &ex) // GCOVR_EXCL_LINE + } catch (JPypeException &) // GCOVR_EXCL_LINE { // Cannot throw } @@ -246,7 +244,7 @@ JPProxyType::JPProxyType(JPJavaFrame& frame, JPProxyType::~JPProxyType() = default; -JPPyObject JPProxyType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) +JPPyObject JPProxyType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool) { JP_TRACE_IN("JPProxyType::convertToPythonObject"); jobject ih = frame.CallStaticObjectMethodA(m_ProxyClass.get(), diff --git a/native/common/jp_stringtype.cpp b/native/common/jp_stringtype.cpp index 518f62f5b..3bd932ec4 100644 --- a/native/common/jp_stringtype.cpp +++ b/native/common/jp_stringtype.cpp @@ -46,7 +46,7 @@ JPPyObject JPStringType::convertToPythonObject(JPJavaFrame& frame, jvalue val, b if (context->getConvertStrings()) { string str = frame.toStringUTF8((jstring) (val.l)); - return JPPyObject::call(PyUnicode_FromStringAndSize(str.c_str(), str.length())); + return JPPyObject::call(PyUnicode_FromStringAndSize(str.c_str(), (Py_ssize_t)str.length())); } } diff --git a/native/common/jp_typefactory.cpp b/native/common/jp_typefactory.cpp index 6388f3764..8fdc21c46 100644 --- a/native/common/jp_typefactory.cpp +++ b/native/common/jp_typefactory.cpp @@ -13,10 +13,12 @@ See NOTICE file for details. *****************************************************************************/ +#include "include/jp_modifier.h" +#include "jp_class.h" +#include "jp_resource.h" #include "jpype.h" #include "pyjp.h" #include "jp_primitive_accessor.h" -#include "jp_classloader.h" #include "jp_boxedtype.h" #include "jp_field.h" #include "jp_objecttype.h" @@ -39,8 +41,9 @@ #include "jp_doubletype.h" #include "jp_functional.h" #include "jp_proxy.h" +#include "jp_extension.hpp" -void JPTypeFactory_rethrow(JPJavaFrame& frame) +static void JPTypeFactory_rethrow(JPJavaFrame& frame) { try { @@ -57,7 +60,8 @@ void JPTypeFactory_rethrow(JPJavaFrame& frame) } } -template void convert(JPJavaFrame& frame, jlongArray array, vector& out) +template +static void convert(JPJavaFrame& frame, jlongArray array, vector& out) { JPPrimitiveArrayAccessor accessor(frame, array, &JPJavaFrame::GetLongArrayElements, &JPJavaFrame::ReleaseLongArrayElements); @@ -90,6 +94,7 @@ extern "C" JNIEXPORT void JNICALL Java_org_jpype_manager_TypeFactoryNative_newWrapper( JNIEnv *env, jobject self, jlong contextPtr, jlong jcls) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_newWrapper"); @@ -104,6 +109,7 @@ JNIEXPORT void JNICALL Java_org_jpype_manager_TypeFactoryNative_destroy( jlongArray resources, jint sz) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_destroy"); @@ -125,6 +131,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineMethodDis jlongArray overloadPtrs, jint modifiers) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_defineMethodDispatch"); @@ -146,6 +153,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineArrayClas jlong componentClass, jint modifiers) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_defineArrayClass"); @@ -166,8 +174,9 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineObjectCla jstring name, jlong superClass, jlongArray interfacePtrs, - jint modifiers) + jlong modifiers) { + (void) self; // All resources are created here are owned by Java and deleted by Java shutdown routine auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); @@ -178,37 +187,44 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineObjectCla if (interfacePtrs != nullptr) convert(frame, interfacePtrs, interfaces); JPClass* result = nullptr; + + if (JPModifier::isExtension(modifiers)) { + return (jlong) new JPExtensionType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers); + } + if (!JPModifier::isSpecial(modifiers)) { - // Create a normal class + // Create a normal class, this also covers the extension base return (jlong) new JPClass(frame, cls, className, (JPClass*) superClass, interfaces, modifiers); } - if (JPModifier::isFunctional(modifiers)) - return (jlong) new JPFunctional(frame, cls, className, (JPClass*) superClass, interfaces, modifiers); - if (JPModifier::isBuffer(modifiers)) - return (jlong) new JPBufferType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers); + if (JPModifier::isFunctional(modifiers)) { + return (jlong) new JPFunctional(frame, cls, className, (JPClass*) superClass, interfaces, (jint)modifiers); + } + if (JPModifier::isBuffer(modifiers)) { + return (jlong) new JPBufferType(frame, cls, className, (JPClass*) superClass, interfaces, (jint)modifiers); + } // Certain classes require special implementations if (className == "java.lang.Object") return (jlong) (context->_java_lang_Object - = new JPObjectType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers)); + = new JPObjectType(frame, cls, className, (JPClass*) superClass, interfaces, (jint)modifiers)); if (className == "java.lang.Class") return (jlong) (context->_java_lang_Class - = new JPClassType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers)); + = new JPClassType(frame, cls, className, (JPClass*) superClass, interfaces, (jint)modifiers)); if (className == "java.lang.CharSequence") return (jlong) (new JPStringType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers)); + (JPClass*) superClass, interfaces, (jint)modifiers)); if (className == "java.lang.String") return (jlong) (context->_java_lang_String = new JPStringType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers)); + (JPClass*) superClass, interfaces, (jint)modifiers)); if (className == "java.lang.Throwable") return (jlong) (context->_java_lang_Throwable = new JPClassType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers)); + (JPClass*) superClass, interfaces, (jint)modifiers)); if (className == "java.lang.Number") - return (jlong) new JPNumberType(frame, cls, className, (JPClass*) superClass, interfaces, modifiers); + return (jlong) new JPNumberType(frame, cls, className, (JPClass*) superClass, interfaces, (jint)modifiers); // Register the box types @@ -217,68 +233,68 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineObjectCla context->_void = new JPVoidType(); return (jlong) (context->_java_lang_Void = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_void)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_void)); } if (className == "java.lang.Boolean") { context->_boolean = new JPBooleanType(); return (jlong) (context->_java_lang_Boolean = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_boolean)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_boolean)); } if (className == "java.lang.Byte") { context->_byte = new JPByteType(); return (jlong) (context->_java_lang_Byte = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_byte)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_byte)); } if (className == "java.lang.Character") { context->_char = new JPCharType(); return (jlong) (context->_java_lang_Character = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_char)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_char)); } if (className == "java.lang.Short") { context->_short = new JPShortType(); return (jlong) (context->_java_lang_Short = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_short)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_short)); } if (className == "java.lang.Integer") { context->_int = new JPIntType(); return (jlong) (context->_java_lang_Integer = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_int)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_int)); } if (className == "java.lang.Long") { context->_long = new JPLongType(); return (jlong) (context->_java_lang_Long = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_long)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_long)); } if (className == "java.lang.Float") { context->_float = new JPFloatType(); return (jlong) (context->_java_lang_Float = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_float)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_float)); } if (className == "java.lang.Double") { context->_double = new JPDoubleType(); return (jlong) (context->_java_lang_Double = new JPBoxedType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers, context->_double)); + (JPClass*) superClass, interfaces, (jint)modifiers, context->_double)); } if (className == "org.jpype.proxy.JPypeProxy") return (jlong) new JPProxyType(frame, cls, className, - (JPClass*) superClass, interfaces, modifiers); + (JPClass*) superClass, interfaces, (jint)modifiers); // Register reflection types for later use if (className == "java.lang.reflect.Method") @@ -303,6 +319,9 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_definePrimitive jlong boxedPtr, jint modifiers) { + (void) self; + (void) boxedPtr; + (void) modifiers; // These resources are created by the boxed types auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); @@ -366,6 +385,7 @@ JNIEXPORT void JNICALL Java_org_jpype_manager_TypeFactoryNative_assignMembers( jlongArray methodPtrs, jlongArray fieldPtrs) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_assignMembers"); @@ -391,6 +411,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineField( jlong fieldType, jint modifiers) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_defineField"); @@ -412,8 +433,10 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineMethod( JNIEnv *env, jobject self, jlong contextPtr, jlong cls, jstring name, jobject method, + jclass declaringCls, jlongArray overloadList, jint modifiers) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_defineMethod"); @@ -426,7 +449,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineMethod( frame, (JPClass*) cls, cname, - method, mid, + method, declaringCls, mid, cover, modifiers)); JP_JAVA_CATCH(0); @@ -439,6 +462,7 @@ JNIEXPORT void JNICALL Java_org_jpype_manager_TypeFactoryNative_populateMethod( jlongArray argumentTypes ) { + (void) self; auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); JP_JAVA_TRY("JPTypeFactory_populateMethod"); @@ -449,5 +473,27 @@ JNIEXPORT void JNICALL Java_org_jpype_manager_TypeFactoryNative_populateMethod( JP_JAVA_CATCH(); // GCOVR_EXCL_LINE } +JNIEXPORT void JNICALL Java_org_jpype_manager_TypeFactoryNative_delete( + JNIEnv *env, jobject self, jlong contextPtr, jlongArray resources, jint sz) +{ + (void) self; + auto* context = (JPContext*) contextPtr; + JPJavaFrame frame = JPJavaFrame::external(context, env); + JP_JAVA_TRY("JPTypeFactory_destroy"); + JPPrimitiveArrayAccessor accessor(frame, resources, + &JPJavaFrame::GetLongArrayElements, &JPJavaFrame::ReleaseLongArrayElements); + JPResource **values = (JPResource **)accessor.get(); + for (int i = 0; i < sz; ++i) { + JPExtensionType *ext = dynamic_cast(values[i]); + if (ext != nullptr) { + ext->reset(frame); + } else { + delete values[i]; + } + } + return; + JP_JAVA_CATCH(); // GCOVR_EXCL_LINE +} + } // extern "C" diff --git a/native/common/jp_typemanager.cpp b/native/common/jp_typemanager.cpp index 618975ff2..157ff2910 100644 --- a/native/common/jp_typemanager.cpp +++ b/native/common/jp_typemanager.cpp @@ -23,6 +23,7 @@ JPTypeManager::JPTypeManager(JPJavaFrame& frame) jclass cls = m_Context->getClassLoader()->findClass(frame, "org.jpype.manager.TypeManager"); m_FindClass = frame.GetMethodID(cls, "findClass", "(Ljava/lang/Class;)J"); + m_FindExtensionBaseClass = frame.GetMethodID(cls, "findExtensionBaseClass", "(Ljava/lang/Class;)J"); m_FindClassByName = frame.GetMethodID(cls, "findClassByName", "(Ljava/lang/String;)J"); m_FindClassForObject = frame.GetMethodID(cls, "findClassForObject", "(Ljava/lang/Object;)J"); m_PopulateMethod = frame.GetMethodID(cls, "populateMethod", "(JLjava/lang/reflect/Executable;)V"); @@ -43,7 +44,17 @@ JPClass* JPTypeManager::findClass(jclass obj) JP_TRACE_OUT; } -JPClass* JPTypeManager::findClassByName(const string& name) +JPClass* JPTypeManager::findExtensionBaseClass(jclass obj) +{ + JP_TRACE_IN("JPTypeManager::findExtensionBaseClass"); + JPJavaFrame frame = JPJavaFrame::outer(m_Context); + jvalue val; + val.l = obj; + return (JPClass*) (frame.CallLongMethodA(m_JavaTypeManager.get(), m_FindExtensionBaseClass, &val)); + JP_TRACE_OUT; +} + +JPClass* JPTypeManager::findClassByName(const std::string_view& name) { JP_TRACE_IN("JPTypeManager::findClassByName"); JPJavaFrame frame = JPJavaFrame::outer(m_Context); diff --git a/native/java/org/jpype/JPypeContext.java b/native/java/org/jpype/JPypeContext.java index c997d3759..d8ba1cb8c 100644 --- a/native/java/org/jpype/JPypeContext.java +++ b/native/java/org/jpype/JPypeContext.java @@ -19,11 +19,8 @@ import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -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; @@ -73,7 +70,7 @@ public class JPypeContext { - public final String VERSION = "1.5.2.dev0"; + public final String VERSION = "2.0.0_dev0"; private static JPypeContext INSTANCE = new JPypeContext(); // This is the C++ portion of the context. @@ -351,7 +348,7 @@ public Object callMethod(Method method, Object obj, Object[] args) /** * Helper function for collect rectangular, */ - private static boolean collect(List l, Object o, int q, int[] shape, int d) + private static boolean collect(List l, Object o, int q, int[] shape, int d) { if (Array.getLength(o) != shape[q]) return false; @@ -390,7 +387,7 @@ public Object[] collectRectangular(Object o) int d = 0; ArrayList out = new ArrayList<>(); Object o1 = o; - Class c1 = o1.getClass(); + Class c1 = o1.getClass(); for (int i = 0; i < 5; ++i) { int l = Array.getLength(o1); @@ -423,7 +420,7 @@ public Object[] collectRectangular(Object o) private Object unpack(int size, Object parts) { Object e0 = Array.get(parts, 0); - Class c = e0.getClass(); + Class c = e0.getClass(); int segments = Array.getLength(parts) / size; Object a2 = Array.newInstance(c, size); Object a1 = Array.newInstance(a2.getClass(), segments); @@ -486,8 +483,8 @@ public static void clearInterrupt(boolean x) throws InterruptedException if (th != JPypeSignal.main) return; - // Unconditionally clear the interrupt flag if we are called from - // C++. This happens when a field get() or method call() is + // 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(); @@ -566,7 +563,7 @@ public JPypePackage getPackage(String s) * @param cls * @return */ - public static String getFunctional(Class cls) + public static String getFunctional(Class cls) { Method m = JPypeUtilities.getFunctionalInterfaceMethod(cls); return m != null ? m.getName() : null; @@ -637,22 +634,22 @@ private static void scanExistingJars() } } - private static long getTotalMemory() + private static long getTotalMemory() { return Runtime.getRuntime().totalMemory(); } - private static long getFreeMemory() + private static long getFreeMemory() { return Runtime.getRuntime().freeMemory(); } - private static long getMaxMemory() + private static long getMaxMemory() { return Runtime.getRuntime().maxMemory(); } - private static long getUsedMemory() + private static long getUsedMemory() { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } diff --git a/native/java/org/jpype/JPypeSignal.java b/native/java/org/jpype/JPypeSignal.java index be8f1d476..2b93de90c 100644 --- a/native/java/org/jpype/JPypeSignal.java +++ b/native/java/org/jpype/JPypeSignal.java @@ -35,8 +35,8 @@ static void installHandlers() { try { - Class Signal = Class.forName("sun.misc.Signal"); - Class SignalHandler = Class.forName("sun.misc.SignalHandler"); + Class Signal = Class.forName("sun.misc.Signal"); + Class SignalHandler = Class.forName("sun.misc.SignalHandler"); main = Thread.currentThread(); Method method = Signal.getMethod("handle", Signal, SignalHandler); diff --git a/native/java/org/jpype/JPypeUtilities.java b/native/java/org/jpype/JPypeUtilities.java index 789db6ddf..cc575230c 100644 --- a/native/java/org/jpype/JPypeUtilities.java +++ b/native/java/org/jpype/JPypeUtilities.java @@ -11,21 +11,22 @@ import java.util.Arrays; import java.util.function.Predicate; +@SuppressWarnings("unchecked") public class JPypeUtilities { - + // a functional interface can only re-declare a public non-final method from Object // this should end up being an array of equals, hashCode and toString private static final Method[] OBJECT_METHODS = Arrays.stream(Object.class.getMethods()) .filter(m -> !Modifier.isFinal(m.getModifiers())) .toArray(Method[]::new); - - private static final Predicate isSealed; + + private static final Predicate> isSealed; static { - Predicate result = null; + Predicate> result = null; try { Method m = Class.class.getMethod("isSealed"); @@ -43,7 +44,7 @@ public class JPypeUtilities isSealed = result; } - public static Path getJarPath(Class c) + public static Path getJarPath(Class c) { try { @@ -55,7 +56,7 @@ public static Path getJarPath(Class c) } } - public static Method getFunctionalInterfaceMethod(Class cls) + public static Method getFunctionalInterfaceMethod(Class cls) { if (!cls.isInterface() || cls.isAnnotation() || isSealed.test(cls)) return null; @@ -67,7 +68,7 @@ public static Method getFunctionalInterfaceMethod(Class cls) { if (isObjectMethodOverride(m)) continue; - + if (result != null && !equals(m, result)) return null; @@ -87,7 +88,7 @@ private static boolean isObjectMethodOverride(Method m) } return false; } - + private static boolean equals(Method a, Method b) { // this should be the fastest possible short circuit @@ -106,5 +107,5 @@ private static boolean equals(Method a, Method b) // if it did compile then it is an override return true; } - + } diff --git a/native/java/org/jpype/asm/AnnotationVisitor.java b/native/java/org/jpype/asm/AnnotationVisitor.java new file mode 100644 index 000000000..e546a6e66 --- /dev/null +++ b/native/java/org/jpype/asm/AnnotationVisitor.java @@ -0,0 +1,158 @@ +// 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 + */ +@SuppressWarnings("all") +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/org/jpype/asm/AnnotationWriter.java b/native/java/org/jpype/asm/AnnotationWriter.java new file mode 100644 index 000000000..6ab397d3a --- /dev/null +++ b/native/java/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/org/jpype/asm/Attribute.java b/native/java/org/jpype/asm/Attribute.java new file mode 100644 index 000000000..b0981122d --- /dev/null +++ b/native/java/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/org/jpype/asm/ByteVector.java b/native/java/org/jpype/asm/ByteVector.java new file mode 100644 index 000000000..d1d8eecad --- /dev/null +++ b/native/java/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/org/jpype/asm/ClassReader.java b/native/java/org/jpype/asm/ClassReader.java new file mode 100644 index 000000000..d3597836e --- /dev/null +++ b/native/java/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/org/jpype/asm/ClassTooLargeException.java b/native/java/org/jpype/asm/ClassTooLargeException.java new file mode 100644 index 000000000..adba9971c --- /dev/null +++ b/native/java/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/org/jpype/asm/ClassVisitor.java b/native/java/org/jpype/asm/ClassVisitor.java new file mode 100644 index 000000000..f715f5c90 --- /dev/null +++ b/native/java/org/jpype/asm/ClassVisitor.java @@ -0,0 +1,381 @@ +// 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 + */ +@SuppressWarnings("all") +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/org/jpype/asm/ClassWriter.java b/native/java/org/jpype/asm/ClassWriter.java new file mode 100644 index 000000000..77922a88f --- /dev/null +++ b/native/java/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/org/jpype/asm/ConstantDynamic.java b/native/java/org/jpype/asm/ConstantDynamic.java new file mode 100644 index 000000000..4814d0aab --- /dev/null +++ b/native/java/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/org/jpype/asm/Constants.java b/native/java/org/jpype/asm/Constants.java new file mode 100644 index 000000000..3e75fcfe2 --- /dev/null +++ b/native/java/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/org/jpype/asm/Context.java b/native/java/org/jpype/asm/Context.java new file mode 100644 index 000000000..09e648997 --- /dev/null +++ b/native/java/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/org/jpype/asm/CurrentFrame.java b/native/java/org/jpype/asm/CurrentFrame.java new file mode 100644 index 000000000..a818e9545 --- /dev/null +++ b/native/java/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/org/jpype/asm/Edge.java b/native/java/org/jpype/asm/Edge.java new file mode 100644 index 000000000..8008755b1 --- /dev/null +++ b/native/java/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/org/jpype/asm/FieldVisitor.java b/native/java/org/jpype/asm/FieldVisitor.java new file mode 100644 index 000000000..5f7d9d310 --- /dev/null +++ b/native/java/org/jpype/asm/FieldVisitor.java @@ -0,0 +1,146 @@ +// 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 + */ +@SuppressWarnings("all") +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/org/jpype/asm/FieldWriter.java b/native/java/org/jpype/asm/FieldWriter.java new file mode 100644 index 000000000..640b27bf0 --- /dev/null +++ b/native/java/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/org/jpype/asm/Frame.java b/native/java/org/jpype/asm/Frame.java new file mode 100644 index 000000000..afc2de981 --- /dev/null +++ b/native/java/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/org/jpype/asm/Handle.java b/native/java/org/jpype/asm/Handle.java new file mode 100644 index 000000000..cf7b267c1 --- /dev/null +++ b/native/java/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/org/jpype/asm/Handler.java b/native/java/org/jpype/asm/Handler.java new file mode 100644 index 000000000..1c04c25c9 --- /dev/null +++ b/native/java/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/org/jpype/asm/Label.java b/native/java/org/jpype/asm/Label.java new file mode 100644 index 000000000..1ed4656a3 --- /dev/null +++ b/native/java/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/org/jpype/asm/MethodTooLargeException.java b/native/java/org/jpype/asm/MethodTooLargeException.java new file mode 100644 index 000000000..34282b1e9 --- /dev/null +++ b/native/java/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/org/jpype/asm/MethodVisitor.java b/native/java/org/jpype/asm/MethodVisitor.java new file mode 100644 index 000000000..870bd2213 --- /dev/null +++ b/native/java/org/jpype/asm/MethodVisitor.java @@ -0,0 +1,787 @@ +// 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 + */ +@SuppressWarnings("all") +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/org/jpype/asm/MethodWriter.java b/native/java/org/jpype/asm/MethodWriter.java new file mode 100644 index 000000000..3331d5672 --- /dev/null +++ b/native/java/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/org/jpype/asm/ModuleVisitor.java b/native/java/org/jpype/asm/ModuleVisitor.java new file mode 100644 index 000000000..d6fae8c9c --- /dev/null +++ b/native/java/org/jpype/asm/ModuleVisitor.java @@ -0,0 +1,186 @@ +// 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 + */ +@SuppressWarnings("all") +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/org/jpype/asm/ModuleWriter.java b/native/java/org/jpype/asm/ModuleWriter.java new file mode 100644 index 000000000..b3d803927 --- /dev/null +++ b/native/java/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/org/jpype/asm/Opcodes.java b/native/java/org/jpype/asm/Opcodes.java new file mode 100644 index 000000000..f01d97cd5 --- /dev/null +++ b/native/java/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/org/jpype/asm/RecordComponentVisitor.java b/native/java/org/jpype/asm/RecordComponentVisitor.java new file mode 100644 index 000000000..df3f318a0 --- /dev/null +++ b/native/java/org/jpype/asm/RecordComponentVisitor.java @@ -0,0 +1,153 @@ +// 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 + */ +@SuppressWarnings("all") +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/org/jpype/asm/RecordComponentWriter.java b/native/java/org/jpype/asm/RecordComponentWriter.java new file mode 100644 index 000000000..7d5d60378 --- /dev/null +++ b/native/java/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/org/jpype/asm/Symbol.java b/native/java/org/jpype/asm/Symbol.java new file mode 100644 index 000000000..661d2ccbc --- /dev/null +++ b/native/java/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/org/jpype/asm/SymbolTable.java b/native/java/org/jpype/asm/SymbolTable.java new file mode 100644 index 000000000..b597b3b23 --- /dev/null +++ b/native/java/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/org/jpype/asm/Type.java b/native/java/org/jpype/asm/Type.java new file mode 100644 index 000000000..811d501d6 --- /dev/null +++ b/native/java/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/org/jpype/asm/TypePath.java b/native/java/org/jpype/asm/TypePath.java new file mode 100644 index 000000000..f0023d379 --- /dev/null +++ b/native/java/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/org/jpype/asm/TypeReference.java b/native/java/org/jpype/asm/TypeReference.java new file mode 100644 index 000000000..f24669dc1 --- /dev/null +++ b/native/java/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/org/jpype/asm/package.html b/native/java/org/jpype/asm/package.html new file mode 100644 index 000000000..3ccd4a6d8 --- /dev/null +++ b/native/java/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/org/jpype/asm/signature/SignatureReader.java b/native/java/org/jpype/asm/signature/SignatureReader.java new file mode 100644 index 000000000..59f1f56f9 --- /dev/null +++ b/native/java/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/org/jpype/asm/signature/SignatureVisitor.java b/native/java/org/jpype/asm/signature/SignatureVisitor.java new file mode 100644 index 000000000..7d3510f6c --- /dev/null +++ b/native/java/org/jpype/asm/signature/SignatureVisitor.java @@ -0,0 +1,210 @@ +// 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 + */ +@SuppressWarnings("all") +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/org/jpype/asm/signature/SignatureWriter.java b/native/java/org/jpype/asm/signature/SignatureWriter.java new file mode 100644 index 000000000..75982e69c --- /dev/null +++ b/native/java/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/org/jpype/asm/signature/package.html b/native/java/org/jpype/asm/signature/package.html new file mode 100644 index 000000000..d1657d41a --- /dev/null +++ b/native/java/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/org/jpype/classloader/DynamicClassLoader.java index 2192391e9..7add7f908 100644 --- a/native/java/org/jpype/classloader/DynamicClassLoader.java +++ b/native/java/org/jpype/classloader/DynamicClassLoader.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -110,7 +111,7 @@ public void addFile(Path path) throws FileNotFoundException * @throws ClassFormatError if the class byte code was invalid. */ @Override - public Class findClass(String name) throws ClassNotFoundException, ClassFormatError + public Class findClass(String name) throws ClassNotFoundException, ClassFormatError { String aname = name.replace('.', '/') + ".class"; URL url = this.getResource(aname); @@ -195,6 +196,7 @@ public void addResource(String name, URL url) * * @param p1 */ + @SuppressWarnings("deprecation") public void scanJar(Path p1) { if (!Files.exists(p1)) @@ -205,7 +207,7 @@ public void scanJar(Path p1) { Enumeration entries = jf.entries(); URI abs = p1.toAbsolutePath().toUri(); - Set urls = new java.util.HashSet(); + Set urls = new HashSet<>(); while (entries.hasMoreElements()) { JarEntry next = entries.nextElement(); diff --git a/native/java/org/jpype/classloader/JPypeClassLoader.java b/native/java/org/jpype/classloader/JPypeClassLoader.java index 21e5268bb..09ab3306b 100644 --- a/native/java/org/jpype/classloader/JPypeClassLoader.java +++ b/native/java/org/jpype/classloader/JPypeClassLoader.java @@ -10,7 +10,7 @@ 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.classloader; @@ -115,7 +115,7 @@ public void importJar(byte[] bytes) * @throws ClassFormatError if the class byte code was invalid. */ @Override - public Class findClass(String name) throws ClassNotFoundException, ClassFormatError + public Class findClass(String name) throws ClassNotFoundException, ClassFormatError { String mname = name.replace('.', '/') + ".class"; byte[] data = map.get(mname); @@ -125,7 +125,7 @@ public Class findClass(String name) throws ClassNotFoundException, ClassFormatEr return super.findClass(name); } - Class cls = defineClass(name, data, 0, data.length); + Class cls = defineClass(name, data, 0, data.length); if (cls == null) throw new ClassFormatError("Class load was null"); return cls; diff --git a/native/java/org/jpype/extension/AnnotationDecl.java b/native/java/org/jpype/extension/AnnotationDecl.java new file mode 100644 index 000000000..fc3bb8a12 --- /dev/null +++ b/native/java/org/jpype/extension/AnnotationDecl.java @@ -0,0 +1,116 @@ +package org.jpype.extension; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.jpype.asm.Type; + +public final class AnnotationDecl { + public final Class cls; + final Map elements; + + // FIXME: is declaration order of annotations important? + + public AnnotationDecl(Class cls) { + this.cls = cls; + Method[] methods = cls.getDeclaredMethods(); + this.elements = new HashMap<>(methods.length); + for (Method m : methods) { + elements.put(m.getName(), new ValueHelper(m)); + } + } + + public void addElements(Map elements) { + for (Map.Entry entry : elements.entrySet()) { + String key = entry.getKey(); + ValueHelper helper = this.elements.get(key); + if (helper == null) { + throw new UnsupportedOperationException(key); + } + helper.setValue(entry.getValue()); + } + validate(); + } + + private void validate() { + for (Map.Entry entry : elements.entrySet()) { + if (entry.getValue().isEmpty()) { + // we will take the missing name from the msg and raise a KeyError + throw new IllegalArgumentException(entry.getKey()); + } + } + } + + String getDescriptor() { + return Type.getDescriptor(cls); + } + + static final class ValueHelper { + // this is necessary because asm will write a Long value into an int + // it won't cause an exception until something tries to get the annotation value + + final Class type; + Object value; + + ValueHelper(Method method) { + this.type = method.getReturnType(); + this.value = method.getDefaultValue(); + } + + private boolean isEmpty() { + return value == null; + } + + private static Byte toByte(Number value) { + if (value.longValue() == value.byteValue()) { + return value.byteValue(); + } + throw new IllegalArgumentException("Value: "+value+" doesn't fit in a byte"); + } + + private static Short toShort(Number value) { + if (value.longValue() == value.shortValue()) { + return value.shortValue(); + } + throw new IllegalArgumentException("Value: "+value+" doesn't fit in a short"); + } + + private static Integer toInteger(Number value) { + if (value.longValue() == value.intValue()) { + return value.intValue(); + } + throw new IllegalArgumentException("Value: "+value+" doesn't fit in a integer"); + } + + private static Float toFloat(Number value) { + if (value.doubleValue() == value.floatValue()) { + return value.floatValue(); + } + throw new IllegalArgumentException("Value: "+value+" doesn't fit in a double"); + } + + private void setValue(Object value) { + if (!type.isPrimitive() || type == Boolean.TYPE) { + // bool doesn't suffer from box conversion trouble + this.value = value; + return; + } + if (type == Byte.TYPE) { + this.value = toByte((Number)value); + } else if (type == Character.TYPE) { + this.value = (Character) value; + } else if (type == Short.TYPE) { + this.value = toShort((Number)value); + } else if (type == Integer.TYPE) { + this.value = toInteger((Number)value); + } else if (type == Long.TYPE) { + this.value = (Long) value; + } else if (type == Float.TYPE) { + this.value = toFloat((Number)value); + } else if (type == Double.TYPE) { + this.value = (Double) value; + } + } + } +} diff --git a/native/java/org/jpype/extension/ClassDecl.java b/native/java/org/jpype/extension/ClassDecl.java new file mode 100644 index 000000000..d0f43dd5e --- /dev/null +++ b/native/java/org/jpype/extension/ClassDecl.java @@ -0,0 +1,84 @@ +/** *************************************************************************** + * 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.extension; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jpype.asm.Type; + +/** + * + * @author nelson85 + */ +public final class ClassDecl extends JavaDecl { + + final String name; + ArrayList methods = new ArrayList<>(); + ArrayList fields = new ArrayList<>(); + Class[] bases; + Class base; + List> interfaces; + String internalName; + ExtensionClassLoader ldr; + + ClassDecl(String name, Class[] bases, ExtensionClassLoader ldr) { + this.name = name; + this.bases = bases; + this.ldr = ldr != null ? ldr : ExtensionClassLoader.BUILTIN_LOADER; + } + + public FieldDecl addField(Class cls, String name, Object value, int modifiers) { + if (value != null && !Modifier.isStatic(modifiers)) { + String msg = "Setting a default value for a non static field has no effect"; + throw new IllegalArgumentException(msg); + } + FieldDecl field = new FieldDecl(cls, name, value, modifiers); + fields.add(field); + return field; + } + + public MethodDecl addCtor(Class[] arguments, String[] paramNames, Class[] exceptions, + int modifiers) { + return addMethod("", null, arguments, paramNames, exceptions, modifiers); + } + + public MethodDecl addMethod(String name, Class ret, Class[] arguments, + String[] paramNames, Class[] exceptions, int modifiers) { + MethodDecl method = + new MethodDecl(name, ret, arguments, paramNames, exceptions, modifiers, methods.size()); + methods.add(method); + return method; + } + + void setBase(Class base) { + this.base = base; + } + + void setInterfaces(List> interfaces) { + this.interfaces = interfaces; + } + + public List getMethods() { + return Collections.unmodifiableList(methods); + } + + public Type getType() { + return Type.getType("L"+internalName+";"); + } +} diff --git a/native/java/org/jpype/extension/ExtensionClassLoader.java b/native/java/org/jpype/extension/ExtensionClassLoader.java new file mode 100755 index 000000000..f8cb2ad38 --- /dev/null +++ b/native/java/org/jpype/extension/ExtensionClassLoader.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 org.jpype.extension; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jpype.JPypeContext; + +/** + * Internal class for loading extension classes. + */ +public class ExtensionClassLoader extends ClassLoader { + + static final ExtensionClassLoader BUILTIN_LOADER = new ExtensionClassLoader(); + + // unfortunately we can't access the one already in ClassLoader + private final List> classes; + + ExtensionClassLoader() { + super(JPypeContext.getInstance().getClassLoader()); + classes = new ArrayList<>(); + } + + Class loadClass(String name, byte[] b) { + Class cls = this.defineClass(name, b, 0, b.length); + if (this != BUILTIN_LOADER) { + synchronized (classes) { + classes.add(cls); + } + } + return cls; + } + + public boolean isBuiltinLoader() { + return this == BUILTIN_LOADER; + } + + public List> getClasses() { + synchronized (classes) { + return Collections.unmodifiableList(classes); + } + } + + /** + * Cleanup all JPClass instances for each Class loaded by this ClassLoader. + *

+ * This method is called when the Python Loader associated with this ClassLoader + * is collected by the garbage collector. This loader is kept alive via the __loader__ + * attribute in the Python module where the extension class was defined. Therefore the + * module is no longer reachable and we are free to cleanup the JPClasses and remove + * the references to our classes so that the JVM's garbage collector can collect them + * and this ClassLoader. + */ + public void cleanup() { + if (this == BUILTIN_LOADER) { + throw new UnsupportedOperationException(); + } + synchronized (classes) { + JPypeContext.getInstance().getTypeManager().destroy(this); + classes.clear(); + } + } + + static native void clearHost(long type); +} diff --git a/native/java/org/jpype/extension/Factory.java b/native/java/org/jpype/extension/Factory.java new file mode 100644 index 000000000..2a8c59f57 --- /dev/null +++ b/native/java/org/jpype/extension/Factory.java @@ -0,0 +1,555 @@ +/** *************************************************************************** + * 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.extension; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.jpype.JPypeContext; +import org.jpype.asm.AnnotationVisitor; +import org.jpype.asm.ClassWriter; +import org.jpype.asm.FieldVisitor; +import org.jpype.asm.MethodVisitor; +import org.jpype.asm.Opcodes; +import org.jpype.asm.Type; +import org.jpype.extension.AnnotationDecl.ValueHelper; +import org.jpype.manager.ClassDescriptor; +import org.jpype.manager.TypeManager; + +/** + * This is used to create an extension class. + * + * The process starts by having Python class marked up with declarations. When + * then metaclass JClassBase sees the class it transfers the important annotated + * methods to the ClassDecl. It then calls the factory to instantiate the new + * class. + * + * @author nelson85 + */ +public class Factory { + + private static final Type CONTEXT_TYPE = Type.getType(JPypeContext.class); + private static final Type TYPE_MANAGER_TYPE = Type.getType(TypeManager.class); + private static final Type FACTORY_TYPE = Type.getType(Factory.class); + private static final String CALL_DESCRIPTOR = "(JJ[Ljava/lang/Object;)Ljava/lang/Object;"; + private static final String FIND_CLASS_DESCRIPTOR = "(Ljava/lang/Class;)J"; + private static final String JCLASS_FIELD = "$jclass"; + private static final String INSTANCE_FIELD = "$instance"; + + public static boolean isExtension(Class cls) { + if (cls == null) { + return false; + } + try { + Field[] fields = cls.getDeclaredFields(); + if (fields.length < 1) { + return false; + } + // this will always be the first field + return fields[0].getName().equals(JCLASS_FIELD); + } catch (Throwable t) {} + return false; + } + + public static ClassLoader getNewExtensionClassLoader() { + return new ExtensionClassLoader(); + } + + public static boolean isExtensionField(Field field) { + if (field == null) { + return false; + } + String name = field.getName(); + return name.equals(JCLASS_FIELD) || name.equals(INSTANCE_FIELD); + } + + private static native Object _call(long ctx, long id, Object[] args); + + // + /** + * Hook to call a Python implemented method + */ + public static Object call(long ctx, long id, Object... args) throws InstantiationException { + if (ctx == 0) { + StackTraceElement ste = Thread.currentThread().getStackTrace()[2]; + String cls = ste.getClassName(); + if (ste.getMethodName().equals("")) { + throw new InstantiationException(cls + " has been collected"); + } + throw new IllegalStateException(cls + " has been collected"); + } + return _call(ctx, id, args); + } + + // + + /** + * Start a new class declaration. + * + * @param name is the name of the nee class. + * @param bases is a list of the bases for this class containing no more than + * one base class. + * @param ldr the ExtensionClassLoader to use for the class or null for the builtin loader. + * @return a new class declaration. + */ + public static ClassDecl newClass(String name, Class[] bases, ExtensionClassLoader ldr) { + return new ClassDecl(name, bases, ldr); + } + + public static long loadClass(ClassDecl decl) { + decl.internalName = "dynamic/" + decl.name.replace('.', '/'); + try { + String name = decl.internalName.replace('/', '.'); + Class cls = Class.forName(name, false, decl.ldr); + + // Oh joy, someone decided to use importlib.reload. + // While it is a niche edge case, we can and should + // handle it gracefully. + + // To handle this, we get the existing JPClass, release + // the old host Python type and return the old JPClass. + // This allows a new Python type to be created and allows + // the old one float into the dark abyss. + // Whether or not it will see the light of day again + // depends on what references still exist and whoever decided + // to do this. It will most definetely leak something, probably, + // but if you didn't want a leak, then don't do that... + + ClassDescriptor desc = JPypeContext.getInstance().getTypeManager().classMap.get(cls); + ExtensionClassLoader.clearHost(desc.classPtr); + return desc.classPtr; + } catch (ClassNotFoundException e) { + // not yet defined + } + + Class base = null; + List> interfaces = new ArrayList<>(); + + for (Class cls : decl.bases) { + if (cls.isInterface()) { + interfaces.add(cls); + continue; + } + + // There can only be one base + if (base != null) { + throw new RuntimeException("Multiple bases not allowed"); + } + + // Base must not be final + if (Modifier.isFinal(cls.getModifiers())) { + throw new RuntimeException("Cannot extend final class"); + } + + // Select this as the base + base = cls; + } + + if (base == null) { + base = Object.class; + } + + // Write back to the decl for auditing. + decl.setBase(base); + decl.setInterfaces(interfaces); + + // Traverse all the bases to see what methods we are covering + for (Class i : decl.bases) { + for (Method m : i.getMethods()) { + MethodDecl m3 = null; + for (MethodDecl m2 : decl.methods) { + if (m2.matches(m)) { + m3 = m2; + break; + } + } + + if (m3 == null && Modifier.isAbstract(m.getModifiers())) { + throw new RuntimeException("Method " + m + " must be overriden"); + } + + if (m3 != null) { + m3.bind(m); + } + } + } + + byte[] out = buildClass(decl); + try { + OutputStream fs = Files.newOutputStream(Paths.get("test.class")); + fs.write(out); + fs.close(); + } catch (IOException ex) { + Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, null, ex); + } + + try { + String name = decl.internalName.replace('/', '.'); + Class res = decl.ldr.loadClass(name, out); + decl.ldr = null; + for (MethodDecl method : decl.methods) { + // resolve must occur AFTER class creation + method.resolve(); + } + return JPypeContext.getInstance().getTypeManager().findClass(res); + } catch (Exception ex) { + Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, null, ex); + } + + return 0; + } + + static byte[] buildClass(ClassDecl cdecl) { + // Create the class + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cw.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, cdecl.internalName, + null, + Type.getInternalName(cdecl.base), + cdecl.interfaces.stream() + .map(Type::getInternalName) + .toArray(String[]::new)); + + addAnnotations(cw, cdecl); + + // Reserve space for parameter fields + implementFields(cw, cdecl); + + for (MethodDecl mdecl : cdecl.methods) { + if (mdecl.name.equals("")) { + implementCtor(cw, cdecl, mdecl); + } else { + implementMethod(cw, cdecl, mdecl); + } + } + + cw.visitEnd(); + return cw.toByteArray(); + } + + private static void addAnnotations(ClassWriter cw, ClassDecl cdecl) { + for (AnnotationDecl annotation : cdecl.annotations) { + AnnotationVisitor av = cw.visitAnnotation(annotation.getDescriptor(), true); + for (Map.Entry entry : annotation.elements.entrySet()) { + av.visit(entry.getKey(), entry.getValue().value); + } + av.visitEnd(); + } + } + + private static void addAnnotations(FieldVisitor fv, FieldDecl fdecl) { + for (AnnotationDecl annotation : fdecl.annotations) { + AnnotationVisitor av = fv.visitAnnotation(annotation.getDescriptor(), true); + for (Map.Entry entry : annotation.elements.entrySet()) { + av.visit(entry.getKey(), entry.getValue().value); + } + av.visitEnd(); + } + } + + private static void addAnnotations(MethodVisitor mv, MethodDecl mdecl) { + for (AnnotationDecl annotation : mdecl.annotations) { + AnnotationVisitor av = mv.visitAnnotation(annotation.getDescriptor(), true); + for (Map.Entry entry : annotation.elements.entrySet()) { + av.visit(entry.getKey(), entry.getValue().value); + } + av.visitEnd(); + } + for (int i = 0; i < mdecl.parameters.length; i++) { + ParameterDecl param = mdecl.parameters[i]; + addAnnotations(mv, param, i); + } + } + + private static void addAnnotations(MethodVisitor mv, ParameterDecl decl, int index) { + for (AnnotationDecl annotation : decl.annotations) { + AnnotationVisitor av = mv.visitParameterAnnotation( + index, annotation.getDescriptor(), true); + for (Map.Entry entry : annotation.elements.entrySet()) { + av.visit(entry.getKey(), entry.getValue().value); + } + av.visitEnd(); + } + } + + + // + private static void implementFields(ClassWriter cw, ClassDecl decl) { + // create a static private field to hold the pointer to our JPClass + cw.visitField( + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC, + JCLASS_FIELD, + "J", + null, + null + ); + + if (!isExtension(decl.base)) { + // create a private field to hold the pointer to our Python object + cw.visitField( + Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC, + INSTANCE_FIELD, + "J", + null, + null + ); + } + + // Implement fields + for (FieldDecl fdecl : decl.fields) { + FieldVisitor fv = cw.visitField( + fdecl.modifiers, fdecl.name, Type.getDescriptor(fdecl.type), null, fdecl.value); + addAnnotations(fv, fdecl); + fv.visitEnd(); + } + + // Initialize the parameter lists + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); + mv.visitCode(); + String context = CONTEXT_TYPE.getInternalName(); + Type type = decl.getType(); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, context, "getInstance", "()Lorg/jpype/JPypeContext;", false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, context, "getTypeManager", "()Lorg/jpype/manager/TypeManager;", false); + mv.visitLdcInsn(type); + mv.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + TYPE_MANAGER_TYPE.getInternalName(), + "findClass", + "(Ljava/lang/Class;)J", + false + ); + mv.visitFieldInsn(Opcodes.PUTSTATIC, type.getInternalName(), JCLASS_FIELD, "J"); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + + private static void implementCtor(ClassWriter cw, ClassDecl cdecl, MethodDecl mdecl) { + // Copy over exceptions + String[] exceptions = null; + if (mdecl.exceptions != null) { + exceptions = new String[mdecl.exceptions.length]; + for (int i = 0; i < mdecl.exceptions.length; ++i) { + exceptions[i] = Type.getInternalName(mdecl.exceptions[i]); + } + } + + // Start a new method + MethodVisitor mv = + cw.visitMethod(mdecl.modifiers, mdecl.name, mdecl.descriptor(), null, exceptions); + + addAnnotations(mv, mdecl); + + mv.visitCode(); + + // forward parameters + mv.visitVarInsn(Opcodes.ALOAD, 0); + for (ParameterDecl param : mdecl.parameters) { + mv.visitVarInsn(param.kind.load, param.slot); + } + + // call super + mv.visitMethodInsn( + Opcodes.INVOKESPECIAL, + Type.getInternalName(cdecl.base), + "", + mdecl.descriptor(), + false + ); + + callPython(mv, cdecl, mdecl); + } + + private static void implementMethod(ClassWriter cw, ClassDecl cdecl, MethodDecl mdecl) { + // Copy over exceptions + String[] exceptions = null; + if (mdecl.exceptions != null) { + exceptions = new String[mdecl.exceptions.length]; + for (int i = 0; i < mdecl.exceptions.length; ++i) { + exceptions[i] = Type.getInternalName(mdecl.exceptions[i]); + } + } + + // Start a new method + MethodVisitor mv = + cw.visitMethod(mdecl.modifiers, mdecl.name, mdecl.descriptor(), null, exceptions); + + addAnnotations(mv, mdecl); + + // Start the implementation + mv.visitCode(); + + callPython(mv, cdecl, mdecl); + } + // + + private static void callPython(MethodVisitor mv, ClassDecl cdecl, MethodDecl mdecl) { + // Object _call(long ctx, long id, Object[] args); + // Place the interpretation information on the stack + mv.visitFieldInsn(Opcodes.GETSTATIC, cdecl.internalName, JCLASS_FIELD, "J"); + mv.visitLdcInsn(mdecl.id); + + // Create the parameter array + mv.visitLdcInsn(mdecl.parameters.length + 1); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + mv.visitInsn(Opcodes.DUP); // two copies of the array reference + mv.visitInsn(Opcodes.ICONST_0); + if (mdecl.isStatic()) { + mv.visitLdcInsn(cdecl.getType()); + } else { + mv.visitIntInsn(Opcodes.ALOAD, 0); + } + mv.visitInsn(Opcodes.AASTORE); + + // Marshal the parameters + for (int i = 0; i < mdecl.parameters.length; i++) { + mv.visitInsn(Opcodes.DUP); + loadConst(mv, i+1); + load(mv, mdecl.parameters[i]); + mv.visitInsn(Opcodes.AASTORE); + } + + // Call the hook in native + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + FACTORY_TYPE.getInternalName(), + "call", + CALL_DESCRIPTOR, + false + ); + + // Process the return + handleReturn(mv, mdecl.ret); + + // fix the stack + mv.visitMaxs(1, 1); + + // Close the method + mv.visitEnd(); + } + + private static void loadConst(MethodVisitor mv, int value) { + switch (value) { + case 0: + mv.visitInsn(Opcodes.ICONST_0); + break; + case 1: + mv.visitInsn(Opcodes.ICONST_1); + break; + case 2: + mv.visitInsn(Opcodes.ICONST_2); + break; + case 3: + mv.visitInsn(Opcodes.ICONST_3); + break; + case 4: + mv.visitInsn(Opcodes.ICONST_4); + break; + case 5: + mv.visitInsn(Opcodes.ICONST_5); + break; + default: + mv.visitLdcInsn(value); + break; + } + } + + private static void load(MethodVisitor mv, ParameterDecl param) { + if (param.kind == TypeKind.OBJECT) { + mv.visitIntInsn(Opcodes.ALOAD, param.slot); + } else { + mv.visitIntInsn(param.kind.load, param.slot); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + param.kind.boxedClass, + "valueOf", + param.kind.boxDescriptor, + false + ); + } + } + + private static void handleReturn(MethodVisitor mv, ParameterDecl ret) { + String name; + String desc; + int op; + + switch (ret.kind) { + case OBJECT: + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(ret.type)); + mv.visitInsn(Opcodes.ARETURN); + return; + case VOID: + mv.visitInsn(Opcodes.RETURN); + return; + case BOOL: + name = "booleanValue"; + desc = "()Z"; + op = Opcodes.IRETURN; + break; + case BYTE: + name = "byteValue"; + desc = "()B"; + op = Opcodes.IRETURN; + break; + case CHAR: + name = "charValue"; + desc = "()C"; + op = Opcodes.IRETURN; + break; + case SHORT: + name = "shortValue"; + desc = "()S"; + op = Opcodes.IRETURN; + break; + case INT: + name = "intValue"; + desc = "()I"; + op = Opcodes.IRETURN; + break; + case LONG: + name = "longValue"; + desc = "()L"; + op = Opcodes.LRETURN; + break; + case FLOAT: + name = "floatValue"; + desc = "()F"; + op = Opcodes.FRETURN; + break; + case DOUBLE: + name = "doubleValue"; + desc = "()D"; + op = Opcodes.DRETURN; + break; + default: + // without the default the compiler thinks some locals are uninitialized + throw new RuntimeException(); + } + + mv.visitTypeInsn(Opcodes.CHECKCAST, ret.kind.boxedClass); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ret.kind.boxedClass, name, desc, false); + mv.visitInsn(op); + } +} diff --git a/native/java/org/jpype/extension/FieldDecl.java b/native/java/org/jpype/extension/FieldDecl.java new file mode 100644 index 000000000..bc7fe9e17 --- /dev/null +++ b/native/java/org/jpype/extension/FieldDecl.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.extension; + +/** + * + * @author nelson85 + */ +public final class FieldDecl extends JavaDecl +{ + + final Class type; + final String name; + final Object value; + final int modifiers; + + public FieldDecl(Class type, String name, Object value, int modifiers) + { + this.type = type; + this.name = name; + this.value = value; + this.modifiers = modifiers; + } +} diff --git a/native/java/org/jpype/extension/JavaDecl.java b/native/java/org/jpype/extension/JavaDecl.java new file mode 100644 index 000000000..a7f504e3a --- /dev/null +++ b/native/java/org/jpype/extension/JavaDecl.java @@ -0,0 +1,12 @@ +package org.jpype.extension; + +import java.util.Collections; +import java.util.List; + +abstract class JavaDecl { + List annotations = Collections.emptyList(); + + public void setAnnotations(List annotations) { + this.annotations = annotations; + } +} diff --git a/native/java/org/jpype/extension/MethodDecl.java b/native/java/org/jpype/extension/MethodDecl.java new file mode 100644 index 000000000..913f39737 --- /dev/null +++ b/native/java/org/jpype/extension/MethodDecl.java @@ -0,0 +1,124 @@ +/** *************************************************************************** + * 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.extension; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.jpype.JPypeContext; +import org.jpype.asm.Type; +import org.jpype.manager.TypeManager; + +/** + * + * @author nelson85 + */ +public final class MethodDecl extends JavaDecl { + + public final String name; + public final ParameterDecl ret; + public final ParameterDecl[] parameters; + public final Class[] exceptions; + public final int modifiers; + public Method method; + public long retId; + public long[] parametersId; + public String parametersName; + public final long id; + + public MethodDecl(String name, Class ret, Class[] params, String[] paramNames, Class[] exc, int mods, int id) { + this.name = name; + if (ret == null) { + ret = Void.TYPE; + } + this.ret = new ParameterDecl(ret, "", -1); + int slot = Modifier.isStatic(mods) ? 0 : 1; + this.parameters = new ParameterDecl[params.length]; + for (int i = 0; i < params.length; i++) { + ParameterDecl param = new ParameterDecl(params[i], paramNames[i], slot); + this.parameters[i] = param; + slot = param.getNextSlot(); + } + this.exceptions = exc; + this.modifiers = mods; + this.id = id; + } + + boolean matches(Method m) { + if (!m.getName().equals(name)) { + return false; + } + + Class[] param2 = m.getParameterTypes(); + if (param2.length != parameters.length) { + return false; + } + + for (int i = 0; i < param2.length; i++) { + if (param2[i] != parameters[i].type) { + return false; + } + } + + // The JVM does not care about exceptions at runtime. + // They still need to be added to the method declaration in case the user + // has a situation where it's needed for some framework. + + if (ret.kind == TypeKind.OBJECT) { + return m.getReturnType().isAssignableFrom(ret.type); + } + + return ret.type == m.getReturnType(); + } + + void bind(Method m) { + this.method = m; + } + + void resolve() { + TypeManager typemanager = JPypeContext.getInstance().getTypeManager(); + retId = typemanager.findClass(ret.type); + this.parametersId = new long[this.parameters.length]; + for (int i = 0; i < this.parameters.length; ++i) { + this.parametersId[i] = typemanager.findClass(parameters[i].type); + } + } + + String descriptor() { + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (ParameterDecl param : this.parameters) { + sb.append(Type.getDescriptor(param.type)); + } + sb.append(')'); + sb.append(Type.getDescriptor(ret.type)); + return sb.toString(); + } + + boolean isStatic() { + return Modifier.isStatic(modifiers); + } + + public ParameterDecl getParameter(String name) { + for (ParameterDecl decl : parameters) { + if (decl.name.equals(name)) { + return decl; + } + } + return null; + } + +} diff --git a/native/java/org/jpype/extension/ParameterDecl.java b/native/java/org/jpype/extension/ParameterDecl.java new file mode 100644 index 000000000..0e40c3307 --- /dev/null +++ b/native/java/org/jpype/extension/ParameterDecl.java @@ -0,0 +1,23 @@ +package org.jpype.extension; + +final class ParameterDecl extends JavaDecl { + final Class type; + final TypeKind kind; + final int slot; + final String name; + long id; + + ParameterDecl(Class type, String name, int slot) { + this.type = type; + this.kind = TypeKind.of(type); + this.slot = slot; + this.name = name; + } + + int getNextSlot() { + if (kind == TypeKind.LONG || kind == TypeKind.DOUBLE) { + return slot + 2; + } + return slot + 1; + } +} diff --git a/native/java/org/jpype/extension/TypeKind.java b/native/java/org/jpype/extension/TypeKind.java new file mode 100644 index 000000000..3f93078fe --- /dev/null +++ b/native/java/org/jpype/extension/TypeKind.java @@ -0,0 +1,60 @@ +package org.jpype.extension; + +import org.jpype.asm.Opcodes; + +enum TypeKind { + BOOL(Opcodes.ILOAD, "java/lang/Boolean", "(Z)Ljava/lang/Boolean;"), + BYTE(Opcodes.ILOAD, "java/lang/Byte", "(B)Ljava/lang/Byte;"), + CHAR(Opcodes.ILOAD, "java/lang/Character", "(C)Ljava/lang/Character;"), + SHORT(Opcodes.ILOAD, "java/lang/Short", "(S)Ljava/lang/Short;"), + INT(Opcodes.ILOAD, "java/lang/Integer", "(I)Ljava/lang/Integer;"), + LONG(Opcodes.LLOAD, "java/lang/Long", "(J)Ljava/lang/Long;"), + FLOAT(Opcodes.FLOAD, "java/lang/Float", "(F)Ljava/lang/Float;"), + DOUBLE(Opcodes.DLOAD, "java/lang/Double", "(D)Ljava/lang/Double;"), + VOID(-1, "java/lang/Void", null), + OBJECT(Opcodes.ALOAD, null, null); + + final int load; + final String boxedClass; + final String boxDescriptor; + + TypeKind(int load, String boxedClass, String boxDescriptor) { + this.load = load; + this.boxedClass = boxedClass; + this.boxDescriptor = boxDescriptor; + } + + static TypeKind of(Class type) { + if (type == null || type == Void.TYPE) { + return VOID; + } + if (!type.isPrimitive()) { + return OBJECT; + } + if (type == Boolean.TYPE) { + return BOOL; + } + if (type == Byte.TYPE) { + return BYTE; + } + if (type == Character.TYPE) { + return CHAR; + } + if (type == Short.TYPE) { + return SHORT; + } + if (type == Integer.TYPE) { + return INT; + } + if (type == Long.TYPE) { + return LONG; + } + if (type == Float.TYPE) { + return FLOAT; + } + if (type == Double.TYPE) { + return DOUBLE; + } + throw new IllegalArgumentException("impossible type " + type.toString()); + } +} diff --git a/native/java/org/jpype/html/AttrGrammar.java b/native/java/org/jpype/html/AttrGrammar.java index dc331eb47..bede7672d 100644 --- a/native/java/org/jpype/html/AttrGrammar.java +++ b/native/java/org/jpype/html/AttrGrammar.java @@ -19,6 +19,7 @@ import org.jpype.html.Parser.Rule; import org.w3c.dom.Attr; +@SuppressWarnings({"rawtypes", "unchecked"}) public class AttrGrammar implements Parser.Grammar { @@ -216,7 +217,7 @@ static class AttrRule extends Parser.MatchRule public void execute(Parser parser) { Entity e2 = (Entity) parser.stack.removeLast(); - Entity e1 = (Entity) parser.stack.removeLast(); + parser.stack.removeLast(); Entity e0 = (Entity) parser.stack.removeLast(); AttrParser aparser = (AttrParser) parser; Attr attr = aparser.doc.createAttribute((String) e0.value); diff --git a/native/java/org/jpype/html/HtmlGrammar.java b/native/java/org/jpype/html/HtmlGrammar.java index edefdf664..0db4e5c6f 100644 --- a/native/java/org/jpype/html/HtmlGrammar.java +++ b/native/java/org/jpype/html/HtmlGrammar.java @@ -10,7 +10,7 @@ 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.html; @@ -19,6 +19,7 @@ import org.jpype.html.Parser.Entity; import org.jpype.html.Parser.Rule; +@SuppressWarnings({"rawtypes", "unchecked"}) public class HtmlGrammar implements Parser.Grammar { @@ -413,9 +414,9 @@ static class EndElement extends Parser.MatchRule public void execute(Parser parser) { LinkedList stack = parser.stack; - Entity e2 = stack.removeLast(); + stack.removeLast(); Entity e1 = stack.removeLast(); - Entity e0 = stack.removeLast(); + stack.removeLast(); String content = e1.value.toString(); getGrammar(parser).flushText(parser); getHandler(parser).endElement(content); @@ -435,9 +436,9 @@ static class EndDirective extends Parser.MatchRule public void execute(Parser parser) { LinkedList stack = parser.stack; - Entity e2 = stack.removeLast(); + stack.removeLast(); Entity e1 = stack.removeLast(); - Entity e0 = stack.removeLast(); + stack.removeLast(); String content = e1.value.toString(); getGrammar(parser).flushText(parser); getHandler(parser).directive(content); diff --git a/native/java/org/jpype/html/HtmlTreeHandler.java b/native/java/org/jpype/html/HtmlTreeHandler.java index 264c718ed..1d5713ce9 100644 --- a/native/java/org/jpype/html/HtmlTreeHandler.java +++ b/native/java/org/jpype/html/HtmlTreeHandler.java @@ -10,7 +10,7 @@ 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.html; @@ -63,7 +63,6 @@ private String lastNodeName() public void startElement(String name, String attr) { name = name.toLowerCase().trim(); - String attr0 = attr; // Html has irregular end rules. while (Html.OPTIONAL_ELEMENTS.contains(name)) diff --git a/native/java/org/jpype/html/HtmlWriter.java b/native/java/org/jpype/html/HtmlWriter.java index 52471b813..04e788b96 100644 --- a/native/java/org/jpype/html/HtmlWriter.java +++ b/native/java/org/jpype/html/HtmlWriter.java @@ -10,7 +10,7 @@ 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.html; @@ -24,7 +24,6 @@ import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; -import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; diff --git a/native/java/org/jpype/html/Parser.java b/native/java/org/jpype/html/Parser.java index 620a28211..6ca82b11a 100644 --- a/native/java/org/jpype/html/Parser.java +++ b/native/java/org/jpype/html/Parser.java @@ -10,7 +10,7 @@ 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.html; @@ -28,6 +28,7 @@ * * @param */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class Parser { @@ -57,7 +58,6 @@ public T parse(InputStream is) int rc = channel.read(incoming); if (rc < 0) break; - int p = incoming.position(); incoming.rewind(); process(incoming, outgoing, rc); } diff --git a/native/java/org/jpype/javadoc/JavadocExtractor.java b/native/java/org/jpype/javadoc/JavadocExtractor.java index 7e00b2e45..d41e7cbc0 100644 --- a/native/java/org/jpype/javadoc/JavadocExtractor.java +++ b/native/java/org/jpype/javadoc/JavadocExtractor.java @@ -10,7 +10,7 @@ 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.javadoc; @@ -46,7 +46,7 @@ public class JavadocExtractor * @param cls * @return */ - public static Javadoc getDocumentation(Class cls) + public static Javadoc getDocumentation(Class cls) { try { @@ -67,7 +67,7 @@ public static Javadoc getDocumentation(Class cls) return null; } - public static InputStream getDocumentationAsStream(Class cls) + public static InputStream getDocumentationAsStream(Class cls) { InputStream is = null; String name = cls.getName().replace('.', '/') + ".html"; @@ -108,7 +108,7 @@ public static InputStream getDocumentationAsStream(Class cls) * @param doc is the DOM holding the javadoc. * @return */ - public static Javadoc extractDocument(Class cls, Document doc) + public static Javadoc extractDocument(Class cls, Document doc) { JavadocRenderer renderer = new JavadocRenderer(); try diff --git a/native/java/org/jpype/javadoc/JavadocTransformer.java b/native/java/org/jpype/javadoc/JavadocTransformer.java index 249b8c03c..ec0363c2e 100644 --- a/native/java/org/jpype/javadoc/JavadocTransformer.java +++ b/native/java/org/jpype/javadoc/JavadocTransformer.java @@ -10,7 +10,7 @@ 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.javadoc; @@ -41,7 +41,7 @@ public class JavadocTransformer final static Pattern ARGS_PATTERN = Pattern.compile(".*\\((.*)\\).*"); - public Node transformDescription(Class cls, Node node) + public Node transformDescription(Class cls, Node node) { try { @@ -64,7 +64,7 @@ public Node transformDescription(Class cls, Node node) * * @param node */ - public Node transformMember(Class cls, Node node) + public Node transformMember(Class cls, Node node) { try { @@ -252,13 +252,13 @@ void handleDetails(Node node, Workspace ws) static class Workspace { - private final Class cls; + private final Class cls; boolean hr = false; String key; Node section; private LinkedList types; - Workspace(Class cls) + Workspace(Class cls) { this.cls = cls; } diff --git a/native/java/org/jpype/manager/ClassDescriptor.java b/native/java/org/jpype/manager/ClassDescriptor.java index b03baf784..a8667d7b6 100644 --- a/native/java/org/jpype/manager/ClassDescriptor.java +++ b/native/java/org/jpype/manager/ClassDescriptor.java @@ -46,12 +46,14 @@ public class ClassDescriptor public int methodCounter = 0; public long[] fields; public long anonymous; - public int functional_interface_parameter_count; + public int functional_interface_parameter_count; + private boolean extension; - ClassDescriptor(Class cls, long classPtr, Method method) + ClassDescriptor(Class cls, long classPtr, Method method, boolean isExtensionBase) { this.cls = cls; this.classPtr = classPtr; + this.extension = isExtensionBase; if (this.classPtr == 0) throw new NullPointerException("Class pointer is null for " + cls); if (method != null) @@ -68,4 +70,8 @@ long getMethod(Method requestedMethod) return 0; } + + public boolean isExtensionBase() { + return extension; + } } diff --git a/native/java/org/jpype/manager/MethodResolution.java b/native/java/org/jpype/manager/MethodResolution.java index 7e6b3f8c3..be732a54b 100644 --- a/native/java/org/jpype/manager/MethodResolution.java +++ b/native/java/org/jpype/manager/MethodResolution.java @@ -16,6 +16,7 @@ package org.jpype.manager; import java.lang.reflect.Executable; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; @@ -34,254 +35,263 @@ * * @author nelson85 */ -public class MethodResolution -{ - - long ptr = 0; - boolean covered = false; - Executable executable; - List children = new ArrayList<>(); - - MethodResolution(Executable method) - { - this.executable = method; - } - - private boolean isCovered() - { - for (MethodResolution ov : this.children) - { - if (!ov.covered) - return false; - } - covered = true; - return true; - } - - /** - * Order methods from least to most specific. - * - * @param - * @param methods - * @return - */ - public static - List sortMethods(List methods) - { - // Create a method resolution for each method - LinkedList unsorted = new LinkedList<>(); - for (T m1 : methods) - { - unsorted.add(new MethodResolution(m1)); - } - - for (MethodResolution m1 : unsorted) - { - for (MethodResolution m2 : unsorted) - { - if (m1 == m2) - continue; - - if (isMoreSpecificThan(m1.executable, m2.executable) - && !isMoreSpecificThan(m2.executable, m1.executable)) - { - m1.children.add(m2); - } - } - } - - // Execute a graph sort problem so that the most specific are always on the front - LinkedList out = new LinkedList<>(); - while (!unsorted.isEmpty()) - { - // Remove the first unsorted element - MethodResolution front = unsorted.pop(); - // Check to see if all dependencies are already ordered - boolean good = front.isCovered(); - - // If all dependencies are included - if (good) - { - front.covered = true; - out.add(front); - } else - { - unsorted.add(front); - } - } - return out; - } - - // Table for primitive rules - static Class[] of(Class... l) - { - return l; - } - static HashMap CONVERSION = new HashMap<>(); - - - { - CONVERSION.put(Byte.TYPE, - of(Byte.TYPE, Byte.class, Short.TYPE, Short.class, - Integer.TYPE, Integer.class, Long.TYPE, Long.class, - Float.TYPE, Float.class, Double.TYPE, Double.class)); - CONVERSION.put(Character.TYPE, - of(Character.TYPE, Character.class, Short.TYPE, Short.class, - Integer.TYPE, Integer.class, Long.TYPE, Long.class, - Float.TYPE, Float.class, Double.TYPE, Double.class)); - CONVERSION.put(Short.TYPE, - of(Short.TYPE, Short.class, - Integer.TYPE, Integer.class, Long.TYPE, Long.class, - Float.TYPE, Float.class, Double.TYPE, Double.class)); - CONVERSION.put(Integer.TYPE, - of(Integer.TYPE, Integer.class, Long.TYPE, Long.class, - Float.TYPE, Float.class, Double.TYPE, Double.class)); - CONVERSION.put(Long.TYPE, - of(Long.TYPE, Long.class, - Float.TYPE, Float.class, Double.TYPE, Double.class)); - CONVERSION.put(Float.TYPE, - of(Float.TYPE, Float.class, Double.TYPE, Double.class)); - CONVERSION.put(Double.TYPE, - of(Double.TYPE, Double.class)); - CONVERSION.put(Boolean.TYPE, - of(Boolean.TYPE, Boolean.class)); - } - - static boolean isAssignableTo(Class c1, Class c2) - { - if (!c1.isPrimitive()) - return c2.isAssignableFrom(c1); - Class[] cl = CONVERSION.get(c1); - if (cl == null) - return false; - for (Class c3 : cl) - if (c2.equals(c3)) - return true; - return false; - } - - /** - * Determine is a executable is more specific than another. - *

- * This is public so that we can debug from within jpype. - * - * @param method1 - * @param method2 - * @return - */ - public static boolean isMoreSpecificThan(Executable method1, Executable method2) - { - List> param1 = new ArrayList<>(Arrays.asList(method1.getParameterTypes())); - List> param2 = new ArrayList<>(Arrays.asList(method2.getParameterTypes())); - - if (!Modifier.isStatic(method1.getModifiers())) - param1.add(0, method1.getDeclaringClass()); - if (!Modifier.isStatic(method2.getModifiers())) - param2.add(0, method2.getDeclaringClass()); - - // Special handling is needed for varargs as it may chop or expand. - // we have 4 cases for a varargs methods - // foo(Arg0, Arg1...) as - // foo(Arg0) - // foo(Arg0, Arg1) - // foo(Arg0, Arg1[]) - // foo(Arg0, Arg1, Arg1+) - if (method1.isVarArgs() && method2.isVarArgs()) - { - // Punt on this as there are too many different cases - return isMoreSpecificThan(param1, param2); - } - - if (method1.isVarArgs()) - { - int n1 = param1.size(); - int n2 = param2.size(); - - // Last element is an array - Class cls = param1.get(n1 - 1); - Class cls2 = cls.getComponentType(); - - // Less arguments, chop the list - if (n1 - 1 == n2) - return isMoreSpecificThan(param1.subList(0, n2), param2); - - // Same arguments - if (n1 == n2) - { - List> q = new ArrayList<>(param1); - q.set(n1 - 1, cls2); - - // Check both ways - boolean isMoreSpecific = isMoreSpecificThan(param1, param2) || isMoreSpecificThan(q, param2); - - // If the varargs array is of the single-variable's type (or they are primitive-equivalent), - // the single-variable signature should win specificity - Class svCls = param2.get(n2-1); - return isMoreSpecific && !(isAssignableTo(cls2, svCls) && isAssignableTo(svCls, cls2)); - } - - // More arguments - if (n1 < n2) - { - // Grow the list - List> q = new ArrayList<>(param1); - q.set(n1 - 1, cls2); - for (int i = n1; i < n2; ++i) - q.add(cls2); - return isMoreSpecificThan(q, param2); - } - } - - if (method2.isVarArgs()) - { - int n1 = param1.size(); - int n2 = param2.size(); - - // Last element is an array - Class cls = param2.get(n2 - 1); - Class cls2 = cls.getComponentType(); - - // Less arguments, chop the list - if (n2 - 1 == n1) - return isMoreSpecificThan(param1, param2.subList(0, n2)); - - // Same arguments - if (n1 == n2) - { - List> q = new ArrayList<>(param2); - q.set(n2 - 1, cls2); - - // Compare both ways - return isMoreSpecificThan(param1, param2) || isMoreSpecificThan(param1, q); - } - - // More arguments - if (n2 < n1) - { - // Grow the list - List> q = new ArrayList<>(param2); - q.set(n2 - 1, cls2); - for (int i = n2; i < n1; ++i) - q.add(cls2); - return isMoreSpecificThan(param1, q); - } - } - - return isMoreSpecificThan(param1, param2); - } - - public static boolean isMoreSpecificThan(List> param1, List> param2) - { - // FIXME need to consider resolving mixing of static and non-static - // Methods here. - if (param1.size() != param2.size()) - return false; - - for (int i = 0; i < param1.size(); ++i) - { - if (!isAssignableTo(param1.get(i), param2.get(i))) - return false; - } - return true; - } +public class MethodResolution { + + long ptr = 0; + boolean covered = false; + Executable executable; + List children = new ArrayList<>(); + + MethodResolution(Executable method) { + this.executable = method; + } + + private boolean isCovered() { + for (MethodResolution ov : this.children) { + if (!ov.covered) { + return false; + } + } + covered = true; + return true; + } + + /** + * Order methods from least to most specific. + * + * @param + * @param methods + * @return + */ + public static List sortMethods(List methods) { + // Create a method resolution for each method + LinkedList unsorted = new LinkedList<>(); + for (T m1 : methods) { + unsorted.add(new MethodResolution(m1)); + } + + for (MethodResolution m1 : unsorted) { + for (MethodResolution m2 : unsorted) { + if (m1 == m2) { + continue; + } + + if (isMoreSpecificThan(m1.executable, m2.executable) && + !isMoreSpecificThan(m2.executable, m1.executable)) { + m1.children.add(m2); + } + } + } + + // Execute a graph sort problem so that the most specific are always on the front + LinkedList out = new LinkedList<>(); + while (!unsorted.isEmpty()) { + // Remove the first unsorted element + MethodResolution front = unsorted.pop(); + // Check to see if all dependencies are already ordered + boolean good = front.isCovered(); + + // If all dependencies are included + if (good) { + front.covered = true; + out.add(front); + } else { + unsorted.add(front); + } + } + return out; + } + + // Table for primitive rules + static Class[] of(Class... l) { + return l; + } + + static HashMap, Class[]> CONVERSION = new HashMap<>(); + + { + CONVERSION.put(Byte.TYPE, + of(Byte.TYPE, Byte.class, Short.TYPE, Short.class, + Integer.TYPE, Integer.class, Long.TYPE, Long.class, + Float.TYPE, Float.class, Double.TYPE, Double.class)); + CONVERSION.put(Character.TYPE, + of(Character.TYPE, Character.class, Short.TYPE, Short.class, + Integer.TYPE, Integer.class, Long.TYPE, Long.class, + Float.TYPE, Float.class, Double.TYPE, Double.class)); + CONVERSION.put(Short.TYPE, + of(Short.TYPE, Short.class, + Integer.TYPE, Integer.class, Long.TYPE, Long.class, + Float.TYPE, Float.class, Double.TYPE, Double.class)); + CONVERSION.put(Integer.TYPE, + of(Integer.TYPE, Integer.class, Long.TYPE, Long.class, + Float.TYPE, Float.class, Double.TYPE, Double.class)); + CONVERSION.put(Long.TYPE, + of(Long.TYPE, Long.class, + Float.TYPE, Float.class, Double.TYPE, Double.class)); + CONVERSION.put(Float.TYPE, + of(Float.TYPE, Float.class, Double.TYPE, Double.class)); + CONVERSION.put(Double.TYPE, + of(Double.TYPE, Double.class)); + CONVERSION.put(Boolean.TYPE, + of(Boolean.TYPE, Boolean.class)); + } + + static boolean isAssignableTo(Class c1, Class c2) { + if (!c1.isPrimitive()) { + return c2.isAssignableFrom(c1); + } + + Class[] cl = CONVERSION.get(c1); + if (cl == null) { + return c1 == c2; + } + + for (Class c3 : cl) { + if (c2.equals(c3)) { + return true; + } + } + return false; + } + + /** + * Determine is a executable is more specific than another. + *

+ * This is public so that we can debug from within jpype. + * + * @param method1 + * @param method2 + * @return + */ + public static boolean isMoreSpecificThan(Executable method1, Executable method2) { + List> param1 = new ArrayList<>(Arrays.asList(method1.getParameterTypes())); + List> param2 = new ArrayList<>(Arrays.asList(method2.getParameterTypes())); + + Class res1 = null; + Class res2 = null; + if (method1 instanceof Method) { + res1 = ((Method) method1).getReturnType(); + } else { + res1 = Void.TYPE; + } + + if (method2 instanceof Method) { + res2 = ((Method) method2).getReturnType(); + } else { + res2 = Void.TYPE; + } + + if (!Modifier.isStatic(method1.getModifiers())) { + param1.add(0, method1.getDeclaringClass()); + } + if (!Modifier.isStatic(method2.getModifiers())) { + param2.add(0, method2.getDeclaringClass()); + } + + // Special handling is needed for varargs as it may chop or expand. + // we have 4 cases for a varargs methods + // foo(Arg0, Arg1...) as + // foo(Arg0) + // foo(Arg0, Arg1) + // foo(Arg0, Arg1[]) + // foo(Arg0, Arg1, Arg1+) + if (method1.isVarArgs() && method2.isVarArgs()) { + // Punt on this as there are too many different cases + return isMoreSpecificThan(param1, param2, res1, res2); + } + + if (method1.isVarArgs()) { + int n1 = param1.size(); + int n2 = param2.size(); + + // Last element is an array + Class cls = param1.get(n1 - 1); + Class cls2 = cls.getComponentType(); + + // Less arguments, chop the list + if (n1 - 1 == n2) { + return isMoreSpecificThan(param1.subList(0, n2), param2, res1, res2); + } + + // Same arguments + if (n1 == n2) { + List> q = new ArrayList<>(param1); + q.set(n1 - 1, cls2); + + // Check both ways + boolean isMoreSpecific = + isMoreSpecificThan(param1, param2, res1, res2) || + isMoreSpecificThan(q, param2, res1, res2); + + // If the varargs array is of the single-variable's type (or they are primitive-equivalent), + // the single-variable signature should win specificity + Class svCls = param2.get(n2 - 1); + return isMoreSpecific && + !(isAssignableTo(cls2, svCls) && isAssignableTo(svCls, cls2)); + } + + // More arguments + if (n1 < n2) { + // Grow the list + List> q = new ArrayList<>(param1); + q.set(n1 - 1, cls2); + for (int i = n1; i < n2; ++i) + q.add(cls2); + return isMoreSpecificThan(q, param2, res1, res2); + } + } + + if (method2.isVarArgs()) { + int n1 = param1.size(); + int n2 = param2.size(); + + // Last element is an array + Class cls = param2.get(n2 - 1); + Class cls2 = cls.getComponentType(); + + // Less arguments, chop the list + if (n2 - 1 == n1) { + return isMoreSpecificThan(param1, param2.subList(0, n2), res1, res2); + } + + // Same arguments + if (n1 == n2) { + List> q = new ArrayList<>(param2); + q.set(n2 - 1, cls2); + + // Compare both ways + return isMoreSpecificThan(param1, param2, res1, res2) || + isMoreSpecificThan(param1, q, res1, res2); + } + + // More arguments + if (n2 < n1) { + // Grow the list + List> q = new ArrayList<>(param2); + q.set(n2 - 1, cls2); + for (int i = n2; i < n1; ++i) { + q.add(cls2); + } + return isMoreSpecificThan(param1, q, res1, res2); + } + } + + return isMoreSpecificThan(param1, param2, res1, res2); + } + + public static boolean isMoreSpecificThan(List> param1, List> param2, + Class res1, Class res2) { + // FIXME need to consider resolving mixing of static and non-static + // Methods here. + if (param1.size() != param2.size()) { + return false; + } + + for (int i = 0; i < param1.size(); ++i) { + if (!isAssignableTo(param1.get(i), param2.get(i))) { + return false; + } + } + return isAssignableTo(res1, res2); + } } diff --git a/native/java/org/jpype/manager/ModifierCode.java b/native/java/org/jpype/manager/ModifierCode.java index aa0c9cb27..d515281e2 100644 --- a/native/java/org/jpype/manager/ModifierCode.java +++ b/native/java/org/jpype/manager/ModifierCode.java @@ -51,17 +51,19 @@ public enum ModifierCode BUFFER(0x01000000), CTOR(0x10000000), BEAN_ACCESSOR(0x20000000), - BEAN_MUTATOR(0x40000000); - final public int value; + BEAN_MUTATOR(0x40000000), + EXTENSION(0x80000000L), + EXTENSION_BASE(0x100000000L); + final public long value; - ModifierCode(int value) + ModifierCode(long value) { this.value = value; } - public static int get(EnumSet set) + public static long get(EnumSet set) { - int out = 0; + long out = 0; for (ModifierCode m : set) { out |= m.value; diff --git a/native/java/org/jpype/manager/TypeFactory.java b/native/java/org/jpype/manager/TypeFactory.java index b43fd719a..8661bd7a8 100644 --- a/native/java/org/jpype/manager/TypeFactory.java +++ b/native/java/org/jpype/manager/TypeFactory.java @@ -10,7 +10,7 @@ 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; @@ -54,7 +54,7 @@ public interface TypeFactory */ long defineArrayClass( long context, - Class cls, + Class cls, String name, long superClass, long componentPtr, @@ -73,11 +73,11 @@ long defineArrayClass( */ long defineObjectClass( long context, - Class cls, + Class cls, String name, long superClass, long[] interfaces, - int modifiers); + long modifiers); /** * Define a primitive types. @@ -91,7 +91,7 @@ long defineObjectClass( long definePrimitive( long context, String name, - Class cls, + Class cls, long boxedPtr, int modifiers); @@ -141,6 +141,7 @@ long defineField( * @param name * @param method is the Java method that will be called, converts to a method * id. + * @param declaringClass * @param overloadList * @param modifiers * @return the pointer to the JPMethod. @@ -150,6 +151,7 @@ long defineMethod( long cls, String name, Executable method, + Class declaringClass, long[] overloadList, int modifiers); @@ -189,4 +191,15 @@ void destroy( long context, long[] resources, int sz); // + + /** + * Delete the resources, now. + * + * @param context JPContext object + * @param resources the resources to delete + * @param sz the length of the resources + */ + void delete( + long context, + long[] resources, int sz); } diff --git a/native/java/org/jpype/manager/TypeFactoryNative.java b/native/java/org/jpype/manager/TypeFactoryNative.java index 38804e539..34dc25b50 100644 --- a/native/java/org/jpype/manager/TypeFactoryNative.java +++ b/native/java/org/jpype/manager/TypeFactoryNative.java @@ -29,12 +29,13 @@ public class TypeFactoryNative implements TypeFactory public long context; - public native void newWrapper(long context, long cls); + @Override +public native void newWrapper(long context, long cls); @Override public native long defineArrayClass( long context, - Class cls, + Class cls, String name, long superClass, long componentPtr, @@ -43,17 +44,17 @@ public native long defineArrayClass( @Override public native long defineObjectClass( long context, - Class cls, + Class cls, String name, long superClass, long[] interfaces, - int modifiers); + long modifiers); @Override public native long definePrimitive( long context, String name, - Class cls, + Class cls, long boxedPtr, int modifiers); @@ -80,6 +81,7 @@ public native long defineMethod( long cls, String name, Executable method, + Class declaringClass, long[] overloadList, int modifiers); @@ -102,4 +104,9 @@ public native long defineMethodDispatch( public native void destroy( long context, long[] resources, int sz); + + @Override + public native void delete( + long context, + long[] resources, int sz); } diff --git a/native/java/org/jpype/manager/TypeManager.java b/native/java/org/jpype/manager/TypeManager.java index 491a53834..0e3dd6968 100644 --- a/native/java/org/jpype/manager/TypeManager.java +++ b/native/java/org/jpype/manager/TypeManager.java @@ -10,7 +10,7 @@ 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; @@ -32,8 +32,15 @@ import java.util.LinkedList; import java.util.List; import java.util.TreeSet; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import java.util.stream.Stream; + import org.jpype.JPypeContext; import org.jpype.JPypeUtilities; +import org.jpype.extension.ExtensionClassLoader; +import org.jpype.extension.Factory; import org.jpype.proxy.JPypeProxy; /** @@ -45,7 +52,8 @@ public class TypeManager public long context = 0; public boolean isStarted = false; public boolean isShutdown = false; - public HashMap classMap = new HashMap<>(); + public HashMap, ClassDescriptor> classMap = new HashMap<>(); + private HashMap, ClassDescriptor> extensionClassMap = new HashMap<>(); public TypeFactory typeFactory = null; public TypeAudit audit = null; private ClassDescriptor java_lang_Object; @@ -80,7 +88,7 @@ public synchronized void init() // 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 = + Class[] cls = { Class.class, Number.class, CharSequence.class, Throwable.class, Void.class, Boolean.class, Byte.class, Character.class, @@ -88,7 +96,7 @@ public synchronized void init() String.class, JPypeProxy.class, Method.class, Field.class }; - for (Class c : cls) + for (Class c : cls) { createClass(c, true); } @@ -154,6 +162,31 @@ public synchronized long findClass(Class cls) return out; } + public synchronized long findExtensionBaseClass(Class cls) { + return findOrCreateExtensionBaseClass(cls).classPtr; + } + + /** + * Find an extension wrapper for a class. + *

+ * Creates one if needed. This a front end used by JPype. + * + * @param cls + * @return the JPClass, or 0 it one cannot be created. + */ + private ClassDescriptor findOrCreateExtensionBaseClass(Class cls) { + ClassDescriptor desc = extensionClassMap.get(cls); + if (desc != null) { + return desc; + } + // bases are guarenteed to have been created already + // this is called from PyJPClass_mro + desc = createOrdinaryClass(cls, false, false, true); + createMembers(desc); + typeFactory.newWrapper(context, desc.classPtr); + return desc; + } + /** * Get a class by name. * @@ -237,7 +270,7 @@ public Class lookupByName(String name) { sb.append("."); sb.append(parts[i]); - Class cls = Class.forName(sb.toString()); + //Class cls = Class.forName(sb.toString()); for (int j = i + 1; j < parts.length; ++j) { sb.append("$"); @@ -316,7 +349,7 @@ public long findClassForObject(Object object) throws InterruptedException if (object == null) return 0; - Class cls = object.getClass(); + Class cls = object.getClass(); if (Proxy.isProxyClass(cls) && (Proxy.getInvocationHandler(object) instanceof JPypeProxy)) { @@ -326,6 +359,36 @@ public long findClassForObject(Object object) throws InterruptedException return this.findClass(cls); } + private static LongStream getPointers(ClassDescriptor entry) { + return LongStream.concat( + Stream.of( + entry.constructors, + entry.methodDispatch, + entry.methods, + entry.fields + ).flatMapToLong(LongStream::of), + LongStream.of(entry.anonymous, entry.classPtr) + ); + } + + public synchronized void destroy(ExtensionClassLoader ldr) { + // NOTE: this method takes an ExtensionClassLoader + // to avoid exposing a way to delete the JPClasses + // from a Class instance + if (ldr.isBuiltinLoader()) { + // this one is cleaned up along with everything else on shutdown + throw new UnsupportedOperationException("Builtin ExtensionClassLoader cannot be destroyed"); + } + long[] ptrs = ldr.getClasses() + .stream() + .map(classMap::remove) + .map(TypeManager::getPointers) + .map(LongStream::toArray) + .flatMapToLong(LongStream::of) + .toArray(); + typeFactory.delete(context, ptrs, ptrs.length); + } + /** * Called to delete all C++ resources */ @@ -338,22 +401,7 @@ public synchronized void shutdown() // Destroy all the resources held in C++ for (ClassDescriptor entry : this.classMap.values()) { - destroyer.add(entry.constructorDispatch); - destroyer.add(entry.constructors); - destroyer.add(entry.methodDispatch); - destroyer.add(entry.methods); - destroyer.add(entry.fields); - destroyer.add(entry.anonymous); - destroyer.add(entry.classPtr); - - // The same wrapper can appear more than once so blank as we go. - entry.constructorDispatch = 0; - entry.constructors = null; - entry.methodDispatch = null; - entry.methods = null; - entry.fields = null; - entry.anonymous = 0; - entry.classPtr = 0; + destroyer.add(entry); } destroyer.flush(); @@ -367,7 +415,7 @@ public synchronized void shutdown() // // - private ClassDescriptor getClass(Class cls) + private ClassDescriptor getClass(Class cls) { if (cls == null) return null; @@ -395,10 +443,10 @@ private ClassDescriptor createClass(Class cls, boolean special) if (cls.isArray()) return this.createArrayClass(cls); - return createOrdinaryClass(cls, special, true); + return createOrdinaryClass(cls, special, true, false); } - private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boolean bases) + private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boolean bases, boolean extensionBase) { // 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 @@ -416,7 +464,12 @@ private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boole superClassPtr = 0; if (superClass != null) { - parents[0] = this.getClass(superClass); + if (Factory.isExtension(cls) && !Factory.isExtension(superClass)) { + // inject a special extension base class wrapper for protected field/member access + parents[0] = findOrCreateExtensionBaseClass(superClass); + } else { + parents[0] = this.getClass(superClass); + } superClassPtr = parents[0].classPtr; } @@ -436,7 +489,7 @@ private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boole } // Set up the modifiers - int modifiers = cls.getModifiers() & 0xffff; + long modifiers = cls.getModifiers() & 0xffff; if (special) modifiers |= ModifierCode.SPECIAL.value; if (Throwable.class.isAssignableFrom(cls)) @@ -448,6 +501,12 @@ private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boole if (Buffer.class.isAssignableFrom(cls)) modifiers |= ModifierCode.BUFFER.value | ModifierCode.SPECIAL.value; + if (Factory.isExtension(cls)) { + modifiers |= ModifierCode.EXTENSION.value; + } else if (extensionBase) { + modifiers |= ModifierCode.EXTENSION_BASE.value; + } + // Check if is Functional class Method method = JPypeUtilities.getFunctionalInterfaceMethod(cls); if (method != null) @@ -465,8 +524,12 @@ private ClassDescriptor createOrdinaryClass(Class cls, boolean special, boole modifiers); // Cache the wrapper. - ClassDescriptor out = new ClassDescriptor(cls, classPtr, method); - this.classMap.put(cls, out); + ClassDescriptor out = new ClassDescriptor(cls, classPtr, method, extensionBase); + if (extensionBase) { + extensionClassMap.put(cls, out); + } else { + classMap.put(cls, out); + } return out; } @@ -479,14 +542,14 @@ private long createAnonymous(ClassDescriptor parent) parent.cls, parent.cls.getCanonicalName() + "$Anonymous", parent.classPtr, null, - ModifierCode.ANONYMOUS.value); + (int)ModifierCode.ANONYMOUS.value); return parent.anonymous; } - ClassDescriptor createArrayClass(Class cls) + ClassDescriptor createArrayClass(Class cls) { // Array classes are simple, we just need the component type - Class componentType = cls.getComponentType(); + Class componentType = cls.getComponentType(); long componentTypePtr = this.getClass(componentType).classPtr; int modifiers = cls.getModifiers() & 0xffff; @@ -501,44 +564,44 @@ ClassDescriptor createArrayClass(Class cls) componentTypePtr, modifiers); - ClassDescriptor out = new ClassDescriptor(cls, classPtr, null); + ClassDescriptor out = new ClassDescriptor(cls, classPtr, null, false); this.classMap.put(cls, out); return out; } /** - * Tell JPype to make a primitive Class. + * Tell JPype to make a primitive Class. * * @param name * @param cls * @param boxed */ - private void createPrimitive(String name, Class cls, Class boxed) + private void createPrimitive(String name, Class cls, Class boxed) { long classPtr = typeFactory.definePrimitive(context, name, cls, this.getClass(boxed).classPtr, cls.getModifiers() & 0xffff); - this.classMap.put(cls, new ClassDescriptor(cls, classPtr, null)); + this.classMap.put(cls, new ClassDescriptor(cls, classPtr, null, false)); } // // - public synchronized void populateMembers(Class cls) + public synchronized void populateMembers(Class cls) { ClassDescriptor desc = this.classMap.get(cls); if (desc == null) - throw new RuntimeException("Class not loaded"); + throw new RuntimeException("Class not loaded"); if (desc.fields != null) return; try { createMembers(desc); - } catch (Exception ex) + } catch (Throwable ex) { ex.printStackTrace(System.out); - throw ex; + throw new RuntimeException(ex); } } @@ -560,12 +623,45 @@ private void createMembers(ClassDescriptor desc) desc.fields); } + private static Predicate getFilter(Class cls) { + if (Factory.isExtension(cls)) { + return (a) -> !a.isSynthetic(); + } + return (a) -> Modifier.isPublic(a.getModifiers()) && !a.isSynthetic(); + } + // + + private static Stream getProtectedFields(Class cls) { + return getProtectedFields(cls, true); + } + + private static Stream getProtectedFields(Class cls, boolean start) { + Class base = start ? cls : cls.getSuperclass(); + if (base == Object.class || base == null) { + // base can be null if cls was Object + return Stream.empty(); + } + Stream fields = Arrays.stream(base.getDeclaredFields()) + .filter(f -> Modifier.isProtected(f.getModifiers()) && !f.isSynthetic()); + return Stream.concat(fields, getProtectedFields(base, false)); + } + private void createFields(ClassDescriptor desc) { // We only need declared fields as the wrappers for previous classes hold // members declared earlier - LinkedList fields = filterPublic(desc.cls.getDeclaredFields()); + Class cls = desc.cls; + Stream builder = Arrays.stream(cls.getDeclaredFields()) + .filter(getFilter(cls)); + if (desc.isExtensionBase()) { + // The first extension class in the hierarchy gets all protected members + // this allows us to keep the old behavior while only adding new behavior + // for the new extension classes. It also reduces the memory footprint. + builder = Stream.concat(builder, getProtectedFields(cls)); + } + List fields = builder.collect(Collectors.toList()); + //LinkedList fields = filterPublic(cls.getDeclaredFields()); long[] fieldPtr = new long[fields.size()]; int i = 0; @@ -590,10 +686,10 @@ private void createFields(ClassDescriptor desc) */ public void createConstructorDispatch(ClassDescriptor desc) { - Class cls = desc.cls; + Class cls = desc.cls; // Get the list of declared constructors - LinkedList constructors + LinkedList> constructors = filterPublic(cls.getDeclaredConstructors()); if (constructors.isEmpty()) @@ -611,7 +707,7 @@ public void createConstructorDispatch(ClassDescriptor desc) desc.classPtr, "", desc.constructors, - ModifierCode.PUBLIC.value | ModifierCode.CTOR.value); + (int)(ModifierCode.PUBLIC.value | ModifierCode.CTOR.value)); } /** @@ -630,7 +726,7 @@ private long[] createConstructors(ClassDescriptor desc, long[] overloadPtrs = new long[overloads.size()]; for (MethodResolution ov : overloads) { - Constructor constructor = (Constructor) ov.executable; + Executable constructor = ov.executable; int i = 0; long[] precedencePtrs = new long[ov.children.size()]; @@ -645,8 +741,10 @@ private long[] createConstructors(ClassDescriptor desc, desc.classPtr, constructor.toString(), constructor, + constructor.getDeclaringClass(), precedencePtrs, - modifiers); + modifiers + ); overloadPtrs[--n] = ov.ptr; } return overloadPtrs; @@ -654,6 +752,22 @@ private long[] createConstructors(ClassDescriptor desc, // // + + private static Stream getProtectedMethods(Class cls) { + return getProtectedMethods(cls, true); + } + + private static Stream getProtectedMethods(Class cls, boolean start) { + Class base = start ? cls : cls.getSuperclass(); + if (base == Object.class || base == null) { + // base can be null if cls was Object + return Stream.empty(); + } + Stream methods = Arrays.stream(base.getDeclaredMethods()) + .filter(m -> Modifier.isProtected(m.getModifiers()) && !m.isSynthetic()); + return Stream.concat(methods, getProtectedMethods(base, false)); + } + /** * Load the methods for a class. * @@ -664,10 +778,26 @@ public void createMethodDispatches(ClassDescriptor desc) Class cls = desc.cls; // Get the list of all public, non-overrided methods we will process - LinkedList methods = filterOverridden(cls, cls.getMethods()); + //LinkedList methods = filterPublic(cls.getMethods()); // Get the list of public declared methods - LinkedList declaredMethods = filterOverridden(cls, cls.getDeclaredMethods()); + //LinkedList declaredMethods = filterPublic(cls.getDeclaredMethods()); + Stream builder = Stream.concat( + Arrays.stream(cls.getDeclaredMethods()), + Arrays.stream(cls.getMethods()) + .filter((m) -> m.getDeclaringClass() != cls) + ).filter(getFilter(cls)); + + if (desc.isExtensionBase()) { + // The first extension class in the hierarchy gets all protected members + // this allows us to keep the old behavior while only adding new behavior + // for the new extension classes. It also reduces the memory footprint. + // The main difference between the old behavior and new extension behavior + // is method resolution. Throwing protecting members into the mix changes + // the behavior of existing user code which should be avoided. + builder = Stream.concat(builder, getProtectedMethods(cls)); + } + LinkedList declaredMethods = builder.collect(Collectors.toCollection(LinkedList::new)); // We only need one dispatch per name TreeSet resolve = new TreeSet<>(); @@ -684,7 +814,7 @@ public void createMethodDispatches(ClassDescriptor desc) int i = 0; for (String name : resolve) { - desc.methodDispatch[i++] = this.createMethodDispatch(desc, name, methods); + desc.methodDispatch[i++] = this.createMethodDispatch(desc, name, declaredMethods); } } @@ -782,6 +912,7 @@ private long[] createMethods( desc.classPtr, method.toString(), method, + method.getDeclaringClass(), precedencePtrs, modifiers); overloadPtrs[--n] = ov.ptr; @@ -798,7 +929,7 @@ private long[] createMethods( { try { - java.lang.reflect.Method method = java.lang.Class.class.getDeclaredMethod("forName", String.class); + Method method = Class.class.getDeclaredMethod("forName", String.class); for (Annotation annotation : method.getAnnotations()) { if ("@jdk.internal.reflect.CallerSensitive()".equals(annotation.toString())) @@ -836,7 +967,7 @@ public static boolean isCallerSensitive(Method method) // require special handling, thus we will just blanket those // classes known to have issues. Class cls = method.getDeclaringClass(); - if (cls.equals(java.lang.Class.class) + if (cls.equals(Class.class) || cls.equals(java.lang.ClassLoader.class) || cls.equals(java.sql.DriverManager.class)) { @@ -989,6 +1120,26 @@ void add(long[] v) flush(); } + void add(ClassDescriptor entry) + { + add(entry.constructorDispatch); + add(entry.constructors); + add(entry.methodDispatch); + add(entry.methods); + add(entry.fields); + add(entry.anonymous); + add(entry.classPtr); + + // The same wrapper can appear more than once so blank as we go. + entry.constructorDispatch = 0; + entry.constructors = null; + entry.methodDispatch = null; + entry.methods = null; + entry.fields = null; + entry.anonymous = 0; + entry.classPtr = 0; + } + void flush() { typeFactory.destroy(context, queue, index); diff --git a/native/java/org/jpype/pkg/JPypePackageManager.java b/native/java/org/jpype/pkg/JPypePackageManager.java index b5164a968..0ea7d26f9 100644 --- a/native/java/org/jpype/pkg/JPypePackageManager.java +++ b/native/java/org/jpype/pkg/JPypePackageManager.java @@ -10,7 +10,7 @@ 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.pkg; @@ -19,6 +19,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.DirectoryStream; import java.net.URLDecoder; import java.nio.file.FileSystem; import java.nio.file.FileSystemNotFoundException; @@ -54,7 +55,7 @@ public class JPypePackageManager { - final static List bases = new ArrayList(); + final static List bases = new ArrayList<>(); final static List modules = getModules(); final static FileSystemProvider jfsp = getFileSystemProvider("jar"); final static Map env = new HashMap<>(); @@ -226,10 +227,13 @@ static List getModules() { FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); Path modulePath = fs.getPath("modules"); - for (Path module : Files.newDirectoryStream(modulePath)) - { - out.add(new ModuleDirectory(module)); - } + try (DirectoryStream dirStream = Files.newDirectoryStream(modulePath)) + { + for (Path module : dirStream) + { + out.add(new ModuleDirectory(module)); + } + } } catch (ProviderNotFoundException | IOException ex) { } @@ -403,7 +407,7 @@ private static void getJarContents(Map out, String packageName) collectContents(out, path3); } } - + Path path2 = getPath(resource); collectContents(out, path2); } @@ -459,6 +463,7 @@ private static void collectContents(Map out, Path path2) } } + @SuppressWarnings("deprecation") private static URI toURI(Path path) { URI uri = path.toUri(); diff --git a/native/java/org/jpype/proxy/JPypeProxy.java b/native/java/org/jpype/proxy/JPypeProxy.java index 836deb74a..9588ddd19 100644 --- a/native/java/org/jpype/proxy/JPypeProxy.java +++ b/native/java/org/jpype/proxy/JPypeProxy.java @@ -77,7 +77,7 @@ public static JPypeProxy newProxy(JPypeContext context, // Proxies must point to the correct class loader. For most cases the // system classloader is find. But if the class is in a custom classloader // we need to use that one instead - for (Class cls : interfaces) + for (Class cls : interfaces) { ClassLoader icl = cls.getClassLoader(); if (icl != null && icl != proxy.cl) diff --git a/native/java/org/jpype/ref/JPypeReference.java b/native/java/org/jpype/ref/JPypeReference.java index e75cabb06..92780a462 100644 --- a/native/java/org/jpype/ref/JPypeReference.java +++ b/native/java/org/jpype/ref/JPypeReference.java @@ -21,7 +21,7 @@ /** * (internal) Reference to a PyObject*. */ -class JPypeReference extends PhantomReference +class JPypeReference extends PhantomReference { long hostReference; @@ -29,7 +29,7 @@ class JPypeReference extends PhantomReference int pool; int index; - public JPypeReference(ReferenceQueue arg1, Object javaObject, long host, long cleanup) + public JPypeReference(ReferenceQueue arg1, Object javaObject, long host, long cleanup) { super(javaObject, arg1); this.hostReference = host; diff --git a/native/java/org/jpype/ref/JPypeReferenceQueue.java b/native/java/org/jpype/ref/JPypeReferenceQueue.java index 9b34ed1bd..ededa5baa 100644 --- a/native/java/org/jpype/ref/JPypeReferenceQueue.java +++ b/native/java/org/jpype/ref/JPypeReferenceQueue.java @@ -10,7 +10,7 @@ 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.ref; @@ -29,7 +29,7 @@ * @author smenard * */ -final public class JPypeReferenceQueue extends ReferenceQueue +final public class JPypeReferenceQueue extends ReferenceQueue { private final static JPypeReferenceQueue INSTANCE = new JPypeReferenceQueue(); @@ -37,7 +37,7 @@ final public class JPypeReferenceQueue extends ReferenceQueue private boolean isStopped = false; private Thread queueThread; private Object queueStopMutex = new Object(); - private PhantomReference sentinel = null; + private PhantomReference sentinel = null; public static JPypeReferenceQueue getInstance() { diff --git a/native/python/include/jp_pythontypes.h b/native/python/include/jp_pythontypes.h index b69ebd690..097dbf3a1 100755 --- a/native/python/include/jp_pythontypes.h +++ b/native/python/include/jp_pythontypes.h @@ -15,9 +15,23 @@ *****************************************************************************/ #ifndef JP_PYTHONTYPES_H_ #define JP_PYTHONTYPES_H_ + +#pragma once + +extern "C" { #include +} + +#include "jni.h" + +#include +#include #include +using std::string; +using std::vector; +using namespace std::literals; + /** * This set of source are mostly sugar with some light weight * reference management. Each type holds a reference for the duration of its @@ -120,6 +134,11 @@ class JPPyObject { } + JPPyObject(JPPyObject &&rhs) noexcept : m_PyObject(rhs.m_PyObject) + { + rhs.m_PyObject = nullptr; + } + JPPyObject(const JPPyObject &self); ~JPPyObject(); @@ -148,7 +167,7 @@ class JPPyObject /** Access the object. This should never appear in * a return statement. */ - PyObject* get() + PyObject* get() const { return m_PyObject; } @@ -202,7 +221,7 @@ class JPPyString : public JPPyObject * * @param str is the string to convert */ - static JPPyObject fromStringUTF8(const string& str); + static JPPyObject fromStringUTF8(const std::string_view &str); /** Get a UTF-8 encoded string from Python */ @@ -323,7 +342,7 @@ class JPPyErrFrame bool good; JPPyErrFrame(); - ~JPPyErrFrame(); + ~JPPyErrFrame() noexcept; void clear(); void normalize(); } ; @@ -335,9 +354,11 @@ class JPPyCallAcquire { public: /** Acquire the lock. */ - JPPyCallAcquire(); + JPPyCallAcquire() : m_State((long) PyGILState_Ensure()) {} /* Release the lock. */ - ~JPPyCallAcquire(); + ~JPPyCallAcquire() { + PyGILState_Release((PyGILState_STATE) m_State); + } private: long m_State; } ; @@ -349,9 +370,12 @@ class JPPyCallRelease { public: /** Release the lock. */ - JPPyCallRelease(); + JPPyCallRelease() : // Release the lock and set the thread state to NULL + m_State1(PyEval_SaveThread()) {} /** Reacquire the lock. */ - ~JPPyCallRelease(); + ~JPPyCallRelease() { + PyEval_RestoreThread(m_State1); + } private: PyThreadState* m_State1; } ; diff --git a/native/python/include/pyjp.h b/native/python/include/pyjp.h index df6983af3..c799b0520 100755 --- a/native/python/include/pyjp.h +++ b/native/python/include/pyjp.h @@ -136,6 +136,7 @@ extern PyTypeObject *PyJPNumberLong_Type; extern PyTypeObject *PyJPNumberFloat_Type; extern PyTypeObject *PyJPNumberBool_Type; extern PyTypeObject *PyJPChar_Type; +extern PyTypeObject *JAnnotation; // JPype resources @@ -153,7 +154,10 @@ extern PyObject *_JMethodAnnotations; extern PyObject *_JMethodCode; extern PyObject *_JObjectKey; extern PyObject *_JVMNotRunning; -extern PyObject* PyJPClassMagic; +extern PyObject *PyJPClassMagic; +extern PyObject *_JExtension; +extern PyObject *JClass; +extern PyObject *_JClassTable; extern JPContext* JPContext_global; @@ -195,6 +199,7 @@ JPPyObject PyJPField_create(JPField* m); JPPyObject PyJPMethod_create(JPMethodDispatch *m, PyObject *instance); JPClass* PyJPClass_getJPClass(PyObject* obj); +void PyJPClass_clearJPClass(PyObject *obj); JPProxy* PyJPProxy_getJPProxy(PyObject* obj); void PyJPModule_rethrow(const JPStackInfo& info); void PyJPValue_assignJavaSlot(JPJavaFrame &frame, PyObject* obj, const JPValue& value); @@ -202,7 +207,7 @@ bool PyJPValue_isSetJavaSlot(PyObject* self); JPPyObject PyTrace_FromJavaException(JPJavaFrame& frame, jthrowable th, jthrowable prev); void PyJPException_normalize(JPJavaFrame frame, JPPyObject exc, jthrowable th, jthrowable enclosing); -#define _ASSERT_JVM_RUNNING(context) assertJVMRunning((JPContext*)context, JP_STACKINFO()) +#define _ASSERT_JVM_RUNNING(context) assertJVMRunning((JPContext*)(context), JP_STACKINFO()) /** * Use this when getting the context where the context must be running. diff --git a/native/python/include/pyjp_module.hpp b/native/python/include/pyjp_module.hpp new file mode 100644 index 000000000..0435e821a --- /dev/null +++ b/native/python/include/pyjp_module.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "Python.h" // IWYU pragma: keep + +extern void PyJPArray_initType(PyObject* module); +extern void PyJPBuffer_initType(PyObject* module); +extern void PyJPClass_initType(PyObject* module); +extern void PyJPField_initType(PyObject* module); +extern void PyJPMethod_initType(PyObject* module); +extern void PyJPMonitor_initType(PyObject* module); +extern void PyJPProxy_initType(PyObject* module); +extern void PyJPObject_initType(PyObject* module); +extern void PyJPNumber_initType(PyObject* module); +extern void PyJPClassHints_initType(PyObject* module); +extern void PyJPPackage_initType(PyObject* module); +extern void PyJPChar_initType(PyObject* module); +extern void PyJPValue_initType(PyObject* module); diff --git a/native/python/jp_pythontypes.cpp b/native/python/jp_pythontypes.cpp index 89c828023..502f65762 100644 --- a/native/python/jp_pythontypes.cpp +++ b/native/python/jp_pythontypes.cpp @@ -117,7 +117,7 @@ JPPyObject::JPPyObject(const JPPyObject &self) } } -JPPyObject::~JPPyObject() +JPPyObject::~JPPyObject() // NOLINT(bugprone-exception-escape) { if (m_PyObject != nullptr) { @@ -129,16 +129,16 @@ JPPyObject::~JPPyObject() } } -JPPyObject& JPPyObject::operator=(const JPPyObject& self) +JPPyObject& JPPyObject::operator=(const JPPyObject& rhs) // NOLINT(bugprone-unhandled-self-assignment) { - if (m_PyObject == self.m_PyObject) + if (m_PyObject == rhs.m_PyObject) return *this; if (m_PyObject != nullptr) { JP_TRACE_PY("pyref op=(dec)", m_PyObject); decref(); } - m_PyObject = self.m_PyObject; + m_PyObject = rhs.m_PyObject; if (m_PyObject != nullptr) { incref(); @@ -259,7 +259,7 @@ jchar JPPyString::asCharUTF16(PyObject* pyobj) if (sz != 1) JP_RAISE(PyExc_ValueError, "Char must be length 1"); - jchar c = PyBytes_AsString(pyobj)[0]; + jchar c = ((jchar*)PyBytes_AsString(pyobj))[0]; JP_PY_CHECK(); return c; } @@ -293,12 +293,10 @@ bool JPPyString::check(PyObject* obj) /** Create a new string from utf8 encoded string. * Note: java utf8 is not utf8. */ -JPPyObject JPPyString::fromStringUTF8(const string& str) +JPPyObject JPPyString::fromStringUTF8(const std::string_view& str) { - auto len = static_cast(str.size()); - // Python 3 is always unicode - JPPyObject bytes = JPPyObject::call(PyBytes_FromStringAndSize(str.c_str(), len)); + JPPyObject bytes = JPPyObject::call(PyBytes_FromStringAndSize(str.data(), (Py_ssize_t)str.size())); return JPPyObject::call(PyUnicode_FromEncodedObject(bytes.get(), "UTF-8", "strict")); } @@ -390,30 +388,6 @@ void JPPyErr::restore(JPPyObject& exceptionClass, JPPyObject& exceptionValue, JP PyErr_Restore(exceptionClass.keepNull(), exceptionValue.keepNull(), exceptionTrace.keepNull()); } -JPPyCallAcquire::JPPyCallAcquire() -{ - m_State = (long) PyGILState_Ensure(); -} - -JPPyCallAcquire::~JPPyCallAcquire() -{ - PyGILState_Release((PyGILState_STATE) m_State); -} - -// This is used when leaving python from to perform some - -JPPyCallRelease::JPPyCallRelease() -{ - // Release the lock and set the thread state to NULL - m_State1 = PyEval_SaveThread(); -} - -JPPyCallRelease::~JPPyCallRelease() -{ - // Re-acquire the lock - PyEval_RestoreThread(m_State1); -} - JPPyBuffer::JPPyBuffer(PyObject* obj, int flags) { int ret = PyObject_GetBuffer(obj, &m_View, flags); @@ -464,7 +438,7 @@ JPPyErrFrame::JPPyErrFrame() good = JPPyErr::fetch(m_ExceptionClass, m_ExceptionValue, m_ExceptionTrace); } -JPPyErrFrame::~JPPyErrFrame() +JPPyErrFrame::~JPPyErrFrame() noexcept { try { diff --git a/native/python/pyjp_buffer.cpp b/native/python/pyjp_buffer.cpp index 561754de5..9e390cf61 100644 --- a/native/python/pyjp_buffer.cpp +++ b/native/python/pyjp_buffer.cpp @@ -16,7 +16,6 @@ #include "jpype.h" #include "pyjp.h" #include "jp_buffer.h" -#include "jp_buffertype.h" #ifdef __cplusplus extern "C" diff --git a/native/python/pyjp_char.cpp b/native/python/pyjp_char.cpp index eb37e62aa..34fae14a2 100644 --- a/native/python/pyjp_char.cpp +++ b/native/python/pyjp_char.cpp @@ -251,7 +251,7 @@ static PyObject *PyJPChar_str(PyJPChar *self) return nullptr; } // GCOVR_EXCL_STOP if (isNull(javaSlot)) - return JPPyString::fromStringUTF8("None").keep(); + return JPPyString::fromStringUTF8("None"sv).keep(); return PyUnicode_FromOrdinal(fromJPChar(self)); JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } @@ -268,7 +268,7 @@ static PyObject *PyJPChar_repr(PyJPChar *self) return nullptr; } // GCOVR_EXCL_STOP if (isNull(javaSlot)) - return JPPyString::fromStringUTF8("None").keep(); + return JPPyString::fromStringUTF8("None"sv).keep(); return PyUnicode_Type.tp_repr((PyObject*) self); JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp index cc9996dc1..30872ca49 100644 --- a/native/python/pyjp_class.cpp +++ b/native/python/pyjp_class.cpp @@ -15,30 +15,79 @@ *****************************************************************************/ #include #include +#include #include +#include #include +#include +#include "jp_class.h" +#include "jp_extension.hpp" #include "jpype.h" +#include "pyconfig.h" #include "pyjp.h" #include "jp_array.h" #include "jp_arrayclass.h" #include "jp_boxedtype.h" #include "jp_field.h" -#include "jp_method.h" #include "jp_methoddispatch.h" #include "jp_primitive_accessor.h" +#include "pyjp_module.hpp" +#include "pytypedefs.h" + + +using namespace std::literals; struct PyJPClass { PyHeapTypeObject ht_type; JPClass *m_Class; PyObject *m_Doc; -} ; +}; PyObject* PyJPClassMagic = nullptr; -#ifdef __cplusplus -extern "C" +static PyObject *extensionCastFinalizer(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + (void)self; + if (nargs == 1) + { + // decref the extension object and the tuple needed for the finalizer class + //Py_DECREF(PyTuple_GetItem(args[0], 0)); + Py_DECREF(args[0]); + } + Py_RETURN_NONE; +} + +static PyMethodDef extensionCastFinalizerMethod = {NULL, (PyCFunction)extensionCastFinalizer, METH_FASTCALL, NULL}; + +static JPPyObject weakrefFinalizeClass{}; +static JPPyObject extensionFinalizer{}; + +static void createExtensionCastFinalizer(PyObject *cast, PyObject *ext) { + if (weakrefFinalizeClass.isNull()) + { + JPPyObject weakref = JPPyObject::call(PyImport_ImportModule("weakref")); + JP_PY_CHECK(); + extensionFinalizer = JPPyObject::call(PyCFunction_New(&extensionCastFinalizerMethod, NULL)); + JP_PY_CHECK(); + weakrefFinalizeClass = JPPyObject::call(PyObject_GetAttrString(weakref.get(), "finalize")); + JP_PY_CHECK(); + } + auto obj = JPPyObject::use(ext); + JPPyObject::call(PyObject_CallFunctionObjArgs( + weakrefFinalizeClass.get(), + cast, + extensionFinalizer.get(), + JPPyTuple_Pack(obj.get()).keep(), + NULL) + ); + obj.incref(); + JP_PY_CHECK(); +} + +#ifdef __cplusplus +extern "C" { #endif int PyJPClass_Check(PyObject* obj) @@ -260,7 +309,25 @@ PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -int PyJPClass_init(PyObject *self, PyObject *args, PyObject *kwargs) +static void PyJPClass_hook_extends(JPJavaFrame &frame, JPClass &cls, PyObject *members) { + const JPFieldList &instFields = cls.getFields(); + for (auto instField : instFields) { + JPPyObject fieldName(JPPyString::fromStringUTF8(instField->getName())); + PyDict_SetItem(members, fieldName.get(), PyJPField_create(instField).get()); + } + + const JPMethodDispatchList& m_Methods = cls.getMethods(); + for (auto m_Method : m_Methods) { + // TODO: it would be great to not have to go through java to call our python methods + JPPyObject methodName(JPPyString::fromStringUTF8(m_Method->getName())); + PyDict_SetItem(members, methodName.get(), PyJPMethod_create(m_Method, nullptr).get()); + } + + JPValue value{frame.getContext()->_java_lang_Class, (jobject) cls.getJavaClass()}; + PyJPValue_assignJavaSlot(frame, (PyObject*)cls.getHost(), value); +} + +static int PyJPClass_init(PyObject *self, PyObject *args, PyObject *kwargs) { JP_PY_TRY("PyJPClass_init"); @@ -273,7 +340,7 @@ int PyJPClass_init(PyObject *self, PyObject *args, PyObject *kwargs) PyTypeObject *type = (PyTypeObject*) self; #if PY_VERSION_HEX >= 0x030d0000 - // Python 3.13 - This flag will try to place the dictionary are part of the object which + // Python 3.13 - This flag will try to place the dictionary are part of the object which // adds an unknown number of bytes to the end of the object making it impossible // to attach our needed data. If we kill the flag then we get usable behavior. type->tp_flags &= ~Py_TPFLAGS_INLINE_VALUES; @@ -288,11 +355,28 @@ int PyJPClass_init(PyObject *self, PyObject *args, PyObject *kwargs) } if (magic == 0) { - PyErr_Format(PyExc_TypeError, "Java classes cannot be extended in Python"); - return -1; + // returns a tuple of the Class pointer and the overrides + JPPyObject tmp = JPPyObject::call(PyObject_Call(_JExtension, args, kwargs)); + unsigned long long value = PyLong_AsUnsignedLongLong(PyTuple_GetItem(tmp.get(), 0)); + JPExtensionType *cls = reinterpret_cast(static_cast(value)); + ((PyJPClass*) self)->m_Class = cls; + + // set host early to prevent double creation + cls->setHost(self); + + if (!cls->wasReloaded()) { + + JPJavaFrame frame = JPJavaFrame::outer(cls->getContext()); + + // fill out the Java class members + cls->setOverrides(frame, PyTuple_GetItem(tmp.get(), 1)); + cls->ensureMembers(frame); + + // finish setting members in our Python class that got skipped due to the host being set + PyJPClass_hook_extends(frame, *cls, ((PyTypeObject *)self)->tp_dict); + } } - // Set the host object PyObject *name = nullptr; PyObject *bases = nullptr; PyObject *members = nullptr; @@ -380,7 +464,7 @@ PyObject* PyJPClass_mro(PyTypeObject *self) bases.push_back((PyObject*) self); // Merge together all bases - std::list out; + std::list out; for (auto iter = bases.begin(); iter != bases.end(); ++iter) { @@ -413,7 +497,7 @@ PyObject* PyJPClass_mro(PyTypeObject *self) } if (front != nullptr) { - out.push_back(front); + out.push_back((PyTypeObject*)front); auto* next = (PyObject*) ((PyTypeObject*) front)->tp_base; if (next) { @@ -423,13 +507,13 @@ PyObject* PyJPClass_mro(PyTypeObject *self) } } - PyObject *obj = PyTuple_New(out.size()); + PyObject *obj = PyTuple_New((Py_ssize_t)out.size()); int j = 0; for (auto iter = out.begin(); iter != out.end(); ++iter) { Py_INCREF(*iter); - PyTuple_SetItem(obj, j++, *iter); + PyTuple_SetItem(obj, j++, (PyObject*)*iter); } return obj; } @@ -562,7 +646,28 @@ PyObject* PyJPClass_subclasscheck(PyTypeObject *type, PyTypeObject *test) JP_PY_CATCH(nullptr); } -static PyObject *PyJPClass_class(PyObject *self, PyObject *closure) +static PyObject *PyJPClass_prepare(PyObject *, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + if (nargs < 2 || (kwnames != nullptr && PyDict_GET_SIZE(kwnames) > 0)) { + return PyDict_New(); + } + PyObject *bases = args[1]; + if (PyTuple_Size(bases) < 1) { + return PyDict_New(); + } + JPClass *cls = PyJPClass_getJPClass(PyTuple_GetItem(bases, 0)); + if (cls == nullptr) { + return PyDict_New(); + } + if (cls->isExtension() || cls->isExtensionBase()) { + return PyObject_CallNoArgs(_JClassTable); + } + // swap in our special extension base + JPClass *base = cls->getContext()->getTypeManager()->findExtensionBaseClass(cls->getJavaClass()); + PyTuple_SetItem(bases, 0, JPPyObject::use((PyObject*)base->getHost()).keep()); + return PyObject_CallNoArgs(_JClassTable); +} + +static PyObject *PyJPClass_class(PyObject *self, PyObject *) { JP_PY_TRY("PyJPClass_class"); JPContext *context = PyJPModule_getContext(); @@ -577,7 +682,7 @@ static PyObject *PyJPClass_class(PyObject *self, PyObject *closure) JP_PY_CATCH(nullptr); } -static int PyJPClass_setClass(PyObject *self, PyObject *type, PyObject *closure) +static int PyJPClass_setClass(PyObject *self, PyObject *type, PyObject *) { JP_PY_TRY("PyJPClass_setClass", self); JPContext *context = PyJPModule_getContext(); @@ -604,7 +709,7 @@ static int PyJPClass_setClass(PyObject *self, PyObject *type, PyObject *closure) JP_PY_CATCH(-1); } -static PyObject *PyJPClass_hints(PyJPClass *self, PyObject *closure) +static PyObject *PyJPClass_hints(PyJPClass *self, PyObject *) { JP_PY_TRY("PyJPClass_hints"); PyJPModule_getContext(); @@ -640,7 +745,7 @@ static PyObject *PyJPClass_hints(PyJPClass *self, PyObject *closure) JP_PY_CATCH(nullptr); } -static int PyJPClass_setHints(PyObject *self, PyObject *value, PyObject *closure) +static int PyJPClass_setHints(PyObject *self, PyObject *value, PyObject *) { JP_PY_TRY("PyJPClass_setHints", self); PyJPModule_getContext(); @@ -709,15 +814,15 @@ static PyObject *PyJPClass_canConvertToJava(PyJPClass *self, PyObject *other) // Report to user if (match.type == JPMatch::_none) - return JPPyString::fromStringUTF8("none").keep(); + return JPPyString::fromStringUTF8("none"sv).keep(); if (match.type == JPMatch::_explicit) - return JPPyString::fromStringUTF8("explicit").keep(); + return JPPyString::fromStringUTF8("explicit"sv).keep(); if (match.type == JPMatch::_implicit) - return JPPyString::fromStringUTF8("implicit").keep(); + return JPPyString::fromStringUTF8("implicit"sv).keep(); if (match.type == JPMatch::_derived) - return JPPyString::fromStringUTF8("derived").keep(); + return JPPyString::fromStringUTF8("derived"sv).keep(); if (match.type == JPMatch::_exact) - return JPPyString::fromStringUTF8("exact").keep(); + return JPPyString::fromStringUTF8("exact"sv).keep(); // Not sure how this could happen Py_RETURN_NONE; // GCOVR_EXCL_LINE @@ -809,7 +914,7 @@ static PyObject *PyJPClass_array(PyJPClass *self, PyObject *item) // Get the type JPClass *cls; if (undefined > 0) - cls = self->m_Class->newArrayType(frame, undefined); + cls = self->m_Class->newArrayType(frame, (long)undefined); else cls = self->m_Class; @@ -818,7 +923,7 @@ static PyObject *PyJPClass_array(PyJPClass *self, PyObject *item) return PyJPClass_create(frame, cls).keep(); // Otherwise create an array - jintArray u = frame.NewIntArray(defined); + jintArray u = frame.NewIntArray((jsize)defined); JPPrimitiveArrayAccessor accessor(frame, u, &JPJavaFrame::GetIntArrayElements, &JPJavaFrame::ReleaseIntArrayElements); for (size_t j = 0; j < sz.size(); ++j) @@ -853,7 +958,7 @@ static PyObject *PyJPClass_cast(PyJPClass *self, PyObject *other) PyErr_Format(PyExc_TypeError, "Unable to cast '%s' to java type '%s'", Py_TYPE(other)->tp_name, - type->getCanonicalName().c_str() + type->getCanonicalName().data() ); return nullptr; } @@ -861,6 +966,14 @@ static PyObject *PyJPClass_cast(PyJPClass *self, PyObject *other) return type->convertToPythonObject(frame, v, true).keep(); } + if (type->isExtension() || type->isExtensionBase()) { + PyErr_Format(PyExc_TypeError, + "Casting to extension class '%s' is not allowed", + ((PyTypeObject*)self)->tp_name + ); + return nullptr; + } + // Cast on java object // if (!type->isSubTypeOf(val->getClass())) jobject obj = val->getJavaObject(); @@ -880,8 +993,8 @@ static PyObject *PyJPClass_cast(PyJPClass *self, PyObject *other) { PyErr_Format(PyExc_TypeError, "Unable to cast '%s' to java type '%s'", - otherClass->getCanonicalName().c_str(), - type->getCanonicalName().c_str() + otherClass->getCanonicalName().data(), + type->getCanonicalName().data() ); return nullptr; } @@ -900,13 +1013,17 @@ static PyObject *PyJPClass_cast(PyJPClass *self, PyObject *other) } } - return type->convertToPythonObject(frame, val->getValue(), true).keep(); + auto res = type->convertToPythonObject(frame, val->getValue(), true).keep(); + if (otherClass->isExtension()) { + createExtensionCastFinalizer(res, other); + } + return res; Py_RETURN_NONE; JP_PY_CATCH(nullptr); } -static PyObject *PyJPClass_castEq(PyJPClass *self, PyObject *other) +static PyObject *PyJPClass_castEq(PyJPClass *, PyObject *) { PyErr_Format(PyExc_TypeError, "Invalid operation"); return nullptr; @@ -947,7 +1064,7 @@ static PyObject *PyJPClass_repr(PyJPClass *self) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -static PyObject *PyJPClass_getDoc(PyJPClass *self, void *ctxt) +static PyObject *PyJPClass_getDoc(PyJPClass *self, void *) { JP_PY_TRY("PyJPMethod_getDoc"); JPContext *context = PyJPModule_getContext(); @@ -970,7 +1087,7 @@ static PyObject *PyJPClass_getDoc(PyJPClass *self, void *ctxt) JP_PY_CATCH(nullptr); } -int PyJPClass_setDoc(PyJPClass *self, PyObject *obj, void *ctxt) +int PyJPClass_setDoc(PyJPClass *self, PyObject *obj, void *) { JP_PY_TRY("PyJPClass_setDoc"); Py_CLEAR(self->m_Doc); @@ -980,7 +1097,7 @@ int PyJPClass_setDoc(PyJPClass *self, PyObject *obj, void *ctxt) JP_PY_CATCH(-1); } -PyObject* PyJPClass_customize(PyJPClass *self, PyObject *args, PyObject *kwargs) +PyObject* PyJPClass_customize(PyJPClass *self, PyObject *args, PyObject *) { JP_PY_TRY("PyJPClass_customize"); PyObject *name = nullptr; @@ -996,6 +1113,7 @@ PyObject* PyJPClass_customize(PyJPClass *self, PyObject *args, PyObject *kwargs) static PyMethodDef classMethods[] = { {"__instancecheck__", (PyCFunction) PyJPClass_instancecheck, METH_O, ""}, {"__subclasscheck__", (PyCFunction) PyJPClass_subclasscheck, METH_O, ""}, + {"__prepare__", (PyCFunction) PyJPClass_prepare, METH_FASTCALL | METH_KEYWORDS | METH_CLASS, ""}, {"mro", (PyCFunction) PyJPClass_mro, METH_NOARGS, ""}, {"_canConvertToJava", (PyCFunction) PyJPClass_canConvertToJava, METH_O, ""}, {"_convertToJava", (PyCFunction) PyJPClass_convertToJava, METH_O, ""}, @@ -1075,7 +1193,21 @@ JPClass* PyJPClass_getJPClass(PyObject* obj) } } -JPPyObject PyJPClass_getBases(JPJavaFrame &frame, JPClass* cls) +void PyJPClass_clearJPClass(PyObject *obj) { + try + { + if (obj == nullptr) + return; + if (PyJPClass_Check(obj)) { + ((PyJPClass*) obj)->m_Class = nullptr; + } + } catch (...) // GCOVR_EXCL_LINE + { + return; // GCOVR_EXCL_LINE + } +} + +static JPPyObject PyJPClass_getBases(JPJavaFrame &frame, JPClass* cls) { JP_TRACE_IN("PyJPClass_bases"); @@ -1129,7 +1261,7 @@ JPPyObject PyJPClass_getBases(JPJavaFrame &frame, JPClass* cls) } const JPClassList& baseItf = cls->getInterfaces(); - Py_ssize_t count = baseItf.size() + (!baseType.isNull() ? 1 : 0) + (super != nullptr ? 1 : 0); + Py_ssize_t count = (Py_ssize_t)baseItf.size() + (!baseType.isNull() ? 1 : 0) + (super != nullptr ? 1 : 0); // Pack into a tuple JPPyObject result = JPPyObject::call(PyList_New(count)); @@ -1241,5 +1373,5 @@ void PyJPClass_hook(JPJavaFrame &frame, JPClass* cls) // Call the post load routine to attach inner classes JP_TRACE("call post"); args = JPPyTuple_Pack(self); - JPPyObject rc2 = JPPyObject::call(PyObject_Call(_JClassPost, args.get(), nullptr)); + JPPyObject::call(PyObject_Call(_JClassPost, args.get(), nullptr)); } diff --git a/native/python/pyjp_extension.cpp b/native/python/pyjp_extension.cpp new file mode 100644 index 000000000..17159921c --- /dev/null +++ b/native/python/pyjp_extension.cpp @@ -0,0 +1,119 @@ +#include "jp_class.h" +#include "jpype.h" +#include "jp_extension.hpp" +#include "pyjp.h" + +static JPClass *getClass(PyObject *ptr) { + unsigned long long value = PyLong_AsUnsignedLongLong(ptr); + if (static_cast(value) <= 0) { + PyErr_Format(PyExc_TypeError, "Java class required: %s", Py_TYPE(ptr)->tp_name); + JP_RAISE_PYTHON(); + } + return reinterpret_cast(static_cast(value)); +} + +static std::vector getParams(PyObject *types) { + std::vector res{}; + Py_ssize_t len = PySequence_Length(types); + res.reserve(len); + for (Py_ssize_t i = 0; i < len; i++) { + JPPyObject type = JPPyObject::call(PySequence_GetItem(types, i)); + res.push_back(getClass(type.get())); + } + return res; +} + +static JPMethodOverride createOverride(PyObject *def) { + JPPyObject resType = JPPyObject::call(PySequence_GetItem(def, 0)); + JPPyObject argTypes = JPPyObject::call(PySequence_GetItem(def, 1)); + + return { + getClass(resType.get()), + getParams(argTypes.get()), + JPPyObject::call(PySequence_GetItem(def, 2)) + }; +} + +void JPExtensionType::setOverrides(JPJavaFrame& frame, PyObject *args) { + if (m_Instance == nullptr) { + // This can't be done in the constructor because it will cause + // the Java class to be initialized (). This will then cause the creation + // of another JPExtensionType whose overrides never get set and that JPExtensionType + // ends up cached by jpype. At the point that setOverrides is called, the correct + // JPExtensionType has already been cached and it is safe to get the field id. + if (m_SuperClass != nullptr && m_SuperClass->isExtension()) { + m_Instance = static_cast(m_SuperClass)->m_Instance; + } else { + m_Instance = frame.GetFieldID(m_Class.get(), "$instance", "J"); + } + } + + Py_ssize_t len = PySequence_Length(args); + if (len <= 0) { + return; + } + + m_Overrides.reserve(len); + + for (Py_ssize_t i = 0; i < len; i++) { + JPPyObject def = JPPyObject::call(PySequence_GetItem(args, i)); + m_Overrides.emplace_back(createOverride(def.get())); + } +} + +JPPyObject JPExtensionType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) { + (void) cast; + + jobject obj = val.l; + PyObject *instance = (PyObject *)frame.GetLongField(obj, m_Instance); + if (instance == nullptr) { + // this is the first access when calling a python implemented constructor + // this cannot be done in newInstance or we will have to deal with it being + // null in the python implemented constructor and anything it calls + + // we might also be a base class so fetching the JPClass is required + JPClass *cls = m_Context->getTypeManager()->findClassForObject(obj); + PyTypeObject *type = (PyTypeObject *) cls->getHost(); + if (type == nullptr) { + // someone is holding onto something they shouldn't be + PyErr_Format(PyExc_TypeError, "%s has been queued for deletion", m_CanonicalName.c_str()); + JP_RAISE_PYTHON(); + } + JPPyObject res = JPPyObject::call(type->tp_alloc(type, 0)); + instance = res.get(); + + JPValue jv{cls, obj}; + PyJPValue_assignJavaSlot(frame, instance, jv); + + frame.SetLongField(obj, m_Instance, (jlong)instance); + frame.registerRef(obj, instance); + return res; + } + return JPPyObject::use(instance); +} + +JPValue JPExtensionType::newInstance(JPJavaFrame& frame, JPPyObjectVector& args) { + if (m_Class.get() == nullptr) { + // someone is holding onto something they shouldn't be + PyErr_Format(PyExc_TypeError, "%s has been queued for deletion", m_CanonicalName.c_str()); + JP_RAISE_PYTHON(); + } + return JPClass::newInstance(frame, args); +} + +void JPExtensionType::reset(JPJavaFrame& frame) { + // prevent creation of another one + // also prevents use of an existing one + // The PyJPValue now has ownership of this JPExtensionType + // it will be deleted in PyJPValue_finalize + // Factory::call checks if $jclass is null prior to calling into native code + auto jclass = frame.GetStaticFieldID(m_Class.get(), "$jclass", "J"); + frame.SetStaticLongField(m_Class.get(), jclass, 0); + m_Host = {}; + m_Constructors = nullptr; + m_Class = {}; + m_Hints = {}; + m_Fields.clear(); + m_Methods.clear(); + m_Overrides.clear(); +} diff --git a/native/python/pyjp_field.cpp b/native/python/pyjp_field.cpp index 017fc5580..d22ad76a3 100644 --- a/native/python/pyjp_field.cpp +++ b/native/python/pyjp_field.cpp @@ -13,14 +13,12 @@ See NOTICE file for details. *****************************************************************************/ +#include "jp_class.h" #include "jpype.h" #include "pyjp.h" #include "jp_field.h" - -#ifdef __cplusplus -extern "C" -{ -#endif +#include "pyjp_module.hpp" +#include struct PyJPField { @@ -34,7 +32,7 @@ static void PyJPField_dealloc(PyJPField *self) Py_TYPE(self)->tp_free(self); } -static PyObject *PyJPField_get(PyJPField *self, PyObject *obj, PyObject *type) +static PyObject *PyJPField_get(PyJPField *self, PyObject *obj, PyObject *) { JP_PY_TRY("PyJPField_get"); JPContext *context = PyJPModule_getContext(); @@ -54,6 +52,52 @@ static PyObject *PyJPField_get(PyJPField *self, PyObject *obj, PyObject *type) JP_PY_CATCH(nullptr); } +static bool isInitializingFinalField(JPField &field) { + if (field.isStatic()) { + // not applicable + // static final fields can use the default value + return false; + } + + JPClass *cls = field.getClass(); + if (!cls->isExtension()) { + return false; + } + + PyObject *locals = PyEval_GetLocals(); + if (locals == nullptr) { + // access denied + return false; + } + + JPPyObject obj = JPPyObject::call(PyMapping_GetItemString(locals, "self")); + if (obj.get() != nullptr) { + obj = JPPyObject::use((PyObject *) Py_TYPE(obj.get())); + } else { + obj = JPPyObject::call(PyMapping_GetItemString(locals, "cls")); + } + + if (obj.isNull()) { + return false; + } + + if (cls != PyJPClass_getJPClass(obj.get())) { + // it may only be initialized in the constructor for this class + return false; + } + + // frame cannot be null or locals would have been null + // code cannot be null + JPPyObject code = JPPyObject::accept((PyObject*)PyFrame_GetCode(PyEval_GetFrame())); + + Py_ssize_t size = 0; + const char *name = PyUnicode_AsUTF8AndSize(((PyCodeObject *)code.get())->co_name, &size); + if (name == nullptr) { + JP_RAISE_PYTHON(); + } + return std::string_view{name, (size_t)size} == "__init__"sv; +} + static int PyJPField_set(PyJPField *self, PyObject *obj, PyObject *pyvalue) { JP_PY_TRY("PyJPField_set"); @@ -61,8 +105,10 @@ static int PyJPField_set(PyJPField *self, PyObject *obj, PyObject *pyvalue) JPJavaFrame frame = JPJavaFrame::outer(context); if (self->m_Field->isFinal()) { - PyErr_SetString(PyExc_AttributeError, "Field is final"); - return -1; + if (self->m_Field->isStatic() || !isInitializingFinalField(*self->m_Field)) { + PyErr_SetString(PyExc_AttributeError, "Field is final"); + return -1; + } } if (self->m_Field->isStatic()) { @@ -110,8 +156,8 @@ static PyType_Slot fieldSlots[] = { {0} }; -PyTypeObject *PyJPField_Type = nullptr; -PyType_Spec PyJPFieldSpec = { +static PyTypeObject *PyJPField_Type = nullptr; +static PyType_Spec PyJPFieldSpec = { "_jpype._JField", sizeof (PyJPField), 0, @@ -119,10 +165,6 @@ PyType_Spec PyJPFieldSpec = { fieldSlots }; -#ifdef __cplusplus -} -#endif - void PyJPField_initType(PyObject* module) { PyJPField_Type = (PyTypeObject*) PyType_FromSpec(&PyJPFieldSpec); diff --git a/native/python/pyjp_method.cpp b/native/python/pyjp_method.cpp index facf2a994..4b8eb0986 100644 --- a/native/python/pyjp_method.cpp +++ b/native/python/pyjp_method.cpp @@ -17,6 +17,7 @@ #include "pyjp.h" #include "jp_methoddispatch.h" #include "jp_method.h" +#include "pyjp_module.hpp" #ifdef __cplusplus extern "C" @@ -62,7 +63,7 @@ static void PyJPMethod_dealloc(PyJPMethod *self) JP_PY_CATCH_NONE(); // GCOVR_EXCL_LINE } -static PyObject *PyJPMethod_get(PyJPMethod *self, PyObject *obj, PyObject *type) +static PyObject *PyJPMethod_get(PyJPMethod *self, PyObject *obj, PyObject *) { JP_PY_TRY("PyJPMethod_get"); PyJPModule_getContext(); @@ -87,7 +88,7 @@ static PyObject *PyJPMethod_get(PyJPMethod *self, PyObject *obj, PyObject *type) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -static PyObject *PyJPMethod_call(PyJPMethod *self, PyObject *args, PyObject *kwargs) +static PyObject *PyJPMethod_call(PyJPMethod *self, PyObject *args, PyObject *) { JP_PY_TRY("PyJPMethod_call"); JPContext *context = PyJPModule_getContext(); @@ -110,7 +111,7 @@ static PyObject *PyJPMethod_call(PyJPMethod *self, PyObject *args, PyObject *kwa JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -static PyObject *PyJPMethod_matches(PyJPMethod *self, PyObject *args, PyObject *kwargs) +static PyObject *PyJPMethod_matches(PyJPMethod *self, PyObject *args, PyObject *) { JP_PY_TRY("PyJPMethod_matches"); JPContext *context = PyJPModule_getContext(); @@ -150,7 +151,7 @@ static PyObject *PyJPMethod_repr(PyJPMethod *self) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -static PyObject *PyJPMethod_getSelf(PyJPMethod *self, void *ctxt) +static PyObject *PyJPMethod_getSelf(PyJPMethod *self, void *) { JP_PY_TRY("PyJPMethod_getSelf"); PyJPModule_getContext(); @@ -161,12 +162,12 @@ static PyObject *PyJPMethod_getSelf(PyJPMethod *self, void *ctxt) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -static PyObject *PyJPMethod_getNone(PyJPMethod *self, void *ctxt) +static PyObject *PyJPMethod_getNone(PyJPMethod *, void *) { Py_RETURN_NONE; } -static PyObject *PyJPMethod_getName(PyJPMethod *self, void *ctxt) +static PyObject *PyJPMethod_getName(PyJPMethod *self, void *) { JP_PY_TRY("PyJPMethod_getName"); PyJPModule_getContext(); @@ -174,7 +175,7 @@ static PyObject *PyJPMethod_getName(PyJPMethod *self, void *ctxt) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -static PyObject *PyJPMethod_getQualName(PyJPMethod *self, void *ctxt) +static PyObject *PyJPMethod_getQualName(PyJPMethod *self, void *) { JP_PY_TRY("PyJPMethod_getQualName"); PyJPModule_getContext(); @@ -184,7 +185,7 @@ static PyObject *PyJPMethod_getQualName(PyJPMethod *self, void *ctxt) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -static PyObject *PyJPMethod_getDoc(PyJPMethod *self, void *ctxt) +static PyObject *PyJPMethod_getDoc(PyJPMethod *self, void *) { JP_PY_TRY("PyJPMethod_getDoc"); JPContext *context = PyJPModule_getContext(); @@ -198,9 +199,9 @@ static PyObject *PyJPMethod_getDoc(PyJPMethod *self, void *ctxt) // Convert the overloads JP_TRACE("Convert overloads"); const JPMethodList& overloads = self->m_Method->getMethodOverloads(); - JPPyObject ov = JPPyObject::call(PyTuple_New(overloads.size())); + JPPyObject ov = JPPyObject::call(PyTuple_New((Py_ssize_t)overloads.size())); int i = 0; - JPClass* methodClass = frame.findClassByName("java.lang.reflect.Method"); + JPClass* methodClass = frame.findClassByName("java.lang.reflect.Method"sv); for (auto iter = overloads.begin(); iter != overloads.end(); ++iter) { JP_TRACE("Set overload", i); @@ -225,7 +226,7 @@ static PyObject *PyJPMethod_getDoc(PyJPMethod *self, void *ctxt) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -int PyJPMethod_setDoc(PyJPMethod *self, PyObject *obj, void *ctxt) +int PyJPMethod_setDoc(PyJPMethod *self, PyObject *obj, void *) { JP_PY_TRY("PyJPMethod_setDoc"); Py_CLEAR(self->m_Doc); @@ -235,7 +236,7 @@ int PyJPMethod_setDoc(PyJPMethod *self, PyObject *obj, void *ctxt) JP_PY_CATCH(-1); // GCOVR_EXCL_LINE } -PyObject *PyJPMethod_getAnnotations(PyJPMethod *self, void *ctxt) +PyObject *PyJPMethod_getAnnotations(PyJPMethod *self, void *) { JP_PY_TRY("PyJPMethod_getAnnotations"); JPContext *context = PyJPModule_getContext(); @@ -249,9 +250,9 @@ PyObject *PyJPMethod_getAnnotations(PyJPMethod *self, void *ctxt) // Convert the overloads JP_TRACE("Convert overloads"); const JPMethodList& overloads = self->m_Method->getMethodOverloads(); - JPPyObject ov = JPPyObject::call(PyTuple_New(overloads.size())); + JPPyObject ov = JPPyObject::call(PyTuple_New((Py_ssize_t)overloads.size())); int i = 0; - JPClass* methodClass = frame.findClassByName("java.lang.reflect.Method"); + JPClass* methodClass = frame.findClassByName("java.lang.reflect.Method"sv); for (auto iter = overloads.begin(); iter != overloads.end(); ++iter) { JP_TRACE("Set overload", i); @@ -277,7 +278,7 @@ PyObject *PyJPMethod_getAnnotations(PyJPMethod *self, void *ctxt) JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE } -int PyJPMethod_setAnnotations(PyJPMethod *self, PyObject* obj, void *ctx) +int PyJPMethod_setAnnotations(PyJPMethod *self, PyObject* obj, void *) { Py_CLEAR(self->m_Annotations); self->m_Annotations = obj; @@ -285,7 +286,7 @@ int PyJPMethod_setAnnotations(PyJPMethod *self, PyObject* obj, void *ctx) return 0; } -PyObject *PyJPMethod_getCodeAttr(PyJPMethod *self, void *ctx, const char *attr) +PyObject *PyJPMethod_getCodeAttr(PyJPMethod *self, void *, const char *attr) { JP_PY_TRY("PyJPMethod_getCodeAttr"); PyJPModule_getContext(); @@ -314,7 +315,7 @@ PyObject *PyJPMethod_getGlobals(PyJPMethod *self, void *ctxt) return PyJPMethod_getCodeAttr(self, ctxt, "__globals__"); } -PyObject *PyJPMethod_isBeanAccessor(PyJPMethod *self, PyObject *arg) +PyObject *PyJPMethod_isBeanAccessor(PyJPMethod *self, PyObject *) { JP_PY_TRY("PyJPMethod_isBeanAccessor"); PyJPModule_getContext(); @@ -322,7 +323,7 @@ PyObject *PyJPMethod_isBeanAccessor(PyJPMethod *self, PyObject *arg) JP_PY_CATCH(nullptr); } -PyObject *PyJPMethod_isBeanMutator(PyJPMethod *self, PyObject *arg) +PyObject *PyJPMethod_isBeanMutator(PyJPMethod *self, PyObject *) { JP_PY_TRY("PyJPMethod_isBeanMutator"); PyJPModule_getContext(); diff --git a/native/python/pyjp_module.cpp b/native/python/pyjp_module.cpp index 338e92d24..155852e10 100644 --- a/native/python/pyjp_module.cpp +++ b/native/python/pyjp_module.cpp @@ -15,30 +15,14 @@ *****************************************************************************/ #include "jpype.h" #include "pyjp.h" -#include "jp_arrayclass.h" #include "jp_primitive_accessor.h" #include "jp_gc.h" -#include "jp_stringtype.h" -#include "jp_classloader.h" +#include "pyjp_module.hpp" -void PyJPModule_installGC(PyObject* module); +static void PyJPModule_installGC(PyObject* module); bool _jp_cpp_exceptions = false; -extern void PyJPArray_initType(PyObject* module); -extern void PyJPBuffer_initType(PyObject* module); -extern void PyJPClass_initType(PyObject* module); -extern void PyJPField_initType(PyObject* module); -extern void PyJPMethod_initType(PyObject* module); -extern void PyJPMonitor_initType(PyObject* module); -extern void PyJPProxy_initType(PyObject* module); -extern void PyJPObject_initType(PyObject* module); -extern void PyJPNumber_initType(PyObject* module); -extern void PyJPClassHints_initType(PyObject* module); -extern void PyJPPackage_initType(PyObject* module); -extern void PyJPChar_initType(PyObject* module); -extern void PyJPValue_initType(PyObject* module); - static PyObject *PyJPModule_convertBuffer(JPPyBuffer& buffer, PyObject *dtype); // To ensure no leaks (requires C++ linkage) @@ -59,20 +43,24 @@ class JPViewWrapper Py_buffer *view; } ; - -PyObject* _JArray = nullptr; -PyObject* _JChar = nullptr; -PyObject* _JObject = nullptr; -PyObject* _JInterface = nullptr; -PyObject* _JException = nullptr; -PyObject* _JClassPre = nullptr; -PyObject* _JClassPost = nullptr; -PyObject* _JClassDoc = nullptr; -PyObject* _JMethodDoc = nullptr; -PyObject* _JMethodAnnotations = nullptr; -PyObject* _JMethodCode = nullptr; -PyObject* _JObjectKey = nullptr; -PyObject* _JVMNotRunning = nullptr; +PyTypeObject* JAnnotation = NULL; + +PyObject* _JArray = NULL; +PyObject* _JChar = NULL; +PyObject* _JObject = NULL; +PyObject* _JInterface = NULL; +PyObject* _JException = NULL; +PyObject* _JClassPre = NULL; +PyObject* _JClassPost = NULL; +PyObject* _JClassDoc = NULL; +PyObject* _JMethodDoc = NULL; +PyObject* _JMethodAnnotations = NULL; +PyObject* _JMethodCode = NULL; +PyObject* _JObjectKey = NULL; +PyObject* _JVMNotRunning = NULL; +PyObject* _JExtension = NULL; +PyObject* JClass = NULL; +PyObject* _JClassTable = NULL; void PyJPModule_loadResources(PyObject* module) { @@ -121,8 +109,19 @@ void PyJPModule_loadResources(PyObject* module) _JMethodCode = PyObject_GetAttrString(module, "getMethodCode"); JP_PY_CHECK(); Py_INCREF(_JMethodCode); - - _JObjectKey = PyCapsule_New(module, "constructor key", nullptr); + _JClassTable = PyObject_GetAttrString(module, "_JClassTable"); + JP_PY_CHECK(); + Py_INCREF(_JClassTable); + JAnnotation = (PyTypeObject *) PyObject_GetAttrString(module, "JAnnotation"); + JP_PY_CHECK(); + Py_INCREF(JAnnotation); + _JExtension = PyObject_GetAttrString(module, "_JExtension"); + JP_PY_CHECK(); + Py_INCREF(_JExtension); + JClass = PyObject_GetAttrString(module, "JClass"); + JP_PY_CHECK(); + Py_INCREF(JClass); + _JObjectKey = PyCapsule_New(module, "constructor key", NULL); } catch (JPypeException&) // GCOVR_EXCL_LINE { @@ -134,8 +133,7 @@ void PyJPModule_loadResources(PyObject* module) } #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif @@ -288,7 +286,7 @@ static PyObject* PyJPModule_startup(PyObject* module, PyObject* pyargs) JP_PY_CATCH(nullptr); } -static PyObject* PyJPModule_shutdown(PyObject* obj, PyObject* pyargs, PyObject* kwargs) +static PyObject* PyJPModule_shutdown(PyObject*, PyObject* pyargs, PyObject*) { JP_PY_TRY("PyJPModule_shutdown"); char destroyJVM = true; @@ -303,14 +301,14 @@ static PyObject* PyJPModule_shutdown(PyObject* obj, PyObject* pyargs, PyObject* } #endif -static PyObject* PyJPModule_isStarted(PyObject* obj) +static PyObject* PyJPModule_isStarted(PyObject*) { return PyBool_FromLong(JPContext_global->isRunning()); } #ifndef ANDROID -static PyObject* PyJPModule_attachThread(PyObject* obj) +static PyObject* PyJPModule_attachThread(PyObject*) { JP_PY_TRY("PyJPModule_attachThread"); PyJPModule_getContext()->attachCurrentThread(); @@ -318,7 +316,7 @@ static PyObject* PyJPModule_attachThread(PyObject* obj) JP_PY_CATCH(nullptr); } -static PyObject* PyJPModule_attachThreadAsDaemon(PyObject* obj) +static PyObject* PyJPModule_attachThreadAsDaemon(PyObject*) { JP_PY_TRY("PyJPModule_attachThreadAsDaemon"); PyJPModule_getContext()->attachCurrentThreadAsDaemon(); @@ -326,7 +324,7 @@ static PyObject* PyJPModule_attachThreadAsDaemon(PyObject* obj) JP_PY_CATCH(nullptr); } -static PyObject* PyJPModule_detachThread(PyObject* obj) +static PyObject* PyJPModule_detachThread(PyObject*) { JP_PY_TRY("PyJPModule_detachThread"); if (JPContext_global->isRunning()) @@ -336,7 +334,7 @@ static PyObject* PyJPModule_detachThread(PyObject* obj) } #endif -static PyObject* PyJPModule_isThreadAttached(PyObject* obj) +static PyObject* PyJPModule_isThreadAttached(PyObject*) { JP_PY_TRY("PyJPModule_isThreadAttached"); if (!JPContext_global->isRunning()) @@ -356,7 +354,7 @@ static void releaseView(void* view) } } -static PyObject* PyJPModule_convertToDirectByteBuffer(PyObject* self, PyObject* src) +static PyObject* PyJPModule_convertToDirectByteBuffer(PyObject*, PyObject* src) { JP_PY_TRY("PyJPModule_convertToDirectByteBuffer"); JPContext *context = PyJPModule_getContext(); @@ -382,13 +380,13 @@ static PyObject* PyJPModule_convertToDirectByteBuffer(PyObject* self, PyObject* JP_PY_CATCH(nullptr); } -static PyObject* PyJPModule_enableStacktraces(PyObject* self, PyObject* src) +static PyObject* PyJPModule_enableStacktraces(PyObject*, PyObject* src) { _jp_cpp_exceptions = PyObject_IsTrue(src); Py_RETURN_TRUE; } -PyObject *PyJPModule_newArrayType(PyObject *module, PyObject *args) +PyObject *PyJPModule_newArrayType(PyObject *, PyObject *args) { JP_PY_TRY("PyJPModule_newArrayType"); JPContext *context = PyJPModule_getContext(); @@ -415,7 +413,7 @@ PyObject *PyJPModule_newArrayType(PyObject *module, PyObject *args) JP_PY_CATCH(nullptr); } -PyObject *PyJPModule_getClass(PyObject* module, PyObject *obj) +PyObject *PyJPModule_getClass(PyObject*, PyObject *obj) { JP_PY_TRY("PyJPModule_getClass"); JPContext *context = PyJPModule_getContext(); @@ -452,7 +450,7 @@ PyObject *PyJPModule_getClass(PyObject* module, PyObject *obj) JP_PY_CATCH(nullptr); } -PyObject *PyJPModule_hasClass(PyObject* module, PyObject *obj) +PyObject *PyJPModule_hasClass(PyObject*, PyObject *obj) { JP_PY_TRY("PyJPModule_hasClass"); if (!JPContext_global->isRunning()) @@ -481,7 +479,7 @@ PyObject *PyJPModule_hasClass(PyObject* module, PyObject *obj) JP_PY_CATCH(nullptr); } -static PyObject *PyJPModule_arrayFromBuffer(PyObject *module, PyObject *args, PyObject *kwargs) +static PyObject *PyJPModule_arrayFromBuffer(PyObject *, PyObject *args, PyObject *) { JP_PY_TRY("PyJPModule_arrayFromBuffer"); PyObject *source = nullptr; @@ -516,7 +514,7 @@ static PyObject *PyJPModule_arrayFromBuffer(PyObject *module, PyObject *args, Py JP_PY_CATCH(nullptr); } -PyObject *PyJPModule_collect(PyObject* module, PyObject *obj) +PyObject *PyJPModule_collect(PyObject*, PyObject *obj) { JPContext* context = JPContext_global; if (!context->isRunning()) @@ -539,7 +537,7 @@ PyObject *PyJPModule_collect(PyObject* module, PyObject *obj) // GCOVR_EXCL_START -PyObject *PyJPModule_gcStats(PyObject* module, PyObject *obj) +PyObject *PyJPModule_gcStats(PyObject*, PyObject *) { JPContext *context = PyJPModule_getContext(); JPGCStats stats; @@ -562,7 +560,7 @@ PyObject *PyJPModule_gcStats(PyObject* module, PyObject *obj) } // GCOVR_EXCL_STOP -static PyObject* PyJPModule_isPackage(PyObject *module, PyObject *pkg) +static PyObject* PyJPModule_isPackage(PyObject *, PyObject *pkg) { JP_PY_TRY("PyJPModule_isPackage"); if (!PyUnicode_Check(pkg)) @@ -580,7 +578,7 @@ static PyObject* PyJPModule_isPackage(PyObject *module, PyObject *pkg) #if 1 // GCOVR_EXCL_START // This code was used in testing the Java slot memory layout. It serves no purpose outside of debugging that issue. -PyObject* examine(PyObject *module, PyObject *other) +PyObject* examine(PyObject *, PyObject *other) { JP_PY_TRY("examine"); int ret = 0; @@ -594,7 +592,7 @@ PyObject* examine(PyObject *module, PyObject *other) int offset = 0; if (!PyType_Check(other)) { - offset = PyJPValue_getJavaSlotOffset(other); + offset = (int)PyJPValue_getJavaSlotOffset(other); printf(" Object:\n"); printf(" size: %d\n", (int) Py_SIZE(other)); printf(" dictoffset: %d\n", (int) ((long long) _PyObject_GetDictPtr(other)-(long long) other)); @@ -616,7 +614,7 @@ PyObject* examine(PyObject *module, PyObject *other) printf(" alloc: %p\n", type->tp_alloc); printf(" free: %p\n", type->tp_free); printf(" finalize: %p\n", type->tp_finalize); - long v = _PyObject_VAR_SIZE(type, 1)+(PyJPValue_hasJavaSlot(type)?sizeof (JPValue):0); + long v = (long)_PyObject_VAR_SIZE(type, 1)+(PyJPValue_hasJavaSlot(type)? (long)sizeof(JPValue):0); printf(" size?: %ld\n",v); printf("======\n"); @@ -629,7 +627,7 @@ PyObject* examine(PyObject *module, PyObject *other) // GCOVR_EXCL_START int _PyJPModule_trace = 0; -static PyObject* PyJPModule_trace(PyObject *module, PyObject *args) +static PyObject* PyJPModule_trace(PyObject *, PyObject *args) { bool old = _PyJPModule_trace; _PyJPModule_trace = PyLong_AsLong(args); @@ -640,7 +638,7 @@ static PyObject* PyJPModule_trace(PyObject *module, PyObject *args) #ifdef JP_INSTRUMENTATION uint32_t _PyJPModule_fault_code = -1; -static PyObject* PyJPModule_fault(PyObject *module, PyObject *args) +static PyObject* PyJPModule_fault(PyObject *, PyObject *args) { if (args == Py_None) { @@ -733,7 +731,7 @@ PyMODINIT_FUNC PyInit__jpype() #ifdef Py_GIL_DISABLED PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); #endif - PyModule_AddStringConstant(module, "__version__", "1.5.2.dev0"); + PyModule_AddStringConstant(module, "__version__", "2.0.0_dev0"); // Our module will be used for PyFrame object and it is a requirement that // we have a builtins in our dictionary. @@ -812,12 +810,12 @@ static PyObject *PyJPModule_convertBuffer(JPPyBuffer& buffer, PyObject *dtype) Py_ssize_t itemsize = view.itemsize; char *format = view.format; if (format == nullptr) - format = "B"; + format = const_cast("B"); // Standard size for 'l' is 4 in docs, but numpy uses format 'l' for long long if (itemsize == 8 && format[0] == 'l') - format = "q"; + format = const_cast("q"); if (itemsize == 8 && format[0] == 'L') - format = "Q"; + format = const_cast("Q"); if (dtype != nullptr && dtype != Py_None ) { @@ -878,7 +876,7 @@ static PyObject *PyJPModule_convertBuffer(JPPyBuffer& buffer, PyObject *dtype) jint *a = accessor.get(); for (int i = 0; i < view.ndim; ++i) { - a[i] = view.shape[i]; + a[i] = (jint)view.shape[i]; } accessor.commit(); for (int i = 0; i < view.ndim - 1; ++i) @@ -895,7 +893,7 @@ static PyObject *PyJPModule_convertBuffer(JPPyBuffer& buffer, PyObject *dtype) } base = view.len / view.itemsize; } - return pcls->newMultiArray(frame, buffer, subs, base, (jobject) jdims); + return pcls->newMultiArray(frame, buffer, (int)subs, (int)base, (jobject) jdims); } #ifdef JP_INSTRUMENTATION @@ -915,7 +913,7 @@ void PyJPModuleFault_throw(uint32_t code) } #endif -void PyJPModule_installGC(PyObject* module) +static void PyJPModule_installGC(PyObject* module) { // Get the Python garbage collector JPPyObject gc = JPPyObject::call(PyImport_ImportModule("gc")); diff --git a/native/python/pyjp_object.cpp b/native/python/pyjp_object.cpp index 0414a4151..550136551 100644 --- a/native/python/pyjp_object.cpp +++ b/native/python/pyjp_object.cpp @@ -13,8 +13,10 @@ See NOTICE file for details. *****************************************************************************/ +#include "jp_extension.hpp" #include "jpype.h" #include "pyjp.h" +#include "pyjp_module.hpp" #ifdef __cplusplus extern "C" @@ -23,6 +25,7 @@ extern "C" static PyObject *PyJPObject_new(PyTypeObject *type, PyObject *pyargs, PyObject *kwargs) { + (void) kwargs; JP_PY_TRY("PyJPObject_new"); // Get the Java class from the type. JPClass *cls = PyJPClass_getJPClass((PyObject*) type); @@ -32,12 +35,27 @@ static PyObject *PyJPObject_new(PyTypeObject *type, PyObject *pyargs, PyObject * return nullptr; } + if (cls->isAnnotation()) { + // We don't create an underlying Java object + // Instead, we allow it to be used as a function or decorator + auto args = JPPyObject::call(PySequence_Concat(JPPyTuple_Pack(cls->getHost()).get(), pyargs)); + JPPyObject self = JPPyObject::call(JAnnotation->tp_call((PyObject*)JAnnotation, args.get(), kwargs)); + JP_PY_CHECK(); + return self.keep(); + } + // Create an instance (this may fail) JPContext *context = PyJPModule_getContext(); JPJavaFrame frame = JPJavaFrame::outer(context); JPPyObjectVector args(pyargs); JPValue jv = cls->newInstance(frame, args); + if (cls->isExtension()) { + // extension allocates the wrapper when its Java constructor is called + // this allows it to be constructed from either Python or Java + return static_cast(cls)->getPythonObject(frame, jv); + } + // If it succeeded then allocate memory PyObject *self = type->tp_alloc(type, 0); JP_PY_CHECK(); @@ -48,7 +66,7 @@ static PyObject *PyJPObject_new(PyTypeObject *type, PyObject *pyargs, PyObject * JP_PY_CATCH(nullptr); } -static PyObject *PyJPObject_compare(PyObject *self, PyObject *other, int op) +static PyObject *PyJPObject_compare(PyObject *self, PyObject *other, int op) // NOLINT(misc-no-recursion) { JP_PY_TRY("PyJPObject_compare"); if (op == Py_NE) @@ -164,7 +182,7 @@ static PyObject *PyJPComparable_compare(PyObject *self, PyObject *other, int op) } else if (!null1 && javaSlot1 != nullptr && !javaSlot1->getClass()->isPrimitive()) obj1 = javaSlot1->getValue().l; - switch (op) + switch (op) // NOLINT(bugprone-switch-missing-default-case) { case Py_EQ: if (null0 && null1) @@ -223,6 +241,9 @@ static PyObject *PyJPObject_repr(PyObject *self) static PyObject *PyJPObject_initSubclass(PyObject *cls, PyObject* args, PyObject *kwargs) { + (void) cls; + (void) args; + (void) kwargs; Py_RETURN_NONE; } diff --git a/native/python/pyjp_package.cpp b/native/python/pyjp_package.cpp index 1583510a6..0f6dd2d50 100644 --- a/native/python/pyjp_package.cpp +++ b/native/python/pyjp_package.cpp @@ -339,7 +339,7 @@ void PyJPPackage_initType(PyObject* module) { // Inherit from module. JPPyObject bases = JPPyTuple_Pack(&PyModule_Type); - packageSpec.basicsize = PyModule_Type.tp_basicsize; + packageSpec.basicsize = (int)PyModule_Type.tp_basicsize; PyJPPackage_Type = (PyTypeObject*) PyType_FromSpecWithBases(&packageSpec, bases.get()); JP_PY_CHECK(); PyModule_AddObject(module, "_JPackage", (PyObject*) PyJPPackage_Type); diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index f3b167d6c..19f83a05b 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -13,12 +13,17 @@ See NOTICE file for details. *****************************************************************************/ +#include "jp_class.h" #include "jpype.h" +#include "object.h" #include "pyjp.h" -#include "jp_stringtype.h" +#include "jp_stringtype.h" // IWYU pragma: keep +#include "pyjp_module.hpp" #include #include +using namespace std::literals; + #ifdef __cplusplus extern "C" { @@ -49,7 +54,7 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) JP_PY_TRY("PyJPValue_alloc"); #if PY_VERSION_HEX >= 0x030d0000 - // This flag will try to place the dictionary are part of the object which + // This flag will try to place the dictionary are part of the object which // adds an unknown number of bytes to the end of the object making it impossible // to attach our needed data. If we kill the flag then we get usable behavior. if (PyType_HasFeature(type, Py_TPFLAGS_INLINE_VALUES)) { @@ -59,13 +64,13 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) #endif PyObject* obj = nullptr; - { + { std::lock_guard lock(mtx); - // Mutate the allocator type + // Mutate the allocator type PyJPAlloc_Type->tp_flags = type->tp_flags; - PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + sizeof (JPValue); + PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + (Py_ssize_t)sizeof(JPValue); PyJPAlloc_Type->tp_itemsize = type->tp_itemsize; - + // Create a new allocation for the dummy type obj = PyType_GenericAlloc(PyJPAlloc_Type, nitems); } @@ -104,22 +109,22 @@ Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self) Py_ssize_t offset = 0; Py_ssize_t sz = 0; - + #if PY_VERSION_HEX>=0x030c0000 // starting in 3.12 there is no longer ob_size in PyLong if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS)) - sz = (((PyLongObject*)self)->long_value.lv_tag) >> 3; // Private NON_SIZE_BITS - else + sz = (Py_ssize_t)(((PyLongObject*)self)->long_value.lv_tag) >> 3; // Private NON_SIZE_BITS + else #endif if (type->tp_itemsize != 0) sz = Py_SIZE(self); // PyLong abuses ob_size with negative values prior to 3.12 - if (sz < 0) + if (sz < 0) // NOLINT sz = -sz; if (type->tp_itemsize == 0) - offset = _PyObject_VAR_SIZE(type, 1); + offset = (Py_ssize_t)_PyObject_VAR_SIZE(type, 1); else - offset = _PyObject_VAR_SIZE(type, sz + 1); + offset = (Py_ssize_t)_PyObject_VAR_SIZE(type, sz + 1); return offset; } @@ -176,6 +181,10 @@ void PyJPValue_finalize(void* obj) JP_TRACE("Dereference object"); context->ReleaseGlobalRef(value->getValue().l); *value = JPValue(); + if (Py_TYPE(obj) == PyJPClass_Type && cls->isExtension()) { + // the Python type object has ownership of the JPExtensionType + delete cls; + } } JP_PY_CATCH_NONE(); } @@ -201,7 +210,7 @@ PyObject* PyJPValue_str(PyObject* self) } if (value->getValue().l == nullptr) - return JPPyString::fromStringUTF8("null").keep(); + return JPPyString::fromStringUTF8("null"sv).keep(); if (cls == context->_java_lang_String) { @@ -240,6 +249,9 @@ PyObject *PyJPValue_getattro(PyObject *obj, PyObject *name) return nullptr; } + const char *name_tmp = PyUnicode_AsUTF8(name); + (void) name_tmp; + // Private members are accessed directly PyObject* pyattr = PyBaseObject_Type.tp_getattro(obj, name); if (pyattr == nullptr) @@ -273,6 +285,12 @@ int PyJPValue_setattro(PyObject *self, PyObject *name, PyObject *value) JPPyObject f = JPPyObject::accept(PyJP_GetAttrDescriptor(Py_TYPE(self), name)); if (f.isNull()) { + JPClass *cls = PyJPClass_getJPClass((PyObject*) Py_TYPE(self)); + if (cls != nullptr && cls->isExtension()) { + // allow it for extension classes + // we only need to do setattr and it will be found later via getattr + return PyObject_GenericSetAttr(self, name, value); + } PyErr_Format(PyExc_AttributeError, "Field '%U' is not found", name); return -1; } @@ -333,12 +351,12 @@ bool PyJPValue_isSetJavaSlot(PyObject* self) } /***************** Create a dummy type for use when allocating. ************************/ -static int PyJPAlloc_traverse(PyObject *self, visitproc visit, void *arg) +static int PyJPAlloc_traverse(PyObject *, visitproc, void *) { return 0; } -static int PyJPAlloc_clear(PyObject *self) +static int PyJPAlloc_clear(PyObject *) { return 0; } @@ -360,6 +378,7 @@ static PyType_Spec allocSpec = { void PyJPValue_initType(PyObject* module) { + (void) module; PyObject *bases = PyTuple_Pack(1, &PyBaseObject_Type); PyJPAlloc_Type = (PyTypeObject*) PyType_FromSpecWithBases(&allocSpec, bases); Py_DECREF(bases); diff --git a/project/jpype_cpython/nbproject/configurations.xml b/project/jpype_cpython/nbproject/configurations.xml index 158b734f0..cd41fdc12 100755 --- a/project/jpype_cpython/nbproject/configurations.xml +++ b/project/jpype_cpython/nbproject/configurations.xml @@ -57,10 +57,7 @@ - + ../../jpype/__init__.py ../../jpype/_classpath.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 @@ -615,6 +613,11 @@ tool="1" flavor2="0"> + + '3': platform_specific['extra_compile_args'] = [ - '/Zi', '/EHsc', f'/std:c++14'] + '/Zi', '/EHsc', f'/std:c++17'] else: platform_specific['extra_compile_args'] = ['/Zi', '/EHsc'] if hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled(): diff --git a/test/harness/jpype/annotation/TestAnnotation.java b/test/harness/jpype/annotation/TestAnnotation.java new file mode 100644 index 000000000..2eaf4834f --- /dev/null +++ b/test/harness/jpype/annotation/TestAnnotation.java @@ -0,0 +1,10 @@ +package jpype.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface TestAnnotation { + String name(); + int value() default 0; +} diff --git a/test/harness/jpype/annotation/TestInvalidRetention.java b/test/harness/jpype/annotation/TestInvalidRetention.java new file mode 100644 index 000000000..294171eec --- /dev/null +++ b/test/harness/jpype/annotation/TestInvalidRetention.java @@ -0,0 +1,9 @@ +package jpype.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.SOURCE) +public @interface TestInvalidRetention { + +} diff --git a/test/harness/jpype/annotation/TestMarkerAnnotation.java b/test/harness/jpype/annotation/TestMarkerAnnotation.java new file mode 100644 index 000000000..a75ab8aa4 --- /dev/null +++ b/test/harness/jpype/annotation/TestMarkerAnnotation.java @@ -0,0 +1,8 @@ +package jpype.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface TestMarkerAnnotation { +} diff --git a/test/harness/jpype/annotation/TestSimpleAnnotation.java b/test/harness/jpype/annotation/TestSimpleAnnotation.java new file mode 100644 index 000000000..d04d40a41 --- /dev/null +++ b/test/harness/jpype/annotation/TestSimpleAnnotation.java @@ -0,0 +1,10 @@ +package jpype.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface TestSimpleAnnotation { + + int value(); +} diff --git a/test/harness/jpype/extension/TestBase.java b/test/harness/jpype/extension/TestBase.java new file mode 100644 index 000000000..ccbf72452 --- /dev/null +++ b/test/harness/jpype/extension/TestBase.java @@ -0,0 +1,32 @@ +package jpype.extension; + +public abstract class TestBase { + + public int initCount = 0; + protected int protectedField = 0; + private int privateBaseField = 0; + + public TestBase() { + initCount++; + } + + public TestBase(int i) { + initCount++; + } + + public TestBase(Object o) { + initCount++; + } + + public int identity(int i) { + return i; + } + + public Object identity(Object o) { + return o; + } + + public int getPrivateBaseField() { + return privateBaseField; + } +} diff --git a/test/harness/jpype/extension/TestDanglingObject.java b/test/harness/jpype/extension/TestDanglingObject.java new file mode 100644 index 000000000..3d11736e3 --- /dev/null +++ b/test/harness/jpype/extension/TestDanglingObject.java @@ -0,0 +1,21 @@ +package jpype.extension; + +public class TestDanglingObject { + + private static TestDanglingObject instance; + private final Object ref; + + public TestDanglingObject(Object ref) { + this.ref = ref; + instance = this; + } + + public static String test() { + return instance.ref.toString(); + } + + @SuppressWarnings("deprecation") + public static Object newInstance() throws Throwable { + return instance.ref.getClass().newInstance(); + } +} diff --git a/test/jpypetest/common.py b/test/jpypetest/common.py index 10e62e60c..c47623ea7 100644 --- a/test/jpypetest/common.py +++ b/test/jpypetest/common.py @@ -122,6 +122,7 @@ def setUp(self): jpype.addClassPath(pathlib.Path("test/jar/*").absolute()) classpath_arg %= jpype.getClassPath() args.append(classpath_arg) + args.append("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005") _jpype.enableStacktraces(True) #JPypeTestCase.str_conversion = eval(os.getenv('JPYPE_STR_CONVERSION', 'True')) jpype.startJVM(jvm_path, *args, diff --git a/test/jpypetest/test_annotation.py b/test/jpypetest/test_annotation.py new file mode 100644 index 000000000..c72c9f913 --- /dev/null +++ b/test/jpypetest/test_annotation.py @@ -0,0 +1,82 @@ +# ***************************************************************************** +# +# 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. +# +# ***************************************************************************** +from __future__ import annotations +import common +from jpype import JParameterAnnotation +from jpype._jclass import * +from jpype.types import * +from jpype.imports import * + +# NOTE: Testing whether or not they are actually applied will be done in the extension tests + +class JAnnotationTestCase(common.JPypeTestCase): + + def setUp(self): + common.JPypeTestCase.setUp(self) + + def testMissingElement(self): + TestAnnotation = JClass("jpype.annotation.TestAnnotation") + with self.assertRaises(KeyError): + TestAnnotation() + + def testInvalidElement(self): + TestAnnotation = JClass("jpype.annotation.TestAnnotation") + with self.assertRaises(KeyError): + TestAnnotation(test="test") + + def testConstructAnnotation(self): + TestAnnotation = JClass("jpype.annotation.TestAnnotation") + TestAnnotation(name="test") + + def testConstructSimpleAnnotation(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + # chosen by fair dice roll. + # guaranteed to be random. + TestSimpleAnnotation(4) + + def testConstructSimpleAnnotationExplicit(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + TestSimpleAnnotation(value=4) + + def testSimpleAnnotationInvalidElement(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + with self.assertRaises(KeyError): + TestSimpleAnnotation(test=4) + + def testParameterAnnotation(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + + @JParameterAnnotation("arg1", TestSimpleAnnotation(4)) + def fun(arg1): + ... + + def testChainedAnnotations(self): + TestAnnotation = JClass("jpype.annotation.TestAnnotation") + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + + @TestAnnotation(name="test") + @TestSimpleAnnotation(4) + def fun(): + ... + + def testMarkerAnnotation(self): + TestMarkerAnnotation = JClass("jpype.annotation.TestMarkerAnnotation") + + @TestMarkerAnnotation + def fun(): + ... diff --git a/test/jpypetest/test_attr.py b/test/jpypetest/test_attr.py index 266fdd80e..c807a9fbd 100755 --- a/test/jpypetest/test_attr.py +++ b/test/jpypetest/test_attr.py @@ -17,8 +17,6 @@ # ***************************************************************************** import jpype from jpype import JString, java, JArray, JClass -import sys -import time import common @@ -133,11 +131,7 @@ def testCallSuperclassMethod(self): def testCallWithLong(self): h = JClass('jpype.attr.Test1')() - if sys.version > '3': - l = int(123) - else: - l = long(123) - + l = int(123) h.setByte(l) self.assertEqual(l, h.mByteValue) h.setShort(l) @@ -149,11 +143,7 @@ def testCallWithLong(self): def testCallWithBigLong(self): h = JClass('jpype.attr.Test1')() - if sys.version > '3': - l = int(4398046511103) - else: - l = long(4398046511103) - + l = int(4398046511103) self.assertRaises(OverflowError, h.setByte, l) self.assertRaises(OverflowError, h.setShort, l) self.assertRaises(OverflowError, h.setInt, l) @@ -162,11 +152,7 @@ def testCallWithBigLong(self): def testCallWithBigInt(self): h = JClass('jpype.attr.Test1')() - if sys.version > '3' or sys.maxint > 2**31: - l = int(4398046511103) - else: - l = long(4398046511103) - + l = int(4398046511103) self.assertRaises(OverflowError, h.setByte, l) self.assertRaises(OverflowError, h.setShort, l) self.assertRaises(OverflowError, h.setInt, l) @@ -185,16 +171,10 @@ def testSetBoolean(self): self.assertEqual(True, h.mBooleanValue) h.setBoolean(0) self.assertEqual(False, h.mBooleanValue) - if sys.version > '3': - l = int(4398046511103) - else: - l = long(4398046511103) + l = int(4398046511103) h.setBoolean(l) self.assertEqual(True, h.mBooleanValue) - if sys.version > '3': - l = int(0) - else: - l = long(0) + l = int(0) h.setBoolean(l) self.assertEqual(False, h.mBooleanValue) @@ -207,10 +187,7 @@ def testCharAttribute(self): h.charValue = u'b' self.assertEqual(h.charValue, 'b') - if sys.version < '3': - exp_repr = "u'b'" - else: - exp_repr = "'b'" + exp_repr = "'b'" self.assertEqual(repr(h.charValue), exp_repr) def testGetPrimitiveType(self): @@ -261,6 +238,7 @@ def testSuperToString(self): # jpype.ConversionConfig.string = True def testComplexMethodOvlerloading(self): + # FIXME c = JClass('jpype.attr.TestOverloadC')() self.assertEqual(c.foo(1), "foo(int) in C: 1") self.assertEqual(c.foo(), "foo() in A") diff --git a/test/jpypetest/test_extension.py b/test/jpypetest/test_extension.py new file mode 100644 index 000000000..8eae15e13 --- /dev/null +++ b/test/jpypetest/test_extension.py @@ -0,0 +1,1072 @@ +# ***************************************************************************** +# +# 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. +# +# ***************************************************************************** +from __future__ import annotations +import common +import gc +from jpype._jclass import * +from jpype.types import * +from jpype.imports import * +from jpype import JParameterAnnotation, synchronized +import importlib.abc +import importlib.util +import textwrap +import sys +import typing + + +class JExtensionTestCase(common.JPypeTestCase): + + def setUp(self): + common.JPypeTestCase.setUp(self) + + def testExtendObject(self): + from java.lang import Object + class MyObject(Object): + + @JPublic + def __init__(self): + ... + + self.assertIsInstance(MyObject(), MyObject) + + def testClassAttribute(self): + from java.lang import Class, Object + class MyObject(Object): + + @JPublic + def __init__(self): + ... + + self.assertIsInstance(MyObject.class_, Class) + self.assertIs(MyObject, JClass(MyObject)) + + def testSameObject(self): + from java.lang import Object + class MyObject(Object): + + @JPublic + def __init__(self): + ... + + def get_self(self) -> Object: + return self + + obj = MyObject() + self.assertIs(obj, obj.get_self()) + + def testAddedMethod(self): + from java.lang import Object + class MyObject(Object): + + @JPublic + def __init__(self): + ... + + @JPublic + def func(self) -> JObject: + return self + + o = MyObject() + self.assertIs(o.func(), o) + + def testInitOnce(self): + TestBase = JClass("jpype.extension.TestBase") + class MyObject(TestBase): + + def __init__(self): + super().__init__() + self.initCount += 1 + + @JPublic + def __init__(self): + self.initCount += 1 + + @JPublic + def get_self(self) -> JObject: + return self + + o = MyObject().get_self() + # once in TestBase constructor, then in the __init__ callback from constructor + # then again when the Python __init__ is called + self.assertEqual(o.initCount, 3) + + def testOverrideSimple(self): + from java.lang import Object, String + class MyObject(Object): + + @JPublic + def __init__(self): + ... + + @JPublic + @JOverride + def toString(self) -> String: + return "test" + + self.assertEqual(str(MyObject()), "test") + + def testOverloads(self): + mode = -1 + + class MyObject(JClass("jpype.extension.TestBase")): + + def __init__(self, *args, **kwargs): + pass + + @JPublic + def __init__(self): + nonlocal mode + mode = 0 + + @JPublic + def __init__(self, i: JInt): + nonlocal mode + mode = 1 + + @JPublic + def __init__(self, o: JObject): + nonlocal mode + mode = 2 + + @JPublic + @JOverride + def identity(self, i: JInt) -> JInt: + return 0 + + @JPublic + @JOverride + def identity(self, o: JObject) -> JObject: + return None + + @JPublic + def func(self) -> JObject: + return self + + o = MyObject() + self.assertEqual(mode, 0) + MyObject(JInt(1)) + self.assertEqual(mode, 1) + MyObject(JObject()) + self.assertEqual(mode, 2) + self.assertEqual(o.identity(JInt(1)), 0) + self.assertEqual(o.identity(JObject()), None) + + def testSupercall(self): + TestBase = JClass("jpype.extension.TestBase") + class MyObject(TestBase): + + @JPublic + def __init__(self): + pass + + @JPublic + @JOverride + def identity(self, o: JObject) -> JObject: + return None # type: ignore[return-value] + + @JPublic + def super_identity(self, o: JObject) -> JObject: + return super().identity(o) + + def test_identity(self, o: JObject) -> JObject: + return super().identity(o) + + def test_identity_explicit(self, o: JObject) -> JObject: + return super(TestBase, self).identity(o) + + def get_super(self): + return super() + + def get_explicit_super(self): + return super(TestBase, self) + + o = MyObject() + sentinel = MyObject() + self.assertIs(o.test_identity(sentinel), sentinel) + self.assertIs(o.super_identity(sentinel), sentinel) + + + def testPythonMembers(self): + class MyObject(JClass("jpype.extension.TestBase")): + + def __init__(self): + self.a = 0 + self.b = 1 + + @JPublic + def __init__(self): + ... + + @JPublic + def get_self(self) -> JObject: + return self + + o = MyObject() + o2 = o.get_self() + self.assertIs(o, o2) + self.assertEqual(o.a, 0) + self.assertEqual(o.b, 1) + + + def testConstructFromJava(self): + class MyObject(JClass("jpype.extension.TestBase")): + + def __init__(self): + self.a = 0 + self.b = 1 + + @JPublic + def __init__(self): + # caveat Python __init__ isn't called when constructed from Java + self.__init__() + + @JPublic + def get_self(self) -> JObject: + return self + + o = MyObject.class_.newInstance() + o2 = o.get_self() + self.assertIs(o, o2) + self.assertEqual(o.a, 0) + self.assertEqual(o.b, 1) + + def testProtectedField(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + def get_protected_field(self): + return self.protectedField + + o = MyObject() + o.get_protected_field() + + def testProtectedFieldExternalAccess(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + o = MyObject() + with self.assertRaises(AttributeError): + o.protectedField + + def testPrivateBaseField(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + def get_private_field(self): + return self.privateBaseField + + o = MyObject() + with self.assertRaises(AttributeError): + o.get_private_field() + + def testPublicField(self): + class MyObject(JClass("jpype.extension.TestBase")): + myField: typing.Annotated[JInt, JPublic] + + def __init__(self): + self.pythonMember = None + + @JPublic + def __init__(self): + # java exposed constructor, called before python __init__ + ... + + o = MyObject() + o.myField + + def testPublicFieldWithValue(self): + from java.lang import IllegalArgumentException + with self.assertRaises(IllegalArgumentException): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPublic] = JInt(1) + + @JPublic + def __init__(self): + ... + + def testPublicFinalField(self): + unittest = self + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPublic, JFinal] + + @JPublic + def __init__(self): + self.test = 1 + unittest.assertEqual(self.test, 1) + self.test = 2 + unittest.assertEqual(self.test, 2) + + o = MyObject() + with self.assertRaises(AttributeError): + o.test = 3 + + + def testPrivateField(self): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPrivate] + + @JPublic + def __init__(self): + ... + + def get_private_field(self): + return self.test + + o = MyObject() + o.get_private_field() + + def testPrivateFieldExternalAccess(self): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPrivate] + + @JPublic + def __init__(self): + ... + + def get_private_field(self): + return self.test + + o = MyObject() + with self.assertRaises(AttributeError): + o.test + + def testPublicStaticField(self): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPublic, JStatic] + + @JPublic + def __init__(self): + ... + + o = MyObject() + o.test + + def testPublicStaticFieldWithValue(self): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPublic, JStatic] = JInt(1) + + @JPublic + def __init__(self): + ... + + o = MyObject() + self.assertEqual(o.test, 1) + o.test = 2 + self.assertEqual(o.test, 2) + + def testPublicStaticFinalFieldWithValue(self): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPublic, JStatic, JFinal] = JInt(1) + + @JPublic + def __init__(self): + ... + + o = MyObject() + self.assertEqual(o.test, 1) + with self.assertRaises(AttributeError): + o.test = 2 + + def testPrivateStaticField(self): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPrivate, JStatic] + + @JPublic + def __init__(self): + ... + + def get_private_field(self): + return self.test + + o = MyObject() + o.get_private_field() + + def testPrivateStaticFieldExternalAccess(self): + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPrivate, JStatic] + + @JPublic + def __init__(self): + ... + + def get_private_field(self): + return self.test + + o = MyObject() + with self.assertRaises(AttributeError): + o.test + + def testPrivateMethod(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + def private_method(self) -> JObject: + return self + + def call_private_method(self): + return self.private_method() + + o = MyObject() + o.call_private_method() + + def testPrivateMethodExternalAccess(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + def private_method(self) -> JObject: + return self + + o = MyObject() + with self.assertRaises(AttributeError): + o.private_method() + + def testPrivateStaticMethod(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JInt): + self.assertIs(cls, MyObject) + + def call_private_method(self): + return self.private_method(0) + + o = MyObject() + o.call_private_method() + + def testPrivateStaticMethodFromClassmethod(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @classmethod + def private_method(cls, v: JInt): + self.assertIs(cls, MyObject) + + def call_private_method(self): + return self.private_method(0) + + o = MyObject() + o.call_private_method() + + def testFinalMethod(self): + from java.lang import IncompatibleClassChangeError, Object, String + class MyBaseObject(Object): + + @JPublic + def __init__(self): + ... + + @JPublic + @JFinal + def toString(self) -> String: + return "test" + + # if this is raised then it means the method was marked as final + with self.assertRaises(IncompatibleClassChangeError): + class MyObject(MyBaseObject): + + @JPublic + @JOverride + def toString(self) -> String: + return "fail" + + def testThrows(self): + from java.lang import Object, UnsupportedOperationException, IllegalArgumentException + from java.lang import IllegalCallerException, Throwable, RuntimeException + class MyObject(Object): + + @JPublic + @JThrows(UnsupportedOperationException, IllegalArgumentException) + @JThrows(IllegalCallerException) + @JThrows(Throwable, RuntimeException) + def __init__(self): + ... + + ctor = MyObject.class_.getDeclaredConstructors()[0] + exceptions = ( + UnsupportedOperationException, IllegalArgumentException, + IllegalCallerException, + Throwable, RuntimeException + ) + self.assertEqual(tuple(ctor.getExceptionTypes()), exceptions) + + + def testThrownException(self): + from java.lang import Object, String, Throwable + class MyObject(Object): + + @JPublic + def __init__(self): + ... + + @JPublic + def toString(self) -> String: + raise Throwable("fdnvdnkvnrne") + + o = MyObject() + with self.assertRaises(Throwable): + str(o) + + + def testExtensionLoaderMutipleClassDefinition(self): + class TestLoader(importlib.abc.InspectLoader): + + def get_source(self, _): + return textwrap.dedent( + """\ + from jpype import JPublic + from java.lang import Object + class TemporaryObject(Object): + + @JPublic + def __init__(self): + ... + + """ + ) + + def create_class(): + loader = TestLoader() + spec = importlib.util.spec_from_loader("__main__", loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + + create_class() + create_class() + + def testExtensionLoaderCleanup(self): + JPypeContext = JClass("org.jpype.JPypeContext") + manager = JPypeContext.getInstance().getTypeManager() + + class TestLoader(importlib.abc.InspectLoader): + + def get_source(self, _): + return textwrap.dedent( + """\ + from jpype import JPublic + from java.lang import Object + class TemporaryObject(Object): + + @JPublic + def __init__(self): + ... + + """ + ) + + loader = TestLoader() + + # NOTE: module instances are never collected when debugging + spec = importlib.util.spec_from_loader("__main__", loader) + m = importlib.util.module_from_spec(spec) + start = manager.classMap.size() + loader.exec_module(m) + end = manager.classMap.size() + self.assertGreater(end, start) + + # ensure it can be collected + m.__dict__.clear() + del spec + del loader + + # collect it + gc.collect() + + # check it was collected + with synchronized(manager): + self.assertLess(manager.classMap.size(), end) + + def testExtensionCleanupWithDanglingPythonReference(self): + JPypeContext = JClass("org.jpype.JPypeContext") + manager = JPypeContext.getInstance().getTypeManager() + + class TestLoader(importlib.abc.InspectLoader): + + def get_source(self, _): + return textwrap.dedent( + """\ + from jpype import JPublic + from java.lang import Object, String + class TemporaryObject(Object): + + @JPublic + def __init__(self): + ... + + @JPublic + def toString(self) -> String: + from java.lang import String + return String("failed") + + def __repr__(self): + from java.lang import String + return String.format("%s", self) + + """ + ) + + loader = TestLoader() + + # NOTE: module instances are never collected when debugging + spec = importlib.util.spec_from_loader("__main__", loader) + m = importlib.util.module_from_spec(spec) + start = manager.classMap.size() + loader.exec_module(m) + TemporaryObject = getattr(m, "TemporaryObject") + cls = TemporaryObject.class_ + obj = TemporaryObject() + end = manager.classMap.size() + self.assertGreater(end, start) + + # ensure the loader can be collected + m.__dict__.clear() + del spec + del loader + del m + + # collect it + gc.collect() + + from java.lang import System + System.gc() + + with self.assertRaises(TypeError): + TemporaryObject() + + from java.lang import InstantiationException + with self.assertRaises(InstantiationException): + cls.newInstance() + + from java.lang import IllegalStateException + with self.assertRaises(IllegalStateException): + str(obj) + + del obj + del TemporaryObject + del cls + gc.collect() + + + def testExtensionCleanupWithDanglingJavaReference(self): + JPypeContext = JClass("org.jpype.JPypeContext") + manager = JPypeContext.getInstance().getTypeManager() + + class TestLoader(importlib.abc.InspectLoader): + + def get_source(self, _): + return textwrap.dedent( + """\ + from jpype import JClass, JPublic + from java.lang import Object, String + class TemporaryObject(Object): + + @JPublic + def __init__(self): + ... + + @JPublic + def toString(self) -> String: + ... + + obj = JClass("jpype.extension.TestDanglingObject")(TemporaryObject()) + """ + ) + + loader = TestLoader() + + # NOTE: module instances are never collected when debugging + spec = importlib.util.spec_from_loader("__main__", loader) + m = importlib.util.module_from_spec(spec) + start = manager.classMap.size() + loader.exec_module(m) + obj = getattr(m, "obj") + end = manager.classMap.size() + self.assertGreater(end, start) + + # ensure the loader can be collected + m.__dict__.clear() + del spec + del loader + + # collect it + gc.collect() + + # check it was collected + with synchronized(manager): + self.assertLess(manager.classMap.size(), end) + + from java.lang import InstantiationException + with self.assertRaises(InstantiationException): + obj.newInstance() + + from java.lang import IllegalStateException + with self.assertRaises(IllegalStateException): + obj.test() + + + def testExtensionFromBuiltinLoader(self): + class TestLoader(importlib.abc.InspectLoader): + + def get_source(self, _): + return textwrap.dedent( + """\ + from jpype import JPublic + from java.lang import Object + class TemporaryObject(Object): + + @JPublic + def __init__(self): + ... + + """ + ) + + loader = TestLoader() + spec = importlib.util.spec_from_loader("dummy", loader) + m = importlib.util.module_from_spec(spec) + sys.modules["dummy"] = m + loader.exec_module(m) + sys.modules.pop("dummy") + + def testExtensionFromBuiltinLoaderReloaded(self): + class TestLoader(importlib.abc.InspectLoader): + + def get_source(self, _): + return textwrap.dedent( + """\ + from jpype import JPublic + from java.lang import Object + class TemporaryObject(Object): + + @JPublic + def __init__(self): + ... + + """ + ) + + loader = TestLoader() + spec = importlib.util.spec_from_loader("dummy", loader) + m = importlib.util.module_from_spec(spec) + sys.modules["dummy"] = m + loader.exec_module(m) + # simulating importlib.reload + loader.exec_module(m) + sys.modules.pop("dummy") + + def testPublicFieldWithAnnotation(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPublic] = TestSimpleAnnotation(4) + + @JPublic + def __init__(self): + ... + + field = MyObject.class_.getDeclaredFields()[2] + annotation = field.getAnnotation(TestSimpleAnnotation) + self.assertIsNotNone(annotation) + self.assertEqual(annotation.value(), 4) + + def testPublicFieldWithMultipleAnnotations(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + TestMarkerAnnotation = JClass("jpype.annotation.TestMarkerAnnotation") + class MyObject(JClass("jpype.extension.TestBase")): + test: typing.Annotated[JInt, JPublic] = ( + TestSimpleAnnotation(4), + TestMarkerAnnotation + ) + + @JPublic + def __init__(self): + ... + + field = MyObject.class_.getDeclaredFields()[2] + annotation = field.getAnnotation(TestSimpleAnnotation) + self.assertIsNotNone(annotation) + self.assertEqual(annotation.value(), 4) + annotation = field.getAnnotation(TestMarkerAnnotation) + self.assertIsNotNone(annotation) + + def testPublicConstructorWithAnnotation(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + @TestSimpleAnnotation(4) + def __init__(self): + ... + + ctor = MyObject.class_.getDeclaredConstructors()[0] + annotation = ctor.getAnnotation(TestSimpleAnnotation) + self.assertIsNotNone(annotation) + self.assertEqual(annotation.value(), 4) + + + def testPublicMethodWithAnnotation(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPublic + @TestSimpleAnnotation(4) + def fun(self): + ... + + method = MyObject.class_.getDeclaredMethods()[0] + annotation = method.getAnnotation(TestSimpleAnnotation) + self.assertIsNotNone(annotation) + self.assertEqual(annotation.value(), 4) + + def testPublicMethodWithParameterAnnotation(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPublic + @JParameterAnnotation("annotation_value", TestSimpleAnnotation(4)) + def fun(self, annotation_value: JLong): + ... + + method = MyObject.class_.getDeclaredMethods()[0] + annotation = method.getParameterAnnotations()[0][0] + self.assertIsNotNone(annotation) + self.assertEqual(annotation.value(), 4) + + def testPublicClassWithAnnotation(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + class MyObject(JClass("jpype.extension.TestBase")): + + __jannotations__ = TestSimpleAnnotation(4) + + @JPublic + def __init__(self): + ... + + annotation = MyObject.class_.getAnnotation(TestSimpleAnnotation) + self.assertIsNotNone(annotation) + self.assertEqual(annotation.value(), 4) + + def testPublicClassWithMultipleAnnotations(self): + TestSimpleAnnotation = JClass("jpype.annotation.TestSimpleAnnotation") + TestMarkerAnnotation = JClass("jpype.annotation.TestMarkerAnnotation") + class MyObject(JClass("jpype.extension.TestBase")): + + __jannotations__ = ( + TestSimpleAnnotation(4), + TestMarkerAnnotation + ) + + @JPublic + def __init__(self): + ... + + annotation = MyObject.class_.getAnnotation(TestSimpleAnnotation) + self.assertIsNotNone(annotation) + self.assertEqual(annotation.value(), 4) + annotation = MyObject.class_.getAnnotation(TestMarkerAnnotation) + self.assertIsNotNone(annotation) + + + def testPrimitiveParameterBool(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JBoolean): + self.assertIs(cls, MyObject) + + def testPrimitiveParameterByte(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JByte): + self.assertIs(cls, MyObject) + + def testPrimitiveParameterChar(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JChar): + self.assertIs(cls, MyObject) + + def testPrimitiveParameterShort(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JShort): + self.assertIs(cls, MyObject) + + def testPrimitiveParameterInt(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JInt): + self.assertIs(cls, MyObject) + + def testPrimitiveParameterLong(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JLong): + self.assertIs(cls, MyObject) + + def testPrimitiveParameterFloat(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JFloat): + self.assertIs(cls, MyObject) + + def testPrimitiveParameterDouble(self): + class MyObject(JClass("jpype.extension.TestBase")): + + @JPublic + def __init__(self): + ... + + @JPrivate + @JStatic + def private_method(cls, v: JDouble): + self.assertIs(cls, MyObject) + + def testBaseCast(self): + TestBase = JClass("jpype.extension.TestBase") + class MyObject(TestBase): + + @JPublic + def __init__(self): + ... + + @JPublic + def identity(self, i: JInt) -> JInt: + return JInt(0) + + obj = MyObject() + obj.toString() + obj.equals(obj) + self.assertEqual(obj.identity(JInt(4)), 0) + obj = TestBase@obj + self.assertEqual(obj.identity(JInt(4)), 0) + + def testSelfCast(self): + TestBase = JClass("jpype.extension.TestBase") + class MyObject(TestBase): + + @JPublic + def __init__(self): + ... + + @JPublic + def identity(self, i: JInt) -> JInt: + return JInt(0) + + obj = MyObject() + MyObject@obj + + def testIllegalExtensionCast(self): + from java.lang import Object, String + class MyBaseObject(Object): + + @JPublic + def __init__(self): + ... + + @JPublic + def toString(self) -> String: + return "test" + + class MyObject(MyBaseObject): + + @JPublic + def __init__(self): + ... + + @JPublic + @JOverride + def toString(self) -> String: + return "again" + + obj = MyObject() + with self.assertRaises(TypeError): + MyBaseObject@obj diff --git a/test/jpypetest/test_javacoverage.py b/test/jpypetest/test_javacoverage.py index fbffb9c90..93784afb7 100644 --- a/test/jpypetest/test_javacoverage.py +++ b/test/jpypetest/test_javacoverage.py @@ -108,6 +108,11 @@ def defineMethodDispatch(self, context, cls, name, overloadList, modifiers): def destroy(self, context, resources, sz): for i in range(sz): del self.entities[resources[i]] + + @JOverride + def delete(self, context, resources, sz): + return + manager = TypeManager() factory = TF() manager = TypeManager(62621463, factory) diff --git a/test/jpypetest/test_jclass.py b/test/jpypetest/test_jclass.py index 9907062cd..70bffbdb8 100644 --- a/test/jpypetest/test_jclass.py +++ b/test/jpypetest/test_jclass.py @@ -152,12 +152,14 @@ def testMethod(self): def testPrivateMethod(self): cls = JClass('jpype.common.Fixture') with self.assertRaises(AttributeError): - cls.callPrivateObject(JObject()) + cls().callPrivateObject(JObject()) def testProtectedMethod(self): cls = JClass('jpype.common.Fixture') with self.assertRaises(AttributeError): cls.callProtectedObject(JObject()) + with self.assertRaises(AttributeError): + cls().callProtectedObject(JObject()) def testJClassFail(self): with self.assertRaises(TypeError): diff --git a/test/jpypetest/test_proxy.py b/test/jpypetest/test_proxy.py index 87dbe67bd..b2f96b9ff 100644 --- a/test/jpypetest/test_proxy.py +++ b/test/jpypetest/test_proxy.py @@ -589,4 +589,4 @@ def run(self): startJVM() assert isinstance(MyImpl(), MyImpl) - + diff --git a/test/jpypetest/test_virtual.py b/test/jpypetest/test_virtual.py index a76ded2e8..6a0a6fa68 100644 --- a/test/jpypetest/test_virtual.py +++ b/test/jpypetest/test_virtual.py @@ -51,6 +51,7 @@ def testCallBooleanAnon(self): self.assertEqual(self.vt.ClassBooleanSupplier.get(v1), True) def testCallBooleanAnonExtends(self): + # FIXME: figure out why the anonymous class didn't get it's methods applied v1 = self.vt.getBooleanAnonExtends() self.assertEqual(v1.get(), False) self.assertEqual(self.vt.BooleanSupplier.get(v1), False)