From d4b4be940cce207cf3ccdce32334aa27748b897f Mon Sep 17 00:00:00 2001 From: Rick Rongen Date: Thu, 30 Jan 2020 22:31:36 +0100 Subject: [PATCH] Initial commit --- .gitignore | 200 +++++++++ .idea/JavaClassFileParser.iml | 17 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + Pipfile | 11 + Pipfile.lock | 20 + pjvm/__init__.py | 0 pjvm/classloader.py | 194 +++++++++ pjvm/clazz.py | 227 ++++++++++ pjvm/exceptions.py | 21 + pjvm/expressions.py | 4 + pjvm/jtypes/__init__.py | 0 pjvm/jtypes/jarray.py | 10 + pjvm/jtypes/jprimitives.py | 70 +++ pjvm/jtypes/jtype.py | 13 + pjvm/opcodes.py | 406 ++++++++++++++++++ pjvm/unpacker.py | 34 ++ pjvm/utils.py | 12 + pjvm/vm.py | 391 +++++++++++++++++ run.py | 22 + testclass/AdvancedTest.java | 12 + testclass/Test.java | 9 + 23 files changed, 1694 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/JavaClassFileParser.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 pjvm/__init__.py create mode 100644 pjvm/classloader.py create mode 100644 pjvm/clazz.py create mode 100644 pjvm/exceptions.py create mode 100644 pjvm/expressions.py create mode 100644 pjvm/jtypes/__init__.py create mode 100644 pjvm/jtypes/jarray.py create mode 100644 pjvm/jtypes/jprimitives.py create mode 100644 pjvm/jtypes/jtype.py create mode 100644 pjvm/opcodes.py create mode 100644 pjvm/unpacker.py create mode 100644 pjvm/utils.py create mode 100644 pjvm/vm.py create mode 100644 run.py create mode 100644 testclass/AdvancedTest.java create mode 100644 testclass/Test.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..256263e --- /dev/null +++ b/.gitignore @@ -0,0 +1,200 @@ + +# Created by https://www.gitignore.io/api/python,pycharm +# Edit at https://www.gitignore.io/?templates=python,pycharm + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/**/sonarlint/ + +# SonarQube Plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator/ + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python,pycharm +# Java class files +*.class diff --git a/.idea/JavaClassFileParser.iml b/.idea/JavaClassFileParser.iml new file mode 100644 index 0000000..8182136 --- /dev/null +++ b/.idea/JavaClassFileParser.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c236a7a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..eecc022 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..b5846df --- /dev/null +++ b/Pipfile @@ -0,0 +1,11 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] + +[requires] +python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..e42812c --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,20 @@ +{ + "_meta": { + "hash": { + "sha256": "7f7606f08e0544d8d012ef4d097dabdd6df6843a28793eb6551245d4b2db4242" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": {} +} diff --git a/pjvm/__init__.py b/pjvm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pjvm/classloader.py b/pjvm/classloader.py new file mode 100644 index 0000000..b420f38 --- /dev/null +++ b/pjvm/classloader.py @@ -0,0 +1,194 @@ +import logging + +from typing import BinaryIO, IO, Union + +from .unpacker import Unpacker + +LOGGER = logging.getLogger(__name__) + +formats = { + 'magic': '4s', + 'major_version': 'h', + 'minor_version': 'h', + 'constant_pool_count': 'h', + 'access_flags': 'h', + 'this_class': 'h', + 'super_class': 'h', + 'interfaces_count': 'h', + 'fields_count': 'h', + 'methods_count': 'h', + 'attributes_count': 'h', + 'byte': 'b', + 'short': 'h' +} + + +def create_constant_type(name, fmt, *fieldnames): + if len(fmt) != len(fieldnames): + raise ValueError("Length mismatch format and names") + return { + 'name': name, + 'format': fmt, + 'fieldnames': fieldnames + } + + +# https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 +CONSTANT_POOL_TYPES = { + 7: create_constant_type('Class', 'h', 'name_index'), + 9: create_constant_type('Fieldref', 'hh', 'class_index', 'name_and_type_index'), + 10: create_constant_type('Methodref', 'hh', 'class_index', 'name_and_type_index'), + 11: create_constant_type('InterfaceMethodref', 'hh', 'class_index', 'name_and_type_index'), + 8: create_constant_type('String', 'h', 'string_index'), + 3: create_constant_type('Integer', 'i', 'value'), # divergence, actual value instead of raw bytes + 4: create_constant_type('Float', 'f', 'value'), # divergence, actual value instead of raw bytes + 5: create_constant_type('Long', 'q', 'value'), # divergence, actual value instead of raw bytes + 6: create_constant_type('Double', 'd', 'value'), # divergence, actual value instead of raw bytes + 12: create_constant_type('NameAndType', 'hh', 'name_index', 'descriptor_index'), + 1: None, # TODO: special format. Pascal coded but with short instead of byte + 15: create_constant_type('MethodHandle', 'bh', 'reference_kind', 'reference_index'), + 16: create_constant_type('MethodType', 'h', 'descriptor_index'), + 18: create_constant_type('InvokeDynamic', 'hh', 'bootstrap_method_attr_index', 'name_ant_type_index') +} + +JAVA_CLASS_MAGIC = b'\xca\xfe\xba\xbe' + + +class ClassLoader: + file: IO + + magic: bytes + major_version: int + minor_version: int + + constant_pool_count: int + constant_pool: list + + access_flags: int + this_class: int + super_class: int + + interfaces_count: int + interfaces: list + + fields_count: int + fields: list + + methods_count: int + methods: list + + attributes_count: int + attributes: list + + def __init__(self, stream: BinaryIO): + self.stream = stream + self.unpacker = Unpacker(formats, self.stream) + + def load(self): + self.magic, = self.unpacker.magic + + if self.magic != JAVA_CLASS_MAGIC: + raise ValueError(f"Not a valid java class file! Magic: {self.magic!r}") + + self.minor_version, = self.unpacker.minor_version + self.major_version, = self.unpacker.major_version + + LOGGER.info(f"Parsing class with version {self.major_version}.{self.minor_version}") + + self.constant_pool_count, = self.unpacker.constant_pool_count + + LOGGER.info(f"Parsing {self.constant_pool_count} constant pool items") + + self.constant_pool = [] + for i in range(self.constant_pool_count - 1): + tag, = self.unpacker.byte + + if tag == 1: + length, = self.unpacker.short + utf8_str, = self.unpacker[f'{length}s'] + data = { + 'tag': tag, + 'type': 'Utf8', + 'value': utf8_str.decode() + } + else: + constant_info = CONSTANT_POOL_TYPES.get(tag) + data = { + 'tag': tag, + 'type': constant_info['name'], + **dict(zip(constant_info['fieldnames'], self.unpacker[constant_info['format']])) + } + self.constant_pool.append(data) + + self.access_flags, = self.unpacker.access_flags # todo enum.IntFlag + self.this_class, = self.unpacker.this_class + self.super_class, = self.unpacker.super_class + + self.interfaces_count, = self.unpacker.interfaces_count + self.interfaces = [] + + for i in range(self.interfaces_count): + self.interfaces.append(self.unpacker.short[0]) + + self.fields_count, = self.unpacker.fields_count + self.fields = [] + + for i in range(self.fields_count): + access_flags, name_index, descriptor_index, attributes_count = \ + self.unpacker['hhhh'] + attributes = [self._parse_attribute() for _ in range(attributes_count)] + self.fields.append({ + 'access_flags': access_flags, + 'name_index': name_index, + 'descriptor_index': descriptor_index, + 'attributes_count': attributes_count, + 'attributes': attributes + }) + + self.methods_count, = self.unpacker.methods_count + self.methods = [] + + for i in range(self.methods_count): + access_flags, name_index, descriptor_index, attributes_count = \ + self.unpacker['hhhh'] + attributes = [self._parse_attribute() for _ in range(attributes_count)] + self.methods.append({ + 'access_flags': access_flags, + 'name_index': name_index, + 'descriptor_index': descriptor_index, + 'attributes_count': attributes_count, + 'attributes': attributes + }) + + self.attributes_count, = self.unpacker.attributes_count + self.attributes = [self._parse_attribute() for _ in range(self.attributes_count)] + + # cleanup + del self.stream + del self.unpacker + + def _parse_attribute(self): + attribute_name_index, attribute_length = self.unpacker['hi'] + data, = self.unpacker[f'{attribute_length}s'] + return {'attribute_name_index': attribute_name_index, 'attribute_length': attribute_length, 'info': data} + + +def load_class(name_or_stream: Union[str, BinaryIO]) -> ClassLoader: + """ + Open a class file and parse it using the ClassLoader + + Args: + name_or_stream: A file name or a binary stream + + Returns: + A ClassLoader instance with a loaded class + """ + stream: BinaryIO + if isinstance(name_or_stream, str): + stream = open(name_or_stream, 'rb') + else: + stream = name_or_stream + + loader = ClassLoader(stream) + loader.load() + return loader diff --git a/pjvm/clazz.py b/pjvm/clazz.py new file mode 100644 index 0000000..6bc97c6 --- /dev/null +++ b/pjvm/clazz.py @@ -0,0 +1,227 @@ +import collections +import enum +import logging +import struct +from typing import Dict, List, Optional, Union, Tuple + +from pjvm.unpacker import Unpacker +from .classloader import ClassLoader +from .expressions import METHOD_SIGNATURE + +LOGGER = logging.getLogger(__name__) +exception_table_tuple = collections.namedtuple('exception_table', ['start_pc', 'end_pc', 'handler_pc', 'catch_type']) + + +class ClassAccessFlags(enum.IntFlag): + ACC_PUBLIC = 0x0001 # Declared public; may be accessed from outside its package. + ACC_FINAL = 0x0010 # Declared final; no subclasses allowed. + ACC_SUPER = 0x0020 # Treat superclass methods specially when invoked by the invokespecial instruction. + ACC_INTERFACE = 0x0200 # Is an interface, not a class. + ACC_ABSTRACT = 0x0400 # Declared abstract; must not be instantiated. + ACC_SYNTHETIC = 0x1000 # Declared synthetic; not present in the source code. + ACC_ANNOTATION = 0x2000 # Declared as an annotation type. + ACC_ENUM = 0x4000 # Declared as an enum type. + + +class FieldAccessFlags(enum.IntFlag): + ACC_PUBLIC = 0x0001 # Declared public; may be accessed from outside its package. + ACC_PRIVATE = 0x0002 # Declared private; usable only within the defining class. + ACC_PROTECTED = 0x0004 # Declared protected; may be accessed within subclasses. + ACC_STATIC = 0x0008 # Declared static. + ACC_FINAL = 0x0010 # Declared final; never directly assigned to after object construction (JLS §17.5). + ACC_VOLATILE = 0x0040 # Declared volatile; cannot be cached. + ACC_TRANSIENT = 0x0080 # Declared transient; not written or read by a persistent object manager. + ACC_SYNTHETIC = 0x1000 # Declared synthetic; not present in the source code. + ACC_ENUM = 0x4000 # Declared as an element of an enum. + + +class MethodAccessFlags(enum.IntFlag): + ACC_PUBLIC = 0x0001 # Declared public; may be accessed from outside its package. + ACC_PRIVATE = 0x0002 # Declared private; accessible only within the defining class. + ACC_PROTECTED = 0x0004 # Declared protected; may be accessed within subclasses. + ACC_STATIC = 0x0008 # Declared static. + ACC_FINAL = 0x0010 # Declared final; must not be overridden (§5.4.5). + ACC_SYNCHRONIZED = 0x0020 # Declared synchronized; invocation is wrapped by a monitor use. + ACC_BRIDGE = 0x0040 # A bridge method, generated by the compiler. + ACC_VARARGS = 0x0080 # Declared with variable number of arguments. + ACC_NATIVE = 0x0100 # Declared native; implemented in a language other than Java. + ACC_ABSTRACT = 0x0400 # Declared abstract; no implementation is provided. + ACC_STRICT = 0x0800 # Declared strictfp; floating-point mode is FP-strict. + ACC_SYNTHETIC = 0x1000 # Declared synthetic; not present in the source code. + + +KNOWN_ATTRIBUTES = ['SourceFile', 'Code'] +NOT_IMPLEMENTED_ATTRIBUTES = ['ConstantValue', 'StackMapTable', 'Exceptions', 'InnerClasses', 'EnclosingMethod', + 'Synthetic', 'Signature', 'SourceDebugExtension', 'LineNumberTable', + 'LocalVariableTable ', 'LocalVariableTypeTable', 'Deprecated', + 'RuntimeVisibleAnnotations', 'RuntimeInvisibleAnnotations', + 'RuntimeVisibleParameterAnnotations', 'RuntimeInvisibleParameterAnnotations', + 'AnnotationDefault', 'BootstrapMethods'] + + +class Class: + methods: Dict[str, "Method"] + fields: Dict[str, "Field"] + + def __init__(self, loader: ClassLoader): + self.loader = loader + self.methods = {} + self.attributes = {} + self.fields = {} + + self.access_flags: ClassAccessFlags = ClassAccessFlags(0) + self.this_class: str = "" + self.super_class: Optional[str] = None + + # todo other info + self.source_file: Optional[str] = None + + self.initialize() + + def initialize(self): + self.access_flags = ClassAccessFlags(self.loader.access_flags) + + self.this_class = self.cp_get_classname(self.loader.this_class) + self.super_class = self.cp_get_classname(self.loader.super_class) + + LOGGER.info(f"Parsing info for {self.access_flags!s} {self.this_class} : {self.super_class}") + + self.initialize_fields() + self.initialize_methods() + self.initialize_attributes() + + def initialize_fields(self): + field: Dict[str, Union[int, list]] + for field in self.loader.fields: + acc = FieldAccessFlags(field['access_flags']) + name = self.cp_get_utf8(field['name_index']) + descriptor = self.cp_get_utf8(field['descriptor_index']) + attributes = field['attributes'] + self.fields[name] = Field(acc, name, descriptor, attributes, self) + + def initialize_methods(self): + method: Dict[str, Union[int, list]] + for method in self.loader.methods: + acc = MethodAccessFlags(method['access_flags']) + name = self.cp_get_utf8(method['name_index']) + descriptor = self.cp_get_utf8(method['descriptor_index']) + attributes = method['attributes'] + self.methods[name] = Method(acc, name, descriptor, attributes, self) + + def initialize_attributes(self): + for attr in self.loader.attributes: + attr_name = self.cp_get_utf8(attr['attribute_name_index']) + if attr_name in NOT_IMPLEMENTED_ATTRIBUTES: + print(f"Attribute {attr_name} found but not implemented. {attr['info'][:10]}") + elif attr_name in KNOWN_ATTRIBUTES: + getattr(self, f'_attr_{attr_name}')(attr['info']) + else: + pass # ignore unknowns + + # noinspection PyPep8Naming + def _attr_SourceFile(self, info: bytes): + self.source_file = self.cp_get_utf8(struct.unpack('>h', info)[0]) + LOGGER.info(f"Found source file {self.source_file} for FULL_NAME") + + def cp_get(self, index: int) -> dict: + return self.loader.constant_pool[index - 1] + + def cp_get_utf8(self, index: int) -> str: + return self.cp_get(index)['value'] + + def cp_get_classname(self, index: int) -> str: + return self.cp_get_utf8(self.cp_get(index)['name_index']) + + def cp_get_name_type(self, index: int) -> Tuple[str, str]: + nt = self.cp_get(index) + return self.cp_get_utf8(nt['name_index']), self.cp_get_utf8(nt['descriptor_index']) + + def cp_get_fieldref(self, index: int) -> Tuple[str, str, str]: + # Exactly the same as methodref, only name differs + return self.cp_get_methodref(index) + + def cp_get_methodref(self, index: int) -> Tuple[str, str, str]: + """ + Get the class name, method name and descriptor + Args: + index: the index + """ + mr = self.cp_get(index) + name, typename = self.cp_get_name_type(mr['name_and_type_index']) + return self.cp_get_classname(mr['class_index']), name, typename + + +class Code: + def __init__(self, code: bytes): + self.exception_handlers = [] + self.attributes = [] + unpacker = Unpacker.from_bytes(code) + self.max_stack, = unpacker['h'] + self.max_locals, = unpacker['h'] + code_len, = unpacker['i'] + self.code, = unpacker[f'{code_len}s'] + exception_table_length, = unpacker['h'] + for i in range(exception_table_length): + self.exception_handlers.append(exception_table_tuple(*unpacker['hhhh'])) + + attributes_count, = unpacker['h'] + for i in range(attributes_count): + attribute_name_index, attribute_length = unpacker['hi'] + data, = unpacker[f'{attribute_length}s'] + self.attributes.append( + {'attribute_name_index': attribute_name_index, 'attribute_length': attribute_length, 'info': data}) + + +class Field: + def __init__(self, acc, name, descriptor, attributes, clazz: Class): + self.acc = acc + self.name = name + self.descriptor = descriptor + self.attributes = attributes + self.clazz = clazz + + self.parse_attributes() + + LOGGER.info(f"New field {name} {descriptor}") + # todo initialize + + def parse_attributes(self): + LOGGER.info("TODO parse field attributes") + pass + + +class Method: + def __init__(self, acc, name, descriptor, attributes, clazz: Class): + self.acc = acc + self.name = name + self.descriptor = descriptor + self.attributes = attributes + self.clazz = clazz + + self.code: Optional[Code] = None + + self.return_value: str = '' + self.args: List[str] = [] + + self.parse_descriptor() + + self.parse_attributes() + + LOGGER.info(f"New method {self.return_value} {name}({', '.join(self.args)})") + + def parse_descriptor(self): + res = METHOD_SIGNATURE.search(self.descriptor) + self.return_value = res.group('RET') + self.args = res.group('ARGS').split(';') + self.args = list(filter(None, self.args)) + + # todo initialize + + def parse_attributes(self): + # todo Exceptions, Synthetic, Signature, Deprecated, RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, AnnotationDefault + + for attr in self.attributes: + name = self.clazz.cp_get_utf8(attr['attribute_name_index']) + + if name == 'Code': + self.code = Code(attr['info']) diff --git a/pjvm/exceptions.py b/pjvm/exceptions.py new file mode 100644 index 0000000..a871c44 --- /dev/null +++ b/pjvm/exceptions.py @@ -0,0 +1,21 @@ +from .opcodes import INSTRUCTIONS + + +class PJVMException(ValueError): + pass + + +class PJVMUnknownOpcode(PJVMException): + def __init__(self, opcode: int): + super(PJVMUnknownOpcode, self).__init__(f"OPCODE {opcode} -> {INSTRUCTIONS[opcode]['name']} not implemented") + + +class PJVMNotImplemented(PJVMException): + pass + + +class PJVMTypeError(PJVMException): + pass + + +__all__ = ['PJVMException', 'PJVMUnknownOpcode', 'PJVMNotImplemented', 'PJVMTypeError'] diff --git a/pjvm/expressions.py b/pjvm/expressions.py new file mode 100644 index 0000000..189e734 --- /dev/null +++ b/pjvm/expressions.py @@ -0,0 +1,4 @@ +import re + +METHOD_SIGNATURE = re.compile(r'\((?P\[?(?:[IFD]|L(?:\w+/)+\w+;)*)\)(?P[IFDV])') +TYPE = re.compile(r'\[?(?:[IVDF]|L(?:[\w\d]+/)+[\w\d]+)') diff --git a/pjvm/jtypes/__init__.py b/pjvm/jtypes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pjvm/jtypes/jarray.py b/pjvm/jtypes/jarray.py new file mode 100644 index 0000000..e13a9f4 --- /dev/null +++ b/pjvm/jtypes/jarray.py @@ -0,0 +1,10 @@ +from .jtype import JType + + +class JArray(JType): + j_base_type = '[' + + values: [] + + def __init__(self): + self.values = [] diff --git a/pjvm/jtypes/jprimitives.py b/pjvm/jtypes/jprimitives.py new file mode 100644 index 0000000..2238faa --- /dev/null +++ b/pjvm/jtypes/jprimitives.py @@ -0,0 +1,70 @@ +import struct +from typing import Union + +from .jtype import JType + + +class JPrimitive(JType): + def __init__(self, value): + self.j_type = self.j_base_type + super(JPrimitive, self).__init__(value) + + +class JChar(JPrimitive): + j_base_type = 'C' # todo 16-bit unicode code point + + +class JDouble(JPrimitive): + j_base_type = 'D' + + +class JFloat(JPrimitive): + j_base_type = 'F' + + +class JBool(JPrimitive): + j_base_type = 'Z' + + def __init__(self, value): + self.value = value + super(JBool, self).__init__(value) + + +# Integer types + +class JPrimitiveInteger(JPrimitive): + pj_mask: int + pj_struct: str + + def __init__(self, value: Union[JType, bytes, int]): + if isinstance(value, int): + self.value = value & self.pj_mask + elif isinstance(value, bytes): + self.value, = struct.unpack(self.pj_struct, value) + else: + raise NotImplementedError() + super(JPrimitiveInteger, self).__init__(value) + + +class JByte(JPrimitiveInteger): + j_base_type = 'B' + pj_mask = 0xff + pj_struct = 'b' + + +class JShort(JPrimitiveInteger): + j_base_type = 'S' + pj_mask = 0xffff + pj_struct = 'h' + + +class JInt(JPrimitiveInteger): + j_base_type = 'I' + pj_mask = 0xffffffff + pj_struct = 'i' + + +class JLong(JPrimitiveInteger): + j_base_type = 'J' + pj_mask = 0xffffffffffffffff + pj_struct = 'q' diff --git a/pjvm/jtypes/jtype.py b/pjvm/jtypes/jtype.py new file mode 100644 index 0000000..d240d8b --- /dev/null +++ b/pjvm/jtypes/jtype.py @@ -0,0 +1,13 @@ + +class JType: + j_base_type: str + j_type: str + + def __init__(self, value): + raise NotImplementedError() + + def instance_of(self, other: "JType") -> bool: + if self.j_type == other.j_type: + return True + # TODO inheritance check + return False diff --git a/pjvm/opcodes.py b/pjvm/opcodes.py new file mode 100644 index 0000000..925110e --- /dev/null +++ b/pjvm/opcodes.py @@ -0,0 +1,406 @@ +INSTRUCTIONS = { + 0x32: {"name": 'aaload', "group": 'aaload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aaload + 0x53: {"name": 'aastore', "group": 'aastore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aastore + 0x1: {"name": 'aconst_null', "group": 'aconst_null', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aconst_null + 0x19: {"name": 'aload', "group": 'aload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aload + 0x2a: {"name": 'aload_0', "group": 'aload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aload_n + 0x2b: {"name": 'aload_1', "group": 'aload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aload_n + 0x2c: {"name": 'aload_2', "group": 'aload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aload_n + 0x2d: {"name": 'aload_3', "group": 'aload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.aload_n + 0xbd: {"name": 'anewarray', "group": 'anewarray', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.anewarray + 0xb0: {"name": 'areturn', "group": 'areturn', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.areturn + 0xbe: {"name": 'arraylength', "group": 'arraylength', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.arraylength + 0x3a: {"name": 'astore', "group": 'astore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.astore + 0x4b: {"name": 'astore_0', "group": 'astore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.astore_n + 0x4c: {"name": 'astore_1', "group": 'astore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.astore_n + 0x4d: {"name": 'astore_2', "group": 'astore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.astore_n + 0x4e: {"name": 'astore_3', "group": 'astore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.astore_n + 0xbf: {"name": 'athrow', "group": 'athrow', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.athrow + 0x33: {"name": 'baload', "group": 'baload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.baload + 0x54: {"name": 'bastore', "group": 'bastore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.bastore + 0x10: {"name": 'bipush', "group": 'bipush', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.bipush + 0x34: {"name": 'caload', "group": 'caload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.caload + 0x55: {"name": 'castore', "group": 'castore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.castore + 0xc0: {"name": 'checkcast', "group": 'checkcast', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.checkcast + 0x90: {"name": 'd2f', "group": 'd2f', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.d2f + 0x8e: {"name": 'd2i', "group": 'd2i', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.d2i + 0x8f: {"name": 'd2l', "group": 'd2l', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.d2l + 0x63: {"name": 'dadd', "group": 'dadd', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dadd + 0x31: {"name": 'daload', "group": 'daload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.daload + 0x52: {"name": 'dastore', "group": 'dastore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dastore + 0x98: {"name": 'dcmpg', "group": 'dcmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dcmpop + 0x97: {"name": 'dcmpl', "group": 'dcmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dcmpop + 0xe: {"name": 'dconst_0', "group": 'dconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dconst_d + 0xf: {"name": 'dconst_1', "group": 'dconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dconst_d + 0x6f: {"name": 'ddiv', "group": 'ddiv', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ddiv + 0x18: {"name": 'dload', "group": 'dload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dload + 0x26: {"name": 'dload_0', "group": 'dload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dload_n + 0x27: {"name": 'dload_1', "group": 'dload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dload_n + 0x28: {"name": 'dload_2', "group": 'dload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dload_n + 0x29: {"name": 'dload_3', "group": 'dload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dload_n + 0x6b: {"name": 'dmul', "group": 'dmul', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dmul + 0x77: {"name": 'dneg', "group": 'dneg', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dneg + 0x73: {"name": 'drem', "group": 'drem', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.drem + 0xaf: {"name": 'dreturn', "group": 'dreturn', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dreturn + 0x39: {"name": 'dstore', "group": 'dstore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dstore + 0x47: {"name": 'dstore_0', "group": 'dstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dstore_n + 0x48: {"name": 'dstore_1', "group": 'dstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dstore_n + 0x49: {"name": 'dstore_2', "group": 'dstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dstore_n + 0x4a: {"name": 'dstore_3', "group": 'dstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dstore_n + 0x67: {"name": 'dsub', "group": 'dsub', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dsub + 0x59: {"name": 'dup', "group": 'dup', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup + 0x5a: {"name": 'dup_x1', "group": 'dup_x1', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup_x1 + 0x5b: {"name": 'dup_x2', "group": 'dup_x2', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup_x2 + 0x5c: {"name": 'dup2', "group": 'dup2', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup2 + 0x5d: {"name": 'dup2_x1', "group": 'dup2_x1', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup2_x1 + 0x5e: {"name": 'dup2_x2', "group": 'dup2_x2', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup2_x2 + 0x8d: {"name": 'f2d', "group": 'f2d', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.f2d + 0x8b: {"name": 'f2i', "group": 'f2i', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.f2i + 0x8c: {"name": 'f2l', "group": 'f2l', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.f2l + 0x62: {"name": 'fadd', "group": 'fadd', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fadd + 0x30: {"name": 'faload', "group": 'faload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.faload + 0x51: {"name": 'fastore', "group": 'fastore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fastore + 0x96: {"name": 'fcmpg', "group": 'fcmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fcmpop + 0x95: {"name": 'fcmpl', "group": 'fcmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fcmpop + 0xb: {"name": 'fconst_0', "group": 'fconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fconst_f + 0xc: {"name": 'fconst_1', "group": 'fconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fconst_f + 0xd: {"name": 'fconst_2', "group": 'fconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fconst_f + 0x6e: {"name": 'fdiv', "group": 'fdiv', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fdiv + 0x17: {"name": 'fload', "group": 'fload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fload + 0x22: {"name": 'fload_0', "group": 'fload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fload_n + 0x23: {"name": 'fload_1', "group": 'fload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fload_n + 0x24: {"name": 'fload_2', "group": 'fload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fload_n + 0x25: {"name": 'fload_3', "group": 'fload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fload_n + 0x6a: {"name": 'fmul', "group": 'fmul', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fmul + 0x76: {"name": 'fneg', "group": 'fneg', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fneg + 0x72: {"name": 'frem', "group": 'frem', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.frem + 0xae: {"name": 'freturn', "group": 'freturn', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.freturn + 0x38: {"name": 'fstore', "group": 'fstore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fstore + 0x43: {"name": 'fstore_0', "group": 'fstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fstore_n + 0x44: {"name": 'fstore_1', "group": 'fstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fstore_n + 0x45: {"name": 'fstore_2', "group": 'fstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fstore_n + 0x46: {"name": 'fstore_3', "group": 'fstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fstore_n + 0x66: {"name": 'fsub', "group": 'fsub', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.fsub + 0xb4: {"name": 'getfield', "group": 'getfield', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.getfield + 0xb2: {"name": 'getstatic', "group": 'getstatic', "impl": 'op_getstatic'}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.getstatic + 0xa7: {"name": 'goto', "group": 'goto', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto + 0xc8: {"name": 'goto_w', "group": 'goto_w', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w + 0x91: {"name": 'i2b', "group": 'i2b', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.i2b + 0x92: {"name": 'i2c', "group": 'i2c', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.i2c + 0x87: {"name": 'i2d', "group": 'i2d', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.i2d + 0x86: {"name": 'i2f', "group": 'i2f', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.i2f + 0x85: {"name": 'i2l', "group": 'i2l', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.i2l + 0x93: {"name": 'i2s', "group": 'i2s', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.i2s + 0x60: {"name": 'iadd', "group": 'iadd', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iadd + 0x2e: {"name": 'iaload', "group": 'iaload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iaload + 0x7e: {"name": 'iand', "group": 'iand', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iand + 0x4f: {"name": 'iastore', "group": 'iastore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iastore + 0x2: {"name": 'iconst_m1', "group": 'iconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i + 0x3: {"name": 'iconst_0', "group": 'iconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i + 0x4: {"name": 'iconst_1', "group": 'iconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i + 0x5: {"name": 'iconst_2', "group": 'iconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i + 0x6: {"name": 'iconst_3', "group": 'iconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i + 0x7: {"name": 'iconst_4', "group": 'iconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i + 0x8: {"name": 'iconst_5', "group": 'iconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i + 0x6c: {"name": 'idiv', "group": 'idiv', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.idiv + 0xa5: {"name": 'if_acmpeq', "group": 'if_acmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_acmpcond + 0xa6: {"name": 'if_acmpne', "group": 'if_acmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_acmpcond + 0x9f: {"name": 'if_icmpeq', "group": 'if_icmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_icmpcond + 0xa0: {"name": 'if_icmpne', "group": 'if_icmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_icmpcond + 0xa1: {"name": 'if_icmplt', "group": 'if_icmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_icmpcond + 0xa2: {"name": 'if_icmpge', "group": 'if_icmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_icmpcond + 0xa3: {"name": 'if_icmpgt', "group": 'if_icmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_icmpcond + 0xa4: {"name": 'if_icmple', "group": 'if_icmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.if_icmpcond + 0x99: {"name": 'ifeq', "group": 'if', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifcond + 0x9a: {"name": 'ifne', "group": 'if', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifcond + 0x9b: {"name": 'iflt', "group": 'if', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifcond + 0x9c: {"name": 'ifge', "group": 'if', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifcond + 0x9d: {"name": 'ifgt', "group": 'if', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifcond + 0x9e: {"name": 'ifle', "group": 'if', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifcond + 0xc7: {"name": 'ifnonnull', "group": 'ifnonnull', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifnonnull + 0xc6: {"name": 'ifnull', "group": 'ifnull', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ifnull + 0x84: {"name": 'iinc', "group": 'iinc', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iinc + 0x15: {"name": 'iload', "group": 'iload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iload + 0x1a: {"name": 'iload_0', "group": 'iload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iload_n + 0x1b: {"name": 'iload_1', "group": 'iload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iload_n + 0x1c: {"name": 'iload_2', "group": 'iload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iload_n + 0x1d: {"name": 'iload_3', "group": 'iload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iload_n + 0x68: {"name": 'imul', "group": 'imul', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.imul + 0x74: {"name": 'ineg', "group": 'ineg', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ineg + 0xc1: {"name": 'instanceof', "group": 'instanceof', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceof + 0xba: {"name": 'invokedynamic', "group": 'invokedynamic', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic + 0xb9: {"name": 'invokeinterface', "group": 'invokeinterface', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokeinterface + 0xb7: {"name": 'invokespecial', "group": 'invokespecial', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial + 0xb8: {"name": 'invokestatic', "group": 'invokestatic', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokestatic + 0xb6: {"name": 'invokevirtual', "group": 'invokevirtual', "impl": 'op_invokevirtual'}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokevirtual + 0x80: {"name": 'ior', "group": 'ior', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ior + 0x70: {"name": 'irem', "group": 'irem', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.irem + 0xac: {"name": 'ireturn', "group": 'ireturn', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ireturn + 0x78: {"name": 'ishl', "group": 'ishl', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ishl + 0x7a: {"name": 'ishr', "group": 'ishr', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ishr + 0x36: {"name": 'istore', "group": 'istore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.istore + 0x3b: {"name": 'istore_0', "group": 'istore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.istore_n + 0x3c: {"name": 'istore_1', "group": 'istore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.istore_n + 0x3d: {"name": 'istore_2', "group": 'istore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.istore_n + 0x3e: {"name": 'istore_3', "group": 'istore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.istore_n + 0x64: {"name": 'isub', "group": 'isub', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.isub + 0x7c: {"name": 'iushr', "group": 'iushr', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iushr + 0x82: {"name": 'ixor', "group": 'ixor', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ixor + 0xa8: {"name": 'jsr', "group": 'jsr', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.jsr + 0xc9: {"name": 'jsr_w', "group": 'jsr_w', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.jsr_w + 0x8a: {"name": 'l2d', "group": 'l2d', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.l2d + 0x89: {"name": 'l2f', "group": 'l2f', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.l2f + 0x88: {"name": 'l2i', "group": 'l2i', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.l2i + 0x61: {"name": 'ladd', "group": 'ladd', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ladd + 0x2f: {"name": 'laload', "group": 'laload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.laload + 0x7f: {"name": 'land', "group": 'land', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.land + 0x50: {"name": 'lastore', "group": 'lastore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lastore + 0x94: {"name": 'lcmp', "group": 'lcmp', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lcmp + 0x9: {"name": 'lconst_0', "group": 'lconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lconst_l + 0xa: {"name": 'lconst_1', "group": 'lconst_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lconst_l + 0x12: {"name": 'ldc', "group": 'ldc', "impl": 'op_ldc'}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ldc + 0x13: {"name": 'ldc_w', "group": 'ldc_w', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ldc_w + 0x14: {"name": 'ldc2_w', "group": 'ldc2_w', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ldc2_w + 0x6d: {"name": 'ldiv', "group": 'ldiv', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ldiv + 0x16: {"name": 'lload', "group": 'lload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lload + 0x1e: {"name": 'lload_0', "group": 'lload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lload_n + 0x1f: {"name": 'lload_1', "group": 'lload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lload_n + 0x20: {"name": 'lload_2', "group": 'lload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lload_n + 0x21: {"name": 'lload_3', "group": 'lload_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lload_n + 0x69: {"name": 'lmul', "group": 'lmul', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lmul + 0x75: {"name": 'lneg', "group": 'lneg', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lneg + 0xab: {"name": 'lookupswitch', "group": 'lookupswitch', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lookupswitch + 0x81: {"name": 'lor', "group": 'lor', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lor + 0x71: {"name": 'lrem', "group": 'lrem', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lrem + 0xad: {"name": 'lreturn', "group": 'lreturn', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lreturn + 0x79: {"name": 'lshl', "group": 'lshl', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lshl + 0x7b: {"name": 'lshr', "group": 'lshr', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lshr + 0x37: {"name": 'lstore', "group": 'lstore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lstore + 0x3f: {"name": 'lstore_0', "group": 'lstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lstore_n + 0x40: {"name": 'lstore_1', "group": 'lstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lstore_n + 0x41: {"name": 'lstore_2', "group": 'lstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lstore_n + 0x42: {"name": 'lstore_3', "group": 'lstore_', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lstore_n + 0x65: {"name": 'lsub', "group": 'lsub', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lsub + 0x7d: {"name": 'lushr', "group": 'lushr', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lushr + 0x83: {"name": 'lxor', "group": 'lxor', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lxor + 0xc2: {"name": 'monitorenter', "group": 'monitorenter', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter + 0xc3: {"name": 'monitorexit', "group": 'monitorexit', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorexit + 0xc5: {"name": 'multianewarray', "group": 'multianewarray', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.multianewarray + 0xbb: {"name": 'new', "group": 'new', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.new + 0xbc: {"name": 'newarray', "group": 'newarray', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.newarray + 0x0: {"name": 'nop', "group": 'nop', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.nop + 0x57: {"name": 'pop', "group": 'pop', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.pop + 0x58: {"name": 'pop2', "group": 'pop2', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.pop2 + 0xb5: {"name": 'putfield', "group": 'putfield', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.putfield + 0xb3: {"name": 'putstatic', "group": 'putstatic', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.putstatic + 0xa9: {"name": 'ret', "group": 'ret', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.ret + 0xb1: {"name": 'return', "group": 'return', "impl": 'op_return'}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.return + 0x35: {"name": 'saload', "group": 'saload', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.saload + 0x56: {"name": 'sastore', "group": 'sastore', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.sastore + 0x11: {"name": 'sipush', "group": 'sipush', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.sipush + 0x5f: {"name": 'swap', "group": 'swap', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.swap + 0xaa: {"name": 'tableswitch', "group": 'tableswitch', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.tableswitch + 0xc4: {"name": 'wide', "group": 'wide', "impl": None}, + # https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.wide +} diff --git a/pjvm/unpacker.py b/pjvm/unpacker.py new file mode 100644 index 0000000..706bca0 --- /dev/null +++ b/pjvm/unpacker.py @@ -0,0 +1,34 @@ +from io import BytesIO +from struct import unpack, calcsize +from typing import Dict, BinaryIO + + +class Unpacker: + @staticmethod + def from_bytes(data: bytes): + return Unpacker({}, BytesIO(data)) + + def __init__(self, formats: Dict[str, str], stream: BinaryIO, endianness: str = '>'): + self.formats = formats + self.stream = stream + self.endianness = endianness + + def __getattribute__(self, item): + try: + return super(Unpacker, self).__getattribute__(item) + except AttributeError: + return self.unpack(item) + + def __getitem__(self, item): + return self.unpack(item) + + def unpack(self, item): + if item in self.formats: + fmt = self.formats.get(item) + else: + fmt = item + + fmt = self.endianness + fmt + + size = calcsize(fmt) + return unpack(fmt, self.stream.read(size)) diff --git a/pjvm/utils.py b/pjvm/utils.py new file mode 100644 index 0000000..a140345 --- /dev/null +++ b/pjvm/utils.py @@ -0,0 +1,12 @@ +from typing import List +import re + + +re_types = re.compile('([BCDFIJSZV]|L(?:[a-zA-Z0-9_]+/)+[a-zA-Z0-9_]+;)') + +re_method_sig = re.compile(fr"\(({re_types.pattern}*)\){re_types.pattern}") + + +def get_argument_count_types_descriptor(descriptor: str) -> List[str]: + args = re_method_sig.search(descriptor).group(1) + return re_types.findall(args) diff --git a/pjvm/vm.py b/pjvm/vm.py new file mode 100644 index 0000000..bf64992 --- /dev/null +++ b/pjvm/vm.py @@ -0,0 +1,391 @@ +import struct +import sys +from logging import getLogger +from typing import Callable, Dict, List, Optional, Tuple, Union + +from pjvm.classloader import load_class +from pjvm.clazz import Class, Code, Method +from . import utils +from .opcodes import INSTRUCTIONS +from .exceptions import * + +OPCODE_TYPE = Callable[[int, "Frame"], Tuple[int, "JObj"]] +OPCODE_BOUND_TYPE = Callable[["PJVirtualMachine", int, "Frame"], Tuple[int, "JObj"]] + +LOGGER = getLogger(__name__) + + +class JObj: + type = 'java/lang/Object' + value = None + + def instance_of(self, java_type: str): + return self.type == java_type # todo inheritance + + +class JInteger(JObj): + type = "I" + + def __init__(self, val: int): + self.value = val + + +class JCharacter(JObj): + type = 'C' + + def __init__(self, val: int): + self.value = val + + +class JGenericObj(JObj): + generic_types: List[str] + pass + + +class JString(JObj): + type = 'java/lang/String' + value: str + + def __init__(self, value: str): + self.value = value + + +class JArray(JGenericObj): + type = 'java/lang/Array' + generic_types: List[str] + values: List + + def __init__(self, generic_types: List[str], values: List): + self.generic_types = generic_types + self.values = values + + +class JMockSystemOut(JObj): + type = "Ljava/io/PrintStream;" + value = None + + +class JMockSystemIn(JObj): + type = "Ljava/io/InputStream;" + value = None + + +def mock_printstream_println(self: JObj, args: List[JObj]): + if len(args) != 1: + raise ValueError("Argument length mismatch") + if args[0].type != 'java/lang/String': + raise ValueError("Argument type mismatch") + + print(args[0].value) + + +def mock_inputstream_read(self: JObj, args: List[JObj]): + character = sys.stdin.read(1).strip() + return JInteger(ord(character)) + + +class Frame: + ip: int = 0 + clazz: Class + method: Method + code: Code + stack: List[JObj] + this: JObj + local_variables: List + + def __init__(self, clazz: Class, method: Method): + self.clazz = clazz + self.method = method + self.code = self.method.code + self.stack = [] + self.local_variables = [None for _ in range(self.code.max_locals)] + + def get_byte(self, idx: int) -> int: + return struct.unpack('>b', self.code.code[idx:idx + 1])[0] + + def get_short(self, idx: int) -> int: + return struct.unpack('>h', self.code.code[idx:idx + 2])[0] + + def get_int(self, idx: int) -> int: + return struct.unpack('>i', self.code.code[idx:idx + 4])[0] + + def set_local(self, val: JObj, index: int): + if index > self.code.max_locals: + raise PJVMException("Local out of bound!") + self.local_variables[index] = val + + def get_local(self, index: int) ->JObj: + if index > self.code.max_locals: + raise PJVMException("Local out of bound!") + val = self.local_variables[index] + if val is None: + raise PJVMException("Local not set") + return val + + +class Opcode: + opcodes: Dict[int, OPCODE_TYPE] = {} + vm: "PJVirtualMachine" + func: OPCODE_BOUND_TYPE + owner: object + + @classmethod + def register(cls, opcode: Union[int, List[int]], method: OPCODE_TYPE): + if isinstance(opcode, int): + cls.opcodes[opcode] = method + else: + for oc in opcode: + cls.opcodes[oc] = method + + @classmethod + def get_opcodes(cls, vm: "PJVirtualMachine") -> Dict[int, OPCODE_TYPE]: + cls.vm = vm + return cls.opcodes + + def __init__(self, opcode: int, size: int = 0, no_ret: bool = False): + self.opcode = opcode + self.size = size + self.no_ret = no_ret + + def wrapped(self, opcode: int, frame: Frame): + retval = self.func(self.vm, opcode, frame) + frame.ip += self.size + if self.no_ret and retval is None: + retval = 0, None + return retval + + def __call__(self, func: OPCODE_BOUND_TYPE): + self.func = func + self.register(self.opcode, self.wrapped) + + +@Opcode(0xb2, size=3, no_ret=True) +def op_getstatic(vm: "PJVirtualMachine", opcode: int, frame: Frame): + """ + Get static value from field + """ + index = frame.get_short(frame.ip + 1) + owner, field_name, field_descriptor = frame.clazz.cp_get_fieldref(index) + LOGGER.info(f"Fetching {owner} -> {field_name} of type {field_descriptor}") + + field = vm.get_static_field(owner, field_name) + if not field.instance_of(field_descriptor): + raise PJVMTypeError(f"Type mismatch {field.type} != {field_descriptor}") + + frame.stack.append(field) + + +@Opcode(0x12, size=2, no_ret=True) +def op_ldc(vm: "PJVirtualMachine", opcode: int, frame: Frame): + """ + Get constant from constant pool + """ + index = frame.get_byte(frame.ip + 1) + + value = frame.clazz.cp_get(index) + resulting_value: JObj + + if value['type'] == 'String': + resulting_value = JString(frame.clazz.cp_get_utf8(value['string_index'])) + else: + raise PJVMNotImplemented("did not feel like implementing the other stuff") + + frame.stack.append(resulting_value) + + +@Opcode(0xb6, size=3, no_ret=True) +def op_invokevirtual(vm: "PJVirtualMachine", opcode: int, frame: Frame): + index = frame.get_short(frame.ip + 1) + result: JObj + + class_name, method_name, descriptor = frame.clazz.cp_get_methodref(index) + LOGGER.info(f"Calling {class_name} -> {method_name} {descriptor}") + args_types = utils.get_argument_count_types_descriptor(descriptor) + # todo type check + args = [] + for _ in args_types: + args.append(frame.stack.pop()) + object_ref = frame.stack.pop() + + result = vm.execute_instance_method(class_name, method_name, args, object_ref) + + if descriptor[-1] != 'V': + frame.stack.append(result) + + +@Opcode(0xb1, size=1) +def op_return(vm: "PJVirtualMachine", opcode: int, frame: Frame) -> Tuple[int, Optional[JObj]]: + return 1, None + + +@Opcode(0x92, size=1, no_ret=True) +def op_i2c(vm: "PJVirtualMachine", opcode: int, frame: Frame): + top = frame.stack.pop() + if not top.instance_of('I'): + raise ValueError("Type mismatch!") + + char = top.value & 0xFFFF + frame.stack.append(JInteger(char)) + + +@Opcode([0x03b, 0x3c, 0x3d, 0x3e], size=1, no_ret=True) +def op_istore_n(vm: "PJVirtualMachine", opcode: int, frame: Frame): + idx = (opcode + 1) & 0b11 + item = frame.stack.pop() + if not item.instance_of('I'): + raise PJVMTypeError("Must be integer") + + frame.set_local(item, idx) + + +@Opcode([0x1a, 0x1b, 0x1c, 0x1d], size=1, no_ret=True) +def op_iload_n(vm: "PJVirtualMachine", opcode: int, frame: Frame): + idx = (opcode + 2) & 0b11 + item = frame.get_local(idx) + if not item.instance_of('I'): + raise PJVMTypeError("Must be integer") + frame.stack.append(item) + + +@Opcode(0x10, size=2, no_ret=True) +def op_bipush(vm: "PJVirtualMachine", opcode: int, frame: Frame): + val = frame.get_byte(frame.ip+1) + frame.stack.append(JInteger(val)) + + +@Opcode([0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4], no_ret=True) +def op_icmp_cond(vm: "PJVirtualMachine", opcode: int, frame: Frame): + opcode_map = { + 0x9f: int.__eq__, + 0xa0: int.__ne__, + 0xa1: int.__lt__, + 0xa2: int.__ge__, + 0xa3: int.__gt__, + 0xa4: int.__le__, + } + left = frame.stack.pop() + right = frame.stack.pop() + if not left.instance_of("I") or not right.instance_of("I"): + raise PJVMTypeError("Must be of type integer") + if opcode_map[opcode](left.value, right.value): + target = frame.get_short(frame.ip + 1) + frame.ip += target + else: + frame.ip += 3 + + +@Opcode(0xa7, no_ret=True) +def op_goto(vm: "PJVirtualMachine", opcode: int, frame: Frame): + target = frame.get_short(frame.ip + 1) + frame.ip += target + + +class PJVirtualMachine: + instructions: Dict[int, OPCODE_TYPE] + + stack: List[Frame] = [] + + classes: Dict[str, Class] = {} + + mock_static_objects = { + 'java/lang/System': { + 'out': JMockSystemOut(), + 'in': JMockSystemIn() + } + } + + mock_instance_methods = { + 'java/io/PrintStream': { + 'println': mock_printstream_println + }, + 'java/io/InputStream': { + 'read': mock_inputstream_read + } + } + + def __init__(self, classpath: []): + self.classpath = classpath + self.instructions = Opcode.get_opcodes(self) + self.load_classes() + + def load_classes(self): + LOGGER.info(f"Loading {len(self.classpath)} classes") + for class_file in self.classpath: + LOGGER.info(f"Loading {class_file}") + loader = load_class(class_file) + clazz = Class(loader) + self.classes[clazz.this_class] = clazz + + LOGGER.info("Done loading") + + @staticmethod + def not_implemented_instruction(opcode: int, frame: Frame): + LOGGER.error(f"OPCODE {opcode} -> {INSTRUCTIONS[opcode]['name']} is not yet implemented!") + raise PJVMUnknownOpcode(opcode) + + def run(self, main_class: str, args: List[str] = None): + if args is None: + args = [] + + j_args: JArray = JArray(['java/lang/String'], list(map(JString, args))) + + if main_class not in self.classes: + raise PJVMException("main_class not found!") + + self.execute_static_method(main_class, 'Main', [j_args]) + + def execute_static_method(self, class_name: str, method_name: str, args: List[JObj]): + clazz = self.classes[class_name] + method = clazz.methods[method_name] + self.execute(clazz, method, args, None) + + def execute_instance_method(self, class_name: str, method_name: str, args: List[JObj], this: JObj) -> Optional[ + JObj]: + mock_class = self.mock_instance_methods.get(class_name) + if mock_class: + mock_method = mock_class.get(method_name) + if mock_method: + return mock_method(this, args) + + raise PJVMNotImplemented(f"Instance methods are not yet implemented {class_name}->{method_name}") + + def execute(self, clazz: Class, method: Method, args: List[JObj], this: Optional[JObj]): + frame = Frame(clazz, method) + frame.stack.extend(args) + frame.this = this + return_value = None + + self.stack.append(frame) + + while True: + instruction = frame.code.code[frame.ip] + try: + impl = self.instructions[instruction] + except KeyError: + self.not_implemented_instruction(instruction, frame) + break # unreachable + + result, meta = impl(instruction, frame) + if result == 0: + continue + + if result == 1: + return_value = meta + break + if result == 2: + raise PJVMNotImplemented("Exceptions are not implemented") + + self.stack.pop() + + if frame.method.return_value != 'V': + # add the return value to the stack of the caller if it is not a void call + self.stack[-1].stack.append(return_value) + + def get_static_field(self, class_name: str, field_name: str): + mock_type = self.mock_static_objects.get(class_name, None) + if mock_type: + mock_field = mock_type.get(field_name) + if mock_field: + return mock_field + + raise PJVMNotImplemented(f"Static fields are not implemented yet, only as mock {class_name}->{field_name}") diff --git a/run.py b/run.py new file mode 100644 index 0000000..b1ba69a --- /dev/null +++ b/run.py @@ -0,0 +1,22 @@ +from pprint import pprint +import logging + +from pjvm.classloader import load_class +from pjvm.clazz import Class +from pjvm.vm import PJVirtualMachine + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + + # parser = load_class('testclass/Test.class') + # pprint(parser.constant_pool) + # print(parser.this_class) + # print(parser.super_class) + # pprint(parser.interfaces) + # pprint(parser.fields) + # pprint(parser.methods) + # pprint(parser.attributes) + # clazz = Class(parser) + vm = PJVirtualMachine(['testclass/Test.class', 'testclass/AdvancedTest.class']) + vm.run('testclass/AdvancedTest') + diff --git a/testclass/AdvancedTest.java b/testclass/AdvancedTest.java new file mode 100644 index 0000000..1a33ccd --- /dev/null +++ b/testclass/AdvancedTest.java @@ -0,0 +1,12 @@ +package testclass; + +public class AdvancedTest { + public static void Main(String[] args) throws Exception{ + char c = (char) System.in.read(); + if (c == 'a') { + System.out.println("Is a"); + } else { + System.out.println("Is not a"); + } + } +} \ No newline at end of file diff --git a/testclass/Test.java b/testclass/Test.java new file mode 100644 index 0000000..8a818cc --- /dev/null +++ b/testclass/Test.java @@ -0,0 +1,9 @@ +package testclass; + +public class Test { + String testfield; + public static int otherField; + public static void Main(String[] args) { + System.out.println("Hello World!"); + } +}