From bd18b1e9001964a38ca0a848c69431e7e6452818 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Mon, 27 Dec 2021 01:03:59 -0500 Subject: [PATCH 1/9] Rebrand jawa -> lawu --- .circleci/config.yml | 35 - CNAME | 1 + MANIFEST.in | 4 +- README.md | 18 +- bytecode.yaml | 1690 +++++++++++++++++ docs/Makefile | 155 -- docs/conf.py | 294 --- docs/examples/dism.rst | 31 - docs/examples/hello_world.rst | 23 - docs/index.rst | 73 - docs/jawa.assemble.rst | 7 - docs/jawa.attribute.rst | 7 - docs/jawa.attributes.bootstrap.rst | 7 - docs/jawa.attributes.code.rst | 7 - docs/jawa.attributes.constant_value.rst | 7 - docs/jawa.attributes.deprecated.rst | 7 - docs/jawa.attributes.enclosing_method.rst | 7 - docs/jawa.attributes.exceptions.rst | 7 - docs/jawa.attributes.inner_classes.rst | 7 - docs/jawa.attributes.line_number_table.rst | 7 - docs/jawa.attributes.local_variable.rst | 7 - docs/jawa.attributes.local_variable_type.rst | 7 - docs/jawa.attributes.rst | 27 - docs/jawa.attributes.signature.rst | 7 - docs/jawa.attributes.source_file.rst | 7 - docs/jawa.attributes.stack_map_table.rst | 7 - docs/jawa.attributes.synthetic.rst | 7 - docs/jawa.cf.rst | 7 - docs/jawa.classloader.rst | 7 - docs/jawa.cli.rst | 6 - docs/jawa.constants.rst | 7 - docs/jawa.fields.rst | 7 - docs/jawa.methods.rst | 7 - docs/jawa.rst | 30 - docs/jawa.transforms.rst | 7 - docs/jawa.util.bytecode.rst | 7 - docs/jawa.util.descriptor.rst | 7 - docs/jawa.util.flags.rst | 7 - docs/jawa.util.rst | 22 - docs/jawa.util.shell.rst | 7 - docs/jawa.util.stream.rst | 7 - docs/jawa.util.tracer.rst | 7 - docs/jawa.util.utf.rst | 7 - docs/jawa.util.verifier.rst | 7 - docs/links.rst | 2 - docs/make.bat | 190 -- examples/creating_fields.py | 2 +- examples/disassemble.py | 4 +- examples/hello_world.py | 4 +- lawu.png | Bin 0 -> 72005 bytes {jawa => lawu}/__init__.py | 0 {jawa => lawu}/assemble.py | 8 +- {jawa => lawu}/attribute.py | 8 +- {jawa => lawu}/attributes/__init__.py | 4 +- {jawa => lawu}/attributes/bootstrap.py | 2 +- {jawa => lawu}/attributes/code.py | 14 +- {jawa => lawu}/attributes/constant_value.py | 2 +- {jawa => lawu}/attributes/deprecated.py | 2 +- {jawa => lawu}/attributes/enclosing_method.py | 2 +- {jawa => lawu}/attributes/exceptions.py | 2 +- {jawa => lawu}/attributes/inner_classes.py | 2 +- .../attributes/line_number_table.py | 2 +- {jawa => lawu}/attributes/local_variable.py | 2 +- .../attributes/local_variable_type.py | 2 +- {jawa => lawu}/attributes/signature.py | 2 +- {jawa => lawu}/attributes/source_file.py | 2 +- {jawa => lawu}/attributes/stack_map_table.py | 4 +- {jawa => lawu}/attributes/synthetic.py | 2 +- {jawa => lawu}/cf.py | 22 +- {jawa => lawu}/classloader.py | 4 +- {jawa => lawu}/cli.py | 14 +- {jawa => lawu}/constants.py | 2 +- {jawa => lawu}/fields.py | 16 +- {jawa => lawu}/methods.py | 14 +- {jawa => lawu}/transforms.py | 4 +- {jawa => lawu}/util/__init__.py | 2 +- {jawa => lawu}/util/bytecode.json | 0 {jawa => lawu}/util/bytecode.py | 2 +- {jawa => lawu}/util/bytecode.yaml | 0 {jawa => lawu}/util/descriptor.py | 4 +- {jawa => lawu}/util/flags.py | 0 {jawa => lawu}/util/shell.py | 0 {jawa => lawu}/util/stream.py | 0 {jawa => lawu}/util/tracer.py | 0 {jawa => lawu}/util/utf.py | 0 {jawa => lawu}/util/verifier.py | 0 lawu_small.png | Bin 0 -> 22376 bytes setup.py | 32 +- tests/attributes/test_general_attributes.py | 2 +- tests/attributes/test_inner_classes.py | 2 +- tests/attributes/test_sourcefile_attribute.py | 4 +- tests/attributes/test_stack_map_attribute.py | 2 +- tests/conftest.py | 2 +- tests/test_bytecode.py | 2 +- tests/test_classloader.py | 8 +- tests/test_expand_constants.py | 4 +- tests/test_hello_world.py | 2 +- tests/test_modified_utf8.py | 2 +- tests/test_printable.py | 4 +- 99 files changed, 1812 insertions(+), 1221 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 CNAME create mode 100644 bytecode.yaml delete mode 100644 docs/Makefile delete mode 100644 docs/conf.py delete mode 100644 docs/examples/dism.rst delete mode 100644 docs/examples/hello_world.rst delete mode 100644 docs/index.rst delete mode 100644 docs/jawa.assemble.rst delete mode 100644 docs/jawa.attribute.rst delete mode 100644 docs/jawa.attributes.bootstrap.rst delete mode 100644 docs/jawa.attributes.code.rst delete mode 100644 docs/jawa.attributes.constant_value.rst delete mode 100644 docs/jawa.attributes.deprecated.rst delete mode 100644 docs/jawa.attributes.enclosing_method.rst delete mode 100644 docs/jawa.attributes.exceptions.rst delete mode 100644 docs/jawa.attributes.inner_classes.rst delete mode 100644 docs/jawa.attributes.line_number_table.rst delete mode 100644 docs/jawa.attributes.local_variable.rst delete mode 100644 docs/jawa.attributes.local_variable_type.rst delete mode 100644 docs/jawa.attributes.rst delete mode 100644 docs/jawa.attributes.signature.rst delete mode 100644 docs/jawa.attributes.source_file.rst delete mode 100644 docs/jawa.attributes.stack_map_table.rst delete mode 100644 docs/jawa.attributes.synthetic.rst delete mode 100644 docs/jawa.cf.rst delete mode 100644 docs/jawa.classloader.rst delete mode 100644 docs/jawa.cli.rst delete mode 100644 docs/jawa.constants.rst delete mode 100644 docs/jawa.fields.rst delete mode 100644 docs/jawa.methods.rst delete mode 100644 docs/jawa.rst delete mode 100644 docs/jawa.transforms.rst delete mode 100644 docs/jawa.util.bytecode.rst delete mode 100644 docs/jawa.util.descriptor.rst delete mode 100644 docs/jawa.util.flags.rst delete mode 100644 docs/jawa.util.rst delete mode 100644 docs/jawa.util.shell.rst delete mode 100644 docs/jawa.util.stream.rst delete mode 100644 docs/jawa.util.tracer.rst delete mode 100644 docs/jawa.util.utf.rst delete mode 100644 docs/jawa.util.verifier.rst delete mode 100644 docs/links.rst delete mode 100644 docs/make.bat create mode 100644 lawu.png rename {jawa => lawu}/__init__.py (100%) rename {jawa => lawu}/assemble.py (95%) rename {jawa => lawu}/attribute.py (96%) rename {jawa => lawu}/attributes/__init__.py (74%) rename {jawa => lawu}/attributes/bootstrap.py (97%) rename {jawa => lawu}/attributes/code.py (92%) rename {jawa => lawu}/attributes/constant_value.py (95%) rename {jawa => lawu}/attributes/deprecated.py (92%) rename {jawa => lawu}/attributes/enclosing_method.py (94%) rename {jawa => lawu}/attributes/exceptions.py (95%) rename {jawa => lawu}/attributes/inner_classes.py (96%) rename {jawa => lawu}/attributes/line_number_table.py (96%) rename {jawa => lawu}/attributes/local_variable.py (96%) rename {jawa => lawu}/attributes/local_variable_type.py (96%) rename {jawa => lawu}/attributes/signature.py (95%) rename {jawa => lawu}/attributes/source_file.py (95%) rename {jawa => lawu}/attributes/stack_map_table.py (98%) rename {jawa => lawu}/attributes/synthetic.py (91%) rename {jawa => lawu}/cf.py (91%) rename {jawa => lawu}/classloader.py (98%) rename {jawa => lawu}/cli.py (93%) rename {jawa => lawu}/constants.py (99%) rename {jawa => lawu}/fields.py (94%) rename {jawa => lawu}/methods.py (95%) rename {jawa => lawu}/transforms.py (92%) rename {jawa => lawu}/util/__init__.py (91%) rename {jawa => lawu}/util/bytecode.json (100%) rename {jawa => lawu}/util/bytecode.py (99%) rename {jawa => lawu}/util/bytecode.yaml (100%) rename {jawa => lawu}/util/descriptor.py (94%) rename {jawa => lawu}/util/flags.py (100%) rename {jawa => lawu}/util/shell.py (100%) rename {jawa => lawu}/util/stream.py (100%) rename {jawa => lawu}/util/tracer.py (100%) rename {jawa => lawu}/util/utf.py (100%) rename {jawa => lawu}/util/verifier.py (100%) create mode 100644 lawu_small.png diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index cae8075..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: 2 -jobs: - build: - branches: - ignore: - - gh-pages - - docker: - - image: circleci/python:3.6.1 - - working_directory: ~/repo - - steps: - - checkout - - - run: - name: install dependencies - command: | - python3 -m venv venv - . venv/bin/activate - pip3 install --upgrade pip - pip3 uninstall -y setuptools - pip3 install setuptools - pip3 install -e "." - pip3 install -e ".[dev]" - - - run: - name: run tests - command: | - . venv/bin/activate - pytest - - - store_artifacts: - path: test-reports - destination: test-reports diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..3a623fb --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +lawu.dev diff --git a/MANIFEST.in b/MANIFEST.in index 9474423..c45737d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include jawa/util/bytecode.json -include jawa/util/bytecode.yaml \ No newline at end of file +include lawu/util/bytecode.json +include lawu/util/bytecode.yaml \ No newline at end of file diff --git a/README.md b/README.md index 9f82d0b..7c86cc6 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,16 @@ -# Jawa +![lawu](lawu_small.png) -[![CircleCI](https://img.shields.io/circleci/project/github/TkTech/Jawa/master.svg?style=for-the-badge)](https://circleci.com/gh/TkTech/Jawa) -[![license](https://img.shields.io/github/license/tktech/jawa.svg?style=for-the-badge)](LICENCE) +# lawu -Jawa is a human-friendly library for assembling, disassembling, and exploring -JVM class files. It's highly suitable for automation tasks. +[![MIT](https://img.shields.io/github/license/tktech/lawu.svg?style=for-the-badge)](LICENCE) -*NOTE*: The assembler does _not_ currently implement Stack Maps, an -artificially complex requirement for ClassFiles generated for Java 7 and -above to properly verify (unless you turn it off with -XX:-UseSplitVerifier). -However, assembled files targeting Java 6 will still work with 7 and above. +Lawu is a human-friendly library for assembling, disassembling, and exploring +JVM class files. It's highly suitable for automation tasks. ## Documentation -API documentation & examples are available at http://jawa.tkte.ch +API documentation & examples are available at https://lawu.dev ## Licence -Jawa is available under the MIT licence. See LICENCE. +Lawu is available under the MIT licence. See LICENCE. \ No newline at end of file diff --git a/bytecode.yaml b/bytecode.yaml new file mode 100644 index 0000000..169cbbd --- /dev/null +++ b/bytecode.yaml @@ -0,0 +1,1690 @@ +# This file provides a description of JVM instructions in a reasonably-portable +# manner. A few instructions (mainly lookupswitch and tableswitch) cannot +# be resonably described and require special casing in parsers. +# It's intent is to enable collaboration and reuse with other JVM tooling. +aaload: + op: 0x32 + desc: load onto the stack a reference from an array + stack: + before: + - ArrayRef + - Index + after: + - Value + runtime: + - NullPointerException + - ArrayIndexOutOfBoundsException +aastore: + op: 0x53 + desc: store into a reference in an array + stack: + before: + - ArrayRef + - Index + - Value + runtime: + - NullPointerException + - ArrayIndexOutOfBoundsException + - ArrayStoreException +aconst_null: + op: 0x01 + stack: + after: + - NullReference +aload: + op: 0x19 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + stack: + after: + - ObjectRef + can_be_wide: True +aload_0: + op: 0x2A + stack: + after: + - ObjectRef + transform: + simple_swap: + op: aload + operands: + - 0 +aload_1: + op: 0x2B + stack: + after: + - ObjectRef + transform: + simple_swap: + op: aload + operands: + - 1 +aload_2: + op: 0x2C + stack: + after: + - ObjectRef + transform: + simple_swap: + op: aload + operands: + - 2 +aload_3: + op: 0x2D + stack: + after: + - ObjectRef + transform: + simple_swap: + op: aload + operands: + - 3 +anewarray: + op: 0xBD + operands: + - ['USHORT', 'CONSTANT_INDEX'] + stack: + before: + - Value + after: + - ArrayRef +areturn: + op: 0xB0 + stack: + before: + - ObjectRef + causes_return: true +arraylength: + op: 0xBE + stack: + before: + - ArrayRef + after: + - Value +astore: + op: 0x3A + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + before: + - ObjectRef +astore_0: + op: 0x4B + transform: + simple_swap: + op: astore + operands: + - 0 + stack: + before: + - ObjectRef +astore_1: + op: 0x4C + transform: + simple_swap: + op: astore + operands: + - 1 + stack: + before: + - ObjectRef +astore_2: + op: 0x4D + transform: + simple_swap: + op: astore + operands: + - 2 + stack: + before: + - ObjectRef +astore_3: + op: 0x4E + transform: + simple_swap: + op: astore + operands: + - 3 + stack: + before: + - ObjectRef +athrow: + op: 0xBF + stack: + before: + - ObjectRef +baload: + op: 0x33 + stack: + before: + - ArrayRef + - Index + after: + - Value +bastore: + op: 0x54 + stack: + before: + - ArrayRef + - Index + - Value +bipush: + op: 0x10 + operands: + - ['BYTE', 'LITERAL'] + stack: + after: + - Value +caload: + op: 0x34 + stack: + before: + - ArrayRef + - Index + after: + - Value +castore: + op: 0x55 + stack: + before: + - ArrayRef + - Index + - Value +checkcast: + op: 0xC0 + operands: + - ['USHORT', 'CONSTANT_INDEX'] + stack: + before: + - ObjectRef + after: + - ObjectRef +d2f: + op: 0x90 + stack: + before: + - Double + - Double + after: + - Float +d2i: + op: 0x8E + stack: + before: + - Double + - Double + after: + - Integer +d2l: + op: 0x8F + stack: + before: + - Double + - Double + after: + - Long + - Long +dadd: + op: 0x63 + stack: + before: + - Double + - Double + - Double + - Double + after: + - Double + - Double +daload: + op: 0x31 + stack: + before: + - ArrayRef + - Index + after: + - Double + - Double +dastore: + op: 0x52 + stack: + before: + - ArrayRef + - Index + - Double + - Double +dcmpg: + op: 0x98 + stack: + before: + - Double + - Double + - Double + - Double + after: + - Integer +dcmpl: + op: 0x97 + stack: + before: + - Double + - Double + - Double + - Double + after: + - Integer +dconst_0: + op: 0x0E + stack: + after: + - Double + - Double +dconst_1: + op: 0x0F + stack: + after: + - Double + - Double +ddiv: + op: 0x6F + stack: + before: + - Double + - Double + - Double + - Double + after: + - Double + - Double +dload: + op: 0x18 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + after: + - Double + - Double +dload_0: + op: 0x26 + transform: + simple_swap: + op: 0x18 + operands: + - 0 + stack: + after: + - Double + - Double +dload_1: + op: 0x27 + transform: + simple_swap: + op: 0x18 + operands: + - 1 + stack: + after: + - Double + - Double +dload_2: + op: 0x28 + transform: + simple_swap: + op: 0x18 + operands: + - 2 + stack: + after: + - Double + - Double +dload_3: + op: 0x29 + transform: + simple_swap: + op: 0x18 + operands: + - 3 + stack: + after: + - Double + - Double +dmul: + op: 0x6B + stack: + before: + - Double + - Double + - Double + - Double + after: + - Double + - Double +dneg: + op: 0x77 + stack: + before: + - Double + - Double + after: + - Double + - Double +drem: + op: 0x73 + stack: + before: + - Double + - Double + - Double + - Double + after: + - Double + - Double +dreturn: + op: 0xAF + stack: + before: + - Double + - Double + causes_return: true +dstore: + op: 0x39 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + before: + - Double + - Double +dstore_0: + op: 0x47 + transform: + simple_swap: + op: dstore + operands: + - 0 + stack: + before: + - Double + - Double +dstore_1: + op: 0x48 + transform: + simple_swap: + op: dstore + operands: + - 1 + stack: + before: + - Double + - Double +dstore_2: + op: 0x49 + transform: + simple_swap: + op: dstore + operands: + - 2 + stack: + before: + - Double + - Double +dstore_3: + op: 0x4A + transform: + simple_swap: + op: dstore + operands: + - 3 + stack: + before: + - Double + - Double +dsub: + op: 0x67 + stack: + before: + - Double + - Double + - Double + - Double + after: + - Double + - Double +dup: + op: 0x59 + stack: + before: + - Any + after: + - Any + - Any +dup_x1: + op: 0x5A + stack: + before: + - Any + - Any + after: + - Any + - Any + - Any +dup_x2: + op: 0x5B + stack: + before: + - Any + - Any + - Any + after: + - Any + - Any + - Any + - Any +dup2: + op: 0x5C + stack: + before: + - Any + - Any + after: + - Any + - Any + - Any + - Any +dup2_x1: + op: 0x5D + stack: + before: + - Any + - Any + - Any + after: + - Any + - Any + - Any + - Any + - Any +dup2_x2: + op: 0x5E + stack: + before: + - Any + - Any + - Any + - Any + after: + - Any + - Any + - Any + - Any + - Any + - Any +f2d: + op: 0x8D + stack: + before: + - Float + after: + - Double + - Double +f2i: + op: 0x8B + stack: + before: + - Float + after: + - Integer +f2l: + op: 0x8C + stack: + before: + - Float + after: + - Long + - Long +fadd: + op: 0x62 + stack: + before: + - Float + - Float + after: + - Float +faload: + op: 0x30 + stack: + before: + - ArrayRef + - Index + after: + - Float +fastore: + op: 0x51 + stack: + before: + - ArrayRef + - Index + - Float +fcmpg: + op: 0x96 + stack: + before: + - Float + - Float + after: + - Integer +fcmpl: + op: 0x95 + stack: + before: + - Float + - Float + after: + - Integer +fconst_0: + op: 0x0B + stack: + after: + - Float +fconst_1: + op: 0x0C + stack: + after: + - Float +fconst_2: + op: 0x0D + stack: + after: + - Float +fdiv: + op: 0x6E + stack: + before: + - Float + - Float + after: + - Float +fload: + op: 0x17 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + after: + - Float +fload_0: + op: 0x22 + transform: + simple_swap: + op: fload + operands: + - 0 + stack: + after: + - Float +fload_1: + op: 0x23 + transform: + simple_swap: + op: fload + operands: + - 1 + stack: + after: + - Float +fload_2: + op: 0x24 + transform: + simple_swap: + op: fload + operands: + - 2 + stack: + after: + - Float +fload_3: + op: 0x25 + transform: + simple_swap: + op: fload + operands: + - 3 + stack: + after: + - Float +fmul: + op: 0x6A + stack: + before: + - Float + - Float + after: + - Float +fneg: + op: 0x76 + stack: + before: + - Float + after: + - Float +frem: + op: 0x72 + stack: + before: + - Float + - Float + after: + - Float +freturn: + op: 0xAE + stack: + before: + - Float + causes_return: true +fstore: + op: 0x38 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + before: + - Float +fstore_0: + op: 0x43 + transform: + simple_swap: + op: fstore + operands: + - 0 + stack: + before: + - Float +fstore_1: + op: 0x44 + transform: + simple_swap: + op: fstore + operands: + - 1 + stack: + before: + - Float +fstore_2: + op: 0x45 + transform: + simple_swap: + op: fstore + operands: + - 2 + stack: + before: + - Float +fstore_3: + op: 0x46 + transform: + simple_swap: + op: fstore + operands: + - 3 + stack: + before: + - Float +fsub: + op: 0x66 + stack: + before: + - Float + - Float + after: + - Float +getfield: + op: 0xB4 + operands: + - ['USHORT', 'CONSTANT_INDEX'] +getstatic: + op: 0xB2 + operands: + - ['USHORT', 'CONSTANT_INDEX'] +goto: + op: 0xA7 + operands: + - ['SHORT', 'BRANCH'] +goto_w: + op: 0xC8 + operands: + - ['INTEGER', 'BRANCH'] +i2b: + op: 0x91 + stack: + before: + - Integer + after: + - Integer +i2c: + op: 0x92 + stack: + before: + - Integer + after: + - Integer +i2d: + op: 0x87 + stack: + before: + - Integer + after: + - Double + - Double +i2f: + op: 0x86 + stack: + before: + - Integer + after: + - Float +i2l: + op: 0x85 + stack: + before: + - Integer + after: + - Long + - Long +i2s: + op: 0x93 + stack: + before: + - Integer + after: + - Integer +iadd: + op: 0x60 + stack: + before: + - Integer + - Integer + after: + - Integer +iaload: + op: 0x2E + stack: + before: + - ArrayRef + - Index + after: + - Integer +iand: + op: 0x7E + stack: + before: + - Integer + - Integer + after: + - Integer +iastore: + op: 0x4F + stack: + before: + - ArrayRef + - Index + - Integer +iconst_m1: + op: 0x02 + transform: + simple_swap: + op: bipush + operands: + - -1 + stack: + after: + - Integer +iconst_0: + op: 0x03 + transform: + simple_swap: + op: bipush + operands: + - 0 + stack: + after: + - Integer +iconst_1: + op: 0x04 + transform: + simple_swap: + op: bipush + operands: + - 1 + stack: + after: + - Integer +iconst_2: + op: 0x05 + transform: + simple_swap: + op: bipush + operands: + - 2 + stack: + after: + - Integer +iconst_3: + op: 0x06 + transform: + simple_swap: + op: bipush + operands: + - 3 + stack: + after: + - Integer +iconst_4: + op: 0x07 + transform: + simple_swap: + op: bipush + operands: + - 4 + stack: + after: + - Integer +iconst_5: + op: 0x08 + transform: + simple_swap: + op: bipush + operands: + - 5 + stack: + after: + - Integer +idiv: + op: 0x6C + stack: + before: + - Integer + - Integer + after: + - Integer +if_acmpeq: + op: 0xA5 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - ObjectRef + - ObjectRef +if_acmpne: + op: 0xA6 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - ObjectRef + - ObjectRef +if_icmpeq: + op: 0x9F + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer + - Integer +if_icmpne: + op: 0xA0 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer + - Integer +if_icmplt: + op: 0xA1 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer + - Integer +if_icmpge: + op: 0xA2 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer + - Integer +if_icmpgt: + op: 0xA3 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer + - Integer +if_icmple: + op: 0xA4 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer + - Integer +ifeq: + op: 0x99 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer +ifne: + op: 0x9A + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer +iflt: + op: 0x9B + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer +ifge: + op: 0x9C + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer +ifgt: + op: 0x9D + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer +ifle: + op: 0x9E + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - Integer +ifnonnull: + op: 0xC7 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - ObjectRef +ifnull: + op: 0xC6 + operands: + - ['SHORT', 'BRANCH'] + stack: + before: + - ObjectRef +iinc: + op: 0x84 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + - ['UBYTE', 'LITERAL'] + can_be_wide: True +iload: + op: 0x15 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + after: + - Integer +iload_0: + op: 0x1A + transform: + simple_swap: + op: iload + operands: + - 0 + stack: + after: + - Integer +iload_1: + op: 0x1B + transform: + simple_swap: + op: iload + operands: + - 1 + stack: + after: + - Integer +iload_2: + op: 0x1C + transform: + simple_swap: + op: iload + operands: + - 2 + stack: + after: + - Integer +iload_3: + op: 0x1D + transform: + simple_swap: + op: iload + operands: + - 3 + stack: + after: + - Integer +imul: + op: 0x68 + stack: + before: + - Integer + - Integer + after: + - Integer +ineg: + op: 0x74 + stack: + before: + - Integer + after: + - Integer +instanceof: + op: 0xC1 + operands: + - ['USHORT', 'CONSTANT_INDEX'] + stack: + before: + - ObjectRef + after: + - Integer +invokedynamic: + op: 0xBA + operands: + - ['USHORT', 'CONSTANT_INDEX'] + - ['UBYTE', 'PADDING'] + - ['UBYTE', 'PADDING'] +invokeinterface: + op: 0xB9 + operands: + - ['USHORT', 'CONSTANT_INDEX'] + - ['UBYTE', 'LITERAL'] + - ['UBYTE', 'PADDING'] +invokespecial: + op: 0xB7 + operands: + - ['USHORT', 'CONSTANT_INDEX'] +invokestatic: + op: 0xB8 + operands: + - ['USHORT', 'CONSTANT_INDEX'] +invokevirtual: + op: 0xB6 + operands: + - ['USHORT', 'CONSTANT_INDEX'] +ior: + op: 0x80 + stack: + before: + - Integer + - Integer + after: + - Integer +irem: + op: 0x70 + stack: + before: + - Integer + - Integer + after: + - Integer +ireturn: + op: 0xAC + stack: + before: + - Integer + causes_return: true +ishl: + op: 0x78 + stack: + before: + - Integer + - Integer + after: + - Integer +ishr: + op: 0x7A + stack: + before: + - Integer + - Integer + after: + - Integer +istore: + op: 0x36 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + before: + - Integer +istore_0: + op: 0x3B + transform: + simple_swap: + op: istore + operands: + - 0 + stack: + before: + - Integer +istore_1: + op: 0x3C + transform: + simple_swap: + op: istore + operands: + - 1 + stack: + before: + - Integer +istore_2: + op: 0x3D + transform: + simple_swap: + op: istore + operands: + - 2 + stack: + before: + - Integer +istore_3: + op: 0x3E + transform: + simple_swap: + op: istore + operands: + - 3 + stack: + before: + - Integer +isub: + op: 0x64 + stack: + before: + - Integer + - Integer + after: + - Integer +iushr: + op: 0x7C + stack: + before: + - Integer + - Integer + after: + - Integer +ixor: + op: 0x82 + stack: + before: + - Integer + - Integer + after: + - Integer +jsr: + op: 0xA8 + operands: + - ['SHORT', 'BRANCH'] + stack: + after: + - PC +jsr_w: + op: 0xC9 + operands: + - ['INTEGER', 'BRANCH'] + stack: + after: + - PC +l2d: + op: 0x8A + stack: + before: + - Long + - Long + after: + - Double + - Double +l2f: + op: 0x89 + stack: + before: + - Long + - Long + after: + - Float +l2i: + op: 0x88 + stack: + before: + - Long + - Long + after: + - Integer +ladd: + op: 0x61 + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +laload: + op: 0x2F + stack: + before: + - ArrayRef + - Index + after: + - Long + - Long +land: + op: 0x7F + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +lastore: + op: 0x50 + stack: + before: + - ArrayRef + - Index + - Long + - Long +lcmp: + op: 0x94 + stack: + before: + - Long + - Long + - Long + - Long + after: + - Integer +lconst_0: + op: 0x09 + stack: + after: + - Long + - Long +lconst_1: + op: 0x0A + stack: + after: + - Long + - Long +ldc: + op: 0x12 + operands: + - ['UBYTE', 'CONSTANT_INDEX'] + stack: + after: + - Any +ldc_w: + op: 0x13 + operands: + - ['USHORT', 'CONSTANT_INDEX'] + stack: + after: + - Any +ldc2_w: + op: 0x14 + operands: + - ['USHORT', 'CONSTANT_INDEX'] + stack: + after: + - Any + - Any +ldiv: + op: 0x6D + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +lload: + op: 0x16 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + after: + - Long + - Long +lload_0: + op: 0x1E + transform: + simple_swap: + op: lload + operands: + - 0 + stack: + after: + - Long + - Long +lload_1: + op: 0x1F + transform: + simple_swap: + op: lload + operands: + - 1 + stack: + after: + - Long + - Long +lload_2: + op: 0x20 + transform: + simple_swap: + op: lload + operands: + - 2 + stack: + after: + - Long + - Long +lload_3: + op: 0x21 + transform: + simple_swap: + op: lload + operands: + - 3 + stack: + after: + - Long + - Long +lmul: + op: 0x69 + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +lneg: + op: 0x75 + stack: + before: + - Long + - Long + after: + - Long + - Long +lookupswitch: + op: 0xAB + stack: + before: + - Value +lor: + op: 0x81 + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +lrem: + op: 0x71 + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +lreturn: + op: 0xAD + stack: + before: + - Long + - Long + causes_return: true +lshl: + op: 0x79 + stack: + before: + - Long + - Long + - Integer + after: + - Long + - Long +lshr: + op: 0x7B + stack: + before: + - Long + - Long + - Integer + after: + - Long + - Long +lstore: + op: 0x37 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True + stack: + before: + - Long + - Long +lstore_0: + op: 0x3F + transform: + simple_swap: + op: lstore + operands: + - 0 + stack: + before: + - Long + - Long +lstore_1: + op: 0x40 + transform: + simple_swap: + op: lstore + operands: + - 1 + stack: + before: + - Long + - Long +lstore_2: + op: 0x41 + transform: + simple_swap: + op: lstore + operands: + - 2 + stack: + before: + - Long + - Long +lstore_3: + op: 0x42 + transform: + simple_swap: + op: lstore + operands: + - 3 + stack: + before: + - Long + - Long +lsub: + op: 0x65 + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +lushr: + op: 0x7D + stack: + before: + - Long + - Long + - Integer + after: + - Long + - Long +lxor: + op: 0x83 + stack: + before: + - Long + - Long + - Long + - Long + after: + - Long + - Long +monitorenter: + op: 0xC2 + stack: + before: + - ObjectRef +monitorexit: + op: 0xC3 + stack: + before: + - ObjectRef +multianewarray: + op: 0xC5 + operands: + - ['USHORT', 'CONSTANT_INDEX'] + - ['UBYTE', 'LITERAL'] +new: + op: 0xBB + operands: + - ['USHORT', 'CONSTANT_INDEX'] + stack: + after: + - ObjectRef +newarray: + op: 0xBC + operands: + - ['UBYTE', 'LITERAL'] + stack: + before: + - Integer + after: + - ArrayRef +nop: + op: 0x00 +pop: + op: 0x57 + stack: + before: + - Any +pop2: + op: 0x58 + stack: + before: + - Any + - Any +putfield: + op: 0xB5 + operands: + - ['USHORT', 'CONSTANT_INDEX'] +putstatic: + op: 0xB3 + operands: + - ['USHORT', 'CONSTANT_INDEX'] +ret: + op: 0xA9 + operands: + - ['UBYTE', 'LOCAL_INDEX'] + can_be_wide: True +return: + op: 0xB1 + causes_return: true +saload: + op: 0x35 + stack: + before: + - ArrayRef + - Index + after: + - Integer +sastore: + op: 0x56 + stack: + before: + - ArrayRef + - Index + - Integer +sipush: + op: 0x11 + operands: + - ['SHORT', 'LITERAL'] + stack: + after: + - Integer +swap: + op: 0x5F + stack: + before: + - Any + - Any + after: + - Any + - Any +tableswitch: + op: 0xAA + stack: + before: + - Any +wide: + op: 0xC4 +breakpoint: + op: 0xCA +impdep1: + op: 0xFE +impdep2: + op: 0xFF diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 6128e89..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,155 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - touch $(BUILDDIR)/html/.nojekyll - echo "jawa.tkte.ch" > $(BUILDDIR)/html/CNAME - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/jawa.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/jawa.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/jawa" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/jawa" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index f65bc88..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,294 +0,0 @@ -# -# jawa documentation build configuration file, created by -# sphinx-quickstart on Wed Oct 31 23:00:27 2012. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('../jawa')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinx.ext.autosummary', - 'sphinxcontrib.googleanalytics', - 'sphinx_click.ext' -] - -# Google Analytics ID -googleanalytics_id = 'UA-11145163-14' - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'jawa' -copyright = u'2019, Tyler Kennedy' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The full version, including alpha/beta/rc tags. -release = '2.2.0' -# The short X.Y version. -version = '.'.join(release.split('.')[:-1]) - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = { -} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'jawadoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'jawa.tex', u'jawa Documentation', - u'Author', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'jawa', u'jawa Documentation', - [u'Author'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'jawa', u'jawa Documentation', - u'Author', 'jawa', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - - -# -- Options for Epub output --------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = u'jawa' -epub_author = u'Author' -epub_publisher = u'Author' -epub_copyright = u'2012, Author' - -# The language of the text. It defaults to the language option -# or en if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -#epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True diff --git a/docs/examples/dism.rst b/docs/examples/dism.rst deleted file mode 100644 index fb1a17b..0000000 --- a/docs/examples/dism.rst +++ /dev/null @@ -1,31 +0,0 @@ -Disassembly - A Simple javap Clone -================================== - -`Javap`_ is the defacto Java disassembler and is included when you installed -the Oracle JDK. We can make a simple clone very easily using Jawa: - -.. literalinclude:: ../../examples/disassemble.py - -If we try this out on a HelloWorld class, our output will look like: - -.. code-block:: text - - $ python disassemble.py --class-path ../tests/data HelloWorld develop - ; ---------------------------------------------- constant pool - ; -------------------------------------------------- total: 21 - ; 0001: ) - ; 0002: ))> - ... - ; ----------------------------------------------------- fields - ; --------------------------------------------------- total: 0 - ; ---------------------------------------------------- methods - ; --------------------------------------------------- total: 1 - acc_public acc_static void main(java/lang/String[]) { - 0000 [0xB2] getstatic <- C[13] - 0003 [0x12] ldc <- C[15] - 0005 [0xB6] invokevirtual <- C[21] - 0008 [0xB1] return <- - } - - -.. _Javap: https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javap.html diff --git a/docs/examples/hello_world.rst b/docs/examples/hello_world.rst deleted file mode 100644 index 7e6c8d5..0000000 --- a/docs/examples/hello_world.rst +++ /dev/null @@ -1,23 +0,0 @@ -Generating Classes From Scratch - Hello World! -============================================== - -A simple example of the classic "Hello World!" program. This example will -generate a ClassFile equivalent to this Java: - -.. code-block:: java - - class HelloWorld { - public static void main(String[] args) { - System.out.println("Hello World!"); - } - } - -.. literalinclude:: ../../examples/hello_world.py - -Our example can then be run in the standard JVM: - -.. code-block:: text - - $ python hello_world.py - $ java HelloWorld develop - Hello World! \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 5d1d603..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,73 +0,0 @@ -Jawa - JVM ClassFile Library -================================ - -Jawa_ is a human-friendly library for assembling, disassembling, and exploring -JVM class files. It's highly suitable for automation tasks. - -.. note:: This documentation is up to date as of |today|. - -Why Jawa? ---------- - -Jawa is intended to be used very differently from most projects similar to it. -Instead of trying to produce a human-readable disassembly or nearly-compilable -Java, it's intended for people that want to dive into or automate work on JVM -bytecode. For example, Jawa has been used for: - -- The automatic verification of community-uploaded plugins -- Analysis and analytics of public Android APKs (thousands at a time) -- Automatic extraction of "private" API keys embedded in Android APKs. -- Automated reverse engineering of the Minecraft server and client. - -Jawa is permissively licenced under the MIT licence. You're free to use it in -any type of project should it be commercial, closed source or open source. - - -Getting Started ---------------- - -It's recommended to use the :class:`~jawa.classloader.ClassLoader` when working -with JARs/directories, as it offers a number of conveniences. Here's an example -of loading each of the classes in the Minecraft_ server. - -.. code-block:: python - - from jawa.classloader import ClassLoader - - loader = ClassLoader('minecraft_server.jar') - for class_path in loader.classes: - cf = loader[class_path] - - -Alternatively you can create & load a :class:`~jawa.cf.ClassFile` directly. - -.. code-block:: python - - from jawa.cf import ClassFile - - with open('HelloWorld.class') as file_in: - cf = ClassFile(file_in) - - -Examples --------- - -.. toctree:: - :maxdepth: 2 - - examples/hello_world.rst - examples/dism.rst - - -Jawa API --------- - -.. toctree:: - :maxdepth: 3 - - jawa - -* :ref:`genindex` -* :ref:`search` - -.. include:: links.rst diff --git a/docs/jawa.assemble.rst b/docs/jawa.assemble.rst deleted file mode 100644 index 3459d13..0000000 --- a/docs/jawa.assemble.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.assemble module -==================== - -.. automodule:: jawa.assemble - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attribute.rst b/docs/jawa.attribute.rst deleted file mode 100644 index 1695ef8..0000000 --- a/docs/jawa.attribute.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attribute module -===================== - -.. automodule:: jawa.attribute - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.bootstrap.rst b/docs/jawa.attributes.bootstrap.rst deleted file mode 100644 index b1059d5..0000000 --- a/docs/jawa.attributes.bootstrap.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.bootstrap module -================================ - -.. automodule:: jawa.attributes.bootstrap - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.code.rst b/docs/jawa.attributes.code.rst deleted file mode 100644 index 6657b94..0000000 --- a/docs/jawa.attributes.code.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.code module -=========================== - -.. automodule:: jawa.attributes.code - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.constant_value.rst b/docs/jawa.attributes.constant_value.rst deleted file mode 100644 index 7ce8ae9..0000000 --- a/docs/jawa.attributes.constant_value.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.constant\_value module -====================================== - -.. automodule:: jawa.attributes.constant_value - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.deprecated.rst b/docs/jawa.attributes.deprecated.rst deleted file mode 100644 index d4e738c..0000000 --- a/docs/jawa.attributes.deprecated.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.deprecated module -================================= - -.. automodule:: jawa.attributes.deprecated - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.enclosing_method.rst b/docs/jawa.attributes.enclosing_method.rst deleted file mode 100644 index 48d3f88..0000000 --- a/docs/jawa.attributes.enclosing_method.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.enclosing\_method module -======================================== - -.. automodule:: jawa.attributes.enclosing_method - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.exceptions.rst b/docs/jawa.attributes.exceptions.rst deleted file mode 100644 index 13b07b0..0000000 --- a/docs/jawa.attributes.exceptions.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.exceptions module -================================= - -.. automodule:: jawa.attributes.exceptions - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.inner_classes.rst b/docs/jawa.attributes.inner_classes.rst deleted file mode 100644 index ef99f1a..0000000 --- a/docs/jawa.attributes.inner_classes.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.inner\_classes module -===================================== - -.. automodule:: jawa.attributes.inner_classes - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.line_number_table.rst b/docs/jawa.attributes.line_number_table.rst deleted file mode 100644 index 31ce85d..0000000 --- a/docs/jawa.attributes.line_number_table.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.line\_number\_table module -========================================== - -.. automodule:: jawa.attributes.line_number_table - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.local_variable.rst b/docs/jawa.attributes.local_variable.rst deleted file mode 100644 index a332d58..0000000 --- a/docs/jawa.attributes.local_variable.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.local\_variable module -====================================== - -.. automodule:: jawa.attributes.local_variable - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.local_variable_type.rst b/docs/jawa.attributes.local_variable_type.rst deleted file mode 100644 index 7c8ac2b..0000000 --- a/docs/jawa.attributes.local_variable_type.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.local\_variable\_type module -============================================ - -.. automodule:: jawa.attributes.local_variable_type - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.rst b/docs/jawa.attributes.rst deleted file mode 100644 index 9623f4c..0000000 --- a/docs/jawa.attributes.rst +++ /dev/null @@ -1,27 +0,0 @@ -jawa.attributes package -======================= - -.. automodule:: jawa.attributes - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - - jawa.attributes.bootstrap - jawa.attributes.code - jawa.attributes.constant_value - jawa.attributes.deprecated - jawa.attributes.exceptions - jawa.attributes.line_number_table - jawa.attributes.local_variable - jawa.attributes.signature - jawa.attributes.source_file - jawa.attributes.stack_map_table - jawa.attributes.enclosing_method - jawa.attributes.inner_classes - jawa.attributes.local_variable_type - jawa.attributes.synthetic diff --git a/docs/jawa.attributes.signature.rst b/docs/jawa.attributes.signature.rst deleted file mode 100644 index 28dc145..0000000 --- a/docs/jawa.attributes.signature.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.signature module -================================ - -.. automodule:: jawa.attributes.signature - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.source_file.rst b/docs/jawa.attributes.source_file.rst deleted file mode 100644 index 2b378b1..0000000 --- a/docs/jawa.attributes.source_file.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.source\_file module -=================================== - -.. automodule:: jawa.attributes.source_file - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.stack_map_table.rst b/docs/jawa.attributes.stack_map_table.rst deleted file mode 100644 index 72ea891..0000000 --- a/docs/jawa.attributes.stack_map_table.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.stack\_map\_table module -======================================== - -.. automodule:: jawa.attributes.stack_map_table - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.attributes.synthetic.rst b/docs/jawa.attributes.synthetic.rst deleted file mode 100644 index 0c37c7a..0000000 --- a/docs/jawa.attributes.synthetic.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.attributes.synthetic module -================================ - -.. automodule:: jawa.attributes.synthetic - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.cf.rst b/docs/jawa.cf.rst deleted file mode 100644 index 2e36130..0000000 --- a/docs/jawa.cf.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.cf module -============== - -.. automodule:: jawa.cf - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.classloader.rst b/docs/jawa.classloader.rst deleted file mode 100644 index 05cf25c..0000000 --- a/docs/jawa.classloader.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.classloader module -======================= - -.. automodule:: jawa.classloader - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.cli.rst b/docs/jawa.cli.rst deleted file mode 100644 index e960c10..0000000 --- a/docs/jawa.cli.rst +++ /dev/null @@ -1,6 +0,0 @@ -jawa.cli package -================ - -.. click:: jawa.cli:cli - :prog: jawa - :show-nested: \ No newline at end of file diff --git a/docs/jawa.constants.rst b/docs/jawa.constants.rst deleted file mode 100644 index 74bf41e..0000000 --- a/docs/jawa.constants.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.constants module -===================== - -.. automodule:: jawa.constants - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.fields.rst b/docs/jawa.fields.rst deleted file mode 100644 index c67f544..0000000 --- a/docs/jawa.fields.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.fields module -================== - -.. automodule:: jawa.fields - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.methods.rst b/docs/jawa.methods.rst deleted file mode 100644 index cc7b71d..0000000 --- a/docs/jawa.methods.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.methods module -=================== - -.. automodule:: jawa.methods - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.rst b/docs/jawa.rst deleted file mode 100644 index 3b72548..0000000 --- a/docs/jawa.rst +++ /dev/null @@ -1,30 +0,0 @@ -jawa package -============ - -.. automodule:: jawa - :members: - :undoc-members: - :show-inheritance: - -Subpackages ------------ - -.. toctree:: - - jawa.attributes - jawa.util - -Submodules ----------- - -.. toctree:: - - jawa.assemble - jawa.attribute - jawa.cf - jawa.classloader - jawa.constants - jawa.fields - jawa.methods - jawa.transforms - jawa.cli diff --git a/docs/jawa.transforms.rst b/docs/jawa.transforms.rst deleted file mode 100644 index 88f8a62..0000000 --- a/docs/jawa.transforms.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.transforms module -====================== - -.. automodule:: jawa.transforms - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.bytecode.rst b/docs/jawa.util.bytecode.rst deleted file mode 100644 index ca8891a..0000000 --- a/docs/jawa.util.bytecode.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.bytecode module -========================= - -.. automodule:: jawa.util.bytecode - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.descriptor.rst b/docs/jawa.util.descriptor.rst deleted file mode 100644 index eb5fdd2..0000000 --- a/docs/jawa.util.descriptor.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.descriptor module -=========================== - -.. automodule:: jawa.util.descriptor - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.flags.rst b/docs/jawa.util.flags.rst deleted file mode 100644 index 9444fd4..0000000 --- a/docs/jawa.util.flags.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.flags module -====================== - -.. automodule:: jawa.util.flags - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.rst b/docs/jawa.util.rst deleted file mode 100644 index 2847720..0000000 --- a/docs/jawa.util.rst +++ /dev/null @@ -1,22 +0,0 @@ -jawa.util package -================= - -.. automodule:: jawa.util - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - - jawa.util.bytecode - jawa.util.descriptor - jawa.util.flags - jawa.util.shell - jawa.util.stream - jawa.util.tracer - jawa.util.utf - jawa.util.verifier - diff --git a/docs/jawa.util.shell.rst b/docs/jawa.util.shell.rst deleted file mode 100644 index 585d3d3..0000000 --- a/docs/jawa.util.shell.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.shell module -====================== - -.. automodule:: jawa.util.shell - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.stream.rst b/docs/jawa.util.stream.rst deleted file mode 100644 index 2a3a817..0000000 --- a/docs/jawa.util.stream.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.stream module -======================= - -.. automodule:: jawa.util.stream - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.tracer.rst b/docs/jawa.util.tracer.rst deleted file mode 100644 index 69d9604..0000000 --- a/docs/jawa.util.tracer.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.tracer module -======================= - -.. automodule:: jawa.util.tracer - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.utf.rst b/docs/jawa.util.utf.rst deleted file mode 100644 index db5d8fd..0000000 --- a/docs/jawa.util.utf.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.utf module -==================== - -.. automodule:: jawa.util.utf - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/jawa.util.verifier.rst b/docs/jawa.util.verifier.rst deleted file mode 100644 index a246d5c..0000000 --- a/docs/jawa.util.verifier.rst +++ /dev/null @@ -1,7 +0,0 @@ -jawa.util.verifier module -========================= - -.. automodule:: jawa.util.verifier - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/links.rst b/docs/links.rst deleted file mode 100644 index e1b0c31..0000000 --- a/docs/links.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. _Jawa: http://github.com/TkTech/Jawa -.. _Minecraft: http://minecraft.net \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 65e1cfd..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,190 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\jawa.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\jawa.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/examples/creating_fields.py b/examples/creating_fields.py index e5afbf2..f6025ef 100644 --- a/examples/creating_fields.py +++ b/examples/creating_fields.py @@ -1,7 +1,7 @@ """ An example showing how to create fields on a new class. """ -from jawa import ClassFile +from lawu import ClassFile if __name__ == '__main__': cf = ClassFile.create('HelloWorld') diff --git a/examples/disassemble.py b/examples/disassemble.py index c9e250d..6563848 100644 --- a/examples/disassemble.py +++ b/examples/disassemble.py @@ -1,7 +1,7 @@ import click -from jawa.classloader import ClassLoader -from jawa.util.bytecode import OperandTypes +from lawu.classloader import ClassLoader +from lawu.util.bytecode import OperandTypes @click.command() diff --git a/examples/hello_world.py b/examples/hello_world.py index d53fa37..02dd80f 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -1,8 +1,8 @@ """ An example showing how to create a "Hello World" class from scratch. """ -from jawa.cf import ClassFile -from jawa.assemble import assemble +from lawu.cf import ClassFile +from lawu.assemble import assemble cf = ClassFile.create('HelloWorld') diff --git a/lawu.png b/lawu.png new file mode 100644 index 0000000000000000000000000000000000000000..a948a079c91c687b57b1bc00e61ceb65a96170b6 GIT binary patch literal 72005 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 from jawa.assemble import assemble, Label + >>> from lawu.assemble import assemble, Label >>> print(list(assemble(( ... Label('start'), ... ('goto', Label('start')) diff --git a/jawa/attribute.py b/lawu/attribute.py similarity index 96% rename from jawa/attribute.py rename to lawu/attribute.py index 62e5ce5..136eedc 100644 --- a/jawa/attribute.py +++ b/lawu/attribute.py @@ -5,8 +5,8 @@ from struct import unpack, pack from itertools import repeat -from jawa.constants import UTF8 -from jawa.util.stream import BufferStreamReader +from lawu.constants import UTF8 +from lawu.util.stream import BufferStreamReader class Attribute(object): @@ -163,8 +163,8 @@ def get_attribute_classes() -> Dict[str, Attribute]: Lookup all builtin Attribute subclasses, load them, and return a dict """ attribute_children = pkgutil.iter_modules( - importlib.import_module('jawa.attributes').__path__, - prefix='jawa.attributes.' + importlib.import_module('lawu.attributes').__path__, + prefix='lawu.attributes.' ) result = {} diff --git a/jawa/attributes/__init__.py b/lawu/attributes/__init__.py similarity index 74% rename from jawa/attributes/__init__.py rename to lawu/attributes/__init__.py index 871304d..f51921b 100644 --- a/jawa/attributes/__init__.py +++ b/lawu/attributes/__init__.py @@ -3,11 +3,11 @@ ====================== In addition to standard JVM attributes various compilers, debuggers, and other -tools may insert additional unknown attributes into a :class:`~jawa.cf +tools may insert additional unknown attributes into a :class:`~lawu.cf .ClassFile`. Not all standard attributes are currently implemented. When an unknown -attribute is encountered, an :class:`~jawa.attribute.UnknownAttribute` object +attribute is encountered, an :class:`~lawu.attribute.UnknownAttribute` object is created instead. This UnknownAttribute retains the name and content of the original attribute, allowing you to parse it yourself or to simply pass it through. diff --git a/jawa/attributes/bootstrap.py b/lawu/attributes/bootstrap.py similarity index 97% rename from jawa/attributes/bootstrap.py rename to lawu/attributes/bootstrap.py index bf6be9f..8e7e389 100644 --- a/jawa/attributes/bootstrap.py +++ b/lawu/attributes/bootstrap.py @@ -3,7 +3,7 @@ from itertools import repeat from struct import pack -from jawa.attribute import Attribute +from lawu.attribute import Attribute BootstrapMethod = namedtuple( 'BootstrapMethod', diff --git a/jawa/attributes/code.py b/lawu/attributes/code.py similarity index 92% rename from jawa/attributes/code.py rename to lawu/attributes/code.py index 8801fdb..1154f67 100644 --- a/jawa/attributes/code.py +++ b/lawu/attributes/code.py @@ -6,8 +6,8 @@ from itertools import repeat from collections import namedtuple -from jawa.attribute import Attribute, AttributeTable -from jawa.util.bytecode import ( +from lawu.attribute import Attribute, AttributeTable +from lawu.util.bytecode import ( read_instruction, write_instruction, Instruction @@ -27,8 +27,8 @@ class CodeAttribute(Attribute): .. code-block:: python - from jawa import ClassFile - from jawa.util.bytecode import Instruction + from lawu import ClassFile + from lawu.util.bytecode import Instruction cf = ClassFile.create('HelloWorld') @@ -37,7 +37,7 @@ class CodeAttribute(Attribute): 'main', # The signature of the method '([Ljava/lang/String;)V', - # Tell Jawa to automatically create an empty CodeAttribute for + # Tell Lawu to automatically create an empty CodeAttribute for # us to use. code=True ) @@ -112,7 +112,7 @@ def pack(self): def assemble(self, code): """ - Assembles an iterable of :class:`~jawa.util.bytecode.Instruction` + Assembles an iterable of :class:`~lawu.util.bytecode.Instruction` objects into a method's code body. """ with io.BytesIO() as code_out: @@ -123,7 +123,7 @@ def assemble(self, code): def disassemble(self, *, transforms=None) -> Iterator[Instruction]: """ Disassembles this method, yielding an iterable of - :class:`~jawa.util.bytecode.Instruction` objects. + :class:`~lawu.util.bytecode.Instruction` objects. """ if transforms is None: if self.cf.classloader: diff --git a/jawa/attributes/constant_value.py b/lawu/attributes/constant_value.py similarity index 95% rename from jawa/attributes/constant_value.py rename to lawu/attributes/constant_value.py index e564f08..c4a7e43 100644 --- a/jawa/attributes/constant_value.py +++ b/lawu/attributes/constant_value.py @@ -1,5 +1,5 @@ from struct import pack -from jawa.attribute import Attribute +from lawu.attribute import Attribute class ConstantValueAttribute(Attribute): diff --git a/jawa/attributes/deprecated.py b/lawu/attributes/deprecated.py similarity index 92% rename from jawa/attributes/deprecated.py rename to lawu/attributes/deprecated.py index 29f8cbb..c02d724 100644 --- a/jawa/attributes/deprecated.py +++ b/lawu/attributes/deprecated.py @@ -1,4 +1,4 @@ -from jawa.attribute import Attribute +from lawu.attribute import Attribute class DeprecatedAttribute(Attribute): diff --git a/jawa/attributes/enclosing_method.py b/lawu/attributes/enclosing_method.py similarity index 94% rename from jawa/attributes/enclosing_method.py rename to lawu/attributes/enclosing_method.py index 0c989a5..0b20146 100644 --- a/jawa/attributes/enclosing_method.py +++ b/lawu/attributes/enclosing_method.py @@ -1,6 +1,6 @@ from struct import pack -from jawa.attribute import Attribute +from lawu.attribute import Attribute class EnclosingMethodAttribute(Attribute): diff --git a/jawa/attributes/exceptions.py b/lawu/attributes/exceptions.py similarity index 95% rename from jawa/attributes/exceptions.py rename to lawu/attributes/exceptions.py index 9b4be39..4225202 100644 --- a/jawa/attributes/exceptions.py +++ b/lawu/attributes/exceptions.py @@ -1,6 +1,6 @@ from struct import pack -from jawa.attribute import Attribute +from lawu.attribute import Attribute class ExceptionsAttribute(Attribute): diff --git a/jawa/attributes/inner_classes.py b/lawu/attributes/inner_classes.py similarity index 96% rename from jawa/attributes/inner_classes.py rename to lawu/attributes/inner_classes.py index b50089f..bdeb52b 100644 --- a/jawa/attributes/inner_classes.py +++ b/lawu/attributes/inner_classes.py @@ -2,7 +2,7 @@ from struct import pack from itertools import repeat from collections import namedtuple -from jawa.attribute import Attribute +from lawu.attribute import Attribute InnerClass = namedtuple('InnerClass', [ diff --git a/jawa/attributes/line_number_table.py b/lawu/attributes/line_number_table.py similarity index 96% rename from jawa/attributes/line_number_table.py rename to lawu/attributes/line_number_table.py index 60c722e..0099857 100644 --- a/jawa/attributes/line_number_table.py +++ b/lawu/attributes/line_number_table.py @@ -1,7 +1,7 @@ from struct import pack from collections import namedtuple -from jawa.attribute import Attribute +from lawu.attribute import Attribute line_number_entry = namedtuple('line_number_entry', 'start_pc line_number') diff --git a/jawa/attributes/local_variable.py b/lawu/attributes/local_variable.py similarity index 96% rename from jawa/attributes/local_variable.py rename to lawu/attributes/local_variable.py index a41b1da..99a80e9 100644 --- a/jawa/attributes/local_variable.py +++ b/lawu/attributes/local_variable.py @@ -1,7 +1,7 @@ from struct import pack from collections import namedtuple -from jawa.attribute import Attribute +from lawu.attribute import Attribute local_variable_entry = namedtuple('local_variable_entry', [ diff --git a/jawa/attributes/local_variable_type.py b/lawu/attributes/local_variable_type.py similarity index 96% rename from jawa/attributes/local_variable_type.py rename to lawu/attributes/local_variable_type.py index 6568b05..8ee6c69 100644 --- a/jawa/attributes/local_variable_type.py +++ b/lawu/attributes/local_variable_type.py @@ -1,7 +1,7 @@ from struct import pack from collections import namedtuple -from jawa.attribute import Attribute +from lawu.attribute import Attribute local_variable_type_entry = namedtuple('local_variable_type_entry', [ diff --git a/jawa/attributes/signature.py b/lawu/attributes/signature.py similarity index 95% rename from jawa/attributes/signature.py rename to lawu/attributes/signature.py index 4b05754..0ce6188 100644 --- a/jawa/attributes/signature.py +++ b/lawu/attributes/signature.py @@ -1,5 +1,5 @@ from struct import pack -from jawa.attribute import Attribute +from lawu.attribute import Attribute class SignatureAttribute(Attribute): diff --git a/jawa/attributes/source_file.py b/lawu/attributes/source_file.py similarity index 95% rename from jawa/attributes/source_file.py rename to lawu/attributes/source_file.py index 55d60f8..771634f 100644 --- a/jawa/attributes/source_file.py +++ b/lawu/attributes/source_file.py @@ -1,5 +1,5 @@ from struct import pack -from jawa.attribute import Attribute +from lawu.attribute import Attribute class SourceFileAttribute(Attribute): diff --git a/jawa/attributes/stack_map_table.py b/lawu/attributes/stack_map_table.py similarity index 98% rename from jawa/attributes/stack_map_table.py rename to lawu/attributes/stack_map_table.py index 411fa81..d07993a 100644 --- a/jawa/attributes/stack_map_table.py +++ b/lawu/attributes/stack_map_table.py @@ -1,7 +1,7 @@ from itertools import repeat -from jawa.attribute import Attribute -from jawa.util.verifier import VerificationTypes +from lawu.attribute import Attribute +from lawu.util.verifier import VerificationTypes # These types are followed by an additional u2. TYPES_WITH_EXTRA = ( diff --git a/jawa/attributes/synthetic.py b/lawu/attributes/synthetic.py similarity index 91% rename from jawa/attributes/synthetic.py rename to lawu/attributes/synthetic.py index 6cd2f6c..4e27d2c 100644 --- a/jawa/attributes/synthetic.py +++ b/lawu/attributes/synthetic.py @@ -1,4 +1,4 @@ -from jawa.attribute import Attribute +from lawu.attribute import Attribute class SyntheticAttribute(Attribute): diff --git a/jawa/cf.py b/lawu/cf.py similarity index 91% rename from jawa/cf.py rename to lawu/cf.py index 7cdb945..5f7aa22 100644 --- a/jawa/cf.py +++ b/lawu/cf.py @@ -1,19 +1,19 @@ """ ClassFile reader & writer. -The :mod:`jawa.cf` module provides tools for working with JVM ``.class`` +The :mod:`lawu.cf` module provides tools for working with JVM ``.class`` ClassFiles. """ from typing import IO, Iterable, Union, Sequence from struct import pack, unpack from collections import namedtuple -from jawa.constants import ConstantPool, ConstantClass -from jawa.fields import FieldTable -from jawa.methods import MethodTable -from jawa.attribute import AttributeTable, ATTRIBUTE_CLASSES -from jawa.util.flags import Flags -from jawa.attributes.bootstrap import BootstrapMethod +from lawu.constants import ConstantPool, ConstantClass +from lawu.fields import FieldTable +from lawu.methods import MethodTable +from lawu.attribute import AttributeTable, ATTRIBUTE_CLASSES +from lawu.util.flags import Flags +from lawu.attributes.bootstrap import BootstrapMethod class ClassVersion(namedtuple('ClassVersion', ['major', 'minor'])): @@ -172,7 +172,7 @@ def _from_io(self, source: IO): @property def version(self) -> ClassVersion: """ - The :class:`~jawa.cf.ClassVersion` for this class. + The :class:`~lawu.cf.ClassVersion` for this class. Example:: @@ -192,14 +192,14 @@ def version(self, major_minor: Union[ClassVersion, Sequence]): @property def constants(self) -> ConstantPool: """ - The :class:`~jawa.cp.ConstantPool` for this class. + The :class:`~lawu.cp.ConstantPool` for this class. """ return self._constants @property def this(self) -> ConstantClass: """ - The :class:`~jawa.constants.ConstantClass` which represents this class. + The :class:`~lawu.constants.ConstantClass` which represents this class. """ return self.constants.get(self._this) @@ -210,7 +210,7 @@ def this(self, value): @property def super_(self) -> ConstantClass: """ - The :class:`~jawa.constants.ConstantClass` which represents this + The :class:`~lawu.constants.ConstantClass` which represents this class's superclass. """ return self.constants.get(self._super) diff --git a/jawa/classloader.py b/lawu/classloader.py similarity index 98% rename from jawa/classloader.py rename to lawu/classloader.py index 04c556f..078f829 100644 --- a/jawa/classloader.py +++ b/lawu/classloader.py @@ -7,8 +7,8 @@ from collections import OrderedDict from contextlib import contextmanager -from jawa.cf import ClassFile -from jawa.constants import ConstantPool, ConstantClass +from lawu.cf import ClassFile +from lawu.constants import ConstantPool, ConstantClass def _walk(path, follow_links=False, maximum_depth=None): diff --git a/jawa/cli.py b/lawu/cli.py similarity index 93% rename from jawa/cli.py rename to lawu/cli.py index dda15cf..0912b5b 100644 --- a/jawa/cli.py +++ b/lawu/cli.py @@ -4,11 +4,11 @@ import click -from jawa.classloader import ClassLoader -from jawa.cf import ClassVersion, ClassFile -from jawa.attribute import get_attribute_classes -from jawa.util import bytecode, shell -from jawa.constants import UTF8 +from lawu.classloader import ClassLoader +from lawu.cf import ClassVersion, ClassFile +from lawu.attribute import get_attribute_classes +from lawu.util import bytecode, shell +from lawu.constants import UTF8 @click.group() @@ -91,7 +91,7 @@ def shell_command(class_path): shell.start_shell(local_ns={ 'ClassFile': ClassFile, 'loader': loader, - 'constants': importlib.import_module('jawa.constants'), + 'constants': importlib.import_module('lawu.constants'), }) @@ -100,7 +100,7 @@ def shell_command(class_path): def definition_to_json(source): """Convert a bytecode.yaml file into a prepared bytecode.json. - Jawa internally uses a YAML file to define all bytecode opcodes, operands, + Lawu internally uses a YAML file to define all bytecode opcodes, operands, runtime exceptions, default transforms, etc... However since JSON is available in the python stdlib and YAML is not, we diff --git a/jawa/constants.py b/lawu/constants.py similarity index 99% rename from jawa/constants.py rename to lawu/constants.py index 2887014..96ff48e 100644 --- a/jawa/constants.py +++ b/lawu/constants.py @@ -1,6 +1,6 @@ from struct import unpack, pack -from jawa.util.utf import decode_modified_utf8, encode_modified_utf8 +from lawu.util.utf import decode_modified_utf8, encode_modified_utf8 class Constant(object): diff --git a/jawa/fields.py b/lawu/fields.py similarity index 94% rename from jawa/fields.py rename to lawu/fields.py index d9d1289..48e9429 100644 --- a/jawa/fields.py +++ b/lawu/fields.py @@ -2,11 +2,11 @@ from struct import unpack, pack from itertools import repeat -from jawa.util.flags import Flags -from jawa.attribute import AttributeTable -from jawa.constants import Constant, UTF8 -from jawa.attributes.constant_value import ConstantValueAttribute -from jawa.util.descriptor import field_descriptor +from lawu.util.flags import Flags +from lawu.attribute import AttributeTable +from lawu.constants import Constant, UTF8 +from lawu.attributes.constant_value import ConstantValueAttribute +from lawu.util.descriptor import field_descriptor class Field(object): @@ -37,7 +37,7 @@ def descriptor(self) -> UTF8: @property def type(self): """ - A :class:`~jawa.util.descriptor.JVMType` representing the field's + A :class:`~lawu.util.descriptor.JVMType` representing the field's type. """ return field_descriptor(self.descriptor.value) @@ -111,13 +111,13 @@ def create(self, name: str, descriptor: str, value: Constant=None) -> Field: """ Creates a new field from `name` and `descriptor`. For example:: - >>> from jawa.cf import ClassFile + >>> from lawu.cf import ClassFile >>> cf = ClassFile.create('BeerCounter') >>> field = cf.fields.create('BeerCount', 'I') To automatically create a static field, pass a value:: - >>> from jawa.cf import ClassFile + >>> from lawu.cf import ClassFile >>> cf = ClassFile.create('BeerCounter') >>> field = cf.fields.create( ... 'MaxBeer', diff --git a/jawa/methods.py b/lawu/methods.py similarity index 95% rename from jawa/methods.py rename to lawu/methods.py index d53d2db..1be511c 100644 --- a/jawa/methods.py +++ b/lawu/methods.py @@ -2,11 +2,11 @@ from struct import unpack, pack from itertools import repeat -from jawa.constants import UTF8 -from jawa.util.flags import Flags -from jawa.util.descriptor import method_descriptor, JVMType -from jawa.attribute import AttributeTable -from jawa.attributes.code import CodeAttribute +from lawu.constants import UTF8 +from lawu.util.flags import Flags +from lawu.util.descriptor import method_descriptor, JVMType +from lawu.attribute import AttributeTable +from lawu.attributes.code import CodeAttribute class Method(object): @@ -47,7 +47,7 @@ def name(self) -> UTF8: @property def returns(self) -> JVMType: """ - A :class:`~jawa.util.descriptor.JVMType` representing the method's + A :class:`~lawu.util.descriptor.JVMType` representing the method's return type. """ return method_descriptor(self.descriptor.value).returns @@ -55,7 +55,7 @@ def returns(self) -> JVMType: @property def args(self) -> List[JVMType]: """ - A list of :class:`~jawa.util.descriptor.JVMType` representing the + A list of :class:`~lawu.util.descriptor.JVMType` representing the method's argument list. """ return method_descriptor(self.descriptor.value).args diff --git a/jawa/transforms.py b/lawu/transforms.py similarity index 92% rename from jawa/transforms.py rename to lawu/transforms.py index 8c39a74..e2e9306 100644 --- a/jawa/transforms.py +++ b/lawu/transforms.py @@ -1,9 +1,9 @@ """ Transforms are simple Instruction modifiers that can be called on each -Instruction by the :func:`~jawa.attributes.code.CodeAttribute.disassemble` +Instruction by the :func:`~lawu.attributes.code.CodeAttribute.disassemble` function. """ -from jawa.util.bytecode import Instruction, Operand, OperandTypes, opcode_table +from lawu.util.bytecode import Instruction, Operand, OperandTypes, opcode_table def expand_constants(ins: Instruction, *, cf) -> Instruction: diff --git a/jawa/util/__init__.py b/lawu/util/__init__.py similarity index 91% rename from jawa/util/__init__.py rename to lawu/util/__init__.py index 109e049..02fd521 100644 --- a/jawa/util/__init__.py +++ b/lawu/util/__init__.py @@ -1,4 +1,4 @@ """ -Standalone utility modules that don't necessarily require the rest of Jawa to +Standalone utility modules that don't necessarily require the rest of Lawu to function. Generally they can be copy-pasted into other projects for reuse. """ \ No newline at end of file diff --git a/jawa/util/bytecode.json b/lawu/util/bytecode.json similarity index 100% rename from jawa/util/bytecode.json rename to lawu/util/bytecode.json diff --git a/jawa/util/bytecode.py b/lawu/util/bytecode.py similarity index 99% rename from jawa/util/bytecode.py rename to lawu/util/bytecode.py index f7989be..86f57af 100644 --- a/jawa/util/bytecode.py +++ b/lawu/util/bytecode.py @@ -272,7 +272,7 @@ def load_bytecode_definitions(*, path=None) -> dict: j = json.load(file_in) else: try: - j = json.loads(pkgutil.get_data('jawa.util', 'bytecode.json')) + j = json.loads(pkgutil.get_data('lawu.util', 'bytecode.json')) except json.JSONDecodeError: # Unfortunately our best way to handle missing/malformed/empty # bytecode.json files since it may not actually be backed by a diff --git a/jawa/util/bytecode.yaml b/lawu/util/bytecode.yaml similarity index 100% rename from jawa/util/bytecode.yaml rename to lawu/util/bytecode.yaml diff --git a/jawa/util/descriptor.py b/lawu/util/descriptor.py similarity index 94% rename from jawa/util/descriptor.py rename to lawu/util/descriptor.py index 0db372a..04d8028 100644 --- a/jawa/util/descriptor.py +++ b/lawu/util/descriptor.py @@ -64,8 +64,8 @@ def field_descriptor(descriptor: str) -> str: def parse_descriptor(descriptor: str) -> list: """ Uses a tiny state machine to parse JVM descriptors. To get useful wrappers - around the results, use :py:func:`jawa.core.descriptor.method_descriptor` - or :py:func:`jawa.core.descriptor.field_descriptor`. + around the results, use :py:func:`lawu.core.descriptor.method_descriptor` + or :py:func:`lawu.core.descriptor.field_descriptor`. """ # States: # 10 == NORMAL, diff --git a/jawa/util/flags.py b/lawu/util/flags.py similarity index 100% rename from jawa/util/flags.py rename to lawu/util/flags.py diff --git a/jawa/util/shell.py b/lawu/util/shell.py similarity index 100% rename from jawa/util/shell.py rename to lawu/util/shell.py diff --git a/jawa/util/stream.py b/lawu/util/stream.py similarity index 100% rename from jawa/util/stream.py rename to lawu/util/stream.py diff --git a/jawa/util/tracer.py b/lawu/util/tracer.py similarity index 100% rename from jawa/util/tracer.py rename to lawu/util/tracer.py diff --git a/jawa/util/utf.py b/lawu/util/utf.py similarity index 100% rename from jawa/util/utf.py rename to lawu/util/utf.py diff --git a/jawa/util/verifier.py b/lawu/util/verifier.py similarity index 100% rename from jawa/util/verifier.py rename to lawu/util/verifier.py diff --git a/lawu_small.png b/lawu_small.png new file mode 100644 index 0000000000000000000000000000000000000000..6004c669f80db3c31f2a3dfa625c7ac57c0e9547 GIT binary patch literal 22376 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 literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py index 3fd21a0..6659d08 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,16 @@ - from setuptools import setup, find_packages setup( - name='jawa', + name='lawu', packages=find_packages(), - version='2.2.0', - python_requires='>=3.6', + version='3.0.0', + python_requires='>=3.7', description='Doing fun stuff with JVM ClassFiles.', long_description=open('README.md', 'r').read(), long_description_content_type='text/markdown', author='Tyler Kennedy', author_email='tk@tkte.ch', - url='http://github.com/TkTech/Jawa', + url='https://lawu.dev', keywords=[ 'java', 'disassembly', @@ -25,32 +24,35 @@ 'Operating System :: OS Independent', 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', - 'Topic :: Software Development :: Disassemblers', - 'Topic :: Software Development :: Assemblers' - ], - install_requires=[ - 'click>=5.0' + 'Topic :: Software Development :: Disassemblers' ], tests_require=[ 'pytest>=2.10', + 'pytest-cov' + ], + install_requires=[ + 'mutf8' ], extras_require={ 'dev': [ 'pytest', + 'pytest-cov', 'sphinx', 'sphinxcontrib-googleanalytics', - 'sphinx_rtd_theme', 'sphinx-click', + 'furo', 'ghp-import', 'pyyaml', - 'ipython', 'twine', - 'wheel', - 'bumpversion' + 'wheel' + ], + 'cli': [ + 'click', + 'rich' ] }, entry_points=''' [console_scripts] - jawa=jawa.cli:cli + lawu=lawu.cli:cli ''' ) diff --git a/tests/attributes/test_general_attributes.py b/tests/attributes/test_general_attributes.py index 47fbc5c..18f48a6 100644 --- a/tests/attributes/test_general_attributes.py +++ b/tests/attributes/test_general_attributes.py @@ -1,4 +1,4 @@ -from jawa.attribute import get_attribute_classes +from lawu.attribute import get_attribute_classes def test_mandatory_attributes(): diff --git a/tests/attributes/test_inner_classes.py b/tests/attributes/test_inner_classes.py index 4e4ec4e..309d896 100644 --- a/tests/attributes/test_inner_classes.py +++ b/tests/attributes/test_inner_classes.py @@ -1,4 +1,4 @@ -from jawa.attributes.inner_classes import InnerClass +from lawu.attributes.inner_classes import InnerClass def test_inner_classes_read(loader): diff --git a/tests/attributes/test_sourcefile_attribute.py b/tests/attributes/test_sourcefile_attribute.py index 4c5f35e..2b19107 100644 --- a/tests/attributes/test_sourcefile_attribute.py +++ b/tests/attributes/test_sourcefile_attribute.py @@ -1,7 +1,7 @@ from io import BytesIO -from jawa.cf import ClassFile -from jawa.attributes.source_file import SourceFileAttribute +from lawu.cf import ClassFile +from lawu.attributes.source_file import SourceFileAttribute def test_sourcefile_read(loader): diff --git a/tests/attributes/test_stack_map_attribute.py b/tests/attributes/test_stack_map_attribute.py index 069b9e6..0fd5929 100644 --- a/tests/attributes/test_stack_map_attribute.py +++ b/tests/attributes/test_stack_map_attribute.py @@ -1,4 +1,4 @@ -from jawa.util.verifier import VerificationTypes +from lawu.util.verifier import VerificationTypes def test_stack_map_table_read(loader): diff --git a/tests/conftest.py b/tests/conftest.py index bb965e8..9c595d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pytest -from jawa.classloader import ClassLoader +from lawu.classloader import ClassLoader @pytest.fixture(scope='session') diff --git a/tests/test_bytecode.py b/tests/test_bytecode.py index f7fb224..c05afbd 100644 --- a/tests/test_bytecode.py +++ b/tests/test_bytecode.py @@ -1,4 +1,4 @@ -from jawa.util.bytecode import Instruction, Operand, OperandTypes +from lawu.util.bytecode import Instruction, Operand, OperandTypes GOOD_TABLE_SWITCH = [ diff --git a/tests/test_classloader.py b/tests/test_classloader.py index 2799e4b..2b419d8 100644 --- a/tests/test_classloader.py +++ b/tests/test_classloader.py @@ -3,10 +3,10 @@ import tempfile import zipfile -from jawa.cf import ClassFile -from jawa.classloader import ClassLoader -from jawa.transforms import simple_swap -from jawa.assemble import assemble +from lawu.cf import ClassFile +from lawu.classloader import ClassLoader +from lawu.transforms import simple_swap +from lawu.assemble import assemble def test_load_from_class(): diff --git a/tests/test_expand_constants.py b/tests/test_expand_constants.py index 5d7c238..ac60e50 100644 --- a/tests/test_expand_constants.py +++ b/tests/test_expand_constants.py @@ -1,5 +1,5 @@ -from jawa.constants import FieldReference -from jawa.transforms import expand_constants +from lawu.constants import FieldReference +from lawu.transforms import expand_constants def test_expand_constants(loader): diff --git a/tests/test_hello_world.py b/tests/test_hello_world.py index d59d59d..a500ef0 100644 --- a/tests/test_hello_world.py +++ b/tests/test_hello_world.py @@ -1,4 +1,4 @@ -from jawa.util.bytecode import Instruction, Operand +from lawu.util.bytecode import Instruction, Operand def test_hello_world(loader): diff --git a/tests/test_modified_utf8.py b/tests/test_modified_utf8.py index b8fc07c..db91a10 100644 --- a/tests/test_modified_utf8.py +++ b/tests/test_modified_utf8.py @@ -1,4 +1,4 @@ -from jawa.util.utf import encode_modified_utf8, decode_modified_utf8 +from lawu.util.utf import encode_modified_utf8, decode_modified_utf8 def test_decode_modified_utf8(loader): diff --git a/tests/test_printable.py b/tests/test_printable.py index e0f58bb..77feb69 100644 --- a/tests/test_printable.py +++ b/tests/test_printable.py @@ -1,5 +1,5 @@ -from jawa.cf import ClassFile -from jawa.constants import ConstantPool +from lawu.cf import ClassFile +from lawu.constants import ConstantPool def test_printable_constants(): From 654459f40bc96f0334a641e3aa2e8581d5649cf6 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Sun, 16 Jan 2022 04:49:23 -0500 Subject: [PATCH 2/9] Switch to mutf8 library for UTF8 constants. Switch to Python's built-in enum.IntFlag, which didn't exist when we wrote this the first time. Start scrubbing some files for rewrite. --- MANIFEST.in | 2 - bytecode.yaml | 1690 ----------------------------------- lawu/__init__.py | 2 + lawu/cf.py | 45 +- lawu/cli.py | 168 ---- lawu/constants.py | 2 +- lawu/fields.py | 37 +- lawu/methods.py | 48 +- lawu/util/flags.py | 62 -- lawu/util/tracer.py | 0 lawu/util/utf.py | 80 -- setup.cfg | 2 - tests/test_modified_utf8.py | 47 - 13 files changed, 71 insertions(+), 2114 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 bytecode.yaml delete mode 100644 lawu/cli.py delete mode 100644 lawu/util/flags.py delete mode 100644 lawu/util/tracer.py delete mode 100644 lawu/util/utf.py delete mode 100644 setup.cfg delete mode 100644 tests/test_modified_utf8.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c45737d..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include lawu/util/bytecode.json -include lawu/util/bytecode.yaml \ No newline at end of file diff --git a/bytecode.yaml b/bytecode.yaml deleted file mode 100644 index 169cbbd..0000000 --- a/bytecode.yaml +++ /dev/null @@ -1,1690 +0,0 @@ -# This file provides a description of JVM instructions in a reasonably-portable -# manner. A few instructions (mainly lookupswitch and tableswitch) cannot -# be resonably described and require special casing in parsers. -# It's intent is to enable collaboration and reuse with other JVM tooling. -aaload: - op: 0x32 - desc: load onto the stack a reference from an array - stack: - before: - - ArrayRef - - Index - after: - - Value - runtime: - - NullPointerException - - ArrayIndexOutOfBoundsException -aastore: - op: 0x53 - desc: store into a reference in an array - stack: - before: - - ArrayRef - - Index - - Value - runtime: - - NullPointerException - - ArrayIndexOutOfBoundsException - - ArrayStoreException -aconst_null: - op: 0x01 - stack: - after: - - NullReference -aload: - op: 0x19 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - stack: - after: - - ObjectRef - can_be_wide: True -aload_0: - op: 0x2A - stack: - after: - - ObjectRef - transform: - simple_swap: - op: aload - operands: - - 0 -aload_1: - op: 0x2B - stack: - after: - - ObjectRef - transform: - simple_swap: - op: aload - operands: - - 1 -aload_2: - op: 0x2C - stack: - after: - - ObjectRef - transform: - simple_swap: - op: aload - operands: - - 2 -aload_3: - op: 0x2D - stack: - after: - - ObjectRef - transform: - simple_swap: - op: aload - operands: - - 3 -anewarray: - op: 0xBD - operands: - - ['USHORT', 'CONSTANT_INDEX'] - stack: - before: - - Value - after: - - ArrayRef -areturn: - op: 0xB0 - stack: - before: - - ObjectRef - causes_return: true -arraylength: - op: 0xBE - stack: - before: - - ArrayRef - after: - - Value -astore: - op: 0x3A - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - before: - - ObjectRef -astore_0: - op: 0x4B - transform: - simple_swap: - op: astore - operands: - - 0 - stack: - before: - - ObjectRef -astore_1: - op: 0x4C - transform: - simple_swap: - op: astore - operands: - - 1 - stack: - before: - - ObjectRef -astore_2: - op: 0x4D - transform: - simple_swap: - op: astore - operands: - - 2 - stack: - before: - - ObjectRef -astore_3: - op: 0x4E - transform: - simple_swap: - op: astore - operands: - - 3 - stack: - before: - - ObjectRef -athrow: - op: 0xBF - stack: - before: - - ObjectRef -baload: - op: 0x33 - stack: - before: - - ArrayRef - - Index - after: - - Value -bastore: - op: 0x54 - stack: - before: - - ArrayRef - - Index - - Value -bipush: - op: 0x10 - operands: - - ['BYTE', 'LITERAL'] - stack: - after: - - Value -caload: - op: 0x34 - stack: - before: - - ArrayRef - - Index - after: - - Value -castore: - op: 0x55 - stack: - before: - - ArrayRef - - Index - - Value -checkcast: - op: 0xC0 - operands: - - ['USHORT', 'CONSTANT_INDEX'] - stack: - before: - - ObjectRef - after: - - ObjectRef -d2f: - op: 0x90 - stack: - before: - - Double - - Double - after: - - Float -d2i: - op: 0x8E - stack: - before: - - Double - - Double - after: - - Integer -d2l: - op: 0x8F - stack: - before: - - Double - - Double - after: - - Long - - Long -dadd: - op: 0x63 - stack: - before: - - Double - - Double - - Double - - Double - after: - - Double - - Double -daload: - op: 0x31 - stack: - before: - - ArrayRef - - Index - after: - - Double - - Double -dastore: - op: 0x52 - stack: - before: - - ArrayRef - - Index - - Double - - Double -dcmpg: - op: 0x98 - stack: - before: - - Double - - Double - - Double - - Double - after: - - Integer -dcmpl: - op: 0x97 - stack: - before: - - Double - - Double - - Double - - Double - after: - - Integer -dconst_0: - op: 0x0E - stack: - after: - - Double - - Double -dconst_1: - op: 0x0F - stack: - after: - - Double - - Double -ddiv: - op: 0x6F - stack: - before: - - Double - - Double - - Double - - Double - after: - - Double - - Double -dload: - op: 0x18 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - after: - - Double - - Double -dload_0: - op: 0x26 - transform: - simple_swap: - op: 0x18 - operands: - - 0 - stack: - after: - - Double - - Double -dload_1: - op: 0x27 - transform: - simple_swap: - op: 0x18 - operands: - - 1 - stack: - after: - - Double - - Double -dload_2: - op: 0x28 - transform: - simple_swap: - op: 0x18 - operands: - - 2 - stack: - after: - - Double - - Double -dload_3: - op: 0x29 - transform: - simple_swap: - op: 0x18 - operands: - - 3 - stack: - after: - - Double - - Double -dmul: - op: 0x6B - stack: - before: - - Double - - Double - - Double - - Double - after: - - Double - - Double -dneg: - op: 0x77 - stack: - before: - - Double - - Double - after: - - Double - - Double -drem: - op: 0x73 - stack: - before: - - Double - - Double - - Double - - Double - after: - - Double - - Double -dreturn: - op: 0xAF - stack: - before: - - Double - - Double - causes_return: true -dstore: - op: 0x39 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - before: - - Double - - Double -dstore_0: - op: 0x47 - transform: - simple_swap: - op: dstore - operands: - - 0 - stack: - before: - - Double - - Double -dstore_1: - op: 0x48 - transform: - simple_swap: - op: dstore - operands: - - 1 - stack: - before: - - Double - - Double -dstore_2: - op: 0x49 - transform: - simple_swap: - op: dstore - operands: - - 2 - stack: - before: - - Double - - Double -dstore_3: - op: 0x4A - transform: - simple_swap: - op: dstore - operands: - - 3 - stack: - before: - - Double - - Double -dsub: - op: 0x67 - stack: - before: - - Double - - Double - - Double - - Double - after: - - Double - - Double -dup: - op: 0x59 - stack: - before: - - Any - after: - - Any - - Any -dup_x1: - op: 0x5A - stack: - before: - - Any - - Any - after: - - Any - - Any - - Any -dup_x2: - op: 0x5B - stack: - before: - - Any - - Any - - Any - after: - - Any - - Any - - Any - - Any -dup2: - op: 0x5C - stack: - before: - - Any - - Any - after: - - Any - - Any - - Any - - Any -dup2_x1: - op: 0x5D - stack: - before: - - Any - - Any - - Any - after: - - Any - - Any - - Any - - Any - - Any -dup2_x2: - op: 0x5E - stack: - before: - - Any - - Any - - Any - - Any - after: - - Any - - Any - - Any - - Any - - Any - - Any -f2d: - op: 0x8D - stack: - before: - - Float - after: - - Double - - Double -f2i: - op: 0x8B - stack: - before: - - Float - after: - - Integer -f2l: - op: 0x8C - stack: - before: - - Float - after: - - Long - - Long -fadd: - op: 0x62 - stack: - before: - - Float - - Float - after: - - Float -faload: - op: 0x30 - stack: - before: - - ArrayRef - - Index - after: - - Float -fastore: - op: 0x51 - stack: - before: - - ArrayRef - - Index - - Float -fcmpg: - op: 0x96 - stack: - before: - - Float - - Float - after: - - Integer -fcmpl: - op: 0x95 - stack: - before: - - Float - - Float - after: - - Integer -fconst_0: - op: 0x0B - stack: - after: - - Float -fconst_1: - op: 0x0C - stack: - after: - - Float -fconst_2: - op: 0x0D - stack: - after: - - Float -fdiv: - op: 0x6E - stack: - before: - - Float - - Float - after: - - Float -fload: - op: 0x17 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - after: - - Float -fload_0: - op: 0x22 - transform: - simple_swap: - op: fload - operands: - - 0 - stack: - after: - - Float -fload_1: - op: 0x23 - transform: - simple_swap: - op: fload - operands: - - 1 - stack: - after: - - Float -fload_2: - op: 0x24 - transform: - simple_swap: - op: fload - operands: - - 2 - stack: - after: - - Float -fload_3: - op: 0x25 - transform: - simple_swap: - op: fload - operands: - - 3 - stack: - after: - - Float -fmul: - op: 0x6A - stack: - before: - - Float - - Float - after: - - Float -fneg: - op: 0x76 - stack: - before: - - Float - after: - - Float -frem: - op: 0x72 - stack: - before: - - Float - - Float - after: - - Float -freturn: - op: 0xAE - stack: - before: - - Float - causes_return: true -fstore: - op: 0x38 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - before: - - Float -fstore_0: - op: 0x43 - transform: - simple_swap: - op: fstore - operands: - - 0 - stack: - before: - - Float -fstore_1: - op: 0x44 - transform: - simple_swap: - op: fstore - operands: - - 1 - stack: - before: - - Float -fstore_2: - op: 0x45 - transform: - simple_swap: - op: fstore - operands: - - 2 - stack: - before: - - Float -fstore_3: - op: 0x46 - transform: - simple_swap: - op: fstore - operands: - - 3 - stack: - before: - - Float -fsub: - op: 0x66 - stack: - before: - - Float - - Float - after: - - Float -getfield: - op: 0xB4 - operands: - - ['USHORT', 'CONSTANT_INDEX'] -getstatic: - op: 0xB2 - operands: - - ['USHORT', 'CONSTANT_INDEX'] -goto: - op: 0xA7 - operands: - - ['SHORT', 'BRANCH'] -goto_w: - op: 0xC8 - operands: - - ['INTEGER', 'BRANCH'] -i2b: - op: 0x91 - stack: - before: - - Integer - after: - - Integer -i2c: - op: 0x92 - stack: - before: - - Integer - after: - - Integer -i2d: - op: 0x87 - stack: - before: - - Integer - after: - - Double - - Double -i2f: - op: 0x86 - stack: - before: - - Integer - after: - - Float -i2l: - op: 0x85 - stack: - before: - - Integer - after: - - Long - - Long -i2s: - op: 0x93 - stack: - before: - - Integer - after: - - Integer -iadd: - op: 0x60 - stack: - before: - - Integer - - Integer - after: - - Integer -iaload: - op: 0x2E - stack: - before: - - ArrayRef - - Index - after: - - Integer -iand: - op: 0x7E - stack: - before: - - Integer - - Integer - after: - - Integer -iastore: - op: 0x4F - stack: - before: - - ArrayRef - - Index - - Integer -iconst_m1: - op: 0x02 - transform: - simple_swap: - op: bipush - operands: - - -1 - stack: - after: - - Integer -iconst_0: - op: 0x03 - transform: - simple_swap: - op: bipush - operands: - - 0 - stack: - after: - - Integer -iconst_1: - op: 0x04 - transform: - simple_swap: - op: bipush - operands: - - 1 - stack: - after: - - Integer -iconst_2: - op: 0x05 - transform: - simple_swap: - op: bipush - operands: - - 2 - stack: - after: - - Integer -iconst_3: - op: 0x06 - transform: - simple_swap: - op: bipush - operands: - - 3 - stack: - after: - - Integer -iconst_4: - op: 0x07 - transform: - simple_swap: - op: bipush - operands: - - 4 - stack: - after: - - Integer -iconst_5: - op: 0x08 - transform: - simple_swap: - op: bipush - operands: - - 5 - stack: - after: - - Integer -idiv: - op: 0x6C - stack: - before: - - Integer - - Integer - after: - - Integer -if_acmpeq: - op: 0xA5 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - ObjectRef - - ObjectRef -if_acmpne: - op: 0xA6 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - ObjectRef - - ObjectRef -if_icmpeq: - op: 0x9F - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer - - Integer -if_icmpne: - op: 0xA0 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer - - Integer -if_icmplt: - op: 0xA1 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer - - Integer -if_icmpge: - op: 0xA2 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer - - Integer -if_icmpgt: - op: 0xA3 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer - - Integer -if_icmple: - op: 0xA4 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer - - Integer -ifeq: - op: 0x99 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer -ifne: - op: 0x9A - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer -iflt: - op: 0x9B - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer -ifge: - op: 0x9C - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer -ifgt: - op: 0x9D - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer -ifle: - op: 0x9E - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - Integer -ifnonnull: - op: 0xC7 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - ObjectRef -ifnull: - op: 0xC6 - operands: - - ['SHORT', 'BRANCH'] - stack: - before: - - ObjectRef -iinc: - op: 0x84 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - - ['UBYTE', 'LITERAL'] - can_be_wide: True -iload: - op: 0x15 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - after: - - Integer -iload_0: - op: 0x1A - transform: - simple_swap: - op: iload - operands: - - 0 - stack: - after: - - Integer -iload_1: - op: 0x1B - transform: - simple_swap: - op: iload - operands: - - 1 - stack: - after: - - Integer -iload_2: - op: 0x1C - transform: - simple_swap: - op: iload - operands: - - 2 - stack: - after: - - Integer -iload_3: - op: 0x1D - transform: - simple_swap: - op: iload - operands: - - 3 - stack: - after: - - Integer -imul: - op: 0x68 - stack: - before: - - Integer - - Integer - after: - - Integer -ineg: - op: 0x74 - stack: - before: - - Integer - after: - - Integer -instanceof: - op: 0xC1 - operands: - - ['USHORT', 'CONSTANT_INDEX'] - stack: - before: - - ObjectRef - after: - - Integer -invokedynamic: - op: 0xBA - operands: - - ['USHORT', 'CONSTANT_INDEX'] - - ['UBYTE', 'PADDING'] - - ['UBYTE', 'PADDING'] -invokeinterface: - op: 0xB9 - operands: - - ['USHORT', 'CONSTANT_INDEX'] - - ['UBYTE', 'LITERAL'] - - ['UBYTE', 'PADDING'] -invokespecial: - op: 0xB7 - operands: - - ['USHORT', 'CONSTANT_INDEX'] -invokestatic: - op: 0xB8 - operands: - - ['USHORT', 'CONSTANT_INDEX'] -invokevirtual: - op: 0xB6 - operands: - - ['USHORT', 'CONSTANT_INDEX'] -ior: - op: 0x80 - stack: - before: - - Integer - - Integer - after: - - Integer -irem: - op: 0x70 - stack: - before: - - Integer - - Integer - after: - - Integer -ireturn: - op: 0xAC - stack: - before: - - Integer - causes_return: true -ishl: - op: 0x78 - stack: - before: - - Integer - - Integer - after: - - Integer -ishr: - op: 0x7A - stack: - before: - - Integer - - Integer - after: - - Integer -istore: - op: 0x36 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - before: - - Integer -istore_0: - op: 0x3B - transform: - simple_swap: - op: istore - operands: - - 0 - stack: - before: - - Integer -istore_1: - op: 0x3C - transform: - simple_swap: - op: istore - operands: - - 1 - stack: - before: - - Integer -istore_2: - op: 0x3D - transform: - simple_swap: - op: istore - operands: - - 2 - stack: - before: - - Integer -istore_3: - op: 0x3E - transform: - simple_swap: - op: istore - operands: - - 3 - stack: - before: - - Integer -isub: - op: 0x64 - stack: - before: - - Integer - - Integer - after: - - Integer -iushr: - op: 0x7C - stack: - before: - - Integer - - Integer - after: - - Integer -ixor: - op: 0x82 - stack: - before: - - Integer - - Integer - after: - - Integer -jsr: - op: 0xA8 - operands: - - ['SHORT', 'BRANCH'] - stack: - after: - - PC -jsr_w: - op: 0xC9 - operands: - - ['INTEGER', 'BRANCH'] - stack: - after: - - PC -l2d: - op: 0x8A - stack: - before: - - Long - - Long - after: - - Double - - Double -l2f: - op: 0x89 - stack: - before: - - Long - - Long - after: - - Float -l2i: - op: 0x88 - stack: - before: - - Long - - Long - after: - - Integer -ladd: - op: 0x61 - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -laload: - op: 0x2F - stack: - before: - - ArrayRef - - Index - after: - - Long - - Long -land: - op: 0x7F - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -lastore: - op: 0x50 - stack: - before: - - ArrayRef - - Index - - Long - - Long -lcmp: - op: 0x94 - stack: - before: - - Long - - Long - - Long - - Long - after: - - Integer -lconst_0: - op: 0x09 - stack: - after: - - Long - - Long -lconst_1: - op: 0x0A - stack: - after: - - Long - - Long -ldc: - op: 0x12 - operands: - - ['UBYTE', 'CONSTANT_INDEX'] - stack: - after: - - Any -ldc_w: - op: 0x13 - operands: - - ['USHORT', 'CONSTANT_INDEX'] - stack: - after: - - Any -ldc2_w: - op: 0x14 - operands: - - ['USHORT', 'CONSTANT_INDEX'] - stack: - after: - - Any - - Any -ldiv: - op: 0x6D - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -lload: - op: 0x16 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - after: - - Long - - Long -lload_0: - op: 0x1E - transform: - simple_swap: - op: lload - operands: - - 0 - stack: - after: - - Long - - Long -lload_1: - op: 0x1F - transform: - simple_swap: - op: lload - operands: - - 1 - stack: - after: - - Long - - Long -lload_2: - op: 0x20 - transform: - simple_swap: - op: lload - operands: - - 2 - stack: - after: - - Long - - Long -lload_3: - op: 0x21 - transform: - simple_swap: - op: lload - operands: - - 3 - stack: - after: - - Long - - Long -lmul: - op: 0x69 - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -lneg: - op: 0x75 - stack: - before: - - Long - - Long - after: - - Long - - Long -lookupswitch: - op: 0xAB - stack: - before: - - Value -lor: - op: 0x81 - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -lrem: - op: 0x71 - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -lreturn: - op: 0xAD - stack: - before: - - Long - - Long - causes_return: true -lshl: - op: 0x79 - stack: - before: - - Long - - Long - - Integer - after: - - Long - - Long -lshr: - op: 0x7B - stack: - before: - - Long - - Long - - Integer - after: - - Long - - Long -lstore: - op: 0x37 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True - stack: - before: - - Long - - Long -lstore_0: - op: 0x3F - transform: - simple_swap: - op: lstore - operands: - - 0 - stack: - before: - - Long - - Long -lstore_1: - op: 0x40 - transform: - simple_swap: - op: lstore - operands: - - 1 - stack: - before: - - Long - - Long -lstore_2: - op: 0x41 - transform: - simple_swap: - op: lstore - operands: - - 2 - stack: - before: - - Long - - Long -lstore_3: - op: 0x42 - transform: - simple_swap: - op: lstore - operands: - - 3 - stack: - before: - - Long - - Long -lsub: - op: 0x65 - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -lushr: - op: 0x7D - stack: - before: - - Long - - Long - - Integer - after: - - Long - - Long -lxor: - op: 0x83 - stack: - before: - - Long - - Long - - Long - - Long - after: - - Long - - Long -monitorenter: - op: 0xC2 - stack: - before: - - ObjectRef -monitorexit: - op: 0xC3 - stack: - before: - - ObjectRef -multianewarray: - op: 0xC5 - operands: - - ['USHORT', 'CONSTANT_INDEX'] - - ['UBYTE', 'LITERAL'] -new: - op: 0xBB - operands: - - ['USHORT', 'CONSTANT_INDEX'] - stack: - after: - - ObjectRef -newarray: - op: 0xBC - operands: - - ['UBYTE', 'LITERAL'] - stack: - before: - - Integer - after: - - ArrayRef -nop: - op: 0x00 -pop: - op: 0x57 - stack: - before: - - Any -pop2: - op: 0x58 - stack: - before: - - Any - - Any -putfield: - op: 0xB5 - operands: - - ['USHORT', 'CONSTANT_INDEX'] -putstatic: - op: 0xB3 - operands: - - ['USHORT', 'CONSTANT_INDEX'] -ret: - op: 0xA9 - operands: - - ['UBYTE', 'LOCAL_INDEX'] - can_be_wide: True -return: - op: 0xB1 - causes_return: true -saload: - op: 0x35 - stack: - before: - - ArrayRef - - Index - after: - - Integer -sastore: - op: 0x56 - stack: - before: - - ArrayRef - - Index - - Integer -sipush: - op: 0x11 - operands: - - ['SHORT', 'LITERAL'] - stack: - after: - - Integer -swap: - op: 0x5F - stack: - before: - - Any - - Any - after: - - Any - - Any -tableswitch: - op: 0xAA - stack: - before: - - Any -wide: - op: 0xC4 -breakpoint: - op: 0xCA -impdep1: - op: 0xFE -impdep2: - op: 0xFF diff --git a/lawu/__init__.py b/lawu/__init__.py index 40a96af..13f0358 100644 --- a/lawu/__init__.py +++ b/lawu/__init__.py @@ -1 +1,3 @@ # -*- coding: utf-8 -*- +def instructions(): + return diff --git a/lawu/cf.py b/lawu/cf.py index 5f7aa22..9f32b23 100644 --- a/lawu/cf.py +++ b/lawu/cf.py @@ -4,15 +4,15 @@ The :mod:`lawu.cf` module provides tools for working with JVM ``.class`` ClassFiles. """ -from typing import IO, Iterable, Union, Sequence +from typing import IO, Iterable, Union, Sequence, Optional from struct import pack, unpack from collections import namedtuple +from enum import IntFlag from lawu.constants import ConstantPool, ConstantClass from lawu.fields import FieldTable from lawu.methods import MethodTable from lawu.attribute import AttributeTable, ATTRIBUTE_CLASSES -from lawu.util.flags import Flags from lawu.attributes.bootstrap import BootstrapMethod @@ -66,21 +66,23 @@ class ClassFile(object): #: The JVM ClassFile magic number. MAGIC = 0xCAFEBABE - - def __init__(self, source: IO=None): + + class AccessFlags(IntFlag): + PUBLIC = 0x0001 + FINAL = 0x0010 + SUPER = 0x0020 + INTERFACE = 0x0200 + ABSTRACT = 0x0400 + SYNTHETIC = 0x1000 + ANNOTATION = 0x2000 + ENUM = 0x4000 + MODULE = 0x8000 + + def __init__(self, source: Optional[IO] = None): # Default to J2SE_7 self._version = ClassVersion(0x32, 0) self._constants = ConstantPool() - self.access_flags = Flags('>H', { - 'acc_public': 0x0001, - 'acc_final': 0x0010, - 'acc_super': 0x0020, - 'acc_interface': 0x0200, - 'acc_abstract': 0x0400, - 'acc_synthetic': 0x1000, - 'acc_annotation': 0x2000, - 'acc_enum': 0x4000 - }) + self.access_flags = ClassFile.AccessFlags(0) self._this = 0 self._super = 0 self._interfaces = [] @@ -94,7 +96,7 @@ def __init__(self, source: IO=None): self._from_io(source) @classmethod - def create(cls, this: str, super_: str=u'java/lang/Object') -> 'ClassFile': + def create(cls, this: str, super_: str = u'java/lang/Object') -> 'ClassFile': """ A utility which sets up reasonable defaults for a new public class. @@ -102,8 +104,9 @@ def create(cls, this: str, super_: str=u'java/lang/Object') -> 'ClassFile': :param super_: The name of this class's superclass. """ cf = ClassFile() - cf.access_flags.acc_public = True - cf.access_flags.acc_super = True + + cf.access_flags.PUBLIC = True + cf.access_flags.SUPER = True cf.this = cf.constants.create_class(this) cf.super_ = cf.constants.create_class(super_) @@ -127,7 +130,7 @@ def save(self, source: IO): self._constants.pack(source) - write(self.access_flags.pack()) + write(pack('>H', int(self.access_flags))) write(pack( f'>HHH{len(self._interfaces)}H', self._this, @@ -155,7 +158,7 @@ def _from_io(self, source: IO): self._constants.unpack(source) # ClassFile access_flags, see section #4.1 of the JVM specs. - self.access_flags.unpack(read(2)) + self.access_flags = unpack('>H', read(2)) # The CONSTANT_Class indexes for "this" class and its superclass. # Interfaces are a simple list of CONSTANT_Class indexes. @@ -230,8 +233,8 @@ def interfaces(self) -> Iterable[ConstantClass]: @property def bootstrap_methods(self) -> BootstrapMethod: """ - Returns the bootstrap methods table from the BootstrapMethods attribute, - if one exists. If it does not, one will be created. + Returns the bootstrap methods' table from the BootstrapMethods + attribute, if one exists. If it does not, one will be created. :returns: Table of `BootstrapMethod` objects. """ diff --git a/lawu/cli.py b/lawu/cli.py deleted file mode 100644 index 0912b5b..0000000 --- a/lawu/cli.py +++ /dev/null @@ -1,168 +0,0 @@ -import re -import json -import importlib - -import click - -from lawu.classloader import ClassLoader -from lawu.cf import ClassVersion, ClassFile -from lawu.attribute import get_attribute_classes -from lawu.util import bytecode, shell -from lawu.constants import UTF8 - - -@click.group() -def cli(): - pass - - -@cli.command() -def attributes(): - """List enabled Attributes. - - Prints a list of all enabled ClassFile Attributes. - """ - attribute_classes = get_attribute_classes() - for name, class_ in attribute_classes.items(): - click.echo( - u'{name} - Added in: {ai} ({cv})'.format( - name=click.style(name, fg='green'), - ai=click.style(class_.ADDED_IN, fg='yellow'), - cv=click.style( - ClassVersion(*class_.MINIMUM_CLASS_VERSION).human, - fg='yellow' - ) - ) - ) - - -@cli.command() -@click.argument('mnemonic') -def ins(mnemonic): - """Lookup instruction information. - - Lookup an instruction by its mnemonic. - """ - try: - opcode = bytecode.opcode_table[mnemonic] - except KeyError: - click.secho(u'No definition found.', fg='red') - return - - click.echo(u'{mnemonic} (0x{op})'.format( - mnemonic=click.style(opcode['mnemonic'], fg='green', underline=True), - op=click.style(format(opcode['op'], '02x'), fg='green') - )) - - if opcode.get('desc'): - click.secho('Description:', fg='yellow') - click.echo(opcode['desc']) - - if opcode['can_be_wide']: - click.echo(u'This instruction can be prefixed by the WIDE opcode.') - - if opcode.get('runtime'): - click.secho('Possible runtime exceptions:', fg='yellow') - for runtime_exception in opcode['runtime']: - click.echo('- {runtime_exception}'.format( - runtime_exception=click.style(runtime_exception, fg='red') - )) - - if opcode['operands']: - click.secho(u'Operand Format:', fg='yellow') - for operand_fmt, operand_type in opcode['operands']: - click.echo(u'- {ty} as a {fmt}'.format( - ty=click.style(operand_type.name, fg='yellow'), - fmt=click.style(operand_fmt.name, fg='yellow') - )) - elif opcode['op'] in (0xAB, 0xAA, 0xC4): - # lookup[table|switch] and WIDE. - click.secho(u'\nOperand Format:', fg='yellow') - click.echo( - u'This is a special-case opcode with variable operand parsing.' - ) - - -@cli.command(name='shell') -@click.option('--class-path', '-cp', multiple=True) -def shell_command(class_path): - """Drop into a debugging shell.""" - loader = ClassLoader(*class_path) - shell.start_shell(local_ns={ - 'ClassFile': ClassFile, - 'loader': loader, - 'constants': importlib.import_module('lawu.constants'), - }) - - -@cli.command(name='def2json') -@click.argument('source', type=click.File('rb')) -def definition_to_json(source): - """Convert a bytecode.yaml file into a prepared bytecode.json. - - Lawu internally uses a YAML file to define all bytecode opcodes, operands, - runtime exceptions, default transforms, etc... - - However since JSON is available in the python stdlib and YAML is not, we - process this YAML file before distribution to prevent adding an unnecessary - dependency. - """ - try: - import yaml - except ImportError: - click.echo( - 'The pyyaml module could not be found and is required' - ' to use this command.', - err=True - ) - return - - y = yaml.load(source) - - for k, v in y.items(): - # We guarantee some keys should always exist to make life easier for - # developers. - v.setdefault('operands', None) - v.setdefault('can_be_wide', False) - v.setdefault('transform', {}) - v['mnemonic'] = k - - click.echo(json.dumps(y, indent=4, sort_keys=True)) - - -@cli.command() -@click.argument('source', type=click.Path(exists=True)) -def dependencies(source): - """Output a list of all classes referenced by the given source.""" - loader = ClassLoader(source, max_cache=-1) - all_dependencies = set() - for klass in loader.classes: - new_dependencies = loader.dependencies(klass) - all_dependencies - all_dependencies.update(new_dependencies) - for new_dep in new_dependencies: - click.echo(new_dep) - - -@cli.command() -@click.argument('source', type=click.Path(exists=True)) -@click.argument('regex') -@click.option( - '--stop-on-first', - default=False, - is_flag=True, - help='Stop iteration on first matching class.' -) -def grep(source, regex, stop_on_first=False): - """Grep the constant pool of all classes in source.""" - loader = ClassLoader(source, max_cache=-1) - r = re.compile(regex) - - def _matches(constant): - return r.match(constant.value) - - for klass in loader.classes: - it = loader.search_constant_pool(path=klass, type_=UTF8, f=_matches) - if next(it, None): - print(klass) - if stop_on_first: - break diff --git a/lawu/constants.py b/lawu/constants.py index 96ff48e..b10fda1 100644 --- a/lawu/constants.py +++ b/lawu/constants.py @@ -1,6 +1,6 @@ from struct import unpack, pack -from lawu.util.utf import decode_modified_utf8, encode_modified_utf8 +from mutf8 import decode_modified_utf8, encode_modified_utf8 class Constant(object): diff --git a/lawu/fields.py b/lawu/fields.py index 48e9429..45e57ec 100644 --- a/lawu/fields.py +++ b/lawu/fields.py @@ -1,8 +1,8 @@ from typing import IO, Callable, Iterator, Optional from struct import unpack, pack from itertools import repeat +from enum import IntFlag -from lawu.util.flags import Flags from lawu.attribute import AttributeTable from lawu.constants import Constant, UTF8 from lawu.attributes.constant_value import ConstantValueAttribute @@ -10,19 +10,20 @@ class Field(object): + class AccessFlags(IntFlag): + PUBLIC = 0x0001 + PRIVATE = 0x0002 + PROTECTED = 0x0004 + STATIC = 0x0008 + FINAL = 0x0010 + VOLATILE = 0x0040 + TRANSIENT = 0x0080 + SYNTHETIC = 0x1000 + ENUM = 0x4000 + def __init__(self, cf): self._cf = cf - self.access_flags = Flags('>H', { - 'acc_public': 0x0001, - 'acc_private': 0x0002, - 'acc_protected': 0x0004, - 'acc_static': 0x0008, - 'acc_final': 0x0010, - 'acc_volatile': 0x0040, - 'acc_transient': 0x0080, - 'acc_synthetic': 0x1000, - 'acc_enum': 0x4000 - }) + self.access_flags = Field.AccessFlags(0) self._name_index = 0 self._descriptor_index = 0 self.attributes = AttributeTable(cf) @@ -67,7 +68,7 @@ def unpack(self, source: IO): :param source: Any file-like object providing `read()` """ - self.access_flags.unpack(source.read(2)) + self.access_flags = Field.AccessFlags(unpack('>H', source.read(2))[0]) self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) self.attributes.unpack(source) @@ -82,7 +83,7 @@ def pack(self, out: IO): :param out: Any file-like object providing `write()` """ - out.write(self.access_flags.pack()) + out.write(pack('>H', int(self.access_flags))) out.write(pack('>HH', self._name_index, self._descriptor_index)) self.attributes.pack(out) @@ -134,11 +135,11 @@ def create(self, name: str, descriptor: str, value: Constant=None) -> Field: descriptor = self._cf.constants.create_utf8(descriptor) field._name_index = name.index field._descriptor_index = descriptor.index - field.access_flags.acc_public = True + field.access_flags.PUBLIC = True if value is not None: field.attributes.create(ConstantValueAttribute, value) - field.access_flags.acc_static = True + field.access_flags.STATIC = True self.append(field) return field @@ -182,8 +183,8 @@ def pack(self, out: IO): def __len__(self): return len(self._table) - def find(self, *, name: str=None, type_: str=None, - f: Callable=None) -> Iterator[Field]: + def find(self, *, name: Optional[str] = None, type_: Optional[str] = None, + f: Optional[Callable] = None) -> Iterator[Field]: """ Iterates over the fields table, yielding each matching method. Calling without any arguments is equivalent to iterating over the table. diff --git a/lawu/methods.py b/lawu/methods.py index 1be511c..3f508cd 100644 --- a/lawu/methods.py +++ b/lawu/methods.py @@ -1,31 +1,32 @@ from typing import Optional, Callable, Iterator, IO, List from struct import unpack, pack from itertools import repeat +from enum import IntFlag from lawu.constants import UTF8 -from lawu.util.flags import Flags from lawu.util.descriptor import method_descriptor, JVMType from lawu.attribute import AttributeTable from lawu.attributes.code import CodeAttribute class Method(object): + class AccessFlags(IntFlag): + PUBLIC = 0x0001 + PRIVATE = 0x0002 + PROTECTED = 0x0004 + STATIC = 0x0008 + FINAL = 0x0010 + SYNCHRONIZED = 0x0020 + BRIDGE = 0x0040 + VARARGS = 0x0080 + NATIVE = 0x0100 + ABSTRACT = 0x0400 + STRICT = 0x0800 + SYNTHETIC = 0x1000 + def __init__(self, cf): self._cf = cf - self.access_flags = Flags('>H', { - 'acc_public': 0x0001, - 'acc_private': 0x0002, - 'acc_protected': 0x0004, - 'acc_static': 0x0008, - 'acc_final': 0x0010, - 'acc_synchronized': 0x0020, - 'acc_bridge': 0x0040, - 'acc_varargs': 0x0080, - 'acc_native': 0x0100, - 'acc_abstract': 0x0400, - 'acc_strict': 0x0800, - 'acc_synthetic': 0x1000 - }) + self.access_flags = Method.AccessFlags(0) self._name_index = 0 self._descriptor_index = 0 self.attributes = AttributeTable(cf) @@ -81,7 +82,7 @@ def unpack(self, source: IO): :param source: Any file-like object providing `read()` """ - self.access_flags.unpack(source.read(2)) + self.access_flags = Method.AccessFlags(unpack('>H', source.read(2))[0]) self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) self.attributes.unpack(source) @@ -96,7 +97,7 @@ def pack(self, out: IO): :param out: Any file-like object providing `write()` """ - out.write(self.access_flags.pack()) + out.write(pack('>H', int(self.access_flags))) out.write(pack( '>HH', self._name_index, @@ -126,7 +127,7 @@ def remove(self, method: Method): self._table = [fld for fld in self._table if fld is not method] def create(self, name: str, descriptor: str, - code: CodeAttribute=None) -> Method: + code: CodeAttribute = None) -> Method: """ Creates a new method from `name` and `descriptor`. If `code` is not ``None``, add a `Code` attribute to this method. @@ -136,7 +137,7 @@ def create(self, name: str, descriptor: str, descriptor = self._cf.constants.create_utf8(descriptor) method._name_index = name.index method._descriptor_index = descriptor.index - method.access_flags.acc_public = True + method.access_flags.PUBLIC = True if code is not None: method.attributes.create(CodeAttribute) @@ -180,8 +181,9 @@ def pack(self, out: IO): for method in self._table: method.pack(out) - def find(self, *, name: str=None, args: str=None, returns: str=None, - f: Callable=None) -> Iterator[Method]: + def find(self, *, name: Optional[str] = None, args: Optional[str] = None, + returns: Optional[str] = None, f: Optional[Callable] = None + ) -> Iterator[Method]: """ Iterates over the methods table, yielding each matching method. Calling without any arguments is equivalent to iterating over the table. For @@ -197,8 +199,8 @@ def find(self, *, name: str=None, args: str=None, returns: str=None, print method.name.value :param name: The name of the method(s) to find. - :param args: The arguments descriptor (ex: ``III``) - :param returns: The returns descriptor (Ex: ``V``) + :param args: The argument descriptor (ex: ``III``) + :param returns: The return descriptor (Ex: ``V``) :param f: Any callable which takes one argument (the method). """ for method in self._table: diff --git a/lawu/util/flags.py b/lawu/util/flags.py deleted file mode 100644 index e847eb2..0000000 --- a/lawu/util/flags.py +++ /dev/null @@ -1,62 +0,0 @@ -__all__ = ('Flags',) -import struct - - -class Flags(object): - """ - Convenience class for handling bit flags. - """ - def __init__(self, binary_format, flags): - object.__setattr__(self, 'binary_format', binary_format) - object.__setattr__(self, 'flags', flags) - object.__setattr__(self, '_value', 0) - object.__setattr__(self, '_cache', struct.Struct(binary_format)) - - def pack(self): - """ - A shortcut for `struct.pack(flag.binary_format, flag.value)`. - """ - return self._cache.pack(self.value) - - @property - def value(self): - """ - The numeric value of the bitfield. - """ - return self._value - - def unpack(self, source): - """ - A shortcut for `struct.unpack(flag.binary_format, )`. - """ - self._value = self._cache.unpack(source)[0] - - def get(self, name): - """ - Returns the value of the field `name`. - """ - return bool(self.flags[name] & self.value) - - def set(self, name, value): - """ - Sets the value of the field `name` to `value`, which is `True` or - `False`. - """ - flag = self.flags[name] - self._value = (self.value | flag) if value else (self.value & ~flag) - - def __getattr__(self, attr): - if attr not in self.flags: - return object.__getattr__(self, attr) - return self.get(attr) - - def __setattr__(self, attr, value): - if attr not in self.flags: - return object.__setattr__(self, attr, value) - self.set(attr, value) - - def to_dict(self): - """ - Returns this `Flags` object's fields as a dictionary. - """ - return dict((k, self.get(k)) for k in self.flags.keys()) diff --git a/lawu/util/tracer.py b/lawu/util/tracer.py deleted file mode 100644 index e69de29..0000000 diff --git a/lawu/util/utf.py b/lawu/util/utf.py deleted file mode 100644 index 15d27bc..0000000 --- a/lawu/util/utf.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Utility methods for handling oddities in character encoding encountered -when parsing and writing JVM ClassFiles or object serialization archives. - -.. note:: - - http://bugs.python.org/issue2857 was an attempt in 2008 to get support - for MUTF-8/CESU-8 into the python core. -""" - - -def decode_modified_utf8(s: bytes) -> str: - """ - Decodes a bytestring containing modified UTF-8 as defined in section - 4.4.7 of the JVM specification. - - :param s: bytestring to be converted. - :returns: A unicode representation of the original string. - """ - s = bytearray(s) - buff = [] - buffer_append = buff.append - ix = 0 - while ix < len(s): - x = s[ix] - ix += 1 - - if x >> 7 == 0: - # Just an ASCII character, nothing else to do. - pass - elif x >> 6 == 6: - y = s[ix] - ix += 1 - x = ((x & 0x1F) << 6) + (y & 0x3F) - elif x >> 4 == 14: - y, z = s[ix:ix+2] - ix += 2 - x = ((x & 0xF) << 12) + ((y & 0x3F) << 6) + (z & 0x3F) - elif x == 0xED: - v, w, x, y, z = s[ix:ix+6] - ix += 5 - x = 0x10000 + ( - ((v & 0x0F) << 16) + - ((w & 0x3F) << 10) + - ((y & 0x0F) << 6) + - (z & 0x3F) - ) - elif x == 0xC0 and s[ix] == 0x80: - ix += 1 - x = 0 - buffer_append(x) - return u''.join(chr(b) for b in buff) - - -def encode_modified_utf8(u: str) -> bytearray: - """ - Encodes a unicode string as modified UTF-8 as defined in section 4.4.7 - of the JVM specification. - - :param u: unicode string to be converted. - :returns: A decoded bytearray. - """ - final_string = bytearray() - - for c in [ord(char) for char in u]: - if c == 0x00 or (0x80 < c < 0x7FF): - final_string.extend([ - (0xC0 | (0x1F & (c >> 6))), - (0x80 | (0x3F & c))] - ) - elif c < 0x7F: - final_string.append(c) - elif 0x800 < c < 0xFFFF: - final_string.extend([ - (0xE0 | (0x0F & (c >> 12))), - (0x80 | (0x3F & (c >> 6))), - (0x80 | (0x3F & c))] - ) - - return final_string diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 224a779..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md \ No newline at end of file diff --git a/tests/test_modified_utf8.py b/tests/test_modified_utf8.py deleted file mode 100644 index db91a10..0000000 --- a/tests/test_modified_utf8.py +++ /dev/null @@ -1,47 +0,0 @@ -from lawu.util.utf import encode_modified_utf8, decode_modified_utf8 - - -def test_decode_modified_utf8(loader): - """ - JVM ClassFile's use a "modified" form of UTF8 which cannot always be - parsed by python's UTF-8 decoder. - - We simply need to make sure no encoding exceptions are raised - when we parse the sample ClassFile. - """ - loader.load('ModifiedUTF8') - - -def test_encode_utf8_1(): - """ - Tests encoding of some special cases: - 1 - byte 00 must be encoded as 'c080' - 2 - supplementary characters (represented by the two surrogate code - units of their UTF-16 representation): each surrogate must be encoded - by three bytes. This means supplementary characters are represented by - six bytes then U+10400 (represented as \uD801\uDC00) will be encoded as - 'eda081edb080' - """ - pairs = ( - (u'1\x002', b'\x31\xc0\x80\x32'), - (u'\uD801\uDC00', b'\xed\xa0\x81\xed\xb0\x80') - ) - for original, encoded in pairs: - assert encode_modified_utf8(original) == encoded - - -def test_decode_utf8_1(): - """ - Counterpart of test_encode_utf8_1. - - Tests decoding of some special cases: - 1 - c080 must be decoded as byte 00 - 2 - eda081edb080 must be decoded as \uD801\uDC00 (representing U+10400) - """ - pairs = ( - (b'\x31\xc0\x80\x32', '1\x002'), - (b'\xed\xa0\x81\xed\xb0\x80', '\uD801\uDC00') - ) - - for original, decoded in pairs: - assert decode_modified_utf8(original) == decoded From 9b54d1860101c319f4d7a75f65d0a867d3afec12 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Sun, 16 Jan 2022 05:32:29 -0500 Subject: [PATCH 3/9] Simplify BufferStreamReader to simply be a wrapper around BytesIO (which also didn't exist when we wrote this). Fix up some PEP8 and typing errors. --- examples/creating_fields.py | 4 +-- examples/disassemble.py | 1 - lawu/attribute.py | 21 +++++++-------- lawu/attributes/code.py | 3 ++- lawu/attributes/stack_map_table.py | 4 +-- lawu/cf.py | 11 ++++---- lawu/classloader.py | 11 ++++---- lawu/fields.py | 13 +++++----- lawu/methods.py | 10 ++++---- lawu/util/shell.py | 2 +- lawu/util/stream.py | 41 ++++++------------------------ tests/test_classloader.py | 6 ++--- 12 files changed, 51 insertions(+), 76 deletions(-) diff --git a/examples/creating_fields.py b/examples/creating_fields.py index f6025ef..f1e3248 100644 --- a/examples/creating_fields.py +++ b/examples/creating_fields.py @@ -1,7 +1,7 @@ """ An example showing how to create fields on a new class. """ -from lawu import ClassFile +from lawu.cf import ClassFile if __name__ == '__main__': cf = ClassFile.create('HelloWorld') @@ -9,7 +9,7 @@ # Creating a field from a field name and descriptor field = cf.fields.create('BeerCount', 'I') - # A convienience shortcut for creating static fields. + # A convenience shortcut for creating static fields. field = cf.fields.create_static( 'HelloWorld', 'Ljava/lang/String;', diff --git a/examples/disassemble.py b/examples/disassemble.py index 6563848..964d665 100644 --- a/examples/disassemble.py +++ b/examples/disassemble.py @@ -46,7 +46,6 @@ def main(class_path, classes): ', '.join(a.name + ('[]' * a.dimensions) for a in method.args) ).strip()) - r = [] if method.code: for ins in method.code.disassemble(): line = [ diff --git a/lawu/attribute.py b/lawu/attribute.py index 136eedc..48a0076 100644 --- a/lawu/attribute.py +++ b/lawu/attribute.py @@ -1,7 +1,7 @@ import inspect import pkgutil import importlib -from typing import IO, Callable, Iterator, Union, Dict, Any, Tuple +from typing import BinaryIO, Callable, Iterator, Union, Dict, Any, Tuple from struct import unpack, pack from itertools import repeat @@ -49,7 +49,7 @@ def __init__(self, parent: 'AttributeTable', name_index: int): super().__init__(parent, name_index) self.info = None - def unpack(self, info: Union[bytes, BufferStreamReader]): + def unpack(self, info: BufferStreamReader): self.info = info def pack(self) -> bytes: @@ -57,14 +57,14 @@ def pack(self) -> bytes: class AttributeTable(object): - def __init__(self, cf, parent: Attribute=None): + def __init__(self, cf, parent: Attribute = None): #: The ClassFile that ultimately owns this AttributeTable. self.cf = cf #: The parent Attribute, if one exists. self.parent = parent self._table = [] - def unpack(self, source: IO): + def unpack(self, source: BinaryIO): """ Read the ConstantPool from the file-like object `source`. @@ -90,17 +90,14 @@ def __getitem__(self, key): attribute_type = ATTRIBUTE_CLASSES.get(name, UnknownAttribute) self._table[key] = attr = attribute_type(self, name_index) - if attribute_type is UnknownAttribute: - attr.unpack(info) - else: - attr.unpack(BufferStreamReader(info)) + attr.unpack(BufferStreamReader(info)) return attr def __len__(self): return len(self._table) - def pack(self, out: IO): + def pack(self, out: BinaryIO): """ Write the AttributeTable to the file-like object `out`. @@ -130,7 +127,7 @@ def create(self, type_, *args, **kwargs) -> Any: self._table.append(attribute) return attribute - def find(self, *, name: str=None, f: Callable=None) -> Iterator[Any]: + def find(self, *, name: str = None, f: Callable = None) -> Iterator[Any]: for idx, attribute in enumerate(self._table): if name is not None: # Optimization to filter solely on name without causing @@ -172,8 +169,8 @@ def get_attribute_classes() -> Dict[str, Attribute]: classes = inspect.getmembers( importlib.import_module(name), lambda c: ( - inspect.isclass(c) and issubclass(c, Attribute) and - c is not Attribute + inspect.isclass(c) and issubclass(c, Attribute) and + c is not Attribute ) ) diff --git a/lawu/attributes/code.py b/lawu/attributes/code.py index 1154f67..d878dea 100644 --- a/lawu/attributes/code.py +++ b/lawu/attributes/code.py @@ -65,7 +65,7 @@ def __init__(self, table, name_index=None): self.max_locals = 0 self.exception_table = [] self.attributes = AttributeTable(table.cf, parent=self) - self._code = '' + self._code = b'' def unpack(self, info): """ @@ -87,6 +87,7 @@ def unpack(self, info): self.exception_table.append(CodeException( *info.unpack('>HHHH') )) + self.attributes = AttributeTable(self.cf, parent=self) self.attributes.unpack(info) diff --git a/lawu/attributes/stack_map_table.py b/lawu/attributes/stack_map_table.py index d07993a..4217062 100644 --- a/lawu/attributes/stack_map_table.py +++ b/lawu/attributes/stack_map_table.py @@ -171,9 +171,9 @@ def _unpack_verification_type_info(info, count): for _ in repeat(None, count): tag = info.u1() if tag in TYPES_WITH_EXTRA: - yield (tag, info.u2()) + yield tag, info.u2() else: - yield (tag,) + yield tag, def pack(self): raise NotImplementedError() diff --git a/lawu/cf.py b/lawu/cf.py index 9f32b23..ddeae96 100644 --- a/lawu/cf.py +++ b/lawu/cf.py @@ -4,7 +4,7 @@ The :mod:`lawu.cf` module provides tools for working with JVM ``.class`` ClassFiles. """ -from typing import IO, Iterable, Union, Sequence, Optional +from typing import BinaryIO, Iterable, Union, Sequence, Optional from struct import pack, unpack from collections import namedtuple from enum import IntFlag @@ -78,7 +78,7 @@ class AccessFlags(IntFlag): ENUM = 0x4000 MODULE = 0x8000 - def __init__(self, source: Optional[IO] = None): + def __init__(self, source: Optional[BinaryIO] = None): # Default to J2SE_7 self._version = ClassVersion(0x32, 0) self._constants = ConstantPool() @@ -96,7 +96,8 @@ def __init__(self, source: Optional[IO] = None): self._from_io(source) @classmethod - def create(cls, this: str, super_: str = u'java/lang/Object') -> 'ClassFile': + def create(cls, this: str, super_: str = u'java/lang/Object')\ + -> 'ClassFile': """ A utility which sets up reasonable defaults for a new public class. @@ -113,7 +114,7 @@ def create(cls, this: str, super_: str = u'java/lang/Object') -> 'ClassFile': return cf - def save(self, source: IO): + def save(self, source: BinaryIO): """ Saves the class to the file-like object `source`. @@ -143,7 +144,7 @@ def save(self, source: IO): self.methods.pack(source) self.attributes.pack(source) - def _from_io(self, source: IO): + def _from_io(self, source: BinaryIO): """ Loads an existing JVM ClassFile from any file-like object. """ diff --git a/lawu/classloader.py b/lawu/classloader.py index 078f829..41a611c 100644 --- a/lawu/classloader.py +++ b/lawu/classloader.py @@ -38,8 +38,9 @@ class ClassLoader(object): :param bytecode_transforms: Default transforms to apply when disassembling a method. """ - def __init__(self, *sources, max_cache: int=50, klass=ClassFile, - bytecode_transforms: Iterable[Callable]=None): + + def __init__(self, *sources, max_cache: int = 50, klass=ClassFile, + bytecode_transforms: Iterable[Callable] = None): self.path_map = {} self.max_cache = max_cache self.class_cache = OrderedDict() @@ -59,8 +60,8 @@ def __contains__(self, path: str) -> bool: return True return False - def update(self, *sources, follow_symlinks: bool=False, - maximum_depth: int=20): + def update(self, *sources, follow_symlinks: bool = False, + maximum_depth: int = 20): """Add one or more ClassFile sources to the class loader. If a given source is a directory path, it is traversed up to the @@ -105,7 +106,7 @@ def update(self, *sources, follow_symlinks: bool=False, self.path_map[path_suffix] = path_full @contextmanager - def open(self, path: str, mode: str='r') -> IO: + def open(self, path: str, mode: str = 'r') -> IO: """Open an IO-like object for `path`. .. note:: diff --git a/lawu/fields.py b/lawu/fields.py index 45e57ec..7c79297 100644 --- a/lawu/fields.py +++ b/lawu/fields.py @@ -1,4 +1,4 @@ -from typing import IO, Callable, Iterator, Optional +from typing import BinaryIO, Callable, Iterator, Optional from struct import unpack, pack from itertools import repeat from enum import IntFlag @@ -57,7 +57,7 @@ def value(self) -> ConstantValueAttribute: """ return self.attributes.find_one(name='ConstantValue') - def unpack(self, source: IO): + def unpack(self, source: BinaryIO): """ Read the Field from the file-like object `fio`. @@ -72,7 +72,7 @@ def unpack(self, source: IO): self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) self.attributes.unpack(source) - def pack(self, out: IO): + def pack(self, out: BinaryIO): """ Write the Field to the file-like object `out`. @@ -108,7 +108,8 @@ def remove(self, field: Field): """ self._table = [fld for fld in self._table if fld is not field] - def create(self, name: str, descriptor: str, value: Constant=None) -> Field: + def create(self, name: str, descriptor: str, value: Constant = None) \ + -> Field: """ Creates a new field from `name` and `descriptor`. For example:: @@ -148,7 +149,7 @@ def __iter__(self): for field in self._table: yield field - def unpack(self, source: IO): + def unpack(self, source: BinaryIO): """ Read the FieldTable from the file-like object `source`. @@ -165,7 +166,7 @@ def unpack(self, source: IO): field.unpack(source) self.append(field) - def pack(self, out: IO): + def pack(self, out: BinaryIO): """ Write the FieldTable to the file-like object `out`. diff --git a/lawu/methods.py b/lawu/methods.py index 3f508cd..f986a37 100644 --- a/lawu/methods.py +++ b/lawu/methods.py @@ -1,4 +1,4 @@ -from typing import Optional, Callable, Iterator, IO, List +from typing import Optional, Callable, Iterator, BinaryIO, List from struct import unpack, pack from itertools import repeat from enum import IntFlag @@ -71,7 +71,7 @@ def code(self) -> CodeAttribute: def __repr__(self): return f'' - def unpack(self, source: IO): + def unpack(self, source: BinaryIO): """ Read the Method from the file-like object `fio`. @@ -86,7 +86,7 @@ def unpack(self, source: IO): self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) self.attributes.unpack(source) - def pack(self, out: IO): + def pack(self, out: BinaryIO): """ Write the Method to the file-like object `out`. @@ -149,7 +149,7 @@ def __iter__(self): for method in self._table: yield method - def unpack(self, source: IO): + def unpack(self, source: BinaryIO): """ Read the MethodTable from the file-like object `source`. @@ -166,7 +166,7 @@ def unpack(self, source: IO): method.unpack(source) self.append(method) - def pack(self, out: IO): + def pack(self, out: BinaryIO): """ Write the MethodTable to the file-like object `out`. diff --git a/lawu/util/shell.py b/lawu/util/shell.py index 8737934..1c78180 100644 --- a/lawu/util/shell.py +++ b/lawu/util/shell.py @@ -10,7 +10,7 @@ IPYTHON_SHELL_AVAILABLE = True -def start_shell(local_ns: Dict=None, banner: str=''): +def start_shell(local_ns: Dict = None, banner: str = ''): """Create and immediately drop into a Python shell. If IPython version 5 or greater is available it will be used instead diff --git a/lawu/util/stream.py b/lawu/util/stream.py index 77c3897..77498e5 100644 --- a/lawu/util/stream.py +++ b/lawu/util/stream.py @@ -1,43 +1,18 @@ -from struct import unpack_from, calcsize +from io import BytesIO +from struct import unpack, calcsize -class BufferStreamReader(object): - """Stream-like reader over a buffer mimicing the JVM spec types. +class BufferStreamReader(BytesIO): + """Stream-like reader over a buffer mimicking the JVM spec types. """ - def __init__(self, buff, starting_offset=0): - self.pos = starting_offset - self.buff = buff - def u1(self): - r = unpack_from('B', self.buff, offset=self.pos) - self.pos += 1 - return r[0] + return unpack('>B', self.read(1))[0] def u2(self): - r = unpack_from('>H', self.buff, offset=self.pos) - self.pos += 2 - return r[0] + return unpack('>H', self.read(2))[0] def u4(self): - r = unpack_from('>I', self.buff, offset=self.pos) - self.pos += 4 - return r[0] + return unpack('>I', self.read(4))[0] def unpack(self, fmt): - size = calcsize(fmt) - r = unpack_from(fmt, self.buff, offset=self.pos) - self.pos += size - return r - - def seek(self, pos): - self.pos = pos - - def read(self, length=None): - if length is None: - r = self.buff[self.pos:] - self.pos = len(self.buff) - return r - - r = self.buff[self.pos:self.pos+length] - self.pos += length - return r + return unpack(fmt, self.read(calcsize(fmt))) diff --git a/tests/test_classloader.py b/tests/test_classloader.py index 2b419d8..2a444b3 100644 --- a/tests/test_classloader.py +++ b/tests/test_classloader.py @@ -54,18 +54,18 @@ def test_default_bytecode_transforms(): def test_load_from_directory(): """Ensure we can load a ClassFile from a simple directory.""" - with tempfile.TemporaryDirectory() as dir: + with tempfile.TemporaryDirectory() as directory: shutil.copy( os.path.join( os.path.dirname(__file__), 'data', 'HelloWorld.class' ), - dir + directory ) cl = ClassLoader() - cl.update(dir) + cl.update(directory) assert isinstance(cl.load('HelloWorld'), cl.klass) From 80dabf6de6048403e26797709e6020a13d0c8d9d Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Sun, 16 Jan 2022 05:50:54 -0500 Subject: [PATCH 4/9] Correct some types in the ConstantPool, and add some guards with useful error messages for bad accesses. --- lawu/constants.py | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/lawu/constants.py b/lawu/constants.py index b10fda1..35c0608 100644 --- a/lawu/constants.py +++ b/lawu/constants.py @@ -1,4 +1,5 @@ from struct import unpack, pack +from typing import List, Union, Tuple from mutf8 import decode_modified_utf8, encode_modified_utf8 @@ -333,10 +334,18 @@ def __repr__(self): ('>HH', 4) ) +# The pool can contain padding entries (stored as a None), a tuple of the +# (constant_type, data) or a resolved Constant. +ConstantPoolType = List[Union[ + None, + Tuple[int, Tuple[int, ...]], + Constant +]] + class ConstantPool(object): def __init__(self): - self._pool = [None] + self._pool: ConstantPoolType = [None] def append(self, constant): """ @@ -355,9 +364,28 @@ def get(self, index): does not exist. """ constant = self._pool[index] + + # Doubles and Longs take two entries in the pool, and the second + # entry is always a None. + if constant is None: + raise ValueError( + f'Attempted to access a ConstantPool index containing a padding' + f' entry. This usually indicates an off-by-one bug. (index ==' + f' {index!r})' + ) + + # If the item in the pool hasn't already been resolved, do so now. if not isinstance(constant, Constant): - constant = _constant_types[constant[0]](self, index, *constant[1:]) + constant_type = _constant_types[constant[0]] + if constant_type is None: + raise ValueError( + f'Attempted to access a ConstantPool index whose type is' + f' unknown (type == {constant[0]!r})' + ) + + constant = constant_type(self, index, *constant[1:]) self._pool[index] = constant + return constant def __getitem__(self, idx): @@ -560,15 +588,9 @@ def unpack(self, fio): tag = ord(read(1)) if tag == 1: - # CONSTANT_Utf8_info, a length prefixed UTF-8-ish string. - # Only attempt to properly decode the MUTF8 if it fails - # regular UTF8 decoding, which overs huge time savings over - # large JARs. + # CONSTANT_Utf8_info, a length prefixed mutf8 string. utf8_str = read(unpack('>H', read(2))[0]) - try: - utf8_str = utf8_str.decode('utf8') - except UnicodeDecodeError: - utf8_str = decode_modified_utf8(utf8_str) + utf8_str = decode_modified_utf8(utf8_str) self.append((tag, utf8_str)) else: # Every other constant type is trivial. From 81ff16e9045fc596f2056a8f6e5c2737aab6a530 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Mon, 17 Jan 2022 06:37:23 -0500 Subject: [PATCH 5/9] Trying a new approach to Constants, still very early. --- examples/creating_fields.py | 20 +- examples/hello_world.py | 32 +- lawu/attributes/code.py | 10 +- lawu/attributes/source_file.py | 7 +- lawu/cf.py | 136 ++-- lawu/constants.py | 750 +++++++++--------- lawu/context.py | 18 + lawu/fields.py | 4 +- lawu/methods.py | 8 +- tests/attributes/test_exceptions_attribute.py | 21 +- tests/attributes/test_sourcefile_attribute.py | 12 +- tests/test_classloader.py | 4 +- tests/test_printable.py | 28 - tests/test_tracer.py | 0 14 files changed, 545 insertions(+), 505 deletions(-) create mode 100644 lawu/context.py delete mode 100644 tests/test_printable.py delete mode 100644 tests/test_tracer.py diff --git a/examples/creating_fields.py b/examples/creating_fields.py index f1e3248..0ac6ad9 100644 --- a/examples/creating_fields.py +++ b/examples/creating_fields.py @@ -2,19 +2,21 @@ An example showing how to create fields on a new class. """ from lawu.cf import ClassFile +from lawu.constants import String if __name__ == '__main__': - cf = ClassFile.create('HelloWorld') + cf = ClassFile(this='HelloWorld') - # Creating a field from a field name and descriptor - field = cf.fields.create('BeerCount', 'I') + with cf: + # Creating a field from a field name and descriptor + field = cf.fields.create('BeerCount', 'I') - # A convenience shortcut for creating static fields. - field = cf.fields.create_static( - 'HelloWorld', - 'Ljava/lang/String;', - cf.constants.create_string('Hello World!') - ) + # A convenience shortcut for creating static fields. + field = cf.fields.create_static( + 'HelloWorld', + 'Ljava/lang/String;', + String('Hello World!') + ) with open('HelloWorld.class', 'wb') as fout: cf.save(fout) diff --git a/examples/hello_world.py b/examples/hello_world.py index 02dd80f..c0daba7 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -3,28 +3,30 @@ """ from lawu.cf import ClassFile from lawu.assemble import assemble +from lawu.constants import String -cf = ClassFile.create('HelloWorld') +cf = ClassFile(this='HelloWorld') main = cf.methods.create('main', '([Ljava/lang/String;)V', code=True) main.access_flags.acc_static = True main.code.max_locals = 1 main.code.max_stack = 2 -main.code.assemble(assemble([ - ('getstatic', cf.constants.create_field_ref( - 'java/lang/System', - 'out', - 'Ljava/io/PrintStream;' - )), - ('ldc', cf.constants.create_string('Hello World!')), - ('invokevirtual', cf.constants.create_method_ref( - 'java/io/PrintStream', - 'println', - '(Ljava/lang/String;)V' - )), - ('return',) -])) +with cf: + main.code.assemble(assemble([ + ('getstatic', cf.constants.create_field_ref( + 'java/lang/System', + 'out', + 'Ljava/io/PrintStream;' + )), + ('ldc', String('Hello World!')), + ('invokevirtual', cf.constants.create_method_ref( + 'java/io/PrintStream', + 'println', + '(Ljava/lang/String;)V' + )), + ('return',) + ])) with open('HelloWorld.class', 'wb') as fout: cf.save(fout) diff --git a/lawu/attributes/code.py b/lawu/attributes/code.py index d878dea..10fa878 100644 --- a/lawu/attributes/code.py +++ b/lawu/attributes/code.py @@ -6,6 +6,7 @@ from itertools import repeat from collections import namedtuple +from lawu.constants import UTF8 from lawu.attribute import Attribute, AttributeTable from lawu.util.bytecode import ( read_instruction, @@ -30,7 +31,7 @@ class CodeAttribute(Attribute): from lawu import ClassFile from lawu.util.bytecode import Instruction - cf = ClassFile.create('HelloWorld') + cf = ClassFile(name='HelloWorld') main = cf.methods.create( # The name of the method @@ -55,10 +56,11 @@ class CodeAttribute(Attribute): MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): - super(CodeAttribute, self).__init__( + super().__init__( table, - name_index or table.cf.constants.create_utf8( - 'Code' + name_index or UTF8( + pool=table.cf.constants, + value='Code' ).index ) self.max_stack = 0 diff --git a/lawu/attributes/source_file.py b/lawu/attributes/source_file.py index 771634f..f7df0eb 100644 --- a/lawu/attributes/source_file.py +++ b/lawu/attributes/source_file.py @@ -1,5 +1,7 @@ from struct import pack + from lawu.attribute import Attribute +from lawu.constants import UTF8 class SourceFileAttribute(Attribute): @@ -9,8 +11,9 @@ class SourceFileAttribute(Attribute): def __init__(self, table, name_index=None): super(SourceFileAttribute, self).__init__( table, - name_index or table.cf.constants.create_utf8( - u'SourceFile' + name_index or UTF8( + pool=table.cf.constants, + value='SourceFile' ).index ) self.source_file_index = None diff --git a/lawu/cf.py b/lawu/cf.py index ddeae96..838bea7 100644 --- a/lawu/cf.py +++ b/lawu/cf.py @@ -4,16 +4,17 @@ The :mod:`lawu.cf` module provides tools for working with JVM ``.class`` ClassFiles. """ -from typing import BinaryIO, Iterable, Union, Sequence, Optional +from typing import BinaryIO, Iterable, Union, Sequence, Optional, List from struct import pack, unpack from collections import namedtuple from enum import IntFlag -from lawu.constants import ConstantPool, ConstantClass +from lawu.constants import ConstantPool, ConstantClass, UTF8 from lawu.fields import FieldTable from lawu.methods import MethodTable from lawu.attribute import AttributeTable, ATTRIBUTE_CLASSES from lawu.attributes.bootstrap import BootstrapMethod +from lawu.context import class_context class ClassVersion(namedtuple('ClassVersion', ['major', 'minor'])): @@ -50,11 +51,11 @@ class ClassFile(object): To save a newly created or modified ClassFile:: - >>> cf = ClassFile.create('HelloWorld') + >>> cf = ClassFile() >>> with open('HelloWorld.class', 'wb') as out: ... cf.save(out) - :meth:`~ClassFile.create` sets up some reasonable defaults equivalent to: + :meth:`~ClassFile()` sets up some reasonable defaults equivalent to: .. code-block:: java @@ -66,8 +67,11 @@ class ClassFile(object): #: The JVM ClassFile magic number. MAGIC = 0xCAFEBABE - + class AccessFlags(IntFlag): + """ + Possible values for the ClassFile.access_flags field. + """ PUBLIC = 0x0001 FINAL = 0x0010 SUPER = 0x0020 @@ -77,42 +81,72 @@ class AccessFlags(IntFlag): ANNOTATION = 0x2000 ENUM = 0x4000 MODULE = 0x8000 - - def __init__(self, source: Optional[BinaryIO] = None): + + def __init__(self, source: Optional[BinaryIO] = None, *, + this: str = 'HelloWorld', super_: str = 'java/lang/Object'): # Default to J2SE_7 self._version = ClassVersion(0x32, 0) - self._constants = ConstantPool() - self.access_flags = ClassFile.AccessFlags(0) - self._this = 0 - self._super = 0 + self.constants = ConstantPool() + self.access_flags = ( + ClassFile.AccessFlags.PUBLIC | + ClassFile.AccessFlags.SUPER + ) + self.this = self.constants.add( + ConstantClass( + pool=self.constants, + name=self.constants.add(UTF8(this)) + ) + ) + self.super_ = self.constants.add( + ConstantClass( + pool=self.constants, + name=self.constants.add(UTF8(super_)) + ) + ) self._interfaces = [] self.fields = FieldTable(self) self.methods = MethodTable(self) self.attributes = AttributeTable(self) - #: The ClassLoader bound to this ClassFile, if any. + #: The ClassLoader instance bound to this ClassFile, if any. self.classloader = None if source: self._from_io(source) - @classmethod - def create(cls, this: str, super_: str = u'java/lang/Object')\ - -> 'ClassFile': - """ - A utility which sets up reasonable defaults for a new public class. + def push_context(self): + """Push this ClassFile to the top of the class context. - :param this: The name of this class. - :param super_: The name of this class's superclass. - """ - cf = ClassFile() + It's generally better to use the ClassFile as a context manager using + 'with' then to manage the stack manually:: - cf.access_flags.PUBLIC = True - cf.access_flags.SUPER = True + >>> cf = ClassFile() + >>> with cf: + ... print('Context is automatically managed.') + """ + class_context().append(self) - cf.this = cf.constants.create_class(this) - cf.super_ = cf.constants.create_class(super_) + def pop_context(self): + """Pop this ClassFile off the top of the class context. - return cf + It is a RuntimeError if the ClassFile popped off the top of the stack + is not _this_ ClassFile. + """ + cont = class_context() + if cont: + ctx = cont.pop() + if ctx is not self: + # This should never happen unless a user is manually managing + # the context stack instead of using the ClassFile as a context + # manager. + raise RuntimeError( + 'A ClassFile tried to pop a context which was not itself.' + ) + + def __enter__(self): + self.push_context() + + def __exit__(self, _, __, ___): + self.pop_context() def save(self, source: BinaryIO): """ @@ -129,13 +163,13 @@ def save(self, source: BinaryIO): self.version.major )) - self._constants.pack(source) + self.constants.pack(source) write(pack('>H', int(self.access_flags))) write(pack( f'>HHH{len(self._interfaces)}H', - self._this, - self._super, + self.this.index, + self.super_.index, len(self._interfaces), *self._interfaces )) @@ -156,14 +190,20 @@ def _from_io(self, source: BinaryIO): # The version is swapped on disk to (minor, major), so swap it back. self.version = unpack('>HH', source.read(4))[::-1] - self._constants.unpack(source) + # We created some default values when the class was constructed, just + # purge them. + self.constants.clear() + self.constants.unpack(source) # ClassFile access_flags, see section #4.1 of the JVM specs. self.access_flags = unpack('>H', read(2)) # The CONSTANT_Class indexes for "this" class and its superclass. # Interfaces are a simple list of CONSTANT_Class indexes. - self._this, self._super, interfaces_count = unpack('>HHH', read(6)) + this_, super_, interfaces_count = unpack('>HHH', read(6)) + self.this = self.constants[this_] + self.super_ = self.constants[super_] + self._interfaces = unpack( f'>{interfaces_count}H', read(2 * interfaces_count) @@ -180,7 +220,7 @@ def version(self) -> ClassVersion: Example:: - >>> cf = ClassFile.create('HelloWorld') + >>> cf = ClassFile(this='HelloWorld') >>> cf.version = 51, 0 >>> print(cf.version) ClassVersion(major=51, minor=0) @@ -193,43 +233,13 @@ def version(self) -> ClassVersion: def version(self, major_minor: Union[ClassVersion, Sequence]): self._version = ClassVersion(*major_minor) - @property - def constants(self) -> ConstantPool: - """ - The :class:`~lawu.cp.ConstantPool` for this class. - """ - return self._constants - - @property - def this(self) -> ConstantClass: - """ - The :class:`~lawu.constants.ConstantClass` which represents this class. - """ - return self.constants.get(self._this) - - @this.setter - def this(self, value): - self._this = value.index - - @property - def super_(self) -> ConstantClass: - """ - The :class:`~lawu.constants.ConstantClass` which represents this - class's superclass. - """ - return self.constants.get(self._super) - - @super_.setter - def super_(self, value: ConstantClass): - self._super = value.index - @property def interfaces(self) -> Iterable[ConstantClass]: """ A list of direct superinterfaces of this class as indexes into the constant pool, in left-to-right order. """ - return [self._constants[idx] for idx in self._interfaces] + return [self.constants[idx] for idx in self._interfaces] @property def bootstrap_methods(self) -> BootstrapMethod: diff --git a/lawu/constants.py b/lawu/constants.py index 35c0608..fc49d76 100644 --- a/lawu/constants.py +++ b/lawu/constants.py @@ -1,8 +1,35 @@ +""" +Utilities for working with the ConstantPool found in JVM ClassFiles. +""" +from typing import Dict, Any, Deque, BinaryIO, Union +from collections import deque from struct import unpack, pack -from typing import List, Union, Tuple from mutf8 import decode_modified_utf8, encode_modified_utf8 +from lawu import context + + +def _missing_elements(lst, start, end): + """Sublinear solution for finding gaps in a list of integers.""" + # From Lie Ryan, Stackoverflow. + if end - start <= 1: + if lst[end] - lst[start] > 1: + yield from range(lst[start] + 1, lst[end]) + return + + index = start + (end - start) // 2 + + # is the lower half consecutive? + consecutive_low = lst[index] == lst[start] + (index - start) + if not consecutive_low: + yield from _missing_elements(lst, start, index) + + # is the upper part consecutive? + consecutive_high = lst[index] == lst[end] - (end - index) + if not consecutive_high: + yield from _missing_elements(lst, index, end) + class Constant(object): """ @@ -10,16 +37,46 @@ class Constant(object): """ __slots__ = ('pool', 'index') - def __init__(self, pool, index): - self.pool = pool + #: The "tag" or leading byte of a constant that identifies its type. + TAG: int = None + + def __init__(self, *, pool=None, index=None): + #: The constants index in the pool that owns it. self.index = index + #: The ConstantPool that owns this constant. + self.pool = pool + + # If no pool was specified, try to see if one is already pushed + # on the stack. + if pool is None: + cf = context.current_class_context() + if cf: + self.pool = cf.constants + + if self.pool: + self.pool.add(self, index=index) + + def pack(self) -> bytes: + """ + Pack the constant into a binary string, minus the tag. + """ + raise NotImplementedError() + + def unpack(self, source: BinaryIO): + """ + Unpack the constant from `source`, minus the tag. + """ + raise NotImplementedError() class Number(Constant): + """ + The base class for all numeric constant types. + """ __slots__ = ('value',) - def __init__(self, pool, index, value): - super().__init__(pool, index) + def __init__(self, *, pool=None, index=None, value=0): + super().__init__(pool=pool, index=index) self.value = value def __repr__(self): @@ -29,25 +86,40 @@ def __repr__(self): ) def __eq__(self, other): + if isinstance(other, Number): + return other.value == self.value return other == self.value + def pack(self): + raise NotImplementedError() + + def unpack(self, source: BinaryIO): + raise NotImplementedError() + class UTF8(Constant): __slots__ = ('value',) TAG = 1 - def __init__(self, pool, index, value): - super().__init__(pool, index) + def __init__(self, value=None, *, pool=None, index=None): + super().__init__(pool=pool, index=index) self.value = value def pack(self): encoded_value = encode_modified_utf8(self.value) - return pack('>BH', self.TAG, len(encoded_value)) + encoded_value + return pack('>H', len(encoded_value)) + encoded_value + + def unpack(self, source: BinaryIO): + self.value = decode_modified_utf8( + source.read(unpack('>H', source.read(2))[0]) + ) def __repr__(self): return f')' def __eq__(self, other): + if isinstance(other, UTF8): + return other.value == self.value return other == self.value @@ -55,44 +127,68 @@ class Integer(Number): TAG = 3 def pack(self): - return pack('>Bi', self.TAG, self.value) + return pack('>i', self.value) + + def unpack(self, source: BinaryIO): + self.value = unpack('>i', source.read(4))[0] class Float(Number): TAG = 4 def pack(self): - return pack('>Bf', self.TAG, self.value) + return pack('>f', self.value) + + def unpack(self, source: BinaryIO): + self.value = unpack('>f', source.read(4))[0] class Long(Number): TAG = 5 def pack(self): - return pack('>Bq', self.TAG, self.value) + return pack('>q', self.value) + + def unpack(self, source: BinaryIO): + self.value = unpack('>q', source.read(8))[0] class Double(Number): TAG = 6 def pack(self): - return pack('>Bd', self.TAG, self.value) + return pack('>d', self.value) + + def unpack(self, source: BinaryIO): + self.value = unpack('>d', source.read(8))[0] class ConstantClass(Constant): __slots__ = ('name_index',) TAG = 7 - def __init__(self, pool, index, name_index): - super().__init__(pool, index) - self.name_index = name_index + def __init__(self, *, pool=None, index=None, name=None): + super().__init__(pool=pool, index=index) + self.name_index = 0 + if name is not None: + self.name = name @property def name(self): - return self.pool.get(self.name_index) + return self.pool[self.name_index] + + @name.setter + def name(self, value: Union[str, UTF8]): + if isinstance(value, UTF8): + self.name_index = self.pool.add(value).index + else: + self.name_index = UTF8(pool=self.pool, value=value).index def pack(self): - return pack('>BH', self.TAG, self.name_index) + return pack('>H', self.name_index) + + def unpack(self, source: BinaryIO): + self.name_index = unpack('>H', source.read(2))[0] def __repr__(self): return f'' @@ -102,47 +198,57 @@ class String(Constant): __slots__ = ('string_index',) TAG = 8 - def __init__(self, pool, index, string_index): - super().__init__(pool, index) - self.string_index = string_index + def __init__(self, string=None, *, pool=None, index=None): + super().__init__(pool=pool, index=index) + self.string_index = None + if string is not None: + self.string = string @property def string(self): - return self.pool.get(self.string_index) + return self.pool[self.string_index] + + @string.setter + def string(self, value): + if isinstance(value, UTF8): + self.string_index = self.pool.add(value).index + else: + self.string_index = UTF8(value, pool=self.pool).index def pack(self): - return pack('>BH', self.TAG, self.string_index) + return pack('>H', self.string_index) + + def unpack(self, source: BinaryIO): + self.string_index = unpack('>H', source.read(2))[0] def __repr__(self): return f'' - def __eq__(self, other): - return other == self.string.value - class Reference(Constant): __slots__ = ('class_index', 'name_and_type_index') TAG = None - def __init__(self, pool, index, class_index, name_and_type_index): - super().__init__(pool, index) - self.class_index = class_index - self.name_and_type_index = name_and_type_index + def __init__(self, *, pool=None, index=None): + super().__init__(pool=pool, index=index) + self.class_index = 0 + self.name_and_type_index = 0 @property def class_(self): - return self.pool.get(self.class_index) + return self.pool[self.class_index] @property def name_and_type(self): - return self.pool.get(self.name_and_type_index) + return self.pool[self.name_and_type_index] def pack(self): - return pack( - '>BHH', - self.TAG, - self.class_index, - self.name_and_type_index + return pack('>HH', self.class_index, self.name_and_type_index) + + def unpack(self, source: BinaryIO): + self.class_index, self.name_and_type_index = unpack( + '>HH', + source.read(4) ) def __repr__(self): @@ -170,21 +276,27 @@ class NameAndType(Constant): __slots__ = ('name_index', 'descriptor_index') TAG = 12 - def __init__(self, pool, index, name_index, descriptor_index): - super().__init__(pool, index) - self.name_index = name_index - self.descriptor_index = descriptor_index + def __init__(self, *, pool=None, index=None): + super().__init__(pool=pool, index=index) + self.name_index = 0 + self.descriptor_index = 0 @property def name(self): - return self.pool.get(self.name_index) + return self.pool[self.name_index] @property def descriptor(self): - return self.pool.get(self.descriptor_index) + return self.pool[self.descriptor_index] def pack(self): - return pack('>BHH', self.TAG, self.name_index, self.descriptor_index) + return pack('>HH', self.name_index, self.descriptor_index) + + def unpack(self, source: BinaryIO): + self.name_index, self.descriptor_index = unpack( + '>HH', + source.read(4) + ) def __repr__(self): return ( @@ -199,17 +311,23 @@ class MethodHandle(Constant): __slots__ = ('reference_kind', 'reference_index') TAG = 15 - def __init__(self, pool, index, reference_kind, reference_index): - super().__init__(pool, index) - self.reference_kind = reference_kind - self.reference_index = reference_index + def __init__(self, *, pool=None, index=None): + super().__init__(pool=pool, index=index) + self.reference_kind = None + self.reference_index = 0 @property def reference(self): return self.pool.get(self.reference_index) def pack(self): - return pack('>BBH', self.TAG, self.reference_kind, self.reference_index) + return pack('>BH', self.reference_kind, self.reference_index) + + def unpack(self, source: BinaryIO): + self.reference_kind, self.reference_index = unpack( + '>BH', + source.read(3) + ) def __repr__(self): return ( @@ -221,30 +339,32 @@ class MethodType(Constant): __slots__ = ('descriptor_index',) TAG = 16 - def __init__(self, pool, index, descriptor_index): - super().__init__(pool, index) - self.descriptor_index = descriptor_index + def __init__(self, *, pool=None, index=None): + super().__init__(pool=pool, index=index) + self.descriptor_index = 0 @property def descriptor(self): return self.pool.get(self.descriptor_index) def pack(self): - return pack('>BH', self.TAG, self.descriptor_index) + return pack('>H', self.descriptor_index) + + def unpack(self, source: BinaryIO): + self.descriptor_index = unpack('>H', source.read(2))[0] def __repr__(self): return f'' -class InvokeDynamic(Constant): +class Dynamic(Constant): __slots__ = ('bootstrap_method_attr_index', 'name_and_type_index') - TAG = 18 + TAG = 17 - def __init__(self, pool, index, bootstrap_method_attr_index, - name_and_type_index): - super().__init__(pool, index) - self.bootstrap_method_attr_index = bootstrap_method_attr_index - self.name_and_type_index = name_and_type_index + def __init__(self, *, pool=None, index=None): + super().__init__(pool=pool, index=index) + self.bootstrap_method_attr_index = 0 + self.name_and_type_index = 0 @property def method_attr_index(self): @@ -256,12 +376,30 @@ def name_and_type(self): def pack(self): return pack( - '>BHH', - self.TAG, + '>HH', self.bootstrap_method_attr_index, self.name_and_type_index ) + def unpack(self, source: BinaryIO): + self.bootstrap_method_attr_index, self.name_and_type_index = unpack( + '>HH', + source.read(4) + ) + + def __repr__(self): + return ( + f'' + ) + + +class InvokeDynamic(Dynamic): + __slots__ = ('bootstrap_method_attr_index', 'name_and_type_index') + TAG = 18 + def __repr__(self): return ( f'' -_constant_types = ( +CONSTANTS = { + 1: UTF8, + 3: Integer, + 4: Float, + 5: Long, + 6: Double, + 7: ConstantClass, + 8: String, + 9: FieldReference, + 10: MethodReference, + 11: InterfaceMethodRef, + 12: NameAndType, + 15: MethodHandle, + 16: MethodType, + 17: Dynamic, + 18: InvokeDynamic, + 19: Module, + 20: PackageInfo +} + + +# The size (in bytes) of each type of Constant in the pool, except the UTF8 +# type which must be handled dynamically. +SIZE = ( None, - UTF8, None, - Integer, - Float, - Long, - Double, - ConstantClass, - String, - FieldReference, - MethodReference, - InterfaceMethodRef, - NameAndType, None, + 4, + 4, + 8, + 8, + 2, + 2, + 4, + 4, + 4, + 4, None, - MethodHandle, - MethodType, None, - InvokeDynamic, - Module, - PackageInfo + 3, + 2, + None, + 4 ) -# The format and size-on-disk of each type of constant -# in the constant pool. -_constant_fmts = ( - None, None, None, - ('>i', 4), - ('>f', 4), - ('>q', 8), - ('>d', 8), - ('>H', 2), - ('>H', 2), - ('>HH', 4), - ('>HH', 4), - ('>HH', 4), - ('>HH', 4), - None, - None, - ('>BH', 3), - ('>H', 2), - None, - ('>HH', 4) -) +class ConstantPool(object): + """ + This class can be used to read, modify, and write the JVM ClassFile + constant pool with a high-level interface. + """ + def __init__(self, *, source: BinaryIO = None): + # We use a dict as our basic pool container because the pool can be + # built out-of-order. For example when loading a Jasmin file, it's + # possible to explicitly set the position of constants in the pool, + # even if earlier elements don't exist yet. In general pool operations + # are not thread-safe. + + #: The internal constant pool. It's not recommended using this + #: directly. + self.pool: Dict[int, Any] = {} + #: A list of free indexes in the constant pool where gaps occur. + self.sparse_map: Deque[int] = deque() + + if source is not None: + self.unpack(source) + + def clear(self): + """Completely erase the ConstantPool. + + Any existing Constants will be disassociated from this pool, and may + still be referenced from elsewhere. + """ + # Disassociate any existing Constants in the pool. + for constant in self.pool.values(): + if constant is not None: + constant.pool = None -# The pool can contain padding entries (stored as a None), a tuple of the -# (constant_type, data) or a resolved Constant. -ConstantPoolType = List[Union[ - None, - Tuple[int, Tuple[int, ...]], - Constant -]] + self.pool.clear() + self.sparse_map.clear() + def unpack(self, source: BinaryIO): + """Unpack a constant pool from a ClassFile.""" + read = source.read + constant_pool_count = unpack('>H', read(2))[0] -class ConstantPool(object): - def __init__(self): - self._pool: ConstantPoolType = [None] + index_iter = iter(range(1, constant_pool_count)) + for index in index_iter: + tag = ord(read(1)) + c = CONSTANTS[tag]() + c.unpack(source) + c.index = index + c.pool = self + self.pool[index] = c + if tag == 5 or tag == 6: + self.pool[index + 1] = None + next(index_iter) + + def pack(self, out: BinaryIO): + """Write the ConstantPool to the file-like object `out`.""" + write = out.write + write(pack('>H', len(self) + 1)) + + for index, constant in sorted(self.pool.items()): + # Skip over double-width padding (Doubles & Longs) + if constant is None: + continue + write(constant.TAG.to_bytes(1, byteorder='big')) + write(constant.pack()) - def append(self, constant): - """ - Appends a new constant to the end of the pool. - """ - self._pool.append(constant) + def update_trackers(self): + """Update the internal tracking lists and counters to update free + indexes. - def __iter__(self): - for index, constant in enumerate(self._pool): - if constant is not None: - yield self.get(index) + .. note:: - def get(self, index): - """ - Returns the `Constant` at `index`, raising a KeyError if it - does not exist. + This is a fairly expensive operation, you should avoid calling it + except when necessary. If you are not updating the pool manually + you should never need to use this method. """ - constant = self._pool[index] - - # Doubles and Longs take two entries in the pool, and the second - # entry is always a None. - if constant is None: - raise ValueError( - f'Attempted to access a ConstantPool index containing a padding' - f' entry. This usually indicates an off-by-one bug. (index ==' - f' {index!r})' + if not self.pool: + self.sparse_map = deque() + return + + self.sparse_map = deque( + _missing_elements( + sorted(self.pool.keys()), + 0, + len(self.pool) - 1 ) + ) - # If the item in the pool hasn't already been resolved, do so now. - if not isinstance(constant, Constant): - constant_type = _constant_types[constant[0]] - if constant_type is None: - raise ValueError( - f'Attempted to access a ConstantPool index whose type is' - f' unknown (type == {constant[0]!r})' - ) + @property + def highest_unused_index(self) -> int: + return max(self.pool.keys(), default=0) + 1 + + def add(self, constant, index: int = None) -> int: + """Add a new entry to the constant pool. + + If no index is provided, this method will first attempt to fill in any + gaps in the constant pool. If no room is found, it'll instead append to + the end of the pool. + + :param constant: The constant to be added to the pool. + :param index: Optionally, an explicit index to use for the + new constant. [default: None] + :returns: The index used for the constant. + """ + if index is None and self.sparse_map: + # No explicit spot in the pool was requested, so lets try to find + # room for it. + desired_index = self.sparse_map.popleft() + if constant.TAG in (5, 6): + # ... however, we want to add a LONG or DOUBLE, so we + # really need two adjacent slots. + if desired_index + 1 not in self.pool: + index = desired_index + # Make sure it's removed from the sparse map if the + # neighbouring index happened to also be free. + try: + self.sparse_map.remove(desired_index + 1) + except ValueError: + pass + else: + # We can't use this slot, add it back to the sparse map, + # so it can be reused. + self.sparse_map.appendleft(desired_index) + else: + # Single-slot constant, we can put it anywhere. + index = desired_index + + # Still no usable index, append it to the pool instead. + if index is None: + index = self.highest_unused_index - constant = constant_type(self, index, *constant[1:]) - self._pool[index] = constant + self.pool[index] = constant + constant.index = index + constant.pool = self + if constant.TAG in (5, 6): + self.pool[index + 1] = None return constant - def __getitem__(self, idx): - return self.get(idx) + def remove(self, index: int): + """Remove the constant at `index` from the pool. + + ..note:: + + If the constant being removed at `index` is a LONG or DOUBLE, the + adjacent padding constant will also be removed. + + :param index: Index in the pool to be removed. + """ + const = self.pool.pop(index) + if const.TAG in (5, 6): + # If this was a double-width LONG or DOUBLE cleanup the adjacent + # padding. + del self.pool[index + 1] + self.update_trackers() + + def __iter__(self): + yield from ( + (k, v) for k, v in sorted(self.pool.items()) + if v is not None + ) + + def __getitem__(self, index): + return self.pool[index] - def __setitem__(self, idx, value): - self._pool[idx] = value + def __len__(self): + return sum(1 for v in self.pool.values() if v is not None) def find(self, type_=None, f=None): """ @@ -402,7 +645,7 @@ def find(self, type_=None, f=None): :param type_: Any subclass of :class:`Constant` or ``None``. :param f: Any callable which takes one argument (the constant). """ - for constant in self: + for index, constant in self: if type_ is not None and not isinstance(constant, type_): continue @@ -420,218 +663,3 @@ def find_one(self, *args, **kwargs): return next(self.find(*args, **kwargs)) except StopIteration: return None - - def create_utf8(self, value): - """ - Creates a new :class:`ConstantUTF8`, adding it to the pool and - returning it. - - :param value: The value of the new UTF8 string. - """ - self.append((1, value)) - return self.get(self.raw_count - 1) - - def create_integer(self, value: int) -> Integer: - """ - Creates a new :class:`ConstantInteger`, adding it to the pool and - returning it. - - :param value: The value of the new integer. - """ - self.append((3, value)) - return self.get(self.raw_count - 1) - - def create_float(self, value: float) -> Float: - """ - Creates a new :class:`ConstantFloat`, adding it to the pool and - returning it. - - :param value: The value of the new float. - """ - self.append((4, value)) - return self.get(self.raw_count - 1) - - def create_long(self, value: int) -> Long: - """ - Creates a new :class:`ConstantLong`, adding it to the pool and - returning it. - - :param value: The value of the new long. - """ - self.append((5, value)) - self.append(None) - return self.get(self.raw_count - 2) - - def create_double(self, value: float) -> Double: - """ - Creates a new :class:`ConstantDouble`, adding it to the pool and - returning it. - - :param value: The value of the new Double. - """ - self.append((6, value)) - self.append(None) - return self.get(self.raw_count - 2) - - def create_class(self, name: str) -> ConstantClass: - """ - Creates a new :class:`ConstantClass`, adding it to the pool and - returning it. - - :param name: The name of the new class. - """ - self.append(( - 7, - self.create_utf8(name).index - )) - return self.get(self.raw_count - 1) - - def create_string(self, value: str) -> String: - """ - Creates a new :class:`ConstantString`, adding it to the pool and - returning it. - - :param value: The value of the new string as a UTF8 string. - """ - self.append(( - 8, - self.create_utf8(value).index - )) - return self.get(self.raw_count - 1) - - def create_name_and_type(self, name: str, descriptor: str) -> NameAndType: - """ - Creates a new :class:`ConstantNameAndType`, adding it to the pool and - returning it. - - :param name: The name of the class. - :param descriptor: The descriptor for `name`. - """ - self.append(( - 12, - self.create_utf8(name).index, - self.create_utf8(descriptor).index - )) - return self.get(self.raw_count - 1) - - def create_field_ref(self, class_: str, field: str, descriptor: str) \ - -> FieldReference: - """ - Creates a new :class:`ConstantFieldRef`, adding it to the pool and - returning it. - - :param class_: The name of the class to which `field` belongs. - :param field: The name of the field. - :param descriptor: The descriptor for `field`. - """ - self.append(( - 9, - self.create_class(class_).index, - self.create_name_and_type(field, descriptor).index - )) - return self.get(self.raw_count - 1) - - def create_method_ref(self, class_: str, method: str, descriptor: str) \ - -> MethodReference: - """ - Creates a new :class:`ConstantMethodRef`, adding it to the pool and - returning it. - - :param class_: The name of the class to which `method` belongs. - :param method: The name of the method. - :param descriptor: The descriptor for `method`. - """ - self.append(( - 10, - self.create_class(class_).index, - self.create_name_and_type(method, descriptor).index - )) - return self.get(self.raw_count - 1) - - def create_interface_method_ref(self, class_: str, if_method: str, - descriptor: str) -> InterfaceMethodRef: - """ - Creates a new :class:`ConstantInterfaceMethodRef`, adding it to the - pool and returning it. - - :param class_: The name of the class to which `if_method` belongs. - :param if_method: The name of the interface method. - :param descriptor: The descriptor for `if_method`. - """ - self.append(( - 11, - self.create_class(class_).index, - self.create_name_and_type(if_method, descriptor).index - )) - return self.get(self.raw_count - 1) - - def unpack(self, fio): - """ - Read the ConstantPool from the file-like object `fio`. - - .. note:: - - Advanced usage only. You will typically never need to call this - method as it will be called for you when loading a ClassFile. - - :param fio: Any file-like object providing `read()` - """ - # Reads in the ConstantPool (constant_pool in the JVM Spec) - constant_pool_count = unpack('>H', fio.read(2))[0] - - # Pull this locally so CPython doesn't do a lookup each time. - read = fio.read - - while constant_pool_count > 1: - constant_pool_count -= 1 - # The 1-byte prefix identifies the type of constant. - tag = ord(read(1)) - - if tag == 1: - # CONSTANT_Utf8_info, a length prefixed mutf8 string. - utf8_str = read(unpack('>H', read(2))[0]) - utf8_str = decode_modified_utf8(utf8_str) - self.append((tag, utf8_str)) - else: - # Every other constant type is trivial. - fmt, size = _constant_fmts[tag] - self.append((tag, *unpack(fmt, read(size)))) - if tag == 5 or tag == 6: - # LONG (5) and DOUBLE (6) count as two entries in the - # pool. - self.append(None) - constant_pool_count -= 1 - - def pack(self, fout): - """ - Write the ConstantPool to the file-like object `fout`. - - .. note:: - - Advanced usage only. You will typically never need to call this - method as it will be calle=d for you when saving a ClassFile. - - :param fout: Any file-like object providing `write()` - """ - write = fout.write - write(pack('>H', self.raw_count)) - - for constant in self: - write(constant.pack()) - - def __len__(self) -> int: - """ - The number of `Constants` in the `ConstantPool`, excluding padding. - """ - count = 0 - for constant in self._pool: - if constant is not None: - count += 1 - return count - - @property - def raw_count(self) -> int: - """ - The number of `Constants` in the `ConstantPool`, including padding. - """ - return len(self._pool) diff --git a/lawu/context.py b/lawu/context.py new file mode 100644 index 0000000..4b3b1d3 --- /dev/null +++ b/lawu/context.py @@ -0,0 +1,18 @@ +from contextvars import ContextVar + +_class_context: ContextVar = ContextVar('class_context') + + +def class_context(): + class_stack = _class_context.get(None) + if class_stack is None: + lst = [] + _class_context.set(lst) + return lst + return class_stack + + +def current_class_context(): + class_stack = class_context() + if class_stack: + return class_stack.pop() diff --git a/lawu/fields.py b/lawu/fields.py index 7c79297..450a690 100644 --- a/lawu/fields.py +++ b/lawu/fields.py @@ -114,13 +114,13 @@ def create(self, name: str, descriptor: str, value: Constant = None) \ Creates a new field from `name` and `descriptor`. For example:: >>> from lawu.cf import ClassFile - >>> cf = ClassFile.create('BeerCounter') + >>> cf = ClassFile(this='BeerCounter') >>> field = cf.fields.create('BeerCount', 'I') To automatically create a static field, pass a value:: >>> from lawu.cf import ClassFile - >>> cf = ClassFile.create('BeerCounter') + >>> cf = ClassFile(this='BeerCounter') >>> field = cf.fields.create( ... 'MaxBeer', ... 'I', diff --git a/lawu/methods.py b/lawu/methods.py index f986a37..f2ffbc1 100644 --- a/lawu/methods.py +++ b/lawu/methods.py @@ -133,10 +133,10 @@ def create(self, name: str, descriptor: str, ``None``, add a `Code` attribute to this method. """ method = Method(self._cf) - name = self._cf.constants.create_utf8(name) - descriptor = self._cf.constants.create_utf8(descriptor) - method._name_index = name.index - method._descriptor_index = descriptor.index + with self._cf: + method._name_index = UTF8(value=name).index + method._descriptor_index = UTF8(value=descriptor).index + method.access_flags.PUBLIC = True if code is not None: diff --git a/tests/attributes/test_exceptions_attribute.py b/tests/attributes/test_exceptions_attribute.py index 6b6d002..ff68c5e 100644 --- a/tests/attributes/test_exceptions_attribute.py +++ b/tests/attributes/test_exceptions_attribute.py @@ -1,3 +1,6 @@ +from lawu.constants import ConstantClass + + def test_exceptions_read(loader): cf = loader['ExceptionsTest'] @@ -12,16 +15,14 @@ def test_exceptions_read(loader): def test_exceptions_write(loader): cf = loader['ExceptionsTest'] + with cf: + m = cf.methods.find_one(name='test') + a = m.attributes.find_one(name='Exceptions') - m = cf.methods.find_one(name='test') - a = m.attributes.find_one(name='Exceptions') - - assert a.pack() == b'\x00\x01\x00\x0A' + assert a.pack() == b'\x00\x01\x00\x0A' - a.exceptions.append( - cf.constants.create_class( - name=u'java/lang/TestException' - ).index - ) + a.exceptions.append( + ConstantClass(name='java/lang/TestException').index + ) - assert a.pack() == b'\x00\x02\x00\x0A\x00\x12' + assert a.pack() == b'\x00\x02\x00\x0A\x00\x11' diff --git a/tests/attributes/test_sourcefile_attribute.py b/tests/attributes/test_sourcefile_attribute.py index 2b19107..cc03336 100644 --- a/tests/attributes/test_sourcefile_attribute.py +++ b/tests/attributes/test_sourcefile_attribute.py @@ -2,6 +2,7 @@ from lawu.cf import ClassFile from lawu.attributes.source_file import SourceFileAttribute +from lawu.constants import UTF8 def test_sourcefile_read(loader): @@ -17,10 +18,11 @@ def test_sourcefile_write(): """ Ensure SourceFileAttribute can be written and read back. """ - cf_one = ClassFile.create(u'SourceFileTest') + cf_one = ClassFile(this='SourceFileTest') - sfa = cf_one.attributes.create(SourceFileAttribute) - sfa.source_file = cf_one.constants.create_utf8(u'SourceFileTest.java') + with cf_one: + sfa = cf_one.attributes.create(SourceFileAttribute) + sfa.source_file = UTF8(value='SourceFileTest.java') fout = BytesIO() cf_one.save(fout) @@ -28,5 +30,5 @@ def test_sourcefile_write(): fin = BytesIO(fout.getvalue()) cf_two = ClassFile(fin) - source_file = cf_two.attributes.find_one(name=u'SourceFile') - assert(source_file.source_file.value == u'SourceFileTest.java') + source_file = cf_two.attributes.find_one(name='SourceFile') + assert(source_file.source_file.value == 'SourceFileTest.java') diff --git a/tests/test_classloader.py b/tests/test_classloader.py index 2a444b3..fc04305 100644 --- a/tests/test_classloader.py +++ b/tests/test_classloader.py @@ -13,7 +13,7 @@ def test_load_from_class(): """Ensure we can add ClassFile's directly to the ClassLoader.""" cl = ClassLoader() - cf = ClassFile.create('TestClass') + cf = ClassFile(this='TestClass') cl.update(cf) assert cl.load('TestClass') is cf @@ -22,7 +22,7 @@ def test_load_from_class(): def test_default_bytecode_transforms(): cl = ClassLoader(bytecode_transforms=[simple_swap]) - cf = ClassFile.create('TestClass') + cf = ClassFile(this='TestClass') cl.update(cf) test_method = cf.methods.create('test', '(V)V;', code=True) diff --git a/tests/test_printable.py b/tests/test_printable.py deleted file mode 100644 index 77feb69..0000000 --- a/tests/test_printable.py +++ /dev/null @@ -1,28 +0,0 @@ -from lawu.cf import ClassFile -from lawu.constants import ConstantPool - - -def test_printable_constants(): - # Ensure we can successfully repr valid constants without crashing. - pool = ConstantPool() - repr(pool.create_utf8('HelloWorld')) - repr(pool.create_class('HelloWorld')) - repr(pool.create_double(1)) - repr(pool.create_float(1)) - repr(pool.create_integer(1)) - repr(pool.create_long(1)) - repr(pool.create_name_and_type('HelloWorld', 'I')) - repr(pool.create_field_ref('HelloWorld', 'test', 'I')) - repr(pool.create_method_ref('HelloWorld', 'test', 'I)V')) - repr(pool.create_interface_method_ref( - 'HelloWorld', - 'test', - 'I)V' - )) - repr(pool.create_string('HelloWorld')) - - -def test_printable_classes(): - cf = ClassFile.create('HelloWorld') - assert repr(cf) == '' - assert repr(cf.version) == 'ClassVersion(major=50, minor=0)' diff --git a/tests/test_tracer.py b/tests/test_tracer.py deleted file mode 100644 index e69de29..0000000 From 93877724178eaf9a924dd2e3d135e29903fce8af Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Mon, 15 Jul 2024 08:32:15 -0400 Subject: [PATCH 6/9] Delete CNAME --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index 3a623fb..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -lawu.dev From 8dca76e919d6c016d3240994d0c605fd5a34799a Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Sat, 12 Jul 2025 21:44:38 -0400 Subject: [PATCH 7/9] Switch to uv/pyproject.toml, begin modernizing. --- .gitignore | 9 +++++++- README.md | 2 +- pyproject.toml | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 58 -------------------------------------------------- 4 files changed, 67 insertions(+), 60 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index b8db0ff..744e71b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,11 @@ test.py *.pyc docs/_build/ .DS_Store -*.un~ \ No newline at end of file +*.un~ +venv +.vscode +*.egg-info +dist +build/ +.venv +.idea \ No newline at end of file diff --git a/README.md b/README.md index 7c86cc6..08a0b88 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ JVM class files. It's highly suitable for automation tasks. ## Documentation -API documentation & examples are available at https://lawu.dev +API documentation & examples are available at https://tkte.ch/lawu/ ## Licence diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4668f09 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "lawu" +version = "3.0.0" +description = "Doing fun stuff with JVM ClassFiles." +readme = "README.md" +authors = [ + { name = "Tyler Kennedy", email = "tk@tkte.ch" } +] +license = { text = "MIT" } +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Topic :: Software Development :: Disassemblers", +] +keywords = ["java", "disassembly", "disassembler", "assembly"] +dependencies = [ + "mutf8", +] + +[project.urls] +Homepage = "https://tkte.ch/lawu/" + +[project.optional-dependencies] +dev = [ + "pytest", + "pytest-cov", + "sphinx", + "sphinxcontrib-googleanalytics", + "sphinx-click", + "furo", + "ghp-import", + "pyyaml", + "twine", + "wheel", +] +cli = [ + "click", + "rich", +] + +[project.scripts] +lawu = "lawu.cli:cli" + +[tool.pytest.ini_options] +addopts = "--cov=lawu --cov-report=term-missing --cov-fail-under=100" + +[tool.coverage.run] +omit = [ + "lawu/util/shell.py", +] \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 6659d08..0000000 --- a/setup.py +++ /dev/null @@ -1,58 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name='lawu', - packages=find_packages(), - version='3.0.0', - python_requires='>=3.7', - description='Doing fun stuff with JVM ClassFiles.', - long_description=open('README.md', 'r').read(), - long_description_content_type='text/markdown', - author='Tyler Kennedy', - author_email='tk@tkte.ch', - url='https://lawu.dev', - keywords=[ - 'java', - 'disassembly', - 'disassembler', - 'assembly' - ], - include_package_data=True, - classifiers=[ - 'Programming Language :: Python', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Disassemblers' - ], - tests_require=[ - 'pytest>=2.10', - 'pytest-cov' - ], - install_requires=[ - 'mutf8' - ], - extras_require={ - 'dev': [ - 'pytest', - 'pytest-cov', - 'sphinx', - 'sphinxcontrib-googleanalytics', - 'sphinx-click', - 'furo', - 'ghp-import', - 'pyyaml', - 'twine', - 'wheel' - ], - 'cli': [ - 'click', - 'rich' - ] - }, - entry_points=''' - [console_scripts] - lawu=lawu.cli:cli - ''' -) From e49635d9fbb07576a9341e3093babc00d3ba0616 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Sun, 13 Jul 2025 02:53:20 -0400 Subject: [PATCH 8/9] Switch to our semi-standard release process. --- .github/workflows/release.yml | 148 ++++++++++++++++++++++++++++++++++ pyproject.toml | 6 +- 2 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..94fd905 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,148 @@ +name: Test and Release + +on: + release: + types: + - published + pull_request: + push: + branches: + - main + - develop + +jobs: + build: + name: Build Package + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.6 + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: "3.13" + + - name: Install uv + run: pip install uv + + - name: Checking for code smells + run: uvx ruff check + + - name: Checking for formatting issues + run: uvx ruff format --check + + - name: Build package + run: uv build + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: wheel-package + path: dist/*.whl + retention-days: 1 + + - name: Upload source artifact + uses: actions/upload-artifact@v4 + with: + name: source-package + path: dist/*.tar.gz + retention-days: 1 + + test: + name: "${{ matrix.os }} - Py${{ matrix.python-version }}" + needs: [build] + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: true + matrix: + python-version: ["3.11", "3.12", "3.13"] + os: ["ubuntu-24.04"] + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python-version }} + + steps: + - uses: actions/checkout@v4.1.6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5.1.0 + with: + python-version: ${{ matrix.python-version }} + + - name: Installing uv + run: pip install uv + + - name: Installing dependencies (Python) + run: uv sync --all-extras + + - name: Running tests + run: | + uv run pytest -s -vvvvv --cov=lawu --cov-report=xml + + - name: Uploading coverage + uses: codecov/codecov-action@v4 + with: + env_vars: OS,PYTHON + fail_ci_if_error: true + flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + slug: TkTech/lawu + + docs: + name: Building Documentation + needs: [build] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.6 + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: "3.13" + + - name: Installing uv + run: pip install uv + + - name: Installing dependencies (Python) + run: uv sync --all-extras + + - name: Building docs + run: | + cd docs && uv run make clean html + + - name: Publishing documentation + if: github.event_name == 'release' + run: | + uv run ghp-import -f -n -p docs/_build/html + + release: + name: Release to PyPI + needs: [test, docs] + runs-on: ubuntu-latest + if: github.event_name == 'release' + permissions: + contents: read + id-token: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: dist + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: "3.13" + + - name: Install uv + run: pip install uv + + - name: Publishing to PyPI + run: | + uv publish \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4668f09..52b136f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,14 +4,14 @@ build-backend = "hatchling.build" [project] name = "lawu" -version = "3.0.0" +version = "4.0.0" description = "Doing fun stuff with JVM ClassFiles." readme = "README.md" authors = [ { name = "Tyler Kennedy", email = "tk@tkte.ch" } ] license = { text = "MIT" } -requires-python = ">=3.7" +requires-python = ">=3.11" classifiers = [ "Programming Language :: Python", "License :: OSI Approved :: MIT License", @@ -50,7 +50,7 @@ cli = [ lawu = "lawu.cli:cli" [tool.pytest.ini_options] -addopts = "--cov=lawu --cov-report=term-missing --cov-fail-under=100" +addopts = "--cov=lawu --cov-report=term-missing" [tool.coverage.run] omit = [ From c0625b5bae331a497456546ac86de9110532c523 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Mon, 14 Jul 2025 01:04:21 -0400 Subject: [PATCH 9/9] Switch to ruff linting. --- examples/creating_fields.py | 13 +- examples/disassemble.py | 70 ++++--- examples/hello_world.py | 41 ++-- lawu/assemble.py | 28 +-- lawu/attribute.py | 26 +-- lawu/attributes/__init__.py | 2 +- lawu/attributes/bootstrap.py | 42 ++-- lawu/attributes/code.py | 51 ++--- lawu/attributes/constant_value.py | 9 +- lawu/attributes/deprecated.py | 9 +- lawu/attributes/enclosing_method.py | 9 +- lawu/attributes/exceptions.py | 17 +- lawu/attributes/inner_classes.py | 28 +-- lawu/attributes/line_number_table.py | 22 +- lawu/attributes/local_variable.py | 28 +-- lawu/attributes/local_variable_type.py | 29 ++- lawu/attributes/signature.py | 9 +- lawu/attributes/source_file.py | 10 +- lawu/attributes/stack_map_table.py | 85 +++----- lawu/attributes/synthetic.py | 7 +- lawu/cf.py | 94 ++++----- lawu/classloader.py | 43 ++-- lawu/constants.py | 188 +++++++----------- lawu/context.py | 2 +- lawu/fields.py | 26 ++- lawu/methods.py | 38 ++-- lawu/transforms.py | 19 +- lawu/util/__init__.py | 2 +- lawu/util/bytecode.py | 125 ++++++------ lawu/util/descriptor.py | 56 +++--- lawu/util/shell.py | 2 +- lawu/util/stream.py | 10 +- lawu/util/verifier.py | 2 - tests/attributes/test_enclosing_method.py | 14 +- tests/attributes/test_exceptions_attribute.py | 26 +-- tests/attributes/test_general_attributes.py | 15 +- tests/attributes/test_inner_classes.py | 12 +- .../attributes/test_line_number_attribute.py | 14 +- tests/attributes/test_sourcefile_attribute.py | 14 +- tests/attributes/test_stack_map_attribute.py | 14 +- tests/conftest.py | 4 +- tests/test_bytecode.py | 71 ++++--- tests/test_classloader.py | 57 ++---- tests/test_expand_constants.py | 4 +- tests/test_hello_world.py | 30 +-- 45 files changed, 618 insertions(+), 799 deletions(-) diff --git a/examples/creating_fields.py b/examples/creating_fields.py index 0ac6ad9..f41a62e 100644 --- a/examples/creating_fields.py +++ b/examples/creating_fields.py @@ -1,22 +1,21 @@ """ An example showing how to create fields on a new class. """ + from lawu.cf import ClassFile from lawu.constants import String -if __name__ == '__main__': - cf = ClassFile(this='HelloWorld') +if __name__ == "__main__": + cf = ClassFile(this="HelloWorld") with cf: # Creating a field from a field name and descriptor - field = cf.fields.create('BeerCount', 'I') + field = cf.fields.create("BeerCount", "I") # A convenience shortcut for creating static fields. field = cf.fields.create_static( - 'HelloWorld', - 'Ljava/lang/String;', - String('Hello World!') + "HelloWorld", "Ljava/lang/String;", String("Hello World!") ) - with open('HelloWorld.class', 'wb') as fout: + with open("HelloWorld.class", "wb") as fout: cf.save(fout) diff --git a/examples/disassemble.py b/examples/disassemble.py index 964d665..3da6fe0 100644 --- a/examples/disassemble.py +++ b/examples/disassemble.py @@ -5,71 +5,69 @@ @click.command() -@click.option('--class-path', multiple=True, type=click.Path(exists=True)) -@click.argument('classes', nargs=-1) +@click.option("--class-path", multiple=True, type=click.Path(exists=True)) +@click.argument("classes", nargs=-1) def main(class_path, classes): loader = ClassLoader(*class_path) for class_ in classes: cf = loader[class_] # The constant pool. - print('; {0:->60}'.format(' constant pool')) - print('; {0:->60}'.format( - ' total: {0}'.format(len(cf.constants)) - )) + print("; {0:->60}".format(" constant pool")) + print("; {0:->60}".format(" total: {0}".format(len(cf.constants)))) for constant in cf.constants: - print('; {0:04}: {1!r}'.format(constant.index, constant)) + print("; {0:04}: {1!r}".format(constant.index, constant)) # The fields table. - print('; {0:->60}'.format(' fields')) - print('; {0:->60}'.format( - ' total: {0}'.format(len(cf.fields)) - )) + print("; {0:->60}".format(" fields")) + print("; {0:->60}".format(" total: {0}".format(len(cf.fields)))) for field in cf.fields: - print('; {0!r}'.format(field)) + print("; {0!r}".format(field)) # The methods table. - print('; {0:->60}'.format(' methods')) - print('; {0:->60}'.format( - ' total: {0}'.format(len(cf.methods)) - )) + print("; {0:->60}".format(" methods")) + print("; {0:->60}".format(" total: {0}".format(len(cf.methods)))) for method in cf.methods: # Find all enabled flags and print them out (such as acc_public # and acc_private) flags = method.access_flags.to_dict() flags = [k for k, v in flags.items() if v] - print('{0} {1} {2}({3}) {{'.format( - ' '.join(flags), - method.returns.name, - method.name.value, - ', '.join(a.name + ('[]' * a.dimensions) for a in method.args) - ).strip()) + print( + "{0} {1} {2}({3}) {{".format( + " ".join(flags), + method.returns.name, + method.name.value, + ", ".join(a.name + ("[]" * a.dimensions) for a in method.args), + ).strip() + ) if method.code: for ins in method.code.disassemble(): line = [ - f'{ins.pos:04}', - f'[0x{ins.opcode:02X}]', - f'{ins.mnemonic:>15} <-' + f"{ins.pos:04}", + f"[0x{ins.opcode:02X}]", + f"{ins.mnemonic:>15} <-", ] for operand in ins.operands: if isinstance(operand, dict): - line.append(f'JT[{operand!r}]') + line.append(f"JT[{operand!r}]") continue - line.append({ - OperandTypes.CONSTANT_INDEX: f'C[{operand.value}]', - OperandTypes.BRANCH: f'J[{operand.value}]', - OperandTypes.LITERAL: f'#[{operand.value}]', - OperandTypes.LOCAL_INDEX: f'L[{operand.value}]', - OperandTypes.PADDING: 'P' - }[operand.op_type]) + line.append( + { + OperandTypes.CONSTANT_INDEX: f"C[{operand.value}]", + OperandTypes.BRANCH: f"J[{operand.value}]", + OperandTypes.LITERAL: f"#[{operand.value}]", + OperandTypes.LOCAL_INDEX: f"L[{operand.value}]", + OperandTypes.PADDING: "P", + }[operand.op_type] + ) - print(' ' + ' '.join(line)) - print('}') + print(" " + " ".join(line)) + print("}") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/hello_world.py b/examples/hello_world.py index c0daba7..8dbbdc3 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -1,32 +1,39 @@ """ An example showing how to create a "Hello World" class from scratch. """ + from lawu.cf import ClassFile from lawu.assemble import assemble from lawu.constants import String -cf = ClassFile(this='HelloWorld') +cf = ClassFile(this="HelloWorld") -main = cf.methods.create('main', '([Ljava/lang/String;)V', code=True) +main = cf.methods.create("main", "([Ljava/lang/String;)V", code=True) main.access_flags.acc_static = True main.code.max_locals = 1 main.code.max_stack = 2 with cf: - main.code.assemble(assemble([ - ('getstatic', cf.constants.create_field_ref( - 'java/lang/System', - 'out', - 'Ljava/io/PrintStream;' - )), - ('ldc', String('Hello World!')), - ('invokevirtual', cf.constants.create_method_ref( - 'java/io/PrintStream', - 'println', - '(Ljava/lang/String;)V' - )), - ('return',) - ])) + main.code.assemble( + assemble( + [ + ( + "getstatic", + cf.constants.create_field_ref( + "java/lang/System", "out", "Ljava/io/PrintStream;" + ), + ), + ("ldc", String("Hello World!")), + ( + "invokevirtual", + cf.constants.create_method_ref( + "java/io/PrintStream", "println", "(Ljava/lang/String;)V" + ), + ), + ("return",), + ] + ) + ) -with open('HelloWorld.class', 'wb') as fout: +with open("HelloWorld.class", "wb") as fout: cf.save(fout) diff --git a/lawu/assemble.py b/lawu/assemble.py index 5d02fc8..dbbc5d4 100644 --- a/lawu/assemble.py +++ b/lawu/assemble.py @@ -1,15 +1,10 @@ from collections import namedtuple from lawu.constants import Constant -from lawu.util.bytecode import ( - Operand, - OperandTypes, - Instruction, - opcode_table -) +from lawu.util.bytecode import Operand, OperandTypes, Instruction, opcode_table -Label = namedtuple('Label', ['name']) +Label = namedtuple("Label", ["name"]) def assemble(code): @@ -45,7 +40,7 @@ def assemble(code): continue mnemonic, operands = line[0], line[1:] - operand_fmts = opcode_table[mnemonic]['operands'] + operand_fmts = opcode_table[mnemonic]["operands"] # We need to coerce each opcodes operands into their # final `Operand` form. @@ -56,10 +51,9 @@ def assemble(code): final_operands.append(operand) elif isinstance(operand, Constant): # Convert constants into CONSTANT_INDEX'es - final_operands.append(Operand( - OperandTypes.CONSTANT_INDEX, - operand.index - )) + final_operands.append( + Operand(OperandTypes.CONSTANT_INDEX, operand.index) + ) elif isinstance(operand, dict): # lookupswitch's operand is a dict as # a special usability case. @@ -69,10 +63,7 @@ def assemble(code): else: # For anything else, lookup that opcode's operand # type from its definition. - final_operands.append(Operand( - operand_fmts[i][1], - operand - )) + final_operands.append(Operand(operand_fmts[i][1], operand)) # Build the final, immutable `Instruction`. final.append(Instruction.create(mnemonic, final_operands)) @@ -105,10 +96,7 @@ def assemble(code): if isinstance(v, Label): operand[k] = Operand(40, label_pcs[v.name] - current_pc) elif isinstance(operand, Label): - ins.operands[i] = Operand( - 40, - label_pcs[operand.name] - current_pc - ) + ins.operands[i] = Operand(40, label_pcs[operand.name] - current_pc) current_pc += ins.size_on_disk(current_pc) diff --git a/lawu/attribute.py b/lawu/attribute.py index 48a0076..29ededb 100644 --- a/lawu/attribute.py +++ b/lawu/attribute.py @@ -13,7 +13,7 @@ class Attribute(object): ADDED_IN: int = None MINIMUM_CLASS_VERSION: Tuple[int, int] = None - def __init__(self, parent: 'AttributeTable', name_index: int): + def __init__(self, parent: "AttributeTable", name_index: int): self.parent = parent self.name_index = name_index @@ -45,7 +45,7 @@ def pack(self) -> bytes: class UnknownAttribute(Attribute): - def __init__(self, parent: 'AttributeTable', name_index: int): + def __init__(self, parent: "AttributeTable", name_index: int): super().__init__(parent, name_index) self.info = None @@ -75,9 +75,9 @@ def unpack(self, source: BinaryIO): :param source: Any file-like object providing `read()` """ - count = unpack('>H', source.read(2))[0] + count = unpack(">H", source.read(2))[0] for _ in repeat(None, count): - name_index, length = unpack('>HI', source.read(6)) + name_index, length = unpack(">HI", source.read(6)) info_blob = source.read(length) self._table.append((name_index, info_blob)) @@ -108,14 +108,10 @@ def pack(self, out: BinaryIO): :param out: Any file-like object providing `write()` """ - out.write(pack('>H', len(self._table))) + out.write(pack(">H", len(self._table))) for attribute in self: info = attribute.pack() - out.write(pack( - '>HI', - attribute.name.index, - len(info) - )) + out.write(pack(">HI", attribute.name.index, len(info))) out.write(info) def create(self, type_, *args, **kwargs) -> Any: @@ -160,8 +156,7 @@ def get_attribute_classes() -> Dict[str, Attribute]: Lookup all builtin Attribute subclasses, load them, and return a dict """ attribute_children = pkgutil.iter_modules( - importlib.import_module('lawu.attributes').__path__, - prefix='lawu.attributes.' + importlib.import_module("lawu.attributes").__path__, prefix="lawu.attributes." ) result = {} @@ -169,13 +164,12 @@ def get_attribute_classes() -> Dict[str, Attribute]: classes = inspect.getmembers( importlib.import_module(name), lambda c: ( - inspect.isclass(c) and issubclass(c, Attribute) and - c is not Attribute - ) + inspect.isclass(c) and issubclass(c, Attribute) and c is not Attribute + ), ) for class_name, class_ in classes: - attribute_name = getattr(class_, 'ATTRIBUTE_NAME', class_name[:-9]) + attribute_name = getattr(class_, "ATTRIBUTE_NAME", class_name[:-9]) result[attribute_name] = class_ return result diff --git a/lawu/attributes/__init__.py b/lawu/attributes/__init__.py index f51921b..20555e9 100644 --- a/lawu/attributes/__init__.py +++ b/lawu/attributes/__init__.py @@ -11,4 +11,4 @@ is created instead. This UnknownAttribute retains the name and content of the original attribute, allowing you to parse it yourself or to simply pass it through. -""" \ No newline at end of file +""" diff --git a/lawu/attributes/bootstrap.py b/lawu/attributes/bootstrap.py index 8e7e389..c75fede 100644 --- a/lawu/attributes/bootstrap.py +++ b/lawu/attributes/bootstrap.py @@ -5,44 +5,37 @@ from lawu.attribute import Attribute -BootstrapMethod = namedtuple( - 'BootstrapMethod', - ['method_ref', 'bootstrap_args'] -) +BootstrapMethod = namedtuple("BootstrapMethod", ["method_ref", "bootstrap_args"]) class BootstrapMethodsAttribute(Attribute): - ADDED_IN = '7' + ADDED_IN = "7" MINIMUM_CLASS_VERSION = (51, 0) def __init__(self, table, name_index=None): super(BootstrapMethodsAttribute, self).__init__( table, - name_index or table.cf.constants.create_utf8( - 'BootstrapMethods' - ).index + name_index or table.cf.constants.create_utf8("BootstrapMethods").index, ) self.table = [] def __repr__(self): - return ''.format( - self=self - ) + return "".format(self=self) def pack(self): out = io.BytesIO() - out.write(pack('>H', len(self.table))) + out.write(pack(">H", len(self.table))) for table_entry in self.table: - out.write(pack( - '>HH', - table_entry.method_ref, - len(table_entry.bootstrap_args) - )) - out.write(pack( - '>{0}H'.format(len(table_entry.bootstrap_args)), - table_entry.bootstrap_args - )) + out.write( + pack(">HH", table_entry.method_ref, len(table_entry.bootstrap_args)) + ) + out.write( + pack( + ">{0}H".format(len(table_entry.bootstrap_args)), + table_entry.bootstrap_args, + ) + ) return out.getvalue() @@ -50,7 +43,6 @@ def unpack(self, info): length = info.u2() for _ in repeat(None, length): - self.table.append(BootstrapMethod( - info.u2(), - info.unpack('>{0}H'.format(info.u2())) - )) + self.table.append( + BootstrapMethod(info.u2(), info.unpack(">{0}H".format(info.u2()))) + ) diff --git a/lawu/attributes/code.py b/lawu/attributes/code.py index 10fa878..16e6dea 100644 --- a/lawu/attributes/code.py +++ b/lawu/attributes/code.py @@ -8,15 +8,11 @@ from lawu.constants import UTF8 from lawu.attribute import Attribute, AttributeTable -from lawu.util.bytecode import ( - read_instruction, - write_instruction, - Instruction -) +from lawu.util.bytecode import read_instruction, write_instruction, Instruction -CodeException = namedtuple('CodeException', [ - 'start_pc', 'end_pc', 'handler_pc', 'catch_type' -]) +CodeException = namedtuple( + "CodeException", ["start_pc", "end_pc", "handler_pc", "catch_type"] +) class CodeAttribute(Attribute): @@ -52,22 +48,19 @@ class CodeAttribute(Attribute): with open('HelloWorld.class', 'wb') as fout: cf.save(fout) """ - ADDED_IN = '1.0.2' + + ADDED_IN = "1.0.2" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super().__init__( - table, - name_index or UTF8( - pool=table.cf.constants, - value='Code' - ).index + table, name_index or UTF8(pool=table.cf.constants, value="Code").index ) self.max_stack = 0 self.max_locals = 0 self.exception_table = [] self.attributes = AttributeTable(table.cf, parent=self) - self._code = b'' + self._code = b"" def unpack(self, info): """ @@ -80,15 +73,13 @@ def unpack(self, info): :param info: A byte string containing an unparsed CodeAttribute. """ - self.max_stack, self.max_locals, c_len = info.unpack('>HHI') + self.max_stack, self.max_locals, c_len = info.unpack(">HHI") self._code = info.read(c_len) # The exception table ex_table_len = info.u2() for _ in repeat(None, ex_table_len): - self.exception_table.append(CodeException( - *info.unpack('>HHHH') - )) + self.exception_table.append(CodeException(*info.unpack(">HHHH"))) self.attributes = AttributeTable(self.cf, parent=self) self.attributes.unpack(info) @@ -98,17 +89,14 @@ def pack(self): The `CodeAttribute` in packed byte string form. """ with io.BytesIO() as file_out: - file_out.write(pack( - '>HHI', - self.max_stack, - self.max_locals, - len(self._code) - )) + file_out.write( + pack(">HHI", self.max_stack, self.max_locals, len(self._code)) + ) file_out.write(self._code) - file_out.write(pack('>H', len(self.exception_table))) + file_out.write(pack(">H", len(self.exception_table))) for exception in self.exception_table: - file_out.write(pack('>HHHH', *exception)) + file_out.write(pack(">HHHH", *exception)) self.attributes.pack(file_out) return file_out.getvalue() @@ -147,8 +135,9 @@ def _bind_transform(self, transform): sig = inspect.signature(transform, follow_wrapped=True) return functools.partial( transform, - **{k: v for k, v in { - 'cf': self.cf, - 'attribute': self - }.items() if k in sig.parameters} + **{ + k: v + for k, v in {"cf": self.cf, "attribute": self}.items() + if k in sig.parameters + }, ) diff --git a/lawu/attributes/constant_value.py b/lawu/attributes/constant_value.py index c4a7e43..1ade4dd 100644 --- a/lawu/attributes/constant_value.py +++ b/lawu/attributes/constant_value.py @@ -3,15 +3,12 @@ class ConstantValueAttribute(Attribute): - ADDED_IN = '1.0.2' + ADDED_IN = "1.0.2" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, value=None, name_index=None): super(ConstantValueAttribute, self).__init__( - table, - name_index or table.cf.constants.create_utf8( - 'ConstantValue' - ).index + table, name_index or table.cf.constants.create_utf8("ConstantValue").index ) self._constant_value_index = value.index if value else None @@ -19,7 +16,7 @@ def unpack(self, info): self._constant_value_index = info.u2() def pack(self): - return pack('>H', self._constant_value_index) + return pack(">H", self._constant_value_index) @property def constant_value(self): diff --git a/lawu/attributes/deprecated.py b/lawu/attributes/deprecated.py index c02d724..47c0f99 100644 --- a/lawu/attributes/deprecated.py +++ b/lawu/attributes/deprecated.py @@ -2,19 +2,16 @@ class DeprecatedAttribute(Attribute): - ADDED_IN = '1.1.0' + ADDED_IN = "1.1.0" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super(DeprecatedAttribute, self).__init__( - table, - name_index or table.cf.constants.create_utf8( - 'Deprecated' - ).index + table, name_index or table.cf.constants.create_utf8("Deprecated").index ) def __repr__(self): - return '' + return "" def pack(self): pass diff --git a/lawu/attributes/enclosing_method.py b/lawu/attributes/enclosing_method.py index 0b20146..0bb6b5d 100644 --- a/lawu/attributes/enclosing_method.py +++ b/lawu/attributes/enclosing_method.py @@ -4,21 +4,18 @@ class EnclosingMethodAttribute(Attribute): - ADDED_IN = '5.0.0' + ADDED_IN = "5.0.0" MINIMUM_CLASS_VERSION = (49, 0) def __init__(self, table, name_index=None): super().__init__( - table, - name_index or table.cf.constants.create_utf8( - 'EnclosingMethod' - ).index + table, name_index or table.cf.constants.create_utf8("EnclosingMethod").index ) self.class_index = None self.method_index = None def pack(self): - return pack('>HH', self.class_index, self.method_index) + return pack(">HH", self.class_index, self.method_index) def unpack(self, info): self.class_index = info.u2() diff --git a/lawu/attributes/exceptions.py b/lawu/attributes/exceptions.py index 4225202..2d6607c 100644 --- a/lawu/attributes/exceptions.py +++ b/lawu/attributes/exceptions.py @@ -4,30 +4,25 @@ class ExceptionsAttribute(Attribute): - ADDED_IN = '1.0.2' + ADDED_IN = "1.0.2" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super(ExceptionsAttribute, self).__init__( - table, - name_index or table.cf.constants.create_utf8( - 'Exceptions' - ).index + table, name_index or table.cf.constants.create_utf8("Exceptions").index ) self.exceptions = [] def unpack(self, info): length = info.u2() - self.exceptions = list(info.unpack('>{0}H'.format(length))) + self.exceptions = list(info.unpack(">{0}H".format(length))) def pack(self): return pack( - '>H{0}H'.format( - len(self.exceptions) - ), + ">H{0}H".format(len(self.exceptions)), len(self.exceptions), - *self.exceptions + *self.exceptions, ) def __repr__(self): - return ''.format(self.exceptions) + return "".format(self.exceptions) diff --git a/lawu/attributes/inner_classes.py b/lawu/attributes/inner_classes.py index bdeb52b..6dcb05c 100644 --- a/lawu/attributes/inner_classes.py +++ b/lawu/attributes/inner_classes.py @@ -5,34 +5,34 @@ from lawu.attribute import Attribute -InnerClass = namedtuple('InnerClass', [ - 'inner_class_info_index', - 'outer_class_info_index', - 'inner_name_index', - 'inner_class_access_flags' -]) +InnerClass = namedtuple( + "InnerClass", + [ + "inner_class_info_index", + "outer_class_info_index", + "inner_name_index", + "inner_class_access_flags", + ], +) class InnerClassesAttribute(Attribute): - ADDED_IN = '1.1.0' + ADDED_IN = "1.1.0" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index): super().__init__( - table, - name_index or table.cf.constants.create_utf8( - 'InnerClasses' - ).index + table, name_index or table.cf.constants.create_utf8("InnerClasses").index ) self.inner_classes = [] def unpack(self, info): for _ in repeat(None, info.u2()): - self.inner_classes.append(InnerClass(*info.unpack('>HHHH'))) + self.inner_classes.append(InnerClass(*info.unpack(">HHHH"))) def pack(self): with io.BytesIO() as out: - out.write(pack('>H', len(self.inner_classes))) + out.write(pack(">H", len(self.inner_classes))) for inner_class in self.inner_classes: - out.write(pack('>HHHH', *inner_class)) + out.write(pack(">HHHH", *inner_class)) return out.getvalue() diff --git a/lawu/attributes/line_number_table.py b/lawu/attributes/line_number_table.py index 0099857..077689d 100644 --- a/lawu/attributes/line_number_table.py +++ b/lawu/attributes/line_number_table.py @@ -4,37 +4,31 @@ from lawu.attribute import Attribute -line_number_entry = namedtuple('line_number_entry', 'start_pc line_number') +line_number_entry = namedtuple("line_number_entry", "start_pc line_number") class LineNumberTableAttribute(Attribute): - ADDED_IN = '1.0.2' + ADDED_IN = "1.0.2" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super(LineNumberTableAttribute, self).__init__( - table, - name_index or table.cf.constants.create_utf8( - 'LineNumberTable' - ).index + table, name_index or table.cf.constants.create_utf8("LineNumberTable").index ) self.line_no = [] def unpack(self, info): length = info.u2() - table = info.unpack('>{0}H'.format(length * 2)) + table = info.unpack(">{0}H".format(length * 2)) - self.line_no = [ - line_number_entry(*x) - for x in zip(*[iter(table)] * 2) - ] + self.line_no = [line_number_entry(*x) for x in zip(*[iter(table)] * 2)] def pack(self): return pack( - '>H{0}H'.format(len(self.line_no) * 2), + ">H{0}H".format(len(self.line_no) * 2), len(self.line_no), - *sum(self.line_no, ()) + *sum(self.line_no, ()), ) def __repr__(self): - return ''.format(self.line_no) + return "".format(self.line_no) diff --git a/lawu/attributes/local_variable.py b/lawu/attributes/local_variable.py index 99a80e9..fc0748d 100644 --- a/lawu/attributes/local_variable.py +++ b/lawu/attributes/local_variable.py @@ -4,43 +4,37 @@ from lawu.attribute import Attribute -local_variable_entry = namedtuple('local_variable_entry', [ - 'start_pc', - 'length', - 'name_index', - 'descriptor_index', - 'index' -]) +local_variable_entry = namedtuple( + "local_variable_entry", + ["start_pc", "length", "name_index", "descriptor_index", "index"], +) class LocalVariableTableAttribute(Attribute): - ADDED_IN = '1.0.2' + ADDED_IN = "1.0.2" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super().__init__( table, - name_index or table.cf.constants.create_utf8( - 'LocalVariableTable' - ).index + name_index or table.cf.constants.create_utf8("LocalVariableTable").index, ) self.local_variables = [] def unpack(self, info): length = info.u2() - table = info.unpack('>{0}H'.format(length * 5)) + table = info.unpack(">{0}H".format(length * 5)) self.local_variables = [ - local_variable_entry(*x) - for x in zip(*[iter(table)] * 5) + local_variable_entry(*x) for x in zip(*[iter(table)] * 5) ] def pack(self): return pack( - '>H{0}H'.format(len(self.local_variables) * 5), + ">H{0}H".format(len(self.local_variables) * 5), len(self.local_variables), - *sum(self.local_variables, ()) + *sum(self.local_variables, ()), ) def __repr__(self): - return f'' + return f"" diff --git a/lawu/attributes/local_variable_type.py b/lawu/attributes/local_variable_type.py index 8ee6c69..0c755b7 100644 --- a/lawu/attributes/local_variable_type.py +++ b/lawu/attributes/local_variable_type.py @@ -4,43 +4,38 @@ from lawu.attribute import Attribute -local_variable_type_entry = namedtuple('local_variable_type_entry', [ - 'start_pc', - 'length', - 'name_index', - 'signature_index', - 'index' -]) +local_variable_type_entry = namedtuple( + "local_variable_type_entry", + ["start_pc", "length", "name_index", "signature_index", "index"], +) class LocalVariableTypeTableAttribute(Attribute): - ADDED_IN = '1.0.2' + ADDED_IN = "1.0.2" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super().__init__( table, - name_index or table.cf.constants.create_utf8( - 'LocalVariableTypeTable' - ).index + name_index + or table.cf.constants.create_utf8("LocalVariableTypeTable").index, ) self.local_variables = [] def unpack(self, info): length = info.u2() - table = info.unpack('>{0}H'.format(length * 5)) + table = info.unpack(">{0}H".format(length * 5)) self.local_variables = [ - local_variable_type_entry(*x) - for x in zip(*[iter(table)] * 5) + local_variable_type_entry(*x) for x in zip(*[iter(table)] * 5) ] def pack(self): return pack( - '>H{0}H'.format(len(self.local_variables) * 5), + ">H{0}H".format(len(self.local_variables) * 5), len(self.local_variables), - *sum(self.local_variables, ()) + *sum(self.local_variables, ()), ) def __repr__(self): - return f'' + return f"" diff --git a/lawu/attributes/signature.py b/lawu/attributes/signature.py index 0ce6188..cc9cbb4 100644 --- a/lawu/attributes/signature.py +++ b/lawu/attributes/signature.py @@ -3,15 +3,12 @@ class SignatureAttribute(Attribute): - ADDED_IN = '5.0.0' + ADDED_IN = "5.0.0" MINIMUM_CLASS_VERSION = (49, 0) def __init__(self, table, name_index): super(SignatureAttribute, self).__init__( - table, - name_index or table.cf.constants.create_utf8( - 'Signature' - ).index + table, name_index or table.cf.constants.create_utf8("Signature").index ) self._signature_index = None @@ -19,7 +16,7 @@ def unpack(self, info): self._signature_index = info.u2() def pack(self): - return pack('>H', self._signature_index) + return pack(">H", self._signature_index) @property def signature(self): diff --git a/lawu/attributes/source_file.py b/lawu/attributes/source_file.py index f7df0eb..31f3c25 100644 --- a/lawu/attributes/source_file.py +++ b/lawu/attributes/source_file.py @@ -5,16 +5,12 @@ class SourceFileAttribute(Attribute): - ADDED_IN = '1.0.2' + ADDED_IN = "1.0.2" MINIMUM_CLASS_VERSION = (45, 3) def __init__(self, table, name_index=None): super(SourceFileAttribute, self).__init__( - table, - name_index or UTF8( - pool=table.cf.constants, - value='SourceFile' - ).index + table, name_index or UTF8(pool=table.cf.constants, value="SourceFile").index ) self.source_file_index = None @@ -22,7 +18,7 @@ def unpack(self, info): self.source_file_index = info.u2() def pack(self): - return pack('>H', self.source_file_index) + return pack(">H", self.source_file_index) @property def source_file(self): diff --git a/lawu/attributes/stack_map_table.py b/lawu/attributes/stack_map_table.py index 4217062..3597900 100644 --- a/lawu/attributes/stack_map_table.py +++ b/lawu/attributes/stack_map_table.py @@ -4,19 +4,11 @@ from lawu.util.verifier import VerificationTypes # These types are followed by an additional u2. -TYPES_WITH_EXTRA = ( - VerificationTypes.ITEM_Object, - VerificationTypes.ITEM_Uninitialized -) +TYPES_WITH_EXTRA = (VerificationTypes.ITEM_Object, VerificationTypes.ITEM_Uninitialized) class StackMapFrame(object): - __slots__ = ( - 'frame_type', - 'frame_offset', - 'frame_locals', - 'frame_stack' - ) + __slots__ = ("frame_type", "frame_offset", "frame_locals", "frame_stack") def __init__(self, frame_type): self.frame_type = frame_type @@ -26,10 +18,10 @@ def __init__(self, frame_type): def __repr__(self): return ( - u'' + "" ).format(s=self) @@ -42,15 +34,13 @@ class StackMapTableAttribute(Attribute): generation of a StackMapTableAttribute requires a complete class hierarchy among other things. """ - ADDED_IN = '6.0.0' + + ADDED_IN = "6.0.0" MINIMUM_CLASS_VERSION = (50, 0) def __init__(self, table, name_index=None): super(StackMapTableAttribute, self).__init__( - table, - name_index or table.cf.constants.create_utf8( - 'StackMapTable' - ).index + table, name_index or table.cf.constants.create_utf8("StackMapTable").index ) self.frames = [] @@ -67,8 +57,7 @@ def unpack(self, info): if i == 0: frame.frame_offset = frame_type else: - frame.frame_offset = previous_frame.frame_offset + \ - frame_type + 1 + frame.frame_offset = previous_frame.frame_offset + frame_type + 1 frame.frame_locals = previous_frame.frame_locals self.frames.append(frame) @@ -79,13 +68,10 @@ def unpack(self, info): if i == 0: frame.frame_offset = frame_type - 64 else: - frame.frame_offset = previous_frame.frame_offset + \ - frame_type - 63 + frame.frame_offset = previous_frame.frame_offset + frame_type - 63 frame.frame_locals = previous_frame.frame_locals - frame.frame_stack = list( - self._unpack_verification_type_info(info, 1) - ) + frame.frame_stack = list(self._unpack_verification_type_info(info, 1)) self.frames.append(frame) previous_frame = frame @@ -103,63 +89,48 @@ def unpack(self, info): if i == 0: frame.frame_offset = frame_offset else: - frame.frame_offset = previous_frame.frame_offset + \ - frame_offset + 1 + frame.frame_offset = previous_frame.frame_offset + frame_offset + 1 frame.frame_locals = previous_frame.frame_locals - frame.frame_stack = list( - self._unpack_verification_type_info( - info, - 1 - ) - ) + frame.frame_stack = list(self._unpack_verification_type_info(info, 1)) elif frame_type < 251: # CHOP if i == 0: frame.frame_offset = frame_offset else: - frame.frame_offset = previous_frame.frame_offset + \ - frame_offset + 1 + frame.frame_offset = previous_frame.frame_offset + frame_offset + 1 frame.frame_locals = previous_frame.frame_locals[ - 0:251 - frame_type + 0 : 251 - frame_type ] elif frame_type == 251: # SAME_FRAME_EXTENDED if i == 0: frame.frame_offset = frame_offset else: - frame.frame_offset = previous_frame.frame_offset + \ - frame_offset + 1 + frame.frame_offset = previous_frame.frame_offset + frame_offset + 1 frame.frame_locals = previous_frame.frame_locals elif frame_type < 255: # APPEND if i == 0: frame.frame_offset = frame_offset else: - frame.frame_offset = previous_frame.frame_offset + \ - frame_offset + 1 + frame.frame_offset = previous_frame.frame_offset + frame_offset + 1 frame.frame_locals = previous_frame.frame_locals + list( - self._unpack_verification_type_info( - info, - frame_type - 251 - ) + self._unpack_verification_type_info(info, frame_type - 251) ) elif frame_type == 255: # FULL_FRAME if i == 0: frame.frame_offset = frame_offset else: - frame.frame_offset = previous_frame.frame_offset + \ - frame_offset + 1 - - frame.frame_locals = list(self._unpack_verification_type_info( - info, - info.u2() - )) - frame.frame_stack = list(self._unpack_verification_type_info( - info, - info.u2() - )) + frame.frame_offset = previous_frame.frame_offset + frame_offset + 1 + + frame.frame_locals = list( + self._unpack_verification_type_info(info, info.u2()) + ) + frame.frame_stack = list( + self._unpack_verification_type_info(info, info.u2()) + ) self.frames.append(frame) previous_frame = frame @@ -173,7 +144,7 @@ def _unpack_verification_type_info(info, count): if tag in TYPES_WITH_EXTRA: yield tag, info.u2() else: - yield tag, + yield (tag,) def pack(self): raise NotImplementedError() diff --git a/lawu/attributes/synthetic.py b/lawu/attributes/synthetic.py index 4e27d2c..0f54089 100644 --- a/lawu/attributes/synthetic.py +++ b/lawu/attributes/synthetic.py @@ -2,15 +2,12 @@ class SyntheticAttribute(Attribute): - ADDED_IN = '5.0.0' + ADDED_IN = "5.0.0" MINIMUM_CLASS_VERSION = (49, 0) def __init__(self, table, name_index=None): super().__init__( - table, - name_index or table.cf.constants.create_utf8( - 'Synthetic' - ).index + table, name_index or table.cf.constants.create_utf8("Synthetic").index ) def pack(self): diff --git a/lawu/cf.py b/lawu/cf.py index 838bea7..f28b2a1 100644 --- a/lawu/cf.py +++ b/lawu/cf.py @@ -4,7 +4,8 @@ The :mod:`lawu.cf` module provides tools for working with JVM ``.class`` ClassFiles. """ -from typing import BinaryIO, Iterable, Union, Sequence, Optional, List + +from typing import BinaryIO, Iterable, Union, Sequence, Optional from struct import pack, unpack from collections import namedtuple from enum import IntFlag @@ -17,7 +18,7 @@ from lawu.context import class_context -class ClassVersion(namedtuple('ClassVersion', ['major', 'minor'])): +class ClassVersion(namedtuple("ClassVersion", ["major", "minor"])): """ClassFile file format version.""" __slots__ = () @@ -30,13 +31,13 @@ def human(self) -> str: If the version is unknown, `None` is returned instead. """ return { - 0x33: 'J2SE_7', - 0x32: 'J2SE_6', - 0x31: 'J2SE_5', - 0x30: 'JDK1_4', - 0x2F: 'JDK1_3', - 0x2E: 'JDK1_2', - 0x2D: 'JDK1_1', + 0x33: "J2SE_7", + 0x32: "J2SE_6", + 0x31: "J2SE_5", + 0x30: "JDK1_4", + 0x2F: "JDK1_3", + 0x2E: "JDK1_2", + 0x2D: "JDK1_1", }.get(self.major, None) @@ -72,6 +73,7 @@ class AccessFlags(IntFlag): """ Possible values for the ClassFile.access_flags field. """ + PUBLIC = 0x0001 FINAL = 0x0010 SUPER = 0x0020 @@ -82,26 +84,22 @@ class AccessFlags(IntFlag): ENUM = 0x4000 MODULE = 0x8000 - def __init__(self, source: Optional[BinaryIO] = None, *, - this: str = 'HelloWorld', super_: str = 'java/lang/Object'): + def __init__( + self, + source: Optional[BinaryIO] = None, + *, + this: str = "HelloWorld", + super_: str = "java/lang/Object", + ): # Default to J2SE_7 self._version = ClassVersion(0x32, 0) self.constants = ConstantPool() - self.access_flags = ( - ClassFile.AccessFlags.PUBLIC | - ClassFile.AccessFlags.SUPER - ) + self.access_flags = ClassFile.AccessFlags.PUBLIC | ClassFile.AccessFlags.SUPER self.this = self.constants.add( - ConstantClass( - pool=self.constants, - name=self.constants.add(UTF8(this)) - ) + ConstantClass(pool=self.constants, name=self.constants.add(UTF8(this))) ) self.super_ = self.constants.add( - ConstantClass( - pool=self.constants, - name=self.constants.add(UTF8(super_)) - ) + ConstantClass(pool=self.constants, name=self.constants.add(UTF8(super_))) ) self._interfaces = [] self.fields = FieldTable(self) @@ -139,7 +137,7 @@ def pop_context(self): # the context stack instead of using the ClassFile as a context # manager. raise RuntimeError( - 'A ClassFile tried to pop a context which was not itself.' + "A ClassFile tried to pop a context which was not itself." ) def __enter__(self): @@ -156,23 +154,20 @@ def save(self, source: BinaryIO): """ write = source.write - write(pack( - '>IHH', - ClassFile.MAGIC, - self.version.minor, - self.version.major - )) + write(pack(">IHH", ClassFile.MAGIC, self.version.minor, self.version.major)) self.constants.pack(source) - write(pack('>H', int(self.access_flags))) - write(pack( - f'>HHH{len(self._interfaces)}H', - self.this.index, - self.super_.index, - len(self._interfaces), - *self._interfaces - )) + write(pack(">H", int(self.access_flags))) + write( + pack( + f">HHH{len(self._interfaces)}H", + self.this.index, + self.super_.index, + len(self._interfaces), + *self._interfaces, + ) + ) self.fields.pack(source) self.methods.pack(source) @@ -184,11 +179,11 @@ def _from_io(self, source: BinaryIO): """ read = source.read - if unpack('>I', source.read(4))[0] != ClassFile.MAGIC: - raise ValueError('invalid magic number') + if unpack(">I", source.read(4))[0] != ClassFile.MAGIC: + raise ValueError("invalid magic number") # The version is swapped on disk to (minor, major), so swap it back. - self.version = unpack('>HH', source.read(4))[::-1] + self.version = unpack(">HH", source.read(4))[::-1] # We created some default values when the class was constructed, just # purge them. @@ -196,18 +191,15 @@ def _from_io(self, source: BinaryIO): self.constants.unpack(source) # ClassFile access_flags, see section #4.1 of the JVM specs. - self.access_flags = unpack('>H', read(2)) + self.access_flags = unpack(">H", read(2)) # The CONSTANT_Class indexes for "this" class and its superclass. # Interfaces are a simple list of CONSTANT_Class indexes. - this_, super_, interfaces_count = unpack('>HHH', read(6)) + this_, super_, interfaces_count = unpack(">HHH", read(6)) self.this = self.constants[this_] self.super_ = self.constants[super_] - self._interfaces = unpack( - f'>{interfaces_count}H', - read(2 * interfaces_count) - ) + self._interfaces = unpack(f">{interfaces_count}H", read(2 * interfaces_count)) self.fields.unpack(source) self.methods.unpack(source) @@ -249,14 +241,12 @@ def bootstrap_methods(self) -> BootstrapMethod: :returns: Table of `BootstrapMethod` objects. """ - bootstrap = self.attributes.find_one(name='BootstrapMethods') + bootstrap = self.attributes.find_one(name="BootstrapMethods") if bootstrap is None: - bootstrap = self.attributes.create( - ATTRIBUTE_CLASSES['BootstrapMethods'] - ) + bootstrap = self.attributes.create(ATTRIBUTE_CLASSES["BootstrapMethods"]) return bootstrap.table def __repr__(self): - return f'' + return f"" diff --git a/lawu/classloader.py b/lawu/classloader.py index 41a611c..3962610 100644 --- a/lawu/classloader.py +++ b/lawu/classloader.py @@ -39,8 +39,13 @@ class ClassLoader(object): a method. """ - def __init__(self, *sources, max_cache: int = 50, klass=ClassFile, - bytecode_transforms: Iterable[Callable] = None): + def __init__( + self, + *sources, + max_cache: int = 50, + klass=ClassFile, + bytecode_transforms: Iterable[Callable] = None, + ): self.path_map = {} self.max_cache = max_cache self.class_cache = OrderedDict() @@ -56,12 +61,11 @@ def __getitem__(self, path: str) -> ClassFile: def __contains__(self, path: str) -> bool: if path in self.path_map: return True - elif path + '.class' in self.path_map: + elif path + ".class" in self.path_map: return True return False - def update(self, *sources, follow_symlinks: bool = False, - maximum_depth: int = 20): + def update(self, *sources, follow_symlinks: bool = False, maximum_depth: int = 20): """Add one or more ClassFile sources to the class loader. If a given source is a directory path, it is traversed up to the @@ -90,14 +94,12 @@ def update(self, *sources, follow_symlinks: bool = False, # Explicit cast to str to support Path objects. source = str(source) - if source.lower().endswith(('.zip', '.jar')): - zf = ZipFile(source, 'r') + if source.lower().endswith((".zip", ".jar")): + zf = ZipFile(source, "r") self.path_map.update(zip(zf.namelist(), repeat(zf))) elif os.path.isdir(source): walker = _walk( - source, - follow_links=follow_symlinks, - maximum_depth=maximum_depth + source, follow_links=follow_symlinks, maximum_depth=maximum_depth ) for root, dirs, files in walker: for file_ in files: @@ -106,7 +108,7 @@ def update(self, *sources, follow_symlinks: bool = False, self.path_map[path_suffix] = path_full @contextmanager - def open(self, path: str, mode: str = 'r') -> IO: + def open(self, path: str, mode: str = "r") -> IO: """Open an IO-like object for `path`. .. note:: @@ -122,7 +124,7 @@ def open(self, path: str, mode: str = 'r') -> IO: raise FileNotFoundError() if isinstance(entry, str): - with open(entry, 'rb' if mode == 'r' else mode) as source: + with open(entry, "rb" if mode == "r" else mode) as source: yield source elif isinstance(entry, ZipFile): yield io.BytesIO(entry.read(path)) @@ -142,7 +144,7 @@ def load(self, path: str) -> ClassFile: try: r = self.class_cache.pop(path) except KeyError: - with self.open(f'{path}.class') as source: + with self.open(f"{path}.class") as source: r = self.klass(source) r.classloader = self @@ -173,10 +175,10 @@ def dependencies(self, path: str) -> Set[str]: :param path: Fully-qualified path to a ClassFile. """ - return set(c.name.value for c in self.search_constant_pool( - path=path, - type_=ConstantClass - )) + return set( + c.name.value + for c in self.search_constant_pool(path=path, type_=ConstantClass) + ) def search_constant_pool(self, *, path: str, **options): """Partially load the class at `path`, yield all matching constants @@ -188,7 +190,7 @@ def search_constant_pool(self, *, path: str, **options): :param path: Fully-qualified path to a ClassFile. :param options: A list of options to pass into `ConstantPool.find()` """ - with self.open(f'{path}.class') as source: + with self.open(f"{path}.class") as source: # Skip over the magic, minor, and major version. source.read(8) pool = ConstantPool() @@ -198,7 +200,4 @@ def search_constant_pool(self, *, path: str, **options): @property def classes(self) -> Iterator[str]: """Yield the name of all classes discovered in the path map.""" - yield from ( - c[:-6] - for c in self.path_map.keys() if c.endswith('.class') - ) + yield from (c[:-6] for c in self.path_map.keys() if c.endswith(".class")) diff --git a/lawu/constants.py b/lawu/constants.py index fc49d76..5cbc1a8 100644 --- a/lawu/constants.py +++ b/lawu/constants.py @@ -1,6 +1,7 @@ """ Utilities for working with the ConstantPool found in JVM ClassFiles. """ + from typing import Dict, Any, Deque, BinaryIO, Union from collections import deque from struct import unpack, pack @@ -35,7 +36,8 @@ class Constant(object): """ The base class for all ``Constant*`` types. """ - __slots__ = ('pool', 'index') + + __slots__ = ("pool", "index") #: The "tag" or leading byte of a constant that identifies its type. TAG: int = None @@ -73,17 +75,15 @@ class Number(Constant): """ The base class for all numeric constant types. """ - __slots__ = ('value',) + + __slots__ = ("value",) def __init__(self, *, pool=None, index=None, value=0): super().__init__(pool=pool, index=index) self.value = value def __repr__(self): - return ( - f'{self.__class__.__name__}(' - f'index={self.index}, value={self.value!r})' - ) + return f"{self.__class__.__name__}(index={self.index}, value={self.value!r})" def __eq__(self, other): if isinstance(other, Number): @@ -98,7 +98,7 @@ def unpack(self, source: BinaryIO): class UTF8(Constant): - __slots__ = ('value',) + __slots__ = ("value",) TAG = 1 def __init__(self, value=None, *, pool=None, index=None): @@ -107,15 +107,13 @@ def __init__(self, value=None, *, pool=None, index=None): def pack(self): encoded_value = encode_modified_utf8(self.value) - return pack('>H', len(encoded_value)) + encoded_value + return pack(">H", len(encoded_value)) + encoded_value def unpack(self, source: BinaryIO): - self.value = decode_modified_utf8( - source.read(unpack('>H', source.read(2))[0]) - ) + self.value = decode_modified_utf8(source.read(unpack(">H", source.read(2))[0])) def __repr__(self): - return f')' + return f")" def __eq__(self, other): if isinstance(other, UTF8): @@ -127,44 +125,44 @@ class Integer(Number): TAG = 3 def pack(self): - return pack('>i', self.value) + return pack(">i", self.value) def unpack(self, source: BinaryIO): - self.value = unpack('>i', source.read(4))[0] + self.value = unpack(">i", source.read(4))[0] class Float(Number): TAG = 4 def pack(self): - return pack('>f', self.value) + return pack(">f", self.value) def unpack(self, source: BinaryIO): - self.value = unpack('>f', source.read(4))[0] + self.value = unpack(">f", source.read(4))[0] class Long(Number): TAG = 5 def pack(self): - return pack('>q', self.value) + return pack(">q", self.value) def unpack(self, source: BinaryIO): - self.value = unpack('>q', source.read(8))[0] + self.value = unpack(">q", source.read(8))[0] class Double(Number): TAG = 6 def pack(self): - return pack('>d', self.value) + return pack(">d", self.value) def unpack(self, source: BinaryIO): - self.value = unpack('>d', source.read(8))[0] + self.value = unpack(">d", source.read(8))[0] class ConstantClass(Constant): - __slots__ = ('name_index',) + __slots__ = ("name_index",) TAG = 7 def __init__(self, *, pool=None, index=None, name=None): @@ -185,17 +183,17 @@ def name(self, value: Union[str, UTF8]): self.name_index = UTF8(pool=self.pool, value=value).index def pack(self): - return pack('>H', self.name_index) + return pack(">H", self.name_index) def unpack(self, source: BinaryIO): - self.name_index = unpack('>H', source.read(2))[0] + self.name_index = unpack(">H", source.read(2))[0] def __repr__(self): - return f'' + return f"" class String(Constant): - __slots__ = ('string_index',) + __slots__ = ("string_index",) TAG = 8 def __init__(self, string=None, *, pool=None, index=None): @@ -216,17 +214,17 @@ def string(self, value): self.string_index = UTF8(value, pool=self.pool).index def pack(self): - return pack('>H', self.string_index) + return pack(">H", self.string_index) def unpack(self, source: BinaryIO): - self.string_index = unpack('>H', source.read(2))[0] + self.string_index = unpack(">H", source.read(2))[0] def __repr__(self): - return f'' + return f"" class Reference(Constant): - __slots__ = ('class_index', 'name_and_type_index') + __slots__ = ("class_index", "name_and_type_index") TAG = None def __init__(self, *, pool=None, index=None): @@ -243,20 +241,17 @@ def name_and_type(self): return self.pool[self.name_and_type_index] def pack(self): - return pack('>HH', self.class_index, self.name_and_type_index) + return pack(">HH", self.class_index, self.name_and_type_index) def unpack(self, source: BinaryIO): - self.class_index, self.name_and_type_index = unpack( - '>HH', - source.read(4) - ) + self.class_index, self.name_and_type_index = unpack(">HH", source.read(4)) def __repr__(self): return ( - f'<{self.__class__.__name__}(' - f'index={self.index},' - f'class_={self.class_!r},' - f'name_and_type={self.name_and_type!r})>' + f"<{self.__class__.__name__}(" + f"index={self.index}," + f"class_={self.class_!r}," + f"name_and_type={self.name_and_type!r})>" ) @@ -273,7 +268,7 @@ class InterfaceMethodRef(Reference): class NameAndType(Constant): - __slots__ = ('name_index', 'descriptor_index') + __slots__ = ("name_index", "descriptor_index") TAG = 12 def __init__(self, *, pool=None, index=None): @@ -290,25 +285,22 @@ def descriptor(self): return self.pool[self.descriptor_index] def pack(self): - return pack('>HH', self.name_index, self.descriptor_index) + return pack(">HH", self.name_index, self.descriptor_index) def unpack(self, source: BinaryIO): - self.name_index, self.descriptor_index = unpack( - '>HH', - source.read(4) - ) + self.name_index, self.descriptor_index = unpack(">HH", source.read(4)) def __repr__(self): return ( - f'' + f"" ) class MethodHandle(Constant): - __slots__ = ('reference_kind', 'reference_index') + __slots__ = ("reference_kind", "reference_index") TAG = 15 def __init__(self, *, pool=None, index=None): @@ -321,22 +313,17 @@ def reference(self): return self.pool.get(self.reference_index) def pack(self): - return pack('>BH', self.reference_kind, self.reference_index) + return pack(">BH", self.reference_kind, self.reference_index) def unpack(self, source: BinaryIO): - self.reference_kind, self.reference_index = unpack( - '>BH', - source.read(3) - ) + self.reference_kind, self.reference_index = unpack(">BH", source.read(3)) def __repr__(self): - return ( - f'' - ) + return f"" class MethodType(Constant): - __slots__ = ('descriptor_index',) + __slots__ = ("descriptor_index",) TAG = 16 def __init__(self, *, pool=None, index=None): @@ -348,17 +335,17 @@ def descriptor(self): return self.pool.get(self.descriptor_index) def pack(self): - return pack('>H', self.descriptor_index) + return pack(">H", self.descriptor_index) def unpack(self, source: BinaryIO): - self.descriptor_index = unpack('>H', source.read(2))[0] + self.descriptor_index = unpack(">H", source.read(2))[0] def __repr__(self): - return f'' + return f"" class Dynamic(Constant): - __slots__ = ('bootstrap_method_attr_index', 'name_and_type_index') + __slots__ = ("bootstrap_method_attr_index", "name_and_type_index") TAG = 17 def __init__(self, *, pool=None, index=None): @@ -375,54 +362,49 @@ def name_and_type(self): return self.pool[self.name_and_type_index] def pack(self): - return pack( - '>HH', - self.bootstrap_method_attr_index, - self.name_and_type_index - ) + return pack(">HH", self.bootstrap_method_attr_index, self.name_and_type_index) def unpack(self, source: BinaryIO): self.bootstrap_method_attr_index, self.name_and_type_index = unpack( - '>HH', - source.read(4) + ">HH", source.read(4) ) def __repr__(self): return ( - f'' + f"" ) class InvokeDynamic(Dynamic): - __slots__ = ('bootstrap_method_attr_index', 'name_and_type_index') + __slots__ = ("bootstrap_method_attr_index", "name_and_type_index") TAG = 18 def __repr__(self): return ( - f'' + f"" ) class Module(ConstantClass): - __slots__ = ('name_index',) + __slots__ = ("name_index",) TAG = 19 def __repr__(self): - return f'' + return f"" class PackageInfo(ConstantClass): - __slots__ = ('name_index',) + __slots__ = ("name_index",) TAG = 20 def __repr__(self): - return f'' + return f"" CONSTANTS = { @@ -442,33 +424,13 @@ def __repr__(self): 17: Dynamic, 18: InvokeDynamic, 19: Module, - 20: PackageInfo + 20: PackageInfo, } # The size (in bytes) of each type of Constant in the pool, except the UTF8 # type which must be handled dynamically. -SIZE = ( - None, - None, - None, - 4, - 4, - 8, - 8, - 2, - 2, - 4, - 4, - 4, - 4, - None, - None, - 3, - 2, - None, - 4 -) +SIZE = (None, None, None, 4, 4, 8, 8, 2, 2, 4, 4, 4, 4, None, None, 3, 2, None, 4) class ConstantPool(object): @@ -476,6 +438,7 @@ class ConstantPool(object): This class can be used to read, modify, and write the JVM ClassFile constant pool with a high-level interface. """ + def __init__(self, *, source: BinaryIO = None): # We use a dict as our basic pool container because the pool can be # built out-of-order. For example when loading a Jasmin file, it's @@ -509,7 +472,7 @@ def clear(self): def unpack(self, source: BinaryIO): """Unpack a constant pool from a ClassFile.""" read = source.read - constant_pool_count = unpack('>H', read(2))[0] + constant_pool_count = unpack(">H", read(2))[0] index_iter = iter(range(1, constant_pool_count)) for index in index_iter: @@ -526,13 +489,13 @@ def unpack(self, source: BinaryIO): def pack(self, out: BinaryIO): """Write the ConstantPool to the file-like object `out`.""" write = out.write - write(pack('>H', len(self) + 1)) + write(pack(">H", len(self) + 1)) for index, constant in sorted(self.pool.items()): # Skip over double-width padding (Doubles & Longs) if constant is None: continue - write(constant.TAG.to_bytes(1, byteorder='big')) + write(constant.TAG.to_bytes(1, byteorder="big")) write(constant.pack()) def update_trackers(self): @@ -550,11 +513,7 @@ def update_trackers(self): return self.sparse_map = deque( - _missing_elements( - sorted(self.pool.keys()), - 0, - len(self.pool) - 1 - ) + _missing_elements(sorted(self.pool.keys()), 0, len(self.pool) - 1) ) @property @@ -626,10 +585,7 @@ def remove(self, index: int): self.update_trackers() def __iter__(self): - yield from ( - (k, v) for k, v in sorted(self.pool.items()) - if v is not None - ) + yield from ((k, v) for k, v in sorted(self.pool.items()) if v is not None) def __getitem__(self, index): return self.pool[index] diff --git a/lawu/context.py b/lawu/context.py index 4b3b1d3..5d17c4c 100644 --- a/lawu/context.py +++ b/lawu/context.py @@ -1,6 +1,6 @@ from contextvars import ContextVar -_class_context: ContextVar = ContextVar('class_context') +_class_context: ContextVar = ContextVar("class_context") def class_context(): diff --git a/lawu/fields.py b/lawu/fields.py index 450a690..d853eb9 100644 --- a/lawu/fields.py +++ b/lawu/fields.py @@ -55,7 +55,7 @@ def value(self) -> ConstantValueAttribute: """ A shortcut for the field's ConstantValue attribute, should one exist. """ - return self.attributes.find_one(name='ConstantValue') + return self.attributes.find_one(name="ConstantValue") def unpack(self, source: BinaryIO): """ @@ -68,8 +68,8 @@ def unpack(self, source: BinaryIO): :param source: Any file-like object providing `read()` """ - self.access_flags = Field.AccessFlags(unpack('>H', source.read(2))[0]) - self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) + self.access_flags = Field.AccessFlags(unpack(">H", source.read(2))[0]) + self._name_index, self._descriptor_index = unpack(">HH", source.read(4)) self.attributes.unpack(source) def pack(self, out: BinaryIO): @@ -83,8 +83,8 @@ def pack(self, out: BinaryIO): :param out: Any file-like object providing `write()` """ - out.write(pack('>H', int(self.access_flags))) - out.write(pack('>HH', self._name_index, self._descriptor_index)) + out.write(pack(">H", int(self.access_flags))) + out.write(pack(">HH", self._name_index, self._descriptor_index)) self.attributes.pack(out) @@ -108,8 +108,7 @@ def remove(self, field: Field): """ self._table = [fld for fld in self._table if fld is not field] - def create(self, name: str, descriptor: str, value: Constant = None) \ - -> Field: + def create(self, name: str, descriptor: str, value: Constant = None) -> Field: """ Creates a new field from `name` and `descriptor`. For example:: @@ -160,7 +159,7 @@ def unpack(self, source: BinaryIO): :param source: Any file-like object providing `read()` """ - field_count = unpack('>H', source.read(2))[0] + field_count = unpack(">H", source.read(2))[0] for _ in repeat(None, field_count): field = Field(self._cf) field.unpack(source) @@ -177,15 +176,20 @@ def pack(self, out: BinaryIO): :param out: Any file-like object providing `write()` """ - out.write(pack('>H', len(self))) + out.write(pack(">H", len(self))) for field in self._table: field.pack(out) def __len__(self): return len(self._table) - def find(self, *, name: Optional[str] = None, type_: Optional[str] = None, - f: Optional[Callable] = None) -> Iterator[Field]: + def find( + self, + *, + name: Optional[str] = None, + type_: Optional[str] = None, + f: Optional[Callable] = None, + ) -> Iterator[Field]: """ Iterates over the fields table, yielding each matching method. Calling without any arguments is equivalent to iterating over the table. diff --git a/lawu/methods.py b/lawu/methods.py index f2ffbc1..d7ccc11 100644 --- a/lawu/methods.py +++ b/lawu/methods.py @@ -66,10 +66,10 @@ def code(self) -> CodeAttribute: """ A shortcut for :code:`method.attributes.find_one(name='Code')`. """ - return self.attributes.find_one(name='Code') + return self.attributes.find_one(name="Code") def __repr__(self): - return f'' + return f"" def unpack(self, source: BinaryIO): """ @@ -82,8 +82,8 @@ def unpack(self, source: BinaryIO): :param source: Any file-like object providing `read()` """ - self.access_flags = Method.AccessFlags(unpack('>H', source.read(2))[0]) - self._name_index, self._descriptor_index = unpack('>HH', source.read(4)) + self.access_flags = Method.AccessFlags(unpack(">H", source.read(2))[0]) + self._name_index, self._descriptor_index = unpack(">HH", source.read(4)) self.attributes.unpack(source) def pack(self, out: BinaryIO): @@ -97,12 +97,8 @@ def pack(self, out: BinaryIO): :param out: Any file-like object providing `write()` """ - out.write(pack('>H', int(self.access_flags))) - out.write(pack( - '>HH', - self._name_index, - self._descriptor_index - )) + out.write(pack(">H", int(self.access_flags))) + out.write(pack(">HH", self._name_index, self._descriptor_index)) self.attributes.pack(out) @@ -126,8 +122,7 @@ def remove(self, method: Method): """ self._table = [fld for fld in self._table if fld is not method] - def create(self, name: str, descriptor: str, - code: CodeAttribute = None) -> Method: + def create(self, name: str, descriptor: str, code: CodeAttribute = None) -> Method: """ Creates a new method from `name` and `descriptor`. If `code` is not ``None``, add a `Code` attribute to this method. @@ -160,7 +155,7 @@ def unpack(self, source: BinaryIO): :param source: Any file-like object providing `read()` """ - method_count = unpack('>H', source.read(2))[0] + method_count = unpack(">H", source.read(2))[0] for _ in repeat(None, method_count): method = Method(self._cf) method.unpack(source) @@ -177,13 +172,18 @@ def pack(self, out: BinaryIO): :param out: Any file-like object providing `write()` """ - out.write(pack('>H', len(self))) + out.write(pack(">H", len(self))) for method in self._table: method.pack(out) - def find(self, *, name: Optional[str] = None, args: Optional[str] = None, - returns: Optional[str] = None, f: Optional[Callable] = None - ) -> Iterator[Method]: + def find( + self, + *, + name: Optional[str] = None, + args: Optional[str] = None, + returns: Optional[str] = None, + f: Optional[Callable] = None, + ) -> Iterator[Method]: """ Iterates over the methods table, yielding each matching method. Calling without any arguments is equivalent to iterating over the table. For @@ -208,13 +208,13 @@ def find(self, *, name: Optional[str] = None, args: Optional[str] = None, continue descriptor = method.descriptor.value - end_para = descriptor.find(')') + end_para = descriptor.find(")") m_args = descriptor[1:end_para] if args is not None and args != m_args: continue - m_returns = descriptor[end_para + 1:] + m_returns = descriptor[end_para + 1 :] if returns is not None and returns != m_returns: continue diff --git a/lawu/transforms.py b/lawu/transforms.py index e2e9306..f0c801f 100644 --- a/lawu/transforms.py +++ b/lawu/transforms.py @@ -3,6 +3,7 @@ Instruction by the :func:`~lawu.attributes.code.CodeAttribute.disassemble` function. """ + from lawu.util.bytecode import Instruction, Operand, OperandTypes, opcode_table @@ -34,18 +35,18 @@ def simple_swap(ins: Instruction) -> Instruction: :return: Potentially modified instruction. """ try: - rule = ins.details['transform']['simple_swap'] + rule = ins.details["transform"]["simple_swap"] except KeyError: return ins - replacement_ins = opcode_table[rule['op']] + replacement_ins = opcode_table[rule["op"]] return Instruction( - replacement_ins['mnemonic'], - replacement_ins['op'], - [Operand( - replacement_ins['operands'][i][1], - r - ) for i, r in enumerate(rule['operands'])], - ins.pos + replacement_ins["mnemonic"], + replacement_ins["op"], + [ + Operand(replacement_ins["operands"][i][1], r) + for i, r in enumerate(rule["operands"]) + ], + ins.pos, ) diff --git a/lawu/util/__init__.py b/lawu/util/__init__.py index 02fd521..aee5c8e 100644 --- a/lawu/util/__init__.py +++ b/lawu/util/__init__.py @@ -1,4 +1,4 @@ """ Standalone utility modules that don't necessarily require the rest of Lawu to function. Generally they can be copy-pasted into other projects for reuse. -""" \ No newline at end of file +""" diff --git a/lawu/util/bytecode.py b/lawu/util/bytecode.py index 86f57af..70dd7ee 100644 --- a/lawu/util/bytecode.py +++ b/lawu/util/bytecode.py @@ -1,6 +1,7 @@ """ Utilities for reading & writing JVM method bytecode. """ + import json import enum import pkgutil @@ -8,13 +9,8 @@ from itertools import repeat from collections import namedtuple -Operand = namedtuple('Operand', ['op_type', 'value']) -_Instruction = namedtuple('Instruction', [ - 'mnemonic', - 'opcode', - 'operands', - 'pos' -]) +Operand = namedtuple("Operand", ["op_type", "value"]) +_Instruction = namedtuple("Instruction", ["mnemonic", "opcode", "operands", "pos"]) class Instruction(_Instruction): @@ -22,6 +18,7 @@ class Instruction(_Instruction): Represents a single JVM instruction, consisting of an opcode and its potential operands. """ + __slots__ = () def size_on_disk(self, start_pos=0): @@ -32,7 +29,7 @@ def size_on_disk(self, start_pos=0): """ # All instructions are at least 1 byte (the opcode itself) size = 1 - fmts = opcode_table[self.opcode]['operands'] + fmts = opcode_table[self.opcode]["operands"] if self.wide: size += 2 @@ -63,7 +60,7 @@ def wide(self): ``True`` if this instruction needs to be prefixed by the WIDE opcode. """ - if not opcode_table[self.opcode].get('can_be_wide'): + if not opcode_table[self.opcode].get("can_be_wide"): return False if self.operands[0].value >= 255: @@ -89,12 +86,7 @@ def details(self): def create(cls, mnemonic_or_op, operands=None): op = opcode_table[mnemonic_or_op] - return cls( - op['mnemonic'], - op['op'], - operands or [], - 0 - ) + return cls(op["mnemonic"], op["op"], operands or [], 0) def __eq__(self, other): return other == self.mnemonic or super().__eq__(other) @@ -105,6 +97,7 @@ class OperandTypes(enum.IntEnum): Constants used to determine the "type" of operand on an opcode, such as a BRANCH [offset] or a LITERAL [value]. """ + LITERAL = 10 LOCAL_INDEX = 20 CONSTANT_INDEX = 30 @@ -113,11 +106,11 @@ class OperandTypes(enum.IntEnum): class OperandFmts(enum.Enum): - UBYTE = Struct('>B') - BYTE = Struct('>b') - USHORT = Struct('>H') - SHORT = Struct('>h') - INTEGER = Struct('>i') + UBYTE = Struct(">B") + BYTE = Struct(">b") + USHORT = Struct(">H") + SHORT = Struct(">h") + INTEGER = Struct(">i") def write_instruction(fout, start_pos, ins): @@ -129,24 +122,24 @@ def write_instruction(fout, start_pos, ins): :param ins: The `Instruction` to write. """ opcode, operands = ins.opcode, ins.operands - fmt_operands = opcode_table[opcode]['operands'] + fmt_operands = opcode_table[opcode]["operands"] if ins.wide: # The "WIDE" prefix - fout.write(pack('>B', 0xC4)) + fout.write(pack(">B", 0xC4)) # The real opcode. - fout.write(pack('>B', opcode)) - fout.write(pack('>H', operands[0].value)) + fout.write(pack(">B", opcode)) + fout.write(pack(">H", operands[0].value)) if opcode == 0x84: - fout.write(pack('>h', operands[1].value)) + fout.write(pack(">h", operands[1].value)) elif fmt_operands: # A normal simple opcode with simple operands. - fout.write(pack('>B', opcode)) + fout.write(pack(">B", opcode)) for i, (fmt, _) in enumerate(fmt_operands): fout.write(fmt.value.pack(operands[i].value)) elif opcode == 0xAB: # Special case for lookupswitch. - fout.write(pack('>B', opcode)) + fout.write(pack(">B", opcode)) # assemble([ # ('lookupswitch', { # 2: -3, @@ -155,27 +148,29 @@ def write_instruction(fout, start_pos, ins): # ]) padding = 4 - (start_pos + 1) % 4 padding = padding if padding != 4 else 0 - fout.write(pack(f'{padding}x')) - fout.write(pack('>ii', operands[1].value, len(operands[0]))) + fout.write(pack(f"{padding}x")) + fout.write(pack(">ii", operands[1].value, len(operands[0]))) for key in sorted(operands[0].keys()): - fout.write(pack('>ii', key, operands[0][key])) + fout.write(pack(">ii", key, operands[0][key])) elif opcode == 0xAA: # Special case for table switch. - fout.write(pack('>B', opcode)) + fout.write(pack(">B", opcode)) padding = 4 - (start_pos + 1) % 4 padding = padding if padding != 4 else 0 - fout.write(pack(f'{padding}x')) - fout.write(pack( - f'>iii{len(operands) - 3}i', - # Default branch offset - operands[0].value, - operands[1].value, - operands[2].value, - *(o.value for o in operands[3:]) - )) + fout.write(pack(f"{padding}x")) + fout.write( + pack( + f">iii{len(operands) - 3}i", + # Default branch offset + operands[0].value, + operands[1].value, + operands[2].value, + *(o.value for o in operands[3:]), + ) + ) else: # opcode with no operands. - fout.write(pack('>B', opcode)) + fout.write(pack(">B", opcode)) def read_instruction(fio, start_pos): @@ -194,18 +189,15 @@ def read_instruction(fio, start_pos): op = ord(op) ins = opcode_table[op] - operands = ins['operands'] - name = ins['mnemonic'] + operands = ins["operands"] + name = ins["mnemonic"] final_operands = [] # Most opcodes have simple operands. if operands: for fmt, type_ in operands: final_operands.append( - Operand( - type_, - fmt.value.unpack(fio.read(fmt.value.size))[0] - ) + Operand(type_, fmt.value.unpack(fio.read(fmt.value.size))[0]) ) # Special case for lookupswitch. elif op == 0xAB: @@ -215,11 +207,11 @@ def read_instruction(fio, start_pos): fio.read(padding) # Default branch address and branch count. - default, npairs = unpack('>ii', fio.read(8)) + default, npairs = unpack(">ii", fio.read(8)) pairs = {} for _ in repeat(None, npairs): - match, offset = unpack('>ii', fio.read(8)) + match, offset = unpack(">ii", fio.read(8)) pairs[match] = offset final_operands.append(pairs) @@ -231,30 +223,28 @@ def read_instruction(fio, start_pos): padding = padding if padding != 4 else 0 fio.read(padding) - default, low, high = unpack('>iii', fio.read(12)) + default, low, high = unpack(">iii", fio.read(12)) final_operands.append(Operand(OperandTypes.BRANCH, default)) final_operands.append(Operand(OperandTypes.LITERAL, low)) final_operands.append(Operand(OperandTypes.LITERAL, high)) for _ in repeat(None, high - low + 1): - offset = unpack('>i', fio.read(4))[0] + offset = unpack(">i", fio.read(4))[0] final_operands.append(Operand(OperandTypes.BRANCH, offset)) # Special case for the wide prefix elif op == 0xC4: - real_op = unpack('>B', fio.read(1))[0] + real_op = unpack(">B", fio.read(1))[0] ins = opcode_table[real_op] - name = ins['mnemonic'] + name = ins["mnemonic"] - final_operands.append(Operand( - OperandTypes.LOCAL_INDEX, - unpack('>H', fio.read(2))[0] - )) + final_operands.append( + Operand(OperandTypes.LOCAL_INDEX, unpack(">H", fio.read(2))[0]) + ) # Further special case for iinc. if real_op == 0x84: - final_operands.append(Operand( - OperandTypes.LITERAL, - unpack('>H', fio.read(2))[0] - )) + final_operands.append( + Operand(OperandTypes.LITERAL, unpack(">H", fio.read(2))[0]) + ) return Instruction(name, op, final_operands, start_pos) @@ -268,11 +258,11 @@ def load_bytecode_definitions(*, path=None) -> dict: bytecode definitions. """ if path is not None: - with open(path, 'rb') as file_in: + with open(path, "rb") as file_in: j = json.load(file_in) else: try: - j = json.loads(pkgutil.get_data('lawu.util', 'bytecode.json')) + j = json.loads(pkgutil.get_data("lawu.util", "bytecode.json")) except json.JSONDecodeError: # Unfortunately our best way to handle missing/malformed/empty # bytecode.json files since it may not actually be backed by a @@ -282,15 +272,14 @@ def load_bytecode_definitions(*, path=None) -> dict: for definition in j.values(): # If the entry has any operands take the text labels and convert # them into pre-cached struct objects and operand types. - operands = definition['operands'] + operands = definition["operands"] if operands: - definition['operands'] = [ - [getattr(OperandFmts, oo[0]), OperandTypes[oo[1]]] - for oo in operands + definition["operands"] = [ + [getattr(OperandFmts, oo[0]), OperandTypes[oo[1]]] for oo in operands ] # Return one dict that contains both mnemonic keys and opcode keys. - return {**j, **{v['op']: v for v in j.values()}} + return {**j, **{v["op"]: v for v in j.values()}} opcode_table = load_bytecode_definitions() diff --git a/lawu/util/descriptor.py b/lawu/util/descriptor.py index 04d8028..258b5af 100644 --- a/lawu/util/descriptor.py +++ b/lawu/util/descriptor.py @@ -1,22 +1,16 @@ """ Methods for parsing standard JVM type descriptors for fields and methods. """ + from collections import namedtuple -JVMType = namedtuple('JVMType', [ - 'base_type', - 'dimensions', - 'name' -]) +JVMType = namedtuple("JVMType", ["base_type", "dimensions", "name"]) -MethodDescriptor = namedtuple('MethodDescriptor', [ - 'returns', - 'args', - 'returns_descriptor', - 'args_descriptor', - 'descriptor' -]) +MethodDescriptor = namedtuple( + "MethodDescriptor", + ["returns", "args", "returns_descriptor", "args_descriptor", "descriptor"], +) def method_descriptor(descriptor: str) -> MethodDescriptor: @@ -24,16 +18,12 @@ def method_descriptor(descriptor: str) -> MethodDescriptor: Parses a Method descriptor as described in section 4.3.3 of the JVM specification. """ - end_para = descriptor.find(')') - returns = descriptor[end_para + 1:] + end_para = descriptor.find(")") + returns = descriptor[end_para + 1 :] args = descriptor[1:end_para] return MethodDescriptor( - parse_descriptor(returns)[0], - parse_descriptor(args), - returns, - args, - descriptor + parse_descriptor(returns)[0], parse_descriptor(args), returns, args, descriptor ) @@ -48,16 +38,16 @@ def field_descriptor(descriptor: str) -> str: # JVM Descriptor "BaseType" characters to their # full simple type. _HUMAN_NAMES = { - 'L': 'reference', - 'B': 'byte', - 'C': 'char', - 'D': 'double', - 'F': 'float', - 'I': 'int', - 'J': 'long', - 'S': 'short', - 'Z': 'boolean', - 'V': 'void' + "L": "reference", + "B": "byte", + "C": "char", + "D": "double", + "F": "float", + "I": "int", + "J": "long", + "S": "short", + "Z": "boolean", + "V": "void", } @@ -75,15 +65,15 @@ def parse_descriptor(descriptor: str) -> list: token = [] dimensions = 0 for char in descriptor: - if state == 10 and char == 'L': + if state == 10 and char == "L": state = 20 - elif state == 10 and char == '[': + elif state == 10 and char == "[": dimensions += 1 elif state == 10: tokens.append(JVMType(char, dimensions, _HUMAN_NAMES[char])) dimensions = 0 - elif state == 20 and char == ';': - tokens.append(JVMType('L', dimensions, ''.join(token))) + elif state == 20 and char == ";": + tokens.append(JVMType("L", dimensions, "".join(token))) dimensions = 0 state = 10 del token[:] diff --git a/lawu/util/shell.py b/lawu/util/shell.py index 1c78180..bfdac54 100644 --- a/lawu/util/shell.py +++ b/lawu/util/shell.py @@ -10,7 +10,7 @@ IPYTHON_SHELL_AVAILABLE = True -def start_shell(local_ns: Dict = None, banner: str = ''): +def start_shell(local_ns: Dict = None, banner: str = ""): """Create and immediately drop into a Python shell. If IPython version 5 or greater is available it will be used instead diff --git a/lawu/util/stream.py b/lawu/util/stream.py index 77498e5..d943090 100644 --- a/lawu/util/stream.py +++ b/lawu/util/stream.py @@ -3,16 +3,16 @@ class BufferStreamReader(BytesIO): - """Stream-like reader over a buffer mimicking the JVM spec types. - """ + """Stream-like reader over a buffer mimicking the JVM spec types.""" + def u1(self): - return unpack('>B', self.read(1))[0] + return unpack(">B", self.read(1))[0] def u2(self): - return unpack('>H', self.read(2))[0] + return unpack(">H", self.read(2))[0] def u4(self): - return unpack('>I', self.read(4))[0] + return unpack(">I", self.read(4))[0] def unpack(self, fmt): return unpack(fmt, self.read(calcsize(fmt))) diff --git a/lawu/util/verifier.py b/lawu/util/verifier.py index 26f18ea..a743183 100644 --- a/lawu/util/verifier.py +++ b/lawu/util/verifier.py @@ -1,5 +1,3 @@ - - class VerificationTypes(object): ITEM_Top = 0 ITEM_Integer = 1 diff --git a/tests/attributes/test_enclosing_method.py b/tests/attributes/test_enclosing_method.py index 4276412..9e8a57f 100644 --- a/tests/attributes/test_enclosing_method.py +++ b/tests/attributes/test_enclosing_method.py @@ -1,11 +1,11 @@ def test_enclosing_method_read(loader): - cf = loader['EnclosingMethod$1EnclosedClass'] - a = cf.attributes.find_one(name='EnclosingMethod') - assert cf.constants[a.method_index].name.value == 'main' - assert cf.constants[a.class_index].name.value == 'EnclosingMethod' + cf = loader["EnclosingMethod$1EnclosedClass"] + a = cf.attributes.find_one(name="EnclosingMethod") + assert cf.constants[a.method_index].name.value == "main" + assert cf.constants[a.class_index].name.value == "EnclosingMethod" def test_exceptions_write(loader): - cf = loader['EnclosingMethod$1EnclosedClass'] - a = cf.attributes.find_one(name='EnclosingMethod') - assert a.pack() == b'\x00\x0b\x00\x0c' + cf = loader["EnclosingMethod$1EnclosedClass"] + a = cf.attributes.find_one(name="EnclosingMethod") + assert a.pack() == b"\x00\x0b\x00\x0c" diff --git a/tests/attributes/test_exceptions_attribute.py b/tests/attributes/test_exceptions_attribute.py index ff68c5e..7d9efcb 100644 --- a/tests/attributes/test_exceptions_attribute.py +++ b/tests/attributes/test_exceptions_attribute.py @@ -2,27 +2,27 @@ def test_exceptions_read(loader): - cf = loader['ExceptionsTest'] + cf = loader["ExceptionsTest"] - m = cf.methods.find_one(name='test') - a = m.attributes.find_one(name='Exceptions') + m = cf.methods.find_one(name="test") + a = m.attributes.find_one(name="Exceptions") assert len(a.exceptions) == 1 - assert cf.constants[a.exceptions[0]].name.value == \ - u'java/lang/IndexOutOfBoundsException' + assert ( + cf.constants[a.exceptions[0]].name.value + == "java/lang/IndexOutOfBoundsException" + ) def test_exceptions_write(loader): - cf = loader['ExceptionsTest'] + cf = loader["ExceptionsTest"] with cf: - m = cf.methods.find_one(name='test') - a = m.attributes.find_one(name='Exceptions') + m = cf.methods.find_one(name="test") + a = m.attributes.find_one(name="Exceptions") - assert a.pack() == b'\x00\x01\x00\x0A' + assert a.pack() == b"\x00\x01\x00\x0a" - a.exceptions.append( - ConstantClass(name='java/lang/TestException').index - ) + a.exceptions.append(ConstantClass(name="java/lang/TestException").index) - assert a.pack() == b'\x00\x02\x00\x0A\x00\x11' + assert a.pack() == b"\x00\x02\x00\x0a\x00\x11" diff --git a/tests/attributes/test_general_attributes.py b/tests/attributes/test_general_attributes.py index 18f48a6..6fc0c16 100644 --- a/tests/attributes/test_general_attributes.py +++ b/tests/attributes/test_general_attributes.py @@ -2,23 +2,20 @@ def test_mandatory_attributes(): - required_properties = ['ADDED_IN', 'MINIMUM_CLASS_VERSION'] + required_properties = ["ADDED_IN", "MINIMUM_CLASS_VERSION"] for name, class_ in get_attribute_classes().items(): for p in required_properties: assert hasattr(class_, p), ( - '{name} parser missing mandatory {p} property'.format( - name=name, - p=p - ) + "{name} parser missing mandatory {p} property".format(name=name, p=p) ) def test_attribute_naming(): for name, class_ in get_attribute_classes().items(): - if hasattr(class_, 'ATTRIBUTE_NAME'): + if hasattr(class_, "ATTRIBUTE_NAME"): continue - assert class_.__name__.endswith('Attribute'), ( - '{name} parser does not follow naming convention and does' - ' not explicitly set it.'.format(name=name) + assert class_.__name__.endswith("Attribute"), ( + "{name} parser does not follow naming convention and does" + " not explicitly set it.".format(name=name) ) diff --git a/tests/attributes/test_inner_classes.py b/tests/attributes/test_inner_classes.py index 309d896..60bb6d9 100644 --- a/tests/attributes/test_inner_classes.py +++ b/tests/attributes/test_inner_classes.py @@ -2,19 +2,19 @@ def test_inner_classes_read(loader): - cf = loader['InnerClasses'] - a = cf.attributes.find_one(name='InnerClasses') + cf = loader["InnerClasses"] + a = cf.attributes.find_one(name="InnerClasses") assert a.inner_classes == [ InnerClass( inner_class_info_index=4, outer_class_info_index=2, inner_name_index=5, - inner_class_access_flags=2 + inner_class_access_flags=2, ) ] def test_exceptions_write(loader): - cf = loader['InnerClasses'] - a = cf.attributes.find_one(name='InnerClasses') - assert a.pack() == b'\x00\x01\x00\x04\x00\x02\x00\x05\x00\x02' + cf = loader["InnerClasses"] + a = cf.attributes.find_one(name="InnerClasses") + assert a.pack() == b"\x00\x01\x00\x04\x00\x02\x00\x05\x00\x02" diff --git a/tests/attributes/test_line_number_attribute.py b/tests/attributes/test_line_number_attribute.py index 0513c61..67b9c84 100644 --- a/tests/attributes/test_line_number_attribute.py +++ b/tests/attributes/test_line_number_attribute.py @@ -1,7 +1,7 @@ def test_exceptions_read(loader): - cf = loader['HelloWorldDebug'] - m = cf.methods.find_one(name='main') - a = m.code.attributes.find_one(name='LineNumberTable') + cf = loader["HelloWorldDebug"] + m = cf.methods.find_one(name="main") + a = m.code.attributes.find_one(name="LineNumberTable") assert len(a.line_no) == 2 @@ -10,8 +10,8 @@ def test_exceptions_read(loader): def test_exceptions_write(loader): - cf = loader['HelloWorldDebug'] - m = cf.methods.find_one(name='main') - a = m.code.attributes.find_one(name='LineNumberTable') + cf = loader["HelloWorldDebug"] + m = cf.methods.find_one(name="main") + a = m.code.attributes.find_one(name="LineNumberTable") - assert a.pack() == b'\x00\x02\x00\x00\x00\x03\x00\x08\x00\x04' + assert a.pack() == b"\x00\x02\x00\x00\x00\x03\x00\x08\x00\x04" diff --git a/tests/attributes/test_sourcefile_attribute.py b/tests/attributes/test_sourcefile_attribute.py index cc03336..5d3bc1d 100644 --- a/tests/attributes/test_sourcefile_attribute.py +++ b/tests/attributes/test_sourcefile_attribute.py @@ -9,20 +9,20 @@ def test_sourcefile_read(loader): """ Ensure we can read a SourceFileAttribute generated by javac. """ - cf = loader['HelloWorldDebug'] - source_file = cf.attributes.find_one(name='SourceFile') - assert(source_file.source_file.value == 'HelloWorldDebug.java') + cf = loader["HelloWorldDebug"] + source_file = cf.attributes.find_one(name="SourceFile") + assert source_file.source_file.value == "HelloWorldDebug.java" def test_sourcefile_write(): """ Ensure SourceFileAttribute can be written and read back. """ - cf_one = ClassFile(this='SourceFileTest') + cf_one = ClassFile(this="SourceFileTest") with cf_one: sfa = cf_one.attributes.create(SourceFileAttribute) - sfa.source_file = UTF8(value='SourceFileTest.java') + sfa.source_file = UTF8(value="SourceFileTest.java") fout = BytesIO() cf_one.save(fout) @@ -30,5 +30,5 @@ def test_sourcefile_write(): fin = BytesIO(fout.getvalue()) cf_two = ClassFile(fin) - source_file = cf_two.attributes.find_one(name='SourceFile') - assert(source_file.source_file.value == 'SourceFileTest.java') + source_file = cf_two.attributes.find_one(name="SourceFile") + assert source_file.source_file.value == "SourceFileTest.java" diff --git a/tests/attributes/test_stack_map_attribute.py b/tests/attributes/test_stack_map_attribute.py index 0fd5929..f1814a5 100644 --- a/tests/attributes/test_stack_map_attribute.py +++ b/tests/attributes/test_stack_map_attribute.py @@ -2,23 +2,21 @@ def test_stack_map_table_read(loader): - cf = loader['ArrayTest'] - m = cf.methods.find_one(name='addOne') - a = m.code.attributes.find_one(name='StackMapTable') + cf = loader["ArrayTest"] + m = cf.methods.find_one(name="addOne") + a = m.code.attributes.find_one(name="StackMapTable") assert len(a.frames) == 2 assert a.frames[0].frame_type == 76 - assert a.frames[0].frame_stack == [ - (VerificationTypes.ITEM_Integer,) - ] + assert a.frames[0].frame_stack == [(VerificationTypes.ITEM_Integer,)] assert a.frames[1].frame_type == 255 assert a.frames[1].frame_stack == [ (VerificationTypes.ITEM_Integer,), - (VerificationTypes.ITEM_Integer,) + (VerificationTypes.ITEM_Integer,), ] assert a.frames[1].frame_locals == [ (VerificationTypes.ITEM_Object, 17), - (VerificationTypes.ITEM_Integer,) + (VerificationTypes.ITEM_Integer,), ] diff --git a/tests/conftest.py b/tests/conftest.py index 9c595d7..7caee27 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,6 @@ from lawu.classloader import ClassLoader -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def loader() -> ClassLoader: - return ClassLoader(Path(__file__).parent / 'data', max_cache=-1) + return ClassLoader(Path(__file__).parent / "data", max_cache=-1) diff --git a/tests/test_bytecode.py b/tests/test_bytecode.py index c05afbd..c9829e8 100644 --- a/tests/test_bytecode.py +++ b/tests/test_bytecode.py @@ -2,41 +2,48 @@ GOOD_TABLE_SWITCH = [ - Instruction(mnemonic='iconst_1', opcode=4, operands=[], pos=0), - Instruction(mnemonic='tableswitch', opcode=170, operands=[ - # DEFAULT - Operand(OperandTypes.BRANCH, value=30), - # LOW - Operand(OperandTypes.LITERAL, value=1), - # HIGH - Operand(OperandTypes.LITERAL, value=3), - # TABLE - Operand(OperandTypes.BRANCH, value=27), - Operand(OperandTypes.BRANCH, value=28), - Operand(OperandTypes.BRANCH, value=29) - ], pos=1), - Instruction(mnemonic='return', opcode=177, operands=[], pos=28), - Instruction(mnemonic='return', opcode=177, operands=[], pos=29), - Instruction(mnemonic='return', opcode=177, operands=[], pos=30), - Instruction(mnemonic='return', opcode=177, operands=[], pos=31) + Instruction(mnemonic="iconst_1", opcode=4, operands=[], pos=0), + Instruction( + mnemonic="tableswitch", + opcode=170, + operands=[ + # DEFAULT + Operand(OperandTypes.BRANCH, value=30), + # LOW + Operand(OperandTypes.LITERAL, value=1), + # HIGH + Operand(OperandTypes.LITERAL, value=3), + # TABLE + Operand(OperandTypes.BRANCH, value=27), + Operand(OperandTypes.BRANCH, value=28), + Operand(OperandTypes.BRANCH, value=29), + ], + pos=1, + ), + Instruction(mnemonic="return", opcode=177, operands=[], pos=28), + Instruction(mnemonic="return", opcode=177, operands=[], pos=29), + Instruction(mnemonic="return", opcode=177, operands=[], pos=30), + Instruction(mnemonic="return", opcode=177, operands=[], pos=31), ] GOOD_LOOKUP_SWITCH = [ - Instruction(mnemonic='iconst_1', opcode=4, operands=[], pos=0), - Instruction(mnemonic='lookupswitch', opcode=171, operands=[ - {1: 27, 3: 28}, - Operand(op_type=OperandTypes.BRANCH, value=29) - ], pos=1), - Instruction(mnemonic='return', opcode=177, operands=[], pos=28), - Instruction(mnemonic='return', opcode=177, operands=[], pos=29), - Instruction(mnemonic='return', opcode=177, operands=[], pos=30) + Instruction(mnemonic="iconst_1", opcode=4, operands=[], pos=0), + Instruction( + mnemonic="lookupswitch", + opcode=171, + operands=[{1: 27, 3: 28}, Operand(op_type=OperandTypes.BRANCH, value=29)], + pos=1, + ), + Instruction(mnemonic="return", opcode=177, operands=[], pos=28), + Instruction(mnemonic="return", opcode=177, operands=[], pos=29), + Instruction(mnemonic="return", opcode=177, operands=[], pos=30), ] def test_table_switch(loader): # Ensure we can both read and write table switch opcodes. - cf = loader['TableSwitch'] - main = cf.methods.find_one(name='main') + cf = loader["TableSwitch"] + main = cf.methods.find_one(name="main") instructions = list(main.code.disassemble()) assert instructions == GOOD_TABLE_SWITCH @@ -49,8 +56,8 @@ def test_table_switch(loader): def test_lookup_switch(loader): # Ensure we can both read and write lookup switch opcodes. - cf = loader['LookupSwitch'] - main = cf.methods.find_one(name='main') + cf = loader["LookupSwitch"] + main = cf.methods.find_one(name="main") instructions = list(main.code.disassemble()) assert instructions == GOOD_LOOKUP_SWITCH @@ -62,7 +69,7 @@ def test_lookup_switch(loader): def test_compare(): - ins = Instruction.create('return') - assert ins == 'return' + ins = Instruction.create("return") + assert ins == "return" assert ins == ins - assert ins != 'not_return' + assert ins != "not_return" diff --git a/tests/test_classloader.py b/tests/test_classloader.py index fc04305..55929fc 100644 --- a/tests/test_classloader.py +++ b/tests/test_classloader.py @@ -13,42 +13,38 @@ def test_load_from_class(): """Ensure we can add ClassFile's directly to the ClassLoader.""" cl = ClassLoader() - cf = ClassFile(this='TestClass') + cf = ClassFile(this="TestClass") cl.update(cf) - assert cl.load('TestClass') is cf + assert cl.load("TestClass") is cf def test_default_bytecode_transforms(): cl = ClassLoader(bytecode_transforms=[simple_swap]) - cf = ClassFile(this='TestClass') + cf = ClassFile(this="TestClass") cl.update(cf) - test_method = cf.methods.create('test', '(V)V;', code=True) + test_method = cf.methods.create("test", "(V)V;", code=True) test_method.code.max_stack = 2 test_method.code.max_locals = 0 - test_method.code.assemble(assemble([ - ('iconst_0',), - ('pop',), - ('return',) - ])) + test_method.code.assemble(assemble([("iconst_0",), ("pop",), ("return",)])) # Load from the ClassLoader to bind to it. - cf = cl.load('TestClass') + cf = cl.load("TestClass") # Ensure the defaults apply. ins_iter = test_method.code.disassemble() ins = next(ins_iter) - assert ins.mnemonic == 'bipush' + assert ins.mnemonic == "bipush" assert len(ins.operands) == 1 assert ins.operands[0].value == 0 # Ensure we can override the default. ins_iter = test_method.code.disassemble(transforms=[]) ins = next(ins_iter) - assert ins.mnemonic == 'iconst_0' + assert ins.mnemonic == "iconst_0" assert len(ins.operands) == 0 @@ -56,48 +52,39 @@ def test_load_from_directory(): """Ensure we can load a ClassFile from a simple directory.""" with tempfile.TemporaryDirectory() as directory: shutil.copy( - os.path.join( - os.path.dirname(__file__), - 'data', - 'HelloWorld.class' - ), - directory + os.path.join(os.path.dirname(__file__), "data", "HelloWorld.class"), + directory, ) cl = ClassLoader() cl.update(directory) - assert isinstance(cl.load('HelloWorld'), cl.klass) + assert isinstance(cl.load("HelloWorld"), cl.klass) def test_load_from_zipfile(): """Ensure we can load a ClassFile from a ZipFile.""" - with tempfile.NamedTemporaryFile(suffix='.jar') as tmp: - with zipfile.ZipFile(tmp, 'w') as zf: + with tempfile.NamedTemporaryFile(suffix=".jar") as tmp: + with zipfile.ZipFile(tmp, "w") as zf: zf.write( - os.path.join( - os.path.dirname(__file__), - 'data', - 'HelloWorld.class' - ), - arcname='HelloWorld.class' - + os.path.join(os.path.dirname(__file__), "data", "HelloWorld.class"), + arcname="HelloWorld.class", ) cl = ClassLoader() cl.update(tmp.name) - assert isinstance(cl.load('HelloWorld'), cl.klass) + assert isinstance(cl.load("HelloWorld"), cl.klass) def test_contains(loader): - assert 'HelloWorld' in loader + assert "HelloWorld" in loader def test_dependencies(loader): - assert loader.dependencies('HelloWorld') == { - 'java/lang/Object', - 'java/io/PrintStream', - 'HelloWorld', - 'java/lang/System' + assert loader.dependencies("HelloWorld") == { + "java/lang/Object", + "java/io/PrintStream", + "HelloWorld", + "java/lang/System", } diff --git a/tests/test_expand_constants.py b/tests/test_expand_constants.py index ac60e50..8e15499 100644 --- a/tests/test_expand_constants.py +++ b/tests/test_expand_constants.py @@ -3,7 +3,7 @@ def test_expand_constants(loader): - cf = loader['HelloWorld'] - main = cf.methods.find_one(name='main') + cf = loader["HelloWorld"] + main = cf.methods.find_one(name="main") ins = list(main.code.disassemble(transforms=[expand_constants])) assert isinstance(ins[0].operands[0], FieldReference) diff --git a/tests/test_hello_world.py b/tests/test_hello_world.py index a500ef0..13cbc4d 100644 --- a/tests/test_hello_world.py +++ b/tests/test_hello_world.py @@ -16,14 +16,14 @@ class HelloWorld { } } """ - cf = loader['HelloWorld'] + cf = loader["HelloWorld"] assert len(cf.constants) == 21 assert len(cf.attributes) == 0 assert len(cf.fields) == 0 assert len(cf.methods) == 1 - main_method = cf.methods.find_one(name='main') + main_method = cf.methods.find_one(name="main") assert main_method is not None # 0x08 for ACC_STATIC, 0x01 for ACC_PUBLIC @@ -33,14 +33,20 @@ class HelloWorld { instruction_list = list(main_method.code.disassemble()) assert instruction_list == [ - Instruction(mnemonic='getstatic', opcode=178, operands=[ - Operand(op_type=30, value=13) - ], pos=0), - Instruction(mnemonic='ldc', opcode=18, operands=[ - Operand(op_type=30, value=15) - ], pos=3), - Instruction(mnemonic='invokevirtual', opcode=182, operands=[ - Operand(op_type=30, value=21) - ], pos=5), - Instruction(mnemonic='return', opcode=177, operands=[], pos=8) + Instruction( + mnemonic="getstatic", + opcode=178, + operands=[Operand(op_type=30, value=13)], + pos=0, + ), + Instruction( + mnemonic="ldc", opcode=18, operands=[Operand(op_type=30, value=15)], pos=3 + ), + Instruction( + mnemonic="invokevirtual", + opcode=182, + operands=[Operand(op_type=30, value=21)], + pos=5, + ), + Instruction(mnemonic="return", opcode=177, operands=[], pos=8), ]