diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE
new file mode 100644
index 0000000000..31b549a89c
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE
@@ -0,0 +1,5 @@
+Hi! This project does not accept pull requests.
+
+Please see the guidelines for contribution on how to file issues or provide patches:
+
+https://github.com/clojure/clojure/blob/master/CONTRIBUTING.md
diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml
new file mode 100644
index 0000000000..11ff789629
--- /dev/null
+++ b/.github/workflows/doc-build.yml
@@ -0,0 +1,75 @@
+name: Build API Docs
+
+permissions:
+ contents: write
+
+on:
+ workflow_dispatch:
+ inputs:
+ commit:
+ description: 'Commit changes to gh-pages branch'
+ type: boolean
+ required: true
+ default: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ java-version: 8
+ distribution: 'temurin'
+
+ - name: Set up Clojure
+ uses: DeLaGuardo/setup-clojure@13.1
+ with:
+ cli: 'latest'
+
+ - name: Cache clojure dependencies
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.m2/repository
+ ~/.gitlibs
+ key: cljdeps-${{ hashFiles('deps.edn') }}
+ restore-keys: cljdeps-
+
+ - name: Clone clojure api doc repo
+ uses: actions/checkout@v4
+ with:
+ repository: clojure/clojure-api-doc
+ path: clojure-api-doc
+ fetch-depth: 0
+
+ - name: Clone clojure source code repo into clojure-api-doc
+ uses: actions/checkout@v4
+ with:
+ path: clojure-api-doc/repo
+ fetch-depth: 0
+
+ - name: Clone clojure gh-pages branch into clojure-api-doc
+ uses: actions/checkout@v4
+ with:
+ repository: clojure/clojure
+ path: clojure-api-doc/repo-docs
+ ref: 'gh-pages'
+ fetch-depth: 0
+
+ - name: Install markdown
+ run: sudo apt install markdown
+
+ - name: Call clojure-api-doc build.sh
+ run: bash ${GITHUB_WORKSPACE}/clojure-api-doc/build.sh
+
+ - name: Commit
+ if: ${{inputs.commit}}
+ run: |
+ git config --global user.name clojure-build
+ git config --global user.email "clojure-build@users.noreply.github.com"
+ cd clojure-api-doc/repo-docs
+ git add -u -v
+ git commit -m "Autodoc commit"
+ git push origin gh-pages
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000..286cf956d5
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,22 @@
+name: Release on demand
+
+permissions:
+ contents: write
+
+on:
+ workflow_dispatch:
+ inputs:
+ releaseVersion:
+ description: "Version to release"
+ required: true
+ snapshotVersion:
+ description: "Snapshot version after release"
+ required: true
+
+jobs:
+ call-release:
+ uses: clojure/build.ci/.github/workflows/release.yml@master
+ with:
+ releaseVersion: ${{ github.event.inputs.releaseVersion }}
+ snapshotVersion: ${{ github.event.inputs.snapshotVersion }}
+ secrets: inherit
\ No newline at end of file
diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml
new file mode 100644
index 0000000000..9fdad8c695
--- /dev/null
+++ b/.github/workflows/snapshot.yml
@@ -0,0 +1,11 @@
+name: Snapshot on demand
+
+permissions:
+ contents: read
+
+on: [workflow_dispatch]
+
+jobs:
+ call-snapshot:
+ uses: clojure/build.ci/.github/workflows/snapshot.yml@master
+ secrets: inherit
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000..ebbb36eb2c
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,29 @@
+name: Test
+
+permissions:
+ contents: read
+
+on:
+ push:
+ workflow_dispatch:
+
+jobs:
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest] # macOS-latest, windows-latest]
+ java-version: ["8", "11", "17", "21", "25"]
+ distribution: ["temurin", "corretto"]
+ profile: ["test-direct", "test-no-direct"]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ java-version: ${{ matrix.java-version }}
+ distribution: ${{ matrix.distribution }}
+ cache: 'maven'
+ - name: Build with Maven
+ run: mvn -ntp -B -P${{ matrix.profile }} clean test
diff --git a/.gitignore b/.gitignore
index 5376efaf3f..18cf4cc05c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ nbproject/private/
maven-classpath
maven-classpath.properties
.idea/
+*.iml
diff --git a/.idea/libraries/Maven__org_clojure_data_generators_0_1_2.xml b/.idea/libraries/Maven__org_clojure_data_generators_0_1_2.xml
deleted file mode 100644
index 78f485eeb9..0000000000
--- a/.idea/libraries/Maven__org_clojure_data_generators_0_1_2.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/Maven__org_clojure_test_generative_0_4_0.xml b/.idea/libraries/Maven__org_clojure_test_generative_0_4_0.xml
deleted file mode 100644
index 78fac6dfd0..0000000000
--- a/.idea/libraries/Maven__org_clojure_test_generative_0_4_0.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 06b2fc0581..84f0aee84b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1 +1,42 @@
-If you'd like to submit a patch, please follow the [contributing guidelines](http://clojure.org/contributing).
+Hi! Thanks for your interest in Clojure!
+
+## I want to ask a question
+
+If you have a question about Clojure, please use the official Ask Clojure forum at https://ask.clojure.org. This forum is monitored by the Clojure maintainers.
+
+## I want to discuss an idea
+
+There are many interactive Clojure forums for discussion and you can find a list at [Clojure Discussion](https://clojure.org/community/resources#_clojure_discussion).
+
+## I want to file a bug / suggest an enhancement
+
+Please file it as a question on https://ask.clojure.org with the tag "problem" (possible bugs) or "request" (enhancements).
+
+## I want to provide a patch / PR
+
+If you would like to contribute patches, the Clojure dev process is described in detail at https://clojure.org/dev.
+
+In short, this process requires:
+
+- [Signing the Contributor Agreement](https://clojure.org/dev/contributor_agreement)
+- [Requesting jira access](https://clojure.atlassian.net/servicedesk/customer/portal/1)
+
+This project does not accept pull requests.
+
+## I am looking for official documentation
+
+You can find official documentation on the Clojure web site:
+
+* Reference docs https://clojure.org/reference
+* Tutorials and guides: https://clojure.org/guides
+* API: https://clojure.org/api/api
+
+## What release should I use?
+
+Find the current release info here:
+
+https://clojure.org/releases/downloads
+
+A list of all releases can be found here:
+
+https://clojure.org/releases/downloads_older
diff --git a/build.xml b/build.xml
index 186b666444..1464b69ed3 100644
--- a/build.xml
+++ b/build.xml
@@ -28,6 +28,8 @@
+
+
@@ -39,7 +41,7 @@
description="Compile Java sources.">
+ debug="true" source="1.8" target="1.8"/>
-
+
+
+
@@ -73,10 +77,20 @@
+
+
+
+
+
+
+
+
+
+
@@ -85,16 +99,20 @@
unless="maven.test.skip">
+ debug="true" source="1.8" target="1.8" includeantruntime="no"/>
+ Direct linking = ${directlinking}
+
+
+
@@ -104,6 +122,9 @@
depends="compile-tests"
unless="maven.test.skip">
+
+
@@ -120,6 +141,7 @@
depends="compile-tests"
unless="maven.test.skip">
+
@@ -183,4 +205,11 @@
+
+
+
+
+
+
+
diff --git a/changes.md b/changes.md
index 684bf7563a..3244776da9 100644
--- a/changes.md
+++ b/changes.md
@@ -1,10 +1,1202 @@
-# Changes to Clojure in Version 1.7
+# Changes to Clojure in Version 1.12.5
+
+* [CLJ-2945](https://clojure.atlassian.net/browse/CLJ-2945) - reify - incorrectly transfers reader metadata to runtime object
+* [CLJ-2228](https://clojure.atlassian.net/browse/CLJ-2228) - constantly - unroll to remove rest args allocation
+
+# Changes to Clojure in Version 1.12.4
+
+* [CLJ-2924](https://clojure.atlassian.net/browse/CLJ-2924) - LazySeq - fix visibility issues with non-volatile reads
+
+# Changes to Clojure in Version 1.12.3
+
+* [CLJ-2919](https://clojure.atlassian.net/browse/CLJ-2919) - Compiler - fix nested compilation emitting for keyword and protocol call sites
+
+# Changes to Clojure in Version 1.12.2
+
+* [CLJ-2914](https://clojure.atlassian.net/browse/CLJ-2914) - Compiler - syntax error if qualified instance method expression is missing instance
+* [CLJ-1798](https://clojure.atlassian.net/browse/CLJ-1798) - Refs - avoid creating RetryEx in LockingTransaction on every transaction
+* [CLJ-2916](https://clojure.atlassian.net/browse/CLJ-2916) - LazySeq - realize before serializing and do not serialize IFn
+* [CLJ-2917](https://clojure.atlassian.net/browse/CLJ-2917) - Iterate - de/serialization no longer supported, throw
+
+# Changes to Clojure in Version 1.12.1
+
+* [CLJ-2899](https://clojure.atlassian.net/browse/CLJ-2899) - Revert change in semantics of qualified symbol in invocation position if field and method of same name
+* [CLJ-2898](https://clojure.atlassian.net/browse/CLJ-2898) - Objects that are both IFn and FunctionalInterface unnecessarily get converted to FI
+* [CLJ-2888](https://clojure.atlassian.net/browse/CLJ-2888) - `gen-class` - did not support new array class symbol syntax
+* [CLJ-2886](https://clojure.atlassian.net/browse/CLJ-2886) - `add-libs` - send only procurer keys to tool invocation
+* [CLJ-2906](https://clojure.atlassian.net/browse/CLJ-2906) - Add missing :added metadata to 1.12 functions
+
+# Changes to Clojure in Version 1.12.0
+
+## 1 Compatibility
+
+### 1.1 Java 8 - Compatiblity EOL notice
+
+Clojure 1.12 produces Java 8 bytecode (same as Clojure 1.10 and 1.11), but this is expected to be the last release using a Java 8 baseline. Future releases will move the bytecode and minimum Java compatibility to a newer Java LTS release.
+
+### 1.2 Java 21 - Virtual thread pinning from user code under `synchronized`
+
+Clojure users want to use virtual threads on JDK 21. Prior to 1.12, Clojure lazy-seqs and delays, in order to enforce run-once behavior, ran user code under synchronized blocks, which as of JDK 21 don't yet participate in cooperative blocking. Thus if that code did e.g. blocking I/O it would pin a real thread. JDK 21 may emit warnings for this when using `-Djdk.tracePinnedThreads=full`.
+
+To avoid this pinning, in 1.12 `lazy-seq` and `delay` use locks instead of synchronized blocks.
+
+See: [CLJ-2804](https://clojure.atlassian.net/browse/CLJ-2804)
+
+### 1.3 Security
+
+Fix [CVE-2024-22871](https://nvd.nist.gov/vuln/detail/CVE-2024-22871) detailed in [GHSA-vr64-r9qj-h27f](https://github.com/advisories/GHSA-vr64-r9qj-h27f):
+
+* [CLJ-2839](https://clojure.atlassian.net/browse/CLJ-2839) `iterate`, `cycle`, `repeat` - infinite seqs have infinite `hashCode()`
+
+### 1.4 Serialization
+
+[CLJ-1327](https://clojure.atlassian.net/browse/CLJ-1327) explicitly sets the Java serialization identifier for the classes in Clojure that implement Java serialization. In Clojure 1.11.0 this changed for two classes unnecessarily and we reverted those changes in Clojure 1.11.1 - this completes that work for the rest of the classes.
+
+Clojure data types have implemented the Java serialization interfaces since Clojure 1.0. Java serialization is designed to save graphs of Java instances into a byte stream. Every class has an identifier (the serialVersionUID) that is automatically generated based on the class name, it's type hierarchy, and the serialized fields. At deserialization time, deserialization can only occur when the available class has an identifier that matches the class id recorded in the serialized bytes.
+
+Clojure has never provided a guarantee of serialization consistency across Clojure versions, but we do not wish to break compatibility any more than necessary and these changes will give us more control over that in the future.
+
+See: [CLJ-1327](https://clojure.atlassian.net/browse/CLJ-1327)
+
+### 1.5 Dependencies
+
+Updated dependencies:
+
+* spec.alpha dependency to 0.5.238 - [changes](https://github.com/clojure/spec.alpha/blob/master/CHANGES.md)
+* core.specs.alpha dependency to 0.4.74 - [changes](https://github.com/clojure/core.specs.alpha/blob/master/CHANGES.md)
+
+See: [CLJ-2852](https://clojure.atlassian.net/browse/CLJ-2852)
+
+## 2 Features
+
+### 2.1 Add libraries for interactive use
+
+There are many development-time cases where it would be useful to add a library interactively without restarting the JVM - speculative evaluation, adding a known dependency to your project, or adding a library to accomplish a specific task.
+
+Clojure now provides new functions to add libraries interactively, without restarting the JVM or losing the state of your work:
+
+* [add-lib](https://clojure.github.io/clojure/branch-master/clojure.repl-api.html#clojure.repl.deps/add-lib) takes a lib that is not available on the classpath, and makes it available by downloading (if necessary) and adding to the classloader. Libs already on the classpath are not updated. If the coordinate is not provided, the newest Maven or git (if the library has an inferred git repo name) version or tag are used.
+* [add-libs](https://clojure.github.io/clojure/branch-master/clojure.repl-api.html#clojure.repl.deps/add-libs) is like `add-lib`, but resolves a set of new libraries and versions together.
+* [sync-deps](https://clojure.github.io/clojure/branch-master/clojure.repl-api.html#clojure.repl.deps/sync-deps) calls `add-libs` with any libs present in deps.edn, but not yet present on the classpath.
+
+These new functions are intended only for development-time interactive use at the repl - using a deps.edn is still the proper way to build and maintain production code. To this end, these functions all check that [\*repl*](https://clojure.github.io/clojure/branch-master/clojure.core-api.html#clojure.core/%2Arepl%2A) is bound to true (that flag is bound automatically by `clojure.main/repl`). In a clojure.main REPL, these new functions are automatically referred in the `user` namespace. In other repls, you may need to `(require '[clojure.repl.deps :refer :all])` before use.
+
+Library resolution and download are provided by [tools.deps](https://github.com/clojure/tools.deps). However, you do not want to add tools.deps and its many dependencies to your project classpath during development, and thus we have also added a new api for invoking functions out of process via the Clojure CLI.
+
+See: [CLJ-2761](https://clojure.atlassian.net/browse/CLJ-2761), [CLJ-2757](https://clojure.atlassian.net/browse/CLJ-2757), [CLJ-2788](https://clojure.atlassian.net/browse/CLJ-2788), [CLJ-2767](https://clojure.atlassian.net/browse/CLJ-2767), [CLJ-2769](https://clojure.atlassian.net/browse/CLJ-2769), [CLJ-2770](https://clojure.atlassian.net/browse/CLJ-2770)
+
+### 2.2 Invoke tool functions out of process
+
+There are many useful tools you can use at development time, but which are not part of your project's actual dependencies. The Clojure CLI provides explicit support for [tools](https://clojure.org/reference/clojure_cli#tools) with their own classpath, but there was not previously a way to invoke these interactively.
+
+Clojure now includes [clojure.tools.deps.interop/invoke-tool](https://clojure.github.io/clojure/branch-master/clojure.tools.deps.interop-api.html#clojure.tools.deps.interop/invoke-tool) to invoke a tool function out of process. The classpath for the tool is defined in deps.edn and you do not need to add the tool's dependencies to your project classpath.
+
+`add-lib` functionality is built using `invoke-tool` but you can also use it to build or invoke your own tools for interactive use. Find more about the function execution protocol on the [CLI reference](https://clojure.org/reference/clojure_cli#function_protocol).
+
+See: [CLJ-2760](https://clojure.atlassian.net/browse/CLJ-2760), [CLJ-2819](https://clojure.atlassian.net/browse/CLJ-2819)
+
+### 2.3 Start and control external processes
+
+For a long time, we've had the `clojure.java.shell` namespace, but over time Java has provided new APIs for process info, process control, and I/O redirection. This release adds a new namespace [clojure.java.process](https://clojure.github.io/clojure/branch-master/index.html#clojure.java.process) that takes advantage of these APIs and is easier to use. See:
+
+* [start](https://clojure.github.io/clojure/branch-master/clojure.java.process-api.html#clojure.java.process/start) - full control over streams with access to the underlying Java objects for advanced usage
+* [exec](https://clojure.github.io/clojure/branch-master/clojure.java.process-api.html#clojure.java.process/exec) - covers the common case of executing an external process and returning its stdout on completion
+
+See: [CLJ-2759](https://clojure.atlassian.net/browse/CLJ-2759), [CLJ-2777](https://clojure.atlassian.net/browse/CLJ-2777), [CLJ-2828](https://clojure.atlassian.net/browse/CLJ-2828), [CLJ-2773](https://clojure.atlassian.net/browse/CLJ-2773), [CLJ-2776](https://clojure.atlassian.net/browse/CLJ-2776), [CLJ-2774](https://clojure.atlassian.net/browse/CLJ-2774), [CLJ-2778](https://clojure.atlassian.net/browse/CLJ-2778), [CLJ-2779](https://clojure.atlassian.net/browse/CLJ-2779), [CLJ-2865](https://clojure.atlassian.net/browse/CLJ-2865)
+
+### 2.4 Method values
+
+Clojure programmers often want to use Java methods in higher-order functions (e.g. passing a Java method to `map`). Until now, programmers have had to manually wrap methods in functions. This is verbose, and might require manual hinting for overload disambiguation, or incur incidental reflection or boxing.
+
+Programmers can now use [qualified methods](#25-qualified-methods---classmethod-classmethod-and-classnew) as ordinary functions in value contexts - the compiler will automatically generate the wrapping function. The compiler will generate a reflective call when a qualified method does not resolve due to overloading. Developers can supply [:param-tags](#26-param-tags-metadata) metadata on qualified methods to specify the signature of a single desired method, 'resolving' it.
+
+See: [CLJ-2793](https://clojure.atlassian.net/browse/CLJ-2793), [CLJ-2844](https://clojure.atlassian.net/browse/CLJ-2844), [CLJ-2835](https://clojure.atlassian.net/browse/CLJ-2835)
+
+### 2.5 Qualified methods - `Class/method`, `Class/.method`, and `Class/new`
+
+Java members inherently exist in a class. For method values we need a way to explicitly specify the class of an instance method because there is no possibility for inference.
+
+Qualified methods have value semantics when used in non-invocation positions:
+
+* `Classname/method` - value is a Clojure function that invokes a static method
+* `Classname/.method` - value is a Clojure function that invokes an instance method
+* `Classname/new` - value is a Clojure function that invokes a constructor
+
+Note: developers must use `Classname/method` and `Classname/.method` syntax to differentiate between static and instance methods.
+
+Qualified method invocations with [param-tags](#26-param-tags-metadata) use only the tags to resolve the method. Without param-tags they behave like the equivalent [dot syntax](https://clojure.org/reference/java_interop#_the_dot_special_form), except the qualifying class takes precedence over hints of the target object, and over its runtime type when invoked via reflection.
+
+Note: Static fields are values and should be referenced without parens unless they are intended as function calls, e.g `(System/out)` should be `System/out`. Future Clojure releases will treat the field's value as something invokable and invoke it.
+
+See: [CLJ-2844](https://clojure.atlassian.net/browse/CLJ-2844), [CLJ-2848](https://clojure.atlassian.net/browse/CLJ-2848), [CLJ-2847](https://clojure.atlassian.net/browse/CLJ-2847), [CLJ-2853](https://clojure.atlassian.net/browse/CLJ-2853), [CLJ-2867](https://clojure.atlassian.net/browse/CLJ-2867)
+
+### 2.6 :param-tags metadata
+
+When used as values, qualified methods supply only the class and method name, and thus cannot resolve overloaded methods.
+
+Developers can supply `:param-tags` metadata on qualified methods to specify the signature of a single desired method, 'resolving' it. The `:param-tags` metadata is a vector of zero or more tags: `[tag ...]`. A tag is any existing valid `:tag` metadata value. Each tag corresponds to a parameter in the desired signature (arity should match the number of tags). Parameters with non-overloaded types can use the placeholder `_` in lieu of the tag. When you supply :param-tags metadata on a qualified method, the metadata must allow the compiler to resolve it to a single method at compile time.
+
+A new metadata reader syntax `^[tag ...]` attaches `:param-tags` metadata to member symbols, just as `^tag` attaches `:tag` metadata to a symbol.
+
+See: [CLJ-2805](https://clojure.atlassian.net/browse/CLJ-2805)
+
+### 2.7 Array class syntax
+
+Clojure supports symbols naming classes both as a value (for class object) and as a type hint, but has not provided syntax for array classes other than strings.
+
+Developers can now refer to an array class using a symbol of the form `ComponentClass/#dimensions`, eg `String/2` refers to the class of a 2 dimensional array of Strings. Component classes can be fully-qualified classes, imported classes, or primitives. Array class syntax can be used as both type hints and values.
+
+Examples: `String/1`, `java.lang.String/1`, `long/2`.
+
+See: [CLJ-2807](https://clojure.atlassian.net/browse/CLJ-2807)
+
+### 2.8 Functional interfaces
+
+Java programs define "functions" with Java functional interfaces (marked with the [@FunctionalInterface](https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html) annotation), which have a single method.
+
+Clojure developers can now invoke Java methods taking functional interfaces by passing functions with matching arity. The Clojure compiler implicitly converts Clojure functions to the required functional interface by constructing a lambda adapter. You can explicitly coerce a Clojure function to a functional interface by hinting the binding name in a `let` binding, e.g. to avoid repeated adapter construction in a loop, e.g. `(let [^java.util.function.Predicate p even?] ...)`.
+
+See: [CLJ-2799](https://clojure.atlassian.net/browse/CLJ-2799), [CLJ-2858](https://clojure.atlassian.net/browse/CLJ-2858), [CLJ-2856](https://clojure.atlassian.net/browse/CLJ-2856), [CLJ-2863](https://clojure.atlassian.net/browse/CLJ-2863), [CLJ-2864](https://clojure.atlassian.net/browse/CLJ-2864)
+
+### 2.9 Java Supplier interop
+
+Calling methods that take a [Supplier](https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html) (a method that supplies a value) had required writing an adapter with reify. Clojure has a "value supplier" interface with semantic support already - `IDeref`. All `IDeref` impls (`delay`, `future`, `atom`, etc) now implement the `Supplier` interface directly.
+
+See: [CLJ-2792](https://clojure.atlassian.net/browse/CLJ-2792), [CLJ-2841](https://clojure.atlassian.net/browse/CLJ-2841)
+
+### 2.10 Streams with seq, into, reduce, and transduce support
+
+Java APIs increasingly return [Stream](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html)s and are hard to consume because they do not implement interfaces that Clojure already supports, and hard to interop with because Clojure doesn't directly implement Java functional interfaces.
+
+In addition to functional interface support, Clojure now provides these functions to interoperate with streams in an idiomatic manner, all functions behave analogously to their Clojure counterparts:
+
+* `(stream-seq! stream) => seq`
+* `(stream-reduce! f [init-val] stream) => val`
+* `(stream-transduce! xf f [init-val] stream) => val`
+* `(stream-into! to-coll [xf] stream) => to-coll`
+
+All of these operations are terminal stream operations (they consume the stream).
+
+See: [CLJ-2775](https://clojure.atlassian.net/browse/CLJ-2775)
+
+### 2.11 PersistentVector implements Spliterable
+
+Java collections implement streams via ["spliterators"](https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html), iterators that can be split for faster parallel traversal. `PersistentVector` now provides a custom spliterator that supports parallelism, with greatly improved performance.
+
+See: [CLJ-2791](https://clojure.atlassian.net/browse/CLJ-2791)
+
+### 2.12 Efficient drop and partition for persistent or algorithmic collections
+
+Partitioning of a collection uses a series of takes (to build a partition) and drops (to skip past that partition). [CLJ-2713](https://clojure.atlassian.net/browse/CLJ-2713) adds a new internal interface (IDrop) indicating that a collection can drop more efficiently than sequential traversal, and implements that for persistent collections and algorithmic collections like `range` and `repeat`. These optimizations are used in `drop`, `nthrest`, and `nthnext`.
+
+Additionally, there are new functions `partitionv`, `partitionv-all`, and `splitv-at` that are more efficient than their existing counterparts and produce vector partitions instead of realized seq partitions.
+
+See: [CLJ-2713](https://clojure.atlassian.net/browse/CLJ-2713), [CLJ-2742](https://clojure.atlassian.net/browse/CLJ-2742), [CLJ-2740](https://clojure.atlassian.net/browse/CLJ-2740), [CLJ-2715](https://clojure.atlassian.net/browse/CLJ-2715), [CLJ-2718](https://clojure.atlassian.net/browse/CLJ-2718), [CLJ-2772](https://clojure.atlassian.net/browse/CLJ-2772), [CLJ-2741](https://clojure.atlassian.net/browse/CLJ-2741)
+
+### 2.13 Var interning policy
+
+[Interning](https://clojure.org/reference/vars#interning) a var in a namespace (vs aliasing) must create a stable reference that is never displaced, so that all references to an interned var get the same object. There were some cases where interned vars could get displaced and those have been tightened up in 1.12.0-alpha1. If you encounter this situation, you'll see a warning like "REJECTED: attempt to replace interned var #'some-ns/foo with #'other-ns/foo in some-ns, you must ns-unmap first".
+
+This addresses the root cause of an issue encountered with Clojure 1.11.0, which added new functions to clojure.core (particularly `abs`). Compiled code from an earlier version of Clojure with var names that matched the newly added functions in clojure.core would be unbound when loaded in a 1.11.0 runtime. In addition to [CLJ-2711](https://clojure.atlassian.net/browse/CLJ-2711), we rolled back a previous fix in this area ([CLJ-1604](https://clojure.atlassian.net/browse/CLJ-1604)).
+
+See: [CLJ-2711](https://clojure.atlassian.net/browse/CLJ-2711)
+
+## 3 Fixes and enhancements
+
+### 3.1 Reader and Compiler
+
+* [CLJ-2726](https://clojure.atlassian.net/browse/CLJ-2726) `#uuid` data reader - Fix exception on invalid input so it flows through reader
+* [CLJ-2813](https://clojure.atlassian.net/browse/CLJ-2813) anonymous function arg reader - no longer accepts invalid arg symbols
+* [CLJ-2843](https://clojure.atlassian.net/browse/CLJ-2843) Reflective calls to Java methods that take primitive long or double now work when passed a narrower boxed number at runtime (Integer, Short, Byte, Float). Previously, these methods were not matched during reflection and an error was thrown.
+* [CLJ-2145](https://clojure.atlassian.net/browse/CLJ-2145) - Fix clearing of closed overs in `^:once` fns
+* [CLJ-2317](https://clojure.atlassian.net/browse/CLJ-2317) - `recur` to head of `:once` fn cancels once
+
+### 3.2 Core
+
+* [CLJ-2739](https://clojure.atlassian.net/browse/CLJ-2739) ArityException - Fix message when function incorrectly called with >20 args
+* [CLJ-2709](https://clojure.atlassian.net/browse/CLJ-2709) `range` - Use optimized range for int args
+* [CLJ-2721](https://clojure.atlassian.net/browse/CLJ-2721) `range` - Fix invalid arg order when adding meta to non-optimized range
+* [CLJ-2683](https://clojure.atlassian.net/browse/CLJ-2683) `with-open` - Fix to not qualify `.close` method on expansion
+* [CLJ-2724](https://clojure.atlassian.net/browse/CLJ-2724) `clojure.java.io/do-copy` - Fix incorrect type hint
+* [CLJ-2640](https://clojure.atlassian.net/browse/CLJ-2640) `ex-info` - now handles nil data map
+* [CLJ-2717](https://clojure.atlassian.net/browse/CLJ-2717) `nthrest` now returns rest output on n=0 or past end of seq
+* [CLJ-1872](https://clojure.atlassian.net/browse/CLJ-1872) `empty?` - adds support for `counted?` collections
+* [CLJ-2694](https://clojure.atlassian.net/browse/CLJ-2694) Fix ratio invariants violated when using Long/MIN_VALUE
+* [CLJ-2568](https://clojure.atlassian.net/browse/CLJ-2568) `clojure.walk/walk` - preserve metadata on lists and seqs
+* [CLJ-2686](https://clojure.atlassian.net/browse/CLJ-2686) `clojure.core.server/parse-props` - Fix exception if system properties concurrently modified during initialization
+* [CLJ-2645](https://clojure.atlassian.net/browse/CLJ-2645) `PrintWriter-on` should support auto-flush, and prepl should use it for the err stream
+* [CLJ-2698](https://clojure.atlassian.net/browse/CLJ-2698) `defprotocol` - ignore unused primitive return type hints
+* [CLJ-2783](https://clojure.atlassian.net/browse/CLJ-2783) replace calls to deprecated URL constructor
+
+### 3.3 Docstrings
+
+* [CLJ-2225](https://clojure.atlassian.net/browse/CLJ-2225) `assert` and `\*assert*` - improve docstrings to add context
+* [CLJ-2290](https://clojure.atlassian.net/browse/CLJ-2290) `into` - add 0- and 1-arity to docstring
+* [CLJ-2552](https://clojure.atlassian.net/browse/CLJ-2552) `reify` - improve docstring and fix example
+* [CLJ-1385](https://clojure.atlassian.net/browse/CLJ-1385) `transient` - include usage model from reference docs
+
+# Changes to Clojure in Version 1.11.4
+
+* [CLJ-2145](https://clojure.atlassian.net/browse/CLJ-2145) - Fix clearing of closed overs in `^:once` fns
+* [CLJ-2317](https://clojure.atlassian.net/browse/CLJ-2317) - `recur` to head of `:once` fn cancels once
+
+# Changes to Clojure in Version 1.11.3
+
+* [CLJ-2843](https://clojure.atlassian.net/browse/CLJ-2843) - Reflective calls to Java methods that take primitive long or double now work when passed a narrower boxed number at runtime (Integer, Short, Byte, Float). Previously, these methods were not matched during reflection and an error was thrown.
+
+# Changes to Clojure in Version 1.11.2
+
+Fixes [CVE-2024-22871](https://nvd.nist.gov/vuln/detail/CVE-2024-22871) detailed in [GHSA-vr64-r9qj-h27f](https://github.com/advisories/GHSA-vr64-r9qj-h27f):
+
+* [CLJ-2839](https://clojure.atlassian.net/browse/CLJ-2839) - `iterate`, `cycle`, `repeat` - infinite seqs have infinite `hashCode()`
+
+# Changes to Clojure in Version 1.11.1
+
+* [CLJ-2701](https://clojure.atlassian.net/browse/CLJ-2701)
+ Pin serialVersionUID for Keyword and ArraySeq back to 1.10.3 values to retain binary serialization
+
+# Changes to Clojure in Version 1.11.0
+
+## 1 Compatibility
+
+### 1.1 Security
+
+Because XML external entity (XXE) attacks can be used to disclose local files using file schemes or relative paths in the system identifier, `clojure.xml/parse` now disables external entity processing by default.
+
+See: https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing
+
+This change disables the following SAX parser features:
+
+* `http://apache.org/xml/features/nonvalidating/load-external-dtd`
+* `http://xml.org/sax/features/external-general-entities`
+* `http://xml.org/sax/features/external-parameter-entities`
+
+If you rely on these features, modify your calls to `clojure.xml/parse` to explicitly
+supply `startparse-sax` function as the final argument:
+`(clojure.xml/parse the-string clojure.xml/startparse-sax)`
+This modification also works on prior Clojure versions.
+
+* [CLJ-2611](http://dev.clojure.org/jira/browse/CLJ-2611) clojure.xml now disables XXE processing by default
+
+### 1.2 Dependencies
+
+Updated dependencies:
+
+* spec.alpha dependency to 0.3.218 - [changes](https://github.com/clojure/spec.alpha/blob/master/CHANGES.md)
+* core.specs.alpha dependency to 0.2.62 - [changes](https://github.com/clojure/core.specs.alpha/blob/master/CHANGES.md)
+
+## 2 Features
+
+### 2.1 Keyword argument functions take a trailing map
+
+Keyword arguments are optional trailing variadic arguments of the form *akey aval bkey bval...*.
+In Clojure 1.11, functions taking keyword arguments can now be passed a map instead of or in addition
+to and following the key/value pairs. When a lone map is passed, it is used for destructuring, else
+a trailing map is added to the key/value pair map by `conj`.
+
+Also see: https://clojure.org/news/2021/03/18/apis-serving-people-and-programs
+
+* [CLJ-2603](https://clojure.atlassian.net/browse/CLJ-2603) Clojure keyword argument functions now also accept a map
+
+### 2.2 `:as-alias` in `require`
+
+Spec (and other libs) rely on qualified keywords as spec names.
+Namespace aliasing in `ns` makes long names shorter but required namespaces to be loadable.
+This change adds `:as-alias` to `require`, which is like `:as` but does not require the namespace to load.
+
+* [CLJ-2123](https://clojure.atlassian.net/browse/CLJ-2123) Add :as-alias option to require like :as but not load
+* [CLJ-2665](https://clojure.atlassian.net/browse/CLJ-2665) Fix require with :as and :as-alias to load
+
+## 3 New functions and namespaces
+
+### 3.1 clojure.math and numeric helper functions
+
+Added a new clojure.math namespace which provides wrappers for the functions available in java.lang.Math.
+These functions are narrowed to only `long` and `double` overloads and provide primitive support without reflection.
+
+In addition, the following functions were added to clojure.core:
+
+* `abs` - absolute value in optimized form for all Clojure numeric types (long, double, ratio, bigint, bigdecimal)
+* `NaN?` - predicate for doubles to check whether "not a number"
+* `infinite?` - predicate for doubles to check whether positive or negative infinity
+
+* [CLJ-2668](https://clojure.atlassian.net/browse/CLJ-2668) Add NaN? and infinite? predicates
+* [CLJ-2664](https://clojure.atlassian.net/browse/CLJ-2664) Add clojure.java.math namespace, wrappers for java.lang.Math
+* [CLJ-2673](https://clojure.atlassian.net/browse/CLJ-2673) Add `abs`, and update `min` and `max` to use Math impls when possible
+* [CLJ-2677](https://clojure.atlassian.net/browse/CLJ-2677) clojure.math - fix method reflection in bodies and inlines, fix docstrings, renamed
+* [CLJ-2689](https://clojure.atlassian.net/browse/CLJ-2689) Fix clojure.math tests to be more tolerant of floating point comparisons
+
+### 3.2 Parser functions
+
+Added the following parsing functions to clojure.core:
+
+* `parse-double` - parses floating point number, including scientific notation
+* `parse-long` - parses integer in long range
+* `parse-boolean` - parses `"true"` or `"false"` to the canonical boolean values
+* `parse-uuid` - parses a UUID string to java.util.UUID
+
+All of these functions expect a string argument and return either the parsed value or `nil` if the value
+is in invalid format.
+
+* [CLJ-2667](https://clojure.atlassian.net/browse/CLJ-2667) Add functions to parse a single long/double/uuid/boolean from a string
+
+### 3.2 `random-uuid`
+
+Added `random-uuid`, a function to construct a random java.util.UUID.
+
+* [CLJ-1925](https://clojure.atlassian.net/browse/CLJ-1925) Add random-uuid
+
+### 3.3 `update-keys` and `update-vals`
+
+Added:
+
+* `update-keys` - applies a function to every key in a map, `m f => {(f k) v ...}`
+* `update-vals` - applies a function to every value in a map, `m f => {k (f v) ...}`
+
+* [CLJ-1959](https://clojure.atlassian.net/browse/CLJ-1959) Add implementation of update-keys
+* [CLJ-2651](https://clojure.atlassian.net/browse/CLJ-2651) Add implementation of update-vals
+
+### 3.4 `iteration`
+
+Added `iteration`, to repeatedly apply a (possibly impure) step function with continuation state.
+This can be used e.g. to consume APIs that return paginated or batched data.
+
+* [CLJ-2555](https://clojure.atlassian.net/browse/CLJ-2555) Add `iteration` generator function
+* [CLJ-2690](https://clojure.atlassian.net/browse/CLJ-2690) Improve `iteration` docstring and arg names
+* [CLJ-2685](https://clojure.atlassian.net/browse/CLJ-2685) Fix `iteration` generative test failure
+
+## 4 Fixes
+
+### 4.1 Compiler
+
+* [CLJ-2680](https://clojure.atlassian.net/browse/CLJ-2680) Fix type hinting a primitive local with matching type hint to not error
+* [CLJ-1180](https://clojure.atlassian.net/browse/CLJ-1180) Fix resolution of class type hints in `defprotocol`
+* [CLJ-1973](https://clojure.atlassian.net/browse/CLJ-1973) Make order of emitted protocol methods in generated classes reproducible
+
+### 4.2 Core
+
+* [CLJ-1879](https://clojure.atlassian.net/browse/CLJ-1879) IKVReduce - make IPersistentMap case faster and extend to Object, detaching it from any fully enumerable set of types
+* [CLJ-2065](https://clojure.atlassian.net/browse/CLJ-2065) IKVReduce - add direct support for SubVector
+* [CLJ-2663](https://clojure.atlassian.net/browse/CLJ-2663) Fix vector `=` not terminating when called with infinite sequence
+* [CLJ-2679](https://clojure.atlassian.net/browse/CLJ-2679) Fix hash collisions in `case` expressions on symbols
+* [CLJ-2600](https://clojure.atlassian.net/browse/CLJ-2600) Don't block `realized?` of `delay` on pending result
+* [CLJ-2649](https://clojure.atlassian.net/browse/CLJ-2649) Fix order of checks in `some-fn` and `every-pred` for 3 predicate case to match other unrollings
+* [CLJ-2234](https://clojure.atlassian.net/browse/CLJ-2234) Fix multimethod preferences to correctly use local hierarchy when it exists
+* [CLJ-2556](https://clojure.atlassian.net/browse/CLJ-2556) Fix `into` completion so `halt-when` works
+
+### 4.3 Performance
+
+* [CLJ-1808](https://clojure.atlassian.net/browse/CLJ-1808) `map-invert` should use `reduce-kv` and transient
+* [CLJ-2621](https://clojure.atlassian.net/browse/CLJ-2621) Fix unnecessary boxing of unused return in statement context for instance method expr
+* [CLJ-2670](https://clojure.atlassian.net/browse/CLJ-2670) Use Math.exact... methods for checked long math ops for performance
+* [CLJ-2636](https://clojure.atlassian.net/browse/CLJ-2636) Get rid of reflection on java.util.Properties when defining `*clojure-version*`
+* [CLJ-1509](https://clojure.atlassian.net/browse/CLJ-1509) AOT compile clojure.instant, clojure.uuid, clojure.core.reducers in build
+
+### 4.4 Error messages
+
+* [CLJ-2529](https://clojure.atlassian.net/browse/CLJ-2529) Fix incorrect reporting of runtime errors as compiler errors in calls through `Compiler.load()`
+* [CLJ-2350](https://clojure.atlassian.net/browse/CLJ-2350) Improve keyword arity exception message
+
+### 4.5 Docstrings
+
+* [CLJ-2249](https://clojure.atlassian.net/browse/CLJ-2249) Clarify `get` docstring regarding sets, strings, arrays, ILookup
+* [CLJ-2488](https://clojure.atlassian.net/browse/CLJ-2488) Add definition to `reify` docstring
+* [CLJ-1360](https://clojure.atlassian.net/browse/CLJ-1360) Update `clojure.string/split` docstring regarding trailing empty parts
+* [CLJ-2444](https://clojure.atlassian.net/browse/CLJ-2444) Fix typo in `test-vars` docstring
+* [CLJ-2666](https://clojure.atlassian.net/browse/CLJ-2666) Make Clojure Java API javadoc text match the example
+
+### 4.6 Other enhancements
+
+* [CLJ-2493](https://clojure.atlassian.net/browse/CLJ-2493) clojure.java.browse - Fix `browse-url` hanging on call to xdg-open
+* [CLJ-1908](https://clojure.atlassian.net/browse/CLJ-1908) clojure.test - Add `run-test` and `run-test-var` to run single test with fixtures and report
+* [CLJ-1379](https://clojure.atlassian.net/browse/CLJ-1379) clojure.test - Fix quoting of `:actual` form in `:pass` maps
+* [CLJ-2620](https://clojure.atlassian.net/browse/CLJ-2620) clojure.server - Fix asymmetric handling of `:exception` `:val`s in `prepl`
+* [CLJ-2387](https://clojure.atlassian.net/browse/CLJ-2387) clojure.server - Fix off-by-one in socket server port validation
+
+
+# Changes to Clojure in Version 1.10.3
+
+## 1 Changes reverted
+
+* [CLJ-2564](https://clojure.atlassian.net/browse/CLJ-2564)
+ Improve error message for case
+
+## 2 Fixes
+
+* [CLJ-2453](https://clojure.atlassian.net/browse/CLJ-2453)
+ Enable reader conditionals in Clojure prepl
+
+# Changes to Clojure in Version 1.10.2
+
+## 1 Dependencies
+
+Updated dependencies:
+
+* spec.alpha dependency to 0.2.194 - [changes](https://github.com/clojure/spec.alpha/blob/master/CHANGES.md)
+* core.specs.alpha dependency to 0.2.56 - [changes](https://github.com/clojure/core.specs.alpha/blob/master/CHANGES.md)
+
+## 2 Fixes
+
+## 2.1 Interop / JVM
+
+* [CLJ-1472](https://clojure.atlassian.net/browse/CLJ-1472)
+ Ensure monitor object is on stack, for verifiers
+* [CLJ-2517](https://clojure.atlassian.net/browse/CLJ-2517)
+ More fixes for invocation of static interface methods with primitive args
+* [CLJ-2492](https://clojure.atlassian.net/browse/CLJ-2492)
+ Remove uses of deprecated Class.newInstance()
+* [CLJ-2534](https://clojure.atlassian.net/browse/CLJ-2534)
+ Fix javadoc urls for JDK 11+
+* [CLJ-2571](https://clojure.atlassian.net/browse/CLJ-2571)
+ Add Throwable return type hint to ex-cause
+* [CLJ-2572](https://clojure.atlassian.net/browse/CLJ-2572)
+ Avoid reflection in clojure.data
+* [CLJ-2502](https://clojure.atlassian.net/browse/CLJ-2502)
+ Fix reflection warnings in clojure.stacktrace/print-stack-trace
+* [CLJ-2597](https://clojure.atlassian.net/browse/CLJ-2597)
+ proxy should emit Java 1.8 bytecode
+
+## 2.2 Core
+
+* [CLJ-2580](https://clojure.atlassian.net/browse/CLJ-2580)
+ Fix case expression branch analysis that resulted in compilation error
+* [CLJ-2564](https://clojure.atlassian.net/browse/CLJ-2564)
+ Improve error message for case
+* [CLJ-2585](https://clojure.atlassian.net/browse/CLJ-2585)
+ nth with not-found on regex matcher returns not-found on last group index
+* [CLJ-1364](https://clojure.atlassian.net/browse/CLJ-1364)
+ vector-of does not implement equals or hashing methods
+* [CLJ-2549](https://clojure.atlassian.net/browse/CLJ-2549)
+ vector-of does not implement IObj for metadata
+* [CLJ-1187](https://clojure.atlassian.net/browse/CLJ-1187)
+ quoted metadata on empty literal colls is lost
+* [CLJ-2459](https://clojure.atlassian.net/browse/CLJ-2459)
+ ExceptionInInitializerError if jars executed with java -jar
+
+## 2.3 Printing
+
+* [CLJ-2469](https://clojure.atlassian.net/browse/CLJ-2469)
+ Fix errors in printing some maps with namespace syntax
+* [CLJ-1445](https://clojure.atlassian.net/browse/CLJ-1445)
+ pprint doesn't print collection metadata when `*print-meta*` is true
+
+## 2.4 Docstrings
+
+* [CLJ-2295](https://clojure.atlassian.net/browse/CLJ-2295)
+ Eliminate duplicate doc string printing for special forms
+* [CLJ-2495](https://clojure.atlassian.net/browse/CLJ-2495)
+ prepl docstring is incorrect
+* [CLJ-2169](https://clojure.atlassian.net/browse/CLJ-2169)
+ conj has out-of-date :arglists
+
+## 3 Performance
+
+* [CLJ-1005](https://clojure.atlassian.net/browse/CLJ-1005)
+ Use transient map in zipmap
+
+# Changes to Clojure in Version 1.10.1
+
+## 1 Features and Major Changes
+
+### 1.1 Workaround Java Performance Regression When Loading user.clj
+
+Recent builds of Java 8 (u202), 11 (11.0.2), 12, and 13 included
+some changes that [drastically affect](https://bugs.openjdk.java.net/browse/JDK-8219233)
+optimization performance of calls from static initializers to static fields.
+Clojure provides support for loading code on startup from a user.clj file and this
+occurred in the static initializer of the Clojure runtime (RT) class and was thus
+affected.
+
+This issue may eventually be resolved in Java, but in Clojure we have
+modified runtime initialization to avoid loading user.clj in a static
+initializer, which mitigates the case where this caused a performance
+degradation.
+
+* [CLJ-2484](https://clojure.atlassian.net/browse/CLJ-2484)
+ Significant performance regression of code loaded in user.clj in Java 8u202/11.0.
+
+### 1.2 clojure.main Error Reporting
+
+clojure.main is frequently used as a Clojure program launcher by external tools.
+Previously, uncaught exceptions would be automatically printed by the JVM, which
+would also print the stack trace.
+
+This release will now catch exceptions and use the same error triage and printing
+functionality as the Clojure repl. The full stack trace, ex-info, and other
+information will be printed to a target specified by the configuration.
+
+The three available error targets are:
+
+* file - write to a temp file (default, falls back to stderr)
+* stderr - write to stderr stream
+* none - don't write
+
+These error targets can be specified either as options to clojure.main, or as
+Java system properties (flags take precedence). When invoking clojure.main
+(or using the clj tool), use `--report `. For Java system property,
+use `-Dclojure.main.report=`.
+
+* [CLJ-2463](https://clojure.atlassian.net/browse/CLJ-2463)
+ Improve error printing in clojure.main with -m, -e, etc
+* [CLJ-2497](https://clojure.atlassian.net/browse/CLJ-2497)
+ Put error report location on its own line
+* [CLJ-2504](https://clojure.atlassian.net/browse/CLJ-2504)
+ Provide more options for error reporting in clojure.main
+
+## 2 Fixes
+
+* [CLJ-2499](http://dev.clojure.org/jira/browse/CLJ-2499)
+ Some compiler expr evals report as wrong error phase
+* [CLJ-2491](https://clojure.atlassian.net/browse/CLJ-2491)
+ Updated fragile tests so Clojure test suite runs on Java 12
+
+# Changes to Clojure in Version 1.10
+
+## 1 Compatibility and Dependencies
+
+### 1.1 Java
+
+Clojure 1.10 now requires Java 8 or above. There were a number of updates related to this change and/or Java compatibility fixes for Java 8, 9, 10, and 11:
+
+* [CLJ-2363](http://dev.clojure.org/jira/browse/CLJ-2363)
+ Bump to Java 8 as minimum requirement, update embedded ASM to 6.2,
+ remove reliance on jsr166 jar, update javadoc links, and remove
+ conditional logic.
+* [CLJ-2367](http://dev.clojure.org/jira/browse/CLJ-2367)
+ ASM regression fix
+* [CLJ-2284](http://dev.clojure.org/jira/browse/CLJ-2284)
+ Fix invalid bytecode generation for static interface method calls in Java 9+
+* [CLJ-2066](http://dev.clojure.org/jira/browse/CLJ-2066)
+ Add reflection fallback for --illegal-access warnings in Java 9+
+* [CLJ-2330](http://dev.clojure.org/jira/browse/CLJ-2330)
+ Fix brittle test that fails on Java 10 build due to serialization drift
+* [CLJ-2374](http://dev.clojure.org/jira/browse/CLJ-2374)
+ Add type hint to address reflection ambiguity in JDK 11
+* [CLJ-2375](http://dev.clojure.org/jira/browse/CLJ-2375)
+ Fix usage of deprecated JDK apis
+* [CLJ-2414](http://dev.clojure.org/jira/browse/CLJ-2414)
+ Regression in reflectively finding default methods
+
+### 1.2 Dependencies
+
+Updated dependencies:
+
+* spec.alpha dependency to 0.2.176 - [changes](https://github.com/clojure/spec.alpha/blob/master/CHANGES.md)
+* core.specs.alpha dependency to 0.2.44 - [changes](https://github.com/clojure/core.specs.alpha/blob/master/CHANGES.md)
+
+## 2 Features and major changes
+
+### 2.1 Error messages
+
+Clojure errors can occur in several distinct "phases" - reading source, macroexpansion, compilation, execution, and result printing. Clojure (and the REPL) now identify these phases in the exception and the message.
+
+The read/macroexpand/compile phases produce a CompilerException and indicate the location in the caller source code where the problem occurred (previously macroexpansion reported the error in the macroexpansion stack). CompilerException now implements IExceptionInfo and ex-data will report exception data including the following (optional) keys:
+
+* :clojure.error/phase - phase (:read-source, :macro-syntax-check, :macroexpansion, :compile-syntax-check, :compilation, :execution, :read-eval-result, :print-eval-result)
+* :clojure.error/source - source file
+* :clojure.error/line - line in source file
+* :clojure.error/column - column of line in source file
+* :clojure.error/symbol - symbol being macroexpanded or compiled
+* :clojure.error/class - cause exception class symbol
+* :clojure.error/cause - cause exception message
+* :clojure.error/spec - explain-data for spec errors
+
+clojure.main also contains two new functions: `ex-triage` and `ex-str` that can be used by external tools to mimic some or all of the Clojure repl reporting. `ex-triage` takes the output of `Throwable->map` and produces a concise analysis of the error phase, cause, etc (same keys as above). `ex-str` takes that analysis data and produces a message to print at the repl.
+
+* [CLJ-2373](http://dev.clojure.org/jira/browse/CLJ-2373)
+ Detect phase and overhaul exception message and printing
+* [CLJ-2415](http://dev.clojure.org/jira/browse/CLJ-2415)
+ Error cause should always be on 2nd line of error message
+* [CLJ-2420](http://dev.clojure.org/jira/browse/CLJ-2420)
+ Refinement of error phases, `ex-triage`, execution error line reporting
+* [CLJ-2427](http://dev.clojure.org/jira/browse/CLJ-2427)
+ CompilerException.toString() can throw if making message during initialization
+* [CLJ-2430](http://dev.clojure.org/jira/browse/CLJ-2430)
+ Elevate phase in throwable data and conveyance for prepl
+* [CLJ-2435](http://dev.clojure.org/jira/browse/CLJ-2435)
+ Include root cause class name in compilation and macroexpansion error phases
+* [CLJ-2438](http://dev.clojure.org/jira/browse/CLJ-2438)
+ Demunge source symbol in execution error messages
+
+
+### 2.2 Protocol extension by metadata
+
+`defprotocol` has a new option `:extend-via-metadata`. When :extend-via-metadata is true, values can extend protocols by adding metadata where keys are fully-qualified protocol function symbols and values are function implementations. Protocol implementations are checked first for direct definitions (defrecord, deftype, reify), then metadata definitions, then external extensions (extend, extend-type, extend-protocol).
+
+### 2.3 tap
+
+tap is a shared, globally accessible system for distributing a series of informational or diagnostic values to a set of (presumably effectful) handler functions. It can be used as a better debug prn, or for facilities like logging etc.
+
+`tap>` sends a value to the set of taps. Taps can be added with `add-tap` and will be called with any value sent to `tap>`. The tap function may (briefly) block (e.g. for streams) and will never impede calls to `tap>`, but blocking indefinitely may cause tap values to be dropped. If no taps are registered, `tap>` discards. Remove taps with `remove-tap`.
+
+### 2.4 Read string capture mode
+
+`read+string` is a new function that mimics `read` but also captures the string that is read and returns both the read value and the (whitespace-trimmed) read string. `read+string` requires a LineNumberingPushbackReader.
+
+### 2.5 prepl (alpha)
+
+prepl is a new stream-based REPL with structured output (suitable for programmatic use). Forms are read from the reader, evaluated, and return data maps for the return value (if successful), output to `*out*` (possibly many), output to `*err*` (possibly many), or tap> values (possibly many).
+
+New functions in clojure.core.server:
+
+* `prepl` - the repl
+* `io-prepl` - a prepl bound to `*in*` and `*out*` suitable for use with the Clojure socket server
+* `remote-prepl` - a prepl that can be connected to a remote prepl over a socket
+
+prepl is alpha and subject to change.
+
+### 2.6 datafy and nav
+
+clojure.datafy is a facility for object to data transformation. The `datafy` and `nav` functions can be used used to transform and (lazily) navigate through object graphs. The data transformation process can be influenced by consumers using protocols or metadata.
+
+datafy is alpha and subject to change.
+
+* [CLJ-2429](http://dev.clojure.org/jira/browse/CLJ-2429)
+ Datafy JavaReflector
+
+
+### 2.6 Other new functions in core
+
+These functions have been added to match existing functions in ClojureScript to increase the portability of error-handling code:
+
+* `ex-cause` - extract the cause exception
+* `ex-message` - extract the cause message
+
+This function has been added to construct a PrintWriter implementation whose behavior on flush and close is provided as functions:
+
+* `PrintWriter-on` - create a PrintWriter from flush-fn and close-fn
+
+The following function has been added, extending `resolve`:
+
+* `requiring-resolve` - resolve or, if needed, require symbol's namespace, then resolve
+* `serialized-require` - like `require` but for use in asynchronous load uses
+
+## 3 Enhancements
+
+### 3.1 Error messages
+
+* [CLJ-1279](http://dev.clojure.org/jira/browse/CLJ-1279)
+ Report correct arity count for function arity errors inside macros
+* [CLJ-2386](http://dev.clojure.org/jira/browse/CLJ-2386)
+ Omit ex-info construction frames
+* [CLJ-2394](http://dev.clojure.org/jira/browse/CLJ-2394)
+ Warn in pst that stack trace for syntax error failed before execution
+* [CLJ-2396](http://dev.clojure.org/jira/browse/CLJ-2396)
+ Omit :in clauses when printing spec function errors if using default explain printer
+* [CLJ-1797](http://dev.clojure.org/jira/browse/CLJ-1797)
+ Mention cljc in error when require fails
+* [CLJ-1130](http://dev.clojure.org/jira/browse/CLJ-1130)
+ Improve error message when unable to match static method
+
+### 3.2 Documentation
+
+* [CLJ-2044](http://dev.clojure.org/jira/browse/CLJ-2044)
+ clojure.instant - add arglist meta for functions
+* [CLJ-2257](http://dev.clojure.org/jira/browse/CLJ-2257)
+ `proxy` - fix typo
+* [CLJ-2332](http://dev.clojure.org/jira/browse/CLJ-2332)
+ `remove-tap` - fix repetition
+* [CLJ-2122](http://dev.clojure.org/jira/browse/CLJ-2122)
+ `flatten` - describe result as lazy
+
+### 3.3 Performance
+
+* [CLJ-1654](http://dev.clojure.org/jira/browse/CLJ-1654)
+ Reuse seq in `some`
+* [CLJ-1366](http://dev.clojure.org/jira/browse/CLJ-1366)
+ The empty map literal is read as a different map each time
+* [CLJ-2362](http://dev.clojure.org/jira/browse/CLJ-2362)
+ `with-meta` should return identity when new meta is identical to prior
+
+### 3.4 Other enhancements
+
+* `symbol` can now take a var or a keyword argument
+* [CLJ-1209](http://dev.clojure.org/jira/browse/CLJ-1209)
+ Print ex-data in clojure.test error reports
+* [CLJ-2163](http://dev.clojure.org/jira/browse/CLJ-2163)
+ Add test for var serialization
+* [CLJ-2417](http://dev.clojure.org/jira/browse/CLJ-2417)
+ `sort` and `sort-by` should retain meta
+
+## 4 Fixes
+
+### 4.1 Collections
+
+* [CLJ-2297](http://dev.clojure.org/jira/browse/CLJ-2297)
+ PersistentHashMap leaks memory when keys are removed with `without`
+* [CLJ-1587](http://dev.clojure.org/jira/browse/CLJ-1587)
+ PersistentArrayMap’s assoc doesn’t respect HASHTABLE_THRESHOLD
+* [CLJ-2050](http://dev.clojure.org/jira/browse/CLJ-2050)
+ Remove redundant key comparisons in HashCollisionNode
+* [CLJ-2089](http://dev.clojure.org/jira/browse/CLJ-2089)
+ Sorted colls with default comparator don’t check that first element is Comparable
+
+### 4.2 API
+
+* [CLJ-2031](http://dev.clojure.org/jira/browse/CLJ-2031)
+ clojure.walk/postwalk does not preserve MapEntry type objects
+* [CLJ-2349](http://dev.clojure.org/jira/browse/CLJ-2349)
+ Report correct line number for uncaught ExceptionInfo in clojure.test
+* [CLJ-1764](http://dev.clojure.org/jira/browse/CLJ-1764)
+ partition-by runs infinite loop when one element of infinite partition is accessed
+* [CLJ-1832](http://dev.clojure.org/jira/browse/CLJ-1832)
+ unchecked-* functions have different behavior on primitive longs vs boxed Longs
+
+### 4.3 Other
+
+* [CLJ-1403](http://dev.clojure.org/jira/browse/CLJ-1403)
+ ns-resolve might throw ClassNotFoundException but should return nil
+* [CLJ-2407](http://dev.clojure.org/jira/browse/CLJ-2407)
+ Fix bugs in Clojure unit tests
+* [CLJ-1079](http://dev.clojure.org/jira/browse/CLJ-1079)
+ In reader, don't ignore explicit :line :col meta
+
+# Changes to Clojure in Version 1.9
+
+## 1 New and Improved Features
+
+### 1.1 spec
+
+spec is a new core library for describing, validating, and testing the structure of data and functions.
+
+For more information, see:
+
+* [About spec](https://clojure.org/about/spec)
+* [spec Guide](https://clojure.org/guides/spec)
+
+Note that spec is in alpha state and API compatibility is not guaranteed. Also, spec and the specs for the Clojure core API are distributed as external libraries that must be included to use Clojure.
+
+### 1.2 Support for working with maps with qualified keys
+
+Several enhancements have been made to add support for working with maps with qualified keys:
+
+* Map namespace syntax - specify the default namespace context for the keys (or symbols) in a map once - `#:car{:make "Jeep" :model "Wrangler"}`. For more information see https://clojure.org/reference/reader#_maps ([CLJ-1910](http://dev.clojure.org/jira/browse/CLJ-1910))
+* Destructuring support - namespaced map keys can now specified once as a namespace for :keys or :syms. For more information see https://clojure.org/reference/special_forms#_map_binding_destructuring ([CLJ-1919](http://dev.clojure.org/jira/browse/CLJ-1919))
+* `*print-namespace-maps*` - by default maps will not print with the map namespace syntax except in the clojure.main repl. This dynamic var is a flag to allow you to control whether the namespace map syntax is used.
+
+### 1.3 New predicates
+
+Specs rely heavily on predicates and many new type and value oriented predicates have been added to clojure.core:
+
+* `boolean?`
+* `int?` `pos-int?` `neg-int?` `nat-int?`
+* `double?`
+* `ident?` `simple-ident?` `qualified-ident?`
+* `simple-symbol?` `qualified-symbol?`
+* `simple-keyword?` `qualified-keyword?`
+* `bytes?` (for `byte[]`)
+* `indexed?`
+* `uuid?` `uri?`
+* `seqable?`
+* `any?`
+
+### 1.4 More support for instants
+
+More support has been added for the notion of instants in time:
+
+* Added a new protocol `Inst` for instant types
+* `Inst` is extended for `java.util.Date`
+* `Inst` is optionally extended for `java.time.Instant` in Java 1.8+
+* New functions that work for instants: `inst?`, `inst-ms`
+
+### 1.5 Other new core functions
+
+These are some other new functions in clojure.core:
+
+* `bounded-count` - a count that avoids realizing the entire collection beyond a bound
+* `swap-vals!` and `reset-vals!` - new atom functions that return both the old and new values ([CLJ-1454](http://dev.clojure.org/jira/browse/CLJ-1454))
+* `halt-when` - new transducer that ends transduction when pred is satisfied
+
+### 1.6 Other reader enhancements
+
+* Can now bind `*reader-resolver*` to an impl of LispReader$Resolver to control the reader’s use of namespace interactions when resolving autoresolved keywords and maps.
+* Add new ## reader macro for symbolic values, and read/print support for double vals ##Inf, ##-Inf, ##NaN ([CLJ-1074](http://dev.clojure.org/jira/browse/CLJ-1074))
+
+## 2 Enhancements
+
+### 2.1 Spec syntax checking
+
+If a macro has a spec defined via fdef, that spec will be checked at compile time. Specs have been defined for many clojure.core macros and errors will be reported for these based on the specs at compile time.
+
+### 2.2 Documentation
+
+* `doc` will now report specs for functions with specs defined using `fdef`
+* `doc` can now be invoked with a fully-qualified keyword representing a spec name
+
+### 2.3 Performance
+
+* Improved update-in performance
+* Optimized seq & destructuring
+* [CLJ-2210](http://dev.clojure.org/jira/browse/CLJ-2210)
+ Cache class derivation in compiler to improve compiler performance
+* [CLJ-2188](http://dev.clojure.org/jira/browse/CLJ-2188)
+ `slurp` - mark return type as String
+* [CLJ-2070](http://dev.clojure.org/jira/browse/CLJ-2070)
+ `clojure.core/delay` - improve performance
+* [CLJ-1917](http://dev.clojure.org/jira/browse/CLJ-1917)
+ Reducing seq over string should call String/length outside of loop
+* [CLJ-1901](http://dev.clojure.org/jira/browse/CLJ-1901)
+ `amap` - should call alength only once
+* [CLJ-1224](http://dev.clojure.org/jira/browse/CLJ-1935)
+ Record instances now cache hasheq and hashCode like maps
+* [CLJ-99](http://dev.clojure.org/jira/browse/CLJ-99)
+ `min-key` and `max-key` - evaluate k on each arg at most once
+
+### 2.4 Other enhancements
+
+* Added Var serialization for identity, not value
+* `into` now has a 0-arity (returns `[]`) and 1-arity (returns the coll that's passed)
+* [CLJ-2184](http://dev.clojure.org/jira/browse/CLJ-2184)
+ Propagate meta in doto forms to improve error reporting
+* [CLJ-1744](http://dev.clojure.org/jira/browse/CLJ-1744)
+ Clear unused locals, which can prevent memory leaks in some cases
+* [CLJ-1673](http://dev.clojure.org/jira/browse/CLJ-1673)
+ `clojure.repl/dir-fn` now works on namespace aliases
+* [CLJ-1423](http://dev.clojure.org/jira/browse/CLJ-1423)
+ Allow vars to be invoked with infinite arglists (also, faster)
+
+## 3 Fixes
+
+### 3.1 Security
+
+* [CLJ-2204](http://dev.clojure.org/jira/browse/CLJ-2204)
+ Disable serialization of proxy classes to avoid potential issue when deserializing
+
+### 3.2 Docs
+
+* [CLJ-2170](http://dev.clojure.org/jira/browse/CLJ-2170)
+ fix improperly located docstrings
+* [CLJ-2156](http://dev.clojure.org/jira/browse/CLJ-2156)
+ `clojure.java.io/copy` - doc char[] support
+* [CLJ-2104](http://dev.clojure.org/jira/browse/CLJ-2104)
+ `clojure.pprint` docstring - fix typo
+* [CLJ-2051](http://dev.clojure.org/jira/browse/CLJ-2051)
+ `clojure.instant/validated` docstring - fix typo
+* [CLJ-2039](http://dev.clojure.org/jira/browse/CLJ-2039)
+ `deftype` - fix typo in docstring
+* [CLJ-2028](http://dev.clojure.org/jira/browse/CLJ-2028)
+ `filter`, `filterv`, `remove`, `take-while` - fix docstrings
+* [CLJ-1918](http://dev.clojure.org/jira/browse/CLJ-1918)
+ `await` - improve docstring re `shutdown-agents`
+* [CLJ-1873](http://dev.clojure.org/jira/browse/CLJ-1873)
+ `require`, `*data-readers*` - add .cljc files to docstrings
+* [CLJ-1859](http://dev.clojure.org/jira/browse/CLJ-1859)
+ `zero?`, `pos?`, `neg?` - fix docstrings
+* [CLJ-1837](http://dev.clojure.org/jira/browse/CLJ-1837)
+ `index-of`, `last-index-of` - clarify docstrings
+* [CLJ-1826](http://dev.clojure.org/jira/browse/CLJ-1826)
+ `drop-last` - fix docstring
+* [CLJ-1159](http://dev.clojure.org/jira/browse/CLJ-1159)
+ `clojure.java.io/delete-file` - improve docstring
+
+### 3.3 Other fixes
+
+* `clojure.core/Throwable->map` formerly returned `StackTraceElement`s which were later handled by the printer. Now the StackTraceElements are converted to data such that the return value is pure Clojure data, as intended.
+* [CLJ-2091](http://dev.clojure.org/jira/browse/CLJ-2091)
+ `clojure.lang.APersistentVector#hashCode` is not thread-safe
+* [CLJ-2077](http://dev.clojure.org/jira/browse/CLJ-2077)
+ Clojure can't be loaded from the boot classpath under java 9
+* [CLJ-2048](http://dev.clojure.org/jira/browse/CLJ-2048)
+ Specify type to avoid ClassCastException when stack trace is elided by JVM
+* [CLJ-1914](http://dev.clojure.org/jira/browse/CLJ-1914)
+ Fixed race condition in concurrent `range` realization
+* [CLJ-1887](http://dev.clojure.org/jira/browse/CLJ-1887)
+ `IPersistentVector.length()` - implement missing method
+* [CLJ-1870](http://dev.clojure.org/jira/browse/CLJ-1870)
+ Fixed reloading a `defmulti` removes metadata on the var
+* [CLJ-1860](http://dev.clojure.org/jira/browse/CLJ-1860)
+ Make -0.0 hash consistent with 0.0
+* [CLJ-1841](http://dev.clojure.org/jira/browse/CLJ-1841)
+ `bean` - iterator was broken
+* [CLJ-1793](http://dev.clojure.org/jira/browse/CLJ-1793)
+ Clear 'this' before calls in tail position
+* [CLJ-1790](http://dev.clojure.org/jira/browse/CLJ-1790)
+ Fixed error extending protocols to Java arrays
+* [CLJ-1714](http://dev.clojure.org/jira/browse/CLJ-1714)
+ using a class in a type hint shouldn’t load the class
+* [CLJ-1705](http://dev.clojure.org/jira/browse/CLJ-1705)
+ `vector-of` - fix NullPointerException if given unrecognized type
+* [CLJ-1398](http://dev.clojure.org/jira/browse/CLJ-1398)
+ `clojure.java.javadoc/javadoc` - update doc urls
+* [CLJ-1371](http://dev.clojure.org/jira/browse/CLJ-1371)
+ `Numbers.divide(Object, Object)` - add checks for NaN
+* [CLJ-1358](http://dev.clojure.org/jira/browse/CLJ-1358)
+ `doc` - does not expand special cases properly (try, catch)
+* [CLJ-1242](http://dev.clojure.org/jira/browse/CLJ-1242)
+ equals doesn't throw on sorted collections
+* [CLJ-700](http://dev.clojure.org/jira/browse/CLJ-700)
+ `contains?`, `get`, and `find` broken for transient collections
+
+# Changes to Clojure in Version 1.8
## 1 New and Improved Features
-### 1.1 Transducers
+### 1.1 Direct Linking
+
+Direct linking can be enabled with `-Dclojure.compiler.direct-linking=true`
+
+Direct linking allows functions compiled with direct linking on to make direct
+static method calls to most other functions, instead of going through the var
+and the Fn object. This can enable further optimization by the jit, at a cost
+in dynamism. In particular, directly-linked calls will not see redefinitions.
+
+With this change, clojure.core itself is compiled with direct linking
+and therefore other namespaces cannot redefine core fns and have those
+redefinitions seen by core code.
+
+A new metadata key ^:redef is provided. A function declared with this key can
+be redefined and will never be direct linked. Also, functions declared as
+^:dynamic will never be direct linked.
+
+* [CLJ-1809](http://dev.clojure.org/jira/browse/CLJ-1809)
+* [CLJ-1805](http://dev.clojure.org/jira/browse/CLJ-1805)
+* [CLJ-1854](http://dev.clojure.org/jira/browse/CLJ-1854)
+* [CLJ-1856](http://dev.clojure.org/jira/browse/CLJ-1856)
+
+### 1.2 String Functions
+
+Several new string functions were added to clojure.string to increase
+portability and reduce the need for Java interop calls:
+
+* index-of - search for the index of a char or string in a string
+* last-index-of - search for the index of a char or string backwards in a string
+* starts-with? - true if string starts with a substring
+* ends-with? - true if string ends with a substring
+* includes? - true if string includes a substring
+
+* [CLJ-1449](http://dev.clojure.org/jira/browse/CLJ-1449)
+
+### 1.3 Socket Server and REPL
+
+The Clojure runtime now has the ability to start a socket server at initialization
+based on system properties. One expected use for this is serving a socket-based
+REPL, but it also has many other potential uses for dynamically adding server
+capability to existing programs without code changes.
+
+A socket server will be started for each JVM system property like
+`clojure.server.`. The value for this property is an edn map
+representing the configuration of the socket server with the following properties:
+
+* address - host or address, defaults to loopback
+* port - positive integer, required
+* accept - namespaced symbol of function to invoke on socket accept, required
+* args - sequential collection of args to pass to accept
+* bind-err - defaults to true, binds `*err*` to socket out stream
+* server-daemon - defaults to true, socket server thread doesn't block exit
+* client-daemon - defaults to true, socket client thread doesn't block exit
+
+Additionally, there is a repl function provided that is slightly customized for
+use with the socket server in `clojure.core.server/repl`.
+
+Following is an example of starting a socket server with a repl listener.
+This can be added to any existing Clojure program to allow it to accept
+external REPL clients.
+
+```
+-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}"
+```
+
+An example client you can use to connect to this socket repl is telnet:
+
+```
+$ telnet 127.0.0.1 5555
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+user=> (println "hello")
+hello
+```
+
+See:
+
+* [CLJ-1671](http://dev.clojure.org/jira/browse/CLJ-1671)
+* [CLJ-1853](http://dev.clojure.org/jira/browse/CLJ-1853)
+* [Socket REPL design page](http://dev.clojure.org/display/design/Socket+Server+REPL)
+* [CLJ-1829](http://dev.clojure.org/jira/browse/CLJ-1829)
+
+## 2 Enhancements
+
+### 2.1 Error handling
+
+* [CLJ-1778](http://dev.clojure.org/jira/browse/CLJ-1778)
+ let-bound namespace-qualified bindings should throw (if not map destructuring)
+* [CLJ-1456](http://dev.clojure.org/jira/browse/CLJ-1456)
+ Compiler now errors if too few or too many arguments to throw
+* [CLJ-1282](http://dev.clojure.org/jira/browse/CLJ-1282)
+ quote now throws if passed more or less than one arg
+* [CLJ-1210](http://dev.clojure.org/jira/browse/CLJ-1210)
+ Improved error message for (clojure.java.io/reader nil)
+
+### 2.2 Documentation strings
+
+* [CLJ-1060](http://dev.clojure.org/jira/browse/CLJ-1060)
+ 'list*' returns not a list
+* [CLJ-1722](http://dev.clojure.org/jira/browse/CLJ-1722)
+ Typo in the docstring of 'with-bindings'
+* [CLJ-1769](http://dev.clojure.org/jira/browse/CLJ-1769)
+ Docstrings for *' and +' refer to * and +
+* [CLJ-1414](http://dev.clojure.org/jira/browse/CLJ-1414)
+ sort and sort-by now indicate sort is stable in docstring
+
+### 2.3 Performance
+
+* [CLJ-703](http://dev.clojure.org/jira/browse/CLJ-703)
+ Improve writeClassFile performance
+* [CLJ-1765](http://dev.clojure.org/jira/browse/CLJ-1765)
+ areduce performance improvements
+* [CLJ-1724](http://dev.clojure.org/jira/browse/CLJ-1724)
+ Remove unnecessary call to seq() in LazySeq.hashCode()
+* [CLJ-1295](http://dev.clojure.org/jira/browse/CLJ-1295)
+ Improved array-map dissoc performance
+* [CLJ-1277](http://dev.clojure.org/jira/browse/CLJ-1277)
+ Speed up printing of time instants with type hints
+* [CLJ-1259](http://dev.clojure.org/jira/browse/CLJ-1259)
+ Speed up pprint and cl-format with type hints
+* [CLJ-668](http://dev.clojure.org/jira/browse/CLJ-668)
+ Improve slurp performance by using StringWriter and jio/copy
+
+### 2.4 Other enhancements
+
+* [CLJ-1208](http://dev.clojure.org/jira/browse/CLJ-1208)
+ Optionally require namespace on defrecord class init
+* [CLJ-1823](http://dev.clojure.org/jira/browse/CLJ-1823)
+ Document new :load-ns option to defrecord/deftype
+* [CLJ-1810](http://dev.clojure.org/jira/browse/CLJ-1810)
+ ATransientMap now marked public
+* [CLJ-1653](http://dev.clojure.org/jira/browse/CLJ-1653)
+ str of an empty list should be "()"
+* [CLJ-1567](http://dev.clojure.org/jira/browse/CLJ-1567)
+ Removed unused local in condp implementation
+* [CLJ-1351](http://dev.clojure.org/jira/browse/CLJ-1351)
+ Unused swapThunk method was being emitted for fns with keyword callsites
+* [CLJ-1329](http://dev.clojure.org/jira/browse/CLJ-1329)
+ Removed unused local in PersistentVector.cons()
+* [CLJ-1831](http://dev.clojure.org/jira/browse/CLJ-1831)
+ Add clojure.core/map-entry? predicate
+* [CLJ-1845](http://dev.clojure.org/jira/browse/CLJ-1845)
+ Make clojure.core/load dynamic so it can be redef'ed even with direct linking
+
+## 3 Bug Fixes
+
+* [CLJ-130](http://dev.clojure.org/jira/browse/CLJ-130)
+ Namespace metadata lost in AOT compile
+* [CLJ-1134](http://dev.clojure.org/jira/browse/CLJ-1134)
+ star-directive in clojure.pprint/cl-format with at-prefix ("~n@*") does
+ not obey its specification
+* [CLJ-1137](http://dev.clojure.org/jira/browse/CLJ-1137)
+ Metadata on a def gets evaluated twice
+* [CLJ-1157](http://dev.clojure.org/jira/browse/CLJ-1157)
+ Classes generated by gen-class aren't loadable from remote codebase
+* [CLJ-1225](http://dev.clojure.org/jira/browse/CLJ-1225)
+ quot overflow issues around Long/MIN_VALUE for BigInt
+* [CLJ-1313](http://dev.clojure.org/jira/browse/CLJ-1313)
+ Correct a few unit tests
+* [CLJ-1319](http://dev.clojure.org/jira/browse/CLJ-1319)
+ array-map fails lazily if passed an odd number of arguments
+* [CLJ-1361](http://dev.clojure.org/jira/browse/CLJ-1361)
+ pprint with code-dispatch incorrectly prints a simple ns macro call
+* [CLJ-1390](http://dev.clojure.org/jira/browse/CLJ-1390)
+ pprint a GregorianCalendar results in Arity exception
+* [CLJ-1399](http://dev.clojure.org/jira/browse/CLJ-1399)
+ field name unmunged when recreating deftypes serialized into bytecode
+* [CLJ-1485](http://dev.clojure.org/jira/browse/CLJ-1485)
+ clojure.test.junit/with-junit-output doesn't handle multiple expressions
+* [CLJ-1528](http://dev.clojure.org/jira/browse/CLJ-1528)
+ clojure.test/inc-report-counter is not thread-safe
+* [CLJ-1533](http://dev.clojure.org/jira/browse/CLJ-1533)
+ invokePrim path does not take into account var or form meta
+* [CLJ-1562](http://dev.clojure.org/jira/browse/CLJ-1562)
+ some->,some->>,cond->,cond->> and as-> doesn't work with (recur)
+* [CLJ-1565](http://dev.clojure.org/jira/browse/CLJ-1565)
+ pprint produces infinite output for a protocol
+* [CLJ-1588](http://dev.clojure.org/jira/browse/CLJ-1588)
+ StackOverflow in clojure.test macroexpand with `are` and anon `fn`
+* [CLJ-1644](http://dev.clojure.org/jira/browse/CLJ-1644)
+ into-array fails for sequences starting with nil
+* [CLJ-1645](http://dev.clojure.org/jira/browse/CLJ-1645)
+ protocol class does not set the source file
+* [CLJ-1657](http://dev.clojure.org/jira/browse/CLJ-1657)
+ proxy bytecode calls super methods of abstract classes
+* [CLJ-1659](http://dev.clojure.org/jira/browse/CLJ-1659)
+ compile leaks files
+* [CLJ-1761](http://dev.clojure.org/jira/browse/CLJ-1761)
+ clojure.core/run! does not always return nil per docstring
+* [CLJ-1782](http://dev.clojure.org/jira/browse/CLJ-1782)
+ Spelling mistake in clojure.test/use-fixtures
+* [CLJ-1785](http://dev.clojure.org/jira/browse/CLJ-1785)
+ Reader conditionals throw when returning nil
+* [CLJ-1766](http://dev.clojure.org/jira/browse/CLJ-1766)
+ Serializing+deserializing lists breaks their hash
+* [CLJ-1609](http://dev.clojure.org/jira/browse/CLJ-1609)
+ Edge case in Reflector's search for a public method declaration
+* [CLJ-1586](http://dev.clojure.org/jira/browse/CLJ-1586)
+ Compiler doesn't preserve metadata for LazySeq literals
+* [CLJ-1232](http://dev.clojure.org/jira/browse/CLJ-1232)
+ Functions with non-qualified return type hints will now work without
+ import from other namespace
+* [CLJ-1812](http://dev.clojure.org/jira/browse/CLJ-1812)
+ Fix test failure on windows due to line endings
+* [CLJ-1380](http://dev.clojure.org/jira/browse/CLJ-1380)
+ 3-arg ExceptionInfo constructor permitted nil data
+* [CLJ-1226](http://dev.clojure.org/jira/browse/CLJ-1226)
+ set! of a deftype field using field-access syntax caused ClassCastException
+* Records and types without fields eval to empty map
+* [CLJ-1827](http://dev.clojure.org/jira/browse/CLJ-1827)
+ Fix reflection warning introduced in CLJ-1259
+* [CLJ-1453](http://dev.clojure.org/jira/browse/CLJ-1453)
+ Ensure that all Iterator implementations throw NoSuchElementException
+ on next() when exhausted
+* [CLJ-1868](http://dev.clojure.org/jira/browse/CLJ-1868)
+ Avoid compiler NPE when checking class return type
+
+# Changes to Clojure in Version 1.7
+
+## 1 Compatibility Notes
+
+Please be aware of the following issues when upgrading to Clojure 1.7.
+
+### Seqs on Java iterators that return the same mutating object
+
+Seqs are fundamentally incompatible with Java iterators that return
+the same mutating object on every call to next(). Some Clojure
+libraries incorrectly rely on calling seq on such iterators.
+
+In 1.7, iterator-seqs are chunked, which will cause many of these
+incorrect usages to return incorrect results immediately.
+
+The `seq` and `iterator-seq` docstrings have been updated to include
+an explicit warning. Libraries that incorrectly use `seq` and
+`iterator-seq` will need to be fixed before running against 1.7.
+
+* [CLJ-1669](http://dev.clojure.org/jira/browse/CLJ-1669)
+* [CLJ-1738](http://dev.clojure.org/jira/browse/CLJ-1738)
+
+### Thread owner check removed on transients
+
+Prior to Clojure 1.7, transients would allow modification only from the
+thread that created the transient. This check has been removed. It is
+still a requirement that transients should be updated by only a single
+thread at a time.
+
+This constraint was relaxed to allow transients to be used in cases where
+code is multiplexed across multiple threads in a pool (such as go blocks
+in core.async).
+
+### keys/vals require custom map type to implement Iterable
+
+Invoking `keys` or `vals` on a custom map type that implements IPersistentMap
+will now use the Iterable iterator() method instead of accessing entries
+via the seq of the map. There have been no changes in the type hierarchy
+(IPersistentMap has always extended Iterable) but former map-like instances
+may have skipped implementing this method in the past.
+
+* [CLJ-1602](http://dev.clojure.org/jira/browse/CLJ-1602)
+
+## 2 New and Improved Features
+
+### 2.1 Transducers
Transducers is a new way to decouple algorithmic transformations from their
application in different contexts. Transducers are functions that transform
@@ -16,7 +1208,6 @@ Many existing sequence functions now have a new arity (one fewer argument
than before). This arity will return a transducer that represents the same
logic but is independent of lazy sequence processing. Functions included are:
-* conj (conjs to [])
* map
* mapcat
* filter
@@ -38,7 +1229,7 @@ logic but is independent of lazy sequence processing. Functions included are:
Additionally some new transducer functions have been added:
* cat - concatenates the contents of each input
-* de-dupe - removes consecutive duplicated values
+* dedupe - removes consecutive duplicated values
* random-sample - returns items from coll with random probability
And this function can be used to make completing transforms:
@@ -51,12 +1242,12 @@ transducers in different ways:
* sequence - takes a transformation and a coll and produces a lazy seq
* transduce - reduce with a transformation (eager)
* eduction - returns a reducible/iterable of applications of the transducer to items in coll. Applications are re-performed with every reduce/iterator.
-* run! - run the transformation for side effects on the collection
There have been a number of internal changes to support transducers:
* volatiles - there are a new set of functions (volatile!, vswap!, vreset!, volatile?) to create and use volatile "boxes" to hold state in stateful transducers. Volatiles are faster than atoms but give up atomicity guarantees so should only be used with thread isolation.
* array iterators - added support for iterators over arrays
+* conj can be used as a reducing function and will conj to []
Some related issues addressed during development:
* [CLJ-1511](http://dev.clojure.org/jira/browse/CLJ-1511)
@@ -71,8 +1262,9 @@ Some related issues addressed during development:
* [CLJ-1635](http://dev.clojure.org/jira/browse/CLJ-1635)
* [CLJ-1683](http://dev.clojure.org/jira/browse/CLJ-1683)
* [CLJ-1669](http://dev.clojure.org/jira/browse/CLJ-1669)
+* [CLJ-1723](http://dev.clojure.org/jira/browse/CLJ-1723)
-### 1.2 Reader Conditionals
+### 2.2 Reader Conditionals
Reader Conditionals are a new capability to support portable code that
can run on multiple Clojure platforms with only small changes. In
@@ -87,13 +1279,14 @@ prior to .cljc.
A new reader form can be used to specify "reader conditional" code in
cljc files (and *only* cljc files). Each platform defines a feature
identifying the platform (:clj, :cljs, :cljr). The reader conditional
-specifies code that is read conditionally based on the feature/
+specifies code that is read conditionally based on the feature. The
+REPL also allows reader conditionals.
Form #? takes a list of alternating feature and expression. These are
checked like cond and the selected expression is read and returned. Other
-branches are unread. If no branch is selected, the reader reads nothing
-(not nil, but literally as if reading ""). An optional ":default" branch
-can be used as a fallthrough.
+branches are read but skipped. If no branch is selected, the reader reads
+nothing (not nil, but literally as if reading no form). An optional
+`:default` branch can be used as a fallthrough.
Reader conditional with 2 features and a default:
@@ -103,14 +1296,14 @@ Reader conditional with 2 features and a default:
There is also a reader conditional splicing form. The evaluated expression
should be sequential and will be spliced into the surrounded code, similar
-to unqoute-splicing.
+to unquote-splicing.
For example:
[1 2 #?@(:clj [3 4] :cljs [5 6])]
This form would read as [1 2 3 4] on Clojure, [1 2 5 6] on ClojureScript,
-and [1 2] on any other platform.
+and [1 2] on any other platform. Splicing is not allowed at the top level.
Additionally, the reader can now be invoked with options for the features
to use and how to interpret reader conditionals. By default, reader conditionals
@@ -125,8 +1318,13 @@ http://dev.clojure.org/display/design/Reader+Conditionals
* [CLJ-1424](http://dev.clojure.org/jira/browse/CLJ-1424)
* [CLJ-1685](http://dev.clojure.org/jira/browse/CLJ-1685)
+* [CLJ-1698](http://dev.clojure.org/jira/browse/CLJ-1698)
+* [CLJ-1699](http://dev.clojure.org/jira/browse/CLJ-1699)
+* [CLJ-1700](http://dev.clojure.org/jira/browse/CLJ-1700)
+* [CLJ-1728](http://dev.clojure.org/jira/browse/CLJ-1728)
+* [CLJ-1706](http://dev.clojure.org/jira/browse/CLJ-1706)
-### 1.3 Keyword and Symbol Construction
+### 2.3 Keyword and Symbol Construction
In response to issues raised in [CLJ-1439](http://dev.clojure.org/jira/browse/CLJ-1439),
several changes have been made in symbol and keyword construction:
@@ -138,7 +1336,7 @@ in a performance increase.
2) Keywords are cached and keyword construction includes a cache check. A change was made
to only clear the cache reference queue when there is a cache miss.
-### 1.4 Warn on Boxed Math
+### 2.4 Warn on Boxed Math
One source of performance issues is the (unintended) use of arithmetic operations on
boxed numbers. To make detecting the presence of boxed math easier, a warning will now
@@ -162,7 +1360,7 @@ Example use:
* [CLJ-1535](http://dev.clojure.org/jira/browse/CLJ-1535)
* [CLJ-1642](http://dev.clojure.org/jira/browse/CLJ-1642)
-### 1.5 update - like update-in for first level
+### 2.5 update - like update-in for first level
`update` is a new function that is like update-in specifically for first-level keys:
@@ -179,7 +1377,7 @@ Example use:
* [CLJ-1251](http://dev.clojure.org/jira/browse/CLJ-1251)
-### 1.6 Faster reduce and iterator paths
+### 2.6 Faster reduce and iterator paths
Several important Clojure functions now return sequences that also
contain fast reduce() (or in some cases iterator()) paths. In many
@@ -210,8 +1408,13 @@ eduction.
* [CLJ-1669](http://dev.clojure.org/jira/browse/CLJ-1669)
* [CLJ-1692](http://dev.clojure.org/jira/browse/CLJ-1692)
* [CLJ-1694](http://dev.clojure.org/jira/browse/CLJ-1694)
+* [CLJ-1711](http://dev.clojure.org/jira/browse/CLJ-1711)
+* [CLJ-1709](http://dev.clojure.org/jira/browse/CLJ-1709)
+* [CLJ-1713](http://dev.clojure.org/jira/browse/CLJ-1713)
+* [CLJ-1726](http://dev.clojure.org/jira/browse/CLJ-1726)
+* [CLJ-1727](http://dev.clojure.org/jira/browse/CLJ-1727)
-### 1.7 Printing as data
+### 2.7 Printing as data
There have been enhancements in how the REPL prints values without a
print-method, specifically Throwable and the fallthrough Object case.
@@ -229,29 +1432,43 @@ REPL but printing them to a stream will show a different form:
user=> (/ 1 0)
ArithmeticException Divide by zero clojure.lang.Numbers.divide (Numbers.java:158)
user=> (println *e)
- #error{:cause Divide by zero,
- :via [{:type java.lang.ArithmeticException,
- :message Divide by zero,
- :at [clojure.lang.Numbers divide Numbers.java 158]}],
- :trace
- [[clojure.lang.Numbers divide Numbers.java 158]
- [clojure.lang.Numbers divide Numbers.java 3808]
- [user$eval5 invoke NO_SOURCE_FILE 3]
- ;; elided ...
- ]]}
+ #error {
+ :cause Divide by zero
+ :via
+ [{:type java.lang.ArithmeticException
+ :message Divide by zero
+ :at [clojure.lang.Numbers divide Numbers.java 158]}]
+ :trace
+ [[clojure.lang.Numbers divide Numbers.java 158]
+ [clojure.lang.Numbers divide Numbers.java 3808]
+ ;; ... elided frames
+ ]}
-## 2 Enhancements
+Additionally, there is a new function available to obtain a Throwable as
+map data: `Throwable->map`.
-### 2.1 Error messages
+* [CLJ-1703](http://dev.clojure.org/jira/browse/CLJ-1703)
+* [CLJ-1716](http://dev.clojure.org/jira/browse/CLJ-1716)
+* [CLJ-1735](http://dev.clojure.org/jira/browse/CLJ-1735)
+
+### 2.8 run!
+
+run! is a new function that takes a side effect reducing function and runs
+it for all items in a collection via reduce. The accumulator is ignored and
+nil is returned.
+
+ (run! println (range 10))
+
+## 3 Enhancements
+
+### 3.1 Error messages
* [CLJ-1261](http://dev.clojure.org/jira/browse/CLJ-1261)
Invalid defrecord results in exception attributed to consuming ns instead of defrecord ns
-* [CLJ-1169](http://dev.clojure.org/jira/browse/CLJ-1169)
- Report line,column, and source in defmacro errors
* [CLJ-1297](http://dev.clojure.org/jira/browse/CLJ-1297)
Give more specific hint if namespace with "-" not found to check file uses "_"
-### 2.2 Documentation strings
+### 3.2 Documentation strings
* [CLJ-1417](http://dev.clojure.org/jira/browse/CLJ-1417)
clojure.java.io/input-stream has incorrect docstring
@@ -265,8 +1482,10 @@ REPL but printing them to a stream will show a different form:
Fix typo in deftype docstring
* [CLJ-1478](http://dev.clojure.org/jira/browse/CLJ-1378)
Fix typo in clojure.main usage
+* [CLJ-1738](http://dev.clojure.org/jira/browse/CLJ-1738)
+ Clarify usage on Java iterators in seq and iterator-seq
-### 2.3 Performance
+### 3.3 Performance
* [CLJ-1430](http://dev.clojure.org/jira/browse/CLJ-1430)
Improve performance of partial with more unrolling
@@ -283,7 +1502,7 @@ REPL but printing them to a stream will show a different form:
* [CLJ-1695](http://dev.clojure.org/jira/browse/CLJ-1695)
Fixed reflection call in variadic vector-of constructor
-### 2.4 Other enhancements
+### 3.4 Other enhancements
* [CLJ-1191](http://dev.clojure.org/jira/browse/CLJ-1191)
Improve apropos to show some indication of namespace of symbols found
@@ -314,7 +1533,7 @@ REPL but printing them to a stream will show a different form:
* [CLJ-1683](http://dev.clojure.org/jira/browse/CLJ-1683)
Change reduce tests to better catch reduce without init bugs
-## 3 Bug Fixes
+## 4 Bug Fixes
* [CLJ-1362](http://dev.clojure.org/jira/browse/CLJ-1362)
Reduce broken on some primitive vectors
@@ -368,7 +1587,12 @@ REPL but printing them to a stream will show a different form:
Fix regression from CLJ-1546 removed PersistentVector.create(List) method
* [CLJ-1681](http://dev.clojure.org/jira/browse/CLJ-1681)
Fix regression from CLJ-1248 (1.6) in reflection warning with literal nil argument
-
+* [CLJ-1648](http://dev.clojure.org/jira/browse/CLJ-1648)
+ Use equals() instead of == when resolving Symbol
+* [CLJ-1195](http://dev.clojure.org/jira/browse/CLJ-1195)
+ emit-hinted-impl expands to ns-qualified invocation of fn
+* [CLJ-1237](http://dev.clojure.org/jira/browse/CLJ-1237)
+ reduce of sequence that switches between chunked and unchunked many times throws StackOverflow
# Changes to Clojure in Version 1.6
diff --git a/clojure.iml b/clojure.iml
deleted file mode 100644
index c90b030069..0000000000
--- a/clojure.iml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/codegen/gen_fn_adapter_tests.clj b/codegen/gen_fn_adapter_tests.clj
new file mode 100644
index 0000000000..423ee77fc6
--- /dev/null
+++ b/codegen/gen_fn_adapter_tests.clj
@@ -0,0 +1,177 @@
+;; This code was used to generate:
+;; generated_all_fi_adapters_in_let.clj
+;; generated_functional_adapters_in_def_requiring_reflection.clj
+;; generated_functional_adapters_in_def.clj
+;; AdapterExerciser.java
+;; This code is not intended to be reused but might be
+;; useful in the future as a template for other code gen.
+
+(ns gen-fn-adapter-tests
+ (:require
+ [clojure.string :as str])
+ (:import
+ [java.io StringWriter Writer]))
+
+(defn let-test-header [imported-methods]
+ (format "
+(ns clojure.test-clojure.generated-all-fi-adapters-in-let
+ (:use clojure.test)
+ (:require [clojure.string :as str])
+ (:import (clojure.test AdapterExerciser %s)))
+
+ (deftest test-all-fi-adapters-in-let
+ (let [^AdapterExerciser exerciser (AdapterExerciser.)" imported-methods))
+
+(defn def-test-header [filename]
+ (format "
+(ns clojure.test-clojure.%s
+ (:use clojure.test)
+ (:require [clojure.string :as str])
+ (:import (clojure.test AdapterExerciser)))
+
+ (deftest functional-adapters-in-def
+ (def exerciser (AdapterExerciser.))" filename))
+
+(def adapter-exerciser-header "
+package clojure.test;
+
+public class AdapterExerciser {")
+
+(defn sigs [args return-types]
+ (let [fun-sig-reducer (fn [res ret]
+ (mapcat seq [res (map (fn [params]
+ (str params ret)) args)]))]
+ (reduce fun-sig-reducer [] return-types)))
+
+(defn gen-sigs []
+ (let [small-rets ["L" "I" "S" "B" "D" "F" "O"]
+ zero-arity (sigs [""] small-rets)
+ single-arity (sigs ["L" "D" "O"] small-rets)
+ two-arity (sigs ["LL" "LO" "OL" "DD" "LD" "DL" "OO" "OD" "DO"] small-rets)
+ big-rets ["O"]
+ three-arity (sigs ["OOO"] big-rets)
+ four-arity (sigs ["OOOO"] big-rets)
+ five-arity (sigs ["OOOOO"] big-rets)
+ six-arity (sigs ["OOOOOO"] big-rets)
+ seven-arity (sigs ["OOOOOOO"] big-rets)
+ eight-arity (sigs ["OOOOOOOO"] big-rets)
+ nine-arity (sigs ["OOOOOOOOO"] big-rets)
+ ten-arity (sigs ["OOOOOOOOOO"] big-rets)]
+ (mapcat seq [zero-arity single-arity two-arity three-arity four-arity five-arity six-arity seven-arity eight-arity nine-arity ten-arity])))
+
+(def alphabet (map char (range 97 122)))
+(def type-hints {:D "^double "
+ :O "^AdapterExerciser "
+ :L "^long "
+ :I "^int "
+ :F "^float "
+ :Z "^boolean "
+ :S "^short "
+ :B "^byte "})
+(def types {:D "double"
+ :O "AdapterExerciser"
+ :L "long"
+ :I "int"
+ :F "float"
+ :Z "boolean"
+ :S "short"
+ :B "byte"})
+(def method-args {:D "(double 1)"
+ :O "exerciser"
+ :L "(long 1)"
+ :I "1"
+ :F "(float 1)"
+ :Z "false"
+ :S "(short 1)"
+ :B "(byte 1)"})
+
+(defn format-parts [sig]
+ (let [return-type-initial (str (last sig))
+ return-type (get types (keyword return-type-initial))
+ input-types (map str (butlast sig))
+ arg-type-hints (map #(get type-hints (keyword %)) input-types)
+ java-types (map #(get types (keyword %)) input-types)
+ fn-vars (str/join " " (map #(str %1 %2) arg-type-hints (take (count input-types) alphabet)))
+ fn-args (str/join " " (map #(get method-args (keyword %)) input-types))
+ java-vars (str/join ", " (map #(str %1 " " %2) java-types (take (count input-types) alphabet)))
+ fn-body (get method-args (keyword return-type-initial))
+ expected-val (get method-args (keyword return-type-initial))]
+ {:return-type return-type :fn-args fn-args :return-type-initial return-type-initial :fn-vars fn-vars :fn-body fn-body :input-types input-types :java-vars java-vars :expected-val expected-val}))
+
+(defn gen-imported-methods [sigs]
+ (let [sb (StringBuilder. " ")]
+ (doseq [sig sigs]
+ (.append sb (format "AdapterExerciser$%s" sig))
+ (.append sb "\n"))
+ (.toString sb)))
+
+(defn gen-test-all-fi-adapters-in-let []
+ (let [adapter-signatures (gen-sigs)
+ imported-methods (gen-imported-methods adapter-signatures)
+ sb (StringBuilder. ^String (let-test-header imported-methods))]
+ ;; Assemble let
+ (doseq [sig adapter-signatures]
+ (let [{:keys [fn-vars fn-body]} (format-parts sig)]
+ (.append sb "\n")
+ (.append sb (format " ^AdapterExerciser$%s %sadapter (fn [%s] %s)" sig sig fn-vars fn-body))))
+ (.append sb "]")
+ ;; Assemble test cases
+ (doseq [sig adapter-signatures]
+ (let [{:keys [return-type-initial fn-args expected-val]} (format-parts sig)]
+ (.append sb "\n")
+ (.append sb (format " (is (= (.takes%sRet%s %sadapter %s) %s))" (str/join "" (butlast sig)) return-type-initial sig fn-args expected-val))))
+ (.append sb "))")
+ (spit "generated_all_fi_adapters_in_let.clj" (.toString sb))))
+
+(defn gen-test-functional-adapters-in-def []
+ (let [sb (StringBuilder. ^String (def-test-header "generated-functional-adapters-in-def"))
+ adapter-signatures (gen-sigs)]
+ (doseq [sig adapter-signatures]
+ (let [{:keys [fn-vars fn-body]} (format-parts sig)]
+ (.append sb "\n")
+ (.append sb (format " (def %sadapter (fn [%s] %s))" sig fn-vars fn-body))
+ (.append sb "\n")
+ (.append sb (format " (is (= (.method%s ^AdapterExerciser exerciser %sadapter) %s))" sig sig (str "\"" sig "\"")))))
+ (.append sb ")")
+ (spit "generated_functional_adapters_in_def.clj" (.toString sb))))
+
+(defn gen-test-functional-adapters-in-def-requiring-reflection []
+ (let [sb (StringBuilder. ^String (def-test-header "generated-functional-adapters-in-def-requiring-reflection"))
+ adapter-signatures (gen-sigs)]
+ (doseq [sig adapter-signatures]
+ (let [{:keys [fn-vars fn-body]} (format-parts sig)]
+ (.append sb "\n")
+ (.append sb (format " (def %sadapter (fn [%s] %s))" sig fn-vars fn-body))
+ (.append sb "\n")
+ (.append sb (format " (is (= (.method%s exerciser %sadapter) %s))" sig sig (str "\"" sig "\"")))))
+ (.append sb ")")
+ (spit "generated_functional_adapters_in_def_requiring_reflection.clj" (.toString sb))))
+
+(defn gen-adapter-exerciser-class []
+ (let [sb (StringBuilder. ^String adapter-exerciser-header)
+ adapter-signatures (gen-sigs)]
+ (doseq [sig adapter-signatures]
+ (let [{:keys [return-type return-type-initial input-types java-vars]} (format-parts sig)]
+ (.append sb "\n")
+ (.append sb " @FunctionalInterface\n")
+ (.append sb (format " public interface %s {\n" sig))
+ (.append sb (format " public %s takes%sRet%s(%s);\n" return-type (str/join "" input-types) return-type-initial java-vars))
+ (.append sb " }")))
+ (doseq [sig adapter-signatures]
+ (.append sb "\n")
+ (.append sb (format " public String method%s(%s a) { return %s; }" sig sig (str "\"" sig "\""))))
+ (.append sb "}")
+ (spit "AdapterExerciser.java" (.toString sb))))
+
+(defn gen-all []
+ (gen-test-all-fi-adapters-in-let)
+ (gen-test-functional-adapters-in-def)
+ (gen-test-functional-adapters-in-def-requiring-reflection)
+ (gen-adapter-exerciser-class))
+
+(comment
+ (gen-all)
+ (gen-test-all-fi-adapters-in-let)
+ (gen-test-functional-adapters-in-def)
+ (gen-test-functional-adapters-in-def-requiring-reflection)
+ (gen-adapter-exerciser-class))
\ No newline at end of file
diff --git a/codegen/gen_fn_invokers.clj b/codegen/gen_fn_invokers.clj
new file mode 100644
index 0000000000..3d2a8a73ca
--- /dev/null
+++ b/codegen/gen_fn_invokers.clj
@@ -0,0 +1,173 @@
+;; This code was used to generate the clojure.lang.FnInvokers class in
+;; Clojure 1.12. This code is not intended to be reused but might be
+;; useful in the future as a template for other code gen.
+
+(ns gen-fn-invokers
+ (:require
+ [clojure.string :as str]))
+
+(def header
+ "/**
+ * Copyright (c) Rich Hickey. All rights reserved.
+ * The use and distribution terms for this software are covered by the
+ * Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0)
+ * which can be found in the file epl-v10.html at the root of this distribution.
+ * By using this software in any fashion, you are agreeing to be bound by
+ * the terms of this license.
+ * You must not remove this notice, or any other, from this software.
+ **/
+
+package clojure.lang;
+
+public class FnInvokers {
+
+ // Encode invoker param/return class to code for method name
+ static char encodeInvokerType(Class c) {
+ if(Long.TYPE.equals(c)) {
+ return 'L';
+ } else if(Double.TYPE.equals(c)) {
+ return 'D';
+ } else if(Integer.TYPE.equals(c)) {
+ return 'I';
+ } else if(Short.TYPE.equals(c)) {
+ return 'S';
+ } else if(Byte.TYPE.equals(c)) {
+ return 'B';
+ } else if(Float.TYPE.equals(c)) {
+ return 'F';
+ } else {
+ return 'O';
+ }
+ }
+
+")
+
+(def footer
+ "}")
+
+(def invokeO-format
+ " public static Object invoke%sO(IFn f0%s) {
+ return f0.invoke(%s);
+ }")
+
+(def invokeO-with-l-or-d-arg-format
+ " public static Object invoke%sO(IFn f0%s) {
+ if(f0 instanceof IFn.%sO) {
+ return ((IFn.%sO)f0).invokePrim(%s);
+ } else {
+ return f0.invoke(%s);
+ }
+ }")
+
+(def invokeD-format
+ " public static double invoke%sD(IFn f0%s) {
+ if(f0 instanceof IFn.%sD) {
+ return ((IFn.%sD)f0).invokePrim(%s);
+ } else {
+ return RT.doubleCast(f0.invoke(%s));
+ }
+ }")
+
+(def invokeF-format
+ " public static float invoke%sF(IFn f0%s) {
+ if(f0 instanceof IFn.%sD) {
+ return RT.floatCast(((IFn.%sD)f0).invokePrim(%s));
+ } else {
+ return RT.floatCast(f0.invoke(%s));
+ }
+ }")
+
+(def invokeL-format
+ " public static long invoke%sL(IFn f0%s) {
+ if(f0 instanceof IFn.%sL) {
+ return ((IFn.%sL)f0).invokePrim(%s);
+ } else {
+ return RT.longCast(f0.invoke(%s));
+ }
+ }")
+
+(def invokeI-format
+ " public static int invoke%sI(IFn f0%s) {
+ if(f0 instanceof IFn.%sL) {
+ return RT.intCast(((IFn.%sL)f0).invokePrim(%s));
+ } else {
+ return RT.intCast(f0.invoke(%s));
+ }
+ }")
+
+(def invokeS-format
+ " public static short invoke%sS(IFn f0%s) {
+ if(f0 instanceof IFn.%sL) {
+ return RT.shortCast(((IFn.%sL)f0).invokePrim(%s));
+ } else {
+ return RT.shortCast(f0.invoke(%s));
+ }
+ }")
+
+(def invokeB-format
+ " public static byte invoke%sB(IFn f0%s) {
+ if(f0 instanceof IFn.%sL) {
+ return RT.byteCast(((IFn.%sL)f0).invokePrim(%s));
+ } else {
+ return RT.byteCast(f0.invoke(%s));
+ }
+ }")
+
+(def alphabet (map char (range 97 122)))
+
+(def arg-types {:D ", double "
+ :L ", long "
+ :O ", Object "})
+
+(defn gen-invoke [sig]
+ (let [formatter (str (last sig))
+ args (map str (butlast sig))
+ arg-types (map #(get arg-types (keyword %)) args)
+ fn-vars (str/join "" (map #(str %1 %2) arg-types (take (count args) alphabet)))
+ fn-vars-sans-type (str/join ", " (take (count args) alphabet))
+ arg-str (str/join args)]
+ (case formatter
+ "O" (if (some #{"D" "L"} args)
+ (format invokeO-with-l-or-d-arg-format arg-str fn-vars arg-str arg-str fn-vars-sans-type fn-vars-sans-type)
+ (format invokeO-format arg-str fn-vars fn-vars-sans-type))
+ "L" (format invokeL-format arg-str fn-vars arg-str arg-str fn-vars-sans-type fn-vars-sans-type)
+ "I" (format invokeI-format arg-str fn-vars arg-str arg-str fn-vars-sans-type fn-vars-sans-type)
+ "S" (format invokeS-format arg-str fn-vars arg-str arg-str fn-vars-sans-type fn-vars-sans-type)
+ "B" (format invokeB-format arg-str fn-vars arg-str arg-str fn-vars-sans-type fn-vars-sans-type)
+ "D" (format invokeD-format arg-str fn-vars arg-str arg-str fn-vars-sans-type fn-vars-sans-type)
+ "F" (format invokeF-format arg-str fn-vars arg-str arg-str fn-vars-sans-type fn-vars-sans-type))))
+
+(defn sigs [args return-types]
+ (let [fun-sig-reducer (fn [res ret]
+ (mapcat seq [res (map (fn [params]
+ (str params ret)) args)]))]
+ (reduce fun-sig-reducer [] return-types)))
+
+(defn gen-sigs []
+ (let [small-rets ["L" "I" "S" "B" "D" "F" "O"]
+ zero-arity (sigs [""] small-rets)
+ single-arity (sigs ["L" "D" "O"] small-rets)
+ two-arity (sigs ["LL" "LO" "OL" "DD" "LD" "DL" "OO" "OD" "DO"] small-rets)
+ big-rets ["O"]
+ three-arity (sigs ["OOO"] big-rets)
+ four-arity (sigs ["OOOO"] big-rets)
+ five-arity (sigs ["OOOOO"] big-rets)
+ six-arity (sigs ["OOOOOO"] big-rets)
+ seven-arity (sigs ["OOOOOOO"] big-rets)
+ eight-arity (sigs ["OOOOOOOO"] big-rets)
+ nine-arity (sigs ["OOOOOOOOO"] big-rets)
+ ten-arity (sigs ["OOOOOOOOOO"] big-rets)]
+ (mapcat seq [zero-arity single-arity two-arity three-arity four-arity five-arity six-arity seven-arity eight-arity nine-arity ten-arity])))
+
+(defn gen-invokers []
+ (let [sb (StringBuilder. ^String header)
+ invoker-signatures (gen-sigs)]
+ (doseq [sig invoker-signatures]
+ (.append sb (gen-invoke sig))
+ (.append sb "\n\n"))
+ (.append sb footer)
+ (spit "src/jvm/clojure/lang/FnInvokers.java" (.toString sb))))
+
+(comment
+ (gen-invokers)
+ )
\ No newline at end of file
diff --git a/codegen/gen_math.clj b/codegen/gen_math.clj
new file mode 100644
index 0000000000..484649d3d3
--- /dev/null
+++ b/codegen/gen_math.clj
@@ -0,0 +1,263 @@
+;; This code was used to generate the clojure.math namespace in
+;; Clojure 1.11 to wrap Java 1.8 java.lang.Math methods. There are
+;; many small tweaks in this to get exactly the output that was
+;; desired and it was not intended to be reused in any way, it is
+;; included here for future reference.
+
+(ns gen-math
+ (:require
+ [clojure.reflect :as reflect]
+ [clojure.set :as set]
+ [clojure.string :as str])
+ (:import
+ [java.io StringWriter Writer]))
+
+;; manually created
+(declare HEADER)
+(declare FNS)
+(declare DOCS)
+(declare ARGS)
+(declare ARGTYPES)
+
+(def const-template
+ "(def
+ ^{:doc %s
+ :added %s
+ :const true
+ :tag %s}
+ %s
+ %s)\n\n")
+
+(defn- emit-constant
+ [^Writer writer {:keys [cname name added type]}]
+ (let [sym (symbol (str cname) (str name))
+ doc (str "\"" (get DOCS (symbol name)) "\"")
+ tag (str "'" type)]
+ (.write writer
+ (format const-template doc (pr-str added) tag name sym))))
+
+(def fn-template
+ "(defn %s
+ {:doc %s
+ :inline-arities %s
+ :inline %s
+ :added %s}
+ %s%s
+ %s)\n\n")
+
+(defn- clojurize
+ [sym]
+ (or
+ (get '{IEEEremainder IEEE-remainder} sym)
+ (let [s (name sym)]
+ (symbol
+ (str
+ (reduce
+ (fn [^StringBuilder b ^Character c]
+ (if (Character/isUpperCase c)
+ (.. b (append "-") (append (Character/toLowerCase c)))
+ (.append b c)))
+ (StringBuilder.)
+ s))))))
+
+(defn- inline-body
+ [params param-types]
+ (str/join " "
+ (map (fn [p pt] (format "(%s ~%s)" pt p))
+ params param-types)))
+
+(defn- body
+ [params param-types on-types]
+ (map (fn [p pt] (if (contains? on-types pt) `(~pt ~p) p))
+ params param-types))
+
+(defn- emit-fn
+ [^Writer writer {:keys [cname fname sigs]}]
+ (let [sym (symbol (str cname) (str fname))
+ arities (group-by #(-> % :parameter-types count) sigs)
+ arity (-> arities keys first) ;; NOTE: ignore multiple arities, none in Math
+ arity-sigs (get arities arity)
+ cname (clojurize fname)
+ doc (str "\"" (get DOCS cname) "\"")
+ sig (if (= 1 (count arity-sigs)) (first arity-sigs) (get ARGTYPES cname))
+ {pts :parameter-types, rt :return-type} sig
+ ps (get ARGS cname)
+ ;; coerce all args in inline body
+ inline-body (format "(fn %s `(%s%s))" (pr-str ps) (if (< 0 (count ps)) (str sym " ") sym) (inline-body ps pts))
+ ;; ps are hinted, so coerce only ps that can't be hinted - int type
+ body `(~sym ~@(body ps pts #{'int}))
+ rts (if (#{'long 'double} rt) (str "^" rt " ") "")
+ hints (map #(if (#{'long 'double} %) (symbol (str "^" %)) nil) pts)
+ pst (vec (remove nil? (interleave hints ps)))]
+ (.write writer
+ (format fn-template cname doc #{arity} inline-body (pr-str "1.11") rts pst body))))
+
+(defn gen-static-wrappers
+ [csym]
+ (let [added "1.11"
+ members (:members (reflect/type-reflect (resolve csym)))
+ statics (filter #(set/subset? #{:public :static} (:flags %)) members)
+ {fs false, ms true} (group-by #(contains? % :return-type) statics)
+ methods (->> ms (filter (fn [m]
+ (or (= 'scalb (:name m))
+ (empty? (set/intersection #{'int 'float} (set (:parameter-types m))))))))
+ by-name (group-by :name methods)
+ writer (StringWriter.)]
+ (.write writer HEADER)
+ (doseq [f fs]
+ (emit-constant writer (merge f {:cname csym, :added added})))
+ (doseq [n FNS]
+ (emit-fn writer {:cname csym, :fname n, :added added, :sigs (get by-name n)}))
+ (spit "src/clj/clojure/math.clj" (str writer))))
+
+(comment
+ (gen-static-wrappers 'Math)
+ )
+
+;;;; Manually provided info used during the generator
+
+(def ^String HEADER
+ "; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns
+ ^{:author \"Alex Miller\",
+ :doc \"Clojure wrapper functions for java.lang.Math static methods.
+
+ Function calls are inlined for performance, and type hinted for primitive
+ long or double parameters where appropriate. In general, Math methods are
+ optimized for performance and have bounds for error tolerance. If
+ greater precision is needed, use java.lang.StrictMath directly instead.
+
+ For more complete information, see:
+ https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html\"}
+ clojure.math)
+
+(set! *warn-on-reflection* true)
+
+")
+
+;; fns
+
+;; omitted: toIntExact
+;; omitted but include in core w/polymorphic impl: abs, min, max
+(def FNS
+ '[sin cos tan asin acos atan toRadians toDegrees exp log log10
+ sqrt cbrt IEEEremainder ceil floor rint atan2 pow round random
+ addExact subtractExact multiplyExact incrementExact decrementExact negateExact
+ floorDiv floorMod ulp signum sinh cosh tanh hypot expm1 log1p copySign getExponent
+ nextAfter nextUp nextDown scalb])
+
+;; docstrings to use
+(def DOCS
+ '{
+ E "Constant for e, the base for natural logarithms.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#E"
+ PI "Constant for pi, the ratio of the circumference of a circle to its diameter.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#PI"
+ sin "Returns the sine of an angle.\n If a is ##NaN, ##-Inf, ##Inf => ##NaN\n If a is zero => zero with the same sign as a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#sin-double-"
+ cos "Returns the cosine of an angle.\n If a is ##NaN, ##-Inf, ##Inf => ##NaN\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#cos-double-"
+ tan "Returns the tangent of an angle.\n If a is ##NaN, ##-Inf, ##Inf => ##NaN\n If a is zero => zero with the same sign as a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#tan-double-"
+ asin "Returns the arc sine of an angle, in the range -pi/2 to pi/2.\n If a is ##NaN or |a|>1 => ##NaN\n If a is zero => zero with the same sign as a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#asin-double-"
+ acos "Returns the arc cosine of a, in the range 0.0 to pi.\n If a is ##NaN or |a|>1 => ##NaN\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#acos-double-"
+ atan "Returns the arc tangent of a, in the range of -pi/2 to pi/2.\n If a is ##NaN => ##NaN\n If a is zero => zero with the same sign as a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#atan-double-"
+ to-radians "Converts an angle in degrees to an approximate equivalent angle in radians.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#toRadians-double-"
+ to-degrees "Converts an angle in radians to an approximate equivalent angle in degrees.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#toDegrees-double-"
+ exp "Returns Euler's number e raised to the power of a.\n If a is ##NaN => ##NaN\n If a is ##Inf => ##Inf\n If a is ##-Inf => +0.0\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#exp-double-"
+ log "Returns the natural logarithm (base e) of a.\n If a is ##NaN or negative => ##NaN\n If a is ##Inf => ##Inf\n If a is zero => ##-Inf\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#log-double-"
+ log10 "Returns the logarithm (base 10) of a.\n If a is ##NaN or negative => ##NaN\n If a is ##Inf => ##Inf\n If a is zero => ##-Inf\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#log10-double-"
+ sqrt "Returns the positive square root of a.\n If a is ##NaN or negative => ##NaN\n If a is ##Inf => ##Inf\n If a is zero => a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#sqrt-double-"
+ cbrt "Returns the cube root of a.\n If a is ##NaN => ##NaN\n If a is ##Inf or ##-Inf => a\n If a is zero => zero with sign matching a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#cbrt-double-"
+ IEEE-remainder "Returns the remainder per IEEE 754 such that\n remainder = dividend - divisor * n\n where n is the integer closest to the exact value of dividend / divisor.\n If two integers are equally close, then n is the even one.\n If the remainder is zero, sign will match dividend.\n If dividend or divisor is ##NaN, or dividend is ##Inf or ##-Inf, or divisor is zero => ##NaN\n If dividend is finite and divisor is infinite => dividend\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#IEEEremainder-double-double-"
+ ceil "Returns the smallest double greater than or equal to a, and equal to a\n mathematical integer.\n If a is ##NaN or ##Inf or ##-Inf or already equal to an integer => a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#ceil-double-"
+ floor "Returns the largest double less than or equal to a, and equal to a\n mathematical integer.\n If a is ##NaN or ##Inf or ##-Inf or already equal to an integer => a\n If a is less than zero but greater than -1.0 => -0.0\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floor-double-"
+ rint "Returns the double closest to a and equal to a mathematical integer.\n If two values are equally close, return the even one.\n If a is ##NaN or ##Inf or ##-Inf or zero => a\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#rint-double-"
+ atan2 "Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta).\n Computes the phase theta by computing an arc tangent of y/x in the range of -pi to pi.\n For more details on special cases, see:\n https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#atan2-double-double-"
+ pow "Returns the value of a raised to the power of b.\n For more details on special cases, see:\n https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#pow-double-double-"
+ round "Returns the closest long to a. If equally close to two values, return the one\n closer to ##Inf.\n If a is ##NaN => 0\n If a is ##-Inf or < Long/MIN_VALUE => Long/MIN_VALUE\n If a is ##Inf or > Long/MAX_VALUE => Long/MAX_VALUE\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#round-double-"
+ random "Returns a positive double between 0.0 and 1.0, chosen pseudorandomly with\n approximately random distribution.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#random--"
+ add-exact "Returns the sum of x and y, throws ArithmeticException on overflow.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#addExact-long-long-"
+ subtract-exact "Returns the difference of x and y, throws ArithmeticException on overflow.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#subtractExact-long-long-"
+ multiply-exact "Returns the product of x and y, throws ArithmeticException on overflow.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#multiplyExact-long-long-"
+ increment-exact "Returns a incremented by 1, throws ArithmeticException on overflow.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#incrementExact-long-"
+ decrement-exact "Returns a decremented by 1, throws ArithmeticException on overflow.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#decrementExact-long-"
+ negate-exact "Returns the negation of a, throws ArithmeticException on overflow.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#negateExact-long-"
+ floor-div "Integer division that rounds to negative infinity (as opposed to zero).\n The special case (floorDiv Long/MIN_VALUE -1) overflows and returns Long/MIN_VALUE.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floorDiv-long-long-"
+ floor-mod "Integer modulus x - (floorDiv(x, y) * y). Sign matches y and is in the\n range -|y| < r < |y|.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floorMod-long-long-"
+ ulp "Returns the size of an ulp (unit in last place) for d.\n If d is ##NaN => ##NaN\n If d is ##Inf or ##-Inf => ##Inf\n If d is zero => Double/MIN_VALUE\n If d is +/- Double/MAX_VALUE => 2^971\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#ulp-double-"
+ signum "Returns the signum function of d - zero for zero, 1.0 if >0, -1.0 if <0.\n If d is ##NaN => ##NaN\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#signum-double-"
+ sinh "Returns the hyperbolic sine of x, (e^x - e^-x)/2.\n If x is ##NaN => ##NaN\n If x is ##Inf or ##-Inf or zero => x\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#sinh-double-"
+ cosh "Returns the hyperbolic cosine of x, (e^x + e^-x)/2.\n If x is ##NaN => ##NaN\n If x is ##Inf or ##-Inf => ##Inf\n If x is zero => 1.0\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#cosh-double-"
+ tanh "Returns the hyperbolic tangent of x, sinh(x)/cosh(x).\n If x is ##NaN => ##NaN\n If x is zero => zero, with same sign\n If x is ##Inf => +1.0\n If x is ##-Inf => -1.0\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#tanh-double-"
+ hypot "Returns sqrt(x^2 + y^2) without intermediate underflow or overflow.\n If x or y is ##Inf or ##-Inf => ##Inf\n If x or y is ##NaN and neither is ##Inf or ##-Inf => ##NaN\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#hypot-double-double-"
+ expm1 "Returns e^x - 1. Near 0, expm1(x)+1 is more accurate to e^x than exp(x).\n If x is ##NaN => ##NaN\n If x is ##Inf => #Inf\n If x is ##-Inf => -1.0\n If x is zero => x\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#expm1-double-"
+ log1p "Returns ln(1+x). For small values of x, log1p(x) is more accurate than\n log(1.0+x).\n If x is ##NaN or < -1 => ##NaN\n If x is ##Inf => ##Inf\n If x is -1 => ##-Inf\n If x is 0 => 0 with sign matching x\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#log1p-double-"
+ copy-sign "Returns a double with the magnitude of the first argument and the sign of\n the second.\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#copySign-double-double-"
+ get-exponent "Returns the exponent of d.\n If d is ##NaN, ##Inf, ##-Inf => Double/MAX_EXPONENT + 1\n If d is zero or subnormal => Double/MIN_EXPONENT - 1\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#getExponent-double-"
+ next-after "Returns the adjacent floating point number to start in the direction of\n the second argument. If the arguments are equal, the second is returned.\n If either arg is #NaN => #NaN\n If both arguments are signed zeros => direction\n If start is +-Double/MIN_VALUE and direction would cause a smaller magnitude\n => zero with sign matching start\n If start is ##Inf or ##-Inf and direction would cause a smaller magnitude\n => Double/MAX_VALUE with same sign as start\n If start is equal to +=Double/MAX_VALUE and direction would cause a larger magnitude\n => ##Inf or ##-Inf with sign matching start\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextAfter-double-double-"
+ next-up "Returns the adjacent double of d in the direction of ##Inf.\n If d is ##NaN => ##NaN\n If d is ##Inf => ##Inf\n If d is zero => Double/MIN_VALUE\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextUp-double-"
+ next-down "Returns the adjacent double of d in the direction of ##-Inf.\n If d is ##NaN => ##NaN\n If d is ##-Inf => ##-Inf\n If d is zero => -Double/MIN_VALUE\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextDown-double-"
+ scalb "Returns d * 2^scaleFactor, scaling by a factor of 2. If the exponent\n is between Double/MIN_EXPONENT and Double/MAX_EXPONENT, the answer is exact.\n If d is ##NaN => ##NaN\n If d is ##Inf or ##-Inf => ##Inf or ##-Inf respectively\n If d is zero => zero of same sign as d\n See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextDown-double-"
+ })
+
+(def FNS
+ '[sin cos tan asin acos atan toRadians toDegrees exp log log10
+ sqrt cbrt IEEEremainder ceil floor rint atan2 pow round random
+ addExact subtractExact multiplyExact incrementExact decrementExact negateExact
+ floorDiv floorMod ulp signum sinh cosh tanh hypot expm1 log1p copySign getExponent
+ nextAfter nextUp nextDown scalb])
+
+;; arg names to use (match java.lang.Math signatures)
+(def ARGS
+ '{
+ sin [a]
+ cos [a]
+ tan [a]
+ asin [a]
+ acos [a]
+ atan [a]
+ to-radians [deg]
+ to-degrees [r]
+ exp [a]
+ log [a]
+ log10 [a]
+ sqrt [a]
+ cbrt [a]
+ IEEE-remainder [dividend divisor]
+ ceil [a]
+ floor [a]
+ rint [a]
+ atan2 [y x]
+ pow [a b]
+ round [a]
+ random []
+ add-exact [x y]
+ subtract-exact [x y]
+ multiply-exact [x y]
+ increment-exact [a]
+ decrement-exact [a]
+ negate-exact [a]
+ floor-div [x y]
+ floor-mod [x y]
+ ulp [d]
+ signum [d]
+ sinh [x]
+ cosh [x]
+ tanh [x]
+ hypot [x y]
+ expm1 [x]
+ log1p [x]
+ copy-sign [magnitude sign]
+ get-exponent [d]
+ next-after [start direction]
+ next-up [d]
+ next-down [d]
+ scalb [d scaleFactor]
+ })
+
+;; type signature to use (otherwise automatically determined)
+(def ARGTYPES
+ '{scalb {:parameter-types [double int] :return-type double}})
diff --git a/pom.xml b/pom.xml
index f3b848f9c4..3f8637f5bf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
clojureclojurejar
- 1.7.0-master-SNAPSHOT
+ 1.13.0-master-SNAPSHOThttp://clojure.org/Clojure core environment and runtime library.
@@ -26,29 +26,32 @@
-
- org.sonatype.oss
- oss-parent
- 7
-
-
scm:git:git@github.com:clojure/clojure.gitscm:git:git@github.com:clojure/clojure.gitgit@github.com:clojure/clojure.git
+ HEAD
+
+ true
+
+
- org.codehaus.jsr166-mirror
- jsr166y
- 1.7.0
- provided
+ org.clojure
+ spec.alpha
+ 0.5.238
+
+
+ org.clojure
+ core.specs.alpha
+ 0.4.74org.clojuretest.generative
- 0.5.2
+ 1.1.0test
@@ -60,7 +63,7 @@
org.clojuretest.check
- 0.5.9
+ 1.1.1test
@@ -69,8 +72,25 @@
+
+ javax.xml.ws
+ jaxws-api
+ 2.3.1
+ test
+
+
+
+ central
+ https://central.sonatype.com
+
+
+ central-snapshot
+ https://central.sonatype.com/repository/maven-snapshots/
+
+
+
@@ -86,16 +106,16 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 2.3.2
+ 3.13.0
- 1.6
- 1.6
- ${project.build.sourceEncoding}
+ 1.8
+ 1.8
+ UTF-8maven-antrun-plugin
- 1.6
+ 3.1.0clojure-compile
@@ -125,10 +145,11 @@
+
org.codehaus.mojobuild-helper-maven-plugin
- 1.5
+ 3.5.0add-clojure-source-dirs
@@ -146,7 +167,7 @@
maven-assembly-plugin
- 2.2
+ 3.7.1clojure-slim-jar
@@ -158,32 +179,20 @@
src/assembly/slim.xml
-
-
- clojure.main
-
- maven-jar-plugin
- 2.3.1
-
-
-
- clojure.main
-
-
-
+ 3.4.1maven-source-plugin
- 2.1.2
+ 3.3.1
- sources-jar
+ attach-sourcespackagejar
@@ -202,7 +211,7 @@
instead, push SCM changes in Hudson configuration -->
org.apache.maven.pluginsmaven-release-plugin
- 2.1
+ 3.0.1falsetrue
@@ -212,15 +221,59 @@
org.apache.maven.pluginsmaven-surefire-plugin
- 2.6
+ 3.2.5true
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.11.2
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+ none
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.7.0
+ true
+
+ central
+ true
+
+
+
+
+
+ test-direct
+
+ true
+
+
+
+ test-no-direct
+
+ false
+
+ distribution
@@ -228,7 +281,7 @@
maven-assembly-plugin
- 2.2
+ 3.7.1clojure-distribution
@@ -249,39 +302,64 @@
- sonatype-oss-release
-
+
+ signorg.apache.maven.plugins
- maven-deploy-plugin
- 2.7
-
- true
-
+ maven-gpg-plugin
+ 3.2.4
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+ local
+
+
+ org.clojure
+ test.check
+ 1.1.1
+
+
+ org.clojure
+ clojure
+
+
+
+
+
+
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.4.4
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.3
- default-deploy
- deploy
-
+ package
- deploy
+ shade
+
+
+
+ clojure.main
+
+
+ clojure.jar
+
-
-
- https://oss.sonatype.org/
-
- sonatype-nexus-staging
-
diff --git a/readme.txt b/readme.txt
index 4871d0f114..12fa75dea8 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,69 +1,67 @@
* Clojure
* Copyright (c) Rich Hickey. All rights reserved.
* The use and distribution terms for this software are covered by the
- * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+ * Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0/)
* which can be found in the file epl-v10.html at the root of this distribution.
* By using this software in any fashion, you are agreeing to be bound by
* the terms of this license.
* You must not remove this notice, or any other, from this software.
-Docs: http://clojure.org
-Feedback: http://groups.google.com/group/clojure
-Getting Started: http://dev.clojure.org/display/doc/Getting+Started
+Docs: https://clojure.org
+Feedback: https://ask.clojure.org
+Getting Started: https://clojure.org/guides/getting_started
-To run: java -cp clojure-${VERSION}.jar clojure.main
+To build locally with Maven:
-To build locally with Ant:
+ To test:
+ mvn test
- One-time setup: ./antsetup.sh
- To build: ant
+ To build (output JARs in target/):
+ mvn package
-Maven 2 build instructions:
+ To build without testing:
+ mvn package -Dmaven.test.skip=true
- To build: mvn package
- The built JARs will be in target/
+ To build and install in local Maven repository:
+ mvn install
- To build without testing: mvn package -Dmaven.test.skip=true
-
- To build and install in local Maven repository: mvn install
-
- To build a ZIP distribution: mvn package -Pdistribution
- The built .zip will be in target/
+ To build a standalone jar with dependencies included:
+ mvn -Plocal -Dmaven.test.skip=true package
+ To run with the standalone jar:
+ java -jar clojure.jar
--------------------------------------------------------------------------
This program uses the ASM bytecode engineering library which is distributed
with the following notice:
-Copyright (c) 2000-2005 INRIA, France Telecom
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
-3. Neither the name of the copyright holders nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
-THE POSSIBILITY OF SUCH DAMAGE.
+ ASM: a very small and fast Java bytecode manipulation framework
+ Copyright (c) 2000-2011 INRIA, France Telecom
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
@@ -75,7 +73,7 @@ under the Apache License:
Apache License
Version 2.0, January 2004
- http://www.apache.org/licenses/
+ https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj
index 9a773a14a4..9694037b50 100644
--- a/src/clj/clojure/core.clj
+++ b/src/clj/clojure/core.clj
@@ -73,20 +73,22 @@
rest (fn ^:static rest [x] (. clojure.lang.RT (more x))))
(def
- ^{:arglists '([coll x] [coll x & xs])
+ ^{:arglists '([] [coll] [coll x] [coll x & xs])
:doc "conj[oin]. Returns a new collection with the xs
- 'added'. (conj nil item) returns (item). The 'addition' may
- happen at different 'places' depending on the concrete type."
+ 'added'. (conj nil item) returns (item).
+ (conj coll) returns coll. (conj) returns [].
+ The 'addition' may happen at different 'places' depending
+ on the concrete type."
:added "1.0"
:static true}
conj (fn ^:static conj
([] [])
([coll] coll)
- ([coll x] (. clojure.lang.RT (conj coll x)))
+ ([coll x] (clojure.lang.RT/conj coll x))
([coll x & xs]
(if xs
- (recur (conj coll x) (first xs) (next xs))
- (conj coll x)))))
+ (recur (clojure.lang.RT/conj coll x) (first xs) (next xs))
+ (clojure.lang.RT/conj coll x)))))
(def
^{:doc "Same as (first (next x))"
@@ -128,7 +130,9 @@
:doc "Returns a seq on the collection. If the collection is
empty, returns nil. (seq nil) returns nil. seq also works on
Strings, native Java arrays (of reference types) and any objects
- that implement Iterable."
+ that implement Iterable. Note that seqs cache values, thus seq
+ should not be used on any Iterable whose iterator repeatedly
+ returns the same mutable object."
:tag clojure.lang.ISeq
:added "1.0"
:static true}
@@ -186,9 +190,9 @@
:static true}
assoc
(fn ^:static assoc
- ([map key val] (. clojure.lang.RT (assoc map key val)))
+ ([map key val] (clojure.lang.RT/assoc map key val))
([map key val & kvs]
- (let [ret (assoc map key val)]
+ (let [ret (clojure.lang.RT/assoc map key val)]
(if kvs
(if (next kvs)
(recur ret (first kvs) (second kvs) (nnext kvs))
@@ -235,13 +239,26 @@
(if (next body)
(with-meta arglist (conj (if (meta arglist) (meta arglist) {}) (first body)))
arglist)
- arglist)))]
+ arglist)))
+ resolve-tag (fn [argvec]
+ (let [m (meta argvec)
+ ^clojure.lang.Symbol tag (:tag m)]
+ (if (instance? clojure.lang.Symbol tag)
+ (if (clojure.lang.Util/equiv (.indexOf (.getName tag) ".") -1)
+ (if (clojure.lang.Util/equals nil (clojure.lang.Compiler$HostExpr/maybeSpecialTag tag))
+ (let [c (clojure.lang.Compiler$HostExpr/maybeClass tag false)]
+ (if c
+ (with-meta argvec (assoc m :tag (clojure.lang.Symbol/intern (.getName c))))
+ argvec))
+ argvec)
+ argvec)
+ argvec)))]
(if (seq? (first fdecl))
(loop [ret [] fdecls fdecl]
(if fdecls
- (recur (conj ret (asig (first fdecls))) (next fdecls))
+ (recur (conj ret (resolve-tag (asig (first fdecls)))) (next fdecls))
(seq ret)))
- (list (asig fdecl))))))
+ (list (resolve-tag (asig fdecl)))))))
(def
@@ -315,7 +332,8 @@
(list 'def (with-meta name m)
;;todo - restore propagation of fn name
;;must figure out how to convey primitive hints to self calls first
- (cons `fn fdecl) ))))
+ ;;(cons `fn fdecl)
+ (with-meta (cons `fn fdecl) {:rettag (:tag m)})))))
(. (var defn) (setMacro))
@@ -343,8 +361,10 @@
([a b] [a b])
([a b c] [a b c])
([a b c d] [a b c d])
- ([a b c d & args]
- (. clojure.lang.LazilyPersistentVector (create (cons a (cons b (cons c (cons d args))))))))
+ ([a b c d e] [a b c d e])
+ ([a b c d e f] [a b c d e f])
+ ([a b c d e f & args]
+ (. clojure.lang.LazilyPersistentVector (create (cons a (cons b (cons c (cons d (cons e (cons f args))))))))))
(defn vec
"Creates a new vector containing the contents of coll. Java arrays
@@ -498,6 +518,11 @@
:static true}
[x] (clojure.lang.Util/identical x true))
+(defn boolean?
+ "Return true if x is a Boolean"
+ {:added "1.9"}
+ [x] (instance? Boolean x))
+
(defn not
"Returns true if x is logical false, false otherwise."
{:tag Boolean
@@ -512,6 +537,12 @@
:static true}
[x] (not (nil? x)))
+(defn any?
+ "Returns true given any argument."
+ {:tag Boolean
+ :added "1.9"}
+ [x] true)
+
(defn str
"With no args, returns the empty string. With one arg x, returns
x.toString(). (str nil) returns the empty string. With more than
@@ -542,12 +573,34 @@
:static true}
[x] (instance? clojure.lang.Keyword x))
+(defmacro cond
+ "Takes a set of test/expr pairs. It evaluates each test one at a
+ time. If a test returns logical true, cond evaluates and returns
+ the value of the corresponding expr and doesn't evaluate any of the
+ other tests or exprs. (cond) returns nil."
+ {:added "1.0"}
+ [& clauses]
+ (when clauses
+ (list 'if (first clauses)
+ (if (next clauses)
+ (second clauses)
+ (throw (IllegalArgumentException.
+ "cond requires an even number of forms")))
+ (cons 'clojure.core/cond (next (next clauses))))))
+
(defn symbol
- "Returns a Symbol with the given namespace and name."
+ "Returns a Symbol with the given namespace and name. Arity-1 works
+ on strings, keywords, and vars."
{:tag clojure.lang.Symbol
:added "1.0"
:static true}
- ([name] (if (symbol? name) name (clojure.lang.Symbol/intern name)))
+ ([name]
+ (cond
+ (symbol? name) name
+ (instance? String name) (clojure.lang.Symbol/intern name)
+ (instance? clojure.lang.Var name) (.toSymbol ^clojure.lang.Var name)
+ (instance? clojure.lang.Keyword name) (.sym ^clojure.lang.Keyword name)
+ :else (throw (IllegalArgumentException. "no conversion to symbol"))))
([ns name] (clojure.lang.Symbol/intern ns name)))
(defn gensym
@@ -559,20 +612,6 @@
([] (gensym "G__"))
([prefix-string] (. clojure.lang.Symbol (intern (str prefix-string (str (. clojure.lang.RT (nextID))))))))
-(defmacro cond
- "Takes a set of test/expr pairs. It evaluates each test one at a
- time. If a test returns logical true, cond evaluates and returns
- the value of the corresponding expr and doesn't evaluate any of the
- other tests or exprs. (cond) returns nil."
- {:added "1.0"}
- [& clauses]
- (when clauses
- (list 'if (first clauses)
- (if (next clauses)
- (second clauses)
- (throw (IllegalArgumentException.
- "cond requires an even number of forms")))
- (cons 'clojure.core/cond (next (next clauses))))))
(defn keyword
"Returns a Keyword with the given namespace and name. Do not use :
@@ -609,7 +648,7 @@
:else (cons (first arglist) (spread (next arglist)))))
(defn list*
- "Creates a new list containing the items prepended to the rest, the
+ "Creates a new seq containing the items prepended to the rest, the
last of which will be treated as a sequence."
{:added "1.0"
:static true}
@@ -830,9 +869,9 @@
(defn zero?
"Returns true if num is zero, else false"
{
- :inline (fn [x] `(. clojure.lang.Numbers (isZero ~x)))
+ :inline (fn [num] `(. clojure.lang.Numbers (isZero ~num)))
:added "1.0"}
- [x] (. clojure.lang.Numbers (isZero x)))
+ [num] (. clojure.lang.Numbers (isZero num)))
(defn count
"Returns the number of items in the collection. (count nil) returns
@@ -933,7 +972,7 @@
(defn ^:private >0? [n] (clojure.lang.Numbers/gt n 0))
(defn +'
- "Returns the sum of nums. (+) returns 0. Supports arbitrary precision.
+ "Returns the sum of nums. (+') returns 0. Supports arbitrary precision.
See also: +"
{:inline (nary-inline 'addP)
:inline-arities >1?
@@ -957,7 +996,7 @@
(reduce1 + (+ x y) more)))
(defn *'
- "Returns the product of nums. (*) returns 1. Supports arbitrary precision.
+ "Returns the product of nums. (*') returns 1. Supports arbitrary precision.
See also: *"
{:inline (nary-inline 'multiplyP)
:inline-arities >1?
@@ -1095,6 +1134,18 @@
([x y & more]
(reduce1 min (min x y) more)))
+(defn abs
+ {:doc "Returns the absolute value of a.
+ If a is Long/MIN_VALUE => Long/MIN_VALUE
+ If a is a double and zero => +0.0
+ If a is a double and ##Inf or ##-Inf => ##Inf
+ If a is a double and ##NaN => ##NaN"
+ :inline-arities #{1}
+ :inline (fn [a] `(clojure.lang.Numbers/abs ~a))
+ :added "1.11"}
+ [a]
+ (clojure.lang.Numbers/abs a))
+
(defn dec'
"Returns a number one less than num. Supports arbitrary precision.
See also: dec"
@@ -1210,16 +1261,16 @@
(defn pos?
"Returns true if num is greater than zero, else false"
{
- :inline (fn [x] `(. clojure.lang.Numbers (isPos ~x)))
+ :inline (fn [num] `(. clojure.lang.Numbers (isPos ~num)))
:added "1.0"}
- [x] (. clojure.lang.Numbers (isPos x)))
+ [num] (. clojure.lang.Numbers (isPos num)))
(defn neg?
"Returns true if num is less than zero, else false"
{
- :inline (fn [x] `(. clojure.lang.Numbers (isNeg ~x)))
+ :inline (fn [num] `(. clojure.lang.Numbers (isNeg ~num)))
:added "1.0"}
- [x] (. clojure.lang.Numbers (isNeg x)))
+ [num] (. clojure.lang.Numbers (isNeg num)))
(defn quot
"quot[ient] of dividing numerator by denominator."
@@ -1360,6 +1411,36 @@
:static true}
[n] (not (even? n)))
+(defn int?
+ "Return true if x is a fixed precision integer"
+ {:added "1.9"}
+ [x] (or (instance? Long x)
+ (instance? Integer x)
+ (instance? Short x)
+ (instance? Byte x)))
+
+(defn pos-int?
+ "Return true if x is a positive fixed precision integer"
+ {:added "1.9"}
+ [x] (and (int? x)
+ (pos? x)))
+
+(defn neg-int?
+ "Return true if x is a negative fixed precision integer"
+ {:added "1.9"}
+ [x] (and (int? x)
+ (neg? x)))
+
+(defn nat-int?
+ "Return true if x is a non-negative fixed precision integer"
+ {:added "1.9"}
+ [x] (and (int? x)
+ (not (neg? x))))
+
+(defn double?
+ "Return true if x is a Double"
+ {:added "1.9"}
+ [x] (instance? Double x))
;;
@@ -1379,7 +1460,11 @@
"Returns a function that takes any number of arguments and returns x."
{:added "1.0"
:static true}
- [x] (fn [& args] x))
+ [x] (fn
+ ([] x)
+ ([_] x)
+ ([_ _] x)
+ ([_ _ & args] x)))
(defn identity
"Returns its argument."
@@ -1408,6 +1493,12 @@
;;map stuff
+(defn map-entry?
+ "Return true if x is a map entry"
+ {:added "1.8"}
+ [x]
+ (instance? java.util.Map$Entry x))
+
(defn contains?
"Returns true if key is present in the given collection, otherwise
returns false. Note that for numerically indexed collections like
@@ -1419,7 +1510,8 @@
[coll key] (. clojure.lang.RT (contains coll key)))
(defn get
- "Returns the value mapped to key, not-found or nil if key not present."
+ "Returns the value mapped to key, not-found or nil if key not present
+ in associative collection, set, string, array, or ILookup instance."
{:inline (fn [m k & nf] `(. clojure.lang.RT (get ~m ~k ~@nf)))
:inline-arities #{2 3}
:added "1.0"}
@@ -1529,6 +1621,48 @@
[^clojure.lang.Named x]
(. x (getNamespace)))
+(defn boolean
+ "Coerce to boolean"
+ {
+ :inline (fn [x] `(. clojure.lang.RT (booleanCast ~x)))
+ :added "1.0"}
+ [x] (clojure.lang.RT/booleanCast x))
+
+(defn ident?
+ "Return true if x is a symbol or keyword"
+ {:added "1.9"}
+ [x] (or (keyword? x) (symbol? x)))
+
+(defn simple-ident?
+ "Return true if x is a symbol or keyword without a namespace"
+ {:added "1.9"}
+ [x] (and (ident? x) (nil? (namespace x))))
+
+(defn qualified-ident?
+ "Return true if x is a symbol or keyword with a namespace"
+ {:added "1.9"}
+ [x] (boolean (and (ident? x) (namespace x) true)))
+
+(defn simple-symbol?
+ "Return true if x is a symbol without a namespace"
+ {:added "1.9"}
+ [x] (and (symbol? x) (nil? (namespace x))))
+
+(defn qualified-symbol?
+ "Return true if x is a symbol with a namespace"
+ {:added "1.9"}
+ [x] (boolean (and (symbol? x) (namespace x) true)))
+
+(defn simple-keyword?
+ "Return true if x is a keyword without a namespace"
+ {:added "1.9"}
+ [x] (and (keyword? x) (nil? (namespace x))))
+
+(defn qualified-keyword?
+ "Return true if x is a keyword with a namespace"
+ {:added "1.9"}
+ [x] (boolean (and (keyword? x) (namespace x) true)))
+
(defmacro locking
"Executes exprs in an implicit do, while holding the monitor of x.
Will release the monitor of x in all circumstances."
@@ -1536,10 +1670,12 @@
[x & body]
`(let [lockee# ~x]
(try
- (monitor-enter lockee#)
- ~@body
- (finally
- (monitor-exit lockee#)))))
+ (let [locklocal# lockee#]
+ (monitor-enter locklocal#)
+ (try
+ ~@body
+ (finally
+ (monitor-exit locklocal#)))))))
(defmacro ..
"form => fieldName-symbol or (instanceMethodName-symbol args*)
@@ -1652,7 +1788,8 @@
m)
m (if (meta mm-name)
(conj (meta mm-name) m)
- m)]
+ m)
+ mm-name (with-meta mm-name m)]
(when (= (count options) 1)
(throw (Exception. "The syntax for defmulti has changed. Example: (defmulti name dispatch-fn :default dispatch-value)")))
(let [options (apply hash-map options)
@@ -1661,7 +1798,7 @@
(check-valid-options options :default :hierarchy)
`(let [v# (def ~mm-name)]
(when-not (and (.hasRoot v#) (instance? clojure.lang.MultiFn (deref v#)))
- (def ~(with-meta mm-name m)
+ (def ~mm-name
(new clojure.lang.MultiFn ~(name mm-name) ~dispatch-fn ~default ~hierarchy)))))))
(defmacro defmethod
@@ -1869,7 +2006,7 @@
(defmacro with-bindings
"Takes a map of Var/value pairs. Installs for the given Vars the associated
- values as thread-local bindings. The executes body. Pops the installed
+ values as thread-local bindings. Then executes body. Pops the installed
bindings after body was evaluated. Returns the value of body."
{:added "1.1"}
[binding-map & body]
@@ -2238,6 +2375,17 @@
([^clojure.lang.IAtom atom f x y] (.swap atom f x y))
([^clojure.lang.IAtom atom f x y & args] (.swap atom f x y args)))
+(defn swap-vals!
+ "Atomically swaps the value of atom to be:
+ (apply f current-value-of-atom args). Note that f may be called
+ multiple times, and thus should be free of side effects.
+ Returns [old new], the value of the atom before and after the swap."
+ {:added "1.9"}
+ (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f] (.swapVals atom f))
+ (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f x] (.swapVals atom f x))
+ (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f x y] (.swapVals atom f x y))
+ (^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom f x y & args] (.swapVals atom f x y args)))
+
(defn compare-and-set!
"Atomically sets the value of atom to newval if and only if the
current value of the atom is identical to oldval. Returns true if
@@ -2253,6 +2401,12 @@
:static true}
[^clojure.lang.IAtom atom newval] (.reset atom newval))
+(defn reset-vals!
+ "Sets the value of atom to newval. Returns [old new], the value of the
+ atom before and after the reset."
+ {:added "1.9"}
+ ^clojure.lang.IPersistentVector [^clojure.lang.IAtom2 atom newval] (.resetVals atom newval))
+
(defn set-validator!
"Sets the validator-fn for a var/ref/agent/atom. validator-fn must be nil or a
side-effect-free fn of one argument, which will be passed the intended
@@ -2564,8 +2718,8 @@
{:added "1.0"
:static true}
[pred coll]
- (when (seq coll)
- (or (pred (first coll)) (recur pred (next coll)))))
+ (when-let [s (seq coll)]
+ (or (pred (first s)) (recur pred (next s)))))
(def
^{:tag Boolean
@@ -2659,7 +2813,7 @@
(defn filter
"Returns a lazy sequence of the items in coll for which
- (pred item) returns true. pred must be free of side-effects.
+ (pred item) returns logical true. pred must be free of side-effects.
Returns a transducer when no collection is provided."
{:added "1.0"
:static true}
@@ -2692,7 +2846,7 @@
(defn remove
"Returns a lazy sequence of the items in coll for which
- (pred item) returns false. pred must be free of side-effects.
+ (pred item) returns logical false. pred must be free of side-effects.
Returns a transducer when no collection is provided."
{:added "1.0"
:static true}
@@ -2754,7 +2908,7 @@
(defn take-while
"Returns a lazy sequence of successive items from coll while
- (pred item) returns true. pred must be free of side-effects.
+ (pred item) returns logical true. pred must be free of side-effects.
Returns a transducer when no collection is provided."
{:added "1.0"
:static true}
@@ -2774,7 +2928,7 @@
(cons (first s) (take-while pred (rest s))))))))
(defn drop
- "Returns a lazy sequence of all but the first n items in coll.
+ "Returns a laziness-preserving sequence of all but the first n items in coll.
Returns a stateful transducer when no collection is provided."
{:added "1.0"
:static true}
@@ -2791,19 +2945,25 @@
result
(rf result input))))))))
([n coll]
- (let [step (fn [n coll]
- (let [s (seq coll)]
- (if (and (pos? n) s)
- (recur (dec n) (rest s))
- s)))]
- (lazy-seq (step n coll)))))
+ (if (instance? clojure.lang.IDrop coll)
+ (or
+ (if (pos? n)
+ (.drop ^clojure.lang.IDrop coll (if (int? n) n (Math/ceil n)))
+ (seq coll))
+ ())
+ (let [step (fn [n coll]
+ (let [s (seq coll)]
+ (if (and (pos? n) s)
+ (recur (dec n) (rest s))
+ s)))]
+ (lazy-seq (step n coll))))))
(defn drop-last
"Return a lazy sequence of all but the last n (default 1) items in coll"
{:added "1.0"
:static true}
- ([s] (drop-last 1 s))
- ([n s] (map (fn [x _] x) s (drop n s))))
+ ([coll] (drop-last 1 coll))
+ ([n coll] (map (fn [x _] x) coll (drop n coll))))
(defn take-last
"Returns a seq of the last n items in coll. Depending on the type
@@ -2878,7 +3038,8 @@
[n x] (take n (repeat x)))
(defn iterate
- "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects"
+ "Returns a lazy (infinite!) sequence of x, (f x), (f (f x)) etc.
+ f must be free of side-effects"
{:added "1.0"
:static true}
[f x] (clojure.lang.Iterate/create f x) )
@@ -2893,15 +3054,15 @@
([]
(iterate inc' 0))
([end]
- (if (instance? Long end)
+ (if (int? end)
(clojure.lang.LongRange/create end)
(clojure.lang.Range/create end)))
([start end]
- (if (and (instance? Long start) (instance? Long end))
+ (if (and (int? start) (int? end))
(clojure.lang.LongRange/create start end)
(clojure.lang.Range/create start end)))
([start end step]
- (if (and (instance? Long start) (instance? Long end) (instance? Long step))
+ (if (and (int? start) (int? end) (int? step))
(clojure.lang.LongRange/create start end step)
(clojure.lang.Range/create start end step))))
@@ -2933,22 +3094,6 @@
(reduce1 merge-entry (or m1 {}) (seq m2)))]
(reduce1 merge2 maps))))
-
-
-(defn zipmap
- "Returns a map with the keys mapped to the corresponding vals."
- {:added "1.0"
- :static true}
- [keys vals]
- (loop [map {}
- ks (seq keys)
- vs (seq vals)]
- (if (and ks vs)
- (recur (assoc map (first ks) (first vs))
- (next ks)
- (next vs))
- map)))
-
(defn line-seq
"Returns the lines of text from rdr as a lazy sequence of strings.
rdr must implement java.io.BufferedReader."
@@ -2969,8 +3114,9 @@
(defn sort
"Returns a sorted sequence of the items in coll. If no comparator is
supplied, uses compare. comparator must implement
- java.util.Comparator. If coll is a Java array, it will be modified.
- To avoid this, sort a copy of the array."
+ java.util.Comparator. Guaranteed to be stable: equal elements will
+ not be reordered. If coll is a Java array, it will be modified. To
+ avoid this, sort a copy of the array."
{:added "1.0"
:static true}
([coll]
@@ -2979,15 +3125,16 @@
(if (seq coll)
(let [a (to-array coll)]
(. java.util.Arrays (sort a comp))
- (seq a))
+ (with-meta (seq a) (meta coll)))
())))
(defn sort-by
"Returns a sorted sequence of the items in coll, where the sort
order is determined by comparing (keyfn item). If no comparator is
supplied, uses compare. comparator must implement
- java.util.Comparator. If coll is a Java array, it will be modified.
- To avoid this, sort a copy of the array."
+ java.util.Comparator. Guaranteed to be stable: equal elements will
+ not be reordered. If coll is a Java array, it will be modified. To
+ avoid this, sort a copy of the array."
{:added "1.0"
:static true}
([keyfn coll]
@@ -3031,20 +3178,30 @@
{:added "1.0"
:static true}
[coll n]
+ (if (instance? clojure.lang.IDrop coll)
+ (if (pos? n)
+ (.drop ^clojure.lang.IDrop coll (if (int? n) n (Math/ceil n)))
+ (seq coll))
(loop [n n xs (seq coll)]
(if (and xs (pos? n))
(recur (dec n) (next xs))
- xs)))
+ xs))))
(defn nthrest
"Returns the nth rest of coll, coll when n is 0."
{:added "1.3"
:static true}
[coll n]
- (loop [n n xs coll]
- (if-let [xs (and (pos? n) (seq xs))]
- (recur (dec n) (rest xs))
- xs)))
+ (if (pos? n)
+ (or
+ (if (instance? clojure.lang.IDrop coll)
+ (.drop ^clojure.lang.IDrop coll (if (int? n) n (Math/ceil n)))
+ (loop [n n xs coll]
+ (if-let [xs (and (pos? n) (seq xs))]
+ (recur (dec n) (rest xs))
+ (seq xs))))
+ ())
+ coll))
(defn partition
"Returns a lazy sequence of lists of n items each, at offsets step
@@ -3140,7 +3297,7 @@
"Blocks the current thread (indefinitely!) until all actions
dispatched thus far, from this thread or agent, to the agent(s) have
occurred. Will block on failed agents. Will never return if
- a failed agent is restarted with :clear-actions true."
+ a failed agent is restarted with :clear-actions true or shutdown-agents was called."
{:added "1.0"
:static true}
[& agents]
@@ -3205,7 +3362,15 @@
;;;;;;;;;;;;;;;;;;;;; editable collections ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn transient
- "Returns a new, transient version of the collection, in constant time."
+ "Returns a new, transient version of the collection, in constant time.
+
+ Transients support a parallel set of 'changing' operations, with similar names
+ followed by ! - assoc!, conj! etc. These do the same things as their persistent
+ counterparts except the return values are themselves transient.
+
+ Note in particular that transients are not designed to be bashed in-place. You
+ must capture and use the return value in the next call. In this way, they support
+ the same code structure as the functional persistent code they replace."
{:added "1.1"
:static true}
[^clojure.lang.IEditableCollection coll]
@@ -3378,13 +3543,6 @@
:added "1.1"}
[x] (. clojure.lang.RT (charCast x)))
-(defn boolean
- "Coerce to boolean"
- {
- :inline (fn [x] `(. clojure.lang.RT (booleanCast ~x)))
- :added "1.0"}
- [x] (clojure.lang.RT/booleanCast x))
-
(defn unchecked-byte
"Coerce to byte. Subject to rounding or truncation."
{:inline (fn [x] `(. clojure.lang.RT (uncheckedByteCast ~x)))
@@ -3639,6 +3797,32 @@
([opts stream]
(. clojure.lang.LispReader (read stream opts))))
+(defn read+string
+ "Like read, and taking the same args. stream must be a LineNumberingPushbackReader.
+ Returns a vector containing the object read and the (whitespace-trimmed) string read."
+ {:added "1.10"}
+ ([] (read+string *in*))
+ ([stream] (read+string stream true nil))
+ ([stream eof-error? eof-value] (read+string stream eof-error? eof-value false))
+ ([^clojure.lang.LineNumberingPushbackReader stream eof-error? eof-value recursive?]
+ (try
+ (.captureString stream)
+ (let [o (read stream eof-error? eof-value recursive?)
+ s (.trim (.getString stream))]
+ [o s])
+ (catch Throwable ex
+ (.getString stream)
+ (throw ex))))
+ ([opts ^clojure.lang.LineNumberingPushbackReader stream]
+ (try
+ (.captureString stream)
+ (let [o (read opts stream)
+ s (.trim (.getString stream))]
+ [o s])
+ (catch Throwable ex
+ (.getString stream)
+ (throw ex)))))
+
(defn read-line
"Reads the next line from stream that is the current value of *in* ."
{:added "1.0"
@@ -3691,7 +3875,7 @@
(try
(with-open ~(subvec bindings 2) ~@body)
(finally
- (. ~(bindings 0) close))))
+ (. ~(bindings 0) ~'close))))
:else (throw (IllegalArgumentException.
"with-open only allows Symbols in bindings"))))
@@ -3706,9 +3890,11 @@
(let [gx (gensym)]
`(let [~gx ~x]
~@(map (fn [f]
- (if (seq? f)
- `(~(first f) ~gx ~@(next f))
- `(~f ~gx)))
+ (with-meta
+ (if (seq? f)
+ `(~(first f) ~gx ~@(next f))
+ `(~f ~gx))
+ (meta f)))
forms)
~gx)))
@@ -4227,81 +4413,128 @@
:static true}
([] (. clojure.lang.PersistentArrayMap EMPTY))
([& keyvals]
- (clojure.lang.PersistentArrayMap/createAsIfByAssoc (to-array keyvals))))
+ (let [ary (to-array keyvals)]
+ (if (odd? (alength ary))
+ (throw (IllegalArgumentException. (str "No value supplied for key: " (last keyvals))))
+ (clojure.lang.PersistentArrayMap/createAsIfByAssoc ary)))))
+
+(defn seq-to-map-for-destructuring
+ "Builds a map from a seq as described in
+ https://clojure.org/reference/special_forms#keyword-arguments"
+ {:added "1.11"}
+ [s]
+ (if (next s)
+ (clojure.lang.PersistentArrayMap/createAsIfByAssoc (to-array s))
+ (if (seq s) (first s) clojure.lang.PersistentArrayMap/EMPTY)))
-;redefine let and loop with destructuring
+;;redefine let and loop with destructuring
(defn destructure [bindings]
(let [bents (partition 2 bindings)
pb (fn pb [bvec b v]
- (let [pvec
- (fn [bvec b val]
- (let [gvec (gensym "vec__")]
- (loop [ret (-> bvec (conj gvec) (conj val))
- n 0
- bs b
- seen-rest? false]
- (if (seq bs)
- (let [firstb (first bs)]
- (cond
- (= firstb '&) (recur (pb ret (second bs) (list `nthnext gvec n))
- n
- (nnext bs)
- true)
- (= firstb :as) (pb ret (second bs) gvec)
- :else (if seen-rest?
- (throw (new Exception "Unsupported binding form, only :as can follow & parameter"))
- (recur (pb ret firstb (list `nth gvec n nil))
- (inc n)
- (next bs)
- seen-rest?))))
- ret))))
- pmap
- (fn [bvec b v]
- (let [gmap (gensym "map__")
- gmapseq (with-meta gmap {:tag 'clojure.lang.ISeq})
- defaults (:or b)]
- (loop [ret (-> bvec (conj gmap) (conj v)
- (conj gmap) (conj `(if (seq? ~gmap) (clojure.lang.PersistentHashMap/create (seq ~gmapseq)) ~gmap))
- ((fn [ret]
- (if (:as b)
- (conj ret (:as b) gmap)
- ret))))
- bes (reduce1
- (fn [bes entry]
- (reduce1 #(assoc %1 %2 ((val entry) %2))
- (dissoc bes (key entry))
- ((key entry) bes)))
- (dissoc b :as :or)
- {:keys #(if (keyword? %) % (keyword (str %))),
- :strs str, :syms #(list `quote %)})]
- (if (seq bes)
- (let [bb (key (first bes))
- bk (val (first bes))
- has-default (contains? defaults bb)]
- (recur (pb ret bb (if has-default
- (list `get gmap bk (defaults bb))
- (list `get gmap bk)))
- (next bes)))
- ret))))]
- (cond
- (symbol? b) (-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v))
- (keyword? b) (-> bvec (conj (symbol (name b))) (conj v))
- (vector? b) (pvec bvec b v)
- (map? b) (pmap bvec b v)
- :else (throw (new Exception (str "Unsupported binding form: " b))))))
+ (let [pvec
+ (fn [bvec b val]
+ (let [gvec (gensym "vec__")
+ gseq (gensym "seq__")
+ gfirst (gensym "first__")
+ has-rest (some #{'&} b)]
+ (loop [ret (let [ret (conj bvec gvec val)]
+ (if has-rest
+ (conj ret gseq (list `seq gvec))
+ ret))
+ n 0
+ bs b
+ seen-rest? false]
+ (if (seq bs)
+ (let [firstb (first bs)]
+ (cond
+ (= firstb '&) (recur (pb ret (second bs) gseq)
+ n
+ (nnext bs)
+ true)
+ (= firstb :as) (pb ret (second bs) gvec)
+ :else (if seen-rest?
+ (throw (new Exception "Unsupported binding form, only :as can follow & parameter"))
+ (recur (pb (if has-rest
+ (conj ret
+ gfirst `(first ~gseq)
+ gseq `(next ~gseq))
+ ret)
+ firstb
+ (if has-rest
+ gfirst
+ (list `nth gvec n nil)))
+ (inc n)
+ (next bs)
+ seen-rest?))))
+ ret))))
+ pmap
+ (fn [bvec b v]
+ (let [gmap (gensym "map__")
+ gmapseq (with-meta gmap {:tag 'clojure.lang.ISeq})
+ defaults (:or b)]
+ (loop [ret (-> bvec (conj gmap) (conj v)
+ (conj gmap) (conj `(if (seq? ~gmap)
+ (if (next ~gmapseq)
+ (clojure.lang.PersistentArrayMap/createAsIfByAssoc (to-array ~gmapseq))
+ (if (seq ~gmapseq) (first ~gmapseq) clojure.lang.PersistentArrayMap/EMPTY))
+ ~gmap))
+ ((fn [ret]
+ (if (:as b)
+ (conj ret (:as b) gmap)
+ ret))))
+ bes (let [transforms
+ (reduce1
+ (fn [transforms mk]
+ (if (keyword? mk)
+ (let [mkns (namespace mk)
+ mkn (name mk)]
+ (cond (= mkn "keys") (assoc transforms mk #(keyword (or mkns (namespace %)) (name %)))
+ (= mkn "syms") (assoc transforms mk #(list `quote (symbol (or mkns (namespace %)) (name %))))
+ (= mkn "strs") (assoc transforms mk str)
+ :else transforms))
+ transforms))
+ {}
+ (keys b))]
+ (reduce1
+ (fn [bes entry]
+ (reduce1 #(assoc %1 %2 ((val entry) %2))
+ (dissoc bes (key entry))
+ ((key entry) bes)))
+ (dissoc b :as :or)
+ transforms))]
+ (if (seq bes)
+ (let [bb (key (first bes))
+ bk (val (first bes))
+ local (if (instance? clojure.lang.Named bb) (with-meta (symbol nil (name bb)) (meta bb)) bb)
+ bv (if (contains? defaults local)
+ (list `get gmap bk (defaults local))
+ (list `get gmap bk))]
+ (recur (if (ident? bb)
+ (-> ret (conj local bv))
+ (pb ret bb bv))
+ (next bes)))
+ ret))))]
+ (cond
+ (symbol? b) (-> bvec (conj b) (conj v))
+ (vector? b) (pvec bvec b v)
+ (map? b) (pmap bvec b v)
+ :else (throw (new Exception (str "Unsupported binding form: " b))))))
process-entry (fn [bvec b] (pb bvec (first b) (second b)))]
(if (every? symbol? (map first bents))
bindings
- (if-let [kwbs (seq (filter #(keyword? (first %)) bents))]
- (throw (new Exception (str "Unsupported binding key: " (ffirst kwbs))))
- (reduce1 process-entry [] bents)))))
+ (reduce1 process-entry [] bents))))
(defmacro let
"binding => binding-form init-expr
+ binding-form => name, or destructuring-form
+ destructuring-form => map-destructure-form, or seq-destructure-form
Evaluates the exprs in a lexical context in which the symbols in
the binding-forms are bound to their respective init-exprs or parts
- therein."
+ therein.
+
+ See https://clojure.org/reference/special_forms#binding-forms for
+ more information about destructuring."
{:added "1.0", :special-form true, :forms '[(let [bindings*] exprs*)]}
[bindings & body]
(assert-args
@@ -4329,12 +4562,14 @@
;redefine fn with destructuring and pre/post conditions
(defmacro fn
- "params => positional-params* , or positional-params* & next-param
+ "params => positional-params*, or positional-params* & rest-param
positional-param => binding-form
- next-param => binding-form
- name => symbol
+ rest-param => binding-form
+ binding-form => name, or destructuring-form
+
+ Defines a function.
- Defines a function"
+ See https://clojure.org/reference/special_forms#fn for more information"
{:added "1.0", :special-form true,
:forms '[(fn name? [params* ] exprs*) (fn name? ([params* ] exprs*)+)]}
[& sigs]
@@ -4588,14 +4823,24 @@
(apply println xs)))
(import clojure.lang.ExceptionInfo clojure.lang.IExceptionInfo)
+
+(defn ^:private elide-top-frames
+ [^Throwable ex class-name]
+ (let [tr (.getStackTrace ex)]
+ (doto ex
+ (.setStackTrace
+ (when tr
+ (into-array StackTraceElement
+ (drop-while #(= class-name (.getClassName ^StackTraceElement %1)) tr)))))))
+
(defn ex-info
"Create an instance of ExceptionInfo, a RuntimeException subclass
that carries a map of additional data."
{:added "1.4"}
([msg map]
- (ExceptionInfo. msg map))
+ (elide-top-frames (ExceptionInfo. msg map) "clojure.core$ex_info"))
([msg map cause]
- (ExceptionInfo. msg map cause)))
+ (elide-top-frames (ExceptionInfo. msg map cause) "clojure.core$ex_info")))
(defn ex-data
"Returns exception data (a map) if ex is an IExceptionInfo.
@@ -4605,9 +4850,29 @@
(when (instance? IExceptionInfo ex)
(.getData ^IExceptionInfo ex)))
+(defn ex-message
+ "Returns the message attached to ex if ex is a Throwable.
+ Otherwise returns nil."
+ {:added "1.10"}
+ [ex]
+ (when (instance? Throwable ex)
+ (.getMessage ^Throwable ex)))
+
+(defn ex-cause
+ "Returns the cause of ex if ex is a Throwable.
+ Otherwise returns nil."
+ {:tag Throwable
+ :added "1.10"}
+ [ex]
+ (when (instance? Throwable ex)
+ (.getCause ^Throwable ex)))
+
(defmacro assert
- "Evaluates expr and throws an exception if it does not evaluate to
- logical true."
+ "Evaluates expression x and throws an AssertionError with optional
+ message if x does not evaluate to logical true.
+
+ Assertion checks are omitted from compiled code if '*assert*' is
+ false."
{:added "1.0"}
([x]
(when *assert*
@@ -4779,22 +5044,44 @@
(^String [^String s start end] (. s (substring start end))))
(defn max-key
- "Returns the x for which (k x), a number, is greatest."
+ "Returns the x for which (k x), a number, is greatest.
+
+ If there are multiple such xs, the last one is returned."
{:added "1.0"
:static true}
([k x] x)
([k x y] (if (> (k x) (k y)) x y))
([k x y & more]
- (reduce1 #(max-key k %1 %2) (max-key k x y) more)))
+ (let [kx (k x) ky (k y)
+ [v kv] (if (> kx ky) [x kx] [y ky])]
+ (loop [v v kv kv more more]
+ (if more
+ (let [w (first more)
+ kw (k w)]
+ (if (>= kw kv)
+ (recur w kw (next more))
+ (recur v kv (next more))))
+ v)))))
(defn min-key
- "Returns the x for which (k x), a number, is least."
+ "Returns the x for which (k x), a number, is least.
+
+ If there are multiple such xs, the last one is returned."
{:added "1.0"
:static true}
([k x] x)
([k x y] (if (< (k x) (k y)) x y))
([k x y & more]
- (reduce1 #(min-key k %1 %2) (min-key k x y) more)))
+ (let [kx (k x) ky (k y)
+ [v kv] (if (< kx ky) [x kx] [y ky])]
+ (loop [v v kv kv more more]
+ (if more
+ (let [w (first more)
+ kw (k w)]
+ (if (<= kw kv)
+ (recur w kw (next more))
+ (recur v kv (next more))))
+ v)))))
(defn distinct
"Returns a lazy sequence of the elements of coll with duplicates removed.
@@ -5023,10 +5310,10 @@
array ret."
{:added "1.0"}
[a idx ret expr]
- `(let [a# ~a
+ `(let [a# ~a l# (alength a#)
~ret (aclone a#)]
(loop [~idx 0]
- (if (< ~idx (alength a#))
+ (if (< ~idx l#)
(do
(aset ~ret ~idx ~expr)
(recur (unchecked-inc ~idx)))
@@ -5038,10 +5325,10 @@
evaluation of expr at each step, returning ret."
{:added "1.0"}
[a idx ret init expr]
- `(let [a# ~a]
+ `(let [a# ~a l# (alength a#)]
(loop [~idx 0 ~ret ~init]
- (if (< ~idx (alength a#))
- (recur (unchecked-inc ~idx) ~expr)
+ (if (< ~idx l#)
+ (recur (unchecked-inc-int ~idx) ~expr)
~ret))))
(defn float-array
@@ -5155,6 +5442,13 @@
{:added "1.0"}
[xs] `(. clojure.lang.Numbers longs ~xs))
+(defn bytes?
+ "Return true if x is a byte array"
+ {:added "1.9"}
+ [x] (if (nil? x)
+ false
+ (-> x class .getComponentType (= Byte/TYPE))))
+
(import '(java.util.concurrent BlockingQueue LinkedBlockingQueue))
(defn seque
@@ -5483,7 +5777,9 @@
(defn iterator-seq
"Returns a seq on a java.util.Iterator. Note that most collections
- providing iterators implement Iterable and thus support seq directly."
+ providing iterators implement Iterable and thus support seq directly.
+ Seqs cache values, thus iterator-seq should not be used on any
+ iterator that repeatedly returns the same mutable object."
{:added "1.0"
:static true}
[iter]
@@ -5566,9 +5862,11 @@
(list* `gen-class :name (.replace (str name) \- \_) :impl-ns name :main true (next gen-class-clause)))
references (remove #(= :gen-class (first %)) references)
;ns-effect (clojure.core/in-ns name)
- ]
+ name-metadata (meta name)]
`(do
(clojure.core/in-ns '~name)
+ ~@(when name-metadata
+ `((.resetMeta (clojure.lang.Namespace/find '~name) ~name-metadata)))
(with-loading-context
~@(when gen-class-call (list gen-class-call))
~@(when (and (not= name 'clojure.core) (not-any? #(= :refer-clojure (first %)) references))
@@ -5618,7 +5916,7 @@
exception (Exception. message)
raw-trace (.getStackTrace exception)
boring? #(not= (.getMethodName ^StackTraceElement %) "doInvoke")
- trace (into-array (drop 2 (drop-while boring? raw-trace)))]
+ trace (into-array StackTraceElement (drop 2 (drop-while boring? raw-trace)))]
(.setStackTrace exception trace)
(throw (clojure.lang.Compiler$CompilerException.
*file*
@@ -5657,7 +5955,7 @@
(let [d (root-resource lib)]
(subs d 0 (.lastIndexOf d "/"))))
-(declare load)
+(def ^:declared ^:redef load)
(defn- load-one
"Loads a lib given its name. If need-ns, ensures that the associated
@@ -5692,13 +5990,15 @@
(name lib) prefix)
(let [lib (if prefix (symbol (str prefix \. lib)) lib)
opts (apply hash-map options)
- {:keys [as reload reload-all require use verbose]} opts
+ {:keys [as reload reload-all require use verbose as-alias]} opts
loaded (contains? @*loaded-libs* lib)
- load (cond reload-all
- load-all
- (or reload (not require) (not loaded))
- load-one)
need-ns (or as use)
+ load (cond reload-all load-all
+ reload load-one
+ (not loaded) (cond need-ns load-one
+ as-alias (fn [lib _need _require] (create-ns lib))
+ :else load-one))
+
filter-opts (select-keys opts '(:exclude :only :rename :refer))
undefined-on-entry (not (find-ns lib))]
(binding [*loading-verbosely* (or *loading-verbosely* verbose)]
@@ -5710,13 +6010,17 @@
(remove-ns lib))
(throw e)))
(throw-if (and need-ns (not (find-ns lib)))
- "namespace '%s' not found" lib))
+ "namespace '%s' not found" lib))
(when (and need-ns *loading-verbosely*)
(printf "(clojure.core/in-ns '%s)\n" (ns-name *ns*)))
(when as
(when *loading-verbosely*
(printf "(clojure.core/alias '%s '%s)\n" as lib))
(alias as lib))
+ (when as-alias
+ (when *loading-verbosely*
+ (printf "(clojure.core/alias '%s '%s)\n" as-alias lib))
+ (alias as-alias lib))
(when (or use (:refer filter-opts))
(when *loading-verbosely*
(printf "(clojure.core/refer '%s" lib)
@@ -5733,7 +6037,7 @@
opts (interleave flags (repeat true))
args (filter (complement keyword?) args)]
; check for unsupported options
- (let [supported #{:as :reload :reload-all :require :use :verbose :refer}
+ (let [supported #{:as :reload :reload-all :require :use :verbose :refer :as-alias}
unsupported (seq (remove supported flags))]
(throw-if unsupported
(apply str "Unsupported option(s) supplied: "
@@ -5783,9 +6087,11 @@
'require loads a lib by loading its root resource. The root resource path
is derived from the lib name in the following manner:
Consider a lib named by the symbol 'x.y.z; it has the root directory
- /x/y/, and its root resource is /x/y/z.clj. The root
- resource should contain code to create the lib's namespace (usually by using
- the ns macro) and load any additional lib resources.
+ /x/y/, and its root resource is /x/y/z.clj, or
+ /x/y/z.cljc if /x/y/z.clj does not exist. The
+ root resource should contain code to create the lib's
+ namespace (usually by using the ns macro) and load any additional
+ lib resources.
Libspecs
@@ -5795,6 +6101,9 @@
Recognized options:
:as takes a symbol as its argument and makes that symbol an alias to the
lib's namespace in the current namespace.
+ :as-alias takes a symbol as its argument and aliases like :as, however
+ the lib will not be loaded. If the lib has not been loaded, a new
+ empty namespace will be created (as with create-ns).
:refer takes a list of symbols to refer from the namespace or the :all
keyword to bring in all public vars.
@@ -5811,9 +6120,10 @@
A flag is a keyword.
Recognized flags: :reload, :reload-all, :verbose
:reload forces loading of all the identified libs even if they are
- already loaded
+ already loaded (has no effect on libspecs using :as-alias)
:reload-all implies :reload and also forces loading of all libs that the
identified libs directly or indirectly load via require or use
+ (has no effect on libspecs using :as-alias)
:verbose triggers printing information about each load, alias, and refer
Example:
@@ -5827,6 +6137,26 @@
[& args]
(apply load-libs :require args))
+(defn- serialized-require
+ "Like 'require', but serializes loading.
+ Interim function preferred over 'require' for known asynchronous loads.
+ Future changes may make these equivalent."
+ {:added "1.10"}
+ [& args]
+ (locking clojure.lang.RT/REQUIRE_LOCK
+ (apply require args)))
+
+(defn requiring-resolve
+ "Resolves namespace-qualified sym per 'resolve'. If initial resolve
+fails, attempts to require sym's namespace and retries."
+ {:added "1.10"}
+ [sym]
+ (if (qualified-symbol? sym)
+ (or (resolve sym)
+ (do (-> sym namespace symbol serialized-require)
+ (resolve sym)))
+ (throw (IllegalArgumentException. (str "Not a qualified symbol: " sym)))))
+
(defn use
"Like 'require, but also refers to each lib's namespace using
clojure.core/refer. Use :use in the ns macro in preference to calling
@@ -5847,7 +6177,8 @@
"Loads Clojure code from resources in classpath. A path is interpreted as
classpath-relative if it begins with a slash or relative to the root
directory for the current namespace otherwise."
- {:added "1.0"}
+ {:redef true
+ :added "1.0"}
[& paths]
(doseq [^String path paths]
(let [^String path (if (.startsWith path "/")
@@ -5913,10 +6244,13 @@
created."
{:added "1.0"
:static true}
- ([m [k & ks] f & args]
- (if ks
- (assoc m k (apply update-in (get m k) ks f args))
- (assoc m k (apply f (get m k) args)))))
+ ([m ks f & args]
+ (let [up (fn up [m ks f args]
+ (let [[k & ks] ks]
+ (if ks
+ (assoc m k (up (get m k) ks f args))
+ (assoc m k (apply f (get m k) args)))))]
+ (up m ks f args))))
(defn update
"'Updates' a value in an associative structure, where k is a
@@ -5936,13 +6270,6 @@
([m k f x y z & more]
(assoc m k (apply f (get m k) x y z more))))
-(defn empty?
- "Returns true if coll has no items - same as (not (seq coll)).
- Please use the idiom (seq x) rather than (not (empty? x))"
- {:added "1.0"
- :static true}
- [coll] (not (seq coll)))
-
(defn coll?
"Returns true if x implements IPersistentCollection"
{:added "1.0"
@@ -5955,6 +6282,11 @@
:static true}
[x] (instance? clojure.lang.IPersistentList x))
+(defn seqable?
+ "Return true if the seq function is supported for x"
+ {:added "1.9"}
+ [x] (clojure.lang.RT/canSeq x))
+
(defn ifn?
"Returns true if x implements IFn. Note that many data structures
(e.g. sets and maps) implement IFn"
@@ -5993,12 +6325,27 @@
:static true}
[coll] (instance? clojure.lang.Counted coll))
+(defn empty?
+ "Returns true if coll has no items. To check the emptiness of a seq,
+ please use the idiom (seq x) rather than (not (empty? x))"
+ {:added "1.0"
+ :static true}
+ [coll]
+ (if (counted? coll)
+ (zero? (count coll))
+ (not (seq coll))))
+
(defn reversible?
"Returns true if coll implements Reversible"
{:added "1.0"
:static true}
[coll] (instance? clojure.lang.Reversible coll))
+(defn indexed?
+ "Return true if coll implements Indexed, indicating efficient lookup by index"
+ {:added "1.9"}
+ [coll] (instance? clojure.lang.Indexed coll))
+
(def ^:dynamic
^{:doc "bound in a repl thread to the most recent value printed"
:added "1.0"}
@@ -6019,6 +6366,11 @@
:added "1.0"}
*e)
+(def ^:dynamic
+ ^{:doc "Bound to true in a repl thread"
+ :added "1.12"}
+ *repl* false)
+
(defn trampoline
"trampoline can be used to convert algorithms requiring mutual
recursion without stack consumption. Calls f with supplied args, if
@@ -6115,8 +6467,7 @@
~(emit pred expr more))
:else `(if-let [p# (~pred ~a ~expr)]
(~c p#)
- ~(emit pred expr more)))))
- gres (gensym "res__")]
+ ~(emit pred expr more)))))]
`(let [~gpred ~pred
~gexpr ~expr]
~(emit gpred gexpr clauses))))
@@ -6173,7 +6524,10 @@
Supported options:
:elide-meta - a collection of metadata keys to elide during compilation.
:disable-locals-clearing - set to true to disable clearing, useful for using a debugger
- Alpha, subject to change."
+ :direct-linking - set to true to use direct static invocation of functions, rather than vars
+ Note that call sites compiled with direct linking will not be affected by var redefinition.
+ Use ^:redef (or ^:dynamic) on a var to prevent direct linking and allow redefinition.
+ See https://clojure.org/reference/compilation for more information."
{:added "1.4"})
(add-doc-and-meta *ns*
@@ -6251,6 +6605,11 @@
"
{:added "1.0"})
+(add-doc-and-meta *assert*
+ "When set to logical false, 'assert' will omit assertion checks in
+ compiled code. Defaults to true."
+ {:added "1.0"})
+
(defn future?
"Returns true if x is a future"
{:added "1.1"
@@ -6302,6 +6661,19 @@
([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c)))
([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds)))))
+(defn zipmap
+ "Returns a map with the keys mapped to the corresponding vals."
+ {:added "1.0"
+ :static true}
+ [keys vals]
+ (loop [map (transient {})
+ ks (seq keys)
+ vs (seq vals)]
+ (if (and ks vs)
+ (recur (assoc! map (first ks) (first vs))
+ (next ks)
+ (next vs))
+ (persistent! map))))
;;;;;;; case ;;;;;;;;;;;;;
(defn- shift-mask [shift mask x]
@@ -6374,7 +6746,7 @@
(next ks) (next vs))
m))
assoc-multi (fn [m h bucket]
- (let [testexprs (apply concat bucket)
+ (let [testexprs (mapcat (fn [kv] [(list 'quote (first kv)) (second kv)]) bucket)
expr `(condp = ~expr-sym ~@testexprs ~default)]
(assoc m h expr)))
hmap (reduce1
@@ -6481,8 +6853,6 @@
`(let [~ge ~e] (case* ~ge ~shift ~mask ~default ~imap ~switch-type :hash-identity ~skip-check))))))))
-;; redefine reduce with internal-reduce
-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; helper files ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(alter-meta! (find-ns 'clojure.core) assoc :doc "Fundamental library of the Clojure language")
(load "core_proxy")
@@ -6491,9 +6861,93 @@
(load "core_deftype")
(load "core/protocols")
(load "gvec")
-(load "instant")
+
+(defn stream-reduce!
+ "Works like reduce but takes a java.util.stream.BaseStream as its source.
+ Honors 'reduced', is a terminal operation on the stream"
+ {:added "1.12"}
+ ([f ^java.util.stream.BaseStream s]
+ (clojure.core.protocols/iterator-reduce! (.iterator s) f))
+ ([f init ^java.util.stream.BaseStream s]
+ (clojure.core.protocols/iterator-reduce! (.iterator s) f init)))
+
+(defn stream-seq!
+ "Takes a java.util.stream.BaseStream instance s and returns a seq of its
+ contents. This is a terminal operation on the stream."
+ {:added "1.12"}
+ [^java.util.stream.BaseStream stream]
+ (iterator-seq (.iterator stream)))
+
+(defn stream-transduce!
+ "Works like transduce but takes a java.util.stream.BaseStream as its source.
+ This is a terminal operation on the stream."
+ {:added "1.12"}
+ ([xform f ^java.util.stream.BaseStream stream] (stream-transduce! xform f (f) stream))
+ ([xform f init ^java.util.stream.BaseStream stream]
+ (let [f (xform f)
+ ret (stream-reduce! f init stream)]
+ (f ret))))
+
+(defn stream-into!
+ "Returns a new coll consisting of coll with all of the items of the
+ stream conjoined. This is a terminal operation on the stream."
+ {:added "1.12"}
+ ([to ^java.util.stream.BaseStream stream]
+ (if (instance? clojure.lang.IEditableCollection to)
+ (with-meta (persistent! (stream-reduce! conj! (transient to) stream)) (meta to))
+ (stream-reduce! conj to stream)))
+ ([to xform ^java.util.stream.BaseStream stream]
+ (if (instance? clojure.lang.IEditableCollection to)
+ (with-meta (persistent! (stream-transduce! xform conj! (transient to) stream)) (meta to))
+ (stream-transduce! xform conj to stream))))
+
+(defmacro ^:private when-class [class-name & body]
+ `(try
+ (Class/forName ^String ~class-name)
+ ~@body
+ (catch ClassNotFoundException _#)))
+
+(when-class "java.sql.Timestamp"
+ (load "instant"))
+
+(defprotocol Inst
+ (inst-ms* [inst]))
+
+(extend-protocol Inst
+ java.util.Date
+ (inst-ms* [inst] (.getTime ^java.util.Date inst)))
+
+(defn inst-ms
+ "Return the number of milliseconds since January 1, 1970, 00:00:00 GMT"
+ {:added "1.9"}
+ [inst]
+ (inst-ms* inst))
+
+(defn inst?
+ "Return true if x satisfies Inst"
+ {:added "1.9"}
+ [x]
+ (satisfies? Inst x))
+
+(extend-protocol clojure.core/Inst
+ java.time.Instant
+ (inst-ms* [inst] (.toEpochMilli ^java.time.Instant inst)))
+
(load "uuid")
+(defn uuid?
+ "Return true if x is a java.util.UUID"
+ {:added "1.9"}
+ [x] (instance? java.util.UUID x))
+
+(defn random-uuid
+ {:doc "Returns a pseudo-randomly generated java.util.UUID instance (i.e. type 4).
+
+ See: https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#randomUUID--"
+ :added "1.11"}
+ ^java.util.UUID [] (java.util.UUID/randomUUID))
+
+;; redefine reduce with internal-reduce
(defn reduce
"f should be a function of 2 arguments. If val is not supplied,
returns the result of applying f to the first 2 items in coll, then
@@ -6521,30 +6975,20 @@
init)
;;slow path default
- clojure.lang.IPersistentMap
- (kv-reduce
- [amap f init]
- (reduce (fn [ret [k v]] (f ret k v)) init amap))
-
- clojure.lang.PersistentHashMap
- (kv-reduce
- [amap f init]
- (.kvreduce amap f init))
-
- clojure.lang.PersistentArrayMap
- (kv-reduce
+ java.lang.Object
+ (kv-reduce
[amap f init]
- (.kvreduce amap f init))
-
- clojure.lang.PersistentTreeMap
- (kv-reduce
+ (reduce (fn [ret ^java.util.Map$Entry me]
+ (f ret
+ (.getKey me)
+ (.getValue me)))
+ init
+ amap))
+
+ clojure.lang.IKVReduce
+ (kv-reduce
[amap f init]
- (.kvreduce amap f init))
-
- clojure.lang.PersistentVector
- (kv-reduce
- [vec f init]
- (.kvreduce vec f init)))
+ (.kvreduce amap f init)))
(defn reduce-kv
"Reduces an associative collection. f should be a function of 3
@@ -6587,17 +7031,24 @@
(f ret))))
(defn into
- "Returns a new coll consisting of to-coll with all of the items of
- from-coll conjoined. A transducer may be supplied."
+ "Returns a new coll consisting of to with all of the items of
+ from conjoined. A transducer may be supplied.
+ (into x) returns x. (into) returns []."
{:added "1.0"
:static true}
+ ([] [])
+ ([to] to)
([to from]
(if (instance? clojure.lang.IEditableCollection to)
(with-meta (persistent! (reduce conj! (transient to) from)) (meta to))
(reduce conj to from)))
([to xform from]
(if (instance? clojure.lang.IEditableCollection to)
- (with-meta (persistent! (transduce xform conj! (transient to) from)) (meta to))
+ (let [tm (meta to)
+ rf (fn
+ ([coll] (-> (persistent! coll) (with-meta tm)))
+ ([coll v] (conj! coll v)))]
+ (transduce xform rf (transient to) from))
(transduce xform conj to from))))
(defn mapv
@@ -6620,7 +7071,7 @@
(defn filterv
"Returns a vector of the items in coll for which
- (pred item) returns true. pred must be free of side-effects."
+ (pred item) returns logical true. pred must be free of side-effects."
{:added "1.4"
:static true}
[pred coll]
@@ -6642,17 +7093,14 @@
(defn slurp
"Opens a reader on f and reads all its contents, returning a string.
See clojure.java.io/reader for a complete list of supported arguments."
- {:added "1.0"}
+ {:added "1.0"
+ :tag String}
([f & opts]
(let [opts (normalize-slurp-opts opts)
- sb (StringBuilder.)]
+ sw (java.io.StringWriter.)]
(with-open [^java.io.Reader r (apply jio/reader f opts)]
- (loop [c (.read r)]
- (if (neg? c)
- (str sb)
- (do
- (.append sb (char c))
- (recur (.read r)))))))))
+ (jio/copy r sw)
+ (.toString sw)))))
(defn spit
"Opposite of slurp. Opens f with writer, writes content, then
@@ -6755,7 +7203,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; clojure version number ;;;;;;;;;;;;;;;;;;;;;;
-(let [properties (with-open [version-stream (.getResourceAsStream
+(let [^java.util.Properties
+ properties (with-open [version-stream (.getResourceAsStream
(clojure.lang.RT/baseLoader)
"clojure/version.properties")]
(doto (new java.util.Properties)
@@ -6838,7 +7287,7 @@
(defn flatten
"Takes any nested combination of sequential things (lists, vectors,
- etc.) and returns their contents as a single, flat sequence.
+ etc.) and returns their contents as a single, flat lazy sequence.
(flatten nil) returns an empty sequence."
{:added "1.2"
:static true}
@@ -6901,7 +7350,7 @@
(let [fst (first s)
fv (f fst)
run (cons fst (take-while #(= fv (f %)) (next s)))]
- (cons run (partition-by f (seq (drop (count run) s)))))))))
+ (cons run (partition-by f (lazy-seq (drop (count run) s)))))))))
(defn frequencies
"Returns a map from distinct items in coll to the number of times
@@ -6974,6 +7423,50 @@
(let [seg (doall (take n s))]
(cons seg (partition-all n step (nthrest s step))))))))
+(defn splitv-at
+ "Returns a vector of [(into [] (take n) coll) (drop n coll)]"
+ {:added "1.12"}
+ [n coll]
+ [(into [] (take n) coll) (drop n coll)])
+
+(defn partitionv
+ "Returns a lazy sequence of vectors of n items each, at offsets step
+ apart. If step is not supplied, defaults to n, i.e. the partitions
+ do not overlap. If a pad collection is supplied, use its elements as
+ necessary to complete last partition upto n items. In case there are
+ not enough padding elements, return a partition with less than n items."
+ {:added "1.12"}
+ ([n coll]
+ (partitionv n n coll))
+ ([n step coll]
+ (lazy-seq
+ (when-let [s (seq coll)]
+ (let [p (into [] (take n) s)]
+ (when (= n (count p))
+ (cons p (partitionv n step (nthrest s step))))))))
+ ([n step pad coll]
+ (lazy-seq
+ (when-let [s (seq coll)]
+ (let [p (into [] (take n) s)]
+ (if (= n (count p))
+ (cons p (partitionv n step pad (nthrest s step)))
+ (list (into [] (take n) (concat p pad)))))))))
+
+(defn partitionv-all
+ "Returns a lazy sequence of vector partitions, but may include
+ partitions with fewer than n items at the end.
+ Returns a stateful transducer when no collection is provided."
+ {:added "1.12"}
+ ([n]
+ (partition-all n))
+ ([n coll]
+ (partitionv-all n n coll))
+ ([n step coll]
+ (lazy-seq
+ (when-let [s (seq coll)]
+ (let [seg (into [] (take n) coll)]
+ (cons seg (partitionv-all n step (drop step s))))))))
+
(defn shuffle
"Return a random permutation of coll"
{:added "1.2"
@@ -7084,6 +7577,18 @@
(cons x (keepi (inc idx) (rest s)))))))))]
(keepi 0 coll))))
+(defn bounded-count
+ "If coll is counted? returns its count, else will count at most the first n
+ elements of coll using its seq"
+ {:added "1.9"}
+ [n coll]
+ (if (counted? coll)
+ (count coll)
+ (loop [i 0 s (seq coll)]
+ (if (and s (< i n))
+ (recur (inc i) (next s))
+ i))))
+
(defn every-pred
"Takes a set of predicates and returns a function f that returns true if all of its
composing predicates return a logical true value against all of its arguments, else it returns
@@ -7110,8 +7615,8 @@
(fn ep3
([] true)
([x] (boolean (and (p1 x) (p2 x) (p3 x))))
- ([x y] (boolean (and (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y))))
- ([x y z] (boolean (and (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y) (p1 z) (p2 z) (p3 z))))
+ ([x y] (boolean (and (p1 x) (p1 y) (p2 x) (p2 y) (p3 x) (p3 y))))
+ ([x y z] (boolean (and (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z) (p3 x) (p3 y) (p3 z))))
([x y z & args] (boolean (and (ep3 x y z)
(every? #(and (p1 %) (p2 %) (p3 %)) args))))))
([p1 p2 p3 & ps]
@@ -7150,8 +7655,8 @@
(fn sp3
([] nil)
([x] (or (p1 x) (p2 x) (p3 x)))
- ([x y] (or (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y)))
- ([x y z] (or (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y) (p1 z) (p2 z) (p3 z)))
+ ([x y] (or (p1 x) (p1 y) (p2 x) (p2 y) (p3 x) (p3 y)))
+ ([x y z] (or (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z) (p3 x) (p3 y) (p3 z)))
([x y z & args] (or (sp3 x y z)
(some #(or (p1 %) (p2 %) (p3 %)) args)))))
([p1 p2 p3 & ps]
@@ -7235,10 +7740,13 @@
[expr & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
- pstep (fn [[test step]] `(if ~test (-> ~g ~step) ~g))]
+ steps (map (fn [[test step]] `(if ~test (-> ~g ~step) ~g))
+ (partition 2 clauses))]
`(let [~g ~expr
- ~@(interleave (repeat g) (map pstep (partition 2 clauses)))]
- ~g)))
+ ~@(interleave (repeat g) (butlast steps))]
+ ~(if (empty? steps)
+ g
+ (last steps)))))
(defmacro cond->>
"Takes an expression and a set of test/form pairs. Threads expr (via ->>)
@@ -7249,10 +7757,13 @@
[expr & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
- pstep (fn [[test step]] `(if ~test (->> ~g ~step) ~g))]
+ steps (map (fn [[test step]] `(if ~test (->> ~g ~step) ~g))
+ (partition 2 clauses))]
`(let [~g ~expr
- ~@(interleave (repeat g) (map pstep (partition 2 clauses)))]
- ~g)))
+ ~@(interleave (repeat g) (butlast steps))]
+ ~(if (empty? steps)
+ g
+ (last steps)))))
(defmacro as->
"Binds name to expr, evaluates the first form in the lexical context
@@ -7261,8 +7772,10 @@
{:added "1.5"}
[expr name & forms]
`(let [~name ~expr
- ~@(interleave (repeat name) forms)]
- ~name))
+ ~@(interleave (repeat name) (butlast forms))]
+ ~(if (empty? forms)
+ name
+ (last forms))))
(defmacro some->
"When expr is not nil, threads it into the first form (via ->),
@@ -7270,10 +7783,13 @@
{:added "1.5"}
[expr & forms]
(let [g (gensym)
- pstep (fn [step] `(if (nil? ~g) nil (-> ~g ~step)))]
+ steps (map (fn [step] `(if (nil? ~g) nil (-> ~g ~step)))
+ forms)]
`(let [~g ~expr
- ~@(interleave (repeat g) (map pstep forms))]
- ~g)))
+ ~@(interleave (repeat g) (butlast steps))]
+ ~(if (empty? steps)
+ g
+ (last steps)))))
(defmacro some->>
"When expr is not nil, threads it into the first form (via ->>),
@@ -7281,10 +7797,13 @@
{:added "1.5"}
[expr & forms]
(let [g (gensym)
- pstep (fn [step] `(if (nil? ~g) nil (->> ~g ~step)))]
+ steps (map (fn [step] `(if (nil? ~g) nil (->> ~g ~step)))
+ forms)]
`(let [~g ~expr
- ~@(interleave (repeat g) (map pstep forms))]
- ~g)))
+ ~@(interleave (repeat g) (butlast steps))]
+ ~(if (empty? steps)
+ g
+ (last steps)))))
(defn ^:private preserving-reduced
[rf]
@@ -7305,6 +7824,30 @@
([result input]
(reduce rrf result input)))))
+(defn halt-when
+ "Returns a transducer that ends transduction when pred returns true
+ for an input. When retf is supplied it must be a fn of 2 arguments -
+ it will be passed the (completed) result so far and the input that
+ triggered the predicate, and its return value (if it does not throw
+ an exception) will be the return value of the transducer. If retf
+ is not supplied, the input that triggered the predicate will be
+ returned. If the predicate never returns true the transduction is
+ unaffected."
+ {:added "1.9"}
+ ([pred] (halt-when pred nil))
+ ([pred retf]
+ (fn [rf]
+ (fn
+ ([] (rf))
+ ([result]
+ (if (and (map? result) (contains? result ::halt))
+ (::halt result)
+ (rf result)))
+ ([result input]
+ (if (pred input)
+ (reduced {::halt (if retf (retf (rf result) input) input)})
+ (rf result input)))))))
+
(defn dedupe
"Returns a lazy sequence removing consecutive duplicates in coll.
Returns a transducer when no collection is provided."
@@ -7365,8 +7908,55 @@
effects, on successive items in the collection. Returns nil"
{:added "1.7"}
[proc coll]
- (reduce #(proc %2) nil coll))
+ (reduce #(proc %2) nil coll)
+ nil)
+(defn iteration
+ "Creates a seqable/reducible via repeated calls to step,
+ a function of some (continuation token) 'k'. The first call to step
+ will be passed initk, returning 'ret'. Iff (somef ret) is true,
+ (vf ret) will be included in the iteration, else iteration will
+ terminate and vf/kf will not be called. If (kf ret) is non-nil it
+ will be passed to the next step call, else iteration will terminate.
+
+ This can be used e.g. to consume APIs that return paginated or batched data.
+
+ step - (possibly impure) fn of 'k' -> 'ret'
+
+ :somef - fn of 'ret' -> logical true/false, default 'some?'
+ :vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity'
+ :kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity'
+ :initk - the first value passed to step, default 'nil'
+
+ It is presumed that step with non-initk is unreproducible/non-idempotent.
+ If step with initk is unreproducible it is on the consumer to not consume twice."
+ {:added "1.11"}
+ [step & {:keys [somef vf kf initk]
+ :or {vf identity
+ kf identity
+ somef some?
+ initk nil}}]
+ (reify
+ clojure.lang.Seqable
+ (seq [_]
+ ((fn next [ret]
+ (when (somef ret)
+ (cons (vf ret)
+ (when-some [k (kf ret)]
+ (lazy-seq (next (step k)))))))
+ (step initk)))
+ clojure.lang.IReduceInit
+ (reduce [_ rf init]
+ (loop [acc init
+ ret (step initk)]
+ (if (somef ret)
+ (let [acc (rf acc (vf ret))]
+ (if (reduced? acc)
+ @acc
+ (if-some [k (kf ret)]
+ (recur acc (step k))
+ acc)))
+ acc)))))
(defn tagged-literal?
"Return true if the value is the data representation of a tagged literal"
@@ -7402,15 +7992,17 @@
(def ^{:added "1.4"} default-data-readers
"Default map of data reader functions provided by Clojure. May be
overridden by binding *data-readers*."
- {'inst #'clojure.instant/read-instant-date
- 'uuid #'clojure.uuid/default-uuid-reader})
+ (merge
+ {'uuid #'clojure.uuid/default-uuid-reader}
+ (when-class "java.sql.Timestamp"
+ {'inst #'clojure.instant/read-instant-date})))
(def ^{:added "1.4" :dynamic true} *data-readers*
"Map from reader tag symbols to data reader Vars.
When Clojure starts, it searches for files named 'data_readers.clj'
- at the root of the classpath. Each such file must contain a literal
- map of symbols, like this:
+ and 'data_readers.cljc' at the root of the classpath. Each such file
+ must contain a literal map of symbols, like this:
{foo/bar my.project.foo/bar
foo/baz my.project/baz}
@@ -7431,7 +8023,7 @@
Reader tags without namespace qualifiers are reserved for
Clojure. Default reader tags are defined in
clojure.core/default-data-readers but may be overridden in
- data_readers.clj or by rebinding this Var."
+ data_readers.clj, data_readers.cljc, or by rebinding this Var."
{})
(def ^{:added "1.5" :dynamic true} *default-data-reader-fn*
@@ -7442,9 +8034,10 @@
nil)
(defn- data-reader-urls []
- (enumeration-seq
- (.. Thread currentThread getContextClassLoader
- (getResources "data_readers.clj"))))
+ (let [cl (.. Thread currentThread getContextClassLoader)]
+ (concat
+ (enumeration-seq (.getResources cl "data_readers.clj"))
+ (enumeration-seq (.getResources cl "data_readers.cljc")))))
(defn- data-reader-var [sym]
(intern (create-ns (symbol (namespace sym)))
@@ -7455,7 +8048,10 @@
(java.io.InputStreamReader.
(.openStream url) "UTF-8"))]
(binding [*file* (.getFile url)]
- (let [new-mappings (read rdr false nil)]
+ (let [read-opts (if (.endsWith (.getPath url) "cljc")
+ {:eof nil :read-cond :allow}
+ {:eof nil})
+ new-mappings (read read-opts rdr)]
(when (not (map? new-mappings))
(throw (ex-info (str "Not a valid data-reader map")
{:url url})))
@@ -7487,3 +8083,151 @@
(catch Throwable t
(.printStackTrace t)
(throw t)))
+
+(defn uri?
+ "Return true if x is a java.net.URI"
+ {:added "1.9"}
+ [x] (instance? java.net.URI x))
+
+(defonce ^:private tapset (atom #{}))
+(defonce ^:private ^java.util.concurrent.ArrayBlockingQueue tapq (java.util.concurrent.ArrayBlockingQueue. 1024))
+
+(defonce ^:private tap-loop
+ (delay
+ (doto (Thread.
+ #(let [t (.take tapq)
+ x (if (identical? ::tap-nil t) nil t)
+ taps @tapset]
+ (doseq [tap taps]
+ (try
+ (tap x)
+ (catch Throwable ex)))
+ (recur))
+ "clojure.core/tap-loop")
+ (.setDaemon true)
+ (.start))))
+
+(defn add-tap
+ "adds f, a fn of one argument, to the tap set. This function will be called with anything sent via tap>.
+ This function may (briefly) block (e.g. for streams), and will never impede calls to tap>,
+ but blocking indefinitely may cause tap values to be dropped.
+ Remember f in order to remove-tap"
+ {:added "1.10"}
+ [f]
+ (force tap-loop)
+ (swap! tapset conj f)
+ nil)
+
+(defn remove-tap
+ "Remove f from the tap set."
+ {:added "1.10"}
+ [f]
+ (swap! tapset disj f)
+ nil)
+
+(defn tap>
+ "sends x to any taps. Will not block. Returns true if there was room in the queue,
+ false if not (dropped)."
+ {:added "1.10"}
+ [x]
+ (force tap-loop)
+ (.offer tapq (if (nil? x) ::tap-nil x)))
+
+(defn update-vals
+ "m f => {k (f v) ...}
+
+ Given a map m and a function f of 1-argument, returns a new map where the keys of m
+ are mapped to result of applying f to the corresponding values of m."
+ {:added "1.11"}
+ [m f]
+ (with-meta
+ (persistent!
+ (reduce-kv (fn [acc k v] (assoc! acc k (f v)))
+ (if (instance? clojure.lang.IEditableCollection m)
+ (transient m)
+ (transient {}))
+ m))
+ (meta m)))
+
+(defn update-keys
+ "m f => {(f k) v ...}
+
+ Given a map m and a function f of 1-argument, returns a new map whose
+ keys are the result of applying f to the keys of m, mapped to the
+ corresponding values of m.
+ f must return a unique key for each key of m, else the behavior is undefined."
+ {:added "1.11"}
+ [m f]
+ (let [ret (persistent!
+ (reduce-kv (fn [acc k v] (assoc! acc (f k) v))
+ (transient {})
+ m))]
+ (with-meta ret (meta m))))
+
+(defn- parsing-err
+ "Construct message for parsing for non-string parsing error"
+ ^String [val]
+ (str "Expected string, got " (if (nil? val) "nil" (-> val class .getName))))
+
+(defn parse-long
+ {:doc "Parse string of decimal digits with optional leading -/+ and return a
+ Long value, or nil if parse fails"
+ :added "1.11"}
+ ^Long [^String s]
+ (if (string? s)
+ (try
+ (Long/valueOf s)
+ (catch NumberFormatException _ nil))
+ (throw (IllegalArgumentException. (parsing-err s)))))
+
+(defn parse-double
+ {:doc "Parse string with floating point components and return a Double value,
+ or nil if parse fails.
+
+ Grammar: https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang.String-"
+ :added "1.11"}
+ ^Double [^String s]
+ (if (string? s)
+ (try
+ (Double/valueOf s)
+ (catch NumberFormatException _ nil))
+ (throw (IllegalArgumentException. (parsing-err s)))))
+
+(defn parse-uuid
+ {:doc "Parse a string representing a UUID and return a java.util.UUID instance,
+ or nil if parse fails.
+
+ Grammar: https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#toString--"
+ :added "1.11"}
+ ^java.util.UUID [^String s]
+ (try
+ (java.util.UUID/fromString s)
+ (catch IllegalArgumentException _ nil)))
+
+(defn parse-boolean
+ {:doc "Parse strings \"true\" or \"false\" and return a boolean, or nil if invalid"
+ :added "1.11"}
+ [^String s]
+ (if (string? s)
+ (case s
+ "true" true
+ "false" false
+ nil)
+ (throw (IllegalArgumentException. (parsing-err s)))))
+
+(defn NaN?
+ {:doc "Returns true if num is NaN, else false"
+ :inline-arities #{1}
+ :inline (fn [num] `(Double/isNaN ~num))
+ :added "1.11"}
+
+ [^double num]
+ (Double/isNaN num))
+
+(defn infinite?
+ {:doc "Returns true if num is negative or positive infinity, else false"
+ :inline-arities #{1}
+ :inline (fn [num] `(Double/isInfinite ~num))
+ :added "1.11"}
+ [^double num]
+ (Double/isInfinite num))
diff --git a/src/clj/clojure/core/protocols.clj b/src/clj/clojure/core/protocols.clj
index bc83a78e64..2ed43152b6 100644
--- a/src/clj/clojure/core/protocols.clj
+++ b/src/clj/clojure/core/protocols.clj
@@ -30,27 +30,46 @@
(let [s (seq coll)]
(internal-reduce s f val))))
-(defn- iter-reduce
- ([^java.lang.Iterable coll f]
- (let [iter (.iterator coll)]
+;; mutates the iterator, respects reduced
+(defn iterator-reduce!
+ ([^java.util.Iterator iter f]
+ (if (.hasNext iter)
+ (iterator-reduce! iter f (.next iter))
+ (f)))
+ ([^java.util.Iterator iter f val]
+ (loop [ret val]
(if (.hasNext iter)
- (loop [ret (.next iter)]
- (if (.hasNext iter)
- (let [ret (f ret (.next iter))]
- (if (reduced? ret)
- @ret
- (recur ret)))
- ret))
- (f))))
- ([^java.lang.Iterable coll f val]
- (let [iter (.iterator coll)]
- (loop [ret val]
- (if (.hasNext iter)
- (let [ret (f ret (.next iter))]
- (if (reduced? ret)
- @ret
- (recur ret)))
- ret)))))
+ (let [ret (f ret (.next iter))]
+ (if (reduced? ret)
+ @ret
+ (recur ret)))
+ ret))))
+
+(defn- iter-reduce
+ ([^Iterable coll f]
+ (iterator-reduce! (.iterator coll) f))
+ ([^Iterable coll f val]
+ (iterator-reduce! (.iterator coll) f val)))
+
+(defn- naive-seq-reduce
+ "Reduces a seq, ignoring any opportunities to switch to a more
+ specialized implementation."
+ [s f val]
+ (loop [s (seq s)
+ val val]
+ (if s
+ (let [ret (f val (first s))]
+ (if (reduced? ret)
+ @ret
+ (recur (next s) ret)))
+ val)))
+
+(defn- interface-or-naive-reduce
+ "Reduces via IReduceInit if possible, else naively."
+ [coll f val]
+ (if (instance? clojure.lang.IReduceInit coll)
+ (.reduce ^clojure.lang.IReduceInit coll f val)
+ (naive-seq-reduce coll f val)))
(extend-protocol CollReduce
nil
@@ -119,16 +138,17 @@
(recur (chunk-next s)
f
ret)))
- (coll-reduce s f val))
+ (interface-or-naive-reduce s f val))
val))
clojure.lang.StringSeq
(internal-reduce
[str-seq f val]
- (let [s (.s str-seq)]
+ (let [s (.s str-seq)
+ len (.length s)]
(loop [i (.i str-seq)
val val]
- (if (< i (.length s))
+ (if (< i len)
(let [ret (f val (.charAt s i))]
(if (reduced? ret)
@ret
@@ -143,13 +163,12 @@
f f
val val]
(if-let [s (seq s)]
- ;; roll over to faster implementation if underlying seq changes type
(if (identical? (class s) cls)
(let [ret (f val (first s))]
(if (reduced? ret)
@ret
(recur cls (next s) f ret)))
- (coll-reduce s f val))
+ (interface-or-naive-reduce s f val))
val))))
(defprotocol IKVReduce
@@ -158,3 +177,25 @@
entries. Called by clojure.core/reduce-kv, and has same
semantics (just different arg order)."
(kv-reduce [amap f init]))
+
+(defprotocol Datafiable
+ :extend-via-metadata true
+
+ (datafy [o] "return a representation of o as data (default identity)"))
+
+(extend-protocol Datafiable
+ nil
+ (datafy [_] nil)
+
+ Object
+ (datafy [x] x))
+
+(defprotocol Navigable
+ :extend-via-metadata true
+
+ (nav [coll k v] "return (possibly transformed) v in the context of coll and k (a key/index or nil),
+defaults to returning v."))
+
+(extend-protocol Navigable
+ Object
+ (nav [_ _ x] x))
diff --git a/src/clj/clojure/core/reducers.clj b/src/clj/clojure/core/reducers.clj
index c0fcf0ba13..94b1a7dd6b 100644
--- a/src/clj/clojure/core/reducers.clj
+++ b/src/clj/clojure/core/reducers.clj
@@ -8,9 +8,7 @@
(ns ^{:doc
"A library for reduction and parallel folding. Alpha and subject
- to change. Note that fold and its derivatives require Java 7+ or
- Java 6 + jsr166y.jar for fork/join support. See Clojure's pom.xml for the
- dependency info."
+ to change."
:author "Rich Hickey"}
clojure.core.reducers
(:refer-clojure :exclude [reduce map mapcat filter remove take take-while drop flatten cat])
@@ -21,51 +19,20 @@
;;;;;;;;;;;;;; some fj stuff ;;;;;;;;;;
-(defmacro ^:private compile-if
- "Evaluate `exp` and if it returns logical true and doesn't error, expand to
- `then`. Else expand to `else`.
+(def pool (delay (java.util.concurrent.ForkJoinPool.)))
- (compile-if (Class/forName \"java.util.concurrent.ForkJoinTask\")
- (do-cool-stuff-with-fork-join)
- (fall-back-to-executor-services))"
- [exp then else]
- (if (try (eval exp)
- (catch Throwable _ false))
- `(do ~then)
- `(do ~else)))
+(defn fjtask [^Callable f]
+ (java.util.concurrent.ForkJoinTask/adapt f))
-(compile-if
- (Class/forName "java.util.concurrent.ForkJoinTask")
- ;; We're running a JDK 7+
- (do
- (def pool (delay (java.util.concurrent.ForkJoinPool.)))
+(defn- fjinvoke [f]
+ (if (java.util.concurrent.ForkJoinTask/inForkJoinPool)
+ (f)
+ (.invoke ^java.util.concurrent.ForkJoinPool @pool ^java.util.concurrent.ForkJoinTask (fjtask f))))
- (defn fjtask [^Callable f]
- (java.util.concurrent.ForkJoinTask/adapt f))
+(defn- fjfork [task] (.fork ^java.util.concurrent.ForkJoinTask task))
- (defn- fjinvoke [f]
- (if (java.util.concurrent.ForkJoinTask/inForkJoinPool)
- (f)
- (.invoke ^java.util.concurrent.ForkJoinPool @pool ^java.util.concurrent.ForkJoinTask (fjtask f))))
+(defn- fjjoin [task] (.join ^java.util.concurrent.ForkJoinTask task))
- (defn- fjfork [task] (.fork ^java.util.concurrent.ForkJoinTask task))
-
- (defn- fjjoin [task] (.join ^java.util.concurrent.ForkJoinTask task)))
- ;; We're running a JDK <7
- (do
- (def pool (delay (jsr166y.ForkJoinPool.)))
-
- (defn fjtask [^Callable f]
- (jsr166y.ForkJoinTask/adapt f))
-
- (defn- fjinvoke [f]
- (if (jsr166y.ForkJoinTask/inForkJoinPool)
- (f)
- (.invoke ^jsr166y.ForkJoinPool @pool ^jsr166y.ForkJoinTask (fjtask f))))
-
- (defn- fjfork [task] (.fork ^jsr166y.ForkJoinTask task))
-
- (defn- fjjoin [task] (.join ^jsr166y.ForkJoinTask task))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn reduce
diff --git a/src/clj/clojure/core/server.clj b/src/clj/clojure/core/server.clj
new file mode 100644
index 0000000000..8f7013fdf0
--- /dev/null
+++ b/src/clj/clojure/core/server.clj
@@ -0,0 +1,341 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns ^{:doc "Socket server support"
+ :author "Alex Miller"}
+ clojure.core.server
+ (:require [clojure.string :as str]
+ [clojure.edn :as edn]
+ [clojure.main :as m])
+ (:import
+ [clojure.lang LineNumberingPushbackReader]
+ [java.net InetAddress Socket ServerSocket SocketException]
+ [java.io Reader Writer PrintWriter BufferedWriter BufferedReader InputStreamReader OutputStreamWriter]
+ [java.util Properties]
+ [java.util.concurrent.locks ReentrantLock]))
+
+(set! *warn-on-reflection* true)
+
+(def ^:dynamic *session* nil)
+
+;; lock protects servers
+(defonce ^:private lock (ReentrantLock.))
+(defonce ^:private servers {})
+
+(defmacro ^:private with-lock
+ [lock-expr & body]
+ `(let [lockee# ~(with-meta lock-expr {:tag 'java.util.concurrent.locks.ReentrantLock})]
+ (.lock lockee#)
+ (try
+ ~@body
+ (finally
+ (.unlock lockee#)))))
+
+(defmacro ^:private thread
+ [^String name daemon & body]
+ `(doto (Thread. (fn [] ~@body) ~name)
+ (.setDaemon ~daemon)
+ (.start)))
+
+(defn- required
+ "Throw if opts does not contain prop."
+ [opts prop]
+ (when (nil? (get opts prop))
+ (throw (ex-info (str "Missing required socket server property " prop) opts))))
+
+(defn- validate-opts
+ "Validate server config options"
+ [{:keys [name port accept] :as opts}]
+ (doseq [prop [:name :port :accept]] (required opts prop))
+ (when (or (not (integer? port)) (not (<= 0 port 65535)))
+ (throw (ex-info (str "Invalid socket server port: " port) opts))))
+
+(defn- accept-connection
+ "Start accept function, to be invoked on a client thread, given:
+ conn - client socket
+ name - server name
+ client-id - client identifier
+ in - in stream
+ out - out stream
+ err - err stream
+ accept - accept fn symbol to invoke
+ args - to pass to accept-fn"
+ [^Socket conn name client-id in out err accept args]
+ (try
+ (binding [*in* in
+ *out* out
+ *err* err
+ *session* {:server name :client client-id}]
+ (with-lock lock
+ (alter-var-root #'servers assoc-in [name :sessions client-id] {}))
+ (require (symbol (namespace accept)))
+ (let [accept-fn (resolve accept)]
+ (apply accept-fn args)))
+ (catch SocketException _disconnect)
+ (finally
+ (with-lock lock
+ (alter-var-root #'servers update-in [name :sessions] dissoc client-id))
+ (.close conn))))
+
+(defn start-server
+ "Start a socket server given the specified opts:
+ :address Host or address, string, defaults to loopback address
+ :port Port, integer, required
+ :name Name, required
+ :accept Namespaced symbol of the accept function to invoke, required
+ :args Vector of args to pass to accept function
+ :bind-err Bind *err* to socket out stream?, defaults to true
+ :server-daemon Is server thread a daemon?, defaults to true
+ :client-daemon Are client threads daemons?, defaults to true
+ Returns server socket."
+ [opts]
+ (validate-opts opts)
+ (let [{:keys [address port name accept args bind-err server-daemon client-daemon]
+ :or {bind-err true
+ server-daemon true
+ client-daemon true}} opts
+ address (InetAddress/getByName address) ;; nil returns loopback
+ socket (ServerSocket. port 0 address)]
+ (with-lock lock
+ (alter-var-root #'servers assoc name {:name name, :socket socket, :sessions {}}))
+ (thread
+ (str "Clojure Server " name) server-daemon
+ (try
+ (loop [client-counter 1]
+ (when (not (.isClosed socket))
+ (try
+ (let [conn (.accept socket)
+ in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream conn)))
+ out (BufferedWriter. (OutputStreamWriter. (.getOutputStream conn)))
+ client-id (str client-counter)]
+ (thread
+ (str "Clojure Connection " name " " client-id) client-daemon
+ (accept-connection conn name client-id in out (if bind-err out *err*) accept args)))
+ (catch SocketException _disconnect))
+ (recur (inc client-counter))))
+ (finally
+ (with-lock lock
+ (alter-var-root #'servers dissoc name)))))
+ socket))
+
+(defn stop-server
+ "Stop server with name or use the server-name from *session* if none supplied.
+ Returns true if server stopped successfully, nil if not found, or throws if
+ there is an error closing the socket."
+ ([]
+ (stop-server (:server *session*)))
+ ([name]
+ (with-lock lock
+ (let [server-socket ^ServerSocket (get-in servers [name :socket])]
+ (when server-socket
+ (alter-var-root #'servers dissoc name)
+ (.close server-socket)
+ true)))))
+
+(defn stop-servers
+ "Stop all servers ignores all errors, and returns nil."
+ []
+ (with-lock lock
+ (doseq [name (keys servers)]
+ (future (stop-server name)))))
+
+(defn- parse-props
+ "Parse clojure.server.* from properties to produce a map of server configs."
+ [^Properties props]
+ (reduce
+ (fn [acc ^String k]
+ (let [[k1 k2 k3] (str/split k #"\.")]
+ (if (and (= k1 "clojure") (= k2 "server"))
+ (let [v (get props k)]
+ (conj acc (merge {:name k3} (edn/read-string v))))
+ acc)))
+ []
+ (.stringPropertyNames props)))
+
+(defn start-servers
+ "Start all servers specified in the system properties."
+ [system-props]
+ (doseq [server (parse-props system-props)]
+ (start-server server)))
+
+(defn repl-init
+ "Initialize repl in user namespace and make standard repl requires."
+ []
+ (in-ns 'user)
+ (apply require clojure.main/repl-requires))
+
+(defn repl-read
+ "Enhanced :read hook for repl supporting :repl/quit."
+ [request-prompt request-exit]
+ (or ({:line-start request-prompt :stream-end request-exit}
+ (m/skip-whitespace *in*))
+ (let [input (read {:read-cond :allow} *in*)]
+ (m/skip-if-eol *in*)
+ (case input
+ :repl/quit request-exit
+ input))))
+
+(defn repl
+ "REPL with predefined hooks for attachable socket server."
+ []
+ (m/repl
+ :init repl-init
+ :read repl-read))
+
+(defn- ex->data
+ [ex phase]
+ (assoc (Throwable->map ex) :phase phase))
+
+(defn prepl
+ "a REPL with structured output (for programs)
+ reads forms to eval from in-reader (a LineNumberingPushbackReader)
+ Closing the input or passing the form :repl/quit will cause it to return
+
+ Calls out-fn with data, one of:
+ {:tag :ret
+ :val val ;;eval result, or Throwable->map data if exception thrown
+ :ns ns-name-string
+ :ms long ;;eval time in milliseconds
+ :form string ;;iff successfully read
+ :exception true ;;iff exception thrown
+ }
+ {:tag :out
+ :val string} ;chars from during-eval *out*
+ {:tag :err
+ :val string} ;chars from during-eval *err*
+ {:tag :tap
+ :val val} ;values from tap>
+
+ You might get more than one :out or :err per eval, but exactly one :ret
+ tap output can happen at any time (i.e. between evals)
+ If during eval an attempt is made to read *in* it will read from in-reader unless :stdin is supplied
+
+ Alpha, subject to change."
+ {:added "1.10"}
+ [in-reader out-fn & {:keys [stdin]}]
+ (let [EOF (Object.)
+ tapfn #(out-fn {:tag :tap :val %1})]
+ (m/with-bindings
+ (in-ns 'user)
+ (binding [*in* (or stdin in-reader)
+ *out* (PrintWriter-on #(out-fn {:tag :out :val %1}) nil true)
+ *err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil true)]
+ (try
+ (add-tap tapfn)
+ (loop []
+ (when (try
+ (let [[form s] (read+string {:eof EOF :read-cond :allow} in-reader)]
+ (try
+ (when-not (identical? form EOF)
+ (let [start (System/nanoTime)
+ ret (eval form)
+ ms (quot (- (System/nanoTime) start) 1000000)]
+ (when-not (= :repl/quit ret)
+ (set! *3 *2)
+ (set! *2 *1)
+ (set! *1 ret)
+ (out-fn {:tag :ret
+ :val (if (instance? Throwable ret)
+ (Throwable->map ret)
+ ret)
+ :ns (str (.name *ns*))
+ :ms ms
+ :form s})
+ true)))
+ (catch Throwable ex
+ (set! *e ex)
+ (out-fn {:tag :ret :val (ex->data ex (or (-> ex ex-data :clojure.error/phase) :execution))
+ :ns (str (.name *ns*)) :form s
+ :exception true})
+ true)))
+ (catch Throwable ex
+ (set! *e ex)
+ (out-fn {:tag :ret :val (ex->data ex :read-source)
+ :ns (str (.name *ns*))
+ :exception true})
+ true))
+ (recur)))
+ (finally
+ (remove-tap tapfn)))))))
+
+(defn- resolve-fn [valf]
+ (if (symbol? valf)
+ (or (resolve valf)
+ (when-let [nsname (namespace valf)]
+ (require (symbol nsname))
+ (resolve valf))
+ (throw (Exception. (str "can't resolve: " valf))))
+ valf))
+
+(defn io-prepl
+ "prepl bound to *in* and *out*, suitable for use with e.g. server/repl (socket-repl).
+ :ret and :tap vals will be processed by valf, a fn of one argument
+ or a symbol naming same (default pr-str)
+
+ Alpha, subject to change."
+ {:added "1.10"}
+ [& {:keys [valf] :or {valf pr-str}}]
+ (let [valf (resolve-fn valf)
+ out *out*
+ lock (Object.)]
+ (prepl *in*
+ (fn [m]
+ (binding [*out* out, *flush-on-newline* true, *print-readably* true]
+ (locking lock
+ (prn (if (#{:ret :tap} (:tag m))
+ (try
+ (assoc m :val (valf (:val m)))
+ (catch Throwable ex
+ (assoc m :val (valf (ex->data ex :print-eval-result))
+ :exception true)))
+ m))))))))
+
+(defn remote-prepl
+ "Implements a prepl on in-reader and out-fn by forwarding to a
+ remote [io-]prepl over a socket. Messages will be read by readf, a
+ fn of a LineNumberingPushbackReader and EOF value or a symbol naming
+ same (default #(read %1 false %2)),
+ :ret and :tap vals will be processed by valf, a fn of one argument
+ or a symbol naming same (default read-string). If that function
+ throws, :val will be unprocessed.
+
+ Alpha, subject to change."
+ {:added "1.10"}
+ [^String host port ^Reader
+ in-reader out-fn & {:keys [valf readf] :or {valf read-string, readf #(read %1 false %2)}}]
+ (let [valf (resolve-fn valf)
+ readf (resolve-fn readf)
+ ^long port (if (string? port) (Integer/valueOf ^String port) port)
+ socket (Socket. host port)
+ rd (-> socket .getInputStream InputStreamReader. BufferedReader. LineNumberingPushbackReader.)
+ wr (-> socket .getOutputStream OutputStreamWriter.)
+ EOF (Object.)]
+ (thread "clojure.core.server/remote-prepl" true
+ (try (loop []
+ (let [{:keys [tag val] :as m} (readf rd EOF)]
+ (when-not (identical? m EOF)
+ (out-fn
+ (if (#{:ret :tap} tag)
+ (try
+ (assoc m :val (valf val))
+ (catch Throwable ex
+ (assoc m :val (ex->data ex :read-eval-result)
+ :exception true)))
+ m))
+ (recur))))
+ (finally
+ (.close wr))))
+ (let [buf (char-array 1024)]
+ (try (loop []
+ (let [n (.read in-reader buf)]
+ (when-not (= n -1)
+ (.write wr buf 0 n)
+ (.flush wr)
+ (recur))))
+ (finally
+ (.close rd))))))
diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj
index 142371fb50..66eeb94d66 100644
--- a/src/clj/clojure/core_deftype.clj
+++ b/src/clj/clojure/core_deftype.clj
@@ -63,12 +63,13 @@
methods (map (fn [[name params & body]]
(cons name (maybe-destructured params body)))
(apply concat (vals impls)))]
- (when-let [bad-opts (seq (remove #{:no-print} (keys opts)))]
+ (when-let [bad-opts (seq (remove #{:no-print :load-ns} (keys opts)))]
(throw (IllegalArgumentException. (apply print-str "Unsupported option(s) -" bad-opts))))
[interfaces methods opts]))
-(defmacro reify
- "reify is a macro with the following structure:
+(defmacro reify
+ "reify creates an object implementing a protocol or interface.
+ reify is a macro with the following structure:
(reify options* specs*)
@@ -100,18 +101,19 @@
same arity in an interface you must specify complete hints to
disambiguate - a missing hint implies Object.
- recur works to method heads The method bodies of reify are lexical
- closures, and can refer to the surrounding local scope:
+ Method heads are recursion points for recur, as in a fn. The method
+ bodies of reify are lexical closures, and can refer to the surrounding
+ local scope:
(str (let [f \"foo\"]
- (reify Object
- (toString [this] f))))
+ (reify Object
+ (toString [this] f))))
== \"foo\"
(seq (let [f \"foo\"]
- (reify clojure.lang.Seqable
- (seq [this] (seq f)))))
- == (\\f \\o \\o))
+ (reify clojure.lang.Seqable
+ (seq [this] (seq f)))))
+ == (\\f \\o \\o)
reify always implements clojure.lang.IObj and transfers meta
data of the form to the created object.
@@ -132,7 +134,7 @@
(defn- imap-cons
[^IPersistentMap this o]
(cond
- (instance? java.util.Map$Entry o)
+ (map-entry? o)
(let [^java.util.Map$Entry pair o]
(.assoc this (.getKey pair) (.getValue pair)))
(instance? clojure.lang.IPersistentVector o)
@@ -148,15 +150,17 @@
(defn- emit-defrecord
"Do not use this directly - use defrecord"
{:added "1.2"}
- [tagname name fields interfaces methods]
- (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." name)) (meta name))
+ [tagname cname fields interfaces methods opts]
+ (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." cname)) (meta cname))
interfaces (vec interfaces)
interface-set (set (map resolve interfaces))
methodname-set (set (map first methods))
hinted-fields fields
fields (vec (map #(with-meta % nil) fields))
base-fields fields
- fields (conj fields '__meta '__extmap)
+ fields (conj fields '__meta '__extmap
+ '^:unsynchronized-mutable __hash
+ '^:unsynchronized-mutable __hasheq)
type-hash (hash classname)]
(when (some #{:volatile-mutable :unsynchronized-mutable} (mapcat (comp keys meta) hinted-fields))
(throw (IllegalArgumentException. ":volatile-mutable or :unsynchronized-mutable not supported for record fields")))
@@ -168,8 +172,18 @@
(eqhash [[i m]]
[(conj i 'clojure.lang.IHashEq)
(conj m
- `(hasheq [this#] (bit-xor ~type-hash (clojure.lang.APersistentMap/mapHasheq this#)))
- `(hashCode [this#] (clojure.lang.APersistentMap/mapHash this#))
+ `(hasheq [this#] (let [hq# ~'__hasheq]
+ (if (zero? hq#)
+ (let [h# (int (bit-xor ~type-hash (clojure.lang.APersistentMap/mapHasheq this#)))]
+ (set! ~'__hasheq h#)
+ h#)
+ hq#)))
+ `(hashCode [this#] (let [hash# ~'__hash]
+ (if (zero? hash#)
+ (let [h# (clojure.lang.APersistentMap/mapHash this#)]
+ (set! ~'__hash h#)
+ h#)
+ hash#)))
`(equals [this# ~gs] (clojure.lang.APersistentMap/mapEquals this# ~gs)))])
(iobj [[i m]]
[(conj i 'clojure.lang.IObj)
@@ -212,20 +226,20 @@
`(containsKey [this# k#] (not (identical? this# (.valAt this# k# this#))))
`(entryAt [this# k#] (let [v# (.valAt this# k# this#)]
(when-not (identical? this# v#)
- (clojure.lang.MapEntry. k# v#))))
- `(seq [this#] (seq (concat [~@(map #(list `new `clojure.lang.MapEntry (keyword %) %) base-fields)]
+ (clojure.lang.MapEntry/create k# v#))))
+ `(seq [this#] (seq (concat [~@(map #(list `clojure.lang.MapEntry/create (keyword %) %) base-fields)]
~'__extmap)))
`(iterator [~gs]
(clojure.lang.RecordIterator. ~gs [~@(map keyword base-fields)] (RT/iter ~'__extmap)))
`(assoc [this# k# ~gs]
(condp identical? k#
~@(mapcat (fn [fld]
- [(keyword fld) (list* `new tagname (replace {fld gs} fields))])
+ [(keyword fld) (list* `new tagname (replace {fld gs} (remove '#{__hash __hasheq} fields)))])
base-fields)
- (new ~tagname ~@(remove #{'__extmap} fields) (assoc ~'__extmap k# ~gs))))
+ (new ~tagname ~@(remove '#{__extmap __hash __hasheq} fields) (assoc ~'__extmap k# ~gs))))
`(without [this# k#] (if (contains? #{~@(map keyword base-fields)} k#)
(dissoc (with-meta (into {} this#) ~'__meta) k#)
- (new ~tagname ~@(remove #{'__extmap} fields)
+ (new ~tagname ~@(remove '#{__extmap __hash __hasheq} fields)
(not-empty (dissoc ~'__extmap k#))))))])
(ijavamap [[i m]]
[(conj i 'java.util.Map 'java.io.Serializable)
@@ -243,8 +257,12 @@
`(entrySet [this#] (set this#)))])
]
(let [[i m] (-> [interfaces methods] irecord eqhash iobj ilookup imap ijavamap)]
- `(deftype* ~tagname ~classname ~(conj hinted-fields '__meta '__extmap)
- :implements ~(vec i)
+ `(deftype* ~(symbol (name (ns-name *ns*)) (name tagname)) ~classname
+ ~(conj hinted-fields '__meta '__extmap
+ '^int ^:unsynchronized-mutable __hash
+ '^int ^:unsynchronized-mutable __hasheq)
+ :implements ~(vec i)
+ ~@(mapcat identity opts)
~@m))))))
(defn- build-positional-factory
@@ -279,7 +297,7 @@
[fields name]
(when-not (vector? fields)
(throw (AssertionError. "No fields vector given.")))
- (let [specials #{'__meta '__extmap}]
+ (let [specials '#{__meta __hash __hasheq __extmap}]
(when (some specials fields)
(throw (AssertionError. (str "The names in " specials " cannot be used as field names for types or records.")))))
(let [non-syms (remove symbol? fields)]
@@ -295,8 +313,13 @@
(defmacro defrecord
"(defrecord name [fields*] options* specs*)
-
- Currently there are no options.
+
+ Options are expressed as sequential keywords and arguments (in any order).
+
+ Supported options:
+ :load-ns - if true, importing the record class will cause the
+ namespace in which the record was defined to be loaded.
+ Defaults to false.
Each spec consists of a protocol or interface name followed by zero
or more method bodies:
@@ -351,9 +374,9 @@
Two constructors will be defined, one taking the designated fields
followed by a metadata map (nil for none) and an extension field
map (nil for none), and one taking only the fields (using nil for
- meta and extension fields). Note that the field names __meta
- and __extmap are currently reserved and should not be used when
- defining your own records.
+ meta and extension fields). Note that the field names __meta,
+ __extmap, __hash and __hasheq are currently reserved and should not
+ be used when defining your own records.
Given (defrecord TypeName ...), two factory functions will be
defined: ->TypeName, taking positional parameters for the fields,
@@ -372,7 +395,7 @@
`(let []
(declare ~(symbol (str '-> gname)))
(declare ~(symbol (str 'map-> gname)))
- ~(emit-defrecord name gname (vec hinted-fields) (vec interfaces) methods)
+ ~(emit-defrecord name gname (vec hinted-fields) (vec interfaces) methods opts)
(import ~classname)
~(build-positional-factory gname classname fields)
(defn ~(symbol (str 'map-> gname))
@@ -390,17 +413,23 @@
(defn- emit-deftype*
"Do not use this directly - use deftype"
- [tagname name fields interfaces methods]
- (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." name)) (meta name))
+ [tagname cname fields interfaces methods opts]
+ (let [classname (with-meta (symbol (str (namespace-munge *ns*) "." cname)) (meta cname))
interfaces (conj interfaces 'clojure.lang.IType)]
- `(deftype* ~tagname ~classname ~fields
+ `(deftype* ~(symbol (name (ns-name *ns*)) (name tagname)) ~classname ~fields
:implements ~interfaces
+ ~@(mapcat identity opts)
~@methods)))
(defmacro deftype
"(deftype name [fields*] options* specs*)
-
- Currently there are no options.
+
+ Options are expressed as sequential keywords and arguments (in any order).
+
+ Supported options:
+ :load-ns - if true, importing the type class will cause the
+ namespace in which the type was defined to be loaded.
+ Defaults to false.
Each spec consists of a protocol or interface name followed by zero
or more method bodies:
@@ -453,8 +482,8 @@
writes the .class file to the *compile-path* directory.
One constructor will be defined, taking the designated fields. Note
- that the field names __meta and __extmap are currently reserved and
- should not be used when defining your own types.
+ that the field names __meta, __extmap, __hash and __hasheq are currently
+ reserved and should not be used when defining your own types.
Given (deftype TypeName ...), a factory function called ->TypeName
will be defined, taking positional parameters for the fields"
@@ -471,7 +500,7 @@
fields (vec (map #(with-meta % nil) fields))
[field-args over] (split-at 20 fields)]
`(let []
- ~(emit-deftype* name gname (vec hinted-fields) (vec interfaces) methods)
+ ~(emit-deftype* name gname (vec hinted-fields) (vec interfaces) methods opts)
(import ~classname)
~(build-positional-factory gname classname fields)
~classname)))
@@ -481,7 +510,7 @@
(defn- expand-method-impl-cache [^clojure.lang.MethodImplCache cache c f]
(if (.map cache)
(let [cs (assoc (.map cache) c (clojure.lang.MethodImplCache$Entry. c f))]
- (clojure.lang.MethodImplCache. (.protocol cache) (.methodk cache) cs))
+ (clojure.lang.MethodImplCache. (.sym cache) (.protocol cache) (.methodk cache) cs))
(let [cs (into1 {} (remove (fn [[c e]] (nil? e)) (map vec (partition 2 (.table cache)))))
cs (assoc cs c (clojure.lang.MethodImplCache$Entry. c f))]
(if-let [[shift mask] (maybe-min-hash (map hash (keys cs)))]
@@ -492,8 +521,8 @@
(aset t (inc i) e)
t))
table cs)]
- (clojure.lang.MethodImplCache. (.protocol cache) (.methodk cache) shift mask table))
- (clojure.lang.MethodImplCache. (.protocol cache) (.methodk cache) cs)))))
+ (clojure.lang.MethodImplCache. (.sym cache) (.protocol cache) (.methodk cache) shift mask table))
+ (clojure.lang.MethodImplCache. (.sym cache) (.protocol cache) (.methodk cache) cs)))))
(defn- super-chain [^Class c]
(when c
@@ -548,16 +577,16 @@
(defn -cache-protocol-fn [^clojure.lang.AFunction pf x ^Class c ^clojure.lang.IFn interf]
(let [cache (.__methodImplCache pf)
f (if (.isInstance c x)
- interf
+ interf
(find-protocol-method (.protocol cache) (.methodk cache) x))]
(when-not f
- (throw (IllegalArgumentException. (str "No implementation of method: " (.methodk cache)
- " of protocol: " (:var (.protocol cache))
+ (throw (IllegalArgumentException. (str "No implementation of method: " (.methodk cache)
+ " of protocol: " (:var (.protocol cache))
" found for class: " (if (nil? x) "nil" (.getName (class x)))))))
(set! (.__methodImplCache pf) (expand-method-impl-cache cache (class x) f))
f))
-(defn- emit-method-builder [on-interface method on-method arglists]
+(defn- emit-method-builder [on-interface method on-method arglists extend-via-meta]
(let [methodk (keyword method)
gthis (with-meta (gensym) {:tag 'clojure.lang.AFunction})
ginterf (gensym)]
@@ -577,19 +606,30 @@
(fn [args]
(let [gargs (map #(gensym (str "gf__" % "__")) args)
target (first gargs)]
- `([~@gargs]
- (let [cache# (.__methodImplCache ~gthis)
- f# (.fnFor cache# (clojure.lang.Util/classOf ~target))]
- (if f#
- (f# ~@gargs)
- ((-cache-protocol-fn ~gthis ~target ~on-interface ~ginterf) ~@gargs))))))
+ (if extend-via-meta
+ `([~@gargs]
+ (let [cache# (.__methodImplCache ~gthis)
+ f# (.fnFor cache# (clojure.lang.Util/classOf ~target))]
+ (if (identical? f# ~ginterf)
+ (f# ~@gargs)
+ (if-let [meta# (when-let [m# (meta ~target)] ((.sym cache#) m#))]
+ (meta# ~@gargs)
+ (if f#
+ (f# ~@gargs)
+ ((-cache-protocol-fn ~gthis ~target ~on-interface ~ginterf) ~@gargs))))))
+ `([~@gargs]
+ (let [cache# (.__methodImplCache ~gthis)
+ f# (.fnFor cache# (clojure.lang.Util/classOf ~target))]
+ (if f#
+ (f# ~@gargs)
+ ((-cache-protocol-fn ~gthis ~target ~on-interface ~ginterf) ~@gargs)))))))
arglists))]
(set! (.__methodImplCache f#) cache#)
f#))))
(defn -reset-methods [protocol]
(doseq [[^clojure.lang.Var v build] (:method-builders protocol)]
- (let [cache (clojure.lang.MethodImplCache. protocol (keyword (.sym v)))]
+ (let [cache (clojure.lang.MethodImplCache. (symbol v) protocol (keyword (.sym v)))]
(.bindRoot v (build cache)))))
(defn- assert-same-protocol [protocol-var method-syms]
@@ -613,7 +653,17 @@
[opts sigs]))
sigs (when sigs
(reduce1 (fn [m s]
- (let [name-meta (meta (first s))
+ (let [disallowed? '#{int long float double char short byte boolean void}
+ array-tag? '#{ints longs floats doubles chars shorts bytes booleans objects}
+ resolve-class-symbol (fn [tag]
+ (when-not (disallowed? tag)
+ (if-let [c (and (instance? clojure.lang.Symbol tag)
+ (= (.indexOf (.getName ^clojure.lang.Symbol tag) ".") -1)
+ (not (array-tag? tag))
+ (resolve tag))]
+ (symbol (.getName c))
+ tag)))
+ name-meta (update-in (meta (first s)) [:tag] resolve-class-symbol)
mname (with-meta (first s) nil)
[arglists doc]
(loop [as [] rs (rest s)]
@@ -657,7 +707,8 @@
(mapcat
(fn [s]
[`(intern *ns* (with-meta '~(:name s) (merge '~s {:protocol (var ~name)})))
- (emit-method-builder (:on-interface opts) (:name s) (:on s) (:arglists s))])
+ (emit-method-builder (:on-interface opts) (:name s) (:on s) (:arglists s)
+ (:extend-via-metadata opts))])
(vals sigs)))))
(-reset-methods ~name)
'~name)))
@@ -669,6 +720,9 @@
;optional doc string
\"A doc string for AProtocol abstraction\"
+ ;options
+ :extend-via-metadata true
+
;method signatures
(bar [this a b] \"bar docs\")
(baz [this a] [this a b] [this a b c] \"baz docs\"))
@@ -683,6 +737,13 @@
effect, and defines no new types or classes. Implementations of
the protocol methods can be provided using extend.
+ When :extend-via-metadata is true, values can extend protocols by
+ adding metadata where keys are fully-qualified protocol function
+ symbols and values are function implementations. Protocol
+ implementations are checked first for direct definitions (defrecord,
+ deftype, reify), then metadata definitions, then external
+ extensions (extend, extend-type, extend-protocol)
+
defprotocol will automatically generate a corresponding interface,
with the same name as the protocol, i.e. given a protocol:
my.ns/Protocol, an interface: my.ns.Protocol. The interface will
@@ -765,7 +826,7 @@
(defn- emit-impl [[p fs]]
[p (zipmap (map #(-> % first keyword) fs)
- (map #(cons 'fn (drop 1 %)) fs))])
+ (map #(cons `fn (drop 1 %)) fs))])
(defn- emit-hinted-impl [c [p fs]]
(let [hint (fn [specs]
@@ -777,7 +838,7 @@
body))
specs)))]
[p (zipmap (map #(-> % first name keyword) fs)
- (map #(cons 'fn (hint (drop 1 %))) fs))]))
+ (map #(cons `fn (hint (drop 1 %))) fs))]))
(defn- emit-extend-type [c specs]
(let [impls (parse-impls specs)]
diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj
index 78c00745a8..7e59c9c809 100644
--- a/src/clj/clojure/core_print.clj
+++ b/src/clj/clojure/core_print.clj
@@ -38,6 +38,13 @@
(def ^:dynamic *verbose-defrecords* false)
+(def ^:dynamic
+ ^{:doc "*print-namespace-maps* controls whether the printer will print
+ namespace map literal syntax. It defaults to false, but the REPL binds
+ to true."
+ :added "1.9"}
+ *print-namespace-maps* false)
+
(defn- print-sequential [^String begin, print-one, ^String sep, ^String end, sequence, ^Writer w]
(binding [*print-level* (and (not *print-dup*) *print-level* (dec *print-level*))]
(if (and *print-level* (neg? *print-level*))
@@ -121,6 +128,20 @@
(defmethod print-method Number [o, ^Writer w]
(.write w (str o)))
+(defmethod print-method Double [o, ^Writer w]
+ (cond
+ (= Double/POSITIVE_INFINITY o) (.write w "##Inf")
+ (= Double/NEGATIVE_INFINITY o) (.write w "##-Inf")
+ (.isNaN ^Double o) (.write w "##NaN")
+ :else (.write w (str o))))
+
+(defmethod print-method Float [o, ^Writer w]
+ (cond
+ (= Float/POSITIVE_INFINITY o) (.write w "##Inf")
+ (= Float/NEGATIVE_INFINITY o) (.write w "##-Inf")
+ (.isNaN ^Float o) (.write w "##NaN")
+ :else (.write w (str o))))
+
(defmethod print-dup Number [o, ^Writer w]
(print-ctor o
(fn [o w]
@@ -205,18 +226,46 @@
(print-meta v w)
(print-sequential "[" pr-on " " "]" v w))
+(defn- print-prefix-map [prefix kvs print-one w]
+ (print-sequential
+ (str prefix "{")
+ (fn [[k v] ^Writer w]
+ (do (print-one k w) (.append w \space) (print-one v w)))
+ ", "
+ "}"
+ kvs w))
+
(defn- print-map [m print-one w]
- (print-sequential
- "{"
- (fn [e ^Writer w]
- (do (print-one (key e) w) (.append w \space) (print-one (val e) w)))
- ", "
- "}"
- (seq m) w))
+ (print-prefix-map nil m print-one w))
+
+(defn- strip-ns
+ [named]
+ (if (symbol? named)
+ (symbol nil (name named))
+ (keyword nil (name named))))
+
+(defn- lift-ns
+ "Returns [lifted-ns lifted-kvs] or nil if m can't be lifted."
+ [m]
+ (when *print-namespace-maps*
+ (loop [ns nil
+ [[k v :as entry] & entries] (seq m)
+ kvs []]
+ (if entry
+ (when (qualified-ident? k)
+ (if ns
+ (when (= ns (namespace k))
+ (recur ns entries (conj kvs [(strip-ns k) v])))
+ (when-let [new-ns (namespace k)]
+ (recur new-ns entries (conj kvs [(strip-ns k) v])))))
+ [ns kvs]))))
(defmethod print-method clojure.lang.IPersistentMap [m, ^Writer w]
(print-meta m w)
- (print-map m pr-on w))
+ (let [[ns lift-kvs] (lift-ns m)]
+ (if ns
+ (print-prefix-map (str "#:" ns) lift-kvs pr-on w)
+ (print-map m pr-on w))))
(defmethod print-dup java.util.Map [m, ^Writer w]
(print-ctor m #(print-map (seq %1) print-dup %2) w))
@@ -331,7 +380,9 @@
Short/TYPE "Short/TYPE"})
(defmethod print-method Class [^Class c, ^Writer w]
- (.write w (.getName c)))
+ (if (.isArray c)
+ (print-method (clojure.lang.Util/arrayTypeToSymbol c) w)
+ (.write w (.getName c))))
(defmethod print-dup Class [^Class c, ^Writer w]
(cond
@@ -413,22 +464,83 @@
(defmethod print-method StackTraceElement [^StackTraceElement o ^Writer w]
(print-method [(symbol (.getClassName o)) (symbol (.getMethodName o)) (.getFileName o) (.getLineNumber o)] w))
-(defn- throwable-as-map [^Throwable o]
+(defn StackTraceElement->vec
+ "Constructs a data representation for a StackTraceElement: [class method file line]"
+ {:added "1.9"}
+ [^StackTraceElement o]
+ [(symbol (.getClassName o)) (symbol (.getMethodName o)) (.getFileName o) (.getLineNumber o)])
+
+(defn Throwable->map
+ "Constructs a data representation for a Throwable with keys:
+ :cause - root cause message
+ :phase - error phase
+ :via - cause chain, with cause keys:
+ :type - exception class symbol
+ :message - exception message
+ :data - ex-data
+ :at - top stack element
+ :trace - root cause stack elements"
+ {:added "1.7"}
+ [^Throwable o]
(let [base (fn [^Throwable t]
- {:type (class t)
- :message (.getLocalizedMessage t)
- :at (get (.getStackTrace t) 0)})
+ (merge {:type (symbol (.getName (class t)))}
+ (when-let [msg (.getLocalizedMessage t)]
+ {:message msg})
+ (when-let [ed (ex-data t)]
+ {:data ed})
+ (let [st (.getStackTrace t)]
+ (when (pos? (alength st))
+ {:at (StackTraceElement->vec (aget st 0))}))))
via (loop [via [], ^Throwable t o]
(if t
(recur (conj via t) (.getCause t))
- via))]
- {:cause (.getLocalizedMessage ^Throwable (last via))
- :via (vec (map base via))
- :trace (vec (.getStackTrace (or ^Throwable (last via) o)))}))
+ via))
+ ^Throwable root (peek via)]
+ (merge {:via (vec (map base via))
+ :trace (vec (map StackTraceElement->vec
+ (.getStackTrace ^Throwable (or root o))))}
+ (when-let [root-msg (.getLocalizedMessage root)]
+ {:cause root-msg})
+ (when-let [data (ex-data root)]
+ {:data data})
+ (when-let [phase (-> o ex-data :clojure.error/phase)]
+ {:phase phase}))))
(defn- print-throwable [^Throwable o ^Writer w]
- (.write w "#error")
- (print-method (throwable-as-map o) w))
+ (.write w "#error {\n :cause ")
+ (let [{:keys [cause data via trace]} (Throwable->map o)
+ print-via #(do (.write w "{:type ")
+ (print-method (:type %) w)
+ (.write w "\n :message ")
+ (print-method (:message %) w)
+ (when-let [data (:data %)]
+ (.write w "\n :data ")
+ (print-method data w))
+ (when-let [at (:at %)]
+ (.write w "\n :at ")
+ (print-method (:at %) w))
+ (.write w "}"))]
+ (print-method cause w)
+ (when data
+ (.write w "\n :data ")
+ (print-method data w))
+ (when via
+ (.write w "\n :via\n [")
+ (when-let [fv (first via)]
+ (print-via fv)
+ (doseq [v (rest via)]
+ (.write w "\n ")
+ (print-via v)))
+ (.write w "]"))
+ (when trace
+ (.write w "\n :trace\n [")
+ (when-let [ft (first trace)]
+ (print-method ft w)
+ (doseq [t (rest trace)]
+ (.write w "\n ")
+ (print-method t w)))
+ (.write w "]")))
+ (.write w "}"))
(defmethod print-method Throwable [^Throwable o ^Writer w]
(print-throwable o w))
@@ -445,3 +557,30 @@
(print-method (:form o) w))
(def ^{:private true} print-initialized true)
+
+(defn ^java.io.PrintWriter PrintWriter-on
+ "implements java.io.PrintWriter given flush-fn, which will be called
+ when .flush() is called, with a string built up since the last call to .flush().
+ if not nil, close-fn will be called with no arguments when .close is called.
+ autoflush? determines if the PrintWriter will autoflush, false by default."
+ {:added "1.10"}
+ ([flush-fn close-fn]
+ (PrintWriter-on flush-fn close-fn false))
+ ([flush-fn close-fn autoflush?]
+ (let [sb (StringBuilder.)]
+ (-> (proxy [Writer] []
+ (flush []
+ (when (pos? (.length sb))
+ (flush-fn (.toString sb)))
+ (.setLength sb 0))
+ (close []
+ (.flush ^Writer this)
+ (when close-fn (close-fn))
+ nil)
+ (write [str-cbuf off len]
+ (when (pos? len)
+ (if (instance? String str-cbuf)
+ (.append sb ^String str-cbuf ^int off ^int len)
+ (.append sb ^chars str-cbuf ^int off ^int len)))))
+ java.io.BufferedWriter.
+ (java.io.PrintWriter. ^boolean autoflush?)))))
diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj
index 570fbedf9a..46f7b4b868 100644
--- a/src/clj/clojure/core_proxy.clj
+++ b/src/clj/clojure/core_proxy.clj
@@ -13,6 +13,7 @@
(import
'(clojure.asm ClassWriter ClassVisitor Opcodes Type)
'(java.lang.reflect Modifier Constructor)
+ '(java.io Serializable NotSerializableException)
'(clojure.asm.commons Method GeneratorAdapter)
'(clojure.lang IProxy Reflector DynamicClassLoader IPersistentMap PersistentHashMap RT))
@@ -23,8 +24,10 @@
(or (some (fn [t] (when (every? #(isa? t %) rtypes) t)) rtypes)
(throw (Exception. "Incompatible return types"))))
-(defn- group-by-sig [coll]
- "takes a collection of [msig meth] and returns a seq of maps from return-types to meths."
+(defn- group-by-sig
+ "Takes a collection of [msig meth] and returns a seq of maps from
+ return-types to meths."
+ [coll]
(vals (reduce1 (fn [m [msig meth]]
(let [rtype (peek msig)
argsig (pop msig)]
@@ -43,8 +46,9 @@
[(Integer/toHexString (hash inames))])))))
(defn- generate-proxy [^Class super interfaces]
- (let [cv (new ClassWriter (. ClassWriter COMPUTE_MAXS))
- cname (.replace (proxy-name super interfaces) \. \/) ;(str "clojure/lang/" (gensym "Proxy__"))
+ (let [cv (clojure.lang.Compiler/classWriter)
+ pname (proxy-name super interfaces)
+ cname (.replace pname \. \/) ;(str "clojure/lang/" (gensym "Proxy__"))
ctype (. Type (getObjectType cname))
iname (fn [^Class c] (.. Type (getType c) (getInternalName)))
fmap "__clojureFnMap"
@@ -127,7 +131,7 @@
(. gen (endMethod))))]
;start class definition
- (. cv (visit (. Opcodes V1_5) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
+ (. cv (visit (. Opcodes V1_8) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
cname nil (iname super)
(into-array (map iname (cons IProxy interfaces)))))
;add field for fn mappings
@@ -148,6 +152,22 @@
(. gen (returnValue))
(. gen (endMethod)))))
+ ;disable serialization
+ (when (some #(isa? % Serializable) (cons super interfaces))
+ (let [m (. Method (getMethod "void writeObject(java.io.ObjectOutputStream)"))
+ gen (new GeneratorAdapter (. Opcodes ACC_PRIVATE) m nil nil cv)]
+ (. gen (visitCode))
+ (. gen (loadThis))
+ (. gen (loadArgs))
+ (. gen (throwException (totype NotSerializableException) pname))
+ (. gen (endMethod)))
+ (let [m (. Method (getMethod "void readObject(java.io.ObjectInputStream)"))
+ gen (new GeneratorAdapter (. Opcodes ACC_PRIVATE) m nil nil cv)]
+ (. gen (visitCode))
+ (. gen (loadThis))
+ (. gen (loadArgs))
+ (. gen (throwException (totype NotSerializableException) pname))
+ (. gen (endMethod))))
;add IProxy methods
(let [m (. Method (getMethod "void __initClojureFnMappings(clojure.lang.IPersistentMap)"))
gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
@@ -210,17 +230,28 @@
(for [^Class iface interfaces meth (. iface (getMethods))
:let [msig (method-sig meth)] :when (not (considered msig))]
{msig meth}))
+ ;; Treat abstract methods as interface methods
+ [mm ifaces-meths] (let [abstract? (fn [[_ ^Method meth]]
+ (Modifier/isAbstract (. meth (getModifiers))))
+ mm-no-abstract (remove abstract? mm)
+ abstract-meths (filter abstract? mm)]
+ [mm-no-abstract (concat ifaces-meths abstract-meths)])
mgroups (group-by-sig (concat mm ifaces-meths))
rtypes (map #(most-specific (keys %)) mgroups)
mb (map #(vector (%1 %2) (vals (dissoc %1 %2))) mgroups rtypes)
bridge? (reduce1 into1 #{} (map second mb))
ifaces-meths (remove bridge? (vals ifaces-meths))
- mm (remove bridge? (vals mm))]
+ mm (remove bridge? (vals mm))
+ reflect-Method-keyfn (fn [meth]
+ (let [[name param-types ^Class return-type] (method-sig meth)]
+ (-> [name]
+ (into1 (map #(.getName ^Class %) param-types))
+ (conj (.getName return-type)))))]
;add methods matching supers', if no mapping -> call super
- (doseq [[^java.lang.reflect.Method dest bridges] mb
- ^java.lang.reflect.Method meth bridges]
+ (doseq [[^java.lang.reflect.Method dest bridges] (sort-by (comp reflect-Method-keyfn first) mb)
+ ^java.lang.reflect.Method meth (sort-by reflect-Method-keyfn bridges)]
(gen-bridge meth dest))
- (doseq [^java.lang.reflect.Method meth mm]
+ (doseq [^java.lang.reflect.Method meth (sort-by reflect-Method-keyfn mm)]
(gen-method meth
(fn [^GeneratorAdapter gen ^Method m]
(. gen (loadThis))
@@ -233,7 +264,7 @@
(. m (getDescriptor)))))))
;add methods matching interfaces', if no mapping -> throw
- (doseq [^java.lang.reflect.Method meth ifaces-meths]
+ (doseq [^java.lang.reflect.Method meth (sort-by reflect-Method-keyfn ifaces-meths)]
(gen-method meth
(fn [^GeneratorAdapter gen ^Method m]
(. gen (throwException ex-type (. m (getName))))))))
@@ -315,7 +346,7 @@
defaults to Object.
The interfaces names must be valid interface types. If a method fn
- is not provided for a class method, the superclass methd will be
+ is not provided for a class method, the superclass method will be
called. If a method fn is not provided for an interface method, an
UnsupportedOperationException will be thrown should it be
called. Method fns are closures and can capture the environment in
@@ -389,23 +420,24 @@
snapshot (fn []
(reduce1 (fn [m e]
(assoc m (key e) ((val e))))
- {} (seq pmap)))]
+ {} (seq pmap)))
+ thisfn (fn thisfn [plseq]
+ (lazy-seq
+ (when-let [pseq (seq plseq)]
+ (cons (clojure.lang.MapEntry/create (first pseq) (v (first pseq)))
+ (thisfn (rest pseq))))))]
(proxy [clojure.lang.APersistentMap]
[]
- (iterator [] (.iterator ^Iterable pmap))
+ (iterator [] (clojure.lang.SeqIterator. ^java.util.Iterator (thisfn (keys pmap))))
(containsKey [k] (contains? pmap k))
- (entryAt [k] (when (contains? pmap k) (new clojure.lang.MapEntry k (v k))))
+ (entryAt [k] (when (contains? pmap k) (clojure.lang.MapEntry/create k (v k))))
(valAt ([k] (when (contains? pmap k) (v k)))
([k default] (if (contains? pmap k) (v k) default)))
(cons [m] (conj (snapshot) m))
(count [] (count pmap))
(assoc [k v] (assoc (snapshot) k v))
(without [k] (dissoc (snapshot) k))
- (seq [] ((fn thisfn [plseq]
- (lazy-seq
- (when-let [pseq (seq plseq)]
- (cons (new clojure.lang.MapEntry (first pseq) (v (first pseq)))
- (thisfn (rest pseq)))))) (keys pmap))))))
+ (seq [] (thisfn (keys pmap))))))
diff --git a/src/clj/clojure/data.clj b/src/clj/clojure/data.clj
index 345b2346f2..6e2105ed38 100644
--- a/src/clj/clojure/data.clj
+++ b/src/clj/clojure/data.clj
@@ -12,6 +12,8 @@
clojure.data
(:require [clojure.set :as set]))
+(set! *warn-on-reflection* true)
+
(declare diff)
(defn- atom-diff
@@ -78,9 +80,11 @@
(extend Object
Diff
- {:diff-similar (fn [a b] ((if (.. a getClass isArray) diff-sequential atom-diff) a b))}
+ {:diff-similar (fn [^Object a b]
+ ((if (.. a getClass isArray) diff-sequential atom-diff) a b))}
EqualityPartition
- {:equality-partition (fn [x] (if (.. x getClass isArray) :sequential :atom))})
+ {:equality-partition (fn [^Object x]
+ (if (.. x getClass isArray) :sequential :atom))})
(extend-protocol EqualityPartition
nil
diff --git a/src/clj/clojure/datafy.clj b/src/clj/clojure/datafy.clj
new file mode 100644
index 0000000000..4c69dea0c5
--- /dev/null
+++ b/src/clj/clojure/datafy.clj
@@ -0,0 +1,62 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns ^{:doc "Functions to turn objects into data. Alpha, subject to change"}
+ clojure.datafy
+ (:require [clojure.core.protocols :as p]))
+
+(set! *warn-on-reflection* true)
+
+(defn datafy
+ "Attempts to return x as data.
+ datafy will return the value of clojure.core.protocols/datafy. If
+ the value has been transformed and the result supports
+ metadata, :clojure.datafy/obj will be set on the metadata to the
+ original value of x, and :clojure.datafy/class to the name of the
+ class of x, as a symbol."
+ [x]
+ (let [v (p/datafy x)]
+ (if (identical? v x)
+ v
+ (if (instance? clojure.lang.IObj v)
+ (vary-meta v assoc ::obj x ::class (-> x class .getName symbol))
+ v))))
+
+(defn nav
+ "Returns (possibly transformed) v in the context of coll and k (a
+ key/index or nil). Callers should attempt to provide the key/index
+ context k for Indexed/Associative/ILookup colls if possible, but not
+ to fabricate one e.g. for sequences (pass nil). nav returns the
+ value of clojure.core.protocols/nav."
+ [coll k v]
+ (p/nav coll k v))
+
+(defn- sortmap [m]
+ (into (sorted-map) m))
+
+(extend-protocol p/Datafiable
+ Throwable
+ (datafy [x]
+ (Throwable->map x))
+
+ clojure.lang.IRef
+ (datafy [r]
+ (with-meta [(deref r)] (meta r)))
+
+ clojure.lang.Namespace
+ (datafy [n]
+ (with-meta {:name (.getName n)
+ :publics (-> n ns-publics sortmap)
+ :imports (-> n ns-imports sortmap)
+ :interns (-> n ns-interns sortmap)}
+ (meta n)))
+
+ java.lang.Class
+ (datafy [c]
+ (let [{:keys [members] :as ret} ((requiring-resolve 'clojure.reflect/reflect) c)]
+ (assoc ret :name (-> c .getName symbol) :members (->> members (group-by :name) sortmap)))))
diff --git a/src/clj/clojure/genclass.clj b/src/clj/clojure/genclass.clj
index 393b0df392..afef85c8ef 100644
--- a/src/clj/clojure/genclass.clj
+++ b/src/clj/clojure/genclass.clj
@@ -101,15 +101,26 @@
'char Character/TYPE
'chars (Class/forName "[C")})
-(defn- ^Class the-class [x]
- (cond
- (class? x) x
- (contains? prim->class x) (prim->class x)
- :else (let [strx (str x)]
- (clojure.lang.RT/classForName
- (if (some #{\. \[} strx)
- strx
- (str "java.lang." strx))))))
+(defn- the-array-class [sym]
+ (clojure.lang.RT/classForName
+ (let [cn (namespace sym)]
+ (clojure.lang.Compiler$HostExpr/buildArrayClassDescriptor
+ (if (or (clojure.lang.Compiler/primClass (symbol cn)) (some #{\.} cn))
+ sym
+ (symbol (str "java.lang." cn) (name sym)))))))
+
+(defn- ^Class the-class [x]
+ (cond
+ (class? x) x
+ (symbol? x) (cond (contains? prim->class x) (prim->class x)
+ (clojure.lang.Compiler$HostExpr/looksLikeArrayClass x)
+ (the-array-class x)
+ :else (let [strx (str x)]
+ (clojure.lang.RT/classForName
+ (if (some #{\. \[} strx)
+ strx
+ (str "java.lang." strx)))))
+ :else (clojure.lang.RT/classForName x)))
;; someday this can be made codepoint aware
(defn- valid-java-method-name
@@ -133,7 +144,7 @@
interfaces (map the-class implements)
supers (cons super interfaces)
ctor-sig-map (or constructors (zipmap (ctor-sigs super) (ctor-sigs super)))
- cv (new ClassWriter (. ClassWriter COMPUTE_MAXS))
+ cv (clojure.lang.Compiler/classWriter)
cname (. name (replace "." "/"))
pkg-name name
impl-pkg-name (str impl-ns)
@@ -161,6 +172,7 @@
ifn-type (totype clojure.lang.IFn)
iseq-type (totype clojure.lang.ISeq)
ex-type (totype java.lang.UnsupportedOperationException)
+ util-type (totype clojure.lang.Util)
all-sigs (distinct (concat (map #(let[[m p] (key %)] {m [p]}) (mapcat non-private-methods supers))
(map (fn [[m p]] {(str m) [p]}) methods)))
sigs-by-name (apply merge-with concat {} all-sigs)
@@ -253,7 +265,7 @@
(. gen (endMethod))))
]
;start class definition
- (. cv (visit (. Opcodes V1_5) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
+ (. cv (visit (. Opcodes V1_8) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
cname nil (iname super)
(when-let [ifc (seq interfaces)]
(into-array (map iname ifc)))))
@@ -287,11 +299,9 @@
(. gen putStatic ctype (var-name v) var-type))
(when load-impl-ns
- (. gen push "clojure.core")
- (. gen push "load")
- (. gen (invokeStatic rt-type (. Method (getMethod "clojure.lang.Var var(String,String)"))))
(. gen push (str "/" impl-cname))
- (. gen (invokeInterface ifn-type (new Method "invoke" obj-type (to-types [Object]))))
+ (. gen push ctype)
+ (. gen (invokeStatic util-type (. Method (getMethod "Object loadWithClass(String,Class)"))))
; (. gen push (str (.replace impl-pkg-name \- \_) "__init"))
; (. gen (invokeStatic class-type (. Method (getMethod "Class forName(String)"))))
(. gen pop))
@@ -647,14 +657,15 @@
fully-qualified class name given as a string or symbol
(such as 'java.lang.String)"
[c]
- (if (or (instance? Class c) (prim->class c))
- (Type/getType (the-class c))
- (let [strx (str c)]
- (Type/getObjectType
- (.replace (if (some #{\. \[} strx)
- strx
- (str "java.lang." strx))
- "." "/")))))
+ (let [c (or (and (symbol? c) (clojure.lang.Compiler$HostExpr/maybeArrayClass c)) c)]
+ (if (or (instance? Class c) (prim->class c))
+ (Type/getType (the-class c))
+ (let [strx (str c)]
+ (Type/getObjectType
+ (.replace (if (some #{\. \[} strx)
+ strx
+ (str "java.lang." strx))
+ "." "/"))))))
(defn- generate-interface
[{:keys [name extends methods]}]
@@ -662,13 +673,14 @@
(throw
(IllegalArgumentException. "Interface methods must not contain '-'")))
(let [iname (.replace (str name) "." "/")
- cv (ClassWriter. ClassWriter/COMPUTE_MAXS)]
- (. cv visit Opcodes/V1_5 (+ Opcodes/ACC_PUBLIC
+ cv (clojure.lang.Compiler/classWriter)]
+ (. cv visit Opcodes/V1_8 (+ Opcodes/ACC_PUBLIC
Opcodes/ACC_ABSTRACT
Opcodes/ACC_INTERFACE)
iname nil "java/lang/Object"
(when (seq extends)
(into-array (map #(.getInternalName (asm-type %)) extends))))
+ (when (not= "NO_SOURCE_FILE" *source-path*) (. cv visitSource *source-path* nil))
(add-annotations cv (meta name))
(doseq [[mname pclasses rclass pmetas] methods]
(let [mv (. cv visitMethod (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT)
diff --git a/src/clj/clojure/gvec.clj b/src/clj/clojure/gvec.clj
index abc01ac93c..590cfcecf9 100644
--- a/src/clj/clojure/gvec.clj
+++ b/src/clj/clojure/gvec.clj
@@ -10,7 +10,8 @@
(in-ns 'clojure.core)
-(import '(clojure.lang Murmur3))
+(import '(clojure.lang Murmur3 IHashEq Sequential Util SeqIterator)
+ '(java.util List))
(set! *warn-on-reflection* true)
@@ -55,7 +56,7 @@
(recur ret (inc i))))
ret))))
-(deftype VecSeq [^clojure.core.ArrayManager am ^clojure.core.IVecImpl vec anode ^int i ^int offset]
+(deftype VecSeq [^clojure.core.ArrayManager am ^clojure.core.IVecImpl vec anode ^int i ^int offset ^clojure.lang.IPersistentMap _meta]
:no-print true
clojure.core.protocols.InternalReduce
@@ -82,11 +83,11 @@
(first [_] (.aget am anode offset))
(next [this]
(if (< (inc offset) (.alength am anode))
- (new VecSeq am vec anode i (inc offset))
+ (new VecSeq am vec anode i (inc offset) nil)
(.chunkedNext this)))
(more [this]
(let [s (.next this)]
- (or s (clojure.lang.PersistentList/EMPTY))))
+ (or s clojure.lang.PersistentList/EMPTY)))
(cons [this o]
(clojure.lang.Cons. o this))
(count [this]
@@ -120,10 +121,48 @@
(chunkedNext [_]
(let [nexti (+ i (.alength am anode))]
(when (< nexti (count vec))
- (new VecSeq am vec (.arrayFor vec nexti) nexti 0))))
+ (new VecSeq am vec (.arrayFor vec nexti) nexti 0 nil))))
(chunkedMore [this]
(let [s (.chunkedNext this)]
- (or s (clojure.lang.PersistentList/EMPTY)))))
+ (or s clojure.lang.PersistentList/EMPTY)))
+
+ clojure.lang.IMeta
+ (meta [_]
+ _meta)
+
+ clojure.lang.IObj
+ (withMeta [_ m]
+ (new VecSeq am vec anode i offset m))
+
+Object
+ (hashCode [this]
+ (loop [hash 1
+ s (seq this)]
+ (if s
+ (let [v (first s)]
+ (if (nil? v)
+ (recur (unchecked-multiply-int 31 hash) (next s))
+ (recur (unchecked-add-int (unchecked-multiply-int 31 hash) (.hashCode ^Object v)) (next s))))
+ hash)))
+ (equals [this other]
+ (cond (identical? this other) true
+ (or (instance? Sequential other) (instance? List other))
+ (loop [s this
+ os (seq other)]
+ (if (nil? s)
+ (nil? os)
+ (if (Util/equals (first s) (first os))
+ (recur (next s) (next os))
+ false)))
+ :else false))
+
+ IHashEq
+ (hasheq [this]
+ (Murmur3/hashOrdered this))
+
+ Iterable
+ (iterator [this]
+ (SeqIterator. this)))
(defmethod print-method ::VecSeq [v w]
((get (methods print-method) clojure.lang.ISeq) v w))
@@ -249,6 +288,7 @@
(new Vec am cnt shift (.doAssoc this shift root i val) tail (meta this)))
(= i cnt) (.cons this val)
:else (throw (IndexOutOfBoundsException.))))
+ (length [_] cnt)
clojure.lang.Reversible
(rseq [this]
@@ -267,7 +307,7 @@
(< (int k) cnt)))
(entryAt [this k]
(if (.containsKey this k)
- (clojure.lang.MapEntry. k (.nth this (int k)))
+ (clojure.lang.MapEntry/create k (.nth this (int k)))
nil))
clojure.lang.ILookup
@@ -295,7 +335,7 @@
(seq [this]
(if (zero? cnt)
nil
- (VecSeq. am this (.arrayFor this 0) 0 0)))
+ (VecSeq. am this (.arrayFor this 0) 0 0 nil)))
clojure.lang.Sequential ;marker, no methods
@@ -385,7 +425,10 @@
(let [i (java.util.concurrent.atomic.AtomicInteger. 0)]
(reify java.util.Iterator
(hasNext [_] (< (.get i) cnt))
- (next [_] (.nth this (dec (.incrementAndGet i))))
+ (next [_] (try
+ (.nth this (dec (.incrementAndGet i)))
+ (catch IndexOutOfBoundsException _
+ (throw (java.util.NoSuchElementException.)))))
(remove [_] (throw (UnsupportedOperationException.))))))
java.util.Collection
@@ -393,7 +436,7 @@
(containsAll [this c] (every? #(.contains this %) c))
(isEmpty [_] (zero? cnt))
(toArray [this] (into-array Object this))
- (toArray [this arr]
+ (^objects toArray [this ^objects arr]
(if (>= (count arr) cnt)
(do
(dotimes [i cnt]
@@ -428,9 +471,15 @@
(reify java.util.ListIterator
(hasNext [_] (< (.get i) cnt))
(hasPrevious [_] (pos? i))
- (next [_] (.nth this (dec (.incrementAndGet i))))
+ (next [_] (try
+ (.nth this (dec (.incrementAndGet i)))
+ (catch IndexOutOfBoundsException _
+ (throw (java.util.NoSuchElementException.)))))
(nextIndex [_] (.get i))
- (previous [_] (.nth this (.decrementAndGet i)))
+ (previous [_] (try
+ (.nth this (.decrementAndGet i))
+ (catch IndexOutOfBoundsException _
+ (throw (java.util.NoSuchElementException.)))))
(previousIndex [_] (dec (.get i)))
(add [_ e] (throw (UnsupportedOperationException.)))
(remove [_] (throw (UnsupportedOperationException.)))
@@ -465,7 +514,13 @@
:char (mk-am char)
:boolean (mk-am boolean)})
-(defn vector-of
+(defmacro ^:private ams-check [t]
+ `(let [am# (ams ~t)]
+ (if am#
+ am#
+ (throw (IllegalArgumentException. (str "Unrecognized type " ~t))))))
+
+(defn vector-of
"Creates a new vector of a single primitive type t, where t is one
of :int :long :float :double :byte :short :char or :boolean. The
resulting vector complies with the interface of vectors in general,
@@ -475,28 +530,28 @@
{:added "1.2"
:arglists '([t] [t & elements])}
([t]
- (let [am ^clojure.core.ArrayManager (ams t)]
+ (let [^clojure.core.ArrayManager am (ams-check t)]
(Vec. am 0 5 EMPTY-NODE (.array am 0) nil)))
([t x1]
- (let [am ^clojure.core.ArrayManager (ams t)
+ (let [^clojure.core.ArrayManager am (ams-check t)
arr (.array am 1)]
(.aset am arr 0 x1)
(Vec. am 1 5 EMPTY-NODE arr nil)))
([t x1 x2]
- (let [am ^clojure.core.ArrayManager (ams t)
+ (let [^clojure.core.ArrayManager am (ams-check t)
arr (.array am 2)]
(.aset am arr 0 x1)
(.aset am arr 1 x2)
(Vec. am 2 5 EMPTY-NODE arr nil)))
([t x1 x2 x3]
- (let [am ^clojure.core.ArrayManager (ams t)
+ (let [^clojure.core.ArrayManager am (ams-check t)
arr (.array am 3)]
(.aset am arr 0 x1)
(.aset am arr 1 x2)
(.aset am arr 2 x3)
(Vec. am 3 5 EMPTY-NODE arr nil)))
([t x1 x2 x3 x4]
- (let [am ^clojure.core.ArrayManager (ams t)
+ (let [^clojure.core.ArrayManager am (ams-check t)
arr (.array am 4)]
(.aset am arr 0 x1)
(.aset am arr 1 x2)
diff --git a/src/clj/clojure/inspector.clj b/src/clj/clojure/inspector.clj
index 85e79c0bfd..bd84d9a8cc 100644
--- a/src/clj/clojure/inspector.clj
+++ b/src/clj/clojure/inspector.clj
@@ -21,7 +21,7 @@
(defn collection-tag [x]
(cond
- (instance? java.util.Map$Entry x) :entry
+ (map-entry? x) :entry
(instance? java.util.Map x) :seqable
(instance? java.util.Set x) :seqable
(sequential? x) :seq
diff --git a/src/clj/clojure/instant.clj b/src/clj/clojure/instant.clj
index c5274a79e3..2c052c03fa 100644
--- a/src/clj/clojure/instant.clj
+++ b/src/clj/clojure/instant.clj
@@ -11,6 +11,8 @@
[java.sql Timestamp]))
+(set! *warn-on-reflection* true)
+
;;; ------------------------------------------------------------------------
;;; convenience macros
@@ -45,7 +47,10 @@
(recur (.append b \0))
(.toString b)))))
-(def parse-timestamp
+(def ^:private timestamp
+ #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?")
+
+(defn parse-timestamp
"Parse a string containing an RFC3339-like like timestamp.
The function new-instant is called with the following arguments.
@@ -96,9 +101,7 @@ Though time-offset is syntactically optional, a missing time-offset
will be treated as if the time-offset zero (+00:00) had been
specified.
"
- (let [timestamp #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?"]
-
- (fn [new-instant ^CharSequence cs]
+ [new-instant ^CharSequence cs]
(if-let [[_ years months days hours minutes seconds fraction
offset-sign offset-hours offset-minutes]
(re-matches timestamp cs)]
@@ -115,7 +118,7 @@ specified.
:else 0)
(if-not offset-hours 0 (parse-int offset-hours))
(if-not offset-minutes 0 (parse-int offset-minutes)))
- (fail (str "Unrecognized date/time syntax: " cs))))))
+ (fail (str "Unrecognized date/time syntax: " cs))))
;;; ------------------------------------------------------------------------
@@ -134,7 +137,7 @@ specified.
((if leap-year? dim-leap dim-norm) month))))
(defn validated
- "Return a function which constructs and instant by calling constructor
+ "Return a function which constructs an instant by calling constructor
after first validating that those arguments are in range and otherwise
plausible. The resulting function will throw an exception if called
with invalid arguments."
@@ -157,7 +160,7 @@ with invalid arguments."
;;; ------------------------------------------------------------------------
;;; print integration
-(def ^:private thread-local-utc-date-format
+(def ^:private ^ThreadLocal thread-local-utc-date-format
;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
(proxy [ThreadLocal] []
@@ -169,7 +172,7 @@ with invalid arguments."
(defn- print-date
"Print a java.util.Date as RFC3339 timestamp, always in UTC."
[^java.util.Date d, ^java.io.Writer w]
- (let [utc-format (.get thread-local-utc-date-format)]
+ (let [^java.text.DateFormat utc-format (.get thread-local-utc-date-format)]
(.write w "#inst \"")
(.write w (.format utc-format d))
(.write w "\"")))
@@ -203,7 +206,7 @@ with invalid arguments."
(print-calendar c w))
-(def ^:private thread-local-utc-timestamp-format
+(def ^:private ^ThreadLocal thread-local-utc-timestamp-format
;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
(proxy [ThreadLocal] []
@@ -214,7 +217,7 @@ with invalid arguments."
(defn- print-timestamp
"Print a java.sql.Timestamp as RFC3339 timestamp, always in UTC."
[^java.sql.Timestamp ts, ^java.io.Writer w]
- (let [utc-format (.get thread-local-utc-timestamp-format)]
+ (let [^java.text.DateFormat utc-format (.get thread-local-utc-timestamp-format)]
(.write w "#inst \"")
(.write w (.format utc-format ts))
;; add on nanos and offset
@@ -268,22 +271,24 @@ milliseconds since the epoch, UTC."
;; nanos must be set separately, pass 0 above for the base calendar
(.setNanos nanoseconds)))
-(def read-instant-date
+(defn read-instant-date
"To read an instant as a java.util.Date, bind *data-readers* to a map with
this var as the value for the 'inst key. The timezone offset will be used
to convert into UTC."
- (partial parse-timestamp (validated construct-date)))
+ [^CharSequence cs]
+ (parse-timestamp (validated construct-date) cs))
-(def read-instant-calendar
+(defn read-instant-calendar
"To read an instant as a java.util.Calendar, bind *data-readers* to a map with
this var as the value for the 'inst key. Calendar preserves the timezone
offset."
- (partial parse-timestamp (validated construct-calendar)))
+ [^CharSequence cs]
+ (parse-timestamp (validated construct-calendar) cs))
-(def read-instant-timestamp
+(defn read-instant-timestamp
"To read an instant as a java.sql.Timestamp, bind *data-readers* to a
map with this var as the value for the 'inst key. Timestamp preserves
fractional seconds with nanosecond precision. The timezone offset will
be used to convert into UTC."
- (partial parse-timestamp (validated construct-timestamp)))
-
+ [^CharSequence cs]
+ (parse-timestamp (validated construct-timestamp) cs))
diff --git a/src/clj/clojure/java/basis.clj b/src/clj/clojure/java/basis.clj
new file mode 100644
index 0000000000..9567801962
--- /dev/null
+++ b/src/clj/clojure/java/basis.clj
@@ -0,0 +1,47 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns clojure.java.basis
+ "The lib basis includes which libraries and versions were loaded both
+ for direct dependencies and transitive dependencies, as well as the
+ classpath and possibly other information from the resolution process.
+ This basis will be known if the runtime was started by the Clojure CLI.
+
+ The Clojure CLI or tools.deps merge a set of deps maps (often from
+ deps.edn files). Additional runtime modifications are supplied via argmap
+ keys, provided via alias maps in the merged deps. Deps maps typically have
+ :paths, :deps, and :aliases keys.
+
+ The basis is a superset of merged deps.edn files with the following
+ additional keys:
+ :basis-config - params used to configure basis deps sources, can be
+ string path, deps map, nil, or :default
+ :root - default = loaded as a resource from tools.deps)
+ :user - default = ~/.clojure/deps.edn)
+ :project - default = ./deps.edn)
+ :extra - default = nil
+ :aliases - coll of keyword aliases to include during dep calculation
+ :argmap - effective argmap (after resolving and merging argmaps from aliases)
+ :libs - map of lib to coord for all included libraries
+ :classpath - classpath map, keys are paths (to directory or .jar), values
+ are maps with source identifier (either :lib-name or :path-key)
+ :classpath-roots - vector of paths in classpath order (keys of :classpath)"
+ (:require
+ [clojure.java.basis.impl :as impl]))
+
+(defn initial-basis
+ "Initial runtime basis at launch, nil if unknown (process not started by CLI)"
+ {:added "1.12"}
+ []
+ @impl/init-basis)
+
+(defn current-basis
+ "Return the current basis, which may have been modified since runtime launch."
+ {:added "1.12"}
+ []
+ @@impl/the-basis)
diff --git a/src/clj/clojure/java/basis/impl.clj b/src/clj/clojure/java/basis/impl.clj
new file mode 100644
index 0000000000..5402a9e201
--- /dev/null
+++ b/src/clj/clojure/java/basis/impl.clj
@@ -0,0 +1,51 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns clojure.java.basis.impl
+ (:require
+ [clojure.edn :as edn]
+ [clojure.java.io :as jio])
+ (:import
+ [java.io PushbackReader]))
+
+(set! *warn-on-reflection* true)
+
+(defn- read-edn
+ "Coerce f to a reader via clojure.java.io/reader and read one edn value.
+ The reader should contain a single value. Empty input returns nil.
+ The reader will be read to EOF and closed."
+ [f]
+ (let [reader (jio/reader f)
+ EOF (Object.)]
+ (with-open [rdr (PushbackReader. reader)]
+ (let [val (edn/read {:default tagged-literal :eof EOF} rdr)]
+ (if (identical? EOF val)
+ nil
+ (if (not (identical? EOF (edn/read {:eof EOF} rdr)))
+ (throw (ex-info "Invalid file, expected edn to contain a single value." {}))
+ val))))))
+
+(defn- read-basis
+ "Read basis edn from basis file or throw"
+ [basis-file]
+ (when-let [f (jio/file basis-file)]
+ (when (.exists f)
+ (read-edn f))))
+
+;; delay construction until needed, access via initial-basis
+(def init-basis
+ (delay (read-basis (System/getProperty "clojure.basis"))))
+
+;; delay construction until needed, access via current-basis
+(def the-basis
+ (delay (atom @init-basis)))
+
+(defn update-basis!
+ "Update the runtime basis by applying f with args"
+ [f & args]
+ (apply swap! @the-basis f args))
diff --git a/src/clj/clojure/java/browse.clj b/src/clj/clojure/java/browse.clj
index 6fcc650756..6a16ce37db 100644
--- a/src/clj/clojure/java/browse.clj
+++ b/src/clj/clojure/java/browse.clj
@@ -12,7 +12,9 @@
clojure.java.browse
(:require [clojure.java.shell :as sh]
[clojure.string :as str])
- (:import (java.net URI)))
+ (:import (java.io File)
+ (java.net URI)
+ (java.lang ProcessBuilder ProcessBuilder$Redirect)))
(defn- macosx? []
(-> "os.name" System/getProperty .toLowerCase
@@ -71,6 +73,16 @@
script (if (= :uninitialized script)
(reset! *open-url-script* (open-url-script-val))
script)]
- (or (when script (sh/sh script (str url)) true)
+ (or (when script
+ (try
+ (let [command [script (str url)]
+ null-file (File. (if (.startsWith (System/getProperty "os.name") "Windows") "NUL" "/dev/null"))
+ pb (doto (ProcessBuilder. ^java.util.List command)
+ ;; emulate ProcessBuilder.Redirect.DISCARD added in Java 9
+ (.redirectOutput null-file)
+ (.redirectError null-file))]
+ (.start pb) ;; do not wait for the process
+ true)
+ (catch Throwable _ false)))
(open-url-in-browser url)
(open-url-in-swing url))))
diff --git a/src/clj/clojure/java/browse_ui.clj b/src/clj/clojure/java/browse_ui.clj
index bf3d7e0960..2c002a2999 100644
--- a/src/clj/clojure/java/browse_ui.clj
+++ b/src/clj/clojure/java/browse_ui.clj
@@ -26,5 +26,5 @@
(doto (javax.swing.JFrame.)
(.setContentPane (javax.swing.JScrollPane. htmlpane))
(.setBounds 32 32 700 900)
- (.show))))
+ (.setVisible true))))
diff --git a/src/clj/clojure/java/io.clj b/src/clj/clojure/java/io.clj
index e5006c2963..5b96664e01 100644
--- a/src/clj/clojure/java/io.clj
+++ b/src/clj/clojure/java/io.clj
@@ -19,8 +19,11 @@
StringReader ByteArrayInputStream
BufferedInputStream BufferedOutputStream
CharArrayReader Closeable)
+ (clojure.lang RT)
(java.net URI URL MalformedURLException Socket URLDecoder URLEncoder)))
+(set! *warn-on-reflection* true)
+
(def
^{:doc "Type object for a Java primitive byte array."
:private true
@@ -48,11 +51,11 @@
String
(as-file [s] (File. s))
- (as-url [s] (URL. s))
+ (as-url [s] (RT/toUrl s))
File
(as-file [f] f)
- (as-url [f] (.toURL (.toURI f)))
+ (as-url [f] (RT/toUrl f))
URL
(as-url [u] u)
@@ -253,12 +256,12 @@
(assoc default-streams-impl
:make-input-stream (fn [^String x opts]
(try
- (make-input-stream (URL. x) opts)
+ (make-input-stream (RT/toUrl x) opts)
(catch MalformedURLException e
(make-input-stream (File. x) opts))))
:make-output-stream (fn [^String x opts]
(try
- (make-output-stream (URL. x) opts)
+ (make-output-stream (RT/toUrl x) opts)
(catch MalformedURLException err
(make-output-stream (File. x) opts))))))
@@ -282,6 +285,16 @@
IOFactory
default-streams-impl)
+(extend nil
+ IOFactory
+ (assoc default-streams-impl
+ :make-reader (fn [x opts]
+ (throw (IllegalArgumentException.
+ (str "Cannot open <" (pr-str x) "> as a Reader."))))
+ :make-writer (fn [x opts]
+ (throw (IllegalArgumentException.
+ (str "Cannot open <" (pr-str x) "> as a Writer."))))))
+
(defmulti
^{:doc "Internal helper for copy"
:private true
@@ -375,12 +388,12 @@
(defmethod do-copy [byte-array-type Writer] [^"[B" input ^Writer output opts]
(do-copy (ByteArrayInputStream. input) output opts))
-(defmethod do-copy [byte-array-type File] [^"[B" input ^Writer output opts]
+(defmethod do-copy [byte-array-type File] [^"[B" input ^File output opts]
(do-copy (ByteArrayInputStream. input) output opts))
(defn copy
"Copies input to output. Returns nil or throws IOException.
- Input may be an InputStream, Reader, File, byte[], or String.
+ Input may be an InputStream, Reader, File, byte[], char[], or String.
Output may be an OutputStream, Writer, or File.
Options are key/value pairs and may be one of
@@ -418,7 +431,7 @@
(reduce file (file parent child) more)))
(defn delete-file
- "Delete file f. Raise an exception if it fails unless silently is true."
+ "Delete file f. If silently is nil or false, raise an exception on failure, else return the value of silently."
{:added "1.2"}
[f & [silently]]
(or (.delete (file f))
diff --git a/src/clj/clojure/java/javadoc.clj b/src/clj/clojure/java/javadoc.clj
index 36527097c0..c36acc3de6 100644
--- a/src/clj/clojure/java/javadoc.clj
+++ b/src/clj/clojure/java/javadoc.clj
@@ -20,20 +20,29 @@
(def ^:dynamic *core-java-api*
(case (System/getProperty "java.specification.version")
- "1.6" "http://java.sun.com/javase/6/docs/api/"
- "http://java.sun.com/javase/7/docs/api/"))
+ "1.8" "http://docs.oracle.com/javase/8/docs/api/"
+ "9" "http://docs.oracle.com/javase/9/docs/api/"
+ "10" "http://docs.oracle.com/javase/10/docs/api/"
+ "11" "https://docs.oracle.com/en/java/javase/11/docs/api/%s/"
+ "12" "https://docs.oracle.com/en/java/javase/12/docs/api/%s/"
+ "13" "https://docs.oracle.com/en/java/javase/13/docs/api/%s/"
+ "14" "https://docs.oracle.com/en/java/javase/14/docs/api/%s/"
+ "15" "https://docs.oracle.com/en/java/javase/15/docs/api/%s/"
+ "http://docs.oracle.com/javase/8/docs/api/"))
(def ^:dynamic *remote-javadocs*
(ref (sorted-map
+ "com.google.common." "http://google.github.io/guava/releases/23.0/api/docs/"
"java." *core-java-api*
"javax." *core-java-api*
"org.ietf.jgss." *core-java-api*
"org.omg." *core-java-api*
"org.w3c.dom." *core-java-api*
"org.xml.sax." *core-java-api*
- "org.apache.commons.codec." "http://commons.apache.org/codec/api-release/"
- "org.apache.commons.io." "http://commons.apache.org/io/api-release/"
- "org.apache.commons.lang." "http://commons.apache.org/lang/api-release/")))
+ "org.apache.commons.codec." "http://commons.apache.org/proper/commons-codec/apidocs/"
+ "org.apache.commons.io." "http://commons.apache.org/proper/commons-io/javadocs/api-release/"
+ "org.apache.commons.lang." "http://commons.apache.org/proper/commons-lang/javadocs/api-2.6/"
+ "org.apache.commons.lang3." "http://commons.apache.org/proper/commons-lang/javadocs/api-release/")))
(defn add-local-javadoc
"Adds to the list of local Javadoc paths."
@@ -48,6 +57,16 @@
[package-prefix url]
(dosync (commute *remote-javadocs* assoc package-prefix url)))
+(defn- fill-in-module-name [^String url ^String classname]
+ ;; The getModule method was introduced in JDK 9, and did not exist
+ ;; in earlier JDK versions. Avoid calling it unless its result is
+ ;; needed.
+ (if (.contains url "%s")
+ (let [klass (Class/forName classname)
+ module-name (.getName (.getModule klass))]
+ (format url module-name))
+ url))
+
(defn- javadoc-url
"Searches for a URL for the given class name. Tries
*local-javadocs* first, then *remote-javadocs*. Returns a string."
@@ -64,7 +83,8 @@
;; If no local file, try remote URLs:
(or (some (fn [[prefix url]]
(when (.startsWith classname prefix)
- (str url url-path ".html")))
+ (str (fill-in-module-name url classname)
+ url-path ".html")))
@*remote-javadocs*)
;; if *feeling-lucky* try a web search
(when *feeling-lucky* (str *feeling-lucky-url* url-path ".html"))))))
diff --git a/src/clj/clojure/java/process.clj b/src/clj/clojure/java/process.clj
new file mode 100644
index 0000000000..734e125797
--- /dev/null
+++ b/src/clj/clojure/java/process.clj
@@ -0,0 +1,195 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns clojure.java.process
+ "A process invocation API wrapping the Java process API.
+
+ The primary function is 'start' which starts a process and handles the
+ streams as directed. It returns the Process object. Use 'exit-ref' to wait
+ for completion and receive the exit value, and ‘stdout', 'stderr', 'stdin'
+ to access the process streams. The 'exec' function handles the common case
+ to 'start' a process, wait for process exit, and return stdout."
+ (:require
+ [clojure.java.io :as jio])
+ (:import
+ [java.io File InputStream OutputStream]
+ [java.lang ProcessBuilder ProcessBuilder$Redirect Process]
+ [java.util List]
+ [clojure.lang IDeref IBlockingDeref]
+ [java.util.concurrent Executors ExecutorService ThreadFactory]))
+
+(set! *warn-on-reflection* true)
+
+;; this is built into Java 9, backfilled here for Java 8
+(def ^:private ^File null-file
+ (delay
+ (jio/file
+ (if (.startsWith (System/getProperty "os.name") "Windows")
+ "NUL"
+ "/dev/null"))))
+
+(defn to-file
+ "Coerce f to a file per clojure.java.io/file and return a ProcessBuilder.Redirect writing to the file.
+ Set ':append' in opts to append. This can be passed to 'start' in :out or :err."
+ {:added "1.12"}
+ ^ProcessBuilder$Redirect [f & {:keys [append] :as opts}]
+ (let [fo (jio/file f)]
+ (if append
+ (ProcessBuilder$Redirect/appendTo fo)
+ (ProcessBuilder$Redirect/to fo))))
+
+(defn from-file
+ "Coerce f to a file per clojure.java.io/file and return a ProcessBuilder.Redirect reading from the file.
+ This can be passed to 'start' in :in."
+ {:added "1.12"}
+ ^ProcessBuilder$Redirect [f]
+ (ProcessBuilder$Redirect/from (jio/file f)))
+
+(defn start
+ "Start an external command, defined in args.
+ The process environment vars are inherited from the parent by
+ default (use :clear-env to clear them).
+
+ If needed, provide options in map as first arg:
+ :in - a ProcessBuilder.Redirect (default = :pipe) or :inherit
+ :out - a ProcessBuilder.Redirect (default = :pipe) or :inherit :discard
+ :err - a ProcessBuilder.Redirect (default = :pipe) or :inherit :discard :stdout
+ :dir - current directory when the process runs (default=\".\")
+ :clear-env - if true, remove all inherited parent env vars
+ :env - {env-var value} of environment variables to set (all strings)
+
+ Returns the java.lang.Process."
+ {:added "1.12"}
+ ^Process [& opts+args]
+ (let [[opts command] (if (map? (first opts+args))
+ [(first opts+args) (rest opts+args)]
+ [{} opts+args])
+ {:keys [in out err dir env clear-env]
+ :or {in :pipe, out :pipe, err :pipe, dir "."}} opts
+ pb (ProcessBuilder. ^List command)
+ to-redirect (fn to-redirect
+ [x]
+ (case x
+ :pipe ProcessBuilder$Redirect/PIPE
+ :inherit ProcessBuilder$Redirect/INHERIT
+ :discard (ProcessBuilder$Redirect/to @null-file)
+ ;; in Java 9+, just use ProcessBuilder$Redirect/DISCARD
+ x))]
+ (.directory pb (jio/file dir))
+ (.redirectInput pb ^ProcessBuilder$Redirect (to-redirect in))
+ (.redirectOutput pb ^ProcessBuilder$Redirect (to-redirect out))
+ (if
+ (= err :stdout) (.redirectErrorStream pb true)
+ (.redirectError pb ^ProcessBuilder$Redirect (to-redirect err)))
+ (when clear-env
+ (.clear (.environment pb)))
+ (when env
+ (let [pb-env (.environment pb)]
+ (run! (fn [[k v]] (.put pb-env k v)) env)))
+ (.start pb)))
+
+(defn stdin
+ "Given a process, return the stdin of the external process (an OutputStream)"
+ {:added "1.12"}
+ ^OutputStream [^Process process]
+ (.getOutputStream process))
+
+(defn stdout
+ "Given a process, return the stdout of the external process (an InputStream)"
+ {:added "1.12"}
+ ^InputStream [^Process process]
+ (.getInputStream process))
+
+(defn stderr
+ "Given a process, return the stderr of the external process (an InputStream)"
+ {:added "1.12"}
+ ^InputStream [^Process process]
+ (.getErrorStream process))
+
+(defn exit-ref
+ "Given a Process (the output of 'start'), return a reference that can be
+ used to wait for process completion then returns the exit value."
+ {:added "1.12"}
+ [^Process process]
+ (reify
+ IDeref
+ (deref [_] (long (.waitFor process)))
+
+ IBlockingDeref
+ (deref [_ timeout-ms timeout-val]
+ (if (.waitFor process timeout-ms java.util.concurrent.TimeUnit/MILLISECONDS)
+ (long (.exitValue process))
+ timeout-val))))
+
+;; A thread factory for daemon threads
+(defonce ^:private io-thread-factory
+ (let [counter (atom 0)]
+ (reify ThreadFactory
+ (newThread [_ r]
+ (doto (Thread. r)
+ (.setName (str "Clojure Process IO " (swap! counter inc)))
+ (.setDaemon true))))))
+
+;; An ExecutorService for cached, daemon threads
+(defonce ^:private io-executor
+ (Executors/newCachedThreadPool ^ThreadFactory io-thread-factory))
+
+(defn io-task
+ {:skip-wiki true}
+ [^Runnable f]
+ (let [f (bound-fn* f)
+ fut (.submit ^ExecutorService io-executor ^Callable f)]
+ (reify
+ clojure.lang.IDeref
+ (deref [_] (#'clojure.core/deref-future fut))
+ clojure.lang.IBlockingDeref
+ (deref
+ [_ timeout-ms timeout-val]
+ (#'clojure.core/deref-future fut timeout-ms timeout-val))
+ clojure.lang.IPending
+ (isRealized [_] (.isDone fut))
+ java.util.concurrent.Future
+ (get [_] (.get fut))
+ (get [_ timeout unit] (.get fut timeout unit))
+ (isCancelled [_] (.isCancelled fut))
+ (isDone [_] (.isDone fut))
+ (cancel [_ interrupt?] (.cancel fut interrupt?)))))
+
+(defn exec
+ "Execute a command and on successful exit, return the captured output,
+ else throw RuntimeException. Args are the same as 'start' and options
+ if supplied override the default 'exec' settings."
+ {:added "1.12"}
+ [& opts+args]
+ (let [[opts command] (if (map? (first opts+args))
+ [(first opts+args) (rest opts+args)]
+ [{} opts+args])
+ opts (merge {:err :inherit} opts)]
+ (let [proc (apply start opts command)
+ captured (io-task #(slurp (stdout proc)))
+ exit (deref (exit-ref proc))]
+ (if (zero? exit)
+ @captured
+ (throw (RuntimeException. (str "Process failed with exit=" exit)))))))
+
+(comment
+ ;; shell out and inherit the i/o
+ (start {:out :inherit, :err :stdout} "ls" "-l")
+
+ ;; write out and err to files, wait for process to exit, return exit code
+ @(exit-ref (start {:out (to-file "out") :err (to-file "err")} "ls" "-l"))
+
+ ;; capture output to string
+ (-> (start "ls" "-l") stdout slurp)
+
+ ;; with exec
+ (exec "ls" "-l")
+
+ ;; read input from file
+ (-> (exec {:in (from-file "deps.edn")} "wc" "-l") clojure.string/trim parse-long)
+ )
diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj
index a87d19848e..89baed7f81 100644
--- a/src/clj/clojure/main.clj
+++ b/src/clj/clojure/main.clj
@@ -12,20 +12,18 @@
:author "Stephen C. Gilardi and Rich Hickey"}
clojure.main
(:refer-clojure :exclude [with-bindings])
- (:import (clojure.lang Compiler Compiler$CompilerException
- LineNumberingPushbackReader RT))
+ (:require [clojure.spec.alpha :as spec])
+ (:import (java.io StringReader BufferedWriter FileWriter)
+ (java.nio.file Files)
+ (java.nio.file.attribute FileAttribute)
+ (clojure.lang Compiler Compiler$CompilerException
+ LineNumberingPushbackReader RT LispReader$ReaderException))
;;(:use [clojure.repl :only (demunge root-cause stack-element-str)])
)
(declare main)
;;;;;;;;;;;;;;;;;;; redundantly copied from clojure.repl to avoid dep ;;;;;;;;;;;;;;
-#_(defn root-cause [x] x)
-#_(defn stack-element-str
- "Returns a (possibly unmunged) string representation of a StackTraceElement"
- {:added "1.3"}
- [^StackTraceElement el]
- (.getClassName el))
(defn demunge
"Given a string representation of a fn class,
@@ -47,6 +45,20 @@
(recur cause)
cause))))
+;;;;;;;;;;;;;;;;;;; end of redundantly copied from clojure.repl to avoid dep ;;;;;;;;;;;;;;
+
+(def ^:private core-namespaces
+ #{"clojure.core" "clojure.core.reducers" "clojure.core.protocols" "clojure.data" "clojure.datafy"
+ "clojure.edn" "clojure.instant" "clojure.java.io" "clojure.main" "clojure.pprint" "clojure.reflect"
+ "clojure.repl" "clojure.set" "clojure.spec.alpha" "clojure.spec.gen.alpha" "clojure.spec.test.alpha"
+ "clojure.string" "clojure.template" "clojure.uuid" "clojure.walk" "clojure.xml" "clojure.zip"})
+
+(defn- core-class?
+ [^String class-name]
+ (and (not (nil? class-name))
+ (or (.startsWith class-name "clojure.lang.")
+ (contains? core-namespaces (second (re-find #"^([^$]+)\$" class-name))))))
+
(defn stack-element-str
"Returns a (possibly unmunged) string representation of a StackTraceElement"
{:added "1.3"}
@@ -61,7 +73,6 @@
" (" (.getFileName el) ":" (.getLineNumber el) ")")))
;;;;;;;;;;;;;;;;;;; end of redundantly copied from clojure.repl to avoid dep ;;;;;;;;;;;;;;
-
(defmacro with-bindings
"Executes body in the context of thread-local bindings for several vars
that often need to be set!: *ns* *warn-on-reflection* *math-context*
@@ -74,12 +85,14 @@
*print-meta* *print-meta*
*print-length* *print-length*
*print-level* *print-level*
+ *print-namespace-maps* true
*data-readers* *data-readers*
*default-data-reader-fn* *default-data-reader-fn*
*compile-path* (System/getProperty "clojure.compile.path" "classes")
*command-line-args* *command-line-args*
*unchecked-math* *unchecked-math*
*assert* *assert*
+ clojure.spec.alpha/*explain-out* clojure.spec.alpha/*explain-out*
*1 nil
*2 nil
*3 nil
@@ -123,6 +136,20 @@
(or (Character/isWhitespace (char c)) (= c (int \,))) (recur (.read s))
:else (do (.unread s c) :body))))
+(defn renumbering-read
+ "Reads from reader, which must be a LineNumberingPushbackReader, while capturing
+ the read string. If the read is successful, reset the line number and re-read.
+ The line number on re-read is the passed line-number unless :line or
+ :clojure.core/eval-file meta are explicitly set on the read value."
+ {:added "1.10"}
+ ([opts ^LineNumberingPushbackReader reader line-number]
+ (let [pre-line (.getLineNumber reader)
+ [pre-read s] (read+string opts reader)
+ {:keys [clojure.core/eval-file line]} (meta pre-read)
+ re-reader (doto (LineNumberingPushbackReader. (StringReader. s))
+ (.setLineNumber (if (and line (or eval-file (not= pre-line line))) line line-number)))]
+ (read opts re-reader))))
+
(defn repl-read
"Default :read hook for repl. Reads from *in* which must either be an
instance of LineNumberingPushbackReader or duplicate its behavior of both
@@ -137,7 +164,7 @@
[request-prompt request-exit]
(or ({:line-start request-prompt :stream-end request-exit}
(skip-whitespace *in*))
- (let [input (read)]
+ (let [input (renumbering-read {:read-cond :allow} *in* 1)]
(skip-if-eol *in*)
input)))
@@ -146,23 +173,190 @@
[throwable]
(root-cause throwable))
+(defn- file-name
+ "Helper to get just the file name part of a path or nil"
+ [^String full-path]
+ (when full-path
+ (try
+ (.getName (java.io.File. full-path))
+ (catch Throwable t))))
+
+(defn- file-path
+ "Helper to get the relative path to the source file or nil"
+ [^String full-path]
+ (when full-path
+ (try
+ (let [path (.getPath (java.io.File. full-path))
+ cd-path (str (.getAbsolutePath (java.io.File. "")) "/")]
+ (if (.startsWith path cd-path)
+ (subs path (count cd-path))
+ path))
+ (catch Throwable t
+ full-path))))
+
+(defn- java-loc->source
+ "Convert Java class name and method symbol to source symbol, either a
+ Clojure function or Java class and method."
+ [clazz method]
+ (if (#{'invoke 'invokeStatic} method)
+ (let [degen #(.replaceAll ^String % "--.*$" "")
+ [ns-name fn-name & nested] (->> (str clazz) (.split #"\$") (map demunge) (map degen))]
+ (symbol ns-name (String/join "$" ^"[Ljava.lang.String;" (into-array String (cons fn-name nested)))))
+ (symbol (name clazz) (name method))))
+
+(defn ex-triage
+ "Returns an analysis of the phase, error, cause, and location of an error that occurred
+ based on Throwable data, as returned by Throwable->map. All attributes other than phase
+ are optional:
+ :clojure.error/phase - keyword phase indicator, one of:
+ :read-source :compile-syntax-check :compilation :macro-syntax-check :macroexpansion
+ :execution :read-eval-result :print-eval-result
+ :clojure.error/source - file name (no path)
+ :clojure.error/path - source path
+ :clojure.error/line - integer line number
+ :clojure.error/column - integer column number
+ :clojure.error/symbol - symbol being expanded/compiled/invoked
+ :clojure.error/class - cause exception class symbol
+ :clojure.error/cause - cause exception message
+ :clojure.error/spec - explain-data for spec error"
+ {:added "1.10"}
+ [datafied-throwable]
+ (let [{:keys [via trace phase] :or {phase :execution}} datafied-throwable
+ {:keys [type message data]} (last via)
+ {:clojure.spec.alpha/keys [problems fn], :clojure.spec.test.alpha/keys [caller]} data
+ {:clojure.error/keys [source] :as top-data} (:data (first via))]
+ (assoc
+ (case phase
+ :read-source
+ (let [{:clojure.error/keys [line column]} data]
+ (cond-> (merge (-> via second :data) top-data)
+ source (assoc :clojure.error/source (file-name source)
+ :clojure.error/path (file-path source))
+ (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source :clojure.error/path)
+ message (assoc :clojure.error/cause message)))
+
+ (:compile-syntax-check :compilation :macro-syntax-check :macroexpansion)
+ (cond-> top-data
+ source (assoc :clojure.error/source (file-name source)
+ :clojure.error/path (file-path source))
+ (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source :clojure.error/path)
+ type (assoc :clojure.error/class type)
+ message (assoc :clojure.error/cause message)
+ problems (assoc :clojure.error/spec data))
+
+ (:read-eval-result :print-eval-result)
+ (let [[source method file line] (-> trace first)]
+ (cond-> top-data
+ line (assoc :clojure.error/line line)
+ file (assoc :clojure.error/source file)
+ (and source method) (assoc :clojure.error/symbol (java-loc->source source method))
+ type (assoc :clojure.error/class type)
+ message (assoc :clojure.error/cause message)))
+
+ :execution
+ (let [[source method file line] (->> trace (drop-while #(core-class? (name (first %)))) first)
+ file (first (remove #(or (nil? %) (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} %)) [(:file caller) file]))
+ err-line (or (:line caller) line)]
+ (cond-> {:clojure.error/class type}
+ err-line (assoc :clojure.error/line err-line)
+ message (assoc :clojure.error/cause message)
+ (or fn (and source method)) (assoc :clojure.error/symbol (or fn (java-loc->source source method)))
+ file (assoc :clojure.error/source file)
+ problems (assoc :clojure.error/spec data))))
+ :clojure.error/phase phase)))
+
+(defn ex-str
+ "Returns a string from exception data, as produced by ex-triage.
+ The first line summarizes the exception phase and location.
+ The subsequent lines describe the cause."
+ {:added "1.10"}
+ [{:clojure.error/keys [phase source path line column symbol class cause spec]
+ :as triage-data}]
+ (let [loc (str (or path source "REPL") ":" (or line 1) (if column (str ":" column) ""))
+ class-name (name (or class ""))
+ simple-class (if class (or (first (re-find #"([^.])++$" class-name)) class-name))
+ cause-type (if (contains? #{"Exception" "RuntimeException"} simple-class)
+ "" ;; omit, not useful
+ (str " (" simple-class ")"))]
+ (case phase
+ :read-source
+ (format "Syntax error reading source at (%s).%n%s%n" loc cause)
+
+ :macro-syntax-check
+ (format "Syntax error macroexpanding %sat (%s).%n%s"
+ (if symbol (str symbol " ") "")
+ loc
+ (if spec
+ (with-out-str
+ (spec/explain-out
+ (if (= spec/*explain-out* spec/explain-printer)
+ (update spec :clojure.spec.alpha/problems
+ (fn [probs] (map #(dissoc % :in) probs)))
+ spec)))
+ (format "%s%n" cause)))
+
+ :macroexpansion
+ (format "Unexpected error%s macroexpanding %sat (%s).%n%s%n"
+ cause-type
+ (if symbol (str symbol " ") "")
+ loc
+ cause)
+
+ :compile-syntax-check
+ (format "Syntax error%s compiling %sat (%s).%n%s%n"
+ cause-type
+ (if symbol (str symbol " ") "")
+ loc
+ cause)
+
+ :compilation
+ (format "Unexpected error%s compiling %sat (%s).%n%s%n"
+ cause-type
+ (if symbol (str symbol " ") "")
+ loc
+ cause)
+
+ :read-eval-result
+ (format "Error reading eval result%s at %s (%s).%n%s%n" cause-type symbol loc cause)
+
+ :print-eval-result
+ (format "Error printing return value%s at %s (%s).%n%s%n" cause-type symbol loc cause)
+
+ :execution
+ (if spec
+ (format "Execution error - invalid arguments to %s at (%s).%n%s"
+ symbol
+ loc
+ (with-out-str
+ (spec/explain-out
+ (if (= spec/*explain-out* spec/explain-printer)
+ (update spec :clojure.spec.alpha/problems
+ (fn [probs] (map #(dissoc % :in) probs)))
+ spec))))
+ (format "Execution error%s at %s(%s).%n%s%n"
+ cause-type
+ (if symbol (str symbol " ") "")
+ loc
+ cause)))))
+
+(defn err->msg
+ "Helper to return an error message string from an exception."
+ [^Throwable e]
+ (-> e Throwable->map ex-triage ex-str))
+
(defn repl-caught
"Default :caught hook for repl"
[e]
- (let [ex (repl-exception e)
- tr (.getStackTrace ex)
- el (when-not (zero? (count tr)) (aget tr 0))]
- (binding [*out* *err*]
- (println (str (-> ex class .getSimpleName)
- " " (.getMessage ex) " "
- (when-not (instance? clojure.lang.Compiler$CompilerException ex)
- (str " " (if el (stack-element-str el) "[trace missing]"))))))))
+ (binding [*out* *err*]
+ (print (err->msg e))
+ (flush)))
(def ^{:doc "A sequence of lib specs that are applied to `require`
by default when a new command-line REPL is started."} repl-requires
'[[clojure.repl :refer (source apropos dir pst doc find-doc)]
[clojure.java.javadoc :refer (javadoc)]
- [clojure.pprint :refer (pp pprint)]])
+ [clojure.pprint :refer (pp pprint)]
+ [clojure.repl.deps :refer (add-libs add-lib sync-deps)]])
(defmacro with-read-known
"Evaluates body with *read-eval* set to a \"known\" value,
@@ -235,35 +429,42 @@ by default when a new command-line REPL is started."} repl-requires
(fn []
(try
(let [read-eval *read-eval*
- input (with-read-known (read request-prompt request-exit))]
+ input (try
+ (with-read-known (read request-prompt request-exit))
+ (catch LispReader$ReaderException e
+ (throw (ex-info nil {:clojure.error/phase :read-source} e))))]
(or (#{request-prompt request-exit} input)
(let [value (binding [*read-eval* read-eval] (eval input))]
- (print value)
(set! *3 *2)
(set! *2 *1)
- (set! *1 value))))
+ (set! *1 value)
+ (try
+ (print value)
+ (catch Throwable e
+ (throw (ex-info nil {:clojure.error/phase :print-eval-result} e)))))))
(catch Throwable e
(caught e)
(set! *e e))))]
(with-bindings
- (try
- (init)
- (catch Throwable e
- (caught e)
- (set! *e e)))
- (prompt)
- (flush)
- (loop []
- (when-not
- (try (identical? (read-eval-print) request-exit)
- (catch Throwable e
- (caught e)
- (set! *e e)
- nil))
- (when (need-prompt)
- (prompt)
- (flush))
- (recur))))))
+ (binding [*repl* true]
+ (try
+ (init)
+ (catch Throwable e
+ (caught e)
+ (set! *e e)))
+ (prompt)
+ (flush)
+ (loop []
+ (when-not
+ (try (identical? (read-eval-print) request-exit)
+ (catch Throwable e
+ (caught e)
+ (set! *e e)
+ nil))
+ (when (need-prompt)
+ (prompt)
+ (flush))
+ (recur)))))))
(defn load-script
"Loads Clojure source from a file or resource given its path. Paths
@@ -381,6 +582,38 @@ java -cp clojure.jar clojure.main -i init.clj script.clj args...")
(let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
(null-opt args (map vector (repeat "-i") inits))))
+(defn report-error
+ "Create and output an exception report for a Throwable to target.
+
+ Options:
+ :target - \"file\" (default), \"stderr\", \"none\"
+
+ If file is specified but cannot be written, falls back to stderr."
+ [^Throwable t & {:keys [target]
+ :or {target "file"} :as opts}]
+ (when-not (= target "none")
+ (let [trace (Throwable->map t)
+ triage (ex-triage trace)
+ message (ex-str triage)
+ report (array-map
+ :clojure.main/message message
+ :clojure.main/triage triage
+ :clojure.main/trace trace)
+ report-str (with-out-str
+ (binding [*print-namespace-maps* false]
+ ((requiring-resolve 'clojure.pprint/pprint) report)))
+ err-path (when (= target "file")
+ (try
+ (let [f (.toFile (Files/createTempFile "clojure-" ".edn" (into-array FileAttribute [])))]
+ (with-open [w (BufferedWriter. (FileWriter. f))]
+ (binding [*out* w] (println report-str)))
+ (.getAbsolutePath f))
+ (catch Throwable _)))] ;; ignore, fallback to stderr
+ (binding [*out* *err*]
+ (if err-path
+ (println (str message (System/lineSeparator) "Full report at:" (System/lineSeparator) err-path))
+ (println (str report-str (System/lineSeparator) message)))))))
+
(defn main
"Usage: java -cp clojure.jar clojure.main [init-opt*] [main-opt] [arg*]
@@ -389,6 +622,8 @@ java -cp clojure.jar clojure.main -i init.clj script.clj args...")
init options:
-i, --init path Load a file or resource
-e, --eval string Evaluate expressions in string; print non-nil values
+ --report target Report uncaught exception to \"file\" (default), \"stderr\",
+ or \"none\", overrides System property clojure.main.report
main options:
-m, --main ns-name Call the -main function from a namespace with args
@@ -415,11 +650,27 @@ java -cp clojure.jar clojure.main -i init.clj script.clj args...")
[& args]
(try
(if args
- (loop [[opt arg & more :as args] args inits []]
- (if (init-dispatch opt)
- (recur more (conj inits [opt arg]))
- ((main-dispatch opt) args inits)))
- (repl-opt nil nil))
- (finally
+ (loop [[opt arg & more :as args] args, inits [], flags nil]
+ (cond
+ ;; flag
+ (contains? #{"--report"} opt)
+ (recur more inits (merge flags {(subs opt 2) arg}))
+
+ ;; init opt
+ (init-dispatch opt)
+ (recur more (conj inits [opt arg]) flags)
+
+ :main-opt
+ (try
+ ((main-dispatch opt) args inits)
+ (catch Throwable t
+ (report-error t :target (get flags "report" (System/getProperty "clojure.main.report" "file")))
+ (System/exit 1)))))
+ (try
+ (repl-opt nil nil)
+ (catch Throwable t
+ (report-error t :target "file")
+ (System/exit 1))))
+ (finally
(flush))))
diff --git a/src/clj/clojure/math.clj b/src/clj/clojure/math.clj
new file mode 100644
index 0000000000..c5b67b1f1c
--- /dev/null
+++ b/src/clj/clojure/math.clj
@@ -0,0 +1,523 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns
+ ^{:author "Alex Miller",
+ :doc "Clojure wrapper functions for java.lang.Math static methods.
+
+ Function calls are inlined for performance, and type hinted for primitive
+ long or double parameters where appropriate. In general, Math methods are
+ optimized for performance and have bounds for error tolerance. If
+ greater precision is needed, use java.lang.StrictMath directly instead.
+
+ For more complete information, see:
+ https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html"}
+ clojure.math)
+
+(set! *warn-on-reflection* true)
+
+(def
+ ^{:doc "Constant for e, the base for natural logarithms.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#E"
+ :added "1.11"
+ :const true
+ :tag 'double}
+ E
+ Math/E)
+
+(def
+ ^{:doc "Constant for pi, the ratio of the circumference of a circle to its diameter.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#PI"
+ :added "1.11"
+ :const true
+ :tag 'double}
+ PI
+ Math/PI)
+
+(defn sin
+ {:doc "Returns the sine of an angle.
+ If a is ##NaN, ##-Inf, ##Inf => ##NaN
+ If a is zero => zero with the same sign as a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#sin-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/sin (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/sin a))
+
+(defn cos
+ {:doc "Returns the cosine of an angle.
+ If a is ##NaN, ##-Inf, ##Inf => ##NaN
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#cos-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/cos (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/cos a))
+
+(defn tan
+ {:doc "Returns the tangent of an angle.
+ If a is ##NaN, ##-Inf, ##Inf => ##NaN
+ If a is zero => zero with the same sign as a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#tan-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/tan (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/tan a))
+
+(defn asin
+ {:doc "Returns the arc sine of an angle, in the range -pi/2 to pi/2.
+ If a is ##NaN or |a|>1 => ##NaN
+ If a is zero => zero with the same sign as a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#asin-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/asin (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/asin a))
+
+(defn acos
+ {:doc "Returns the arc cosine of a, in the range 0.0 to pi.
+ If a is ##NaN or |a|>1 => ##NaN
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#acos-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/acos (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/acos a))
+
+(defn atan
+ {:doc "Returns the arc tangent of a, in the range of -pi/2 to pi/2.
+ If a is ##NaN => ##NaN
+ If a is zero => zero with the same sign as a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#atan-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/atan (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/atan a))
+
+(defn to-radians
+ {:doc "Converts an angle in degrees to an approximate equivalent angle in radians.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#toRadians-double-"
+ :inline-arities #{1}
+ :inline (fn [deg] `(Math/toRadians (double ~deg)))
+ :added "1.11"}
+ ^double [^double deg]
+ (Math/toRadians deg))
+
+(defn to-degrees
+ {:doc "Converts an angle in radians to an approximate equivalent angle in degrees.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#toDegrees-double-"
+ :inline-arities #{1}
+ :inline (fn [r] `(Math/toDegrees (double ~r)))
+ :added "1.11"}
+ ^double [^double r]
+ (Math/toDegrees r))
+
+(defn exp
+ {:doc "Returns Euler's number e raised to the power of a.
+ If a is ##NaN => ##NaN
+ If a is ##Inf => ##Inf
+ If a is ##-Inf => +0.0
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#exp-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/exp (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/exp a))
+
+(defn log
+ {:doc "Returns the natural logarithm (base e) of a.
+ If a is ##NaN or negative => ##NaN
+ If a is ##Inf => ##Inf
+ If a is zero => ##-Inf
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#log-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/log (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/log a))
+
+(defn log10
+ {:doc "Returns the logarithm (base 10) of a.
+ If a is ##NaN or negative => ##NaN
+ If a is ##Inf => ##Inf
+ If a is zero => ##-Inf
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#log10-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/log10 (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/log10 a))
+
+(defn sqrt
+ {:doc "Returns the positive square root of a.
+ If a is ##NaN or negative => ##NaN
+ If a is ##Inf => ##Inf
+ If a is zero => a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#sqrt-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/sqrt (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/sqrt a))
+
+(defn cbrt
+ {:doc "Returns the cube root of a.
+ If a is ##NaN => ##NaN
+ If a is ##Inf or ##-Inf => a
+ If a is zero => zero with sign matching a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#cbrt-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/cbrt (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/cbrt a))
+
+(defn IEEE-remainder
+ {:doc "Returns the remainder per IEEE 754 such that
+ remainder = dividend - divisor * n
+ where n is the integer closest to the exact value of dividend / divisor.
+ If two integers are equally close, then n is the even one.
+ If the remainder is zero, sign will match dividend.
+ If dividend or divisor is ##NaN, or dividend is ##Inf or ##-Inf, or divisor is zero => ##NaN
+ If dividend is finite and divisor is infinite => dividend
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#IEEEremainder-double-double-"
+ :inline-arities #{2}
+ :inline (fn [dividend divisor] `(Math/IEEEremainder (double ~dividend) (double ~divisor)))
+ :added "1.11"}
+ ^double [^double dividend ^double divisor]
+ (Math/IEEEremainder dividend divisor))
+
+(defn ceil
+ {:doc "Returns the smallest double greater than or equal to a, and equal to a
+ mathematical integer.
+ If a is ##NaN or ##Inf or ##-Inf or already equal to an integer => a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#ceil-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/ceil (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/ceil a))
+
+(defn floor
+ {:doc "Returns the largest double less than or equal to a, and equal to a
+ mathematical integer.
+ If a is ##NaN or ##Inf or ##-Inf or already equal to an integer => a
+ If a is less than zero but greater than -1.0 => -0.0
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floor-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/floor (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/floor a))
+
+(defn rint
+ {:doc "Returns the double closest to a and equal to a mathematical integer.
+ If two values are equally close, return the even one.
+ If a is ##NaN or ##Inf or ##-Inf or zero => a
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#rint-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/rint (double ~a)))
+ :added "1.11"}
+ ^double [^double a]
+ (Math/rint a))
+
+(defn atan2
+ {:doc "Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta).
+ Computes the phase theta by computing an arc tangent of y/x in the range of -pi to pi.
+ For more details on special cases, see:
+ https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#atan2-double-double-"
+ :inline-arities #{2}
+ :inline (fn [y x] `(Math/atan2 (double ~y) (double ~x)))
+ :added "1.11"}
+ ^double [^double y ^double x]
+ (Math/atan2 y x))
+
+(defn pow
+ {:doc "Returns the value of a raised to the power of b.
+ For more details on special cases, see:
+ https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#pow-double-double-"
+ :inline-arities #{2}
+ :inline (fn [a b] `(Math/pow (double ~a) (double ~b)))
+ :added "1.11"}
+ ^double [^double a ^double b]
+ (Math/pow a b))
+
+(defn round
+ {:doc "Returns the closest long to a. If equally close to two values, return the one
+ closer to ##Inf.
+ If a is ##NaN => 0
+ If a is ##-Inf or < Long/MIN_VALUE => Long/MIN_VALUE
+ If a is ##Inf or > Long/MAX_VALUE => Long/MAX_VALUE
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#round-double-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/round (double ~a)))
+ :added "1.11"}
+ ^long [^double a]
+ (Math/round a))
+
+(defn random
+ {:doc "Returns a positive double between 0.0 and 1.0, chosen pseudorandomly with
+ approximately random distribution.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#random--"
+ :inline-arities #{0}
+ :inline (fn [] `(Math/random))
+ :added "1.11"}
+ ^double []
+ (Math/random))
+
+(defn add-exact
+ {:doc "Returns the sum of x and y, throws ArithmeticException on overflow.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#addExact-long-long-"
+ :inline-arities #{2}
+ :inline (fn [x y] `(Math/addExact (long ~x) (long ~y)))
+ :added "1.11"}
+ ^long [^long x ^long y]
+ (Math/addExact x y))
+
+(defn subtract-exact
+ {:doc "Returns the difference of x and y, throws ArithmeticException on overflow.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#subtractExact-long-long-"
+ :inline-arities #{2}
+ :inline (fn [x y] `(Math/subtractExact (long ~x) (long ~y)))
+ :added "1.11"}
+ ^long [^long x ^long y]
+ (Math/subtractExact x y))
+
+(defn multiply-exact
+ {:doc "Returns the product of x and y, throws ArithmeticException on overflow.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#multiplyExact-long-long-"
+ :inline-arities #{2}
+ :inline (fn [x y] `(Math/multiplyExact (long ~x) (long ~y)))
+ :added "1.11"}
+ ^long [^long x ^long y]
+ (Math/multiplyExact x y))
+
+(defn increment-exact
+ {:doc "Returns a incremented by 1, throws ArithmeticException on overflow.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#incrementExact-long-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/incrementExact (long ~a)))
+ :added "1.11"}
+ ^long [^long a]
+ (Math/incrementExact a))
+
+(defn decrement-exact
+ {:doc "Returns a decremented by 1, throws ArithmeticException on overflow.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#decrementExact-long-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/decrementExact (long ~a)))
+ :added "1.11"}
+ ^long [^long a]
+ (Math/decrementExact a))
+
+(defn negate-exact
+ {:doc "Returns the negation of a, throws ArithmeticException on overflow.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#negateExact-long-"
+ :inline-arities #{1}
+ :inline (fn [a] `(Math/negateExact (long ~a)))
+ :added "1.11"}
+ ^long [^long a]
+ (Math/negateExact a))
+
+(defn floor-div
+ {:doc "Integer division that rounds to negative infinity (as opposed to zero).
+ The special case (floorDiv Long/MIN_VALUE -1) overflows and returns Long/MIN_VALUE.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floorDiv-long-long-"
+ :inline-arities #{2}
+ :inline (fn [x y] `(Math/floorDiv (long ~x) (long ~y)))
+ :added "1.11"}
+ ^long [^long x ^long y]
+ (Math/floorDiv x y))
+
+(defn floor-mod
+ {:doc "Integer modulus x - (floorDiv(x, y) * y). Sign matches y and is in the
+ range -|y| < r < |y|.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floorMod-long-long-"
+ :inline-arities #{2}
+ :inline (fn [x y] `(Math/floorMod (long ~x) (long ~y)))
+ :added "1.11"}
+ ^long [^long x ^long y]
+ (Math/floorMod x y))
+
+(defn ulp
+ {:doc "Returns the size of an ulp (unit in last place) for d.
+ If d is ##NaN => ##NaN
+ If d is ##Inf or ##-Inf => ##Inf
+ If d is zero => Double/MIN_VALUE
+ If d is +/- Double/MAX_VALUE => 2^971
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#ulp-double-"
+ :inline-arities #{1}
+ :inline (fn [d] `(Math/ulp (double ~d)))
+ :added "1.11"}
+ ^double [^double d]
+ (Math/ulp d))
+
+(defn signum
+ {:doc "Returns the signum function of d - zero for zero, 1.0 if >0, -1.0 if <0.
+ If d is ##NaN => ##NaN
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#signum-double-"
+ :inline-arities #{1}
+ :inline (fn [d] `(Math/signum (double ~d)))
+ :added "1.11"}
+ ^double [^double d]
+ (Math/signum d))
+
+(defn sinh
+ {:doc "Returns the hyperbolic sine of x, (e^x - e^-x)/2.
+ If x is ##NaN => ##NaN
+ If x is ##Inf or ##-Inf or zero => x
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#sinh-double-"
+ :inline-arities #{1}
+ :inline (fn [x] `(Math/sinh (double ~x)))
+ :added "1.11"}
+ ^double [^double x]
+ (Math/sinh x))
+
+(defn cosh
+ {:doc "Returns the hyperbolic cosine of x, (e^x + e^-x)/2.
+ If x is ##NaN => ##NaN
+ If x is ##Inf or ##-Inf => ##Inf
+ If x is zero => 1.0
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#cosh-double-"
+ :inline-arities #{1}
+ :inline (fn [x] `(Math/cosh (double ~x)))
+ :added "1.11"}
+ ^double [^double x]
+ (Math/cosh x))
+
+(defn tanh
+ {:doc "Returns the hyperbolic tangent of x, sinh(x)/cosh(x).
+ If x is ##NaN => ##NaN
+ If x is zero => zero, with same sign
+ If x is ##Inf => +1.0
+ If x is ##-Inf => -1.0
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#tanh-double-"
+ :inline-arities #{1}
+ :inline (fn [x] `(Math/tanh (double ~x)))
+ :added "1.11"}
+ ^double [^double x]
+ (Math/tanh x))
+
+(defn hypot
+ {:doc "Returns sqrt(x^2 + y^2) without intermediate underflow or overflow.
+ If x or y is ##Inf or ##-Inf => ##Inf
+ If x or y is ##NaN and neither is ##Inf or ##-Inf => ##NaN
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#hypot-double-double-"
+ :inline-arities #{2}
+ :inline (fn [x y] `(Math/hypot (double ~x) (double ~y)))
+ :added "1.11"}
+ ^double [^double x ^double y]
+ (Math/hypot x y))
+
+(defn expm1
+ {:doc "Returns e^x - 1. Near 0, expm1(x)+1 is more accurate to e^x than exp(x).
+ If x is ##NaN => ##NaN
+ If x is ##Inf => #Inf
+ If x is ##-Inf => -1.0
+ If x is zero => x
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#expm1-double-"
+ :inline-arities #{1}
+ :inline (fn [x] `(Math/expm1 (double ~x)))
+ :added "1.11"}
+ ^double [^double x]
+ (Math/expm1 x))
+
+(defn log1p
+ {:doc "Returns ln(1+x). For small values of x, log1p(x) is more accurate than
+ log(1.0+x).
+ If x is ##NaN or < -1 => ##NaN
+ If x is ##Inf => ##Inf
+ If x is -1 => ##-Inf
+ If x is 0 => 0 with sign matching x
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#log1p-double-"
+ :inline-arities #{1}
+ :inline (fn [x] `(Math/log1p (double ~x)))
+ :added "1.11"}
+ ^double [^double x]
+ (Math/log1p x))
+
+(defn copy-sign
+ {:doc "Returns a double with the magnitude of the first argument and the sign of
+ the second.
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#copySign-double-double-"
+ :inline-arities #{2}
+ :inline (fn [magnitude sign] `(Math/copySign (double ~magnitude) (double ~sign)))
+ :added "1.11"}
+ ^double [^double magnitude ^double sign]
+ (Math/copySign magnitude sign))
+
+(defn get-exponent
+ {:doc "Returns the exponent of d.
+ If d is ##NaN, ##Inf, ##-Inf => Double/MAX_EXPONENT + 1
+ If d is zero or subnormal => Double/MIN_EXPONENT - 1
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#getExponent-double-"
+ :inline-arities #{1}
+ :inline (fn [d] `(Math/getExponent (double ~d)))
+ :added "1.11"}
+ [^double d]
+ (Math/getExponent d))
+
+(defn next-after
+ {:doc "Returns the adjacent floating point number to start in the direction of
+ the second argument. If the arguments are equal, the second is returned.
+ If either arg is #NaN => #NaN
+ If both arguments are signed zeros => direction
+ If start is +-Double/MIN_VALUE and direction would cause a smaller magnitude
+ => zero with sign matching start
+ If start is ##Inf or ##-Inf and direction would cause a smaller magnitude
+ => Double/MAX_VALUE with same sign as start
+ If start is equal to +=Double/MAX_VALUE and direction would cause a larger magnitude
+ => ##Inf or ##-Inf with sign matching start
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextAfter-double-double-"
+ :inline-arities #{2}
+ :inline (fn [start direction] `(Math/nextAfter (double ~start) (double ~direction)))
+ :added "1.11"}
+ ^double [^double start ^double direction]
+ (Math/nextAfter start direction))
+
+(defn next-up
+ {:doc "Returns the adjacent double of d in the direction of ##Inf.
+ If d is ##NaN => ##NaN
+ If d is ##Inf => ##Inf
+ If d is zero => Double/MIN_VALUE
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextUp-double-"
+ :inline-arities #{1}
+ :inline (fn [d] `(Math/nextUp (double ~d)))
+ :added "1.11"}
+ ^double [^double d]
+ (Math/nextUp d))
+
+(defn next-down
+ {:doc "Returns the adjacent double of d in the direction of ##-Inf.
+ If d is ##NaN => ##NaN
+ If d is ##-Inf => ##-Inf
+ If d is zero => -Double/MIN_VALUE
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextDown-double-"
+ :inline-arities #{1}
+ :inline (fn [d] `(Math/nextDown (double ~d)))
+ :added "1.11"}
+ ^double [^double d]
+ (Math/nextDown d))
+
+(defn scalb
+ {:doc "Returns d * 2^scaleFactor, scaling by a factor of 2. If the exponent
+ is between Double/MIN_EXPONENT and Double/MAX_EXPONENT, the answer is exact.
+ If d is ##NaN => ##NaN
+ If d is ##Inf or ##-Inf => ##Inf or ##-Inf respectively
+ If d is zero => zero of same sign as d
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextDown-double-"
+ :inline-arities #{2}
+ :inline (fn [d scaleFactor] `(Math/scalb (double ~d) (int ~scaleFactor)))
+ :added "1.11"}
+ ^double [^double d scaleFactor]
+ (Math/scalb d (int scaleFactor)))
+
diff --git a/src/clj/clojure/pprint.clj b/src/clj/clojure/pprint.clj
index ba90aa6588..42836ac854 100644
--- a/src/clj/clojure/pprint.clj
+++ b/src/clj/clojure/pprint.clj
@@ -32,12 +32,13 @@ cl-format, it supports very concise custom dispatch. It also provides
a more powerful alternative to Clojure's standard format function.
See documentation for pprint and cl-format for more information or
-complete documentation on the the clojure web site on github.",
+complete documentation on the Clojure web site on GitHub.",
:added "1.2"}
clojure.pprint
(:refer-clojure :exclude (deftype))
(:use [clojure.walk :only [walk]]))
+(set! *warn-on-reflection* true)
(load "pprint/utilities")
(load "pprint/column_writer")
diff --git a/src/clj/clojure/pprint/cl_format.clj b/src/clj/clojure/pprint/cl_format.clj
index a8308c0424..1399e3ad31 100644
--- a/src/clj/clojure/pprint/cl_format.clj
+++ b/src/clj/clojure/pprint/cl_format.clj
@@ -111,7 +111,7 @@ http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm
(defn- absolute-reposition [navigator position]
(if (>= position (:pos navigator))
- (relative-reposition navigator (- (:pos navigator) position))
+ (relative-reposition navigator (- position (:pos navigator)))
(struct arg-navigator (:seq navigator) (drop position (:seq navigator)) position)))
(defn- relative-reposition [navigator position]
@@ -174,9 +174,9 @@ http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm
(opt-base-str *print-base* n)))
(ratio? n) (str
(if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))
- (opt-base-str *print-base* (.numerator n))
+ (opt-base-str *print-base* (.numerator ^clojure.lang.Ratio n))
"/"
- (opt-base-str *print-base* (.denominator n)))
+ (opt-base-str *print-base* (.denominator ^clojure.lang.Ratio n)))
:else nil))
(defn- format-ascii [print-func params arg-navigator offsets]
@@ -681,7 +681,7 @@ string, or one character longer."
append-zero (and (not d) (<= (dec (count mantissa)) scaled-exp))
[rounded-mantissa scaled-exp expanded] (round-str mantissa scaled-exp
d (if w (- w (if add-sign 1 0))))
- fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)
+ ^String fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)
fixed-repr (if (and w d
(>= d 1)
(= (.charAt fixed-repr 0) \0)
@@ -1471,14 +1471,15 @@ not a pretty writer (which keeps track of columns), this function always outputs
#(absolute-tabulation %1 %2 %3)))
(\*
- [ :n [1 Integer] ]
+ [ :n [nil Integer] ]
#{ :colon :at } {}
- (fn [params navigator offsets]
- (let [n (:n params)]
- (if (:at params)
- (absolute-reposition navigator n)
- (relative-reposition navigator (if (:colon params) (- n) n)))
- )))
+ (if (:at params)
+ (fn [params navigator offsets]
+ (let [n (or (:n params) 0)] ; ~@* has a default n = 0
+ (absolute-reposition navigator n)))
+ (fn [params navigator offsets]
+ (let [n (or (:n params) 1)] ; whereas ~* and ~:* have a default n = 1
+ (relative-reposition navigator (if (:colon params) (- n) n))))))
(\?
[ ]
@@ -1648,7 +1649,7 @@ not a pretty writer (which keeps track of columns), this function always outputs
(and (= (.length p) 1) (contains? #{\v \V} (nth p 0))) :parameter-from-args
(and (= (.length p) 1) (= \# (nth p 0))) :remaining-arg-count
(and (= (.length p) 2) (= \' (nth p 0))) (nth p 1)
- true (new Integer p))
+ true (Integer/parseInt p))
offset])
(def ^{:private true}
diff --git a/src/clj/clojure/pprint/column_writer.clj b/src/clj/clojure/pprint/column_writer.clj
index 7e754455d0..704fc0c363 100644
--- a/src/clj/clojure/pprint/column_writer.clj
+++ b/src/clj/clojure/pprint/column_writer.clj
@@ -54,7 +54,7 @@
(defn- column-writer
([writer] (column-writer writer *default-page-width*))
- ([writer max-columns]
+ ([^Writer writer max-columns]
(let [fields (ref {:max max-columns, :cur 0, :line 0 :base writer})]
(proxy [Writer IDeref] []
(deref [] fields)
diff --git a/src/clj/clojure/pprint/dispatch.clj b/src/clj/clojure/pprint/dispatch.clj
index 105a89dcef..965c0b255a 100644
--- a/src/clj/clojure/pprint/dispatch.clj
+++ b/src/clj/clojure/pprint/dispatch.clj
@@ -19,7 +19,7 @@
(defn- use-method
"Installs a function as a new method of multimethod associated with dispatch-value. "
- [multifn dispatch-val func]
+ [^clojure.lang.MultiFn multifn dispatch-val func]
(. multifn addMethod dispatch-val func))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -62,8 +62,19 @@
;;; are a little easier on the stack. (Or, do "real" compilation, a
;;; la Common Lisp)
+(declare pprint-map)
+
+(defn- pprint-meta [obj]
+ (when *print-meta*
+ (when-let [m (meta obj)]
+ (.write ^java.io.Writer *out* "^")
+ (pprint-map m)
+ (.write ^java.io.Writer *out* " ")
+ (pprint-newline :linear))))
+
;;; (def pprint-simple-list (formatter-out "~:<~@{~w~^ ~_~}~:>"))
(defn- pprint-simple-list [alis]
+ (pprint-meta alis)
(pprint-logical-block :prefix "(" :suffix ")"
(print-length-loop [alis (seq alis)]
(when alis
@@ -79,6 +90,7 @@
;;; (def pprint-vector (formatter-out "~<[~;~@{~w~^ ~_~}~;]~:>"))
(defn- pprint-vector [avec]
+ (pprint-meta avec)
(pprint-logical-block :prefix "[" :suffix "]"
(print-length-loop [aseq (seq avec)]
(when aseq
@@ -92,22 +104,37 @@
;;; (def pprint-map (formatter-out "~<{~;~@{~<~w~^ ~_~w~:>~^, ~_~}~;}~:>"))
(defn- pprint-map [amap]
- (pprint-logical-block :prefix "{" :suffix "}"
- (print-length-loop [aseq (seq amap)]
+ (pprint-meta amap)
+ (let [[ns lift-map] (when (not (record? amap))
+ (#'clojure.core/lift-ns amap))
+ amap (or lift-map amap)
+ prefix (if ns (str "#:" ns "{") "{")]
+ (pprint-logical-block :prefix prefix :suffix "}"
+ (print-length-loop [aseq (seq amap)]
+ (when aseq
+ (pprint-logical-block
+ (write-out (ffirst aseq))
+ (.write ^java.io.Writer *out* " ")
+ (pprint-newline :linear)
+ (set! *current-length* 0) ; always print both parts of the [k v] pair
+ (write-out (fnext (first aseq))))
+ (when (next aseq)
+ (.write ^java.io.Writer *out* ", ")
+ (pprint-newline :linear)
+ (recur (next aseq))))))))
+
+;;; (def ^{:private true} pprint-set (formatter-out "~<#{~;~@{~w~^ ~:_~}~;}~:>"))
+(defn- pprint-set [aset]
+ (pprint-meta aset)
+ (pprint-logical-block :prefix "#{" :suffix "}"
+ (print-length-loop [aseq (seq aset)]
(when aseq
- (pprint-logical-block
- (write-out (ffirst aseq))
- (.write ^java.io.Writer *out* " ")
- (pprint-newline :linear)
- (set! *current-length* 0) ; always print both parts of the [k v] pair
- (write-out (fnext (first aseq))))
+ (write-out (first aseq))
(when (next aseq)
- (.write ^java.io.Writer *out* ", ")
+ (.write ^java.io.Writer *out* " ")
(pprint-newline :linear)
(recur (next aseq)))))))
-(def ^{:private true} pprint-set (formatter-out "~<#{~;~@{~w~^ ~:_~}~;}~:>"))
-
(def ^{:private true}
type-map {"core$future_call" "Future",
"core$promise" "Promise"})
@@ -132,7 +159,7 @@
(pprint-newline :linear)
(write-out (cond
(and (future? o) (not (future-done? o))) :pending
- (and (instance? clojure.lang.IPending o) (not (.isRealized o))) :not-delivered
+ (and (instance? clojure.lang.IPending o) (not (.isRealized ^clojure.lang.IPending o))) :not-delivered
:else @o)))))
(def ^{:private true} pprint-pqueue (formatter-out "~<<-(~;~@{~w~^ ~_~}~;)-<~:>"))
@@ -155,6 +182,7 @@
(use-method simple-dispatch clojure.lang.IPersistentMap pprint-map)
(use-method simple-dispatch clojure.lang.IPersistentSet pprint-set)
(use-method simple-dispatch clojure.lang.PersistentQueue pprint-pqueue)
+(use-method simple-dispatch clojure.lang.Var pprint-simple-default)
(use-method simple-dispatch clojure.lang.IDeref pprint-ideref)
(use-method simple-dispatch nil pr)
(use-method simple-dispatch :default pprint-simple-default)
@@ -210,7 +238,7 @@
(when (next args)
((formatter-out "~:_"))))))
(recur (next args))))))
- (write-out reference)))
+ (when reference (write-out reference))))
(defn- pprint-ns
"The pretty print dispatch chunk for the ns macro"
diff --git a/src/clj/clojure/pprint/pprint_base.clj b/src/clj/clojure/pprint/pprint_base.clj
index 2d450d8ed7..d95bd2613d 100644
--- a/src/clj/clojure/pprint/pprint_base.clj
+++ b/src/clj/clojure/pprint/pprint_base.clj
@@ -164,7 +164,7 @@ radix specifier is in the form #XXr where XX is the decimal value of *print-base
(make-pretty-writer base-writer# *print-right-margin* *print-miser-width*)
base-writer#)]
~@body
- (.ppflush *out*))))
+ (.ppflush ^PrettyFlush *out*))))
;;;TODO: if pretty print is not set, don't use pr but rather something that respects *print-base*, etc.
diff --git a/src/clj/clojure/pprint/pretty_writer.clj b/src/clj/clojure/pprint/pretty_writer.clj
index c31142c691..49023b8230 100644
--- a/src/clj/clojure/pprint/pretty_writer.clj
+++ b/src/clj/clojure/pprint/pretty_writer.clj
@@ -36,13 +36,14 @@
(defmacro ^{:private true}
getf
- "Get the value of the field a named by the argument (which should be a keyword)."
+ "Get the value of the field named by the argument (which should be a keyword)."
[sym]
`(~sym @@~'this))
-(defmacro ^{:private true}
- setf [sym new-val]
+(defmacro ^{:private true}
+ setf
"Set the value of the field SYM to NEW-VAL"
+ [sym new-val]
`(alter @~'this assoc ~sym ~new-val))
(defmacro ^{:private true}
@@ -55,6 +56,14 @@
[& vals#] (apply struct ~type-name ~(keyword name-str) vals#))
(defn- ~(symbol (str name-str "?")) [x#] (= (:type-tag x#) ~(keyword name-str))))))
+(defmacro ^{:private true}
+ write-to-base
+ "Call .write on Writer (getf :base) with proper type-hinting to
+ avoid reflection."
+ [& args]
+ `(let [^Writer w# (getf :base)]
+ (.write w# ~@args)))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The data structures used by pretty-writer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -106,7 +115,7 @@
(let [lb (:logical-block token)]
(dosync
(when-let [^String prefix (:prefix lb)]
- (.write (getf :base) prefix))
+ (write-to-base prefix))
(let [col (get-column (getf :base))]
(ref-set (:start-col lb) col)
(ref-set (:indent lb) col)))))
@@ -114,7 +123,7 @@
(defmethod write-token :end-block-t [^Writer this token]
(when-let [cb (getf :logical-block-callback)] (cb :end))
(when-let [^String suffix (:suffix (:logical-block token))]
- (.write (getf :base) suffix)))
+ (write-to-base suffix)))
(defmethod write-token :indent-t [^Writer this token]
(let [lb (:logical-block token)]
@@ -125,7 +134,7 @@
:current (get-column (getf :base)))))))
(defmethod write-token :buffer-blob [^Writer this token]
- (.write (getf :base) ^String (:data token)))
+ (write-to-base ^String (:data token)))
(defmethod write-token :nl-t [^Writer this token]
; (prlabel wt @(:done-nl (:logical-block token)))
@@ -135,19 +144,19 @@
@(:done-nl (:logical-block token))))
(emit-nl this token)
(if-let [^String tws (getf :trailing-white-space)]
- (.write (getf :base) tws)))
+ (write-to-base tws)))
(dosync (setf :trailing-white-space nil)))
(defn- write-tokens [^Writer this tokens force-trailing-whitespace]
(doseq [token tokens]
(if-not (= (:type-tag token) :nl-t)
(if-let [^String tws (getf :trailing-white-space)]
- (.write (getf :base) tws)))
+ (write-to-base tws)))
(write-token this token)
(setf :trailing-white-space (:trailing-white-space token)))
(let [^String tws (getf :trailing-white-space)]
(when (and force-trailing-whitespace tws)
- (.write (getf :base) tws)
+ (write-to-base tws)
(setf :trailing-white-space nil))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -225,15 +234,15 @@
(recur (:parent lb)))))))
(defn- emit-nl [^Writer this nl]
- (.write (getf :base) (pp-newline))
+ (write-to-base ^String (pp-newline))
(dosync (setf :trailing-white-space nil))
(let [lb (:logical-block nl)
^String prefix (:per-line-prefix lb)]
(if prefix
- (.write (getf :base) prefix))
+ (write-to-base prefix))
(let [^String istr (apply str (repeat (- @(:indent lb) (count prefix))
\space))]
- (.write (getf :base) istr))
+ (write-to-base istr))
(update-nl-state lb)))
(defn- split-at-newline [tokens]
@@ -315,7 +324,7 @@
(defn- write-white-space [^Writer this]
(when-let [^String tws (getf :trailing-white-space)]
; (prlabel wws (str "*" tws "*"))
- (.write (getf :base) tws)
+ (write-to-base tws)
(dosync
(setf :trailing-white-space nil))))
@@ -337,13 +346,13 @@
(write-buffered-output this))
(do
(write-white-space this)
- (.write (getf :base) l)))
- (.write (getf :base) (int \newline))
+ (write-to-base l)))
+ (write-to-base (int \newline))
(doseq [^String l (next (butlast lines))]
- (.write (getf :base) l)
- (.write (getf :base) (pp-newline))
+ (write-to-base l)
+ (write-to-base ^String (pp-newline))
(if prefix
- (.write (getf :base) prefix)))
+ (write-to-base prefix)))
(setf :buffering :writing)
(last lines))))))
@@ -352,7 +361,7 @@
(if (= (getf :mode) :writing)
(do
(write-white-space this)
- (.write (getf :base) c))
+ (write-to-base c))
(if (= c \newline)
(write-initial-lines this "\n")
(let [oldpos (getf :pos)
@@ -396,7 +405,7 @@
(if (= mode :writing)
(do
(write-white-space this)
- (.write (getf :base) s)
+ (write-to-base s)
(setf :trailing-white-space white-space))
(let [oldpos (getf :pos)
newpos (+ oldpos (count s0))]
@@ -406,7 +415,9 @@
Integer
(p-write-char this x)
Long
- (p-write-char this x))))
+ (p-write-char this x)))
+ ([x off len]
+ (.write ^Writer this (subs (str x) off (+ off len)))))
(ppflush []
(if (= (getf :mode) :buffering)
@@ -416,11 +427,12 @@
(write-white-space this)))
(flush []
- (.ppflush this)
- (.flush (getf :base)))
+ (.ppflush ^PrettyFlush this)
+ (let [^Writer w (getf :base)]
+ (.flush w)))
(close []
- (.flush this)))))
+ (.flush ^Writer this)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -440,7 +452,7 @@
(write-white-space this)
(when-let [cb (getf :logical-block-callback)] (cb :start))
(if prefix
- (.write (getf :base) prefix))
+ (write-to-base prefix))
(let [col (get-column (getf :base))]
(ref-set (:start-col lb) col)
(ref-set (:indent lb) col)))
@@ -457,7 +469,7 @@
(do
(write-white-space this)
(if suffix
- (.write (getf :base) suffix))
+ (write-to-base suffix))
(when-let [cb (getf :logical-block-callback)] (cb :end)))
(let [oldpos (getf :pos)
newpos (+ oldpos (if suffix (count suffix) 0))]
diff --git a/src/clj/clojure/pprint/utilities.clj b/src/clj/clojure/pprint/utilities.clj
index 53c4e97383..95655bd664 100644
--- a/src/clj/clojure/pprint/utilities.clj
+++ b/src/clj/clojure/pprint/utilities.clj
@@ -50,19 +50,22 @@
[acc context]
(recur new-context (conj acc result))))))
-(defn- unzip-map [m]
- "Take a map that has pairs in the value slots and produce a pair of maps,
- the first having all the first elements of the pairs and the second all
- the second elements of the pairs"
+(defn- unzip-map
+ "Take a map that has pairs in the value slots and produce a pair of
+ maps, the first having all the first elements of the pairs and the
+ second all the second elements of the pairs"
+ [m]
[(into {} (for [[k [v1 v2]] m] [k v1]))
(into {} (for [[k [v1 v2]] m] [k v2]))])
-(defn- tuple-map [m v1]
+(defn- tuple-map
"For all the values, v, in the map, replace them with [v v1]"
+ [m v1]
(into {} (for [[k v] m] [k [v v1]])))
-(defn- rtrim [s c]
+(defn- rtrim
"Trim all instances of c from the end of sequence s"
+ [s c]
(let [len (count s)]
(if (and (pos? len) (= (nth s (dec (count s))) c))
(loop [n (dec len)]
@@ -72,8 +75,9 @@
true (recur (dec n))))
s)))
-(defn- ltrim [s c]
+(defn- ltrim
"Trim all instances of c from the beginning of sequence s"
+ [s c]
(let [len (count s)]
(if (and (pos? len) (= (nth s 0) c))
(loop [n 0]
@@ -82,24 +86,27 @@
(recur (inc n))))
s)))
-(defn- prefix-count [aseq val]
- "Return the number of times that val occurs at the start of sequence aseq,
-if val is a seq itself, count the number of times any element of val occurs at the
-beginning of aseq"
+(defn- prefix-count
+ "Return the number of times that val occurs at the start of sequence aseq,
+ if val is a seq itself, count the number of times any element of val
+ occurs at the beginning of aseq"
+ [aseq val]
(let [test (if (coll? val) (set val) #{val})]
(loop [pos 0]
(if (or (= pos (count aseq)) (not (test (nth aseq pos))))
pos
(recur (inc pos))))))
-(defn- prerr [& args]
+(defn- prerr
"Println to *err*"
+ [& args]
(binding [*out* *err*]
(apply println args)))
-
-(defmacro ^{:private true} prlabel [prefix arg & more-args]
+
+(defmacro ^{:private true} prlabel
"Print args to *err* in name = value format"
- `(prerr ~@(cons (list 'quote prefix) (mapcat #(list (list 'quote %) "=" %)
+ [prefix arg & more-args]
+ `(prerr ~@(cons (list 'quote prefix) (mapcat #(list (list 'quote %) "=" %)
(cons arg (seq more-args))))))
;; Flush the pretty-print buffer without flushing the underlying stream
diff --git a/src/clj/clojure/reflect/java.clj b/src/clj/clojure/reflect/java.clj
index b66c7f633e..0cdfe5814c 100644
--- a/src/clj/clojure/reflect/java.clj
+++ b/src/clj/clojure/reflect/java.clj
@@ -9,12 +9,15 @@
;; Java-specific parts of clojure.reflect
(in-ns 'clojure.reflect)
-(require '[clojure.set :as set]
+(require '[clojure.datafy :refer (datafy)]
+ '[clojure.set :as set]
'[clojure.string :as str])
(import '[clojure.asm ClassReader ClassVisitor Type Opcodes]
'[java.lang.reflect Modifier]
java.io.InputStream)
+(set! *warn-on-reflection* true)
+
(extend-protocol TypeReference
clojure.lang.Symbol
(typename [s] (str/replace (str s) "<>" "[]"))
@@ -34,9 +37,12 @@
"Given a typeref, create a legal Clojure symbol version of the
type's name."
[t]
- (-> (typename t)
- (str/replace "[]" "<>")
- (symbol)))
+ (cond->
+ (-> (typename t)
+ (str/replace "[]" "<>")
+ (symbol))
+ (class? t) (with-meta {'clojure.core.protocols/datafy
+ (fn [_] (datafy t))})))
(defn- resource-name
"Given a typeref, return implied resource name. Used by Reflectors
@@ -163,10 +169,16 @@ the kinds of objects to which they can apply."}
field->map
(.getDeclaredFields cls))))
+(defn- typeref->class
+ ^Class [typeref classloader]
+ (if (class? typeref)
+ typeref
+ (clojure.lang.RT/classForName (typename typeref) false classloader)))
+
(deftype JavaReflector [classloader]
Reflector
(do-reflect [_ typeref]
- (let [cls (clojure.lang.RT/classForName (typename typeref) false classloader)]
+ (let [cls (typeref->class typeref classloader)]
{:bases (not-empty (set (map typesym (bases cls))))
:flags (parse-flags (.getModifiers cls) :class)
:members (set/union (declared-fields cls)
diff --git a/src/clj/clojure/repl.clj b/src/clj/clojure/repl.clj
index 0547594c31..533559b77d 100644
--- a/src/clj/clojure/repl.clj
+++ b/src/clj/clojure/repl.clj
@@ -12,6 +12,7 @@
^{:author "Chris Houser, Christophe Grand, Stephen Gilardi, Michel Salim"
:doc "Utilities meant to be used interactively at the REPL"}
clojure.repl
+ (:require [clojure.spec.alpha :as spec])
(:import (java.io LineNumberReader InputStreamReader PushbackReader)
(clojure.lang RT Reflector)))
@@ -79,27 +80,37 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}})
(defn- namespace-doc [nspace]
(assoc (meta nspace) :name (ns-name nspace)))
-(defn- print-doc [m]
+(defn- print-doc [{n :ns
+ nm :name
+ :keys [forms arglists special-form doc url macro spec]
+ :as m}]
(println "-------------------------")
- (println (str (when-let [ns (:ns m)] (str (ns-name ns) "/")) (:name m)))
+ (println (or spec (str (when n (str (ns-name n) "/")) nm)))
+ (when forms
+ (doseq [f forms]
+ (print " ")
+ (prn f)))
+ (when arglists
+ (prn arglists))
(cond
- (:forms m) (doseq [f (:forms m)]
- (print " ")
- (prn f))
- (:arglists m) (prn (:arglists m)))
- (if (:special-form m)
- (do
- (println "Special Form")
- (println " " (:doc m))
- (if (contains? m :url)
- (when (:url m)
- (println (str "\n Please see http://clojure.org/" (:url m))))
- (println (str "\n Please see http://clojure.org/special_forms#"
- (:name m)))))
- (do
- (when (:macro m)
- (println "Macro"))
- (println " " (:doc m)))))
+ special-form
+ (println "Special Form")
+ macro
+ (println "Macro")
+ spec
+ (println "Spec"))
+ (when doc (println " " doc))
+ (when special-form
+ (if (contains? m :url)
+ (when url
+ (println (str "\n Please see http://clojure.org/" url)))
+ (println (str "\n Please see http://clojure.org/special_forms#" nm))))
+ (when n
+ (when-let [fnspec (spec/get-spec (symbol (str (ns-name n)) (name nm)))]
+ (println "Spec")
+ (doseq [role [:args :ret :fn]]
+ (when-let [spec (get fnspec role)]
+ (println " " (str (name role) ":") (spec/describe spec)))))))
(defn find-doc
"Prints documentation for any var whose documentation or name
@@ -118,13 +129,15 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}})
(print-doc m))))
(defmacro doc
- "Prints documentation for a var or special form given its name"
+ "Prints documentation for a var or special form given its name,
+ or for a spec if given a keyword"
{:added "1.0"}
[name]
(if-let [special-name ('{& fn catch try finally try} name)]
- (#'print-doc (#'special-doc special-name))
+ `(#'print-doc (#'special-doc '~special-name))
(cond
(special-doc-map name) `(#'print-doc (#'special-doc '~name))
+ (keyword? name) `(#'print-doc {:spec '~name :doc '~(spec/describe name)})
(find-ns name) `(#'print-doc (#'namespace-doc (find-ns '~name)))
(resolve name) `(#'print-doc (meta (var ~name))))))
@@ -149,10 +162,11 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}})
pbr (proxy [PushbackReader] [rdr]
(read [] (let [i (proxy-super read)]
(.append text (char i))
- i)))]
+ i)))
+ read-opts (if (.endsWith ^String filepath "cljc") {:read-cond :allow} {})]
(if (= :unknown *read-eval*)
(throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown."))
- (read (PushbackReader. pbr)))
+ (read read-opts (PushbackReader. pbr)))
(str text)))))))
(defmacro source
@@ -180,9 +194,9 @@ str-or-pattern."
(defn dir-fn
"Returns a sorted seq of symbols naming public vars in
- a namespace"
+ a namespace or namespace alias. Looks for aliases in *ns*"
[ns]
- (sort (map first (ns-publics (the-ns ns)))))
+ (sort (map first (ns-publics (the-ns (get (ns-aliases *ns*) ns ns))))))
(defmacro dir
"Prints a sorted directory of public vars in a namespace"
@@ -235,6 +249,9 @@ str-or-pattern."
(pst (root-cause e) e-or-depth))))
([^Throwable e depth]
(binding [*out* *err*]
+ (when (#{:read-source :macro-syntax-check :macroexpansion :compile-syntax-check :compilation}
+ (-> e ex-data :clojure.error/phase))
+ (println "Note: The following stack trace applies to the reader or compiler, your code was not executed."))
(println (str (-> e class .getSimpleName) " "
(.getMessage e)
(when-let [info (ex-data e)] (str " " (pr-str info)))))
diff --git a/src/clj/clojure/repl/deps.clj b/src/clj/clojure/repl/deps.clj
new file mode 100644
index 0000000000..64b22c713d
--- /dev/null
+++ b/src/clj/clojure/repl/deps.clj
@@ -0,0 +1,96 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+(ns clojure.repl.deps
+ "clojure.repl.deps provides facilities for dynamically modifying the available
+ libraries in the runtime when running at the REPL, without restarting"
+ (:require
+ [clojure.java.io :as jio]
+ [clojure.java.basis :as basis]
+ [clojure.java.basis.impl :as basis-impl]
+ [clojure.tools.deps.interop :as tool])
+ (:import
+ [clojure.lang DynamicClassLoader RT]
+ [java.io File]))
+
+(set! *warn-on-reflection* true)
+
+(defn- add-loader-url
+ "Add url string or URL to the highest level DynamicClassLoader url set."
+ [url]
+ (let [u (if (string? url) (RT/toUrl ^String url) url)
+ loader (loop [loader (.getContextClassLoader (Thread/currentThread))]
+ (let [parent (.getParent loader)]
+ (if (instance? DynamicClassLoader parent)
+ (recur parent)
+ loader)))]
+ (if (instance? DynamicClassLoader loader)
+ (.addURL ^DynamicClassLoader loader u)
+ (throw (IllegalAccessError. "Context classloader is not a DynamicClassLoader")))))
+
+(defn add-libs
+ "Given lib-coords, a map of lib to coord, will resolve all transitive deps for the libs
+ together and add them to the repl classpath, unlike separate calls to add-lib."
+ {:added "1.12"}
+ [lib-coords]
+ (when-not *repl* (throw (RuntimeException. "add-libs is only available at the REPL")))
+ (let [{:keys [libs] :as basis} (basis/current-basis)
+ current-libs (set (keys libs))
+ lib-coords (reduce-kv #(if (contains? current-libs %2) %1 (assoc %1 %2 %3))
+ {} lib-coords)]
+ (when-not (empty? lib-coords)
+ (let [procurer (reduce-kv (fn [m k v] (if (contains? #{"mvn" "git" "local"} (namespace k)) (assoc m k v) m)) {} basis)
+ tool-args {:existing libs, :add lib-coords, :procurer procurer}
+ {:keys [added] :as _res} (tool/invoke-tool {:tool-alias :deps, :fn 'clojure.tools.deps/resolve-added-libs, :args tool-args})
+ ;_ (clojure.pprint/pprint _res)
+ paths (mapcat :paths (vals added))
+ urls (->> paths (map jio/file) (map #(RT/toUrl ^File %)))]
+ (run! add-loader-url urls)
+ (basis-impl/update-basis! update :libs merge added)
+ ;; reload root *data-readers* from classpath
+ (set! *data-readers* (merge (#'clojure.core/load-data-readers) *data-readers*))
+ (let [ret (-> added keys sort vec)]
+ (when (seq ret) ret))))))
+
+(defn add-lib
+ "Given a lib that is not yet on the repl classpath, make it available by
+ downloading the library if necessary and adding it to the classloader.
+ Libs already on the classpath are not updated. Requires a valid parent
+ DynamicClassLoader.
+
+ lib - symbol identifying a library, for Maven: groupId/artifactId
+ coord - optional map of location information specific to the procurer,
+ or latest if not supplied
+
+ Returns coll of libs loaded, including transitive (or nil if none).
+
+ For info on libs, coords, and versions, see:
+ https://clojure.org/reference/deps_and_cli"
+ {:added "1.12"}
+ ([lib coord]
+ (add-libs {lib coord}))
+ ([lib]
+ (let [procurer (select-keys (basis/current-basis) [:mvn/repos :mvn/local-repo])
+ coord (tool/invoke-tool {:tool-alias :deps
+ :fn 'clojure.tools.deps/find-latest-version
+ :args {:lib lib, :procurer procurer}})]
+ (if coord
+ (add-libs {lib coord})
+ (throw (ex-info (str "No version found for lib " lib) {}))))))
+
+(defn sync-deps
+ "Calls add-libs with any libs present in deps.edn but not yet present on the classpath.
+
+ :aliases - coll of alias keywords to use during the sync"
+ {:added "1.12"}
+ [& {:as opts}]
+ (let [{:keys [aliases]} opts
+ basis-config (:basis-config (basis/current-basis))
+ new-basis-config (update basis-config :aliases (fnil into []) aliases)
+ new-basis (tool/invoke-tool {:tool-alias :deps, :fn 'clojure.tools.deps/create-basis, :args new-basis-config})
+ new-libs (:libs new-basis)]
+ (add-libs new-libs)))
diff --git a/src/clj/clojure/set.clj b/src/clj/clojure/set.clj
index 6a60d4f279..9ffb72900a 100644
--- a/src/clj/clojure/set.clj
+++ b/src/clj/clojure/set.clj
@@ -10,9 +10,10 @@
:author "Rich Hickey"}
clojure.set)
-(defn- bubble-max-key [k coll]
- "Move a maximal element of coll according to fn k (which returns a number)
- to the front of coll."
+(defn- bubble-max-key
+ "Move a maximal element of coll according to fn k (which returns a
+ number) to the front of coll."
+ [k coll]
(let [max (apply max-key k coll)]
(cons max (remove #(identical? max %) coll))))
@@ -105,7 +106,11 @@
(defn map-invert
"Returns the map with the vals mapped to the keys."
{:added "1.0"}
- [m] (reduce (fn [m [k v]] (assoc m v k)) {} m))
+ [m]
+ (persistent!
+ (reduce-kv (fn [m k v] (assoc! m v k))
+ (transient {})
+ m)))
(defn join
"When passed 2 rels, returns the rel corresponding to the natural
diff --git a/src/clj/clojure/stacktrace.clj b/src/clj/clojure/stacktrace.clj
index 977a8bb14b..293e84a9ec 100644
--- a/src/clj/clojure/stacktrace.clj
+++ b/src/clj/clojure/stacktrace.clj
@@ -15,10 +15,12 @@
:author "Stuart Sierra"}
clojure.stacktrace)
+(set! *warn-on-reflection* true)
+
(defn root-cause
"Returns the last 'cause' Throwable in a chain of Throwables."
{:added "1.1"}
- [tr]
+ [^Throwable tr]
(if-let [cause (.getCause tr)]
(recur cause)
tr))
@@ -26,7 +28,7 @@
(defn print-trace-element
"Prints a Clojure-oriented view of one element in a stack trace."
{:added "1.1"}
- [e]
+ [^StackTraceElement e]
(let [class (.getClassName e)
method (.getMethodName e)]
(let [match (re-matches #"^([A-Za-z0-9_.-]+)\$(\w+)__\d+$" (str class))]
@@ -36,10 +38,14 @@
(printf " (%s:%d)" (or (.getFileName e) "") (.getLineNumber e)))
(defn print-throwable
- "Prints the class and message of a Throwable."
+ "Prints the class and message of a Throwable. Prints the ex-data map
+ if present."
{:added "1.1"}
- [tr]
- (printf "%s: %s" (.getName (class tr)) (.getMessage tr)))
+ [^Throwable tr]
+ (printf "%s: %s" (.getName (class tr)) (.getMessage tr))
+ (when-let [info (ex-data tr)]
+ (newline)
+ (pr info)))
(defn print-stack-trace
"Prints a Clojure-oriented stack trace of tr, a Throwable.
@@ -67,7 +73,7 @@
"Like print-stack-trace but prints chained exceptions (causes)."
{:added "1.1"}
([tr] (print-cause-trace tr nil))
- ([tr n]
+ ([^Throwable tr n]
(print-stack-trace tr n)
(when-let [cause (.getCause tr)]
(print "Caused by: " )
diff --git a/src/clj/clojure/string.clj b/src/clj/clojure/string.clj
index bc3b4e4429..38f0d6d91d 100644
--- a/src/clj/clojure/string.clj
+++ b/src/clj/clojure/string.clj
@@ -43,6 +43,8 @@ Design notes for clojure.string:
(:import (java.util.regex Pattern Matcher)
clojure.lang.LazilyPersistentVector))
+(set! *warn-on-reflection* true)
+
(defn ^String reverse
"Returns s with its characters reversed."
{:added "1.2"}
@@ -216,7 +218,8 @@ Design notes for clojure.string:
(defn split
"Splits string on a regular expression. Optional argument limit is
- the maximum number of splits. Not lazy. Returns vector of the splits."
+ the maximum number of parts. Not lazy. Returns vector of the parts.
+ Trailing empty strings are not returned - pass limit of -1 to return all."
{:added "1.2"}
([^CharSequence s ^Pattern re]
(LazilyPersistentVector/createOwning (.split re s)))
@@ -224,7 +227,7 @@ Design notes for clojure.string:
(LazilyPersistentVector/createOwning (.split re s limit))))
(defn split-lines
- "Splits s on \\n or \\r\\n."
+ "Splits s on \\n or \\r\\n. Trailing empty lines are not returned."
{:added "1.2"}
[^CharSequence s]
(split s #"\r?\n"))
@@ -312,3 +315,63 @@ Design notes for clojure.string:
(.append buffer replacement)
(.append buffer ch))
(recur (inc index) buffer)))))
+
+(defn index-of
+ "Return index of value (string or char) in s, optionally searching
+ forward from from-index. Return nil if value not found."
+ {:added "1.8"}
+ ([^CharSequence s value]
+ (let [result ^long
+ (if (instance? Character value)
+ (.indexOf (.toString s) ^int (.charValue ^Character value))
+ (.indexOf (.toString s) ^String value))]
+ (if (= result -1)
+ nil
+ result)))
+ ([^CharSequence s value ^long from-index]
+ (let [result ^long
+ (if (instance? Character value)
+ (.indexOf (.toString s) ^int (.charValue ^Character value) (unchecked-int from-index))
+ (.indexOf (.toString s) ^String value (unchecked-int from-index)))]
+ (if (= result -1)
+ nil
+ result))))
+
+(defn last-index-of
+ "Return last index of value (string or char) in s, optionally
+ searching backward from from-index. Return nil if value not found."
+ {:added "1.8"}
+ ([^CharSequence s value]
+ (let [result ^long
+ (if (instance? Character value)
+ (.lastIndexOf (.toString s) ^int (.charValue ^Character value))
+ (.lastIndexOf (.toString s) ^String value))]
+ (if (= result -1)
+ nil
+ result)))
+ ([^CharSequence s value ^long from-index]
+ (let [result ^long
+ (if (instance? Character value)
+ (.lastIndexOf (.toString s) ^int (.charValue ^Character value) (unchecked-int from-index))
+ (.lastIndexOf (.toString s) ^String value (unchecked-int from-index)))]
+ (if (= result -1)
+ nil
+ result))))
+
+(defn starts-with?
+ "True if s starts with substr."
+ {:added "1.8"}
+ [^CharSequence s ^String substr]
+ (.startsWith (.toString s) substr))
+
+(defn ends-with?
+ "True if s ends with substr."
+ {:added "1.8"}
+ [^CharSequence s ^String substr]
+ (.endsWith (.toString s) substr))
+
+(defn includes?
+ "True if s includes substr."
+ {:added "1.8"}
+ [^CharSequence s ^CharSequence substr]
+ (.contains (.toString s) substr))
diff --git a/src/clj/clojure/template.clj b/src/clj/clojure/template.clj
index b48795642f..bda8eae40c 100644
--- a/src/clj/clojure/template.clj
+++ b/src/clj/clojure/template.clj
@@ -40,7 +40,7 @@
[argv expr values]
(assert (vector? argv))
(assert (every? symbol? argv))
- (walk/prewalk-replace (zipmap argv values) expr))
+ (walk/postwalk-replace (zipmap argv values) expr))
(defmacro do-template
"Repeatedly copies expr (in a do block) for each group of arguments
diff --git a/src/clj/clojure/test.clj b/src/clj/clojure/test.clj
index 55e00f7c3e..fc3536d7f3 100644
--- a/src/clj/clojure/test.clj
+++ b/src/clj/clojure/test.clj
@@ -233,7 +233,8 @@
"}
clojure.test
(:require [clojure.template :as temp]
- [clojure.stacktrace :as stack]))
+ [clojure.stacktrace :as stack]
+ [clojure.string :as str]))
;; Nothing is marked "private" here, so you can rebind things to plug
;; in your own testing or reporting frameworks.
@@ -316,8 +317,7 @@
{:added "1.1"}
[name]
(when *report-counters*
- (dosync (commute *report-counters* assoc name
- (inc (or (*report-counters* name) 0))))))
+ (dosync (commute *report-counters* update-in [name] (fnil inc 0)))))
;;; TEST RESULT REPORTING
@@ -332,7 +332,8 @@
:added "1.1"}
report :type)
-(defn- file-and-line
+(defn- file-and-line
+ {:deprecated "1.8"}
[^Throwable exception depth]
(let [stacktrace (.getStackTrace exception)]
(if (< depth (count stacktrace))
@@ -340,6 +341,13 @@
{:file (.getFileName s) :line (.getLineNumber s)})
{:file nil :line nil})))
+(defn- stacktrace-file-and-line
+ [stacktrace]
+ (if (seq stacktrace)
+ (let [^StackTraceElement s (first stacktrace)]
+ {:file (.getFileName s) :line (.getLineNumber s)})
+ {:file nil :line nil}))
+
(defn do-report
"Add file and line information to a test result and call report.
If you are writing a custom assert-expr method, call this function
@@ -349,8 +357,13 @@
(report
(case
(:type m)
- :fail (merge (file-and-line (new java.lang.Throwable) 1) m)
- :error (merge (file-and-line (:actual m) 0) m)
+ :fail (merge (stacktrace-file-and-line (drop-while
+ #(let [cl-name (.getClassName ^StackTraceElement %)]
+ (or (str/starts-with? cl-name "java.lang.")
+ (str/starts-with? cl-name "clojure.test$")
+ (str/starts-with? cl-name "clojure.core$ex_info")))
+ (.getStackTrace (Thread/currentThread)))) m)
+ :error (merge (stacktrace-file-and-line (.getStackTrace ^Throwable (:actual m))) m)
m)))
(defmethod report :default [m]
@@ -434,7 +447,7 @@
result# (apply ~pred values#)]
(if result#
(do-report {:type :pass, :message ~msg,
- :expected '~form, :actual (cons ~pred values#)})
+ :expected '~form, :actual (cons '~pred values#)})
(do-report {:type :fail, :message ~msg,
:expected '~form, :actual (list '~'not (cons '~pred values#))}))
result#)))
@@ -657,7 +670,7 @@
(defmulti use-fixtures
"Wrap test runs in a fixture function to perform setup and
teardown. Using a fixture-type of :each wraps every test
- individually, while:once wraps the whole run in a single function."
+ individually, while :once wraps the whole run in a single function."
{:added "1.1"}
(fn [fixture-type & args] fixture-type))
@@ -708,8 +721,8 @@
(do-report {:type :end-test-var, :var v}))))
(defn test-vars
- "Groups vars by their namespace and runs test-vars on them with
- appropriate fixtures applied."
+ "Groups vars by their namespace and runs test-var on them with
+ appropriate fixtures applied."
{:added "1.6"}
[vars]
(doseq [[ns vars] (group-by (comp :ns meta) vars)]
@@ -780,3 +793,38 @@
[summary]
(and (zero? (:fail summary 0))
(zero? (:error summary 0))))
+
+(defn run-test-var
+ "Runs the tests for a single Var, with fixtures executed around the test, and summary output after."
+ {:added "1.11"}
+ [v]
+ (binding [*report-counters* (ref *initial-report-counters*)]
+ (let [ns-obj (-> v meta :ns)
+ summary (do
+ (do-report {:type :begin-test-ns
+ :ns ns-obj})
+ (test-vars [v])
+ (do-report {:type :end-test-ns
+ :ns ns-obj})
+ (assoc @*report-counters* :type :summary))]
+ (do-report summary)
+ summary)))
+
+(defmacro run-test
+ "Runs a single test.
+
+ Because the intent is to run a single test, there is no check for the namespace test-ns-hook."
+ {:added "1.11"}
+ [test-symbol]
+ (let [test-var (resolve test-symbol)]
+ (cond
+ (nil? test-var)
+ (binding [*out* *err*]
+ (println "Unable to resolve" test-symbol "to a test function."))
+
+ (not (-> test-var meta :test))
+ (binding [*out* *err*]
+ (println test-symbol "is not a test."))
+
+ :else
+ `(run-test-var ~test-var))))
diff --git a/src/clj/clojure/test/junit.clj b/src/clj/clojure/test/junit.clj
index 33077033f9..3d129b186b 100644
--- a/src/clj/clojure/test/junit.clj
+++ b/src/clj/clojure/test/junit.clj
@@ -190,6 +190,6 @@
(t/with-test-out
(println "")
(println ""))
- (let [result# ~@body]
+ (let [result# (do ~@body)]
(t/with-test-out (println ""))
result#)))
diff --git a/src/clj/clojure/tools/deps/interop.clj b/src/clj/clojure/tools/deps/interop.clj
new file mode 100644
index 0000000000..0b6106801c
--- /dev/null
+++ b/src/clj/clojure/tools/deps/interop.clj
@@ -0,0 +1,105 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+(ns clojure.tools.deps.interop
+ "Functions for invoking Java processes and invoking tools via the Clojure CLI."
+ (:require
+ [clojure.java.process :as proc]
+ [clojure.edn :as edn]
+ [clojure.java.io :as jio]
+ [clojure.string :as str]))
+
+(set! *warn-on-reflection* true)
+
+(def ^:private build (atom nil))
+
+(defn- cli-build
+ "Return CLI build number (a long) or nil if it can't be determined.
+ The build number is cached if found and subsequently read from cache."
+ []
+ (or @build
+ (let [result (try
+ (proc/exec "clojure" "--version")
+ (catch Exception e ""))
+ ;; Version string: "Clojure CLI version 1.11.3.1463"
+ ;; Match MAJOR.MINOR.PATCH.BUILD and take a capture group just for the last BUILD part
+ version (-> (re-find #"[0-9]+\.[0-9]+\.[0-9]+\.([0-9]+)" result) (nth 1))]
+ (when version
+ (reset! build (parse-long version))))))
+
+(defn- validate-version
+ [version]
+ (if version
+ (when (< version 1347)
+ (throw (RuntimeException. "Clojure CLI version is older than minimum required version, 1.11.1.1347. Please update to latest version.")))
+ (throw (ex-info "Clojure CLI version unknown, please install the latest version." {}))))
+
+(defn ^:dynamic invoke-tool
+ "Invoke tool using Clojure CLI. Args (one of :tool-alias or :tool-name, and :fn
+ are required):
+ :tool-alias - Tool alias to invoke (keyword)
+ :tool-name - Name of installed tool to invoke (string or symbol)
+ :fn - Function (symbol)
+ :args - map of args to pass to function
+
+ Options:
+ :preserve-envelope - if true, return the full invocation envelope, default=false"
+ {:added "1.12"}
+ [{:keys [tool-name tool-alias fn args preserve-envelope]
+ :or {preserve-envelope false}
+ :as opts}]
+ (when-not (or tool-name tool-alias) (throw (ex-info "Either :tool-alias or :tool-name must be provided" (or opts {}))))
+ (when-not (symbol? fn) (throw (ex-info (str ":fn should be a symbol " fn) (or opts {}))))
+ (validate-version (cli-build))
+ (let [args (conj [fn] (assoc args :clojure.exec/invoke :fn))
+ _ (when (:debug opts) (println "args" args))
+ command-strs ["clojure" (str "-T" (or tool-alias tool-name)) "-"]
+ _ (when (:debug opts) (apply println "Invoking: " command-strs))
+ proc (apply proc/start command-strs)
+ in (proc/stdin proc)
+ out (proc/stdout proc)
+ err (proc/stderr proc)]
+ (binding [*print-length* nil
+ *print-level* nil
+ *print-namespace-maps* false]
+ (proc/io-task
+ #(with-open [w (jio/writer in)]
+ (doseq [a args]
+ (.write w (pr-str a))
+ (.write w " ")))))
+ (if-let [envelope (edn/read-string (slurp out))]
+ (if preserve-envelope
+ envelope
+ (let [{:keys [tag val]} envelope
+ parsed-val (edn/read-string val)]
+ (if (= :ret tag)
+ parsed-val
+ (throw (ex-info (:cause parsed-val) (or parsed-val {}))))))
+ (let [err-str (slurp err)
+ err-msg (if (= "" err-str) "Unknown error invoking Clojure CLI" err-str)]
+ (throw (ex-info err-msg
+ {:command (str/join " " command-strs)
+ :in (str/join " " args)}))))))
+
+(comment
+ ;; regular invocation, should return {:hi :there}
+ (invoke-tool {:tool-alias :deps, :fn 'clojure.core/identity, :args {:hi :there}})
+
+ ;; invocation throws, should return throwable map data
+ (try
+ (invoke-tool {:tool-alias :deps, :fn 'clojure.core/+, :args {:fail :here}})
+ (catch clojure.lang.ExceptionInfo e (ex-data e)))
+
+ ;; capture stdout in returned envelope
+ (let [resp (invoke-tool {:tool-alias :deps,
+ :fn 'list
+ :args {:format :edn
+ :clojure.exec/out :capture}
+ :preserve-envelope true})]
+ (edn/read-string (:out resp)))
+
+ )
diff --git a/src/clj/clojure/uuid.clj b/src/clj/clojure/uuid.clj
index dbaa9d141e..4ffbdf60d5 100644
--- a/src/clj/clojure/uuid.clj
+++ b/src/clj/clojure/uuid.clj
@@ -9,8 +9,9 @@
(ns clojure.uuid)
(defn- default-uuid-reader [form]
- {:pre [(string? form)]}
- (java.util.UUID/fromString form))
+ (if (string? form)
+ (java.util.UUID/fromString form)
+ (throw (IllegalArgumentException. "#uuid data reader expected string"))))
(defmethod print-method java.util.UUID [uuid ^java.io.Writer w]
(.write w (str "#uuid \"" (str uuid) "\"")))
diff --git a/src/clj/clojure/walk.clj b/src/clj/clojure/walk.clj
index 0d9e0e1802..0f027e7ad0 100644
--- a/src/clj/clojure/walk.clj
+++ b/src/clj/clojure/walk.clj
@@ -41,9 +41,10 @@ the sorting function."}
{:added "1.1"}
[inner outer form]
(cond
- (list? form) (outer (apply list (map inner form)))
- (instance? clojure.lang.IMapEntry form) (outer (vec (map inner form)))
- (seq? form) (outer (doall (map inner form)))
+ (list? form) (outer (with-meta (apply list (map inner form)) (meta form)))
+ (instance? clojure.lang.IMapEntry form)
+ (outer (clojure.lang.MapEntry/create (inner (key form)) (inner (val form))))
+ (seq? form) (outer (with-meta (doall (map inner form)) (meta form)))
(instance? clojure.lang.IRecord form)
(outer (reduce (fn [r x] (conj r (inner x))) form form))
(coll? form) (outer (into (empty form) (map inner form)))
diff --git a/src/clj/clojure/xml.clj b/src/clj/clojure/xml.clj
index 4e4220f265..d892e19279 100644
--- a/src/clj/clojure/xml.clj
+++ b/src/clj/clojure/xml.clj
@@ -72,8 +72,36 @@
(skippedEntity [name])
))))
-(defn startparse-sax [s ch]
- (.. SAXParserFactory (newInstance) (newSAXParser) (parse s ch)))
+(defn sax-parser
+ "Create a new SAXParser"
+ {:added "1.11"}
+ ^SAXParser []
+ (.newSAXParser (SAXParserFactory/newInstance)))
+
+(defn disable-external-entities
+ "Modifies a SAXParser to disable external entity resolution to prevent XXE attacks"
+ {:added "1.11"}
+ ^SAXParser [^SAXParser parser]
+ (let [reader (.getXMLReader parser)]
+ ;; as per https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
+ (.setFeature reader "http://apache.org/xml/features/nonvalidating/load-external-dtd" false)
+ (.setFeature reader "http://xml.org/sax/features/external-general-entities", false)
+ (.setFeature reader "http://xml.org/sax/features/external-parameter-entities" false)
+ parser))
+
+(defn startparse-sax
+ "A startparse function suitable for use with clojure.xml/parse.
+ Note that this function is open to XXE entity attacks, see startparse-sax-safe."
+ {:added "1.0"}
+ [s ch]
+ (.parse (sax-parser) s ch))
+
+(defn startparse-sax-safe
+ "A startparse function suitable for use with clojure.xml/parse.
+ External entity resolution is disabled to prevent XXE entity attacks."
+ {:added "1.11"}
+ [s ch]
+ (.parse (disable-external-entities (sax-parser)) s ch))
(defn parse
"Parses and loads the source s, which can be a File, InputStream or
@@ -81,9 +109,13 @@
which has the keys :tag, :attrs, and :content. and accessor fns tag,
attrs, and content. Other parsers can be supplied by passing
startparse, a fn taking a source and a ContentHandler and returning
- a parser"
+ a parser.
+
+ Prior to 1.11, used startparse-sax by default. As of 1.11, uses
+ startparse-sax-safe, which disables XXE (XML External Entity)
+ processing. Pass startparse-sax to revert to prior behavior."
{:added "1.0"}
- ([s] (parse s startparse-sax))
+ ([s] (parse s startparse-sax-safe))
([s startparse]
(binding [*stack* nil
*current* (struct element)
diff --git a/src/jvm/clojure/asm/AnnotationVisitor.java b/src/jvm/clojure/asm/AnnotationVisitor.java
index eb01145144..940bfb77c6 100644
--- a/src/jvm/clojure/asm/AnnotationVisitor.java
+++ b/src/jvm/clojure/asm/AnnotationVisitor.java
@@ -1,169 +1,150 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A visitor to visit a Java annotation. The methods of this class must be
- * called in the following order: ( visit | visitEnum |
- * visitAnnotation | visitArray )* visitEnd.
+ * A visitor to visit a Java annotation. The methods of this class must be called in the following
+ * order: ( visit | visitEnum | visitAnnotation | visitArray )*
+ * visitEnd.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public abstract class AnnotationVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ protected final int api;
- /**
- * The annotation visitor to which this visitor must delegate method calls.
- * May be null.
- */
- protected AnnotationVisitor av;
+ /** The annotation visitor to which this visitor must delegate method calls. May be null. */
+ protected AnnotationVisitor av;
- /**
- * Constructs a new {@link AnnotationVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public AnnotationVisitor(final int api) {
- this(api, null);
- }
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ public AnnotationVisitor(final int api) {
+ this(api, null);
+ }
- /**
- * Constructs a new {@link AnnotationVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param av
- * the annotation visitor to which this visitor must delegate
- * method calls. May be null.
- */
- public AnnotationVisitor(final int api, final AnnotationVisitor av) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.av = av;
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param annotationVisitor the annotation visitor to which this visitor must delegate method
+ * calls. May be null.
+ */
+ public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) {
+ if (api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM7_EXPERIMENTAL) {
+ throw new IllegalArgumentException();
}
+ this.api = api;
+ this.av = annotationVisitor;
+ }
- /**
- * Visits a primitive value of the annotation.
- *
- * @param name
- * the value name.
- * @param value
- * the actual value, whose type must be {@link Byte},
- * {@link Boolean}, {@link Character}, {@link Short},
- * {@link Integer} , {@link Long}, {@link Float}, {@link Double},
- * {@link String} or {@link Type} or OBJECT or ARRAY sort. This
- * value can also be an array of byte, boolean, short, char, int,
- * long, float or double values (this is equivalent to using
- * {@link #visitArray visitArray} and visiting each array element
- * in turn, but is more convenient).
- */
- public void visit(String name, Object value) {
- if (av != null) {
- av.visit(name, value);
- }
+ /**
+ * Visits a primitive value of the annotation.
+ *
+ * @param name the value name.
+ * @param value the actual value, whose type must be {@link Byte}, {@link Boolean}, {@link
+ * Character}, {@link Short}, {@link Integer} , {@link Long}, {@link Float}, {@link Double},
+ * {@link String} or {@link Type} of {@link Type#OBJECT} or {@link Type#ARRAY} sort. This
+ * value can also be an array of byte, boolean, short, char, int, long, float or double values
+ * (this is equivalent to using {@link #visitArray} and visiting each array element in turn,
+ * but is more convenient).
+ */
+ public void visit(final String name, final Object value) {
+ if (av != null) {
+ av.visit(name, value);
}
+ }
- /**
- * Visits an enumeration value of the annotation.
- *
- * @param name
- * the value name.
- * @param desc
- * the class descriptor of the enumeration class.
- * @param value
- * the actual enumeration value.
- */
- public void visitEnum(String name, String desc, String value) {
- if (av != null) {
- av.visitEnum(name, desc, value);
- }
+ /**
+ * Visits an enumeration value of the annotation.
+ *
+ * @param name the value name.
+ * @param descriptor the class descriptor of the enumeration class.
+ * @param value the actual enumeration value.
+ */
+ public void visitEnum(final String name, final String descriptor, final String value) {
+ if (av != null) {
+ av.visitEnum(name, descriptor, value);
}
+ }
- /**
- * Visits a nested annotation value of the annotation.
- *
- * @param name
- * the value name.
- * @param desc
- * the class descriptor of the nested annotation class.
- * @return a visitor to visit the actual nested annotation value, or
- * null if this visitor is not interested in visiting this
- * nested annotation. The nested annotation value must be fully
- * visited before calling other methods on this annotation
- * visitor.
- */
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- if (av != null) {
- return av.visitAnnotation(name, desc);
- }
- return null;
+ /**
+ * Visits a nested annotation value of the annotation.
+ *
+ * @param name the value name.
+ * @param descriptor the class descriptor of the nested annotation class.
+ * @return a visitor to visit the actual nested annotation value, or null if this visitor
+ * is not interested in visiting this nested annotation. The nested annotation value must
+ * be fully visited before calling other methods on this annotation visitor.
+ */
+ public AnnotationVisitor visitAnnotation(final String name, final String descriptor) {
+ if (av != null) {
+ return av.visitAnnotation(name, descriptor);
}
+ return null;
+ }
- /**
- * Visits an array value of the annotation. Note that arrays of primitive
- * types (such as byte, boolean, short, char, int, long, float or double)
- * can be passed as value to {@link #visit visit}. This is what
- * {@link ClassReader} does.
- *
- * @param name
- * the value name.
- * @return a visitor to visit the actual array value elements, or
- * null if this visitor is not interested in visiting these
- * values. The 'name' parameters passed to the methods of this
- * visitor are ignored. All the array values must be visited
- * before calling other methods on this annotation visitor.
- */
- public AnnotationVisitor visitArray(String name) {
- if (av != null) {
- return av.visitArray(name);
- }
- return null;
+ /**
+ * Visits an array value of the annotation. Note that arrays of primitive types (such as byte,
+ * boolean, short, char, int, long, float or double) can be passed as value to {@link #visit
+ * visit}. This is what {@link ClassReader} does.
+ *
+ * @param name the value name.
+ * @return a visitor to visit the actual array value elements, or null if this visitor is
+ * not interested in visiting these values. The 'name' parameters passed to the methods of
+ * this visitor are ignored. All the array values must be visited before calling other
+ * methods on this annotation visitor.
+ */
+ public AnnotationVisitor visitArray(final String name) {
+ if (av != null) {
+ return av.visitArray(name);
}
+ return null;
+ }
- /**
- * Visits the end of the annotation.
- */
- public void visitEnd() {
- if (av != null) {
- av.visitEnd();
- }
+ /** Visits the end of the annotation. */
+ public void visitEnd() {
+ if (av != null) {
+ av.visitEnd();
}
+ }
}
diff --git a/src/jvm/clojure/asm/AnnotationWriter.java b/src/jvm/clojure/asm/AnnotationWriter.java
index 9a40bf8224..4ee16510a3 100644
--- a/src/jvm/clojure/asm/AnnotationWriter.java
+++ b/src/jvm/clojure/asm/AnnotationWriter.java
@@ -1,318 +1,418 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * An {@link AnnotationVisitor} that generates annotations in bytecode form.
+ * An {@link AnnotationVisitor} that generates a corresponding 'annotation' or 'type_annotation'
+ * structure, as defined in the Java Virtual Machine Specification (JVMS). AnnotationWriter
+ * instances can be chained in a doubly linked list, from which Runtime[In]Visible[Type]Annotations
+ * attributes can be generated with the {@link #putAnnotations} method. Similarly, arrays of such
+ * lists can be used to generate Runtime[In]VisibleParameterAnnotations attributes.
*
+ * @see JVMS
+ * 4.7.16
+ * @see JVMS
+ * 4.7.20
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
final class AnnotationWriter extends AnnotationVisitor {
- /**
- * The class writer to which this annotation must be added.
- */
- private final ClassWriter cw;
+ /** Where the constants used in this AnnotationWriter must be stored. */
+ private final SymbolTable symbolTable;
- /**
- * The number of values in this annotation.
- */
- private int size;
+ /**
+ * Whether values are named or not. AnnotationWriter instances used for annotation default and
+ * annotation arrays use unnamed values (i.e. they generate an 'element_value' structure for each
+ * value, instead of an element_name_index followed by an element_value).
+ */
+ private final boolean useNamedValues;
- /**
- * true if values are named, false otherwise. Annotation
- * writers used for annotation default and annotation arrays use unnamed
- * values.
- */
- private final boolean named;
+ /**
+ * The 'annotation' or 'type_annotation' JVMS structure corresponding to the annotation values
+ * visited so far. All the fields of these structures, except the last one - the
+ * element_value_pairs array, must be set before this ByteVector is passed to the constructor
+ * (num_element_value_pairs can be set to 0, it is reset to the correct value in {@link
+ * #visitEnd()}). The element_value_pairs array is filled incrementally in the various visit()
+ * methods.
+ *
+ *
Note: as an exception to the above rules, for AnnotationDefault attributes (which contain a
+ * single element_value by definition), this ByteVector is initially empty when passed to the
+ * constructor, and {@link #numElementValuePairsOffset} is set to -1.
+ */
+ private final ByteVector annotation;
- /**
- * The annotation values in bytecode form. This byte vector only contains
- * the values themselves, i.e. the number of values must be stored as a
- * unsigned short just before these bytes.
- */
- private final ByteVector bv;
+ /**
+ * The offset in {@link #annotation} where {@link #numElementValuePairs} must be stored (or -1 for
+ * the case of AnnotationDefault attributes).
+ */
+ private final int numElementValuePairsOffset;
- /**
- * The byte vector to be used to store the number of values of this
- * annotation. See {@link #bv}.
- */
- private final ByteVector parent;
+ /** The number of element value pairs visited so far. */
+ private int numElementValuePairs;
- /**
- * Where the number of values of this annotation must be stored in
- * {@link #parent}.
- */
- private final int offset;
+ /**
+ * The previous AnnotationWriter. This field is used to store the list of annotations of a
+ * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
+ * (annotation values of annotation type), or for AnnotationDefault attributes.
+ */
+ private final AnnotationWriter previousAnnotation;
- /**
- * Next annotation writer. This field is used to store annotation lists.
- */
- AnnotationWriter next;
+ /**
+ * The next AnnotationWriter. This field is used to store the list of annotations of a
+ * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
+ * (annotation values of annotation type), or for AnnotationDefault attributes.
+ */
+ private AnnotationWriter nextAnnotation;
- /**
- * Previous annotation writer. This field is used to store annotation lists.
- */
- AnnotationWriter prev;
+ // -----------------------------------------------------------------------------------------------
+ // Constructors
+ // -----------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new {@link AnnotationWriter}.
- *
- * @param cw
- * the class writer to which this annotation must be added.
- * @param named
- * true if values are named, false otherwise.
- * @param bv
- * where the annotation values must be stored.
- * @param parent
- * where the number of annotation values must be stored.
- * @param offset
- * where in parent the number of annotation values must
- * be stored.
- */
- AnnotationWriter(final ClassWriter cw, final boolean named,
- final ByteVector bv, final ByteVector parent, final int offset) {
- super(Opcodes.ASM4);
- this.cw = cw;
- this.named = named;
- this.bv = bv;
- this.parent = parent;
- this.offset = offset;
+ /**
+ * Constructs a new {@link AnnotationWriter}.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param useNamedValues whether values are named or not. AnnotationDefault and annotation arrays
+ * use unnamed values.
+ * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to
+ * the visited content must be stored. This ByteVector must already contain all the fields of
+ * the structure except the last one (the element_value_pairs array).
+ * @param previousAnnotation the previously visited annotation of the
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in
+ * other cases (e.g. nested or array annotations).
+ */
+ AnnotationWriter(
+ final SymbolTable symbolTable,
+ final boolean useNamedValues,
+ final ByteVector annotation,
+ final AnnotationWriter previousAnnotation) {
+ super(Opcodes.ASM6);
+ this.symbolTable = symbolTable;
+ this.useNamedValues = useNamedValues;
+ this.annotation = annotation;
+ // By hypothesis, num_element_value_pairs is stored in the last unsigned short of 'annotation'.
+ this.numElementValuePairsOffset = annotation.length == 0 ? -1 : annotation.length - 2;
+ this.previousAnnotation = previousAnnotation;
+ if (previousAnnotation != null) {
+ previousAnnotation.nextAnnotation = this;
}
+ }
+
+ /**
+ * Constructs a new {@link AnnotationWriter} using named values.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to
+ * the visited content must be stored. This ByteVector must already contain all the fields of
+ * the structure except the last one (the element_value_pairs array).
+ * @param previousAnnotation the previously visited annotation of the
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in
+ * other cases (e.g. nested or array annotations).
+ */
+ AnnotationWriter(
+ final SymbolTable symbolTable,
+ final ByteVector annotation,
+ final AnnotationWriter previousAnnotation) {
+ this(symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation);
+ }
- // ------------------------------------------------------------------------
- // Implementation of the AnnotationVisitor abstract class
- // ------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the AnnotationVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
- @Override
- public void visit(final String name, final Object value) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- if (value instanceof String) {
- bv.put12('s', cw.newUTF8((String) value));
- } else if (value instanceof Byte) {
- bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index);
- } else if (value instanceof Boolean) {
- int v = ((Boolean) value).booleanValue() ? 1 : 0;
- bv.put12('Z', cw.newInteger(v).index);
- } else if (value instanceof Character) {
- bv.put12('C', cw.newInteger(((Character) value).charValue()).index);
- } else if (value instanceof Short) {
- bv.put12('S', cw.newInteger(((Short) value).shortValue()).index);
- } else if (value instanceof Type) {
- bv.put12('c', cw.newUTF8(((Type) value).getDescriptor()));
- } else if (value instanceof byte[]) {
- byte[] v = (byte[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('B', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof boolean[]) {
- boolean[] v = (boolean[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index);
- }
- } else if (value instanceof short[]) {
- short[] v = (short[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('S', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof char[]) {
- char[] v = (char[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('C', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof int[]) {
- int[] v = (int[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('I', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof long[]) {
- long[] v = (long[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('J', cw.newLong(v[i]).index);
- }
- } else if (value instanceof float[]) {
- float[] v = (float[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('F', cw.newFloat(v[i]).index);
- }
- } else if (value instanceof double[]) {
- double[] v = (double[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('D', cw.newDouble(v[i]).index);
- }
- } else {
- Item i = cw.newConstItem(value);
- bv.put12(".s.IFJDCS".charAt(i.type), i.index);
- }
+ @Override
+ public void visit(final String name, final Object value) {
+ // Case of an element_value with a const_value_index, class_info_index or array_index field.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
+ }
+ if (value instanceof String) {
+ annotation.put12('s', symbolTable.addConstantUtf8((String) value));
+ } else if (value instanceof Byte) {
+ annotation.put12('B', symbolTable.addConstantInteger(((Byte) value).byteValue()).index);
+ } else if (value instanceof Boolean) {
+ int booleanValue = ((Boolean) value).booleanValue() ? 1 : 0;
+ annotation.put12('Z', symbolTable.addConstantInteger(booleanValue).index);
+ } else if (value instanceof Character) {
+ annotation.put12('C', symbolTable.addConstantInteger(((Character) value).charValue()).index);
+ } else if (value instanceof Short) {
+ annotation.put12('S', symbolTable.addConstantInteger(((Short) value).shortValue()).index);
+ } else if (value instanceof Type) {
+ annotation.put12('c', symbolTable.addConstantUtf8(((Type) value).getDescriptor()));
+ } else if (value instanceof byte[]) {
+ byte[] byteArray = (byte[]) value;
+ annotation.put12('[', byteArray.length);
+ for (byte byteValue : byteArray) {
+ annotation.put12('B', symbolTable.addConstantInteger(byteValue).index);
+ }
+ } else if (value instanceof boolean[]) {
+ boolean[] booleanArray = (boolean[]) value;
+ annotation.put12('[', booleanArray.length);
+ for (boolean booleanValue : booleanArray) {
+ annotation.put12('Z', symbolTable.addConstantInteger(booleanValue ? 1 : 0).index);
+ }
+ } else if (value instanceof short[]) {
+ short[] shortArray = (short[]) value;
+ annotation.put12('[', shortArray.length);
+ for (short shortValue : shortArray) {
+ annotation.put12('S', symbolTable.addConstantInteger(shortValue).index);
+ }
+ } else if (value instanceof char[]) {
+ char[] charArray = (char[]) value;
+ annotation.put12('[', charArray.length);
+ for (char charValue : charArray) {
+ annotation.put12('C', symbolTable.addConstantInteger(charValue).index);
+ }
+ } else if (value instanceof int[]) {
+ int[] intArray = (int[]) value;
+ annotation.put12('[', intArray.length);
+ for (int intValue : intArray) {
+ annotation.put12('I', symbolTable.addConstantInteger(intValue).index);
+ }
+ } else if (value instanceof long[]) {
+ long[] longArray = (long[]) value;
+ annotation.put12('[', longArray.length);
+ for (long longValue : longArray) {
+ annotation.put12('J', symbolTable.addConstantLong(longValue).index);
+ }
+ } else if (value instanceof float[]) {
+ float[] floatArray = (float[]) value;
+ annotation.put12('[', floatArray.length);
+ for (float floatValue : floatArray) {
+ annotation.put12('F', symbolTable.addConstantFloat(floatValue).index);
+ }
+ } else if (value instanceof double[]) {
+ double[] doubleArray = (double[]) value;
+ annotation.put12('[', doubleArray.length);
+ for (double doubleValue : doubleArray) {
+ annotation.put12('D', symbolTable.addConstantDouble(doubleValue).index);
+ }
+ } else {
+ Symbol symbol = symbolTable.addConstant(value);
+ annotation.put12(".s.IFJDCS".charAt(symbol.tag), symbol.index);
}
+ }
- @Override
- public void visitEnum(final String name, final String desc,
- final String value) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value));
+ @Override
+ public void visitEnum(final String name, final String descriptor, final String value) {
+ // Case of an element_value with an enum_const_value field.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
}
+ annotation
+ .put12('e', symbolTable.addConstantUtf8(descriptor))
+ .putShort(symbolTable.addConstantUtf8(value));
+ }
- @Override
- public AnnotationVisitor visitAnnotation(final String name,
- final String desc) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- // write tag and type, and reserve space for values count
- bv.put12('@', cw.newUTF8(desc)).putShort(0);
- return new AnnotationWriter(cw, true, bv, bv, bv.length - 2);
+ @Override
+ public AnnotationVisitor visitAnnotation(final String name, final String descriptor) {
+ // Case of an element_value with an annotation_value field.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
}
+ // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs.
+ annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ return new AnnotationWriter(symbolTable, annotation, null);
+ }
- @Override
- public AnnotationVisitor visitArray(final String name) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- // write tag, and reserve space for array size
- bv.put12('[', 0);
- return new AnnotationWriter(cw, false, bv, bv, bv.length - 2);
+ @Override
+ public AnnotationVisitor visitArray(final String name) {
+ // Case of an element_value with an array_value field.
+ // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
}
+ // Write tag, and reserve 2 bytes for num_values. Here we take advantage of the fact that the
+ // end of an element_value of array type is similar to the end of an 'annotation' structure: an
+ // unsigned short num_values followed by num_values element_value, versus an unsigned short
+ // num_element_value_pairs, followed by num_element_value_pairs { element_name_index,
+ // element_value } tuples. This allows us to use an AnnotationWriter with unnamed values to
+ // visit the array elements. Its num_element_value_pairs will correspond to the number of array
+ // elements and will be stored in what is in fact num_values.
+ annotation.put12('[', 0);
+ return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null);
+ }
- @Override
- public void visitEnd() {
- if (parent != null) {
- byte[] data = parent.data;
- data[offset] = (byte) (size >>> 8);
- data[offset + 1] = (byte) size;
- }
+ @Override
+ public void visitEnd() {
+ if (numElementValuePairsOffset != -1) {
+ byte[] data = annotation.data;
+ data[numElementValuePairsOffset] = (byte) (numElementValuePairs >>> 8);
+ data[numElementValuePairsOffset + 1] = (byte) numElementValuePairs;
}
+ }
- // ------------------------------------------------------------------------
- // Utility methods
- // ------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
- /**
- * Returns the size of this annotation writer list.
- *
- * @return the size of this annotation writer list.
- */
- int getSize() {
- int size = 0;
- AnnotationWriter aw = this;
- while (aw != null) {
- size += aw.bv.length;
- aw = aw.next;
- }
- return size;
+ /**
+ * Returns the size of a Runtime[In]Visible[Type]Annotations attribute containing this annotation
+ * and all its predecessors (see {@link #previousAnnotation}. Also adds the attribute name
+ * to the constant pool of the class (if not null).
+ *
+ * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or null.
+ * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this
+ * annotation and all its predecessors. This includes the size of the attribute_name_index and
+ * attribute_length fields.
+ */
+ int computeAnnotationsSize(final String attributeName) {
+ if (attributeName != null) {
+ symbolTable.addConstantUtf8(attributeName);
}
+ // The attribute_name_index, attribute_length and num_annotations fields use 8 bytes.
+ int attributeSize = 8;
+ AnnotationWriter annotationWriter = this;
+ while (annotationWriter != null) {
+ attributeSize += annotationWriter.annotation.length;
+ annotationWriter = annotationWriter.previousAnnotation;
+ }
+ return attributeSize;
+ }
- /**
- * Puts the annotations of this annotation writer list into the given byte
- * vector.
- *
- * @param out
- * where the annotations must be put.
- */
- void put(final ByteVector out) {
- int n = 0;
- int size = 2;
- AnnotationWriter aw = this;
- AnnotationWriter last = null;
- while (aw != null) {
- ++n;
- size += aw.bv.length;
- aw.visitEnd(); // in case user forgot to call visitEnd
- aw.prev = last;
- last = aw;
- aw = aw.next;
- }
- out.putInt(size);
- out.putShort(n);
- aw = last;
- while (aw != null) {
- out.putByteArray(aw.bv.data, 0, aw.bv.length);
- aw = aw.prev;
- }
+ /**
+ * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its
+ * predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are
+ * put in the same order they have been visited.
+ *
+ * @param attributeNameIndex the constant pool index of the attribute name (one of
+ * "Runtime[In]Visible[Type]Annotations").
+ * @param output where the attribute must be put.
+ */
+ void putAnnotations(final int attributeNameIndex, final ByteVector output) {
+ int attributeLength = 2; // For num_annotations.
+ int numAnnotations = 0;
+ AnnotationWriter annotationWriter = this;
+ AnnotationWriter firstAnnotation = null;
+ while (annotationWriter != null) {
+ // In case the user forgot to call visitEnd().
+ annotationWriter.visitEnd();
+ attributeLength += annotationWriter.annotation.length;
+ numAnnotations++;
+ firstAnnotation = annotationWriter;
+ annotationWriter = annotationWriter.previousAnnotation;
+ }
+ output.putShort(attributeNameIndex);
+ output.putInt(attributeLength);
+ output.putShort(numAnnotations);
+ annotationWriter = firstAnnotation;
+ while (annotationWriter != null) {
+ output.putByteArray(annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
+ annotationWriter = annotationWriter.nextAnnotation;
}
+ }
- /**
- * Puts the given annotation lists into the given byte vector.
- *
- * @param panns
- * an array of annotation writer lists.
- * @param off
- * index of the first annotation to be written.
- * @param out
- * where the annotations must be put.
- */
- static void put(final AnnotationWriter[] panns, final int off,
- final ByteVector out) {
- int size = 1 + 2 * (panns.length - off);
- for (int i = off; i < panns.length; ++i) {
- size += panns[i] == null ? 0 : panns[i].getSize();
- }
- out.putInt(size).putByte(panns.length - off);
- for (int i = off; i < panns.length; ++i) {
- AnnotationWriter aw = panns[i];
- AnnotationWriter last = null;
- int n = 0;
- while (aw != null) {
- ++n;
- aw.visitEnd(); // in case user forgot to call visitEnd
- aw.prev = last;
- last = aw;
- aw = aw.next;
- }
- out.putShort(n);
- aw = last;
- while (aw != null) {
- out.putByteArray(aw.bv.data, 0, aw.bv.length);
- aw = aw.prev;
- }
- }
+ /**
+ * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the
+ * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the
+ * constant pool of the class.
+ *
+ * @param attributeName one of "Runtime[In]VisibleParameterAnnotations".
+ * @param annotationWriters an array of AnnotationWriter lists (designated by their last
+ * element).
+ * @param annotableParameterCount the number of elements in annotationWriters to take into account
+ * (elements [0..annotableParameterCount[ are taken into account).
+ * @return the size in bytes of a Runtime[In]VisibleParameterAnnotations attribute corresponding
+ * to the given sub-array of AnnotationWriter lists. This includes the size of the
+ * attribute_name_index and attribute_length fields.
+ */
+ static int computeParameterAnnotationsSize(
+ final String attributeName,
+ final AnnotationWriter[] annotationWriters,
+ final int annotableParameterCount) {
+ // Note: attributeName is added to the constant pool by the call to computeAnnotationsSize
+ // below. This assumes that there is at least one non-null element in the annotationWriters
+ // sub-array (which is ensured by the lazy instantiation of this array in MethodWriter).
+ // The attribute_name_index, attribute_length and num_parameters fields use 7 bytes, and each
+ // element of the parameter_annotations array uses 2 bytes for its num_annotations field.
+ int attributeSize = 7 + 2 * annotableParameterCount;
+ for (int i = 0; i < annotableParameterCount; ++i) {
+ AnnotationWriter annotationWriter = annotationWriters[i];
+ attributeSize +=
+ annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8;
+ }
+ return attributeSize;
+ }
+
+ /**
+ * Puts a Runtime[In]VisibleParameterAnnotations attribute containing all the annotation lists
+ * from the given AnnotationWriter sub-array in the given ByteVector.
+ *
+ * @param attributeNameIndex constant pool index of the attribute name (one of
+ * Runtime[In]VisibleParameterAnnotations).
+ * @param annotationWriters an array of AnnotationWriter lists (designated by their last
+ * element).
+ * @param annotableParameterCount the number of elements in annotationWriters to put (elements
+ * [0..annotableParameterCount[ are put).
+ * @param output where the attribute must be put.
+ */
+ static void putParameterAnnotations(
+ final int attributeNameIndex,
+ final AnnotationWriter[] annotationWriters,
+ final int annotableParameterCount,
+ final ByteVector output) {
+ // The num_parameters field uses 1 byte, and each element of the parameter_annotations array
+ // uses 2 bytes for its num_annotations field.
+ int attributeLength = 1 + 2 * annotableParameterCount;
+ for (int i = 0; i < annotableParameterCount; ++i) {
+ AnnotationWriter annotationWriter = annotationWriters[i];
+ attributeLength +=
+ annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8;
+ }
+ output.putShort(attributeNameIndex);
+ output.putInt(attributeLength);
+ output.putByte(annotableParameterCount);
+ for (int i = 0; i < annotableParameterCount; ++i) {
+ AnnotationWriter annotationWriter = annotationWriters[i];
+ AnnotationWriter firstAnnotation = null;
+ int numAnnotations = 0;
+ while (annotationWriter != null) {
+ // In case user the forgot to call visitEnd().
+ annotationWriter.visitEnd();
+ numAnnotations++;
+ firstAnnotation = annotationWriter;
+ annotationWriter = annotationWriter.previousAnnotation;
+ }
+ output.putShort(numAnnotations);
+ annotationWriter = firstAnnotation;
+ while (annotationWriter != null) {
+ output.putByteArray(
+ annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
+ annotationWriter = annotationWriter.nextAnnotation;
+ }
}
+ }
}
diff --git a/src/jvm/clojure/asm/Attribute.java b/src/jvm/clojure/asm/Attribute.java
index 1259c3c3e0..dcbe935cd7 100644
--- a/src/jvm/clojure/asm/Attribute.java
+++ b/src/jvm/clojure/asm/Attribute.java
@@ -1,255 +1,323 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A non standard class, field, method or code attribute.
+ * A non standard class, field, method or code attribute, as defined in the Java Virtual Machine
+ * Specification (JVMS).
*
+ * @see JVMS
+ * 4.7
+ * @see JVMS
+ * 4.7.3
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class Attribute {
- /**
- * The type of this attribute.
- */
- public final String type;
-
- /**
- * The raw value of this attribute, used only for unknown attributes.
- */
- byte[] value;
-
- /**
- * The next attribute in this attribute list. May be null.
- */
- Attribute next;
-
- /**
- * Constructs a new empty attribute.
- *
- * @param type
- * the type of the attribute.
- */
- protected Attribute(final String type) {
- this.type = type;
- }
+ /** The type of this attribute, also called its name in the JVMS. */
+ public final String type;
- /**
- * Returns true if this type of attribute is unknown. The default
- * implementation of this method always returns true.
- *
- * @return true if this type of attribute is unknown.
- */
- public boolean isUnknown() {
- return true;
- }
+ /**
+ * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}).
+ * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not
+ * included.
+ */
+ private byte[] content;
- /**
- * Returns true if this type of attribute is a code attribute.
- *
- * @return true if this type of attribute is a code attribute.
- */
- public boolean isCodeAttribute() {
- return false;
- }
+ /**
+ * The next attribute in this attribute list (Attribute instances can be linked via this field to
+ * store a list of class, field, method or code attributes). May be null.
+ */
+ Attribute nextAttribute;
+
+ /**
+ * Constructs a new empty attribute.
+ *
+ * @param type the type of the attribute.
+ */
+ protected Attribute(final String type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns true if this type of attribute is unknown. This means that the attribute
+ * content can't be parsed to extract constant pool references, labels, etc. Instead, the
+ * attribute content is read as an opaque byte array, and written back as is. This can lead to
+ * invalid attributes, if the content actually contains constant pool references, labels, or other
+ * symbolic references that need to be updated when there are changes to the constant pool, the
+ * method bytecode, etc. The default implementation of this method always returns true.
+ *
+ * @return true if this type of attribute is unknown.
+ */
+ public boolean isUnknown() {
+ return true;
+ }
+
+ /**
+ * Returns true if this type of attribute is a code attribute.
+ *
+ * @return true if this type of attribute is a code attribute.
+ */
+ public boolean isCodeAttribute() {
+ return false;
+ }
+
+ /**
+ * Returns the labels corresponding to this attribute.
+ *
+ * @return the labels corresponding to this attribute, or null if this attribute is not a
+ * code attribute that contains labels.
+ */
+ protected Label[] getLabels() {
+ return new Label[0];
+ }
- /**
- * Returns the labels corresponding to this attribute.
- *
- * @return the labels corresponding to this attribute, or null if
- * this attribute is not a code attribute that contains labels.
- */
- protected Label[] getLabels() {
- return null;
+ /**
+ * Reads a {@link #type} attribute. This method must return a new {@link Attribute} object,
+ * of type {@link #type}, corresponding to the 'length' bytes starting at 'offset', in the given
+ * ClassReader.
+ *
+ * @param classReader the class that contains the attribute to be read.
+ * @param offset index of the first byte of the attribute's content in {@link ClassReader#b}. The
+ * 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param length the length of the attribute's content (excluding the 6 attribute header bytes).
+ * @param charBuffer the buffer to be used to call the ClassReader methods requiring a
+ * 'charBuffer' parameter.
+ * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute
+ * in {@link ClassReader#b}, or -1 if the attribute to be read is not a code attribute. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param labels the labels of the method's code, or null if the attribute to be read is
+ * not a code attribute.
+ * @return a new {@link Attribute} object corresponding to the specified bytes.
+ */
+ protected Attribute read(
+ final ClassReader classReader,
+ final int offset,
+ final int length,
+ final char[] charBuffer,
+ final int codeAttributeOffset,
+ final Label[] labels) {
+ Attribute attribute = new Attribute(type);
+ attribute.content = new byte[length];
+ System.arraycopy(classReader.b, offset, attribute.content, 0, length);
+ return attribute;
+ }
+
+ /**
+ * Returns the byte array form of the content of this attribute. The 6 header bytes
+ * (attribute_name_index and attribute_length) must not be added in the returned
+ * ByteVector.
+ *
+ * @param classWriter the class to which this attribute must be added. This parameter can be used
+ * to add the items that corresponds to this attribute to the constant pool of this class.
+ * @param code the bytecode of the method corresponding to this code attribute, or null
+ * if this attribute is not a code attribute. Corresponds to the 'code' field of the Code
+ * attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to this code
+ * attribute, or 0 if this attribute is not a code attribute. Corresponds to the 'code_length'
+ * field of the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to this code attribute, or
+ * -1 if this attribute is not a code attribute.
+ * @param maxLocals the maximum number of local variables of the method corresponding to this code
+ * attribute, or -1 if this attribute is not a code attribute.
+ * @return the byte array form of this attribute.
+ */
+ protected ByteVector write(
+ final ClassWriter classWriter,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals) {
+ return new ByteVector(content);
+ }
+
+ /**
+ * Returns the number of attributes of the attribute list that begins with this attribute.
+ *
+ * @return the number of attributes of the attribute list that begins with this attribute.
+ */
+ final int getAttributeCount() {
+ int count = 0;
+ Attribute attribute = this;
+ while (attribute != null) {
+ count += 1;
+ attribute = attribute.nextAttribute;
}
+ return count;
+ }
+
+ /**
+ * Returns the total size in bytes of all the attributes in the attribute list that begins with
+ * this attribute. This size includes the 6 header bytes (attribute_name_index and
+ * attribute_length) per attribute. Also adds the attribute type names to the constant pool.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @return the size of all the attributes in this attribute list. This size includes the size of
+ * the attribute headers.
+ */
+ final int computeAttributesSize(final SymbolTable symbolTable) {
+ final byte[] code = null;
+ final int codeLength = 0;
+ final int maxStack = -1;
+ final int maxLocals = -1;
+ return computeAttributesSize(symbolTable, code, codeLength, maxStack, maxLocals);
+ }
- /**
- * Reads a {@link #type type} attribute. This method must return a
- * new {@link Attribute} object, of type {@link #type type},
- * corresponding to the len bytes starting at the given offset, in
- * the given class reader.
- *
- * @param cr
- * the class that contains the attribute to be read.
- * @param off
- * index of the first byte of the attribute's content in
- * {@link ClassReader#b cr.b}. The 6 attribute header bytes,
- * containing the type and the length of the attribute, are not
- * taken into account here.
- * @param len
- * the length of the attribute's content.
- * @param buf
- * buffer to be used to call {@link ClassReader#readUTF8
- * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass}
- * or {@link ClassReader#readConst readConst}.
- * @param codeOff
- * index of the first byte of code's attribute content in
- * {@link ClassReader#b cr.b}, or -1 if the attribute to be read
- * is not a code attribute. The 6 attribute header bytes,
- * containing the type and the length of the attribute, are not
- * taken into account here.
- * @param labels
- * the labels of the method's code, or null if the
- * attribute to be read is not a code attribute.
- * @return a new {@link Attribute} object corresponding to the given
- * bytes.
- */
- protected Attribute read(final ClassReader cr, final int off,
- final int len, final char[] buf, final int codeOff,
- final Label[] labels) {
- Attribute attr = new Attribute(type);
- attr.value = new byte[len];
- System.arraycopy(cr.b, off, attr.value, 0, len);
- return attr;
+ /**
+ * Returns the total size in bytes of all the attributes in the attribute list that begins with
+ * this attribute. This size includes the 6 header bytes (attribute_name_index and
+ * attribute_length) per attribute. Also adds the attribute type names to the constant pool.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param code the bytecode of the method corresponding to these code attributes, or null
+ * if they are not code attributes. Corresponds to the 'code' field of the Code attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to these code
+ * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of
+ * the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to these code attributes, or
+ * -1 if they are not code attributes.
+ * @param maxLocals the maximum number of local variables of the method corresponding to these
+ * code attributes, or -1 if they are not code attribute.
+ * @return the size of all the attributes in this attribute list. This size includes the size of
+ * the attribute headers.
+ */
+ final int computeAttributesSize(
+ final SymbolTable symbolTable,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals) {
+ final ClassWriter classWriter = symbolTable.classWriter;
+ int size = 0;
+ Attribute attribute = this;
+ while (attribute != null) {
+ symbolTable.addConstantUtf8(attribute.type);
+ size += 6 + attribute.write(classWriter, code, codeLength, maxStack, maxLocals).length;
+ attribute = attribute.nextAttribute;
}
+ return size;
+ }
- /**
- * Returns the byte array form of this attribute.
- *
- * @param cw
- * the class to which this attribute must be added. This
- * parameter can be used to add to the constant pool of this
- * class the items that corresponds to this attribute.
- * @param code
- * the bytecode of the method corresponding to this code
- * attribute, or null if this attribute is not a code
- * attributes.
- * @param len
- * the length of the bytecode of the method corresponding to this
- * code attribute, or null if this attribute is not a
- * code attribute.
- * @param maxStack
- * the maximum stack size of the method corresponding to this
- * code attribute, or -1 if this attribute is not a code
- * attribute.
- * @param maxLocals
- * the maximum number of local variables of the method
- * corresponding to this code attribute, or -1 if this attribute
- * is not a code attribute.
- * @return the byte array form of this attribute.
- */
- protected ByteVector write(final ClassWriter cw, final byte[] code,
- final int len, final int maxStack, final int maxLocals) {
- ByteVector v = new ByteVector();
- v.data = value;
- v.length = value.length;
- return v;
+ /**
+ * Puts all the attributes of the attribute list that begins with this attribute, in the given
+ * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per
+ * attribute.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param output where the attributes must be written.
+ */
+ final void putAttributes(final SymbolTable symbolTable, final ByteVector output) {
+ final byte[] code = null;
+ final int codeLength = 0;
+ final int maxStack = -1;
+ final int maxLocals = -1;
+ putAttributes(symbolTable, code, codeLength, maxStack, maxLocals, output);
+ }
+
+ /**
+ * Puts all the attributes of the attribute list that begins with this attribute, in the given
+ * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per
+ * attribute.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param code the bytecode of the method corresponding to these code attributes, or null
+ * if they are not code attributes. Corresponds to the 'code' field of the Code attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to these code
+ * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of
+ * the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to these code attributes, or
+ * -1 if they are not code attributes.
+ * @param maxLocals the maximum number of local variables of the method corresponding to these
+ * code attributes, or -1 if they are not code attribute.
+ * @param output where the attributes must be written.
+ */
+ final void putAttributes(
+ final SymbolTable symbolTable,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals,
+ final ByteVector output) {
+ final ClassWriter classWriter = symbolTable.classWriter;
+ Attribute attribute = this;
+ while (attribute != null) {
+ ByteVector attributeContent =
+ attribute.write(classWriter, code, codeLength, maxStack, maxLocals);
+ // Put attribute_name_index and attribute_length.
+ output.putShort(symbolTable.addConstantUtf8(attribute.type)).putInt(attributeContent.length);
+ output.putByteArray(attributeContent.data, 0, attributeContent.length);
+ attribute = attribute.nextAttribute;
}
+ }
+
+ /** A set of attribute prototypes (attributes with the same type are considered equal). */
+ static final class Set {
- /**
- * Returns the length of the attribute list that begins with this attribute.
- *
- * @return the length of the attribute list that begins with this attribute.
- */
- final int getCount() {
- int count = 0;
- Attribute attr = this;
- while (attr != null) {
- count += 1;
- attr = attr.next;
+ private static final int SIZE_INCREMENT = 6;
+
+ private int size;
+ private Attribute[] data = new Attribute[SIZE_INCREMENT];
+
+ void addAttributes(final Attribute attributeList) {
+ Attribute attribute = attributeList;
+ while (attribute != null) {
+ if (!contains(attribute)) {
+ add(attribute);
}
- return count;
+ attribute = attribute.nextAttribute;
+ }
}
- /**
- * Returns the size of all the attributes in this attribute list.
- *
- * @param cw
- * the class writer to be used to convert the attributes into
- * byte arrays, with the {@link #write write} method.
- * @param code
- * the bytecode of the method corresponding to these code
- * attributes, or null if these attributes are not code
- * attributes.
- * @param len
- * the length of the bytecode of the method corresponding to
- * these code attributes, or null if these attributes
- * are not code attributes.
- * @param maxStack
- * the maximum stack size of the method corresponding to these
- * code attributes, or -1 if these attributes are not code
- * attributes.
- * @param maxLocals
- * the maximum number of local variables of the method
- * corresponding to these code attributes, or -1 if these
- * attributes are not code attributes.
- * @return the size of all the attributes in this attribute list. This size
- * includes the size of the attribute headers.
- */
- final int getSize(final ClassWriter cw, final byte[] code, final int len,
- final int maxStack, final int maxLocals) {
- Attribute attr = this;
- int size = 0;
- while (attr != null) {
- cw.newUTF8(attr.type);
- size += attr.write(cw, code, len, maxStack, maxLocals).length + 6;
- attr = attr.next;
- }
- return size;
+ Attribute[] toArray() {
+ Attribute[] result = new Attribute[size];
+ System.arraycopy(data, 0, result, 0, size);
+ return result;
}
- /**
- * Writes all the attributes of this attribute list in the given byte
- * vector.
- *
- * @param cw
- * the class writer to be used to convert the attributes into
- * byte arrays, with the {@link #write write} method.
- * @param code
- * the bytecode of the method corresponding to these code
- * attributes, or null if these attributes are not code
- * attributes.
- * @param len
- * the length of the bytecode of the method corresponding to
- * these code attributes, or null if these attributes
- * are not code attributes.
- * @param maxStack
- * the maximum stack size of the method corresponding to these
- * code attributes, or -1 if these attributes are not code
- * attributes.
- * @param maxLocals
- * the maximum number of local variables of the method
- * corresponding to these code attributes, or -1 if these
- * attributes are not code attributes.
- * @param out
- * where the attributes must be written.
- */
- final void put(final ClassWriter cw, final byte[] code, final int len,
- final int maxStack, final int maxLocals, final ByteVector out) {
- Attribute attr = this;
- while (attr != null) {
- ByteVector b = attr.write(cw, code, len, maxStack, maxLocals);
- out.putShort(cw.newUTF8(attr.type)).putInt(b.length);
- out.putByteArray(b.data, 0, b.length);
- attr = attr.next;
+ private boolean contains(final Attribute attribute) {
+ for (int i = 0; i < size; ++i) {
+ if (data[i].type.equals(attribute.type)) {
+ return true;
}
+ }
+ return false;
+ }
+
+ private void add(final Attribute attribute) {
+ if (size >= data.length) {
+ Attribute[] newData = new Attribute[data.length + SIZE_INCREMENT];
+ System.arraycopy(data, 0, newData, 0, size);
+ data = newData;
+ }
+ data[size++] = attribute;
}
+ }
}
diff --git a/src/jvm/clojure/asm/ByteVector.java b/src/jvm/clojure/asm/ByteVector.java
index 0e4f861905..066dec8a62 100644
--- a/src/jvm/clojure/asm/ByteVector.java
+++ b/src/jvm/clojure/asm/ByteVector.java
@@ -1,306 +1,360 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A dynamically extensible vector of bytes. This class is roughly equivalent to
- * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient.
+ * A dynamically extensible vector of bytes. This class is roughly equivalent to a DataOutputStream
+ * on top of a ByteArrayOutputStream, but is more efficient.
*
* @author Eric Bruneton
*/
public class ByteVector {
- /**
- * The content of this vector.
- */
- byte[] data;
+ /** The content of this vector. Only the first {@link #length} bytes contain real data. */
+ byte[] data;
+
+ /** The actual number of bytes in this vector. */
+ int length;
- /**
- * Actual number of bytes in this vector.
- */
- int length;
+ /** Constructs a new {@link ByteVector} with a default initial capacity. */
+ public ByteVector() {
+ data = new byte[64];
+ }
- /**
- * Constructs a new {@link ByteVector ByteVector} with a default initial
- * size.
- */
- public ByteVector() {
- data = new byte[64];
+ /**
+ * Constructs a new {@link ByteVector} with the given initial capacity.
+ *
+ * @param initialCapacity the initial capacity of the byte vector to be constructed.
+ */
+ public ByteVector(final int initialCapacity) {
+ data = new byte[initialCapacity];
+ }
+
+ /**
+ * Constructs a new {@link ByteVector} from the given initial data.
+ *
+ * @param data the initial data of the new byte vector.
+ */
+ ByteVector(final byte[] data) {
+ this.data = data;
+ this.length = data.length;
+ }
+
+ /**
+ * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param byteValue a byte.
+ * @return this byte vector.
+ */
+ public ByteVector putByte(final int byteValue) {
+ int currentLength = length;
+ if (currentLength + 1 > data.length) {
+ enlarge(1);
}
+ data[currentLength++] = (byte) byteValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Constructs a new {@link ByteVector ByteVector} with the given initial
- * size.
- *
- * @param initialSize
- * the initial size of the byte vector to be constructed.
- */
- public ByteVector(final int initialSize) {
- data = new byte[initialSize];
+ /**
+ * Puts two bytes into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param byteValue1 a byte.
+ * @param byteValue2 another byte.
+ * @return this byte vector.
+ */
+ final ByteVector put11(final int byteValue1, final int byteValue2) {
+ int currentLength = length;
+ if (currentLength + 2 > data.length) {
+ enlarge(2);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue1;
+ currentData[currentLength++] = (byte) byteValue2;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a byte into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param b
- * a byte.
- * @return this byte vector.
- */
- public ByteVector putByte(final int b) {
- int length = this.length;
- if (length + 1 > data.length) {
- enlarge(1);
- }
- data[length++] = (byte) b;
- this.length = length;
- return this;
+ /**
+ * Puts a short into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param shortValue a short.
+ * @return this byte vector.
+ */
+ public ByteVector putShort(final int shortValue) {
+ int currentLength = length;
+ if (currentLength + 2 > data.length) {
+ enlarge(2);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) (shortValue >>> 8);
+ currentData[currentLength++] = (byte) shortValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts two bytes into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param b1
- * a byte.
- * @param b2
- * another byte.
- * @return this byte vector.
- */
- ByteVector put11(final int b1, final int b2) {
- int length = this.length;
- if (length + 2 > data.length) {
- enlarge(2);
- }
- byte[] data = this.data;
- data[length++] = (byte) b1;
- data[length++] = (byte) b2;
- this.length = length;
- return this;
+ /**
+ * Puts a byte and a short into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param byteValue a byte.
+ * @param shortValue a short.
+ * @return this byte vector.
+ */
+ final ByteVector put12(final int byteValue, final int shortValue) {
+ int currentLength = length;
+ if (currentLength + 3 > data.length) {
+ enlarge(3);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue;
+ currentData[currentLength++] = (byte) (shortValue >>> 8);
+ currentData[currentLength++] = (byte) shortValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a short into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param s
- * a short.
- * @return this byte vector.
- */
- public ByteVector putShort(final int s) {
- int length = this.length;
- if (length + 2 > data.length) {
- enlarge(2);
- }
- byte[] data = this.data;
- data[length++] = (byte) (s >>> 8);
- data[length++] = (byte) s;
- this.length = length;
- return this;
+ /**
+ * Puts two bytes and a short into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param byteValue1 a byte.
+ * @param byteValue2 another byte.
+ * @param shortValue a short.
+ * @return this byte vector.
+ */
+ final ByteVector put112(final int byteValue1, final int byteValue2, final int shortValue) {
+ int currentLength = length;
+ if (currentLength + 4 > data.length) {
+ enlarge(4);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue1;
+ currentData[currentLength++] = (byte) byteValue2;
+ currentData[currentLength++] = (byte) (shortValue >>> 8);
+ currentData[currentLength++] = (byte) shortValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a byte and a short into this byte vector. The byte vector is
- * automatically enlarged if necessary.
- *
- * @param b
- * a byte.
- * @param s
- * a short.
- * @return this byte vector.
- */
- ByteVector put12(final int b, final int s) {
- int length = this.length;
- if (length + 3 > data.length) {
- enlarge(3);
- }
- byte[] data = this.data;
- data[length++] = (byte) b;
- data[length++] = (byte) (s >>> 8);
- data[length++] = (byte) s;
- this.length = length;
- return this;
+ /**
+ * Puts an int into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param intValue an int.
+ * @return this byte vector.
+ */
+ public ByteVector putInt(final int intValue) {
+ int currentLength = length;
+ if (currentLength + 4 > data.length) {
+ enlarge(4);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) (intValue >>> 24);
+ currentData[currentLength++] = (byte) (intValue >>> 16);
+ currentData[currentLength++] = (byte) (intValue >>> 8);
+ currentData[currentLength++] = (byte) intValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts an int into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param i
- * an int.
- * @return this byte vector.
- */
- public ByteVector putInt(final int i) {
- int length = this.length;
- if (length + 4 > data.length) {
- enlarge(4);
- }
- byte[] data = this.data;
- data[length++] = (byte) (i >>> 24);
- data[length++] = (byte) (i >>> 16);
- data[length++] = (byte) (i >>> 8);
- data[length++] = (byte) i;
- this.length = length;
- return this;
+ /**
+ * Puts one byte and two shorts into this byte vector. The byte vector is automatically enlarged
+ * if necessary.
+ *
+ * @param byteValue a byte.
+ * @param shortValue1 a short.
+ * @param shortValue2 another short.
+ * @return this byte vector.
+ */
+ final ByteVector put122(final int byteValue, final int shortValue1, final int shortValue2) {
+ int currentLength = length;
+ if (currentLength + 5 > data.length) {
+ enlarge(5);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue;
+ currentData[currentLength++] = (byte) (shortValue1 >>> 8);
+ currentData[currentLength++] = (byte) shortValue1;
+ currentData[currentLength++] = (byte) (shortValue2 >>> 8);
+ currentData[currentLength++] = (byte) shortValue2;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a long into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param l
- * a long.
- * @return this byte vector.
- */
- public ByteVector putLong(final long l) {
- int length = this.length;
- if (length + 8 > data.length) {
- enlarge(8);
- }
- byte[] data = this.data;
- int i = (int) (l >>> 32);
- data[length++] = (byte) (i >>> 24);
- data[length++] = (byte) (i >>> 16);
- data[length++] = (byte) (i >>> 8);
- data[length++] = (byte) i;
- i = (int) l;
- data[length++] = (byte) (i >>> 24);
- data[length++] = (byte) (i >>> 16);
- data[length++] = (byte) (i >>> 8);
- data[length++] = (byte) i;
- this.length = length;
- return this;
+ /**
+ * Puts a long into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param longValue a long.
+ * @return this byte vector.
+ */
+ public ByteVector putLong(final long longValue) {
+ int currentLength = length;
+ if (currentLength + 8 > data.length) {
+ enlarge(8);
}
+ byte[] currentData = data;
+ int intValue = (int) (longValue >>> 32);
+ currentData[currentLength++] = (byte) (intValue >>> 24);
+ currentData[currentLength++] = (byte) (intValue >>> 16);
+ currentData[currentLength++] = (byte) (intValue >>> 8);
+ currentData[currentLength++] = (byte) intValue;
+ intValue = (int) longValue;
+ currentData[currentLength++] = (byte) (intValue >>> 24);
+ currentData[currentLength++] = (byte) (intValue >>> 16);
+ currentData[currentLength++] = (byte) (intValue >>> 8);
+ currentData[currentLength++] = (byte) intValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts an UTF8 string into this byte vector. The byte vector is
- * automatically enlarged if necessary.
- *
- * @param s
- * a String.
- * @return this byte vector.
- */
- public ByteVector putUTF8(final String s) {
- int charLength = s.length();
- int len = length;
- if (len + 2 + charLength > data.length) {
- enlarge(2 + charLength);
- }
- byte[] data = this.data;
- // optimistic algorithm: instead of computing the byte length and then
- // serializing the string (which requires two loops), we assume the byte
- // length is equal to char length (which is the most frequent case), and
- // we start serializing the string right away. During the serialization,
- // if we find that this assumption is wrong, we continue with the
- // general method.
- data[len++] = (byte) (charLength >>> 8);
- data[len++] = (byte) charLength;
- for (int i = 0; i < charLength; ++i) {
- char c = s.charAt(i);
- if (c >= '\001' && c <= '\177') {
- data[len++] = (byte) c;
- } else {
- int byteLength = i;
- for (int j = i; j < charLength; ++j) {
- c = s.charAt(j);
- if (c >= '\001' && c <= '\177') {
- byteLength++;
- } else if (c > '\u07FF') {
- byteLength += 3;
- } else {
- byteLength += 2;
- }
- }
- data[length] = (byte) (byteLength >>> 8);
- data[length + 1] = (byte) byteLength;
- if (length + 2 + byteLength > data.length) {
- length = len;
- enlarge(2 + byteLength);
- data = this.data;
- }
- for (int j = i; j < charLength; ++j) {
- c = s.charAt(j);
- if (c >= '\001' && c <= '\177') {
- data[len++] = (byte) c;
- } else if (c > '\u07FF') {
- data[len++] = (byte) (0xE0 | c >> 12 & 0xF);
- data[len++] = (byte) (0x80 | c >> 6 & 0x3F);
- data[len++] = (byte) (0x80 | c & 0x3F);
- } else {
- data[len++] = (byte) (0xC0 | c >> 6 & 0x1F);
- data[len++] = (byte) (0x80 | c & 0x3F);
- }
- }
- break;
- }
- }
- length = len;
- return this;
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param stringValue a String whose UTF8 encoded length must be less than 65536.
+ * @return this byte vector.
+ */
+ public ByteVector putUTF8(final String stringValue) {
+ int charLength = stringValue.length();
+ if (charLength > 65535) {
+ throw new IllegalArgumentException();
+ }
+ int currentLength = length;
+ if (currentLength + 2 + charLength > data.length) {
+ enlarge(2 + charLength);
}
+ byte[] currentData = data;
+ // Optimistic algorithm: instead of computing the byte length and then serializing the string
+ // (which requires two loops), we assume the byte length is equal to char length (which is the
+ // most frequent case), and we start serializing the string right away. During the
+ // serialization, if we find that this assumption is wrong, we continue with the general method.
+ currentData[currentLength++] = (byte) (charLength >>> 8);
+ currentData[currentLength++] = (byte) charLength;
+ for (int i = 0; i < charLength; ++i) {
+ char charValue = stringValue.charAt(i);
+ if (charValue >= '\u0001' && charValue <= '\u007F') {
+ currentData[currentLength++] = (byte) charValue;
+ } else {
+ length = currentLength;
+ return encodeUTF8(stringValue, i, 65535);
+ }
+ }
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts an array of bytes into this byte vector. The byte vector is
- * automatically enlarged if necessary.
- *
- * @param b
- * an array of bytes. May be null to put len
- * null bytes into this byte vector.
- * @param off
- * index of the fist byte of b that must be copied.
- * @param len
- * number of bytes of b that must be copied.
- * @return this byte vector.
- */
- public ByteVector putByteArray(final byte[] b, final int off, final int len) {
- if (length + len > data.length) {
- enlarge(len);
- }
- if (b != null) {
- System.arraycopy(b, off, data, length, len);
- }
- length += len;
- return this;
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if
+ * necessary. The string length is encoded in two bytes before the encoded characters, if there is
+ * space for that (i.e. if this.length - offset - 2 >= 0).
+ *
+ * @param stringValue the String to encode.
+ * @param offset the index of the first character to encode. The previous characters are supposed
+ * to have already been encoded, using only one byte per character.
+ * @param maxByteLength the maximum byte length of the encoded string, including the already
+ * encoded characters.
+ * @return this byte vector.
+ */
+ final ByteVector encodeUTF8(final String stringValue, final int offset, final int maxByteLength) {
+ int charLength = stringValue.length();
+ int byteLength = offset;
+ for (int i = offset; i < charLength; ++i) {
+ char charValue = stringValue.charAt(i);
+ if (charValue >= '\u0001' && charValue <= '\u007F') {
+ byteLength++;
+ } else if (charValue <= '\u07FF') {
+ byteLength += 2;
+ } else {
+ byteLength += 3;
+ }
+ }
+ if (byteLength > maxByteLength) {
+ throw new IllegalArgumentException();
+ }
+ // Compute where 'byteLength' must be stored in 'data', and store it at this location.
+ int byteLengthOffset = length - offset - 2;
+ if (byteLengthOffset >= 0) {
+ data[byteLengthOffset] = (byte) (byteLength >>> 8);
+ data[byteLengthOffset + 1] = (byte) byteLength;
+ }
+ if (length + byteLength - offset > data.length) {
+ enlarge(byteLength - offset);
}
+ int currentLength = length;
+ for (int i = offset; i < charLength; ++i) {
+ char charValue = stringValue.charAt(i);
+ if (charValue >= '\u0001' && charValue <= '\u007F') {
+ data[currentLength++] = (byte) charValue;
+ } else if (charValue <= '\u07FF') {
+ data[currentLength++] = (byte) (0xC0 | charValue >> 6 & 0x1F);
+ data[currentLength++] = (byte) (0x80 | charValue & 0x3F);
+ } else {
+ data[currentLength++] = (byte) (0xE0 | charValue >> 12 & 0xF);
+ data[currentLength++] = (byte) (0x80 | charValue >> 6 & 0x3F);
+ data[currentLength++] = (byte) (0x80 | charValue & 0x3F);
+ }
+ }
+ length = currentLength;
+ return this;
+ }
- /**
- * Enlarge this byte vector so that it can receive n more bytes.
- *
- * @param size
- * number of additional bytes that this byte vector should be
- * able to receive.
- */
- private void enlarge(final int size) {
- int length1 = 2 * data.length;
- int length2 = length + size;
- byte[] newData = new byte[length1 > length2 ? length1 : length2];
- System.arraycopy(data, 0, newData, 0, length);
- data = newData;
+ /**
+ * Puts an array of bytes into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param byteArrayValue an array of bytes. May be null to put byteLength null
+ * bytes into this byte vector.
+ * @param byteOffset index of the first byte of byteArrayValue that must be copied.
+ * @param byteLength number of bytes of byteArrayValue that must be copied.
+ * @return this byte vector.
+ */
+ public ByteVector putByteArray(
+ final byte[] byteArrayValue, final int byteOffset, final int byteLength) {
+ if (length + byteLength > data.length) {
+ enlarge(byteLength);
+ }
+ if (byteArrayValue != null) {
+ System.arraycopy(byteArrayValue, byteOffset, data, length, byteLength);
}
+ length += byteLength;
+ return this;
+ }
+
+ /**
+ * Enlarges this byte vector so that it can receive 'size' more bytes.
+ *
+ * @param size number of additional bytes that this byte vector should be able to receive.
+ */
+ private void enlarge(final int size) {
+ int doubleCapacity = 2 * data.length;
+ int minimalCapacity = length + size;
+ byte[] newData = new byte[doubleCapacity > minimalCapacity ? doubleCapacity : minimalCapacity];
+ System.arraycopy(data, 0, newData, 0, length);
+ data = newData;
+ }
}
diff --git a/src/jvm/clojure/asm/ClassReader.java b/src/jvm/clojure/asm/ClassReader.java
index 9265a37aa9..6502e86d3a 100644
--- a/src/jvm/clojure/asm/ClassReader.java
+++ b/src/jvm/clojure/asm/ClassReader.java
@@ -1,2202 +1,3567 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
- * A Java class parser to make a {@link ClassVisitor} visit an existing class.
- * This class parses a byte array conforming to the Java class file format and
- * calls the appropriate visit methods of a given class visitor for each field,
- * method and bytecode instruction encountered.
+ * A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java
+ * Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the
+ * appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode
+ * instruction encountered.
*
+ * @see JVMS 4
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class ClassReader {
- /**
- * True to enable signatures support.
- */
- static final boolean SIGNATURES = true;
-
- /**
- * True to enable annotations support.
- */
- static final boolean ANNOTATIONS = true;
-
- /**
- * True to enable stack map frames support.
- */
- static final boolean FRAMES = true;
-
- /**
- * True to enable bytecode writing support.
- */
- static final boolean WRITER = true;
-
- /**
- * True to enable JSR_W and GOTO_W support.
- */
- static final boolean RESIZE = true;
-
- /**
- * Flag to skip method code. If this class is set CODE
- * attribute won't be visited. This can be used, for example, to retrieve
- * annotations for methods and method parameters.
- */
- public static final int SKIP_CODE = 1;
-
- /**
- * Flag to skip the debug information in the class. If this flag is set the
- * debug information of the class is not visited, i.e. the
- * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
- * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
- * called.
- */
- public static final int SKIP_DEBUG = 2;
-
- /**
- * Flag to skip the stack map frames in the class. If this flag is set the
- * stack map frames of the class is not visited, i.e. the
- * {@link MethodVisitor#visitFrame visitFrame} method will not be called.
- * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is
- * used: it avoids visiting frames that will be ignored and recomputed from
- * scratch in the class writer.
- */
- public static final int SKIP_FRAMES = 4;
-
- /**
- * Flag to expand the stack map frames. By default stack map frames are
- * visited in their original format (i.e. "expanded" for classes whose
- * version is less than V1_6, and "compressed" for the other classes). If
- * this flag is set, stack map frames are always visited in expanded format
- * (this option adds a decompression/recompression step in ClassReader and
- * ClassWriter which degrades performances quite a lot).
- */
- public static final int EXPAND_FRAMES = 8;
-
- /**
- * The class to be parsed. The content of this array must not be
- * modified. This field is intended for {@link Attribute} sub classes, and
- * is normally not needed by class generators or adapters.
- */
- public final byte[] b;
-
- /**
- * The start index of each constant pool item in {@link #b b}, plus one. The
- * one byte offset skips the constant pool item tag that indicates its type.
- */
- private final int[] items;
-
- /**
- * The String objects corresponding to the CONSTANT_Utf8 items. This cache
- * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
- * which GREATLY improves performances (by a factor 2 to 3). This caching
- * strategy could be extended to all constant pool items, but its benefit
- * would not be so great for these items (because they are much less
- * expensive to parse than CONSTANT_Utf8 items).
- */
- private final String[] strings;
-
- /**
- * Maximum length of the strings contained in the constant pool of the
- * class.
- */
- private final int maxStringLength;
-
- /**
- * Start index of the class header information (access, name...) in
- * {@link #b b}.
- */
- public final int header;
-
- // ------------------------------------------------------------------------
- // Constructors
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param b
- * the bytecode of the class to be read.
- */
- public ClassReader(final byte[] b) {
- this(b, 0, b.length);
- }
-
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param b
- * the bytecode of the class to be read.
- * @param off
- * the start offset of the class data.
- * @param len
- * the length of the class data.
- */
- public ClassReader(final byte[] b, final int off, final int len) {
- this.b = b;
- // checks the class version
- if (readShort(off + 6) > Opcodes.V1_7) {
- throw new IllegalArgumentException();
- }
- // parses the constant pool
- items = new int[readUnsignedShort(off + 8)];
- int n = items.length;
- strings = new String[n];
- int max = 0;
- int index = off + 10;
- for (int i = 1; i < n; ++i) {
- items[i] = index + 1;
- int size;
- switch (b[index]) {
- case ClassWriter.FIELD:
- case ClassWriter.METH:
- case ClassWriter.IMETH:
- case ClassWriter.INT:
- case ClassWriter.FLOAT:
- case ClassWriter.NAME_TYPE:
- case ClassWriter.INDY:
- size = 5;
- break;
- case ClassWriter.LONG:
- case ClassWriter.DOUBLE:
- size = 9;
- ++i;
- break;
- case ClassWriter.UTF8:
- size = 3 + readUnsignedShort(index + 1);
- if (size > max) {
- max = size;
- }
- break;
- case ClassWriter.HANDLE:
- size = 4;
- break;
- // case ClassWriter.CLASS:
- // case ClassWriter.STR:
- // case ClassWriter.MTYPE
- default:
- size = 3;
- break;
- }
- index += size;
- }
- maxStringLength = max;
- // the class header information starts just after the constant pool
- header = index;
- }
-
- /**
- * Returns the class's access flags (see {@link Opcodes}). This value may
- * not reflect Deprecated and Synthetic flags when bytecode is before 1.5
- * and those flags are represented by attributes.
- *
- * @return the class access flags
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public int getAccess() {
- return readUnsignedShort(header);
- }
-
- /**
- * Returns the internal name of the class (see
- * {@link Type#getInternalName() getInternalName}).
- *
- * @return the internal class name
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public String getClassName() {
- return readClass(header + 2, new char[maxStringLength]);
- }
-
- /**
- * Returns the internal of name of the super class (see
- * {@link Type#getInternalName() getInternalName}). For interfaces, the
- * super class is {@link Object}.
- *
- * @return the internal name of super class, or null for
- * {@link Object} class.
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public String getSuperName() {
- return readClass(header + 4, new char[maxStringLength]);
- }
-
- /**
- * Returns the internal names of the class's interfaces (see
- * {@link Type#getInternalName() getInternalName}).
- *
- * @return the array of internal names for all implemented interfaces or
- * null.
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public String[] getInterfaces() {
- int index = header + 6;
- int n = readUnsignedShort(index);
- String[] interfaces = new String[n];
- if (n > 0) {
- char[] buf = new char[maxStringLength];
- for (int i = 0; i < n; ++i) {
- index += 2;
- interfaces[i] = readClass(index, buf);
- }
+ /**
+ * A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed
+ * nor visited.
+ */
+ public static final int SKIP_CODE = 1;
+
+ /**
+ * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable
+ * and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor
+ * visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and
+ * {@link MethodVisitor#visitLineNumber} are not called).
+ */
+ public static final int SKIP_DEBUG = 2;
+
+ /**
+ * A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes
+ * are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag
+ * is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames
+ * that will be ignored and recomputed from scratch.
+ */
+ public static final int SKIP_FRAMES = 4;
+
+ /**
+ * A flag to expand the stack map frames. By default stack map frames are visited in their
+ * original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed"
+ * for the other classes). If this flag is set, stack map frames are always visited in expanded
+ * format (this option adds a decompression/compression step in ClassReader and ClassWriter which
+ * degrades performance quite a lot).
+ */
+ public static final int EXPAND_FRAMES = 8;
+
+ /**
+ * A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode
+ * instructions. When resolving a forward jump it may happen that the signed 2 bytes offset
+ * reserved for it is not sufficient to store the bytecode offset. In this case the jump
+ * instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes
+ * offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing
+ * such instructions, in order to replace them with standard instructions. In addition, when this
+ * flag is used, goto_w and jsr_w are not converted into goto and jsr, to make sure that
+ * infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a
+ * goto_w in ClassWriter cannot occur.
+ */
+ static final int EXPAND_ASM_INSNS = 256;
+
+ /** The size of the temporary byte array used to read class input streams chunk by chunk. */
+ private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
+
+ /**
+ * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array
+ * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally
+ * not needed by class visitors.
+ *
+ *
NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not
+ * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct
+ * ClassFile element offsets within this byte array.
+ */
+ public final byte[] b;
+
+ /**
+ * The offset in bytes, in {@link #b}, of each cp_info entry of the ClassFile's constant_pool
+ * array, plus one. In other words, the offset of constant pool entry i is given by
+ * cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - 1].
+ */
+ private final int[] cpInfoOffsets;
+
+ /**
+ * The value of each cp_info entry of the ClassFile's constant_pool array, for Constant_Utf8
+ * and Constant_Dynamic constants only. The value of constant pool entry i is given by
+ * cpInfoValues[i]. This cache avoids multiple parsing of those constant pool items.
+ */
+ private final Object[] cpInfoValues;
+
+ /**
+ * The start offsets in {@link #b} of each element of the bootstrap_methods array (in the
+ * BootstrapMethods attribute).
+ *
+ * @see JVMS
+ * 4.7.23
+ */
+ private final int[] bootstrapMethodOffsets;
+
+ /**
+ * A conservative estimate of the maximum length of the strings contained in the constant pool of
+ * the class.
+ */
+ private final int maxStringLength;
+
+ /** The offset in bytes, in {@link #b}, of the ClassFile's access_flags field. */
+ public final int header;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param classFile the JVMS ClassFile structure to be read.
+ */
+ public ClassReader(final byte[] classFile) {
+ this(classFile, 0, classFile.length);
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
+ * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
+ * @param classFileLength the length in bytes of the ClassFile to be read.
+ */
+ public ClassReader(
+ final byte[] classFileBuffer, final int classFileOffset, final int classFileLength) {
+ this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true);
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object. This internal constructor must not be exposed
+ * as a public API.
+ *
+ * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
+ * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
+ * @param checkClassVersion whether to check the class version or not.
+ */
+ ClassReader(
+ final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
+ this.b = classFileBuffer;
+ // Check the class' major_version. This field is after the magic and minor_version fields, which
+ // use 4 and 2 bytes respectively.
+ if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V11) {
+ throw new IllegalArgumentException(
+ "Unsupported class file major version " + readShort(classFileOffset + 6));
+ }
+ // Create the constant pool arrays. The constant_pool_count field is after the magic,
+ // minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
+ int constantPoolCount = readUnsignedShort(classFileOffset + 8);
+ cpInfoOffsets = new int[constantPoolCount];
+ cpInfoValues = new Object[constantPoolCount];
+ // Compute the offset of each constant pool entry, as well as a conservative estimate of the
+ // maximum length of the constant pool strings. The first constant pool entry is after the
+ // magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2
+ // bytes respectively.
+ int currentCpInfoIndex = 1;
+ int currentCpInfoOffset = classFileOffset + 10;
+ int currentMaxStringLength = 0;
+ // The offset of the other entries depend on the total size of all the previous entries.
+ while (currentCpInfoIndex < constantPoolCount) {
+ cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1;
+ int cpInfoSize;
+ switch (classFileBuffer[currentCpInfoOffset]) {
+ case Symbol.CONSTANT_FIELDREF_TAG:
+ case Symbol.CONSTANT_METHODREF_TAG:
+ case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
+ case Symbol.CONSTANT_INTEGER_TAG:
+ case Symbol.CONSTANT_FLOAT_TAG:
+ case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
+ case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ cpInfoSize = 5;
+ break;
+ case Symbol.CONSTANT_LONG_TAG:
+ case Symbol.CONSTANT_DOUBLE_TAG:
+ cpInfoSize = 9;
+ currentCpInfoIndex++;
+ break;
+ case Symbol.CONSTANT_UTF8_TAG:
+ cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1);
+ if (cpInfoSize > currentMaxStringLength) {
+ // The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate
+ // of the length in characters of the corresponding string, and is much cheaper to
+ // compute than this exact length.
+ currentMaxStringLength = cpInfoSize;
+ }
+ break;
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ cpInfoSize = 4;
+ break;
+ case Symbol.CONSTANT_CLASS_TAG:
+ case Symbol.CONSTANT_STRING_TAG:
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ case Symbol.CONSTANT_PACKAGE_TAG:
+ case Symbol.CONSTANT_MODULE_TAG:
+ cpInfoSize = 3;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ currentCpInfoOffset += cpInfoSize;
+ }
+ this.maxStringLength = currentMaxStringLength;
+ // The Classfile's access_flags field is just after the last constant pool entry.
+ this.header = currentCpInfoOffset;
+
+ // Read the BootstrapMethods attribute, if any (only get the offset of each method).
+ int currentAttributeOffset = getFirstAttributeOffset();
+ int[] currentBootstrapMethodOffsets = null;
+ for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentAttributeOffset, new char[maxStringLength]);
+ int attributeLength = readInt(currentAttributeOffset + 2);
+ currentAttributeOffset += 6;
+ if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ // Read the num_bootstrap_methods field and create an array of this size.
+ currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)];
+ // Compute and store the offset of each 'bootstrap_methods' array field entry.
+ int currentBootstrapMethodOffset = currentAttributeOffset + 2;
+ for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) {
+ currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset;
+ // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each),
+ // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2).
+ currentBootstrapMethodOffset +=
+ 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2;
}
- return interfaces;
- }
-
- /**
- * Copies the constant pool data into the given {@link ClassWriter}. Should
- * be called before the {@link #accept(ClassVisitor,int)} method.
- *
- * @param classWriter
- * the {@link ClassWriter} to copy constant pool into.
- */
- void copyPool(final ClassWriter classWriter) {
- char[] buf = new char[maxStringLength];
- int ll = items.length;
- Item[] items2 = new Item[ll];
- for (int i = 1; i < ll; i++) {
- int index = items[i];
- int tag = b[index - 1];
- Item item = new Item(i);
- int nameType;
- switch (tag) {
- case ClassWriter.FIELD:
- case ClassWriter.METH:
- case ClassWriter.IMETH:
- nameType = items[readUnsignedShort(index + 2)];
- item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
- readUTF8(nameType + 2, buf));
- break;
- case ClassWriter.INT:
- item.set(readInt(index));
- break;
- case ClassWriter.FLOAT:
- item.set(Float.intBitsToFloat(readInt(index)));
- break;
- case ClassWriter.NAME_TYPE:
- item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
- null);
- break;
- case ClassWriter.LONG:
- item.set(readLong(index));
- ++i;
- break;
- case ClassWriter.DOUBLE:
- item.set(Double.longBitsToDouble(readLong(index)));
- ++i;
- break;
- case ClassWriter.UTF8: {
- String s = strings[i];
- if (s == null) {
- index = items[i];
- s = strings[i] = readUTF(index + 2,
- readUnsignedShort(index), buf);
- }
- item.set(tag, s, null, null);
- break;
- }
- case ClassWriter.HANDLE: {
- int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
- nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
- item.set(ClassWriter.HANDLE_BASE + readByte(index),
- readClass(fieldOrMethodRef, buf),
- readUTF8(nameType, buf), readUTF8(nameType + 2, buf));
- break;
- }
- case ClassWriter.INDY:
- if (classWriter.bootstrapMethods == null) {
- copyBootstrapMethods(classWriter, items2, buf);
- }
- nameType = items[readUnsignedShort(index + 2)];
- item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
- readUnsignedShort(index));
- break;
- // case ClassWriter.STR:
- // case ClassWriter.CLASS:
- // case ClassWriter.MTYPE
- default:
- item.set(tag, readUTF8(index, buf), null, null);
- break;
- }
+ }
+ currentAttributeOffset += attributeLength;
+ }
+ this.bootstrapMethodOffsets = currentBootstrapMethodOffsets;
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param inputStream an input stream of the JVMS ClassFile structure to be read. This input
+ * stream must contain nothing more than the ClassFile structure itself. It is read from its
+ * current position to its end.
+ * @throws IOException if a problem occurs during reading.
+ */
+ public ClassReader(final InputStream inputStream) throws IOException {
+ this(readStream(inputStream, false));
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param className the fully qualified name of the class to be read. The ClassFile structure is
+ * retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}.
+ * @throws IOException if an exception occurs during reading.
+ */
+ public ClassReader(final String className) throws IOException {
+ this(
+ readStream(
+ ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
+ }
+
+ /**
+ * Reads the given input stream and returns its content as a byte array.
+ *
+ * @param inputStream an input stream.
+ * @param close true to close the input stream after reading.
+ * @return the content of the given input stream.
+ * @throws IOException if a problem occurs during reading.
+ */
+ private static byte[] readStream(final InputStream inputStream, final boolean close)
+ throws IOException {
+ if (inputStream == null) {
+ throw new IOException("Class not found");
+ }
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
+ outputStream.write(data, 0, bytesRead);
+ }
+ outputStream.flush();
+ return outputStream.toByteArray();
+ } finally {
+ if (close) {
+ inputStream.close();
+ }
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated
+ * and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes.
+ *
+ * @return the class access flags.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public int getAccess() {
+ return readUnsignedShort(header);
+ }
+
+ /**
+ * Returns the internal name of the class (see {@link Type#getInternalName()}).
+ *
+ * @return the internal class name.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getClassName() {
+ // this_class is just after the access_flags field (using 2 bytes).
+ return readClass(header + 2, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
+ * interfaces, the super class is {@link Object}.
+ *
+ * @return the internal name of the super class, or null for {@link Object} class.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getSuperName() {
+ // super_class is after the access_flags and this_class fields (2 bytes each).
+ return readClass(header + 4, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}).
+ *
+ * @return the internal names of the directly implemented interfaces. Inherited implemented
+ * interfaces are not returned.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String[] getInterfaces() {
+ // interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each).
+ int currentOffset = header + 6;
+ int interfacesCount = readUnsignedShort(currentOffset);
+ String[] interfaces = new String[interfacesCount];
+ if (interfacesCount > 0) {
+ char[] charBuffer = new char[maxStringLength];
+ for (int i = 0; i < interfacesCount; ++i) {
+ currentOffset += 2;
+ interfaces[i] = readClass(currentOffset, charBuffer);
+ }
+ }
+ return interfaces;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Public methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
+ * {@link ClassReader}.
+ *
+ * @param classVisitor the visitor that must visit this class.
+ * @param parsingOptions the options to use to parse this class. One or more of {@link
+ * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
+ */
+ public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
+ accept(classVisitor, new Attribute[0], parsingOptions);
+ }
+
+ /**
+ * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
+ * {@link ClassReader}.
+ *
+ * @param classVisitor the visitor that must visit this class.
+ * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
+ * the class. Any attribute whose type is not equal to the type of one the prototypes will not
+ * be parsed: its byte array value will be passed unchanged to the ClassWriter. This may
+ * corrupt it if this value contains references to the constant pool, or has syntactic or
+ * semantic links with a class element that has been transformed by a class adapter between
+ * the reader and the writer.
+ * @param parsingOptions the options to use to parse this class. One or more of {@link
+ * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
+ */
+ public void accept(
+ final ClassVisitor classVisitor,
+ final Attribute[] attributePrototypes,
+ final int parsingOptions) {
+ Context context = new Context();
+ context.attributePrototypes = attributePrototypes;
+ context.parsingOptions = parsingOptions;
+ context.charBuffer = new char[maxStringLength];
+
+ // Read the access_flags, this_class, super_class, interface_count and interfaces fields.
+ char[] charBuffer = context.charBuffer;
+ int currentOffset = header;
+ int accessFlags = readUnsignedShort(currentOffset);
+ String thisClass = readClass(currentOffset + 2, charBuffer);
+ String superClass = readClass(currentOffset + 4, charBuffer);
+ String[] interfaces = new String[readUnsignedShort(currentOffset + 6)];
+ currentOffset += 8;
+ for (int i = 0; i < interfaces.length; ++i) {
+ interfaces[i] = readClass(currentOffset, charBuffer);
+ currentOffset += 2;
+ }
- int index2 = item.hashCode % items2.length;
- item.next = items2[index2];
- items2[index2] = item;
- }
+ // Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS).
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The offset of the InnerClasses attribute, or 0.
+ int innerClassesOffset = 0;
+ // - The offset of the EnclosingMethod attribute, or 0.
+ int enclosingMethodOffset = 0;
+ // - The string corresponding to the Signature attribute, or null.
+ String signature = null;
+ // - The string corresponding to the SourceFile attribute, or null.
+ String sourceFile = null;
+ // - The string corresponding to the SourceDebugExtension attribute, or null.
+ String sourceDebugExtension = null;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The offset of the Module attribute, or 0.
+ int moduleOffset = 0;
+ // - The offset of the ModulePackages attribute, or 0.
+ int modulePackagesOffset = 0;
+ // - The string corresponding to the ModuleMainClass attribute, or null.
+ String moduleMainClass = null;
+ // - The string corresponding to the NestHost attribute, or null.
+ String nestHostClass = null;
+ // - The offset of the NestMembers attribute, or 0.
+ int nestMembersOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int currentAttributeOffset = getFirstAttributeOffset();
+ for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentAttributeOffset, charBuffer);
+ int attributeLength = readInt(currentAttributeOffset + 2);
+ currentAttributeOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.SOURCE_FILE.equals(attributeName)) {
+ sourceFile = readUTF8(currentAttributeOffset, charBuffer);
+ } else if (Constants.INNER_CLASSES.equals(attributeName)) {
+ innerClassesOffset = currentAttributeOffset;
+ } else if (Constants.ENCLOSING_METHOD.equals(attributeName)) {
+ enclosingMethodOffset = currentAttributeOffset;
+ } else if (Constants.NEST_HOST.equals(attributeName)) {
+ nestHostClass = readClass(currentAttributeOffset, charBuffer);
+ } else if (Constants.NEST_MEMBERS.equals(attributeName)) {
+ nestMembersOffset = currentAttributeOffset;
+ } else if (Constants.SIGNATURE.equals(attributeName)) {
+ signature = readUTF8(currentAttributeOffset, charBuffer);
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.DEPRECATED.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_DEPRECATED;
+ } else if (Constants.SYNTHETIC.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_SYNTHETIC;
+ } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
+ sourceDebugExtension =
+ readUTF(currentAttributeOffset, attributeLength, new char[attributeLength]);
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.MODULE.equals(attributeName)) {
+ moduleOffset = currentAttributeOffset;
+ } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
+ moduleMainClass = readClass(currentAttributeOffset, charBuffer);
+ } else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
+ modulePackagesOffset = currentAttributeOffset;
+ } else if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ // This attribute is read in the constructor.
+ } else {
+ Attribute attribute =
+ readAttribute(
+ attributePrototypes,
+ attributeName,
+ currentAttributeOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentAttributeOffset += attributeLength;
+ }
- int off = items[1] - 1;
- classWriter.pool.putByteArray(b, off, header - off);
- classWriter.items = items2;
- classWriter.threshold = (int) (0.75d * ll);
- classWriter.index = ll;
- }
-
- /**
- * Copies the bootstrap method data into the given {@link ClassWriter}.
- * Should be called before the {@link #accept(ClassVisitor,int)} method.
- *
- * @param classWriter
- * the {@link ClassWriter} to copy bootstrap methods into.
- */
- private void copyBootstrapMethods(final ClassWriter classWriter,
- final Item[] items, final char[] c) {
- // finds the "BootstrapMethods" attribute
- int u = getAttributes();
- boolean found = false;
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- if ("BootstrapMethods".equals(attrName)) {
- found = true;
- break;
- }
- u += 6 + readInt(u + 4);
- }
- if (!found) {
- return;
- }
- // copies the bootstrap methods in the class writer
- int boostrapMethodCount = readUnsignedShort(u + 8);
- for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
- int position = v - u - 10;
- int hashCode = readConst(readUnsignedShort(v), c).hashCode();
- for (int k = readUnsignedShort(v + 2); k > 0; --k) {
- hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
- v += 2;
- }
- v += 4;
- Item item = new Item(j);
- item.set(position, hashCode & 0x7FFFFFFF);
- int index = item.hashCode % items.length;
- item.next = items[index];
- items[index] = item;
- }
- int attrSize = readInt(u + 4);
- ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
- bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
- classWriter.bootstrapMethodsCount = boostrapMethodCount;
- classWriter.bootstrapMethods = bootstrapMethods;
- }
-
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param is
- * an input stream from which to read the class.
- * @throws IOException
- * if a problem occurs during reading.
- */
- public ClassReader(final InputStream is) throws IOException {
- this(readClass(is, false));
- }
-
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param name
- * the binary qualified name of the class to be read.
- * @throws IOException
- * if an exception occurs during reading.
- */
- public ClassReader(final String name) throws IOException {
- this(readClass(
- ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
- + ".class"), true));
- }
-
- /**
- * Reads the bytecode of a class.
- *
- * @param is
- * an input stream from which to read the class.
- * @param close
- * true to close the input stream after reading.
- * @return the bytecode read from the given input stream.
- * @throws IOException
- * if a problem occurs during reading.
- */
- private static byte[] readClass(final InputStream is, boolean close)
- throws IOException {
- if (is == null) {
- throw new IOException("Class not found");
- }
- try {
- byte[] b = new byte[is.available()];
- int len = 0;
- while (true) {
- int n = is.read(b, len, b.length - len);
- if (n == -1) {
- if (len < b.length) {
- byte[] c = new byte[len];
- System.arraycopy(b, 0, c, 0, len);
- b = c;
- }
- return b;
- }
- len += n;
- if (len == b.length) {
- int last = is.read();
- if (last < 0) {
- return b;
- }
- byte[] c = new byte[b.length + 1000];
- System.arraycopy(b, 0, c, 0, len);
- c[len++] = (byte) last;
- b = c;
- }
- }
- } finally {
- if (close) {
- is.close();
- }
- }
+ // Visit the class declaration. The minor_version and major_version fields start 6 bytes before
+ // the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition).
+ classVisitor.visit(
+ readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);
+
+ // Visit the SourceFile and SourceDebugExtenstion attributes.
+ if ((parsingOptions & SKIP_DEBUG) == 0
+ && (sourceFile != null || sourceDebugExtension != null)) {
+ classVisitor.visitSource(sourceFile, sourceDebugExtension);
}
- // ------------------------------------------------------------------------
- // Public methods
- // ------------------------------------------------------------------------
-
- /**
- * Makes the given visitor visit the Java class of this {@link ClassReader}
- * . This class is the one specified in the constructor (see
- * {@link #ClassReader(byte[]) ClassReader}).
- *
- * @param classVisitor
- * the visitor that must visit this class.
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
- * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
- */
- public void accept(final ClassVisitor classVisitor, final int flags) {
- accept(classVisitor, new Attribute[0], flags);
- }
-
- /**
- * Makes the given visitor visit the Java class of this {@link ClassReader}.
- * This class is the one specified in the constructor (see
- * {@link #ClassReader(byte[]) ClassReader}).
- *
- * @param classVisitor
- * the visitor that must visit this class.
- * @param attrs
- * prototypes of the attributes that must be parsed during the
- * visit of the class. Any attribute whose type is not equal to
- * the type of one the prototypes will not be parsed: its byte
- * array value will be passed unchanged to the ClassWriter.
- * This may corrupt it if this value contains references to
- * the constant pool, or has syntactic or semantic links with a
- * class element that has been transformed by a class adapter
- * between the reader and the writer.
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
- * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
- */
- public void accept(final ClassVisitor classVisitor,
- final Attribute[] attrs, final int flags) {
- int u = header; // current offset in the class file
- char[] c = new char[maxStringLength]; // buffer used to read strings
-
- Context context = new Context();
- context.attrs = attrs;
- context.flags = flags;
- context.buffer = c;
-
- // reads the class declaration
- int access = readUnsignedShort(u);
- String name = readClass(u + 2, c);
- String superClass = readClass(u + 4, c);
- String[] interfaces = new String[readUnsignedShort(u + 6)];
- u += 8;
- for (int i = 0; i < interfaces.length; ++i) {
- interfaces[i] = readClass(u, c);
- u += 2;
- }
+ // Visit the Module, ModulePackages and ModuleMainClass attributes.
+ if (moduleOffset != 0) {
+ readModule(classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass);
+ }
- // reads the class attributes
- String signature = null;
- String sourceFile = null;
- String sourceDebug = null;
- String enclosingOwner = null;
- String enclosingName = null;
- String enclosingDesc = null;
- int anns = 0;
- int ianns = 0;
- int innerClasses = 0;
- Attribute attributes = null;
-
- u = getAttributes();
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- // tests are sorted in decreasing frequency order
- // (based on frequencies observed on typical classes)
- if ("SourceFile".equals(attrName)) {
- sourceFile = readUTF8(u + 8, c);
- } else if ("InnerClasses".equals(attrName)) {
- innerClasses = u + 8;
- } else if ("EnclosingMethod".equals(attrName)) {
- enclosingOwner = readClass(u + 8, c);
- int item = readUnsignedShort(u + 10);
- if (item != 0) {
- enclosingName = readUTF8(items[item], c);
- enclosingDesc = readUTF8(items[item] + 2, c);
- }
- } else if (SIGNATURES && "Signature".equals(attrName)) {
- signature = readUTF8(u + 8, c);
- } else if (ANNOTATIONS
- && "RuntimeVisibleAnnotations".equals(attrName)) {
- anns = u + 8;
- } else if ("Deprecated".equals(attrName)) {
- access |= Opcodes.ACC_DEPRECATED;
- } else if ("Synthetic".equals(attrName)) {
- access |= Opcodes.ACC_SYNTHETIC
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
- } else if ("SourceDebugExtension".equals(attrName)) {
- int len = readInt(u + 4);
- sourceDebug = readUTF(u + 8, len, new char[len]);
- } else if (ANNOTATIONS
- && "RuntimeInvisibleAnnotations".equals(attrName)) {
- ianns = u + 8;
- } else if ("BootstrapMethods".equals(attrName)) {
- int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
- for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
- bootstrapMethods[j] = v;
- v += 2 + readUnsignedShort(v + 2) << 1;
- }
- context.bootstrapMethods = bootstrapMethods;
- } else {
- Attribute attr = readAttribute(attrs, attrName, u + 8,
- readInt(u + 4), c, -1, null);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- u += 6 + readInt(u + 4);
- }
+ // Visit the NestHost attribute.
+ if (nestHostClass != null) {
+ classVisitor.visitNestHostExperimental(nestHostClass);
+ }
- // visits the class declaration
- classVisitor.visit(readInt(items[1] - 7), access, name, signature,
- superClass, interfaces);
+ // Visit the EnclosingMethod attribute.
+ if (enclosingMethodOffset != 0) {
+ String className = readClass(enclosingMethodOffset, charBuffer);
+ int methodIndex = readUnsignedShort(enclosingMethodOffset + 2);
+ String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer);
+ String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer);
+ classVisitor.visitOuterClass(className, name, type);
+ }
- // visits the source and debug info
- if ((flags & SKIP_DEBUG) == 0
- && (sourceFile != null || sourceDebug != null)) {
- classVisitor.visitSource(sourceFile, sourceDebug);
- }
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the outer class
- if (enclosingOwner != null) {
- classVisitor.visitOuterClass(enclosingOwner, enclosingName,
- enclosingDesc);
- }
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the class annotations
- if (ANNOTATIONS && anns != 0) {
- for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- classVisitor.visitAnnotation(readUTF8(v, c), true));
- }
- }
- if (ANNOTATIONS && ianns != 0) {
- for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- classVisitor.visitAnnotation(readUTF8(v, c), false));
- }
- }
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- classVisitor.visitAttribute(attributes);
- attributes = attr;
- }
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the inner classes
- if (innerClasses != 0) {
- int v = innerClasses + 2;
- for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
- classVisitor.visitInnerClass(readClass(v, c),
- readClass(v + 2, c), readUTF8(v + 4, c),
- readUnsignedShort(v + 6));
- v += 8;
- }
- }
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in ClassWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ classVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
- // visits the fields and methods
- u = header + 10 + 2 * interfaces.length;
- for (int i = readUnsignedShort(u - 2); i > 0; --i) {
- u = readField(classVisitor, context, u);
- }
- u += 2;
- for (int i = readUnsignedShort(u - 2); i > 0; --i) {
- u = readMethod(classVisitor, context, u);
- }
+ // Visit the NestedMembers attribute.
+ if (nestMembersOffset != 0) {
+ int numberOfNestMembers = readUnsignedShort(nestMembersOffset);
+ int currentNestMemberOffset = nestMembersOffset + 2;
+ while (numberOfNestMembers-- > 0) {
+ classVisitor.visitNestMemberExperimental(readClass(currentNestMemberOffset, charBuffer));
+ currentNestMemberOffset += 2;
+ }
+ }
- // visits the end of the class
- classVisitor.visitEnd();
- }
-
- /**
- * Reads a field and makes the given visitor visit it.
- *
- * @param classVisitor
- * the visitor that must visit the field.
- * @param context
- * information about the class being parsed.
- * @param u
- * the start offset of the field in the class file.
- * @return the offset of the first byte following the field in the class.
- */
- private int readField(final ClassVisitor classVisitor,
- final Context context, int u) {
- // reads the field declaration
- char[] c = context.buffer;
- int access = readUnsignedShort(u);
- String name = readUTF8(u + 2, c);
- String desc = readUTF8(u + 4, c);
- u += 6;
-
- // reads the field attributes
- String signature = null;
- int anns = 0;
- int ianns = 0;
- Object value = null;
- Attribute attributes = null;
-
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- // tests are sorted in decreasing frequency order
- // (based on frequencies observed on typical classes)
- if ("ConstantValue".equals(attrName)) {
- int item = readUnsignedShort(u + 8);
- value = item == 0 ? null : readConst(item, c);
- } else if (SIGNATURES && "Signature".equals(attrName)) {
- signature = readUTF8(u + 8, c);
- } else if ("Deprecated".equals(attrName)) {
- access |= Opcodes.ACC_DEPRECATED;
- } else if ("Synthetic".equals(attrName)) {
- access |= Opcodes.ACC_SYNTHETIC
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
- } else if (ANNOTATIONS
- && "RuntimeVisibleAnnotations".equals(attrName)) {
- anns = u + 8;
- } else if (ANNOTATIONS
- && "RuntimeInvisibleAnnotations".equals(attrName)) {
- ianns = u + 8;
- } else {
- Attribute attr = readAttribute(context.attrs, attrName, u + 8,
- readInt(u + 4), c, -1, null);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- u += 6 + readInt(u + 4);
+ // Visit the InnerClasses attribute.
+ if (innerClassesOffset != 0) {
+ int numberOfClasses = readUnsignedShort(innerClassesOffset);
+ int currentClassesOffset = innerClassesOffset + 2;
+ while (numberOfClasses-- > 0) {
+ classVisitor.visitInnerClass(
+ readClass(currentClassesOffset, charBuffer),
+ readClass(currentClassesOffset + 2, charBuffer),
+ readUTF8(currentClassesOffset + 4, charBuffer),
+ readUnsignedShort(currentClassesOffset + 6));
+ currentClassesOffset += 8;
+ }
+ }
+
+ // Visit the fields and methods.
+ int fieldsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (fieldsCount-- > 0) {
+ currentOffset = readField(classVisitor, context, currentOffset);
+ }
+ int methodsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (methodsCount-- > 0) {
+ currentOffset = readMethod(classVisitor, context, currentOffset);
+ }
+
+ // Visit the end of the class.
+ classVisitor.visitEnd();
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse modules, fields and methods
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Reads the module attribute and visit it.
+ *
+ * @param classVisitor the current class visitor
+ * @param context information about the class being parsed.
+ * @param moduleOffset the offset of the Module attribute (excluding the attribute_info's
+ * attribute_name_index and attribute_length fields).
+ * @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the
+ * attribute_info's attribute_name_index and attribute_length fields), or 0.
+ * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or null.
+ */
+ private void readModule(
+ final ClassVisitor classVisitor,
+ final Context context,
+ final int moduleOffset,
+ final int modulePackagesOffset,
+ final String moduleMainClass) {
+ char[] buffer = context.charBuffer;
+
+ // Read the module_name_index, module_flags and module_version_index fields and visit them.
+ int currentOffset = moduleOffset;
+ String moduleName = readModule(currentOffset, buffer);
+ int moduleFlags = readUnsignedShort(currentOffset + 2);
+ String moduleVersion = readUTF8(currentOffset + 4, buffer);
+ currentOffset += 6;
+ ModuleVisitor moduleVisitor = classVisitor.visitModule(moduleName, moduleFlags, moduleVersion);
+ if (moduleVisitor == null) {
+ return;
+ }
+
+ // Visit the ModuleMainClass attribute.
+ if (moduleMainClass != null) {
+ moduleVisitor.visitMainClass(moduleMainClass);
+ }
+
+ // Visit the ModulePackages attribute.
+ if (modulePackagesOffset != 0) {
+ int packageCount = readUnsignedShort(modulePackagesOffset);
+ int currentPackageOffset = modulePackagesOffset + 2;
+ while (packageCount-- > 0) {
+ moduleVisitor.visitPackage(readPackage(currentPackageOffset, buffer));
+ currentPackageOffset += 2;
+ }
+ }
+
+ // Read the 'requires_count' and 'requires' fields.
+ int requiresCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (requiresCount-- > 0) {
+ // Read the requires_index, requires_flags and requires_version fields and visit them.
+ String requires = readModule(currentOffset, buffer);
+ int requiresFlags = readUnsignedShort(currentOffset + 2);
+ String requiresVersion = readUTF8(currentOffset + 4, buffer);
+ currentOffset += 6;
+ moduleVisitor.visitRequire(requires, requiresFlags, requiresVersion);
+ }
+
+ // Read the 'exports_count' and 'exports' fields.
+ int exportsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (exportsCount-- > 0) {
+ // Read the exports_index, exports_flags, exports_to_count and exports_to_index fields
+ // and visit them.
+ String exports = readPackage(currentOffset, buffer);
+ int exportsFlags = readUnsignedShort(currentOffset + 2);
+ int exportsToCount = readUnsignedShort(currentOffset + 4);
+ currentOffset += 6;
+ String[] exportsTo = null;
+ if (exportsToCount != 0) {
+ exportsTo = new String[exportsToCount];
+ for (int i = 0; i < exportsToCount; ++i) {
+ exportsTo[i] = readModule(currentOffset, buffer);
+ currentOffset += 2;
}
- u += 2;
+ }
+ moduleVisitor.visitExport(exports, exportsFlags, exportsTo);
+ }
- // visits the field declaration
- FieldVisitor fv = classVisitor.visitField(access, name, desc,
- signature, value);
- if (fv == null) {
- return u;
+ // Reads the 'opens_count' and 'opens' fields.
+ int opensCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (opensCount-- > 0) {
+ // Read the opens_index, opens_flags, opens_to_count and opens_to_index fields and visit them.
+ String opens = readPackage(currentOffset, buffer);
+ int opensFlags = readUnsignedShort(currentOffset + 2);
+ int opensToCount = readUnsignedShort(currentOffset + 4);
+ currentOffset += 6;
+ String[] opensTo = null;
+ if (opensToCount != 0) {
+ opensTo = new String[opensToCount];
+ for (int i = 0; i < opensToCount; ++i) {
+ opensTo[i] = readModule(currentOffset, buffer);
+ currentOffset += 2;
}
+ }
+ moduleVisitor.visitOpen(opens, opensFlags, opensTo);
+ }
- // visits the field annotations
- if (ANNOTATIONS && anns != 0) {
- for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- fv.visitAnnotation(readUTF8(v, c), true));
- }
+ // Read the 'uses_count' and 'uses' fields.
+ int usesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (usesCount-- > 0) {
+ moduleVisitor.visitUse(readClass(currentOffset, buffer));
+ currentOffset += 2;
+ }
+
+ // Read the 'provides_count' and 'provides' fields.
+ int providesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (providesCount-- > 0) {
+ // Read the provides_index, provides_with_count and provides_with_index fields and visit them.
+ String provides = readClass(currentOffset, buffer);
+ int providesWithCount = readUnsignedShort(currentOffset + 2);
+ currentOffset += 4;
+ String[] providesWith = new String[providesWithCount];
+ for (int i = 0; i < providesWithCount; ++i) {
+ providesWith[i] = readClass(currentOffset, buffer);
+ currentOffset += 2;
+ }
+ moduleVisitor.visitProvide(provides, providesWith);
+ }
+
+ // Visit the end of the module attributes.
+ moduleVisitor.visitEnd();
+ }
+
+ /**
+ * Reads a JVMS field_info structure and makes the given visitor visit it.
+ *
+ * @param classVisitor the visitor that must visit the field.
+ * @param context information about the class being parsed.
+ * @param fieldInfoOffset the start offset of the field_info structure.
+ * @return the offset of the first byte following the field_info structure.
+ */
+ private int readField(
+ final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) {
+ char[] charBuffer = context.charBuffer;
+
+ // Read the access_flags, name_index and descriptor_index fields.
+ int currentOffset = fieldInfoOffset;
+ int accessFlags = readUnsignedShort(currentOffset);
+ String name = readUTF8(currentOffset + 2, charBuffer);
+ String descriptor = readUTF8(currentOffset + 4, charBuffer);
+ currentOffset += 6;
+
+ // Read the field attributes (the variables are ordered as in Section 4.7 of the JVMS).
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The value corresponding to the ConstantValue attribute, or null.
+ Object constantValue = null;
+ // - The string corresponding to the Signature attribute, or null.
+ String signature = null;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.CONSTANT_VALUE.equals(attributeName)) {
+ int constantvalueIndex = readUnsignedShort(currentOffset);
+ constantValue = constantvalueIndex == 0 ? null : readConst(constantvalueIndex, charBuffer);
+ } else if (Constants.SIGNATURE.equals(attributeName)) {
+ signature = readUTF8(currentOffset, charBuffer);
+ } else if (Constants.DEPRECATED.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_DEPRECATED;
+ } else if (Constants.SYNTHETIC.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_SYNTHETIC;
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentOffset;
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
+
+ // Visit the field declaration.
+ FieldVisitor fieldVisitor =
+ classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue);
+ if (fieldVisitor == null) {
+ return currentOffset;
+ }
+
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in FieldWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ fieldVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
+
+ // Visit the end of the field.
+ fieldVisitor.visitEnd();
+ return currentOffset;
+ }
+
+ /**
+ * Reads a JVMS method_info structure and makes the given visitor visit it.
+ *
+ * @param classVisitor the visitor that must visit the method.
+ * @param context information about the class being parsed.
+ * @param methodInfoOffset the start offset of the method_info structure.
+ * @return the offset of the first byte following the method_info structure.
+ */
+ private int readMethod(
+ final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) {
+ char[] charBuffer = context.charBuffer;
+
+ // Read the access_flags, name_index and descriptor_index fields.
+ int currentOffset = methodInfoOffset;
+ context.currentMethodAccessFlags = readUnsignedShort(currentOffset);
+ context.currentMethodName = readUTF8(currentOffset + 2, charBuffer);
+ context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer);
+ currentOffset += 6;
+
+ // Read the method attributes (the variables are ordered as in Section 4.7 of the JVMS).
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The offset of the Code attribute, or 0.
+ int codeOffset = 0;
+ // - The offset of the Exceptions attribute, or 0.
+ int exceptionsOffset = 0;
+ // - The strings corresponding to the Exceptions attribute, or null.
+ String[] exceptions = null;
+ // - Whether the method has a Synthetic attribute.
+ boolean synthetic = false;
+ // - The constant pool index contained in the Signature attribute, or 0.
+ int signatureIndex = 0;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0.
+ int runtimeVisibleParameterAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0.
+ int runtimeInvisibleParameterAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The offset of the AnnotationDefault attribute, or 0.
+ int annotationDefaultOffset = 0;
+ // - The offset of the MethodParameters attribute, or 0.
+ int methodParametersOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.CODE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_CODE) == 0) {
+ codeOffset = currentOffset;
}
- if (ANNOTATIONS && ianns != 0) {
- for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- fv.visitAnnotation(readUTF8(v, c), false));
- }
+ } else if (Constants.EXCEPTIONS.equals(attributeName)) {
+ exceptionsOffset = currentOffset;
+ exceptions = new String[readUnsignedShort(exceptionsOffset)];
+ int currentExceptionOffset = exceptionsOffset + 2;
+ for (int i = 0; i < exceptions.length; ++i) {
+ exceptions[i] = readClass(currentExceptionOffset, charBuffer);
+ currentExceptionOffset += 2;
}
+ } else if (Constants.SIGNATURE.equals(attributeName)) {
+ signatureIndex = readUnsignedShort(currentOffset);
+ } else if (Constants.DEPRECATED.equals(attributeName)) {
+ context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED;
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) {
+ annotationDefaultOffset = currentOffset;
+ } else if (Constants.SYNTHETIC.equals(attributeName)) {
+ synthetic = true;
+ context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC;
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleParameterAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleParameterAnnotationsOffset = currentOffset;
+ } else if (Constants.METHOD_PARAMETERS.equals(attributeName)) {
+ methodParametersOffset = currentOffset;
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
- // visits the field attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- fv.visitAttribute(attributes);
- attributes = attr;
- }
+ // Visit the method declaration.
+ MethodVisitor methodVisitor =
+ classVisitor.visitMethod(
+ context.currentMethodAccessFlags,
+ context.currentMethodName,
+ context.currentMethodDescriptor,
+ signatureIndex == 0 ? null : readUTF(signatureIndex, charBuffer),
+ exceptions);
+ if (methodVisitor == null) {
+ return currentOffset;
+ }
- // visits the end of the field
- fv.visitEnd();
-
- return u;
- }
-
- /**
- * Reads a method and makes the given visitor visit it.
- *
- * @param classVisitor
- * the visitor that must visit the method.
- * @param context
- * information about the class being parsed.
- * @param u
- * the start offset of the method in the class file.
- * @return the offset of the first byte following the method in the class.
- */
- private int readMethod(final ClassVisitor classVisitor,
- final Context context, int u) {
- // reads the method declaration
- char[] c = context.buffer;
- int access = readUnsignedShort(u);
- String name = readUTF8(u + 2, c);
- String desc = readUTF8(u + 4, c);
- u += 6;
-
- // reads the method attributes
- int code = 0;
- int exception = 0;
- String[] exceptions = null;
- String signature = null;
- int anns = 0;
- int ianns = 0;
- int dann = 0;
- int mpanns = 0;
- int impanns = 0;
- int firstAttribute = u;
- Attribute attributes = null;
-
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- // tests are sorted in decreasing frequency order
- // (based on frequencies observed on typical classes)
- if ("Code".equals(attrName)) {
- if ((context.flags & SKIP_CODE) == 0) {
- code = u + 8;
- }
- } else if ("Exceptions".equals(attrName)) {
- exceptions = new String[readUnsignedShort(u + 8)];
- exception = u + 10;
- for (int j = 0; j < exceptions.length; ++j) {
- exceptions[j] = readClass(exception, c);
- exception += 2;
- }
- } else if (SIGNATURES && "Signature".equals(attrName)) {
- signature = readUTF8(u + 8, c);
- } else if ("Deprecated".equals(attrName)) {
- access |= Opcodes.ACC_DEPRECATED;
- } else if (ANNOTATIONS
- && "RuntimeVisibleAnnotations".equals(attrName)) {
- anns = u + 8;
- } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
- dann = u + 8;
- } else if ("Synthetic".equals(attrName)) {
- access |= Opcodes.ACC_SYNTHETIC
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
- } else if (ANNOTATIONS
- && "RuntimeInvisibleAnnotations".equals(attrName)) {
- ianns = u + 8;
- } else if (ANNOTATIONS
- && "RuntimeVisibleParameterAnnotations".equals(attrName)) {
- mpanns = u + 8;
- } else if (ANNOTATIONS
- && "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
- impanns = u + 8;
- } else {
- Attribute attr = readAttribute(context.attrs, attrName, u + 8,
- readInt(u + 4), c, -1, null);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- u += 6 + readInt(u + 4);
- }
- u += 2;
+ // If the returned MethodVisitor is in fact a MethodWriter, it means there is no method
+ // adapter between the reader and the writer. In this case, it might be possible to copy
+ // the method attributes directly into the writer. If so, return early without visiting
+ // the content of these attributes.
+ if (methodVisitor instanceof MethodWriter) {
+ MethodWriter methodWriter = (MethodWriter) methodVisitor;
+ if (methodWriter.canCopyMethodAttributes(
+ this,
+ methodInfoOffset,
+ currentOffset - methodInfoOffset,
+ synthetic,
+ (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
+ signatureIndex,
+ exceptionsOffset)) {
+ return currentOffset;
+ }
+ }
- // visits the method declaration
- MethodVisitor mv = classVisitor.visitMethod(access, name, desc,
- signature, exceptions);
- if (mv == null) {
- return u;
- }
+ // Visit the MethodParameters attribute.
+ if (methodParametersOffset != 0) {
+ int parametersCount = readByte(methodParametersOffset);
+ int currentParameterOffset = methodParametersOffset + 1;
+ while (parametersCount-- > 0) {
+ // Read the name_index and access_flags fields and visit them.
+ methodVisitor.visitParameter(
+ readUTF8(currentParameterOffset, charBuffer),
+ readUnsignedShort(currentParameterOffset + 2));
+ currentParameterOffset += 4;
+ }
+ }
- /*
- * if the returned MethodVisitor is in fact a MethodWriter, it means
- * there is no method adapter between the reader and the writer. If, in
- * addition, the writer's constant pool was copied from this reader
- * (mw.cw.cr == this), and the signature and exceptions of the method
- * have not been changed, then it is possible to skip all visit events
- * and just copy the original code of the method to the writer (the
- * access, name and descriptor can have been changed, this is not
- * important since they are not copied as is from the reader).
- */
- if (WRITER && mv instanceof MethodWriter) {
- MethodWriter mw = (MethodWriter) mv;
- if (mw.cw.cr == this && signature == mw.signature) {
- boolean sameExceptions = false;
- if (exceptions == null) {
- sameExceptions = mw.exceptionCount == 0;
- } else if (exceptions.length == mw.exceptionCount) {
- sameExceptions = true;
- for (int j = exceptions.length - 1; j >= 0; --j) {
- exception -= 2;
- if (mw.exceptions[j] != readUnsignedShort(exception)) {
- sameExceptions = false;
- break;
- }
- }
- }
- if (sameExceptions) {
- /*
- * we do not copy directly the code into MethodWriter to
- * save a byte array copy operation. The real copy will be
- * done in ClassWriter.toByteArray().
- */
- mw.classReaderOffset = firstAttribute;
- mw.classReaderLength = u - firstAttribute;
- return u;
- }
- }
- }
+ // Visit the AnnotationDefault attribute.
+ if (annotationDefaultOffset != 0) {
+ AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
+ readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer);
+ if (annotationVisitor != null) {
+ annotationVisitor.visitEnd();
+ }
+ }
- // visits the method annotations
- if (ANNOTATIONS && dann != 0) {
- AnnotationVisitor dv = mv.visitAnnotationDefault();
- readAnnotationValue(dann, c, null, dv);
- if (dv != null) {
- dv.visitEnd();
- }
- }
- if (ANNOTATIONS && anns != 0) {
- for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- mv.visitAnnotation(readUTF8(v, c), true));
- }
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeVisibleParameterAnnotations attribute.
+ if (runtimeVisibleParameterAnnotationsOffset != 0) {
+ readParameterAnnotations(
+ methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true);
+ }
+
+ // Visit the RuntimeInvisibleParameterAnnotations attribute.
+ if (runtimeInvisibleParameterAnnotationsOffset != 0) {
+ readParameterAnnotations(
+ methodVisitor,
+ context,
+ runtimeInvisibleParameterAnnotationsOffset,
+ /* visible = */ false);
+ }
+
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in MethodWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ methodVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
+
+ // Visit the Code attribute.
+ if (codeOffset != 0) {
+ methodVisitor.visitCode();
+ readCode(methodVisitor, context, codeOffset);
+ }
+
+ // Visit the end of the method.
+ methodVisitor.visitEnd();
+ return currentOffset;
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse a Code attribute
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Reads a JVMS 'Code' attribute and makes the given visitor visit it.
+ *
+ * @param methodVisitor the visitor that must visit the Code attribute.
+ * @param context information about the class being parsed.
+ * @param codeOffset the start offset in {@link #b} of the Code attribute, excluding its
+ * attribute_name_index and attribute_length fields.
+ */
+ private void readCode(
+ final MethodVisitor methodVisitor, final Context context, final int codeOffset) {
+ int currentOffset = codeOffset;
+
+ // Read the max_stack, max_locals and code_length fields.
+ final byte[] classFileBuffer = b;
+ final char[] charBuffer = context.charBuffer;
+ final int maxStack = readUnsignedShort(currentOffset);
+ final int maxLocals = readUnsignedShort(currentOffset + 2);
+ final int codeLength = readInt(currentOffset + 4);
+ currentOffset += 8;
+
+ // Read the bytecode 'code' array to create a label for each referenced instruction.
+ final int bytecodeStartOffset = currentOffset;
+ final int bytecodeEndOffset = currentOffset + codeLength;
+ final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1];
+ while (currentOffset < bytecodeEndOffset) {
+ final int bytecodeOffset = currentOffset - bytecodeStartOffset;
+ final int opcode = classFileBuffer[currentOffset] & 0xFF;
+ switch (opcode) {
+ case Constants.NOP:
+ case Constants.ACONST_NULL:
+ case Constants.ICONST_M1:
+ case Constants.ICONST_0:
+ case Constants.ICONST_1:
+ case Constants.ICONST_2:
+ case Constants.ICONST_3:
+ case Constants.ICONST_4:
+ case Constants.ICONST_5:
+ case Constants.LCONST_0:
+ case Constants.LCONST_1:
+ case Constants.FCONST_0:
+ case Constants.FCONST_1:
+ case Constants.FCONST_2:
+ case Constants.DCONST_0:
+ case Constants.DCONST_1:
+ case Constants.IALOAD:
+ case Constants.LALOAD:
+ case Constants.FALOAD:
+ case Constants.DALOAD:
+ case Constants.AALOAD:
+ case Constants.BALOAD:
+ case Constants.CALOAD:
+ case Constants.SALOAD:
+ case Constants.IASTORE:
+ case Constants.LASTORE:
+ case Constants.FASTORE:
+ case Constants.DASTORE:
+ case Constants.AASTORE:
+ case Constants.BASTORE:
+ case Constants.CASTORE:
+ case Constants.SASTORE:
+ case Constants.POP:
+ case Constants.POP2:
+ case Constants.DUP:
+ case Constants.DUP_X1:
+ case Constants.DUP_X2:
+ case Constants.DUP2:
+ case Constants.DUP2_X1:
+ case Constants.DUP2_X2:
+ case Constants.SWAP:
+ case Constants.IADD:
+ case Constants.LADD:
+ case Constants.FADD:
+ case Constants.DADD:
+ case Constants.ISUB:
+ case Constants.LSUB:
+ case Constants.FSUB:
+ case Constants.DSUB:
+ case Constants.IMUL:
+ case Constants.LMUL:
+ case Constants.FMUL:
+ case Constants.DMUL:
+ case Constants.IDIV:
+ case Constants.LDIV:
+ case Constants.FDIV:
+ case Constants.DDIV:
+ case Constants.IREM:
+ case Constants.LREM:
+ case Constants.FREM:
+ case Constants.DREM:
+ case Constants.INEG:
+ case Constants.LNEG:
+ case Constants.FNEG:
+ case Constants.DNEG:
+ case Constants.ISHL:
+ case Constants.LSHL:
+ case Constants.ISHR:
+ case Constants.LSHR:
+ case Constants.IUSHR:
+ case Constants.LUSHR:
+ case Constants.IAND:
+ case Constants.LAND:
+ case Constants.IOR:
+ case Constants.LOR:
+ case Constants.IXOR:
+ case Constants.LXOR:
+ case Constants.I2L:
+ case Constants.I2F:
+ case Constants.I2D:
+ case Constants.L2I:
+ case Constants.L2F:
+ case Constants.L2D:
+ case Constants.F2I:
+ case Constants.F2L:
+ case Constants.F2D:
+ case Constants.D2I:
+ case Constants.D2L:
+ case Constants.D2F:
+ case Constants.I2B:
+ case Constants.I2C:
+ case Constants.I2S:
+ case Constants.LCMP:
+ case Constants.FCMPL:
+ case Constants.FCMPG:
+ case Constants.DCMPL:
+ case Constants.DCMPG:
+ case Constants.IRETURN:
+ case Constants.LRETURN:
+ case Constants.FRETURN:
+ case Constants.DRETURN:
+ case Constants.ARETURN:
+ case Constants.RETURN:
+ case Constants.ARRAYLENGTH:
+ case Constants.ATHROW:
+ case Constants.MONITORENTER:
+ case Constants.MONITOREXIT:
+ case Constants.ILOAD_0:
+ case Constants.ILOAD_1:
+ case Constants.ILOAD_2:
+ case Constants.ILOAD_3:
+ case Constants.LLOAD_0:
+ case Constants.LLOAD_1:
+ case Constants.LLOAD_2:
+ case Constants.LLOAD_3:
+ case Constants.FLOAD_0:
+ case Constants.FLOAD_1:
+ case Constants.FLOAD_2:
+ case Constants.FLOAD_3:
+ case Constants.DLOAD_0:
+ case Constants.DLOAD_1:
+ case Constants.DLOAD_2:
+ case Constants.DLOAD_3:
+ case Constants.ALOAD_0:
+ case Constants.ALOAD_1:
+ case Constants.ALOAD_2:
+ case Constants.ALOAD_3:
+ case Constants.ISTORE_0:
+ case Constants.ISTORE_1:
+ case Constants.ISTORE_2:
+ case Constants.ISTORE_3:
+ case Constants.LSTORE_0:
+ case Constants.LSTORE_1:
+ case Constants.LSTORE_2:
+ case Constants.LSTORE_3:
+ case Constants.FSTORE_0:
+ case Constants.FSTORE_1:
+ case Constants.FSTORE_2:
+ case Constants.FSTORE_3:
+ case Constants.DSTORE_0:
+ case Constants.DSTORE_1:
+ case Constants.DSTORE_2:
+ case Constants.DSTORE_3:
+ case Constants.ASTORE_0:
+ case Constants.ASTORE_1:
+ case Constants.ASTORE_2:
+ case Constants.ASTORE_3:
+ currentOffset += 1;
+ break;
+ case Constants.IFEQ:
+ case Constants.IFNE:
+ case Constants.IFLT:
+ case Constants.IFGE:
+ case Constants.IFGT:
+ case Constants.IFLE:
+ case Constants.IF_ICMPEQ:
+ case Constants.IF_ICMPNE:
+ case Constants.IF_ICMPLT:
+ case Constants.IF_ICMPGE:
+ case Constants.IF_ICMPGT:
+ case Constants.IF_ICMPLE:
+ case Constants.IF_ACMPEQ:
+ case Constants.IF_ACMPNE:
+ case Constants.GOTO:
+ case Constants.JSR:
+ case Constants.IFNULL:
+ case Constants.IFNONNULL:
+ createLabel(bytecodeOffset + readShort(currentOffset + 1), labels);
+ currentOffset += 3;
+ break;
+ case Constants.ASM_IFEQ:
+ case Constants.ASM_IFNE:
+ case Constants.ASM_IFLT:
+ case Constants.ASM_IFGE:
+ case Constants.ASM_IFGT:
+ case Constants.ASM_IFLE:
+ case Constants.ASM_IF_ICMPEQ:
+ case Constants.ASM_IF_ICMPNE:
+ case Constants.ASM_IF_ICMPLT:
+ case Constants.ASM_IF_ICMPGE:
+ case Constants.ASM_IF_ICMPGT:
+ case Constants.ASM_IF_ICMPLE:
+ case Constants.ASM_IF_ACMPEQ:
+ case Constants.ASM_IF_ACMPNE:
+ case Constants.ASM_GOTO:
+ case Constants.ASM_JSR:
+ case Constants.ASM_IFNULL:
+ case Constants.ASM_IFNONNULL:
+ createLabel(bytecodeOffset + readUnsignedShort(currentOffset + 1), labels);
+ currentOffset += 3;
+ break;
+ case Constants.GOTO_W:
+ case Constants.JSR_W:
+ case Constants.ASM_GOTO_W:
+ createLabel(bytecodeOffset + readInt(currentOffset + 1), labels);
+ currentOffset += 5;
+ break;
+ case Constants.WIDE:
+ switch (classFileBuffer[currentOffset + 1] & 0xFF) {
+ case Constants.ILOAD:
+ case Constants.FLOAD:
+ case Constants.ALOAD:
+ case Constants.LLOAD:
+ case Constants.DLOAD:
+ case Constants.ISTORE:
+ case Constants.FSTORE:
+ case Constants.ASTORE:
+ case Constants.LSTORE:
+ case Constants.DSTORE:
+ case Constants.RET:
+ currentOffset += 4;
+ break;
+ case Constants.IINC:
+ currentOffset += 6;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ break;
+ case Constants.TABLESWITCH:
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (bytecodeOffset & 3);
+ // Read the default label and the number of table entries.
+ createLabel(bytecodeOffset + readInt(currentOffset), labels);
+ int numTableEntries = readInt(currentOffset + 8) - readInt(currentOffset + 4) + 1;
+ currentOffset += 12;
+ // Read the table labels.
+ while (numTableEntries-- > 0) {
+ createLabel(bytecodeOffset + readInt(currentOffset), labels);
+ currentOffset += 4;
+ }
+ break;
+ case Constants.LOOKUPSWITCH:
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (bytecodeOffset & 3);
+ // Read the default label and the number of switch cases.
+ createLabel(bytecodeOffset + readInt(currentOffset), labels);
+ int numSwitchCases = readInt(currentOffset + 4);
+ currentOffset += 8;
+ // Read the switch labels.
+ while (numSwitchCases-- > 0) {
+ createLabel(bytecodeOffset + readInt(currentOffset + 4), labels);
+ currentOffset += 8;
+ }
+ break;
+ case Constants.ILOAD:
+ case Constants.LLOAD:
+ case Constants.FLOAD:
+ case Constants.DLOAD:
+ case Constants.ALOAD:
+ case Constants.ISTORE:
+ case Constants.LSTORE:
+ case Constants.FSTORE:
+ case Constants.DSTORE:
+ case Constants.ASTORE:
+ case Constants.RET:
+ case Constants.BIPUSH:
+ case Constants.NEWARRAY:
+ case Constants.LDC:
+ currentOffset += 2;
+ break;
+ case Constants.SIPUSH:
+ case Constants.LDC_W:
+ case Constants.LDC2_W:
+ case Constants.GETSTATIC:
+ case Constants.PUTSTATIC:
+ case Constants.GETFIELD:
+ case Constants.PUTFIELD:
+ case Constants.INVOKEVIRTUAL:
+ case Constants.INVOKESPECIAL:
+ case Constants.INVOKESTATIC:
+ case Constants.NEW:
+ case Constants.ANEWARRAY:
+ case Constants.CHECKCAST:
+ case Constants.INSTANCEOF:
+ case Constants.IINC:
+ currentOffset += 3;
+ break;
+ case Constants.INVOKEINTERFACE:
+ case Constants.INVOKEDYNAMIC:
+ currentOffset += 5;
+ break;
+ case Constants.MULTIANEWARRAY:
+ currentOffset += 4;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ // Read the 'exception_table_length' and 'exception_table' field to create a label for each
+ // referenced instruction, and to make methodVisitor visit the corresponding try catch blocks.
+ {
+ int exceptionTableLength = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (exceptionTableLength-- > 0) {
+ Label start = createLabel(readUnsignedShort(currentOffset), labels);
+ Label end = createLabel(readUnsignedShort(currentOffset + 2), labels);
+ Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels);
+ String catchType =
+ readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer);
+ currentOffset += 8;
+ methodVisitor.visitTryCatchBlock(start, end, handler, catchType);
+ }
+ }
+
+ // Read the Code attributes to create a label for each referenced instruction (the variables
+ // are ordered as in Section 4.7 of the JVMS). Attribute offsets exclude the
+ // attribute_name_index and attribute_length fields.
+ // - The offset of the current 'stack_map_frame' in the StackMap[Table] attribute, or 0.
+ // Initially, this is the offset of the first 'stack_map_frame' entry. Then this offset is
+ // updated after each stack_map_frame is read.
+ int stackMapFrameOffset = 0;
+ // - The end offset of the StackMap[Table] attribute, or 0.
+ int stackMapTableEndOffset = 0;
+ // - Whether the stack map frames are compressed (i.e. in a StackMapTable) or not.
+ boolean compressedFrames = true;
+ // - The offset of the LocalVariableTable attribute, or 0.
+ int localVariableTableOffset = 0;
+ // - The offset of the LocalVariableTypeTable attribute, or 0.
+ int localVariableTypeTableOffset = 0;
+ // - The offset of each 'type_annotation' entry in the RuntimeVisibleTypeAnnotations
+ // attribute, or null.
+ int[] visibleTypeAnnotationOffsets = null;
+ // - The offset of each 'type_annotation' entry in the RuntimeInvisibleTypeAnnotations
+ // attribute, or null.
+ int[] invisibleTypeAnnotationOffsets = null;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ if (Constants.LOCAL_VARIABLE_TABLE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_DEBUG) == 0) {
+ localVariableTableOffset = currentOffset;
+ // Parse the attribute to find the corresponding (debug only) labels.
+ int currentLocalVariableTableOffset = currentOffset;
+ int localVariableTableLength = readUnsignedShort(currentLocalVariableTableOffset);
+ currentLocalVariableTableOffset += 2;
+ while (localVariableTableLength-- > 0) {
+ int startPc = readUnsignedShort(currentLocalVariableTableOffset);
+ createDebugLabel(startPc, labels);
+ int length = readUnsignedShort(currentLocalVariableTableOffset + 2);
+ createDebugLabel(startPc + length, labels);
+ // Skip the name_index, descriptor_index and index fields (2 bytes each).
+ currentLocalVariableTableOffset += 10;
+ }
}
- if (ANNOTATIONS && ianns != 0) {
- for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- mv.visitAnnotation(readUTF8(v, c), false));
- }
+ } else if (Constants.LOCAL_VARIABLE_TYPE_TABLE.equals(attributeName)) {
+ localVariableTypeTableOffset = currentOffset;
+ // Here we do not extract the labels corresponding to the attribute content. We assume they
+ // are the same or a subset of those of the LocalVariableTable attribute.
+ } else if (Constants.LINE_NUMBER_TABLE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_DEBUG) == 0) {
+ // Parse the attribute to find the corresponding (debug only) labels.
+ int currentLineNumberTableOffset = currentOffset;
+ int lineNumberTableLength = readUnsignedShort(currentLineNumberTableOffset);
+ currentLineNumberTableOffset += 2;
+ while (lineNumberTableLength-- > 0) {
+ int startPc = readUnsignedShort(currentLineNumberTableOffset);
+ int lineNumber = readUnsignedShort(currentLineNumberTableOffset + 2);
+ currentLineNumberTableOffset += 4;
+ createDebugLabel(startPc, labels);
+ labels[startPc].addLineNumber(lineNumber);
+ }
}
- if (ANNOTATIONS && mpanns != 0) {
- readParameterAnnotations(mpanns, desc, c, true, mv);
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ visibleTypeAnnotationOffsets =
+ readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true);
+ // Here we do not extract the labels corresponding to the attribute content. This would
+ // require a full parsing of the attribute, which would need to be repeated when parsing
+ // the bytecode instructions (see below). Instead, the content of the attribute is read one
+ // type annotation at a time (i.e. after a type annotation has been visited, the next type
+ // annotation is read), and the labels it contains are also extracted one annotation at a
+ // time. This assumes that type annotations are ordered by increasing bytecode offset.
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ invisibleTypeAnnotationOffsets =
+ readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false);
+ // Same comment as above for the RuntimeVisibleTypeAnnotations attribute.
+ } else if (Constants.STACK_MAP_TABLE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_FRAMES) == 0) {
+ stackMapFrameOffset = currentOffset + 2;
+ stackMapTableEndOffset = currentOffset + attributeLength;
}
- if (ANNOTATIONS && impanns != 0) {
- readParameterAnnotations(impanns, desc, c, false, mv);
+ // Here we do not extract the labels corresponding to the attribute content. This would
+ // require a full parsing of the attribute, which would need to be repeated when parsing
+ // the bytecode instructions (see below). Instead, the content of the attribute is read one
+ // frame at a time (i.e. after a frame has been visited, the next frame is read), and the
+ // labels it contains are also extracted one frame at a time. Thanks to the ordering of
+ // frames, having only a "one frame lookahead" is not a problem, i.e. it is not possible to
+ // see an offset smaller than the offset of the current instruction and for which no Label
+ // exist. Except for UNINITIALIZED type offsets. We solve this by parsing the stack map
+ // table without a full decoding (see below).
+ } else if ("StackMap".equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_FRAMES) == 0) {
+ stackMapFrameOffset = currentOffset + 2;
+ stackMapTableEndOffset = currentOffset + attributeLength;
+ compressedFrames = false;
}
+ // IMPORTANT! Here we assume that the frames are ordered, as in the StackMapTable attribute,
+ // although this is not guaranteed by the attribute format. This allows an incremental
+ // extraction of the labels corresponding to this attribute (see the comment above for the
+ // StackMapTable attribute).
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ codeOffset,
+ labels);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
- // visits the method attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- mv.visitAttribute(attributes);
- attributes = attr;
+ // Initialize the context fields related to stack map frames, and generate the first
+ // (implicit) stack map frame, if needed.
+ final boolean expandFrames = (context.parsingOptions & EXPAND_FRAMES) != 0;
+ if (stackMapFrameOffset != 0) {
+ // The bytecode offset of the first explicit frame is not offset_delta + 1 but only
+ // offset_delta. Setting the implicit frame offset to -1 allows us to use of the
+ // "offset_delta + 1" rule in all cases.
+ context.currentFrameOffset = -1;
+ context.currentFrameType = 0;
+ context.currentFrameLocalCount = 0;
+ context.currentFrameLocalCountDelta = 0;
+ context.currentFrameLocalTypes = new Object[maxLocals];
+ context.currentFrameStackCount = 0;
+ context.currentFrameStackTypes = new Object[maxStack];
+ if (expandFrames) {
+ computeImplicitFrame(context);
+ }
+ // Find the labels for UNINITIALIZED frame types. Instead of decoding each element of the
+ // stack map table, we look for 3 consecutive bytes that "look like" an UNINITIALIZED type
+ // (tag ITEM_Uninitialized, offset within bytecode bounds, NEW instruction at this offset).
+ // We may find false positives (i.e. not real UNINITIALIZED types), but this should be rare,
+ // and the only consequence will be the creation of an unneeded label. This is better than
+ // creating a label for each NEW instruction, and faster than fully decoding the whole stack
+ // map table.
+ for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) {
+ if (classFileBuffer[offset] == Frame.ITEM_UNINITIALIZED) {
+ int potentialBytecodeOffset = readUnsignedShort(offset + 1);
+ if (potentialBytecodeOffset >= 0
+ && potentialBytecodeOffset < codeLength
+ && (classFileBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF)
+ == Opcodes.NEW) {
+ createLabel(potentialBytecodeOffset, labels);
+ }
}
+ }
+ }
+ if (expandFrames && (context.parsingOptions & EXPAND_ASM_INSNS) != 0) {
+ // Expanding the ASM specific instructions can introduce F_INSERT frames, even if the method
+ // does not currently have any frame. These inserted frames must be computed by simulating the
+ // effect of the bytecode instructions, one by one, starting from the implicit first frame.
+ // For this, MethodWriter needs to know maxLocals before the first instruction is visited. To
+ // ensure this, we visit the implicit first frame here (passing only maxLocals - the rest is
+ // computed in MethodWriter).
+ methodVisitor.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null);
+ }
- // visits the method code
- if (code != 0) {
- context.access = access;
- context.name = name;
- context.desc = desc;
- mv.visitCode();
- readCode(mv, context, code);
+ // Visit the bytecode instructions. First, introduce state variables for the incremental parsing
+ // of the type annotations.
+
+ // Index of the next runtime visible type annotation to read (in the
+ // visibleTypeAnnotationOffsets array).
+ int currentVisibleTypeAnnotationIndex = 0;
+ // The bytecode offset of the next runtime visible type annotation to read, or -1.
+ int currentVisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0);
+ // Index of the next runtime invisible type annotation to read (in the
+ // invisibleTypeAnnotationOffsets array).
+ int currentInvisibleTypeAnnotationIndex = 0;
+ // The bytecode offset of the next runtime invisible type annotation to read, or -1.
+ int currentInvisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0);
+
+ // Whether a F_INSERT stack map frame must be inserted before the current instruction.
+ boolean insertFrame = false;
+
+ // The delta to subtract from a goto_w or jsr_w opcode to get the corresponding goto or jsr
+ // opcode, or 0 if goto_w and jsr_w must be left unchanged (i.e. when expanding ASM specific
+ // instructions).
+ final int wideJumpOpcodeDelta =
+ (context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0;
+
+ currentOffset = bytecodeStartOffset;
+ while (currentOffset < bytecodeEndOffset) {
+ final int currentBytecodeOffset = currentOffset - bytecodeStartOffset;
+
+ // Visit the label and the line number(s) for this bytecode offset, if any.
+ Label currentLabel = labels[currentBytecodeOffset];
+ if (currentLabel != null) {
+ currentLabel.accept(methodVisitor, (context.parsingOptions & SKIP_DEBUG) == 0);
+ }
+
+ // Visit the stack map frame for this bytecode offset, if any.
+ while (stackMapFrameOffset != 0
+ && (context.currentFrameOffset == currentBytecodeOffset
+ || context.currentFrameOffset == -1)) {
+ // If there is a stack map frame for this offset, make methodVisitor visit it, and read the
+ // next stack map frame if there is one.
+ if (context.currentFrameOffset != -1) {
+ if (!compressedFrames || expandFrames) {
+ methodVisitor.visitFrame(
+ Opcodes.F_NEW,
+ context.currentFrameLocalCount,
+ context.currentFrameLocalTypes,
+ context.currentFrameStackCount,
+ context.currentFrameStackTypes);
+ } else {
+ methodVisitor.visitFrame(
+ context.currentFrameType,
+ context.currentFrameLocalCountDelta,
+ context.currentFrameLocalTypes,
+ context.currentFrameStackCount,
+ context.currentFrameStackTypes);
+ }
+ // Since there is already a stack map frame for this bytecode offset, there is no need to
+ // insert a new one.
+ insertFrame = false;
}
-
- // visits the end of the method
- mv.visitEnd();
-
- return u;
- }
-
- /**
- * Reads the bytecode of a method and makes the given visitor visit it.
- *
- * @param mv
- * the visitor that must visit the method's code.
- * @param context
- * information about the class being parsed.
- * @param u
- * the start offset of the code attribute in the class file.
- */
- private void readCode(final MethodVisitor mv, final Context context, int u) {
- // reads the header
- byte[] b = this.b;
- char[] c = context.buffer;
- int maxStack = readUnsignedShort(u);
- int maxLocals = readUnsignedShort(u + 2);
- int codeLength = readInt(u + 4);
- u += 8;
-
- // reads the bytecode to find the labels
- int codeStart = u;
- int codeEnd = u + codeLength;
- Label[] labels = new Label[codeLength + 2];
- readLabel(codeLength + 1, labels);
- while (u < codeEnd) {
- int offset = u - codeStart;
- int opcode = b[u] & 0xFF;
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- case ClassWriter.IMPLVAR_INSN:
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- readLabel(offset + readShort(u + 1), labels);
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- readLabel(offset + readInt(u + 1), labels);
- u += 5;
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- u += 6;
- } else {
- u += 4;
- }
- break;
- case ClassWriter.TABL_INSN:
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- readLabel(offset + readInt(u), labels);
- for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
- readLabel(offset + readInt(u + 12), labels);
- u += 4;
- }
- u += 12;
- break;
- case ClassWriter.LOOK_INSN:
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- readLabel(offset + readInt(u), labels);
- for (int i = readInt(u + 4); i > 0; --i) {
- readLabel(offset + readInt(u + 12), labels);
- u += 8;
- }
- u += 8;
- break;
- case ClassWriter.VAR_INSN:
- case ClassWriter.SBYTE_INSN:
- case ClassWriter.LDC_INSN:
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- case ClassWriter.LDCW_INSN:
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.TYPE_INSN:
- case ClassWriter.IINC_INSN:
- u += 3;
- break;
- case ClassWriter.ITFMETH_INSN:
- case ClassWriter.INDYMETH_INSN:
- u += 5;
- break;
- // case MANA_INSN:
- default:
- u += 4;
- break;
- }
+ if (stackMapFrameOffset < stackMapTableEndOffset) {
+ stackMapFrameOffset =
+ readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context);
+ } else {
+ stackMapFrameOffset = 0;
}
+ }
- // reads the try catch entries to find the labels, and also visits them
- for (int i = readUnsignedShort(u); i > 0; --i) {
- Label start = readLabel(readUnsignedShort(u + 2), labels);
- Label end = readLabel(readUnsignedShort(u + 4), labels);
- Label handler = readLabel(readUnsignedShort(u + 6), labels);
- String type = readUTF8(items[readUnsignedShort(u + 8)], c);
- mv.visitTryCatchBlock(start, end, handler, type);
- u += 8;
+ // Insert a stack map frame for this bytecode offset, if requested by setting insertFrame to
+ // true during the previous iteration. The actual frame content is computed in MethodWriter.
+ if (insertFrame) {
+ if ((context.parsingOptions & EXPAND_FRAMES) != 0) {
+ methodVisitor.visitFrame(Constants.F_INSERT, 0, null, 0, null);
}
- u += 2;
-
- // reads the code attributes
- int varTable = 0;
- int varTypeTable = 0;
- boolean zip = true;
- boolean unzip = (context.flags & EXPAND_FRAMES) != 0;
- int stackMap = 0;
- int stackMapSize = 0;
- int frameCount = 0;
- Context frame = null;
- Attribute attributes = null;
-
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- if ("LocalVariableTable".equals(attrName)) {
- if ((context.flags & SKIP_DEBUG) == 0) {
- varTable = u + 8;
- for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
- int label = readUnsignedShort(v + 10);
- if (labels[label] == null) {
- readLabel(label, labels).status |= Label.DEBUG;
- }
- label += readUnsignedShort(v + 12);
- if (labels[label] == null) {
- readLabel(label, labels).status |= Label.DEBUG;
- }
- v += 10;
- }
- }
- } else if ("LocalVariableTypeTable".equals(attrName)) {
- varTypeTable = u + 8;
- } else if ("LineNumberTable".equals(attrName)) {
- if ((context.flags & SKIP_DEBUG) == 0) {
- for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
- int label = readUnsignedShort(v + 10);
- if (labels[label] == null) {
- readLabel(label, labels).status |= Label.DEBUG;
- }
- labels[label].line = readUnsignedShort(v + 12);
- v += 4;
- }
- }
- } else if (FRAMES && "StackMapTable".equals(attrName)) {
- if ((context.flags & SKIP_FRAMES) == 0) {
- stackMap = u + 10;
- stackMapSize = readInt(u + 4);
- frameCount = readUnsignedShort(u + 8);
- }
- /*
- * here we do not extract the labels corresponding to the
- * attribute content. This would require a full parsing of the
- * attribute, which would need to be repeated in the second
- * phase (see below). Instead the content of the attribute is
- * read one frame at a time (i.e. after a frame has been
- * visited, the next frame is read), and the labels it contains
- * are also extracted one frame at a time. Thanks to the
- * ordering of frames, having only a "one frame lookahead" is
- * not a problem, i.e. it is not possible to see an offset
- * smaller than the offset of the current insn and for which no
- * Label exist.
- */
- /*
- * This is not true for UNINITIALIZED type offsets. We solve
- * this by parsing the stack map table without a full decoding
- * (see below).
- */
- } else if (FRAMES && "StackMap".equals(attrName)) {
- if ((context.flags & SKIP_FRAMES) == 0) {
- zip = false;
- stackMap = u + 10;
- stackMapSize = readInt(u + 4);
- frameCount = readUnsignedShort(u + 8);
- }
- /*
- * IMPORTANT! here we assume that the frames are ordered, as in
- * the StackMapTable attribute, although this is not guaranteed
- * by the attribute format.
- */
+ insertFrame = false;
+ }
+
+ // Visit the instruction at this bytecode offset.
+ int opcode = classFileBuffer[currentOffset] & 0xFF;
+ switch (opcode) {
+ case Constants.NOP:
+ case Constants.ACONST_NULL:
+ case Constants.ICONST_M1:
+ case Constants.ICONST_0:
+ case Constants.ICONST_1:
+ case Constants.ICONST_2:
+ case Constants.ICONST_3:
+ case Constants.ICONST_4:
+ case Constants.ICONST_5:
+ case Constants.LCONST_0:
+ case Constants.LCONST_1:
+ case Constants.FCONST_0:
+ case Constants.FCONST_1:
+ case Constants.FCONST_2:
+ case Constants.DCONST_0:
+ case Constants.DCONST_1:
+ case Constants.IALOAD:
+ case Constants.LALOAD:
+ case Constants.FALOAD:
+ case Constants.DALOAD:
+ case Constants.AALOAD:
+ case Constants.BALOAD:
+ case Constants.CALOAD:
+ case Constants.SALOAD:
+ case Constants.IASTORE:
+ case Constants.LASTORE:
+ case Constants.FASTORE:
+ case Constants.DASTORE:
+ case Constants.AASTORE:
+ case Constants.BASTORE:
+ case Constants.CASTORE:
+ case Constants.SASTORE:
+ case Constants.POP:
+ case Constants.POP2:
+ case Constants.DUP:
+ case Constants.DUP_X1:
+ case Constants.DUP_X2:
+ case Constants.DUP2:
+ case Constants.DUP2_X1:
+ case Constants.DUP2_X2:
+ case Constants.SWAP:
+ case Constants.IADD:
+ case Constants.LADD:
+ case Constants.FADD:
+ case Constants.DADD:
+ case Constants.ISUB:
+ case Constants.LSUB:
+ case Constants.FSUB:
+ case Constants.DSUB:
+ case Constants.IMUL:
+ case Constants.LMUL:
+ case Constants.FMUL:
+ case Constants.DMUL:
+ case Constants.IDIV:
+ case Constants.LDIV:
+ case Constants.FDIV:
+ case Constants.DDIV:
+ case Constants.IREM:
+ case Constants.LREM:
+ case Constants.FREM:
+ case Constants.DREM:
+ case Constants.INEG:
+ case Constants.LNEG:
+ case Constants.FNEG:
+ case Constants.DNEG:
+ case Constants.ISHL:
+ case Constants.LSHL:
+ case Constants.ISHR:
+ case Constants.LSHR:
+ case Constants.IUSHR:
+ case Constants.LUSHR:
+ case Constants.IAND:
+ case Constants.LAND:
+ case Constants.IOR:
+ case Constants.LOR:
+ case Constants.IXOR:
+ case Constants.LXOR:
+ case Constants.I2L:
+ case Constants.I2F:
+ case Constants.I2D:
+ case Constants.L2I:
+ case Constants.L2F:
+ case Constants.L2D:
+ case Constants.F2I:
+ case Constants.F2L:
+ case Constants.F2D:
+ case Constants.D2I:
+ case Constants.D2L:
+ case Constants.D2F:
+ case Constants.I2B:
+ case Constants.I2C:
+ case Constants.I2S:
+ case Constants.LCMP:
+ case Constants.FCMPL:
+ case Constants.FCMPG:
+ case Constants.DCMPL:
+ case Constants.DCMPG:
+ case Constants.IRETURN:
+ case Constants.LRETURN:
+ case Constants.FRETURN:
+ case Constants.DRETURN:
+ case Constants.ARETURN:
+ case Constants.RETURN:
+ case Constants.ARRAYLENGTH:
+ case Constants.ATHROW:
+ case Constants.MONITORENTER:
+ case Constants.MONITOREXIT:
+ methodVisitor.visitInsn(opcode);
+ currentOffset += 1;
+ break;
+ case Constants.ILOAD_0:
+ case Constants.ILOAD_1:
+ case Constants.ILOAD_2:
+ case Constants.ILOAD_3:
+ case Constants.LLOAD_0:
+ case Constants.LLOAD_1:
+ case Constants.LLOAD_2:
+ case Constants.LLOAD_3:
+ case Constants.FLOAD_0:
+ case Constants.FLOAD_1:
+ case Constants.FLOAD_2:
+ case Constants.FLOAD_3:
+ case Constants.DLOAD_0:
+ case Constants.DLOAD_1:
+ case Constants.DLOAD_2:
+ case Constants.DLOAD_3:
+ case Constants.ALOAD_0:
+ case Constants.ALOAD_1:
+ case Constants.ALOAD_2:
+ case Constants.ALOAD_3:
+ opcode -= Constants.ILOAD_0;
+ methodVisitor.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
+ currentOffset += 1;
+ break;
+ case Constants.ISTORE_0:
+ case Constants.ISTORE_1:
+ case Constants.ISTORE_2:
+ case Constants.ISTORE_3:
+ case Constants.LSTORE_0:
+ case Constants.LSTORE_1:
+ case Constants.LSTORE_2:
+ case Constants.LSTORE_3:
+ case Constants.FSTORE_0:
+ case Constants.FSTORE_1:
+ case Constants.FSTORE_2:
+ case Constants.FSTORE_3:
+ case Constants.DSTORE_0:
+ case Constants.DSTORE_1:
+ case Constants.DSTORE_2:
+ case Constants.DSTORE_3:
+ case Constants.ASTORE_0:
+ case Constants.ASTORE_1:
+ case Constants.ASTORE_2:
+ case Constants.ASTORE_3:
+ opcode -= Constants.ISTORE_0;
+ methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3);
+ currentOffset += 1;
+ break;
+ case Constants.IFEQ:
+ case Constants.IFNE:
+ case Constants.IFLT:
+ case Constants.IFGE:
+ case Constants.IFGT:
+ case Constants.IFLE:
+ case Constants.IF_ICMPEQ:
+ case Constants.IF_ICMPNE:
+ case Constants.IF_ICMPLT:
+ case Constants.IF_ICMPGE:
+ case Constants.IF_ICMPGT:
+ case Constants.IF_ICMPLE:
+ case Constants.IF_ACMPEQ:
+ case Constants.IF_ACMPNE:
+ case Constants.GOTO:
+ case Constants.JSR:
+ case Constants.IFNULL:
+ case Constants.IFNONNULL:
+ methodVisitor.visitJumpInsn(
+ opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]);
+ currentOffset += 3;
+ break;
+ case Constants.GOTO_W:
+ case Constants.JSR_W:
+ methodVisitor.visitJumpInsn(
+ opcode - wideJumpOpcodeDelta,
+ labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
+ currentOffset += 5;
+ break;
+ case Constants.ASM_IFEQ:
+ case Constants.ASM_IFNE:
+ case Constants.ASM_IFLT:
+ case Constants.ASM_IFGE:
+ case Constants.ASM_IFGT:
+ case Constants.ASM_IFLE:
+ case Constants.ASM_IF_ICMPEQ:
+ case Constants.ASM_IF_ICMPNE:
+ case Constants.ASM_IF_ICMPLT:
+ case Constants.ASM_IF_ICMPGE:
+ case Constants.ASM_IF_ICMPGT:
+ case Constants.ASM_IF_ICMPLE:
+ case Constants.ASM_IF_ACMPEQ:
+ case Constants.ASM_IF_ACMPNE:
+ case Constants.ASM_GOTO:
+ case Constants.ASM_JSR:
+ case Constants.ASM_IFNULL:
+ case Constants.ASM_IFNONNULL:
+ {
+ // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO
+ // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:...,
+ // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and
+ // where designates the instruction just after the GOTO_W.
+ // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and
+ // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL.
+ opcode =
+ opcode < Constants.ASM_IFNULL
+ ? opcode - Constants.ASM_OPCODE_DELTA
+ : opcode - Constants.ASM_IFNULL_OPCODE_DELTA;
+ Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)];
+ if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
+ // Replace GOTO with GOTO_W and JSR with JSR_W.
+ methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target);
} else {
- for (int j = 0; j < context.attrs.length; ++j) {
- if (context.attrs[j].type.equals(attrName)) {
- Attribute attr = context.attrs[j].read(this, u + 8,
- readInt(u + 4), c, codeStart - 8, labels);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- }
- }
- u += 6 + readInt(u + 4);
- }
- u += 2;
-
- // generates the first (implicit) stack map frame
- if (FRAMES && stackMap != 0) {
- /*
- * for the first explicit frame the offset is not offset_delta + 1
- * but only offset_delta; setting the implicit frame offset to -1
- * allow the use of the "offset_delta + 1" rule in all cases
- */
- frame = context;
- frame.offset = -1;
- frame.mode = 0;
- frame.localCount = 0;
- frame.localDiff = 0;
- frame.stackCount = 0;
- frame.local = new Object[maxLocals];
- frame.stack = new Object[maxStack];
- if (unzip) {
- getImplicitFrame(context);
- }
- /*
- * Finds labels for UNINITIALIZED frame types. Instead of decoding
- * each element of the stack map table, we look for 3 consecutive
- * bytes that "look like" an UNINITIALIZED type (tag 8, offset
- * within code bounds, NEW instruction at this offset). We may find
- * false positives (i.e. not real UNINITIALIZED types), but this
- * should be rare, and the only consequence will be the creation of
- * an unneeded label. This is better than creating a label for each
- * NEW instruction, and faster than fully decoding the whole stack
- * map table.
- */
- for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) {
- if (b[i] == 8) { // UNINITIALIZED FRAME TYPE
- int v = readUnsignedShort(i + 1);
- if (v >= 0 && v < codeLength) {
- if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) {
- readLabel(v, labels);
- }
- }
- }
+ // Compute the "opposite" of opcode. This can be done by flipping the least
+ // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ
+ // (with a pre and post offset by 1).
+ opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1;
+ Label endif = createLabel(currentBytecodeOffset + 3, labels);
+ methodVisitor.visitJumpInsn(opcode, endif);
+ methodVisitor.visitJumpInsn(Constants.GOTO_W, target);
+ // endif designates the instruction just after GOTO_W, and is visited as part of the
+ // next instruction. Since it is a jump target, we need to insert a frame here.
+ insertFrame = true;
}
- }
-
- // visits the instructions
- u = codeStart;
- while (u < codeEnd) {
- int offset = u - codeStart;
-
- // visits the label and line number for this offset, if any
- Label l = labels[offset];
- if (l != null) {
- mv.visitLabel(l);
- if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
- mv.visitLineNumber(l.line, l);
- }
- }
-
- // visits the frame for this offset, if any
- while (FRAMES && frame != null
- && (frame.offset == offset || frame.offset == -1)) {
- // if there is a frame for this offset, makes the visitor visit
- // it, and reads the next frame if there is one.
- if (frame.offset != -1) {
- if (!zip || unzip) {
- mv.visitFrame(Opcodes.F_NEW, frame.localCount,
- frame.local, frame.stackCount, frame.stack);
- } else {
- mv.visitFrame(frame.mode, frame.localDiff, frame.local,
- frame.stackCount, frame.stack);
- }
- }
- if (frameCount > 0) {
- stackMap = readFrame(stackMap, zip, unzip, labels, frame);
- --frameCount;
- } else {
- frame = null;
- }
- }
-
- // visits the instruction at this offset
- int opcode = b[u] & 0xFF;
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- mv.visitInsn(opcode);
- u += 1;
- break;
- case ClassWriter.IMPLVAR_INSN:
- if (opcode > Opcodes.ISTORE) {
- opcode -= 59; // ISTORE_0
- mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
- opcode & 0x3);
- } else {
- opcode -= 26; // ILOAD_0
- mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
- }
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]);
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]);
- u += 5;
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4));
- u += 6;
- } else {
- mv.visitVarInsn(opcode, readUnsignedShort(u + 2));
- u += 4;
- }
- break;
- case ClassWriter.TABL_INSN: {
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- int label = offset + readInt(u);
- int min = readInt(u + 4);
- int max = readInt(u + 8);
- Label[] table = new Label[max - min + 1];
- u += 12;
- for (int i = 0; i < table.length; ++i) {
- table[i] = labels[offset + readInt(u)];
- u += 4;
- }
- mv.visitTableSwitchInsn(min, max, labels[label], table);
- break;
+ currentOffset += 3;
+ break;
+ }
+ case Constants.ASM_GOTO_W:
+ {
+ // Replace ASM_GOTO_W with GOTO_W.
+ methodVisitor.visitJumpInsn(
+ Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
+ // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns
+ // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame
+ // here.
+ insertFrame = true;
+ currentOffset += 5;
+ break;
+ }
+ case Constants.WIDE:
+ opcode = classFileBuffer[currentOffset + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ methodVisitor.visitIincInsn(
+ readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4));
+ currentOffset += 6;
+ } else {
+ methodVisitor.visitVarInsn(opcode, readUnsignedShort(currentOffset + 2));
+ currentOffset += 4;
+ }
+ break;
+ case Constants.TABLESWITCH:
+ {
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (currentBytecodeOffset & 3);
+ // Read the instruction.
+ Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
+ int low = readInt(currentOffset + 4);
+ int high = readInt(currentOffset + 8);
+ currentOffset += 12;
+ Label[] table = new Label[high - low + 1];
+ for (int i = 0; i < table.length; ++i) {
+ table[i] = labels[currentBytecodeOffset + readInt(currentOffset)];
+ currentOffset += 4;
}
- case ClassWriter.LOOK_INSN: {
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- int label = offset + readInt(u);
- int len = readInt(u + 4);
- int[] keys = new int[len];
- Label[] values = new Label[len];
- u += 8;
- for (int i = 0; i < len; ++i) {
- keys[i] = readInt(u);
- values[i] = labels[offset + readInt(u + 4)];
- u += 8;
- }
- mv.visitLookupSwitchInsn(labels[label], keys, values);
- break;
+ methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table);
+ break;
+ }
+ case Constants.LOOKUPSWITCH:
+ {
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (currentBytecodeOffset & 3);
+ // Read the instruction.
+ Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
+ int nPairs = readInt(currentOffset + 4);
+ currentOffset += 8;
+ int[] keys = new int[nPairs];
+ Label[] values = new Label[nPairs];
+ for (int i = 0; i < nPairs; ++i) {
+ keys[i] = readInt(currentOffset);
+ values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)];
+ currentOffset += 8;
}
- case ClassWriter.VAR_INSN:
- mv.visitVarInsn(opcode, b[u + 1] & 0xFF);
- u += 2;
- break;
- case ClassWriter.SBYTE_INSN:
- mv.visitIntInsn(opcode, b[u + 1]);
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- mv.visitIntInsn(opcode, readShort(u + 1));
- u += 3;
- break;
- case ClassWriter.LDC_INSN:
- mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c));
- u += 2;
- break;
- case ClassWriter.LDCW_INSN:
- mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c));
- u += 3;
- break;
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.ITFMETH_INSN: {
- int cpIndex = items[readUnsignedShort(u + 1)];
- String iowner = readClass(cpIndex, c);
- cpIndex = items[readUnsignedShort(cpIndex + 2)];
- String iname = readUTF8(cpIndex, c);
- String idesc = readUTF8(cpIndex + 2, c);
- if (opcode < Opcodes.INVOKEVIRTUAL) {
- mv.visitFieldInsn(opcode, iowner, iname, idesc);
- } else {
- mv.visitMethodInsn(opcode, iowner, iname, idesc);
- }
- if (opcode == Opcodes.INVOKEINTERFACE) {
- u += 5;
- } else {
- u += 3;
- }
- break;
+ methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values);
+ break;
+ }
+ case Constants.ILOAD:
+ case Constants.LLOAD:
+ case Constants.FLOAD:
+ case Constants.DLOAD:
+ case Constants.ALOAD:
+ case Constants.ISTORE:
+ case Constants.LSTORE:
+ case Constants.FSTORE:
+ case Constants.DSTORE:
+ case Constants.ASTORE:
+ case Constants.RET:
+ methodVisitor.visitVarInsn(opcode, classFileBuffer[currentOffset + 1] & 0xFF);
+ currentOffset += 2;
+ break;
+ case Constants.BIPUSH:
+ case Constants.NEWARRAY:
+ methodVisitor.visitIntInsn(opcode, classFileBuffer[currentOffset + 1]);
+ currentOffset += 2;
+ break;
+ case Constants.SIPUSH:
+ methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1));
+ currentOffset += 3;
+ break;
+ case Constants.LDC:
+ methodVisitor.visitLdcInsn(
+ readConst(classFileBuffer[currentOffset + 1] & 0xFF, charBuffer));
+ currentOffset += 2;
+ break;
+ case Constants.LDC_W:
+ case Constants.LDC2_W:
+ methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer));
+ currentOffset += 3;
+ break;
+ case Constants.GETSTATIC:
+ case Constants.PUTSTATIC:
+ case Constants.GETFIELD:
+ case Constants.PUTFIELD:
+ case Constants.INVOKEVIRTUAL:
+ case Constants.INVOKESPECIAL:
+ case Constants.INVOKESTATIC:
+ case Constants.INVOKEINTERFACE:
+ {
+ int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String owner = readClass(cpInfoOffset, charBuffer);
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ if (opcode < Opcodes.INVOKEVIRTUAL) {
+ methodVisitor.visitFieldInsn(opcode, owner, name, descriptor);
+ } else {
+ boolean isInterface =
+ classFileBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
+ methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
- case ClassWriter.INDYMETH_INSN: {
- int cpIndex = items[readUnsignedShort(u + 1)];
- int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
- Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
- int bsmArgCount = readUnsignedShort(bsmIndex + 2);
- Object[] bsmArgs = new Object[bsmArgCount];
- bsmIndex += 4;
- for (int i = 0; i < bsmArgCount; i++) {
- bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
- bsmIndex += 2;
- }
- cpIndex = items[readUnsignedShort(cpIndex + 2)];
- String iname = readUTF8(cpIndex, c);
- String idesc = readUTF8(cpIndex + 2, c);
- mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs);
- u += 5;
- break;
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ currentOffset += 5;
+ } else {
+ currentOffset += 3;
}
- case ClassWriter.TYPE_INSN:
- mv.visitTypeInsn(opcode, readClass(u + 1, c));
- u += 3;
- break;
- case ClassWriter.IINC_INSN:
- mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]);
- u += 3;
- break;
- // case MANA_INSN:
- default:
- mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF);
- u += 4;
- break;
+ break;
+ }
+ case Constants.INVOKEDYNAMIC:
+ {
+ int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)];
+ Handle handle =
+ (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ Object[] bootstrapMethodArguments =
+ new Object[readUnsignedShort(bootstrapMethodOffset + 2)];
+ bootstrapMethodOffset += 4;
+ for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+ bootstrapMethodArguments[i] =
+ readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ bootstrapMethodOffset += 2;
}
+ methodVisitor.visitInvokeDynamicInsn(
+ name, descriptor, handle, bootstrapMethodArguments);
+ currentOffset += 5;
+ break;
+ }
+ case Constants.NEW:
+ case Constants.ANEWARRAY:
+ case Constants.CHECKCAST:
+ case Constants.INSTANCEOF:
+ methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer));
+ currentOffset += 3;
+ break;
+ case Constants.IINC:
+ methodVisitor.visitIincInsn(
+ classFileBuffer[currentOffset + 1] & 0xFF, classFileBuffer[currentOffset + 2]);
+ currentOffset += 3;
+ break;
+ case Constants.MULTIANEWARRAY:
+ methodVisitor.visitMultiANewArrayInsn(
+ readClass(currentOffset + 1, charBuffer), classFileBuffer[currentOffset + 3] & 0xFF);
+ currentOffset += 4;
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ // Visit the runtime visible instruction annotations, if any.
+ while (visibleTypeAnnotationOffsets != null
+ && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length
+ && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
+ if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
+ // Parse the target_type, target_info and target_path fields.
+ int currentAnnotationOffset =
+ readTypeAnnotationTarget(
+ context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitInsnAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
}
- if (labels[codeLength] != null) {
- mv.visitLabel(labels[codeLength]);
+ currentVisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(
+ visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex);
+ }
+
+ // Visit the runtime invisible instruction annotations, if any.
+ while (invisibleTypeAnnotationOffsets != null
+ && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length
+ && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
+ if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
+ // Parse the target_type, target_info and target_path fields.
+ int currentAnnotationOffset =
+ readTypeAnnotationTarget(
+ context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitInsnAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
}
+ currentInvisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(
+ invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex);
+ }
+ }
+ if (labels[codeLength] != null) {
+ methodVisitor.visitLabel(labels[codeLength]);
+ }
- // visits the local variable tables
- if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) {
- int[] typeTable = null;
- if (varTypeTable != 0) {
- u = varTypeTable + 2;
- typeTable = new int[readUnsignedShort(varTypeTable) * 3];
- for (int i = typeTable.length; i > 0;) {
- typeTable[--i] = u + 6; // signature
- typeTable[--i] = readUnsignedShort(u + 8); // index
- typeTable[--i] = readUnsignedShort(u); // start
- u += 10;
- }
- }
- u = varTable + 2;
- for (int i = readUnsignedShort(varTable); i > 0; --i) {
- int start = readUnsignedShort(u);
- int length = readUnsignedShort(u + 2);
- int index = readUnsignedShort(u + 8);
- String vsignature = null;
- if (typeTable != null) {
- for (int j = 0; j < typeTable.length; j += 3) {
- if (typeTable[j] == start && typeTable[j + 1] == index) {
- vsignature = readUTF8(typeTable[j + 2], c);
- break;
- }
- }
- }
- mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c),
- vsignature, labels[start], labels[start + length],
- index);
- u += 10;
+ // Visit LocalVariableTable and LocalVariableTypeTable attributes.
+ if (localVariableTableOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) {
+ // The (start_pc, index, signature_index) fields of each entry of the LocalVariableTypeTable.
+ int[] typeTable = null;
+ if (localVariableTypeTableOffset != 0) {
+ typeTable = new int[readUnsignedShort(localVariableTypeTableOffset) * 3];
+ currentOffset = localVariableTypeTableOffset + 2;
+ int typeTableIndex = typeTable.length;
+ while (typeTableIndex > 0) {
+ // Store the offset of 'signature_index', and the value of 'index' and 'start_pc'.
+ typeTable[--typeTableIndex] = currentOffset + 6;
+ typeTable[--typeTableIndex] = readUnsignedShort(currentOffset + 8);
+ typeTable[--typeTableIndex] = readUnsignedShort(currentOffset);
+ currentOffset += 10;
+ }
+ }
+ int localVariableTableLength = readUnsignedShort(localVariableTableOffset);
+ currentOffset = localVariableTableOffset + 2;
+ while (localVariableTableLength-- > 0) {
+ int startPc = readUnsignedShort(currentOffset);
+ int length = readUnsignedShort(currentOffset + 2);
+ String name = readUTF8(currentOffset + 4, charBuffer);
+ String descriptor = readUTF8(currentOffset + 6, charBuffer);
+ int index = readUnsignedShort(currentOffset + 8);
+ currentOffset += 10;
+ String signature = null;
+ if (typeTable != null) {
+ for (int i = 0; i < typeTable.length; i += 3) {
+ if (typeTable[i] == startPc && typeTable[i + 1] == index) {
+ signature = readUTF8(typeTable[i + 2], charBuffer);
+ break;
}
+ }
}
+ methodVisitor.visitLocalVariable(
+ name, descriptor, signature, labels[startPc], labels[startPc + length], index);
+ }
+ }
- // visits the code attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- mv.visitAttribute(attributes);
- attributes = attr;
+ // Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute.
+ if (visibleTypeAnnotationOffsets != null) {
+ for (int i = 0; i < visibleTypeAnnotationOffsets.length; ++i) {
+ int targetType = readByte(visibleTypeAnnotationOffsets[i]);
+ if (targetType == TypeReference.LOCAL_VARIABLE
+ || targetType == TypeReference.RESOURCE_VARIABLE) {
+ // Parse the target_type, target_info and target_path fields.
+ currentOffset = readTypeAnnotationTarget(context, visibleTypeAnnotationOffsets[i]);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitLocalVariableAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ context.currentLocalVariableAnnotationRangeStarts,
+ context.currentLocalVariableAnnotationRangeEnds,
+ context.currentLocalVariableAnnotationRangeIndices,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
}
+ }
+ }
- // visits the max stack and max locals values
- mv.visitMaxs(maxStack, maxLocals);
- }
-
- /**
- * Reads parameter annotations and makes the given visitor visit them.
- *
- * @param v
- * start offset in {@link #b b} of the annotations to be read.
- * @param desc
- * the method descriptor.
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param visible
- * true if the annotations to be read are visible at
- * runtime.
- * @param mv
- * the visitor that must visit the annotations.
- */
- private void readParameterAnnotations(int v, final String desc,
- final char[] buf, final boolean visible, final MethodVisitor mv) {
- int i;
- int n = b[v++] & 0xFF;
- // workaround for a bug in javac (javac compiler generates a parameter
- // annotation array whose size is equal to the number of parameters in
- // the Java source file, while it should generate an array whose size is
- // equal to the number of parameters in the method descriptor - which
- // includes the synthetic parameters added by the compiler). This work-
- // around supposes that the synthetic parameters are the first ones.
- int synthetics = Type.getArgumentTypes(desc).length - n;
- AnnotationVisitor av;
- for (i = 0; i < synthetics; ++i) {
- // virtual annotation to detect synthetic parameters in MethodWriter
- av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false);
- if (av != null) {
- av.visitEnd();
- }
- }
- for (; i < n + synthetics; ++i) {
- int j = readUnsignedShort(v);
- v += 2;
- for (; j > 0; --j) {
- av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible);
- v = readAnnotationValues(v + 2, buf, true, av);
- }
+ // Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute.
+ if (invisibleTypeAnnotationOffsets != null) {
+ for (int i = 0; i < invisibleTypeAnnotationOffsets.length; ++i) {
+ int targetType = readByte(invisibleTypeAnnotationOffsets[i]);
+ if (targetType == TypeReference.LOCAL_VARIABLE
+ || targetType == TypeReference.RESOURCE_VARIABLE) {
+ // Parse the target_type, target_info and target_path fields.
+ currentOffset = readTypeAnnotationTarget(context, invisibleTypeAnnotationOffsets[i]);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitLocalVariableAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ context.currentLocalVariableAnnotationRangeStarts,
+ context.currentLocalVariableAnnotationRangeEnds,
+ context.currentLocalVariableAnnotationRangeIndices,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
}
+ }
}
- /**
- * Reads the values of an annotation and makes the given visitor visit them.
- *
- * @param v
- * the start offset in {@link #b b} of the values to be read
- * (including the unsigned short that gives the number of
- * values).
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param named
- * if the annotation values are named or not.
- * @param av
- * the visitor that must visit the values.
- * @return the end offset of the annotation values.
- */
- private int readAnnotationValues(int v, final char[] buf,
- final boolean named, final AnnotationVisitor av) {
- int i = readUnsignedShort(v);
- v += 2;
- if (named) {
- for (; i > 0; --i) {
- v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
- }
- } else {
- for (; i > 0; --i) {
- v = readAnnotationValue(v, buf, null, av);
- }
- }
- if (av != null) {
- av.visitEnd();
- }
- return v;
- }
-
- /**
- * Reads a value of an annotation and makes the given visitor visit it.
- *
- * @param v
- * the start offset in {@link #b b} of the value to be read
- * (not including the value name constant pool index).
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param name
- * the name of the value to be read.
- * @param av
- * the visitor that must visit the value.
- * @return the end offset of the annotation value.
- */
- private int readAnnotationValue(int v, final char[] buf, final String name,
- final AnnotationVisitor av) {
- int i;
- if (av == null) {
- switch (b[v] & 0xFF) {
- case 'e': // enum_const_value
- return v + 5;
- case '@': // annotation_value
- return readAnnotationValues(v + 3, buf, true, null);
- case '[': // array_value
- return readAnnotationValues(v + 1, buf, false, null);
- default:
- return v + 3;
- }
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in MethodWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ methodVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
+
+ // Visit the max stack and max locals values.
+ methodVisitor.visitMaxs(maxStack, maxLocals);
+ }
+
+ /**
+ * Returns the label corresponding to the given bytecode offset. The default implementation of
+ * this method creates a label for the given offset if it has not been already created.
+ *
+ * @param bytecodeOffset a bytecode offset in a method.
+ * @param labels the already created labels, indexed by their offset. If a label already exists
+ * for bytecodeOffset this method must not create a new one. Otherwise it must store the new
+ * label in this array.
+ * @return a non null Label, which must be equal to labels[bytecodeOffset].
+ */
+ protected Label readLabel(final int bytecodeOffset, final Label[] labels) {
+ if (labels[bytecodeOffset] == null) {
+ labels[bytecodeOffset] = new Label();
+ }
+ return labels[bytecodeOffset];
+ }
+
+ /**
+ * Creates a label without the {@link Label#FLAG_DEBUG_ONLY} flag set, for the given bytecode
+ * offset. The label is created with a call to {@link #readLabel} and its {@link
+ * Label#FLAG_DEBUG_ONLY} flag is cleared.
+ *
+ * @param bytecodeOffset a bytecode offset in a method.
+ * @param labels the already created labels, indexed by their offset.
+ * @return a Label without the {@link Label#FLAG_DEBUG_ONLY} flag set.
+ */
+ private Label createLabel(final int bytecodeOffset, final Label[] labels) {
+ Label label = readLabel(bytecodeOffset, labels);
+ label.flags &= ~Label.FLAG_DEBUG_ONLY;
+ return label;
+ }
+
+ /**
+ * Creates a label with the {@link Label#FLAG_DEBUG_ONLY} flag set, if there is no already
+ * existing label for the given bytecode offset (otherwise does nothing). The label is created
+ * with a call to {@link #readLabel}.
+ *
+ * @param bytecodeOffset a bytecode offset in a method.
+ * @param labels the already created labels, indexed by their offset.
+ */
+ private void createDebugLabel(final int bytecodeOffset, final Label[] labels) {
+ if (labels[bytecodeOffset] == null) {
+ readLabel(bytecodeOffset, labels).flags |= Label.FLAG_DEBUG_ONLY;
+ }
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse annotations, type annotations and parameter annotations
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Parses a Runtime[In]VisibleTypeAnnotations attribute to find the offset of each type_annotation
+ * entry it contains, to find the corresponding labels, and to visit the try catch block
+ * annotations.
+ *
+ * @param methodVisitor the method visitor to be used to visit the try catch block annotations.
+ * @param context information about the class being parsed.
+ * @param runtimeTypeAnnotationsOffset the start offset of a Runtime[In]VisibleTypeAnnotations
+ * attribute, excluding the attribute_info's attribute_name_index and attribute_length fields.
+ * @param visible true if the attribute to parse is a RuntimeVisibleTypeAnnotations attribute,
+ * false it is a RuntimeInvisibleTypeAnnotations attribute.
+ * @return the start offset of each entry of the Runtime[In]VisibleTypeAnnotations_attribute's
+ * 'annotations' array field.
+ */
+ private int[] readTypeAnnotations(
+ final MethodVisitor methodVisitor,
+ final Context context,
+ final int runtimeTypeAnnotationsOffset,
+ final boolean visible) {
+ char[] charBuffer = context.charBuffer;
+ int currentOffset = runtimeTypeAnnotationsOffset;
+ // Read the num_annotations field and create an array to store the type_annotation offsets.
+ int[] typeAnnotationsOffsets = new int[readUnsignedShort(currentOffset)];
+ currentOffset += 2;
+ // Parse the 'annotations' array field.
+ for (int i = 0; i < typeAnnotationsOffsets.length; ++i) {
+ typeAnnotationsOffsets[i] = currentOffset;
+ // Parse the type_annotation's target_type and the target_info fields. The size of the
+ // target_info field depends on the value of target_type.
+ int targetType = readInt(currentOffset);
+ switch (targetType >>> 24) {
+ case TypeReference.LOCAL_VARIABLE:
+ case TypeReference.RESOURCE_VARIABLE:
+ // A localvar_target has a variable size, which depends on the value of their table_length
+ // field. It also references bytecode offsets, for which we need labels.
+ int tableLength = readUnsignedShort(currentOffset + 1);
+ currentOffset += 3;
+ while (tableLength-- > 0) {
+ int startPc = readUnsignedShort(currentOffset);
+ int length = readUnsignedShort(currentOffset + 2);
+ // Skip the index field (2 bytes).
+ currentOffset += 6;
+ createLabel(startPc, context.currentMethodLabels);
+ createLabel(startPc + length, context.currentMethodLabels);
+ }
+ break;
+ case TypeReference.CAST:
+ case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+ case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
+ currentOffset += 4;
+ break;
+ case TypeReference.CLASS_EXTENDS:
+ case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
+ case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
+ case TypeReference.THROWS:
+ case TypeReference.EXCEPTION_PARAMETER:
+ case TypeReference.INSTANCEOF:
+ case TypeReference.NEW:
+ case TypeReference.CONSTRUCTOR_REFERENCE:
+ case TypeReference.METHOD_REFERENCE:
+ currentOffset += 3;
+ break;
+ case TypeReference.CLASS_TYPE_PARAMETER:
+ case TypeReference.METHOD_TYPE_PARAMETER:
+ case TypeReference.METHOD_FORMAL_PARAMETER:
+ case TypeReference.FIELD:
+ case TypeReference.METHOD_RETURN:
+ case TypeReference.METHOD_RECEIVER:
+ default:
+ // TypeReference type which can't be used in Code attribute, or which is unknown.
+ throw new IllegalArgumentException();
+ }
+ // Parse the rest of the type_annotation structure, starting with the target_path structure
+ // (whose size depends on its path_length field).
+ int pathLength = readByte(currentOffset);
+ if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) {
+ // Parse the target_path structure and create a corresponding TypePath.
+ TypePath path = pathLength == 0 ? null : new TypePath(b, currentOffset);
+ currentOffset += 1 + 2 * pathLength;
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentOffset =
+ readElementValues(
+ methodVisitor.visitTryCatchAnnotation(
+ targetType & 0xFFFFFF00, path, annotationDescriptor, visible),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
+ } else {
+ // We don't want to visit the other target_type annotations, so we just skip them (which
+ // requires some parsing because the element_value_pairs array has a variable size). First,
+ // skip the target_path structure:
+ currentOffset += 3 + 2 * pathLength;
+ // Then skip the num_element_value_pairs and element_value_pairs fields (by reading them
+ // with a null AnnotationVisitor).
+ currentOffset =
+ readElementValues(
+ /* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer);
+ }
+ }
+ return typeAnnotationsOffsets;
+ }
+
+ /**
+ * Returns the bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or
+ * -1 if there is no such type_annotation of if it does not have a bytecode offset.
+ *
+ * @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a
+ * Runtime[In]VisibleTypeAnnotations attribute, or null.
+ * @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets.
+ * @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1
+ * if there is no such type_annotation of if it does not have a bytecode offset.
+ */
+ private int getTypeAnnotationBytecodeOffset(
+ final int[] typeAnnotationOffsets, final int typeAnnotationIndex) {
+ if (typeAnnotationOffsets == null
+ || typeAnnotationIndex >= typeAnnotationOffsets.length
+ || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) {
+ return -1;
+ }
+ return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1);
+ }
+
+ /**
+ * Parses the header of a JVMS type_annotation structure to extract its target_type, target_info
+ * and target_path (the result is stored in the given context), and returns the start offset of
+ * the rest of the type_annotation structure.
+ *
+ * @param context information about the class being parsed. This is where the extracted
+ * target_type and target_path must be stored.
+ * @param typeAnnotationOffset the start offset of a type_annotation structure.
+ * @return the start offset of the rest of the type_annotation structure.
+ */
+ private int readTypeAnnotationTarget(final Context context, final int typeAnnotationOffset) {
+ int currentOffset = typeAnnotationOffset;
+ // Parse and store the target_type structure.
+ int targetType = readInt(typeAnnotationOffset);
+ switch (targetType >>> 24) {
+ case TypeReference.CLASS_TYPE_PARAMETER:
+ case TypeReference.METHOD_TYPE_PARAMETER:
+ case TypeReference.METHOD_FORMAL_PARAMETER:
+ targetType &= 0xFFFF0000;
+ currentOffset += 2;
+ break;
+ case TypeReference.FIELD:
+ case TypeReference.METHOD_RETURN:
+ case TypeReference.METHOD_RECEIVER:
+ targetType &= 0xFF000000;
+ currentOffset += 1;
+ break;
+ case TypeReference.LOCAL_VARIABLE:
+ case TypeReference.RESOURCE_VARIABLE:
+ targetType &= 0xFF000000;
+ int tableLength = readUnsignedShort(currentOffset + 1);
+ currentOffset += 3;
+ context.currentLocalVariableAnnotationRangeStarts = new Label[tableLength];
+ context.currentLocalVariableAnnotationRangeEnds = new Label[tableLength];
+ context.currentLocalVariableAnnotationRangeIndices = new int[tableLength];
+ for (int i = 0; i < tableLength; ++i) {
+ int startPc = readUnsignedShort(currentOffset);
+ int length = readUnsignedShort(currentOffset + 2);
+ int index = readUnsignedShort(currentOffset + 4);
+ currentOffset += 6;
+ context.currentLocalVariableAnnotationRangeStarts[i] =
+ createLabel(startPc, context.currentMethodLabels);
+ context.currentLocalVariableAnnotationRangeEnds[i] =
+ createLabel(startPc + length, context.currentMethodLabels);
+ context.currentLocalVariableAnnotationRangeIndices[i] = index;
}
- switch (b[v++] & 0xFF) {
- case 'I': // pointer to CONSTANT_Integer
- case 'J': // pointer to CONSTANT_Long
- case 'F': // pointer to CONSTANT_Float
- case 'D': // pointer to CONSTANT_Double
- av.visit(name, readConst(readUnsignedShort(v), buf));
- v += 2;
- break;
- case 'B': // pointer to CONSTANT_Byte
- av.visit(name,
- new Byte((byte) readInt(items[readUnsignedShort(v)])));
- v += 2;
- break;
- case 'Z': // pointer to CONSTANT_Boolean
- av.visit(name,
- readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
- : Boolean.TRUE);
- v += 2;
- break;
- case 'S': // pointer to CONSTANT_Short
- av.visit(name, new Short(
- (short) readInt(items[readUnsignedShort(v)])));
- v += 2;
- break;
- case 'C': // pointer to CONSTANT_Char
- av.visit(name, new Character(
- (char) readInt(items[readUnsignedShort(v)])));
- v += 2;
- break;
- case 's': // pointer to CONSTANT_Utf8
- av.visit(name, readUTF8(v, buf));
- v += 2;
- break;
+ break;
+ case TypeReference.CAST:
+ case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+ case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
+ targetType &= 0xFF0000FF;
+ currentOffset += 4;
+ break;
+ case TypeReference.CLASS_EXTENDS:
+ case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
+ case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
+ case TypeReference.THROWS:
+ case TypeReference.EXCEPTION_PARAMETER:
+ targetType &= 0xFFFFFF00;
+ currentOffset += 3;
+ break;
+ case TypeReference.INSTANCEOF:
+ case TypeReference.NEW:
+ case TypeReference.CONSTRUCTOR_REFERENCE:
+ case TypeReference.METHOD_REFERENCE:
+ targetType &= 0xFF000000;
+ currentOffset += 3;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ context.currentTypeAnnotationTarget = targetType;
+ // Parse and store the target_path structure.
+ int pathLength = readByte(currentOffset);
+ context.currentTypeAnnotationTargetPath =
+ pathLength == 0 ? null : new TypePath(b, currentOffset);
+ // Return the start offset of the rest of the type_annotation structure.
+ return currentOffset + 1 + 2 * pathLength;
+ }
+
+ /**
+ * Reads a Runtime[In]VisibleParameterAnnotations attribute and makes the given visitor visit it.
+ *
+ * @param methodVisitor the visitor that must visit the parameter annotations.
+ * @param context information about the class being parsed.
+ * @param runtimeParameterAnnotationsOffset the start offset of a
+ * Runtime[In]VisibleParameterAnnotations attribute, excluding the attribute_info's
+ * attribute_name_index and attribute_length fields.
+ * @param visible true if the attribute to parse is a RuntimeVisibleParameterAnnotations
+ * attribute, false it is a RuntimeInvisibleParameterAnnotations attribute.
+ */
+ private void readParameterAnnotations(
+ final MethodVisitor methodVisitor,
+ final Context context,
+ final int runtimeParameterAnnotationsOffset,
+ final boolean visible) {
+ int currentOffset = runtimeParameterAnnotationsOffset;
+ int numParameters = b[currentOffset++] & 0xFF;
+ methodVisitor.visitAnnotableParameterCount(numParameters, visible);
+ char[] charBuffer = context.charBuffer;
+ for (int i = 0; i < numParameters; ++i) {
+ int numAnnotations = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentOffset =
+ readElementValues(
+ methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+ }
+
+ /**
+ * Reads the element values of a JVMS 'annotation' structure and makes the given visitor visit
+ * them. This method can also be used to read the values of the JVMS 'array_value' field of an
+ * annotation's 'element_value'.
+ *
+ * @param annotationVisitor the visitor that must visit the values.
+ * @param annotationOffset the start offset of an 'annotation' structure (excluding its type_index
+ * field) or of an 'array_value' structure.
+ * @param named if the annotation values are named or not. This should be true to parse the values
+ * of a JVMS 'annotation' structure, and false to parse the JVMS 'array_value' of an
+ * annotation's element_value.
+ * @param charBuffer the buffer used to read strings in the constant pool.
+ * @return the end offset of the JVMS 'annotation' or 'array_value' structure.
+ */
+ private int readElementValues(
+ final AnnotationVisitor annotationVisitor,
+ final int annotationOffset,
+ final boolean named,
+ final char[] charBuffer) {
+ int currentOffset = annotationOffset;
+ // Read the num_element_value_pairs field (or num_values field for an array_value).
+ int numElementValuePairs = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ if (named) {
+ // Parse the element_value_pairs array.
+ while (numElementValuePairs-- > 0) {
+ String elementName = readUTF8(currentOffset, charBuffer);
+ currentOffset =
+ readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer);
+ }
+ } else {
+ // Parse the array_value array.
+ while (numElementValuePairs-- > 0) {
+ currentOffset =
+ readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer);
+ }
+ }
+ if (annotationVisitor != null) {
+ annotationVisitor.visitEnd();
+ }
+ return currentOffset;
+ }
+
+ /**
+ * Reads a JVMS 'element_value' structure and makes the given visitor visit it.
+ *
+ * @param annotationVisitor the visitor that must visit the element_value structure.
+ * @param elementValueOffset the start offset in {@link #b} of the element_value structure to be
+ * read.
+ * @param elementName the name of the element_value structure to be read, or null.
+ * @param charBuffer the buffer used to read strings in the constant pool.
+ * @return the end offset of the JVMS 'element_value' structure.
+ */
+ private int readElementValue(
+ final AnnotationVisitor annotationVisitor,
+ final int elementValueOffset,
+ final String elementName,
+ final char[] charBuffer) {
+ int currentOffset = elementValueOffset;
+ if (annotationVisitor == null) {
+ switch (b[currentOffset] & 0xFF) {
case 'e': // enum_const_value
- av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
- v += 4;
- break;
- case 'c': // class_info
- av.visit(name, Type.getType(readUTF8(v, buf)));
- v += 2;
- break;
+ return currentOffset + 5;
case '@': // annotation_value
- v = readAnnotationValues(v + 2, buf, true,
- av.visitAnnotation(name, readUTF8(v, buf)));
- break;
+ return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer);
case '[': // array_value
- int size = readUnsignedShort(v);
- v += 2;
- if (size == 0) {
- return readAnnotationValues(v - 2, buf, false,
- av.visitArray(name));
- }
- switch (this.b[v++] & 0xFF) {
- case 'B':
- byte[] bv = new byte[size];
- for (i = 0; i < size; i++) {
- bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, bv);
- --v;
- break;
- case 'Z':
- boolean[] zv = new boolean[size];
- for (i = 0; i < size; i++) {
- zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
- v += 3;
- }
- av.visit(name, zv);
- --v;
- break;
- case 'S':
- short[] sv = new short[size];
- for (i = 0; i < size; i++) {
- sv[i] = (short) readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, sv);
- --v;
- break;
- case 'C':
- char[] cv = new char[size];
- for (i = 0; i < size; i++) {
- cv[i] = (char) readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, cv);
- --v;
- break;
- case 'I':
- int[] iv = new int[size];
- for (i = 0; i < size; i++) {
- iv[i] = readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, iv);
- --v;
- break;
- case 'J':
- long[] lv = new long[size];
- for (i = 0; i < size; i++) {
- lv[i] = readLong(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, lv);
- --v;
- break;
- case 'F':
- float[] fv = new float[size];
- for (i = 0; i < size; i++) {
- fv[i] = Float
- .intBitsToFloat(readInt(items[readUnsignedShort(v)]));
- v += 3;
- }
- av.visit(name, fv);
- --v;
- break;
- case 'D':
- double[] dv = new double[size];
- for (i = 0; i < size; i++) {
- dv[i] = Double
- .longBitsToDouble(readLong(items[readUnsignedShort(v)]));
- v += 3;
- }
- av.visit(name, dv);
- --v;
- break;
- default:
- v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
- }
- }
- return v;
- }
-
- /**
- * Computes the implicit frame of the method currently being parsed (as
- * defined in the given {@link Context}) and stores it in the given context.
- *
- * @param frame
- * information about the class being parsed.
- */
- private void getImplicitFrame(final Context frame) {
- String desc = frame.desc;
- Object[] locals = frame.local;
- int local = 0;
- if ((frame.access & Opcodes.ACC_STATIC) == 0) {
- if ("".equals(frame.name)) {
- locals[local++] = Opcodes.UNINITIALIZED_THIS;
- } else {
- locals[local++] = readClass(header + 2, frame.buffer);
- }
+ return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer);
+ default:
+ return currentOffset + 3;
+ }
+ }
+ switch (b[currentOffset++] & 0xFF) {
+ case 'B': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ currentOffset += 2;
+ break;
+ case 'C': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ currentOffset += 2;
+ break;
+ case 'D': // const_value_index, CONSTANT_Double
+ case 'F': // const_value_index, CONSTANT_Float
+ case 'I': // const_value_index, CONSTANT_Integer
+ case 'J': // const_value_index, CONSTANT_Long
+ annotationVisitor.visit(
+ elementName, readConst(readUnsignedShort(currentOffset), charBuffer));
+ currentOffset += 2;
+ break;
+ case 'S': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ currentOffset += 2;
+ break;
+
+ case 'Z': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName,
+ readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0
+ ? Boolean.FALSE
+ : Boolean.TRUE);
+ currentOffset += 2;
+ break;
+ case 's': // const_value_index, CONSTANT_Utf8
+ annotationVisitor.visit(elementName, readUTF8(currentOffset, charBuffer));
+ currentOffset += 2;
+ break;
+ case 'e': // enum_const_value
+ annotationVisitor.visitEnum(
+ elementName,
+ readUTF8(currentOffset, charBuffer),
+ readUTF8(currentOffset + 2, charBuffer));
+ currentOffset += 4;
+ break;
+ case 'c': // class_info
+ annotationVisitor.visit(elementName, Type.getType(readUTF8(currentOffset, charBuffer)));
+ currentOffset += 2;
+ break;
+ case '@': // annotation_value
+ currentOffset =
+ readElementValues(
+ annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)),
+ currentOffset + 2,
+ true,
+ charBuffer);
+ break;
+ case '[': // array_value
+ int numValues = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ if (numValues == 0) {
+ return readElementValues(
+ annotationVisitor.visitArray(elementName),
+ currentOffset - 2,
+ /* named = */ false,
+ charBuffer);
}
- int i = 1;
- loop: while (true) {
- int j = i;
- switch (desc.charAt(i++)) {
- case 'Z':
- case 'C':
- case 'B':
- case 'S':
- case 'I':
- locals[local++] = Opcodes.INTEGER;
- break;
- case 'F':
- locals[local++] = Opcodes.FLOAT;
- break;
- case 'J':
- locals[local++] = Opcodes.LONG;
- break;
- case 'D':
- locals[local++] = Opcodes.DOUBLE;
- break;
- case '[':
- while (desc.charAt(i) == '[') {
- ++i;
- }
- if (desc.charAt(i) == 'L') {
- ++i;
- while (desc.charAt(i) != ';') {
- ++i;
- }
- }
- locals[local++] = desc.substring(j, ++i);
- break;
- case 'L':
- while (desc.charAt(i) != ';') {
- ++i;
- }
- locals[local++] = desc.substring(j + 1, i++);
- break;
- default:
- break loop;
+ switch (b[currentOffset] & 0xFF) {
+ case 'B':
+ byte[] byteValues = new byte[numValues];
+ for (int i = 0; i < numValues; i++) {
+ byteValues[i] = (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
}
- }
- frame.localCount = local;
- }
-
- /**
- * Reads a stack map frame and stores the result in the given
- * {@link Context} object.
- *
- * @param stackMap
- * the start offset of a stack map frame in the class file.
- * @param zip
- * if the stack map frame at stackMap is compressed or not.
- * @param unzip
- * if the stack map frame must be uncompressed.
- * @param labels
- * the labels of the method currently being parsed, indexed by
- * their offset. A new label for the parsed stack map frame is
- * stored in this array if it does not already exist.
- * @param frame
- * where the parsed stack map frame must be stored.
- * @return the offset of the first byte following the parsed frame.
- */
- private int readFrame(int stackMap, boolean zip, boolean unzip,
- Label[] labels, Context frame) {
- char[] c = frame.buffer;
- int tag;
- int delta;
- if (zip) {
- tag = b[stackMap++] & 0xFF;
- } else {
- tag = MethodWriter.FULL_FRAME;
- frame.offset = -1;
- }
- frame.localDiff = 0;
- if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
- delta = tag;
- frame.mode = Opcodes.F_SAME;
- frame.stackCount = 0;
- } else if (tag < MethodWriter.RESERVED) {
- delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
- stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
- frame.mode = Opcodes.F_SAME1;
- frame.stackCount = 1;
- } else {
- delta = readUnsignedShort(stackMap);
- stackMap += 2;
- if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
- stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
- frame.mode = Opcodes.F_SAME1;
- frame.stackCount = 1;
- } else if (tag >= MethodWriter.CHOP_FRAME
- && tag < MethodWriter.SAME_FRAME_EXTENDED) {
- frame.mode = Opcodes.F_CHOP;
- frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag;
- frame.localCount -= frame.localDiff;
- frame.stackCount = 0;
- } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
- frame.mode = Opcodes.F_SAME;
- frame.stackCount = 0;
- } else if (tag < MethodWriter.FULL_FRAME) {
- int local = unzip ? frame.localCount : 0;
- for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) {
- stackMap = readFrameType(frame.local, local++, stackMap, c,
- labels);
- }
- frame.mode = Opcodes.F_APPEND;
- frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED;
- frame.localCount += frame.localDiff;
- frame.stackCount = 0;
- } else { // if (tag == FULL_FRAME) {
- frame.mode = Opcodes.F_FULL;
- int n = readUnsignedShort(stackMap);
- stackMap += 2;
- frame.localDiff = n;
- frame.localCount = n;
- for (int local = 0; n > 0; n--) {
- stackMap = readFrameType(frame.local, local++, stackMap, c,
- labels);
- }
- n = readUnsignedShort(stackMap);
- stackMap += 2;
- frame.stackCount = n;
- for (int stack = 0; n > 0; n--) {
- stackMap = readFrameType(frame.stack, stack++, stackMap, c,
- labels);
- }
+ annotationVisitor.visit(elementName, byteValues);
+ break;
+ case 'Z':
+ boolean[] booleanValues = new boolean[numValues];
+ for (int i = 0; i < numValues; i++) {
+ booleanValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]) != 0;
+ currentOffset += 3;
}
- }
- frame.offset += delta + 1;
- readLabel(frame.offset, labels);
- return stackMap;
- }
-
- /**
- * Reads a stack map frame type and stores it at the given index in the
- * given array.
- *
- * @param frame
- * the array where the parsed type must be stored.
- * @param index
- * the index in 'frame' where the parsed type must be stored.
- * @param v
- * the start offset of the stack map frame type to read.
- * @param buf
- * a buffer to read strings.
- * @param labels
- * the labels of the method currently being parsed, indexed by
- * their offset. If the parsed type is an Uninitialized type, a
- * new label for the corresponding NEW instruction is stored in
- * this array if it does not already exist.
- * @return the offset of the first byte after the parsed type.
- */
- private int readFrameType(final Object[] frame, final int index, int v,
- final char[] buf, final Label[] labels) {
- int type = b[v++] & 0xFF;
- switch (type) {
- case 0:
- frame[index] = Opcodes.TOP;
+ annotationVisitor.visit(elementName, booleanValues);
break;
- case 1:
- frame[index] = Opcodes.INTEGER;
+ case 'S':
+ short[] shortValues = new short[numValues];
+ for (int i = 0; i < numValues; i++) {
+ shortValues[i] = (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, shortValues);
break;
- case 2:
- frame[index] = Opcodes.FLOAT;
+ case 'C':
+ char[] charValues = new char[numValues];
+ for (int i = 0; i < numValues; i++) {
+ charValues[i] = (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, charValues);
break;
- case 3:
- frame[index] = Opcodes.DOUBLE;
+ case 'I':
+ int[] intValues = new int[numValues];
+ for (int i = 0; i < numValues; i++) {
+ intValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, intValues);
break;
- case 4:
- frame[index] = Opcodes.LONG;
+ case 'J':
+ long[] longValues = new long[numValues];
+ for (int i = 0; i < numValues; i++) {
+ longValues[i] = readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, longValues);
break;
- case 5:
- frame[index] = Opcodes.NULL;
+ case 'F':
+ float[] floatValues = new float[numValues];
+ for (int i = 0; i < numValues; i++) {
+ floatValues[i] =
+ Float.intBitsToFloat(
+ readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, floatValues);
break;
- case 6:
- frame[index] = Opcodes.UNINITIALIZED_THIS;
+ case 'D':
+ double[] doubleValues = new double[numValues];
+ for (int i = 0; i < numValues; i++) {
+ doubleValues[i] =
+ Double.longBitsToDouble(
+ readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, doubleValues);
break;
- case 7: // Object
- frame[index] = readClass(v, buf);
- v += 2;
+ default:
+ currentOffset =
+ readElementValues(
+ annotationVisitor.visitArray(elementName),
+ currentOffset - 2,
+ /* named = */ false,
+ charBuffer);
break;
- default: // Uninitialized
- frame[index] = readLabel(readUnsignedShort(v), labels);
- v += 2;
- }
- return v;
- }
-
- /**
- * Returns the label corresponding to the given offset. The default
- * implementation of this method creates a label for the given offset if it
- * has not been already created.
- *
- * @param offset
- * a bytecode offset in a method.
- * @param labels
- * the already created labels, indexed by their offset. If a
- * label already exists for offset this method must not create a
- * new one. Otherwise it must store the new label in this array.
- * @return a non null Label, which must be equal to labels[offset].
- */
- protected Label readLabel(int offset, Label[] labels) {
- if (labels[offset] == null) {
- labels[offset] = new Label();
- }
- return labels[offset];
- }
-
- /**
- * Returns the start index of the attribute_info structure of this class.
- *
- * @return the start index of the attribute_info structure of this class.
- */
- private int getAttributes() {
- // skips the header
- int u = header + 8 + readUnsignedShort(header + 6) * 2;
- // skips fields and methods
- for (int i = readUnsignedShort(u); i > 0; --i) {
- for (int j = readUnsignedShort(u + 8); j > 0; --j) {
- u += 6 + readInt(u + 12);
- }
- u += 8;
}
- u += 2;
- for (int i = readUnsignedShort(u); i > 0; --i) {
- for (int j = readUnsignedShort(u + 8); j > 0; --j) {
- u += 6 + readInt(u + 12);
- }
- u += 8;
- }
- // the attribute_info structure starts just after the methods
- return u + 2;
- }
-
- /**
- * Reads an attribute in {@link #b b}.
- *
- * @param attrs
- * prototypes of the attributes that must be parsed during the
- * visit of the class. Any attribute whose type is not equal to
- * the type of one the prototypes is ignored (i.e. an empty
- * {@link Attribute} instance is returned).
- * @param type
- * the type of the attribute.
- * @param off
- * index of the first byte of the attribute's content in
- * {@link #b b}. The 6 attribute header bytes, containing the
- * type and the length of the attribute, are not taken into
- * account here (they have already been read).
- * @param len
- * the length of the attribute's content.
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param codeOff
- * index of the first byte of code's attribute content in
- * {@link #b b}, or -1 if the attribute to be read is not a code
- * attribute. The 6 attribute header bytes, containing the type
- * and the length of the attribute, are not taken into account
- * here.
- * @param labels
- * the labels of the method's code, or null if the
- * attribute to be read is not a code attribute.
- * @return the attribute that has been read, or null to skip this
- * attribute.
- */
- private Attribute readAttribute(final Attribute[] attrs, final String type,
- final int off, final int len, final char[] buf, final int codeOff,
- final Label[] labels) {
- for (int i = 0; i < attrs.length; ++i) {
- if (attrs[i].type.equals(type)) {
- return attrs[i].read(this, off, len, buf, codeOff, labels);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ return currentOffset;
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse stack map frames
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Computes the implicit frame of the method currently being parsed (as defined in the given
+ * {@link Context}) and stores it in the given context.
+ *
+ * @param context information about the class being parsed.
+ */
+ private void computeImplicitFrame(final Context context) {
+ String methodDescriptor = context.currentMethodDescriptor;
+ Object[] locals = context.currentFrameLocalTypes;
+ int nLocal = 0;
+ if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) {
+ if ("".equals(context.currentMethodName)) {
+ locals[nLocal++] = Opcodes.UNINITIALIZED_THIS;
+ } else {
+ locals[nLocal++] = readClass(header + 2, context.charBuffer);
+ }
+ }
+ // Parse the method descriptor, one argument type descriptor at each iteration. Start by
+ // skipping the first method descriptor character, which is always '('.
+ int currentMethodDescritorOffset = 1;
+ while (true) {
+ int currentArgumentDescriptorStartOffset = currentMethodDescritorOffset;
+ switch (methodDescriptor.charAt(currentMethodDescritorOffset++)) {
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ locals[nLocal++] = Opcodes.INTEGER;
+ break;
+ case 'F':
+ locals[nLocal++] = Opcodes.FLOAT;
+ break;
+ case 'J':
+ locals[nLocal++] = Opcodes.LONG;
+ break;
+ case 'D':
+ locals[nLocal++] = Opcodes.DOUBLE;
+ break;
+ case '[':
+ while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') {
+ ++currentMethodDescritorOffset;
+ }
+ if (methodDescriptor.charAt(currentMethodDescritorOffset) == 'L') {
+ ++currentMethodDescritorOffset;
+ while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') {
+ ++currentMethodDescritorOffset;
}
+ }
+ locals[nLocal++] =
+ methodDescriptor.substring(
+ currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset);
+ break;
+ case 'L':
+ while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') {
+ ++currentMethodDescritorOffset;
+ }
+ locals[nLocal++] =
+ methodDescriptor.substring(
+ currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++);
+ break;
+ default:
+ context.currentFrameLocalCount = nLocal;
+ return;
+ }
+ }
+ }
+
+ /**
+ * Reads a JVMS 'stack_map_frame' structure and stores the result in the given {@link Context}
+ * object. This method can also be used to read a full_frame structure, excluding its frame_type
+ * field (this is used to parse the legacy StackMap attributes).
+ *
+ * @param stackMapFrameOffset the start offset in {@link #b} of the stack_map_frame_value
+ * structure to be read, or the start offset of a full_frame structure (excluding its
+ * frame_type field).
+ * @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame'
+ * structure without its frame_type field.
+ * @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}.
+ * @param context where the parsed stack map frame must be stored.
+ * @return the end offset of the JVMS 'stack_map_frame' or 'full_frame' structure.
+ */
+ private int readStackMapFrame(
+ final int stackMapFrameOffset,
+ final boolean compressed,
+ final boolean expand,
+ final Context context) {
+ int currentOffset = stackMapFrameOffset;
+ final char[] charBuffer = context.charBuffer;
+ final Label[] labels = context.currentMethodLabels;
+ int frameType;
+ if (compressed) {
+ // Read the frame_type field.
+ frameType = b[currentOffset++] & 0xFF;
+ } else {
+ frameType = Frame.FULL_FRAME;
+ context.currentFrameOffset = -1;
+ }
+ int offsetDelta;
+ context.currentFrameLocalCountDelta = 0;
+ if (frameType < Frame.SAME_LOCALS_1_STACK_ITEM_FRAME) {
+ offsetDelta = frameType;
+ context.currentFrameType = Opcodes.F_SAME;
+ context.currentFrameStackCount = 0;
+ } else if (frameType < Frame.RESERVED) {
+ offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME;
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
+ context.currentFrameType = Opcodes.F_SAME1;
+ context.currentFrameStackCount = 1;
+ } else if (frameType >= Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+ offsetDelta = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
+ context.currentFrameType = Opcodes.F_SAME1;
+ context.currentFrameStackCount = 1;
+ } else if (frameType >= Frame.CHOP_FRAME && frameType < Frame.SAME_FRAME_EXTENDED) {
+ context.currentFrameType = Opcodes.F_CHOP;
+ context.currentFrameLocalCountDelta = Frame.SAME_FRAME_EXTENDED - frameType;
+ context.currentFrameLocalCount -= context.currentFrameLocalCountDelta;
+ context.currentFrameStackCount = 0;
+ } else if (frameType == Frame.SAME_FRAME_EXTENDED) {
+ context.currentFrameType = Opcodes.F_SAME;
+ context.currentFrameStackCount = 0;
+ } else if (frameType < Frame.FULL_FRAME) {
+ int local = expand ? context.currentFrameLocalCount : 0;
+ for (int k = frameType - Frame.SAME_FRAME_EXTENDED; k > 0; k--) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels);
}
- return new Attribute(type).read(this, off, len, null, -1, null);
- }
-
- // ------------------------------------------------------------------------
- // Utility methods: low level parsing
- // ------------------------------------------------------------------------
-
- /**
- * Returns the number of constant pool items in {@link #b b}.
- *
- * @return the number of constant pool items in {@link #b b}.
- */
- public int getItemCount() {
- return items.length;
- }
-
- /**
- * Returns the start index of the constant pool item in {@link #b b}, plus
- * one. This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param item
- * the index a constant pool item.
- * @return the start index of the constant pool item in {@link #b b}, plus
- * one.
- */
- public int getItem(final int item) {
- return items[item];
- }
-
- /**
- * Returns the maximum length of the strings contained in the constant pool
- * of the class.
- *
- * @return the maximum length of the strings contained in the constant pool
- * of the class.
- */
- public int getMaxStringLength() {
- return maxStringLength;
- }
-
- /**
- * Reads a byte value in {@link #b b}. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public int readByte(final int index) {
- return b[index] & 0xFF;
- }
-
- /**
- * Reads an unsigned short value in {@link #b b}. This method is intended
- * for {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public int readUnsignedShort(final int index) {
- byte[] b = this.b;
- return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
- }
-
- /**
- * Reads a signed short value in {@link #b b}. This method is intended
- * for {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public short readShort(final int index) {
- byte[] b = this.b;
- return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
- }
-
- /**
- * Reads a signed int value in {@link #b b}. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public int readInt(final int index) {
- byte[] b = this.b;
- return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
- | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
- }
-
- /**
- * Reads a signed long value in {@link #b b}. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public long readLong(final int index) {
- long l1 = readInt(index);
- long l0 = readInt(index + 4) & 0xFFFFFFFFL;
- return (l1 << 32) | l0;
- }
-
- /**
- * Reads an UTF8 string constant pool item in {@link #b b}. This method
- * is intended for {@link Attribute} sub classes, and is normally not needed
- * by class generators or adapters.
- *
- * @param index
- * the start index of an unsigned short value in {@link #b b},
- * whose value is the index of an UTF8 constant pool item.
- * @param buf
- * buffer to be used to read the item. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the String corresponding to the specified UTF8 item.
- */
- public String readUTF8(int index, final char[] buf) {
- int item = readUnsignedShort(index);
- if (index == 0 || item == 0) {
- return null;
- }
- String s = strings[item];
- if (s != null) {
- return s;
- }
- index = items[item];
- return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
- }
-
- /**
- * Reads UTF8 string in {@link #b b}.
- *
- * @param index
- * start offset of the UTF8 string to be read.
- * @param utfLen
- * length of the UTF8 string to be read.
- * @param buf
- * buffer to be used to read the string. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the String corresponding to the specified UTF8 string.
- */
- private String readUTF(int index, final int utfLen, final char[] buf) {
- int endIndex = index + utfLen;
- byte[] b = this.b;
- int strLen = 0;
- int c;
- int st = 0;
- char cc = 0;
- while (index < endIndex) {
- c = b[index++];
- switch (st) {
- case 0:
- c = c & 0xFF;
- if (c < 0x80) { // 0xxxxxxx
- buf[strLen++] = (char) c;
- } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
- cc = (char) (c & 0x1F);
- st = 1;
- } else { // 1110 xxxx 10xx xxxx 10xx xxxx
- cc = (char) (c & 0x0F);
- st = 2;
- }
- break;
-
- case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
- buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
- st = 0;
- break;
-
- case 2: // byte 2 of 3-byte char
- cc = (char) ((cc << 6) | (c & 0x3F));
- st = 1;
- break;
- }
+ context.currentFrameType = Opcodes.F_APPEND;
+ context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED;
+ context.currentFrameLocalCount += context.currentFrameLocalCountDelta;
+ context.currentFrameStackCount = 0;
+ } else {
+ final int numberOfLocals = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ context.currentFrameType = Opcodes.F_FULL;
+ context.currentFrameLocalCountDelta = numberOfLocals;
+ context.currentFrameLocalCount = numberOfLocals;
+ for (int local = 0; local < numberOfLocals; ++local) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels);
}
- return new String(buf, 0, strLen);
- }
-
- /**
- * Reads a class constant pool item in {@link #b b}. This method is
- * intended for {@link Attribute} sub classes, and is normally not needed by
- * class generators or adapters.
- *
- * @param index
- * the start index of an unsigned short value in {@link #b b},
- * whose value is the index of a class constant pool item.
- * @param buf
- * buffer to be used to read the item. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the String corresponding to the specified class item.
- */
- public String readClass(final int index, final char[] buf) {
- // computes the start index of the CONSTANT_Class item in b
- // and reads the CONSTANT_Utf8 item designated by
- // the first two bytes of this CONSTANT_Class item
- return readUTF8(items[readUnsignedShort(index)], buf);
- }
-
- /**
- * Reads a numeric or string constant pool item in {@link #b b}. This
- * method is intended for {@link Attribute} sub classes, and is normally not
- * needed by class generators or adapters.
- *
- * @param item
- * the index of a constant pool item.
- * @param buf
- * buffer to be used to read the item. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double},
- * {@link String}, {@link Type} or {@link Handle} corresponding to
- * the given constant pool item.
- */
- public Object readConst(final int item, final char[] buf) {
- int index = items[item];
- switch (b[index - 1]) {
- case ClassWriter.INT:
- return new Integer(readInt(index));
- case ClassWriter.FLOAT:
- return new Float(Float.intBitsToFloat(readInt(index)));
- case ClassWriter.LONG:
- return new Long(readLong(index));
- case ClassWriter.DOUBLE:
- return new Double(Double.longBitsToDouble(readLong(index)));
- case ClassWriter.CLASS:
- return Type.getObjectType(readUTF8(index, buf));
- case ClassWriter.STR:
- return readUTF8(index, buf);
- case ClassWriter.MTYPE:
- return Type.getMethodType(readUTF8(index, buf));
- default: // case ClassWriter.HANDLE_BASE + [1..9]:
- int tag = readByte(index);
- int[] items = this.items;
- int cpIndex = items[readUnsignedShort(index + 1)];
- String owner = readClass(cpIndex, buf);
- cpIndex = items[readUnsignedShort(cpIndex + 2)];
- String name = readUTF8(cpIndex, buf);
- String desc = readUTF8(cpIndex + 2, buf);
- return new Handle(tag, owner, name, desc);
+ final int numberOfStackItems = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ context.currentFrameStackCount = numberOfStackItems;
+ for (int stack = 0; stack < numberOfStackItems; ++stack) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels);
}
+ }
+ } else {
+ throw new IllegalArgumentException();
+ }
+ context.currentFrameOffset += offsetDelta + 1;
+ createLabel(context.currentFrameOffset, labels);
+ return currentOffset;
+ }
+
+ /**
+ * Reads a JVMS 'verification_type_info' structure and stores it at the given index in the given
+ * array.
+ *
+ * @param verificationTypeInfoOffset the start offset of the 'verification_type_info' structure to
+ * read.
+ * @param frame the array where the parsed type must be stored.
+ * @param index the index in 'frame' where the parsed type must be stored.
+ * @param charBuffer the buffer used to read strings in the constant pool.
+ * @param labels the labels of the method currently being parsed, indexed by their offset. If the
+ * parsed type is an ITEM_Uninitialized, a new label for the corresponding NEW instruction is
+ * stored in this array if it does not already exist.
+ * @return the end offset of the JVMS 'verification_type_info' structure.
+ */
+ private int readVerificationTypeInfo(
+ final int verificationTypeInfoOffset,
+ final Object[] frame,
+ final int index,
+ final char[] charBuffer,
+ final Label[] labels) {
+ int currentOffset = verificationTypeInfoOffset;
+ int tag = b[currentOffset++] & 0xFF;
+ switch (tag) {
+ case Frame.ITEM_TOP:
+ frame[index] = Opcodes.TOP;
+ break;
+ case Frame.ITEM_INTEGER:
+ frame[index] = Opcodes.INTEGER;
+ break;
+ case Frame.ITEM_FLOAT:
+ frame[index] = Opcodes.FLOAT;
+ break;
+ case Frame.ITEM_DOUBLE:
+ frame[index] = Opcodes.DOUBLE;
+ break;
+ case Frame.ITEM_LONG:
+ frame[index] = Opcodes.LONG;
+ break;
+ case Frame.ITEM_NULL:
+ frame[index] = Opcodes.NULL;
+ break;
+ case Frame.ITEM_UNINITIALIZED_THIS:
+ frame[index] = Opcodes.UNINITIALIZED_THIS;
+ break;
+ case Frame.ITEM_OBJECT:
+ frame[index] = readClass(currentOffset, charBuffer);
+ currentOffset += 2;
+ break;
+ case Frame.ITEM_UNINITIALIZED:
+ frame[index] = createLabel(readUnsignedShort(currentOffset), labels);
+ currentOffset += 2;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ return currentOffset;
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse attributes
+ // ----------------------------------------------------------------------------------------------
+
+ /** @return the offset in {@link #b} of the first ClassFile's 'attributes' array field entry. */
+ final int getFirstAttributeOffset() {
+ // Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes
+ // each), as well as the interfaces array field (2 bytes per interface).
+ int currentOffset = header + 8 + readUnsignedShort(header + 6) * 2;
+
+ // Read the fields_count field.
+ int fieldsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ // Skip the 'fields' array field.
+ while (fieldsCount-- > 0) {
+ // Invariant: currentOffset is the offset of a field_info structure.
+ // Skip the access_flags, name_index and descriptor_index fields (2 bytes each), and read the
+ // attributes_count field.
+ int attributesCount = readUnsignedShort(currentOffset + 6);
+ currentOffset += 8;
+ // Skip the 'attributes' array field.
+ while (attributesCount-- > 0) {
+ // Invariant: currentOffset is the offset of an attribute_info structure.
+ // Read the attribute_length field (2 bytes after the start of the attribute_info) and skip
+ // this many bytes, plus 6 for the attribute_name_index and attribute_length fields
+ // (yielding the total size of the attribute_info structure).
+ currentOffset += 6 + readInt(currentOffset + 2);
+ }
+ }
+
+ // Skip the methods_count and 'methods' fields, using the same method as above.
+ int methodsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (methodsCount-- > 0) {
+ int attributesCount = readUnsignedShort(currentOffset + 6);
+ currentOffset += 8;
+ while (attributesCount-- > 0) {
+ currentOffset += 6 + readInt(currentOffset + 2);
+ }
+ }
+
+ // Skip the ClassFile's attributes_count field.
+ return currentOffset + 2;
+ }
+
+ /**
+ * Reads a non standard JVMS 'attribute' structure in {@link #b}.
+ *
+ * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
+ * the class. Any attribute whose type is not equal to the type of one the prototypes will not
+ * be parsed: its byte array value will be passed unchanged to the ClassWriter.
+ * @param type the type of the attribute.
+ * @param offset the start offset of the JVMS 'attribute' structure in {@link #b}. The 6 attribute
+ * header bytes (attribute_name_index and attribute_length) are not taken into account here.
+ * @param length the length of the attribute's content (excluding the 6 attribute header bytes).
+ * @param charBuffer the buffer to be used to read strings in the constant pool.
+ * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link #b}, or
+ * -1 if the attribute to be read is not a code attribute. The 6 attribute header bytes
+ * (attribute_name_index and attribute_length) are not taken into account here.
+ * @param labels the labels of the method's code, or null if the attribute to be read is
+ * not a code attribute.
+ * @return the attribute that has been read.
+ */
+ private Attribute readAttribute(
+ final Attribute[] attributePrototypes,
+ final String type,
+ final int offset,
+ final int length,
+ final char[] charBuffer,
+ final int codeAttributeOffset,
+ final Label[] labels) {
+ for (int i = 0; i < attributePrototypes.length; ++i) {
+ if (attributePrototypes[i].type.equals(type)) {
+ return attributePrototypes[i].read(
+ this, offset, length, charBuffer, codeAttributeOffset, labels);
+ }
+ }
+ return new Attribute(type).read(this, offset, length, null, -1, null);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: low level parsing
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the number of entries in the class's constant pool table.
+ *
+ * @return the number of entries in the class's constant pool table.
+ */
+ public int getItemCount() {
+ return cpInfoOffsets.length;
+ }
+
+ /**
+ * Returns the start offset in {@link #b} of a JVMS 'cp_info' structure (i.e. a constant pool
+ * entry), plus one. This method is intended for {@link Attribute} sub classes, and is normally
+ * not needed by class generators or adapters.
+ *
+ * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool
+ * table.
+ * @return the start offset in {@link #b} of the corresponding JVMS 'cp_info' structure, plus one.
+ */
+ public int getItem(final int constantPoolEntryIndex) {
+ return cpInfoOffsets[constantPoolEntryIndex];
+ }
+
+ /**
+ * Returns a conservative estimate of the maximum length of the strings contained in the class's
+ * constant pool table.
+ *
+ * @return a conservative estimate of the maximum length of the strings contained in the class's
+ * constant pool table.
+ */
+ public int getMaxStringLength() {
+ return maxStringLength;
+ }
+
+ /**
+ * Reads a byte value in {@link #b}. This method is intended for {@link Attribute} sub classes,
+ * and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in {@link #b}.
+ * @return the read value.
+ */
+ public int readByte(final int offset) {
+ return b[offset] & 0xFF;
+ }
+
+ /**
+ * Reads an unsigned short value in {@link #b}. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start index of the value to be read in {@link #b}.
+ * @return the read value.
+ */
+ public int readUnsignedShort(final int offset) {
+ byte[] classFileBuffer = b;
+ return ((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF);
+ }
+
+ /**
+ * Reads a signed short value in {@link #b}. This method is intended for {@link Attribute} sub
+ * classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in {@link #b}.
+ * @return the read value.
+ */
+ public short readShort(final int offset) {
+ byte[] classFileBuffer = b;
+ return (short) (((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF));
+ }
+
+ /**
+ * Reads a signed int value in {@link #b}. This method is intended for {@link Attribute} sub
+ * classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in {@link #b}.
+ * @return the read value.
+ */
+ public int readInt(final int offset) {
+ byte[] classFileBuffer = b;
+ return ((classFileBuffer[offset] & 0xFF) << 24)
+ | ((classFileBuffer[offset + 1] & 0xFF) << 16)
+ | ((classFileBuffer[offset + 2] & 0xFF) << 8)
+ | (classFileBuffer[offset + 3] & 0xFF);
+ }
+
+ /**
+ * Reads a signed long value in {@link #b}. This method is intended for {@link Attribute} sub
+ * classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in {@link #b}.
+ * @return the read value.
+ */
+ public long readLong(final int offset) {
+ long l1 = readInt(offset);
+ long l0 = readInt(offset + 4) & 0xFFFFFFFFL;
+ return (l1 << 32) | l0;
+ }
+
+ /**
+ * Reads a CONSTANT_Utf8 constant pool entry in {@link #b}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
+ * index of a CONSTANT_Utf8 entry in the class's constant pool table.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Utf8 entry.
+ */
+ public String readUTF8(final int offset, final char[] charBuffer) {
+ int constantPoolEntryIndex = readUnsignedShort(offset);
+ if (offset == 0 || constantPoolEntryIndex == 0) {
+ return null;
+ }
+ return readUTF(constantPoolEntryIndex, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Utf8 constant pool entry in {@link #b}.
+ *
+ * @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool
+ * table.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Utf8 entry.
+ */
+ final String readUTF(final int constantPoolEntryIndex, final char[] charBuffer) {
+ String value = (String) cpInfoValues[constantPoolEntryIndex];
+ if (value != null) {
+ return value;
+ }
+ int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
+ value = readUTF(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer);
+ cpInfoValues[constantPoolEntryIndex] = value;
+ return value;
+ }
+
+ /**
+ * Reads an UTF8 string in {@link #b}.
+ *
+ * @param utfOffset the start offset of the UTF8 string to be read.
+ * @param utfLength the length of the UTF8 string to be read.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified UTF8 string.
+ */
+ private String readUTF(final int utfOffset, final int utfLength, final char[] charBuffer) {
+ int currentOffset = utfOffset;
+ int endOffset = currentOffset + utfLength;
+ int strLength = 0;
+ byte[] classFileBuffer = b;
+ while (currentOffset < endOffset) {
+ int currentByte = classFileBuffer[currentOffset++];
+ if ((currentByte & 0x80) == 0) {
+ charBuffer[strLength++] = (char) (currentByte & 0x7F);
+ } else if ((currentByte & 0xE0) == 0xC0) {
+ charBuffer[strLength++] =
+ (char) (((currentByte & 0x1F) << 6) + (classFileBuffer[currentOffset++] & 0x3F));
+ } else {
+ charBuffer[strLength++] =
+ (char)
+ (((currentByte & 0xF) << 12)
+ + ((classFileBuffer[currentOffset++] & 0x3F) << 6)
+ + (classFileBuffer[currentOffset++] & 0x3F));
+ }
+ }
+ return new String(charBuffer, 0, strLength);
+ }
+
+ /**
+ * Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or
+ * CONSTANT_Package constant pool entry in {@link #b}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
+ * index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or
+ * CONSTANT_Package entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified constant pool entry.
+ */
+ private String readStringish(final int offset, final char[] charBuffer) {
+ // Get the start offset of the cp_info structure (plus one), and read the CONSTANT_Utf8 entry
+ // designated by the first two bytes of this cp_info.
+ return readUTF8(cpInfoOffsets[readUnsignedShort(offset)], charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Class constant pool entry in {@link #b}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
+ * index of a CONSTANT_Class entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Class entry.
+ */
+ public String readClass(final int offset, final char[] charBuffer) {
+ return readStringish(offset, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Module constant pool entry in {@link #b}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
+ * index of a CONSTANT_Module entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Module entry.
+ */
+ public String readModule(final int offset, final char[] charBuffer) {
+ return readStringish(offset, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Package constant pool entry in {@link #b}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
+ * index of a CONSTANT_Package entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Package entry.
+ */
+ public String readPackage(final int offset, final char[] charBuffer) {
+ return readStringish(offset, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Dynamic constant pool entry in {@link #b}.
+ *
+ * @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant
+ * pool table.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry.
+ */
+ private ConstantDynamic readConstantDynamic(
+ final int constantPoolEntryIndex, final char[] charBuffer) {
+ ConstantDynamic constantDynamic = (ConstantDynamic) cpInfoValues[constantPoolEntryIndex];
+ if (constantDynamic != null) {
+ return constantDynamic;
+ }
+ int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)];
+ Handle handle = (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ Object[] bootstrapMethodArguments = new Object[readUnsignedShort(bootstrapMethodOffset + 2)];
+ bootstrapMethodOffset += 4;
+ for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+ bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ bootstrapMethodOffset += 2;
+ }
+ constantDynamic = new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments);
+ cpInfoValues[constantPoolEntryIndex] = constantDynamic;
+ return constantDynamic;
+ }
+
+ /**
+ * Reads a numeric or string constant pool entry in {@link #b}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long,
+ * CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType,
+ * CONSTANT_MethodHandle or CONSTANT_Dynamic entry in the class's constant pool.
+ * @param charBuffer the buffer to be used to read strings. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String},
+ * {@link Type}, {@link Handle} or {@link ConstantDynamic} corresponding to the specified
+ * constant pool entry.
+ */
+ public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) {
+ int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
+ switch (b[cpInfoOffset - 1]) {
+ case Symbol.CONSTANT_INTEGER_TAG:
+ return readInt(cpInfoOffset);
+ case Symbol.CONSTANT_FLOAT_TAG:
+ return Float.intBitsToFloat(readInt(cpInfoOffset));
+ case Symbol.CONSTANT_LONG_TAG:
+ return readLong(cpInfoOffset);
+ case Symbol.CONSTANT_DOUBLE_TAG:
+ return Double.longBitsToDouble(readLong(cpInfoOffset));
+ case Symbol.CONSTANT_CLASS_TAG:
+ return Type.getObjectType(readUTF8(cpInfoOffset, charBuffer));
+ case Symbol.CONSTANT_STRING_TAG:
+ return readUTF8(cpInfoOffset, charBuffer);
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ return Type.getMethodType(readUTF8(cpInfoOffset, charBuffer));
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ int referenceKind = readByte(cpInfoOffset);
+ int referenceCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(referenceCpInfoOffset + 2)];
+ String owner = readClass(referenceCpInfoOffset, charBuffer);
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ boolean isInterface =
+ b[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
+ return new Handle(referenceKind, owner, name, descriptor, isInterface);
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ return readConstantDynamic(constantPoolEntryIndex, charBuffer);
+ default:
+ throw new IllegalArgumentException();
}
+ }
}
diff --git a/src/jvm/clojure/asm/ClassVisitor.java b/src/jvm/clojure/asm/ClassVisitor.java
index bc5919bf82..dc20a8107a 100644
--- a/src/jvm/clojure/asm/ClassVisitor.java
+++ b/src/jvm/clojure/asm/ClassVisitor.java
@@ -1,286 +1,341 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A visitor to visit a Java class. The methods of this class must be called in
- * the following order: visit [ visitSource ] [
- * visitOuterClass ] ( visitAnnotation |
- * visitAttribute )* ( visitInnerClass | visitField |
- * visitMethod )* visitEnd.
+ * A visitor to visit a Java class. The methods of this class must be called in the following order:
+ * visit [ visitSource ] [ visitModule ][ visitNestHost ][
+ * visitOuterClass ] ( visitAnnotation | visitTypeAnnotation |
+ * visitAttribute )* ( visitNestMember | visitInnerClass |
+ * visitField | visitMethod )* visitEnd.
*
* @author Eric Bruneton
*/
public abstract class ClassVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ protected final int api;
+
+ /** The class visitor to which this visitor must delegate method calls. May be null. */
+ protected ClassVisitor cv;
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ public ClassVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param classVisitor the class visitor to which this visitor must delegate method calls. May be
+ * null.
+ */
+ public ClassVisitor(final int api, final ClassVisitor classVisitor) {
+ if (api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM7_EXPERIMENTAL) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.cv = classVisitor;
+ }
+
+ /**
+ * Visits the header of the class.
+ *
+ * @param version the class version. The minor version is stored in the 16 most significant bits,
+ * and the major version in the 16 least significant bits.
+ * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if
+ * the class is deprecated.
+ * @param name the internal name of the class (see {@link Type#getInternalName()}).
+ * @param signature the signature of this class. May be null if the class is not a
+ * generic one, and does not extend or implement generic classes or interfaces.
+ * @param superName the internal of name of the super class (see {@link Type#getInternalName()}).
+ * For interfaces, the super class is {@link Object}. May be null, but only for the
+ * {@link Object} class.
+ * @param interfaces the internal names of the class's interfaces (see {@link
+ * Type#getInternalName()}). May be null.
+ */
+ public void visit(
+ final int version,
+ final int access,
+ final String name,
+ final String signature,
+ final String superName,
+ final String[] interfaces) {
+ if (cv != null) {
+ cv.visit(version, access, name, signature, superName, interfaces);
+ }
+ }
- /**
- * The class visitor to which this visitor must delegate method calls. May
- * be null.
- */
- protected ClassVisitor cv;
+ /**
+ * Visits the source of the class.
+ *
+ * @param source the name of the source file from which the class was compiled. May be
+ * null.
+ * @param debug additional debug information to compute the correspondence between source and
+ * compiled elements of the class. May be null.
+ */
+ public void visitSource(final String source, final String debug) {
+ if (cv != null) {
+ cv.visitSource(source, debug);
+ }
+ }
- /**
- * Constructs a new {@link ClassVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public ClassVisitor(final int api) {
- this(api, null);
+ /**
+ * Visit the module corresponding to the class.
+ *
+ * @param name the fully qualified name (using dots) of the module.
+ * @param access the module access flags, among {@code ACC_OPEN}, {@code ACC_SYNTHETIC} and {@code
+ * ACC_MANDATED}.
+ * @param version the module version, or null.
+ * @return a visitor to visit the module values, or null if this visitor is not
+ * interested in visiting this module.
+ */
+ public ModuleVisitor visitModule(final String name, final int access, final String version) {
+ if (api < Opcodes.ASM6) {
+ throw new UnsupportedOperationException();
+ }
+ if (cv != null) {
+ return cv.visitModule(name, access, version);
}
+ return null;
+ }
- /**
- * Constructs a new {@link ClassVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param cv
- * the class visitor to which this visitor must delegate method
- * calls. May be null.
- */
- public ClassVisitor(final int api, final ClassVisitor cv) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.cv = cv;
+ /**
+ * Experimental, use at your own risk. This method will be renamed when it becomes stable, this
+ * will break existing code using it. Visits the nest host class of the class. A nest is a set
+ * of classes of the same package that share access to their private members. One of these
+ * classes, called the host, lists the other members of the nest, which in turn should link to the
+ * host of their nest. This method must be called only once and only if the visited class is a
+ * non-host member of a nest. A class is implicitly its own nest, so it's invalid to call this
+ * method with the visited class name as argument.
+ *
+ * @param nestHost the internal name of the host class of the nest.
+ * @deprecated This API is experimental.
+ */
+ @Deprecated
+ public void visitNestHostExperimental(final String nestHost) {
+ if (api < Opcodes.ASM7_EXPERIMENTAL) {
+ throw new UnsupportedOperationException();
}
+ if (cv != null) {
+ cv.visitNestHostExperimental(nestHost);
+ }
+ }
- /**
- * Visits the header of the class.
- *
- * @param version
- * the class version.
- * @param access
- * the class's access flags (see {@link Opcodes}). This parameter
- * also indicates if the class is deprecated.
- * @param name
- * the internal name of the class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param signature
- * the signature of this class. May be null if the class
- * is not a generic one, and does not extend or implement generic
- * classes or interfaces.
- * @param superName
- * the internal of name of the super class (see
- * {@link Type#getInternalName() getInternalName}). For
- * interfaces, the super class is {@link Object}. May be
- * null, but only for the {@link Object} class.
- * @param interfaces
- * the internal names of the class's interfaces (see
- * {@link Type#getInternalName() getInternalName}). May be
- * null.
- */
- public void visit(int version, int access, String name, String signature,
- String superName, String[] interfaces) {
- if (cv != null) {
- cv.visit(version, access, name, signature, superName, interfaces);
- }
+ /**
+ * Visits the enclosing class of the class. This method must be called only if the class has an
+ * enclosing class.
+ *
+ * @param owner internal name of the enclosing class of the class.
+ * @param name the name of the method that contains the class, or null if the class is
+ * not enclosed in a method of its enclosing class.
+ * @param descriptor the descriptor of the method that contains the class, or null if the
+ * class is not enclosed in a method of its enclosing class.
+ */
+ public void visitOuterClass(final String owner, final String name, final String descriptor) {
+ if (cv != null) {
+ cv.visitOuterClass(owner, name, descriptor);
}
+ }
- /**
- * Visits the source of the class.
- *
- * @param source
- * the name of the source file from which the class was compiled.
- * May be null.
- * @param debug
- * additional debug information to compute the correspondance
- * between source and compiled elements of the class. May be
- * null.
- */
- public void visitSource(String source, String debug) {
- if (cv != null) {
- cv.visitSource(source, debug);
- }
+ /**
+ * Visits an annotation of the class.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (cv != null) {
+ return cv.visitAnnotation(descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits the enclosing class of the class. This method must be called only
- * if the class has an enclosing class.
- *
- * @param owner
- * internal name of the enclosing class of the class.
- * @param name
- * the name of the method that contains the class, or
- * null if the class is not enclosed in a method of its
- * enclosing class.
- * @param desc
- * the descriptor of the method that contains the class, or
- * null if the class is not enclosed in a method of its
- * enclosing class.
- */
- public void visitOuterClass(String owner, String name, String desc) {
- if (cv != null) {
- cv.visitOuterClass(owner, name, desc);
- }
+ /**
+ * Visits an annotation on a type in the class signature.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link
+ * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be null if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException();
+ }
+ if (cv != null) {
+ return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits an annotation of the class.
- *
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (cv != null) {
- return cv.visitAnnotation(desc, visible);
- }
- return null;
+ /**
+ * Visits a non standard attribute of the class.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (cv != null) {
+ cv.visitAttribute(attribute);
}
+ }
- /**
- * Visits a non standard attribute of the class.
- *
- * @param attr
- * an attribute.
- */
- public void visitAttribute(Attribute attr) {
- if (cv != null) {
- cv.visitAttribute(attr);
- }
+ /**
+ * Experimental, use at your own risk. This method will be renamed when it becomes stable, this
+ * will break existing code using it. Visits a member of the nest. A nest is a set of classes
+ * of the same package that share access to their private members. One of these classes, called
+ * the host, lists the other members of the nest, which in turn should link to the host of their
+ * nest. This method must be called only if the visited class is the host of a nest. A nest host
+ * is implicitly a member of its own nest, so it's invalid to call this method with the visited
+ * class name as argument.
+ *
+ * @param nestMember the internal name of a nest member.
+ * @deprecated This API is experimental.
+ */
+ @Deprecated
+ public void visitNestMemberExperimental(final String nestMember) {
+ if (api < Opcodes.ASM7_EXPERIMENTAL) {
+ throw new UnsupportedOperationException();
+ }
+ if (cv != null) {
+ cv.visitNestMemberExperimental(nestMember);
}
+ }
- /**
- * Visits information about an inner class. This inner class is not
- * necessarily a member of the class being visited.
- *
- * @param name
- * the internal name of an inner class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param outerName
- * the internal name of the class to which the inner class
- * belongs (see {@link Type#getInternalName() getInternalName}).
- * May be null for not member classes.
- * @param innerName
- * the (simple) name of the inner class inside its enclosing
- * class. May be null for anonymous inner classes.
- * @param access
- * the access flags of the inner class as originally declared in
- * the enclosing class.
- */
- public void visitInnerClass(String name, String outerName,
- String innerName, int access) {
- if (cv != null) {
- cv.visitInnerClass(name, outerName, innerName, access);
- }
+ /**
+ * Visits information about an inner class. This inner class is not necessarily a member of the
+ * class being visited.
+ *
+ * @param name the internal name of an inner class (see {@link Type#getInternalName()}).
+ * @param outerName the internal name of the class to which the inner class belongs (see {@link
+ * Type#getInternalName()}). May be null for not member classes.
+ * @param innerName the (simple) name of the inner class inside its enclosing class. May be
+ * null for anonymous inner classes.
+ * @param access the access flags of the inner class as originally declared in the enclosing
+ * class.
+ */
+ public void visitInnerClass(
+ final String name, final String outerName, final String innerName, final int access) {
+ if (cv != null) {
+ cv.visitInnerClass(name, outerName, innerName, access);
}
+ }
- /**
- * Visits a field of the class.
- *
- * @param access
- * the field's access flags (see {@link Opcodes}). This parameter
- * also indicates if the field is synthetic and/or deprecated.
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor (see {@link Type Type}).
- * @param signature
- * the field's signature. May be null if the field's
- * type does not use generic types.
- * @param value
- * the field's initial value. This parameter, which may be
- * null if the field does not have an initial value,
- * must be an {@link Integer}, a {@link Float}, a {@link Long}, a
- * {@link Double} or a {@link String} (for int,
- * float, long or String fields
- * respectively). This parameter is only used for static
- * fields. Its value is ignored for non static fields, which
- * must be initialized through bytecode instructions in
- * constructors or methods.
- * @return a visitor to visit field annotations and attributes, or
- * null if this class visitor is not interested in visiting
- * these annotations and attributes.
- */
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- if (cv != null) {
- return cv.visitField(access, name, desc, signature, value);
- }
- return null;
+ /**
+ * Visits a field of the class.
+ *
+ * @param access the field's access flags (see {@link Opcodes}). This parameter also indicates if
+ * the field is synthetic and/or deprecated.
+ * @param name the field's name.
+ * @param descriptor the field's descriptor (see {@link Type}).
+ * @param signature the field's signature. May be null if the field's type does not use
+ * generic types.
+ * @param value the field's initial value. This parameter, which may be null if the field
+ * does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link
+ * Long}, a {@link Double} or a {@link String} (for int, float,
+ * long or String fields respectively). This parameter is only used for
+ * static fields. Its value is ignored for non static fields, which must be initialized
+ * through bytecode instructions in constructors or methods.
+ * @return a visitor to visit field annotations and attributes, or null if this class
+ * visitor is not interested in visiting these annotations and attributes.
+ */
+ public FieldVisitor visitField(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Object value) {
+ if (cv != null) {
+ return cv.visitField(access, name, descriptor, signature, value);
}
+ return null;
+ }
- /**
- * Visits a method of the class. This method must return a new
- * {@link MethodVisitor} instance (or null) each time it is called,
- * i.e., it should not return a previously returned visitor.
- *
- * @param access
- * the method's access flags (see {@link Opcodes}). This
- * parameter also indicates if the method is synthetic and/or
- * deprecated.
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type Type}).
- * @param signature
- * the method's signature. May be null if the method
- * parameters, return type and exceptions do not use generic
- * types.
- * @param exceptions
- * the internal names of the method's exception classes (see
- * {@link Type#getInternalName() getInternalName}). May be
- * null.
- * @return an object to visit the byte code of the method, or null
- * if this class visitor is not interested in visiting the code of
- * this method.
- */
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- if (cv != null) {
- return cv.visitMethod(access, name, desc, signature, exceptions);
- }
- return null;
+ /**
+ * Visits a method of the class. This method must return a new {@link MethodVisitor}
+ * instance (or null) each time it is called, i.e., it should not return a previously
+ * returned visitor.
+ *
+ * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if
+ * the method is synthetic and/or deprecated.
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param signature the method's signature. May be null if the method parameters, return
+ * type and exceptions do not use generic types.
+ * @param exceptions the internal names of the method's exception classes (see {@link
+ * Type#getInternalName()}). May be null.
+ * @return an object to visit the byte code of the method, or null if this class visitor
+ * is not interested in visiting the code of this method.
+ */
+ public MethodVisitor visitMethod(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions) {
+ if (cv != null) {
+ return cv.visitMethod(access, name, descriptor, signature, exceptions);
}
+ return null;
+ }
- /**
- * Visits the end of the class. This method, which is the last one to be
- * called, is used to inform the visitor that all the fields and methods of
- * the class have been visited.
- */
- public void visitEnd() {
- if (cv != null) {
- cv.visitEnd();
- }
+ /**
+ * Visits the end of the class. This method, which is the last one to be called, is used to inform
+ * the visitor that all the fields and methods of the class have been visited.
+ */
+ public void visitEnd() {
+ if (cv != null) {
+ cv.visitEnd();
}
+ }
}
diff --git a/src/jvm/clojure/asm/ClassWriter.java b/src/jvm/clojure/asm/ClassWriter.java
index 3d78868dee..4727fe376c 100644
--- a/src/jvm/clojure/asm/ClassWriter.java
+++ b/src/jvm/clojure/asm/ClassWriter.java
@@ -1,1693 +1,971 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A {@link ClassVisitor} that generates classes in bytecode form. More
- * precisely this visitor generates a byte array conforming to the Java class
- * file format. It can be used alone, to generate a Java class "from scratch",
- * or with one or more {@link ClassReader ClassReader} and adapter class visitor
- * to generate a modified class from one or more existing Java classes.
+ * A {@link ClassVisitor} that generates a corresponding ClassFile structure, as defined in the Java
+ * Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from
+ * scratch", or with one or more {@link ClassReader} and adapter {@link ClassVisitor} to generate a
+ * modified class from one or more existing Java classes.
*
+ * @see JVMS 4
* @author Eric Bruneton
*/
public class ClassWriter extends ClassVisitor {
- /**
- * Flag to automatically compute the maximum stack size and the maximum
- * number of local variables of methods. If this flag is set, then the
- * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the
- * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod}
- * method will be ignored, and computed automatically from the signature and
- * the bytecode of each method.
- *
- * @see #ClassWriter(int)
- */
- public static final int COMPUTE_MAXS = 1;
-
- /**
- * Flag to automatically compute the stack map frames of methods from
- * scratch. If this flag is set, then the calls to the
- * {@link MethodVisitor#visitFrame} method are ignored, and the stack map
- * frames are recomputed from the methods bytecode. The arguments of the
- * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and
- * recomputed from the bytecode. In other words, computeFrames implies
- * computeMaxs.
- *
- * @see #ClassWriter(int)
- */
- public static final int COMPUTE_FRAMES = 2;
-
- /**
- * Pseudo access flag to distinguish between the synthetic attribute and the
- * synthetic access flag.
- */
- static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
-
- /**
- * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC.
- */
- static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE
- / Opcodes.ACC_SYNTHETIC;
-
- /**
- * The type of instructions without any argument.
- */
- static final int NOARG_INSN = 0;
-
- /**
- * The type of instructions with an signed byte argument.
- */
- static final int SBYTE_INSN = 1;
-
- /**
- * The type of instructions with an signed short argument.
- */
- static final int SHORT_INSN = 2;
-
- /**
- * The type of instructions with a local variable index argument.
- */
- static final int VAR_INSN = 3;
-
- /**
- * The type of instructions with an implicit local variable index argument.
- */
- static final int IMPLVAR_INSN = 4;
-
- /**
- * The type of instructions with a type descriptor argument.
- */
- static final int TYPE_INSN = 5;
-
- /**
- * The type of field and method invocations instructions.
- */
- static final int FIELDORMETH_INSN = 6;
-
- /**
- * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction.
- */
- static final int ITFMETH_INSN = 7;
-
- /**
- * The type of the INVOKEDYNAMIC instruction.
- */
- static final int INDYMETH_INSN = 8;
-
- /**
- * The type of instructions with a 2 bytes bytecode offset label.
- */
- static final int LABEL_INSN = 9;
-
- /**
- * The type of instructions with a 4 bytes bytecode offset label.
- */
- static final int LABELW_INSN = 10;
-
- /**
- * The type of the LDC instruction.
- */
- static final int LDC_INSN = 11;
-
- /**
- * The type of the LDC_W and LDC2_W instructions.
- */
- static final int LDCW_INSN = 12;
-
- /**
- * The type of the IINC instruction.
- */
- static final int IINC_INSN = 13;
-
- /**
- * The type of the TABLESWITCH instruction.
- */
- static final int TABL_INSN = 14;
-
- /**
- * The type of the LOOKUPSWITCH instruction.
- */
- static final int LOOK_INSN = 15;
-
- /**
- * The type of the MULTIANEWARRAY instruction.
- */
- static final int MANA_INSN = 16;
-
- /**
- * The type of the WIDE instruction.
- */
- static final int WIDE_INSN = 17;
-
- /**
- * The instruction types of all JVM opcodes.
- */
- static final byte[] TYPE;
-
- /**
- * The type of CONSTANT_Class constant pool items.
- */
- static final int CLASS = 7;
-
- /**
- * The type of CONSTANT_Fieldref constant pool items.
- */
- static final int FIELD = 9;
-
- /**
- * The type of CONSTANT_Methodref constant pool items.
- */
- static final int METH = 10;
-
- /**
- * The type of CONSTANT_InterfaceMethodref constant pool items.
- */
- static final int IMETH = 11;
-
- /**
- * The type of CONSTANT_String constant pool items.
- */
- static final int STR = 8;
-
- /**
- * The type of CONSTANT_Integer constant pool items.
- */
- static final int INT = 3;
-
- /**
- * The type of CONSTANT_Float constant pool items.
- */
- static final int FLOAT = 4;
-
- /**
- * The type of CONSTANT_Long constant pool items.
- */
- static final int LONG = 5;
-
- /**
- * The type of CONSTANT_Double constant pool items.
- */
- static final int DOUBLE = 6;
-
- /**
- * The type of CONSTANT_NameAndType constant pool items.
- */
- static final int NAME_TYPE = 12;
-
- /**
- * The type of CONSTANT_Utf8 constant pool items.
- */
- static final int UTF8 = 1;
-
- /**
- * The type of CONSTANT_MethodType constant pool items.
- */
- static final int MTYPE = 16;
-
- /**
- * The type of CONSTANT_MethodHandle constant pool items.
- */
- static final int HANDLE = 15;
-
- /**
- * The type of CONSTANT_InvokeDynamic constant pool items.
- */
- static final int INDY = 18;
-
- /**
- * The base value for all CONSTANT_MethodHandle constant pool items.
- * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9
- * different items.
- */
- static final int HANDLE_BASE = 20;
-
- /**
- * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable},
- * instead of the constant pool, in order to avoid clashes with normal
- * constant pool items in the ClassWriter constant pool's hash table.
- */
- static final int TYPE_NORMAL = 30;
-
- /**
- * Uninitialized type Item stored in the ClassWriter
- * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
- * avoid clashes with normal constant pool items in the ClassWriter constant
- * pool's hash table.
- */
- static final int TYPE_UNINIT = 31;
-
- /**
- * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable},
- * instead of the constant pool, in order to avoid clashes with normal
- * constant pool items in the ClassWriter constant pool's hash table.
- */
- static final int TYPE_MERGED = 32;
-
- /**
- * The type of BootstrapMethods items. These items are stored in a special
- * class attribute named BootstrapMethods and not in the constant pool.
- */
- static final int BSM = 33;
-
- /**
- * The class reader from which this class writer was constructed, if any.
- */
- ClassReader cr;
-
- /**
- * Minor and major version numbers of the class to be generated.
- */
- int version;
-
- /**
- * Index of the next item to be added in the constant pool.
- */
- int index;
-
- /**
- * The constant pool of this class.
- */
- final ByteVector pool;
-
- /**
- * The constant pool's hash table data.
- */
- Item[] items;
-
- /**
- * The threshold of the constant pool's hash table.
- */
- int threshold;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key2;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key3;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key4;
-
- /**
- * A type table used to temporarily store internal names that will not
- * necessarily be stored in the constant pool. This type table is used by
- * the control flow and data flow analysis algorithm used to compute stack
- * map frames from scratch. This array associates to each index i
- * the Item whose index is i. All Item objects stored in this array
- * are also stored in the {@link #items} hash table. These two arrays allow
- * to retrieve an Item from its index or, conversely, to get the index of an
- * Item from its value. Each Item stores an internal name in its
- * {@link Item#strVal1} field.
- */
- Item[] typeTable;
-
- /**
- * Number of elements in the {@link #typeTable} array.
- */
- private short typeCount;
-
- /**
- * The access flags of this class.
- */
- private int access;
-
- /**
- * The constant pool item that contains the internal name of this class.
- */
- private int name;
-
- /**
- * The internal name of this class.
- */
- String thisName;
-
- /**
- * The constant pool item that contains the signature of this class.
- */
- private int signature;
-
- /**
- * The constant pool item that contains the internal name of the super class
- * of this class.
- */
- private int superName;
-
- /**
- * Number of interfaces implemented or extended by this class or interface.
- */
- private int interfaceCount;
-
- /**
- * The interfaces implemented or extended by this class or interface. More
- * precisely, this array contains the indexes of the constant pool items
- * that contain the internal names of these interfaces.
- */
- private int[] interfaces;
-
- /**
- * The index of the constant pool item that contains the name of the source
- * file from which this class was compiled.
- */
- private int sourceFile;
-
- /**
- * The SourceDebug attribute of this class.
- */
- private ByteVector sourceDebug;
-
- /**
- * The constant pool item that contains the name of the enclosing class of
- * this class.
- */
- private int enclosingMethodOwner;
-
- /**
- * The constant pool item that contains the name and descriptor of the
- * enclosing method of this class.
- */
- private int enclosingMethod;
-
- /**
- * The runtime visible annotations of this class.
- */
- private AnnotationWriter anns;
-
- /**
- * The runtime invisible annotations of this class.
- */
- private AnnotationWriter ianns;
-
- /**
- * The non standard attributes of this class.
- */
- private Attribute attrs;
-
- /**
- * The number of entries in the InnerClasses attribute.
- */
- private int innerClassesCount;
-
- /**
- * The InnerClasses attribute.
- */
- private ByteVector innerClasses;
-
- /**
- * The number of entries in the BootstrapMethods attribute.
- */
- int bootstrapMethodsCount;
-
- /**
- * The BootstrapMethods attribute.
- */
- ByteVector bootstrapMethods;
-
- /**
- * The fields of this class. These fields are stored in a linked list of
- * {@link FieldWriter} objects, linked to each other by their
- * {@link FieldWriter#fv} field. This field stores the first element of this
- * list.
- */
- FieldWriter firstField;
-
- /**
- * The fields of this class. These fields are stored in a linked list of
- * {@link FieldWriter} objects, linked to each other by their
- * {@link FieldWriter#fv} field. This field stores the last element of this
- * list.
- */
- FieldWriter lastField;
-
- /**
- * The methods of this class. These methods are stored in a linked list of
- * {@link MethodWriter} objects, linked to each other by their
- * {@link MethodWriter#mv} field. This field stores the first element of
- * this list.
- */
- MethodWriter firstMethod;
-
- /**
- * The methods of this class. These methods are stored in a linked list of
- * {@link MethodWriter} objects, linked to each other by their
- * {@link MethodWriter#mv} field. This field stores the last element of this
- * list.
- */
- MethodWriter lastMethod;
-
- /**
- * true if the maximum stack size and number of local variables
- * must be automatically computed.
- */
- private final boolean computeMaxs;
-
- /**
- * true if the stack map frames must be recomputed from scratch.
- */
- private final boolean computeFrames;
-
- /**
- * true if the stack map tables of this class are invalid. The
- * {@link MethodWriter#resizeInstructions} method cannot transform existing
- * stack map tables, and so produces potentially invalid classes when it is
- * executed. In this case the class is reread and rewritten with the
- * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize
- * stack map tables when this option is used).
- */
- boolean invalidFrames;
-
- // ------------------------------------------------------------------------
- // Static initializer
- // ------------------------------------------------------------------------
-
- /**
- * Computes the instruction types of JVM opcodes.
- */
- static {
- int i;
- byte[] b = new byte[220];
- String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD"
- + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA"
- + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ";
- for (i = 0; i < b.length; ++i) {
- b[i] = (byte) (s.charAt(i) - 'A');
- }
- TYPE = b;
-
- // code to generate the above string
- //
- // // SBYTE_INSN instructions
- // b[Constants.NEWARRAY] = SBYTE_INSN;
- // b[Constants.BIPUSH] = SBYTE_INSN;
- //
- // // SHORT_INSN instructions
- // b[Constants.SIPUSH] = SHORT_INSN;
- //
- // // (IMPL)VAR_INSN instructions
- // b[Constants.RET] = VAR_INSN;
- // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) {
- // b[i] = VAR_INSN;
- // }
- // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) {
- // b[i] = VAR_INSN;
- // }
- // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3
- // b[i] = IMPLVAR_INSN;
- // }
- // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3
- // b[i] = IMPLVAR_INSN;
- // }
- //
- // // TYPE_INSN instructions
- // b[Constants.NEW] = TYPE_INSN;
- // b[Constants.ANEWARRAY] = TYPE_INSN;
- // b[Constants.CHECKCAST] = TYPE_INSN;
- // b[Constants.INSTANCEOF] = TYPE_INSN;
- //
- // // (Set)FIELDORMETH_INSN instructions
- // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) {
- // b[i] = FIELDORMETH_INSN;
- // }
- // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN;
- // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN;
- //
- // // LABEL(W)_INSN instructions
- // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) {
- // b[i] = LABEL_INSN;
- // }
- // b[Constants.IFNULL] = LABEL_INSN;
- // b[Constants.IFNONNULL] = LABEL_INSN;
- // b[200] = LABELW_INSN; // GOTO_W
- // b[201] = LABELW_INSN; // JSR_W
- // // temporary opcodes used internally by ASM - see Label and
- // MethodWriter
- // for (i = 202; i < 220; ++i) {
- // b[i] = LABEL_INSN;
- // }
- //
- // // LDC(_W) instructions
- // b[Constants.LDC] = LDC_INSN;
- // b[19] = LDCW_INSN; // LDC_W
- // b[20] = LDCW_INSN; // LDC2_W
- //
- // // special instructions
- // b[Constants.IINC] = IINC_INSN;
- // b[Constants.TABLESWITCH] = TABL_INSN;
- // b[Constants.LOOKUPSWITCH] = LOOK_INSN;
- // b[Constants.MULTIANEWARRAY] = MANA_INSN;
- // b[196] = WIDE_INSN; // WIDE
- //
- // for (i = 0; i < b.length; ++i) {
- // System.err.print((char)('A' + b[i]));
- // }
- // System.err.println();
- }
-
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new {@link ClassWriter} object.
- *
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. See {@link #COMPUTE_MAXS},
- * {@link #COMPUTE_FRAMES}.
- */
- public ClassWriter(final int flags) {
- super(Opcodes.ASM4);
- index = 1;
- pool = new ByteVector();
- items = new Item[256];
- threshold = (int) (0.75d * items.length);
- key = new Item();
- key2 = new Item();
- key3 = new Item();
- key4 = new Item();
- this.computeMaxs = (flags & COMPUTE_MAXS) != 0;
- this.computeFrames = (flags & COMPUTE_FRAMES) != 0;
- }
-
- /**
- * Constructs a new {@link ClassWriter} object and enables optimizations for
- * "mostly add" bytecode transformations. These optimizations are the
- * following:
- *
- *
- *
The constant pool from the original class is copied as is in the new
- * class, which saves time. New constant pool entries will be added at the
- * end if necessary, but unused constant pool entries won't be
- * removed.
- *
Methods that are not transformed are copied as is in the new class,
- * directly from the original class bytecode (i.e. without emitting visit
- * events for all the method instructions), which saves a lot of
- * time. Untransformed methods are detected by the fact that the
- * {@link ClassReader} receives {@link MethodVisitor} objects that come from
- * a {@link ClassWriter} (and not from any other {@link ClassVisitor}
- * instance).
- *
- *
- * @param classReader
- * the {@link ClassReader} used to read the original class. It
- * will be used to copy the entire constant pool from the
- * original class and also to copy other fragments of original
- * bytecode where applicable.
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. These option flags do not affect methods
- * that are copied as is in the new class. This means that the
- * maximum stack size nor the stack frames will be computed for
- * these methods. See {@link #COMPUTE_MAXS},
- * {@link #COMPUTE_FRAMES}.
- */
- public ClassWriter(final ClassReader classReader, final int flags) {
- this(flags);
- classReader.copyPool(this);
- this.cr = classReader;
- }
-
- // ------------------------------------------------------------------------
- // Implementation of the ClassVisitor abstract class
- // ------------------------------------------------------------------------
-
- @Override
- public final void visit(final int version, final int access,
- final String name, final String signature, final String superName,
- final String[] interfaces) {
- this.version = version;
- this.access = access;
- this.name = newClass(name);
- thisName = name;
- if (ClassReader.SIGNATURES && signature != null) {
- this.signature = newUTF8(signature);
- }
- this.superName = superName == null ? 0 : newClass(superName);
- if (interfaces != null && interfaces.length > 0) {
- interfaceCount = interfaces.length;
- this.interfaces = new int[interfaceCount];
- for (int i = 0; i < interfaceCount; ++i) {
- this.interfaces[i] = newClass(interfaces[i]);
- }
- }
- }
-
- @Override
- public final void visitSource(final String file, final String debug) {
- if (file != null) {
- sourceFile = newUTF8(file);
- }
- if (debug != null) {
- sourceDebug = new ByteVector().putUTF8(debug);
- }
- }
-
- @Override
- public final void visitOuterClass(final String owner, final String name,
- final String desc) {
- enclosingMethodOwner = newClass(owner);
- if (name != null && desc != null) {
- enclosingMethod = newNameType(name, desc);
- }
- }
-
- @Override
- public final AnnotationVisitor visitAnnotation(final String desc,
- final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- // write type, and reserve space for values count
- bv.putShort(newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2);
- if (visible) {
- aw.next = anns;
- anns = aw;
- } else {
- aw.next = ianns;
- ianns = aw;
- }
- return aw;
- }
-
- @Override
- public final void visitAttribute(final Attribute attr) {
- attr.next = attrs;
- attrs = attr;
- }
-
- @Override
- public final void visitInnerClass(final String name,
- final String outerName, final String innerName, final int access) {
- if (innerClasses == null) {
- innerClasses = new ByteVector();
- }
- ++innerClassesCount;
- innerClasses.putShort(name == null ? 0 : newClass(name));
- innerClasses.putShort(outerName == null ? 0 : newClass(outerName));
- innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName));
- innerClasses.putShort(access);
- }
-
- @Override
- public final FieldVisitor visitField(final int access, final String name,
- final String desc, final String signature, final Object value) {
- return new FieldWriter(this, access, name, desc, signature, value);
- }
-
- @Override
- public final MethodVisitor visitMethod(final int access, final String name,
- final String desc, final String signature, final String[] exceptions) {
- return new MethodWriter(this, access, name, desc, signature,
- exceptions, computeMaxs, computeFrames);
- }
-
- @Override
- public final void visitEnd() {
- }
-
- // ------------------------------------------------------------------------
- // Other public methods
- // ------------------------------------------------------------------------
-
- /**
- * Returns the bytecode of the class that was build with this class writer.
- *
- * @return the bytecode of the class that was build with this class writer.
- */
- public byte[] toByteArray() {
- if (index > 0xFFFF) {
- throw new RuntimeException("Class file too large!");
- }
- // computes the real size of the bytecode of this class
- int size = 24 + 2 * interfaceCount;
- int nbFields = 0;
- FieldWriter fb = firstField;
- while (fb != null) {
- ++nbFields;
- size += fb.getSize();
- fb = (FieldWriter) fb.fv;
- }
- int nbMethods = 0;
- MethodWriter mb = firstMethod;
- while (mb != null) {
- ++nbMethods;
- size += mb.getSize();
- mb = (MethodWriter) mb.mv;
- }
- int attributeCount = 0;
- if (bootstrapMethods != null) {
- // we put it as first attribute in order to improve a bit
- // ClassReader.copyBootstrapMethods
- ++attributeCount;
- size += 8 + bootstrapMethods.length;
- newUTF8("BootstrapMethods");
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- ++attributeCount;
- size += 8;
- newUTF8("Signature");
- }
- if (sourceFile != 0) {
- ++attributeCount;
- size += 8;
- newUTF8("SourceFile");
- }
- if (sourceDebug != null) {
- ++attributeCount;
- size += sourceDebug.length + 4;
- newUTF8("SourceDebugExtension");
- }
- if (enclosingMethodOwner != 0) {
- ++attributeCount;
- size += 10;
- newUTF8("EnclosingMethod");
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- ++attributeCount;
- size += 6;
- newUTF8("Deprecated");
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((version & 0xFFFF) < Opcodes.V1_5
- || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- ++attributeCount;
- size += 6;
- newUTF8("Synthetic");
- }
- }
- if (innerClasses != null) {
- ++attributeCount;
- size += 8 + innerClasses.length;
- newUTF8("InnerClasses");
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- ++attributeCount;
- size += 8 + anns.getSize();
- newUTF8("RuntimeVisibleAnnotations");
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- ++attributeCount;
- size += 8 + ianns.getSize();
- newUTF8("RuntimeInvisibleAnnotations");
- }
- if (attrs != null) {
- attributeCount += attrs.getCount();
- size += attrs.getSize(this, null, 0, -1, -1);
- }
- size += pool.length;
- // allocates a byte vector of this size, in order to avoid unnecessary
- // arraycopy operations in the ByteVector.enlarge() method
- ByteVector out = new ByteVector(size);
- out.putInt(0xCAFEBABE).putInt(version);
- out.putShort(index).putByteArray(pool.data, 0, pool.length);
- int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE
- | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);
- out.putShort(access & ~mask).putShort(name).putShort(superName);
- out.putShort(interfaceCount);
- for (int i = 0; i < interfaceCount; ++i) {
- out.putShort(interfaces[i]);
- }
- out.putShort(nbFields);
- fb = firstField;
- while (fb != null) {
- fb.put(out);
- fb = (FieldWriter) fb.fv;
- }
- out.putShort(nbMethods);
- mb = firstMethod;
- while (mb != null) {
- mb.put(out);
- mb = (MethodWriter) mb.mv;
- }
- out.putShort(attributeCount);
- if (bootstrapMethods != null) {
- out.putShort(newUTF8("BootstrapMethods"));
- out.putInt(bootstrapMethods.length + 2).putShort(
- bootstrapMethodsCount);
- out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- out.putShort(newUTF8("Signature")).putInt(2).putShort(signature);
- }
- if (sourceFile != 0) {
- out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile);
- }
- if (sourceDebug != null) {
- int len = sourceDebug.length - 2;
- out.putShort(newUTF8("SourceDebugExtension")).putInt(len);
- out.putByteArray(sourceDebug.data, 2, len);
- }
- if (enclosingMethodOwner != 0) {
- out.putShort(newUTF8("EnclosingMethod")).putInt(4);
- out.putShort(enclosingMethodOwner).putShort(enclosingMethod);
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- out.putShort(newUTF8("Deprecated")).putInt(0);
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((version & 0xFFFF) < Opcodes.V1_5
- || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- out.putShort(newUTF8("Synthetic")).putInt(0);
- }
- }
- if (innerClasses != null) {
- out.putShort(newUTF8("InnerClasses"));
- out.putInt(innerClasses.length + 2).putShort(innerClassesCount);
- out.putByteArray(innerClasses.data, 0, innerClasses.length);
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- out.putShort(newUTF8("RuntimeVisibleAnnotations"));
- anns.put(out);
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- out.putShort(newUTF8("RuntimeInvisibleAnnotations"));
- ianns.put(out);
- }
- if (attrs != null) {
- attrs.put(this, null, 0, -1, -1, out);
- }
- if (invalidFrames) {
- ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
- new ClassReader(out.data).accept(cw, ClassReader.SKIP_FRAMES);
- return cw.toByteArray();
- }
- return out.data;
- }
-
- // ------------------------------------------------------------------------
- // Utility methods: constant pool management
- // ------------------------------------------------------------------------
-
- /**
- * Adds a number or string constant to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- *
- * @param cst
- * the value of the constant to be added to the constant pool.
- * This parameter must be an {@link Integer}, a {@link Float}, a
- * {@link Long}, a {@link Double}, a {@link String} or a
- * {@link Type}.
- * @return a new or already existing constant item with the given value.
- */
- Item newConstItem(final Object cst) {
- if (cst instanceof Integer) {
- int val = ((Integer) cst).intValue();
- return newInteger(val);
- } else if (cst instanceof Byte) {
- int val = ((Byte) cst).intValue();
- return newInteger(val);
- } else if (cst instanceof Character) {
- int val = ((Character) cst).charValue();
- return newInteger(val);
- } else if (cst instanceof Short) {
- int val = ((Short) cst).intValue();
- return newInteger(val);
- } else if (cst instanceof Boolean) {
- int val = ((Boolean) cst).booleanValue() ? 1 : 0;
- return newInteger(val);
- } else if (cst instanceof Float) {
- float val = ((Float) cst).floatValue();
- return newFloat(val);
- } else if (cst instanceof Long) {
- long val = ((Long) cst).longValue();
- return newLong(val);
- } else if (cst instanceof Double) {
- double val = ((Double) cst).doubleValue();
- return newDouble(val);
- } else if (cst instanceof String) {
- return newString((String) cst);
- } else if (cst instanceof Type) {
- Type t = (Type) cst;
- int s = t.getSort();
- if (s == Type.OBJECT) {
- return newClassItem(t.getInternalName());
- } else if (s == Type.METHOD) {
- return newMethodTypeItem(t.getDescriptor());
- } else { // s == primitive type or array
- return newClassItem(t.getDescriptor());
- }
- } else if (cst instanceof Handle) {
- Handle h = (Handle) cst;
- return newHandleItem(h.tag, h.owner, h.name, h.desc);
- } else {
- throw new IllegalArgumentException("value " + cst);
- }
- }
-
- /**
- * Adds a number or string constant to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param cst
- * the value of the constant to be added to the constant pool.
- * This parameter must be an {@link Integer}, a {@link Float}, a
- * {@link Long}, a {@link Double} or a {@link String}.
- * @return the index of a new or already existing constant item with the
- * given value.
- */
- public int newConst(final Object cst) {
- return newConstItem(cst).index;
- }
-
- /**
- * Adds an UTF8 string to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item. This
- * method is intended for {@link Attribute} sub classes, and is normally not
- * needed by class generators or adapters.
- *
- * @param value
- * the String value.
- * @return the index of a new or already existing UTF8 item.
- */
- public int newUTF8(final String value) {
- key.set(UTF8, value, null, null);
- Item result = get(key);
- if (result == null) {
- pool.putByte(UTF8).putUTF8(value);
- result = new Item(index++, key);
- put(result);
- }
- return result.index;
- }
-
- /**
- * Adds a class reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param value
- * the internal name of the class.
- * @return a new or already existing class reference item.
- */
- Item newClassItem(final String value) {
- key2.set(CLASS, value, null, null);
- Item result = get(key2);
- if (result == null) {
- pool.put12(CLASS, newUTF8(value));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a class reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param value
- * the internal name of the class.
- * @return the index of a new or already existing class reference item.
- */
- public int newClass(final String value) {
- return newClassItem(value).index;
- }
-
- /**
- * Adds a method type reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param methodDesc
- * method descriptor of the method type.
- * @return a new or already existing method type reference item.
- */
- Item newMethodTypeItem(final String methodDesc) {
- key2.set(MTYPE, methodDesc, null, null);
- Item result = get(key2);
- if (result == null) {
- pool.put12(MTYPE, newUTF8(methodDesc));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a method type reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param methodDesc
- * method descriptor of the method type.
- * @return the index of a new or already existing method type reference
- * item.
- */
- public int newMethodType(final String methodDesc) {
- return newMethodTypeItem(methodDesc).index;
- }
-
- /**
- * Adds a handle to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item. This method is
- * intended for {@link Attribute} sub classes, and is normally not needed by
- * class generators or adapters.
- *
- * @param tag
- * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
- * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
- * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
- * {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- * @param owner
- * the internal name of the field or method owner class.
- * @param name
- * the name of the field or method.
- * @param desc
- * the descriptor of the field or method.
- * @return a new or an already existing method type reference item.
- */
- Item newHandleItem(final int tag, final String owner, final String name,
- final String desc) {
- key4.set(HANDLE_BASE + tag, owner, name, desc);
- Item result = get(key4);
- if (result == null) {
- if (tag <= Opcodes.H_PUTSTATIC) {
- put112(HANDLE, tag, newField(owner, name, desc));
- } else {
- put112(HANDLE,
- tag,
- newMethod(owner, name, desc,
- tag == Opcodes.H_INVOKEINTERFACE));
- }
- result = new Item(index++, key4);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a handle to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item. This method is
- * intended for {@link Attribute} sub classes, and is normally not needed by
- * class generators or adapters.
- *
- * @param tag
- * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
- * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
- * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
- * {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- * @param owner
- * the internal name of the field or method owner class.
- * @param name
- * the name of the field or method.
- * @param desc
- * the descriptor of the field or method.
- * @return the index of a new or already existing method type reference
- * item.
- */
- public int newHandle(final int tag, final String owner, final String name,
- final String desc) {
- return newHandleItem(tag, owner, name, desc).index;
- }
-
- /**
- * Adds an invokedynamic reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param name
- * name of the invoked method.
- * @param desc
- * descriptor of the invoke method.
- * @param bsm
- * the bootstrap method.
- * @param bsmArgs
- * the bootstrap method constant arguments.
- *
- * @return a new or an already existing invokedynamic type reference item.
- */
- Item newInvokeDynamicItem(final String name, final String desc,
- final Handle bsm, final Object... bsmArgs) {
- // cache for performance
- ByteVector bootstrapMethods = this.bootstrapMethods;
- if (bootstrapMethods == null) {
- bootstrapMethods = this.bootstrapMethods = new ByteVector();
- }
-
- int position = bootstrapMethods.length; // record current position
-
- int hashCode = bsm.hashCode();
- bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name,
- bsm.desc));
-
- int argsLength = bsmArgs.length;
- bootstrapMethods.putShort(argsLength);
-
- for (int i = 0; i < argsLength; i++) {
- Object bsmArg = bsmArgs[i];
- hashCode ^= bsmArg.hashCode();
- bootstrapMethods.putShort(newConst(bsmArg));
- }
-
- byte[] data = bootstrapMethods.data;
- int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments)
- hashCode &= 0x7FFFFFFF;
- Item result = items[hashCode % items.length];
- loop: while (result != null) {
- if (result.type != BSM || result.hashCode != hashCode) {
- result = result.next;
- continue;
- }
-
- // because the data encode the size of the argument
- // we don't need to test if these size are equals
- int resultPosition = result.intVal;
- for (int p = 0; p < length; p++) {
- if (data[position + p] != data[resultPosition + p]) {
- result = result.next;
- continue loop;
- }
- }
- break;
- }
-
- int bootstrapMethodIndex;
- if (result != null) {
- bootstrapMethodIndex = result.index;
- bootstrapMethods.length = position; // revert to old position
- } else {
- bootstrapMethodIndex = bootstrapMethodsCount++;
- result = new Item(bootstrapMethodIndex);
- result.set(position, hashCode);
- put(result);
- }
-
- // now, create the InvokeDynamic constant
- key3.set(name, desc, bootstrapMethodIndex);
- result = get(key3);
- if (result == null) {
- put122(INDY, bootstrapMethodIndex, newNameType(name, desc));
- result = new Item(index++, key3);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds an invokedynamic reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param name
- * name of the invoked method.
- * @param desc
- * descriptor of the invoke method.
- * @param bsm
- * the bootstrap method.
- * @param bsmArgs
- * the bootstrap method constant arguments.
- *
- * @return the index of a new or already existing invokedynamic reference
- * item.
- */
- public int newInvokeDynamic(final String name, final String desc,
- final Handle bsm, final Object... bsmArgs) {
- return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index;
- }
-
- /**
- * Adds a field reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- *
- * @param owner
- * the internal name of the field's owner class.
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor.
- * @return a new or already existing field reference item.
- */
- Item newFieldItem(final String owner, final String name, final String desc) {
- key3.set(FIELD, owner, name, desc);
- Item result = get(key3);
- if (result == null) {
- put122(FIELD, newClass(owner), newNameType(name, desc));
- result = new Item(index++, key3);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a field reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param owner
- * the internal name of the field's owner class.
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor.
- * @return the index of a new or already existing field reference item.
- */
- public int newField(final String owner, final String name, final String desc) {
- return newFieldItem(owner, name, desc).index;
- }
-
- /**
- * Adds a method reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- *
- * @param owner
- * the internal name of the method's owner class.
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor.
- * @param itf
- * true if owner is an interface.
- * @return a new or already existing method reference item.
- */
- Item newMethodItem(final String owner, final String name,
- final String desc, final boolean itf) {
- int type = itf ? IMETH : METH;
- key3.set(type, owner, name, desc);
- Item result = get(key3);
- if (result == null) {
- put122(type, newClass(owner), newNameType(name, desc));
- result = new Item(index++, key3);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a method reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param owner
- * the internal name of the method's owner class.
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor.
- * @param itf
- * true if owner is an interface.
- * @return the index of a new or already existing method reference item.
- */
- public int newMethod(final String owner, final String name,
- final String desc, final boolean itf) {
- return newMethodItem(owner, name, desc, itf).index;
- }
-
- /**
- * Adds an integer to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item.
- *
- * @param value
- * the int value.
- * @return a new or already existing int item.
- */
- Item newInteger(final int value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(INT).putInt(value);
- result = new Item(index++, key);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a float to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the float value.
- * @return a new or already existing float item.
- */
- Item newFloat(final float value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(FLOAT).putInt(key.intVal);
- result = new Item(index++, key);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a long to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the long value.
- * @return a new or already existing long item.
- */
- Item newLong(final long value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(LONG).putLong(value);
- result = new Item(index, key);
- index += 2;
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a double to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the double value.
- * @return a new or already existing double item.
- */
- Item newDouble(final double value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(DOUBLE).putLong(key.longVal);
- result = new Item(index, key);
- index += 2;
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a string to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the String value.
- * @return a new or already existing string item.
- */
- private Item newString(final String value) {
- key2.set(STR, value, null, null);
- Item result = get(key2);
- if (result == null) {
- pool.put12(STR, newUTF8(value));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds a name and type to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item. This
- * method is intended for {@link Attribute} sub classes, and is normally not
- * needed by class generators or adapters.
- *
- * @param name
- * a name.
- * @param desc
- * a type descriptor.
- * @return the index of a new or already existing name and type item.
- */
- public int newNameType(final String name, final String desc) {
- return newNameTypeItem(name, desc).index;
- }
-
- /**
- * Adds a name and type to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item.
- *
- * @param name
- * a name.
- * @param desc
- * a type descriptor.
- * @return a new or already existing name and type item.
- */
- Item newNameTypeItem(final String name, final String desc) {
- key2.set(NAME_TYPE, name, desc, null);
- Item result = get(key2);
- if (result == null) {
- put122(NAME_TYPE, newUTF8(name), newUTF8(desc));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
- }
-
- /**
- * Adds the given internal name to {@link #typeTable} and returns its index.
- * Does nothing if the type table already contains this internal name.
- *
- * @param type
- * the internal name to be added to the type table.
- * @return the index of this internal name in the type table.
- */
- int addType(final String type) {
- key.set(TYPE_NORMAL, type, null, null);
- Item result = get(key);
- if (result == null) {
- result = addType(key);
- }
- return result.index;
- }
-
- /**
- * Adds the given "uninitialized" type to {@link #typeTable} and returns its
- * index. This method is used for UNINITIALIZED types, made of an internal
- * name and a bytecode offset.
- *
- * @param type
- * the internal name to be added to the type table.
- * @param offset
- * the bytecode offset of the NEW instruction that created this
- * UNINITIALIZED type value.
- * @return the index of this internal name in the type table.
- */
- int addUninitializedType(final String type, final int offset) {
- key.type = TYPE_UNINIT;
- key.intVal = offset;
- key.strVal1 = type;
- key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset);
- Item result = get(key);
- if (result == null) {
- result = addType(key);
- }
- return result.index;
- }
-
- /**
- * Adds the given Item to {@link #typeTable}.
- *
- * @param item
- * the value to be added to the type table.
- * @return the added Item, which a new Item instance with the same value as
- * the given Item.
- */
- private Item addType(final Item item) {
- ++typeCount;
- Item result = new Item(typeCount, key);
- put(result);
- if (typeTable == null) {
- typeTable = new Item[16];
- }
- if (typeCount == typeTable.length) {
- Item[] newTable = new Item[2 * typeTable.length];
- System.arraycopy(typeTable, 0, newTable, 0, typeTable.length);
- typeTable = newTable;
- }
- typeTable[typeCount] = result;
- return result;
- }
-
- /**
- * Returns the index of the common super type of the two given types. This
- * method calls {@link #getCommonSuperClass} and caches the result in the
- * {@link #items} hash table to speedup future calls with the same
- * parameters.
- *
- * @param type1
- * index of an internal name in {@link #typeTable}.
- * @param type2
- * index of an internal name in {@link #typeTable}.
- * @return the index of the common super type of the two given types.
- */
- int getMergedType(final int type1, final int type2) {
- key2.type = TYPE_MERGED;
- key2.longVal = type1 | (((long) type2) << 32);
- key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2);
- Item result = get(key2);
- if (result == null) {
- String t = typeTable[type1].strVal1;
- String u = typeTable[type2].strVal1;
- key2.intVal = addType(getCommonSuperClass(t, u));
- result = new Item((short) 0, key2);
- put(result);
- }
- return result.intVal;
- }
-
- /**
- * Returns the common super type of the two given types. The default
- * implementation of this method loads the two given classes and uses
- * the java.lang.Class methods to find the common super class. It can be
- * overridden to compute this common super type in other ways, in particular
- * without actually loading any class, or to take into account the class
- * that is currently being generated by this ClassWriter, which can of
- * course not be loaded since it is under construction.
- *
- * @param type1
- * the internal name of a class.
- * @param type2
- * the internal name of another class.
- * @return the internal name of the common super class of the two given
- * classes.
- */
- protected String getCommonSuperClass(final String type1, final String type2) {
- Class> c, d;
- ClassLoader classLoader = getClass().getClassLoader();
- try {
- c = Class.forName(type1.replace('/', '.'), false, classLoader);
- d = Class.forName(type2.replace('/', '.'), false, classLoader);
- } catch (Exception e) {
- throw new RuntimeException(e.toString());
- }
- if (c.isAssignableFrom(d)) {
- return type1;
- }
- if (d.isAssignableFrom(c)) {
- return type2;
- }
- if (c.isInterface() || d.isInterface()) {
- return "java/lang/Object";
- } else {
- do {
- c = c.getSuperclass();
- } while (!c.isAssignableFrom(d));
- return c.getName().replace('.', '/');
- }
- }
-
- /**
- * Returns the constant pool's hash table item which is equal to the given
- * item.
- *
- * @param key
- * a constant pool item.
- * @return the constant pool's hash table item which is equal to the given
- * item, or null if there is no such item.
- */
- private Item get(final Item key) {
- Item i = items[key.hashCode % items.length];
- while (i != null && (i.type != key.type || !key.isEqualTo(i))) {
- i = i.next;
- }
- return i;
- }
-
- /**
- * Puts the given item in the constant pool's hash table. The hash table
- * must not already contains this item.
- *
- * @param i
- * the item to be added to the constant pool's hash table.
- */
- private void put(final Item i) {
- if (index + typeCount > threshold) {
- int ll = items.length;
- int nl = ll * 2 + 1;
- Item[] newItems = new Item[nl];
- for (int l = ll - 1; l >= 0; --l) {
- Item j = items[l];
- while (j != null) {
- int index = j.hashCode % newItems.length;
- Item k = j.next;
- j.next = newItems[index];
- newItems[index] = j;
- j = k;
- }
- }
- items = newItems;
- threshold = (int) (nl * 0.75);
- }
- int index = i.hashCode % items.length;
- i.next = items[index];
- items[index] = i;
- }
-
- /**
- * Puts one byte and two shorts into the constant pool.
- *
- * @param b
- * a byte.
- * @param s1
- * a short.
- * @param s2
- * another short.
- */
- private void put122(final int b, final int s1, final int s2) {
- pool.put12(b, s1).putShort(s2);
- }
-
- /**
- * Puts two bytes and one short into the constant pool.
- *
- * @param b1
- * a byte.
- * @param b2
- * another byte.
- * @param s
- * a short.
- */
- private void put112(final int b1, final int b2, final int s) {
- pool.put11(b1, b2).putShort(s);
- }
+ /**
+ * A flag to automatically compute the maximum stack size and the maximum number of local
+ * variables of methods. If this flag is set, then the arguments of the {@link
+ * MethodVisitor#visitMaxs} method of the {@link MethodVisitor} returned by the {@link
+ * #visitMethod} method will be ignored, and computed automatically from the signature and the
+ * bytecode of each method.
+ *
+ *
Note: for classes whose version is {@link Opcodes#V1_7} of more, this option requires
+ * valid stack map frames. The maximum stack size is then computed from these frames, and from the
+ * bytecode instructions in between. If stack map frames are not present or must be recomputed,
+ * used {@link #COMPUTE_FRAMES} instead.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_MAXS = 1;
+
+ /**
+ * A flag to automatically compute the stack map frames of methods from scratch. If this flag is
+ * set, then the calls to the {@link MethodVisitor#visitFrame} method are ignored, and the stack
+ * map frames are recomputed from the methods bytecode. The arguments of the {@link
+ * MethodVisitor#visitMaxs} method are also ignored and recomputed from the bytecode. In other
+ * words, {@link #COMPUTE_FRAMES} implies {@link #COMPUTE_MAXS}.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_FRAMES = 2;
+
+ // Note: fields are ordered as in the ClassFile structure, and those related to attributes are
+ // ordered as in Section 4.7 of the JVMS.
+
+ /**
+ * The minor_version and major_version fields of the JVMS ClassFile structure. minor_version is
+ * stored in the 16 most significant bits, and major_version in the 16 least significant bits.
+ */
+ private int version;
+
+ /** The symbol table for this class (contains the constant_pool and the BootstrapMethods). */
+ private final SymbolTable symbolTable;
+
+ /**
+ * The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
+ * ClassFile structure.
+ */
+ private int accessFlags;
+
+ /** The this_class field of the JVMS ClassFile structure. */
+ private int thisClass;
+
+ /** The super_class field of the JVMS ClassFile structure. */
+ private int superClass;
+
+ /** The interface_count field of the JVMS ClassFile structure. */
+ private int interfaceCount;
+
+ /** The 'interfaces' array of the JVMS ClassFile structure. */
+ private int[] interfaces;
+
+ /**
+ * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their
+ * {@link FieldWriter#fv} field. This field stores the first element of this list.
+ */
+ private FieldWriter firstField;
+
+ /**
+ * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their
+ * {@link FieldWriter#fv} field. This field stores the last element of this list.
+ */
+ private FieldWriter lastField;
+
+ /**
+ * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their
+ * {@link MethodWriter#mv} field. This field stores the first element of this list.
+ */
+ private MethodWriter firstMethod;
+
+ /**
+ * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their
+ * {@link MethodWriter#mv} field. This field stores the last element of this list.
+ */
+ private MethodWriter lastMethod;
+
+ /** The number_of_classes field of the InnerClasses attribute, or 0. */
+ private int numberOfInnerClasses;
+
+ /** The 'classes' array of the InnerClasses attribute, or null. */
+ private ByteVector innerClasses;
+
+ /** The class_index field of the EnclosingMethod attribute, or 0. */
+ private int enclosingClassIndex;
+
+ /** The method_index field of the EnclosingMethod attribute. */
+ private int enclosingMethodIndex;
+
+ /** The signature_index field of the Signature attribute, or 0. */
+ private int signatureIndex;
+
+ /** The source_file_index field of the SourceFile attribute, or 0. */
+ private int sourceFileIndex;
+
+ /** The debug_extension field of the SourceDebugExtension attribute, or null. */
+ private ByteVector debugExtension;
+
+ /**
+ * The last runtime visible annotation of this class. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
+
+ /**
+ * The last runtime invisible annotation of this class. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
+
+ /**
+ * The last runtime visible type annotation of this class. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of this class. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /** The Module attribute of this class, or null. */
+ private ModuleWriter moduleWriter;
+
+ /** The host_class_index field of the NestHost attribute, or 0. */
+ private int nestHostClassIndex;
+
+ /** The number_of_classes field of the NestMembers attribute, or 0. */
+ private int numberOfNestMemberClasses;
+
+ /** The 'classes' array of the NestMembers attribute, or null. */
+ private ByteVector nestMemberClasses;
+
+ /**
+ * The first non standard attribute of this class. The next ones can be accessed with the {@link
+ * Attribute#nextAttribute} field. May be null.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #toByteArray} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
+
+ /**
+ * Indicates what must be automatically computed in {@link MethodWriter}. Must be one of {@link
+ * MethodWriter#COMPUTE_NOTHING}, {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL}, {@link
+ * MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}.
+ */
+ private int compute;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructor
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassWriter} object.
+ *
+ * @param flags option flags that can be used to modify the default behavior of this class. Must
+ * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}.
+ */
+ public ClassWriter(final int flags) {
+ this(null, flags);
+ }
+
+ /**
+ * Constructs a new {@link ClassWriter} object and enables optimizations for "mostly add" bytecode
+ * transformations. These optimizations are the following:
+ *
+ *
+ *
The constant pool and bootstrap methods from the original class are copied as is in the
+ * new class, which saves time. New constant pool entries and new bootstrap methods will be
+ * added at the end if necessary, but unused constant pool entries or bootstrap methods
+ * won't be removed.
+ *
Methods that are not transformed are copied as is in the new class, directly from the
+ * original class bytecode (i.e. without emitting visit events for all the method
+ * instructions), which saves a lot of time. Untransformed methods are detected by
+ * the fact that the {@link ClassReader} receives {@link MethodVisitor} objects that come
+ * from a {@link ClassWriter} (and not from any other {@link ClassVisitor} instance).
+ *
+ *
+ * @param classReader the {@link ClassReader} used to read the original class. It will be used to
+ * copy the entire constant pool and bootstrap methods from the original class and also to
+ * copy other fragments of original bytecode where applicable.
+ * @param flags option flags that can be used to modify the default behavior of this class.Must be
+ * zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. These option flags do
+ * not affect methods that are copied as is in the new class. This means that neither the
+ * maximum stack size nor the stack frames will be computed for these methods.
+ */
+ public ClassWriter(final ClassReader classReader, final int flags) {
+ super(Opcodes.ASM6);
+ symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader);
+ if ((flags & COMPUTE_FRAMES) != 0) {
+ this.compute = MethodWriter.COMPUTE_ALL_FRAMES;
+ } else if ((flags & COMPUTE_MAXS) != 0) {
+ this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL;
+ } else {
+ this.compute = MethodWriter.COMPUTE_NOTHING;
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the ClassVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public final void visit(
+ final int version,
+ final int access,
+ final String name,
+ final String signature,
+ final String superName,
+ final String[] interfaces) {
+ this.version = version;
+ this.accessFlags = access;
+ this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name);
+ if (signature != null) {
+ this.signatureIndex = symbolTable.addConstantUtf8(signature);
+ }
+ this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index;
+ if (interfaces != null && interfaces.length > 0) {
+ interfaceCount = interfaces.length;
+ this.interfaces = new int[interfaceCount];
+ for (int i = 0; i < interfaceCount; ++i) {
+ this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index;
+ }
+ }
+ if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) {
+ compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES;
+ }
+ }
+
+ @Override
+ public final void visitSource(final String file, final String debug) {
+ if (file != null) {
+ sourceFileIndex = symbolTable.addConstantUtf8(file);
+ }
+ if (debug != null) {
+ debugExtension = new ByteVector().encodeUTF8(debug, 0, Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public final ModuleVisitor visitModule(
+ final String name, final int access, final String version) {
+ return moduleWriter =
+ new ModuleWriter(
+ symbolTable,
+ symbolTable.addConstantModule(name).index,
+ access,
+ version == null ? 0 : symbolTable.addConstantUtf8(version));
+ }
+
+ @Override
+ public void visitNestHostExperimental(final String nestHost) {
+ nestHostClassIndex = symbolTable.addConstantClass(nestHost).index;
+ }
+
+ @Override
+ public final void visitOuterClass(
+ final String owner, final String name, final String descriptor) {
+ enclosingClassIndex = symbolTable.addConstantClass(owner).index;
+ if (name != null && descriptor != null) {
+ enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor);
+ }
+ }
+
+ @Override
+ public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold an 'annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
+ ByteVector annotation = new ByteVector();
+ // Write type_index and reserve space for num_element_value_pairs.
+ annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
+ }
+ }
+
+ @Override
+ public final AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ TypeReference.putTarget(typeRef, typeAnnotation);
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public final void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
+
+ @Override
+ public void visitNestMemberExperimental(final String nestMember) {
+ if (nestMemberClasses == null) {
+ nestMemberClasses = new ByteVector();
+ }
+ ++numberOfNestMemberClasses;
+ nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index);
+ }
+
+ @Override
+ public final void visitInnerClass(
+ final String name, final String outerName, final String innerName, final int access) {
+ if (innerClasses == null) {
+ innerClasses = new ByteVector();
+ }
+ // Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the constant_pool table
+ // which represents a class or interface C that is not a package member must have exactly one
+ // corresponding entry in the classes array". To avoid duplicates we keep track in the info
+ // field of the Symbol of each CONSTANT_Class_info entry C whether an inner class entry has
+ // already been added for C. If so, we store the index of this inner class entry (plus one) in
+ // the info field. This trick allows duplicate detection in O(1) time.
+ Symbol nameSymbol = symbolTable.addConstantClass(name);
+ if (nameSymbol.info == 0) {
+ ++numberOfInnerClasses;
+ innerClasses.putShort(nameSymbol.index);
+ innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index);
+ innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName));
+ innerClasses.putShort(access);
+ nameSymbol.info = numberOfInnerClasses;
+ } else {
+ // Compare the inner classes entry nameSymbol.info - 1 with the arguments of this method and
+ // throw an exception if there is a difference?
+ }
+ }
+
+ @Override
+ public final FieldVisitor visitField(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Object value) {
+ FieldWriter fieldWriter =
+ new FieldWriter(symbolTable, access, name, descriptor, signature, value);
+ if (firstField == null) {
+ firstField = fieldWriter;
+ } else {
+ lastField.fv = fieldWriter;
+ }
+ return lastField = fieldWriter;
+ }
+
+ @Override
+ public final MethodVisitor visitMethod(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions) {
+ MethodWriter methodWriter =
+ new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
+ if (firstMethod == null) {
+ firstMethod = methodWriter;
+ } else {
+ lastMethod.mv = methodWriter;
+ }
+ return lastMethod = methodWriter;
+ }
+
+ @Override
+ public final void visitEnd() {
+ // Nothing to do.
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Other public methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the content of the class file that was built by this ClassWriter.
+ *
+ * @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter.
+ */
+ public byte[] toByteArray() {
+ // First step: compute the size in bytes of the ClassFile structure.
+ // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version,
+ // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count,
+ // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too.
+ int size = 24 + 2 * interfaceCount;
+ int fieldsCount = 0;
+ FieldWriter fieldWriter = firstField;
+ while (fieldWriter != null) {
+ ++fieldsCount;
+ size += fieldWriter.computeFieldInfoSize();
+ fieldWriter = (FieldWriter) fieldWriter.fv;
+ }
+ int methodsCount = 0;
+ MethodWriter methodWriter = firstMethod;
+ while (methodWriter != null) {
+ ++methodsCount;
+ size += methodWriter.computeMethodInfoSize();
+ methodWriter = (MethodWriter) methodWriter.mv;
+ }
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributesCount = 0;
+ if (innerClasses != null) {
+ ++attributesCount;
+ size += 8 + innerClasses.length;
+ symbolTable.addConstantUtf8(Constants.INNER_CLASSES);
+ }
+ if (enclosingClassIndex != 0) {
+ ++attributesCount;
+ size += 10;
+ symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD);
+ }
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
+ ++attributesCount;
+ size += 6;
+ symbolTable.addConstantUtf8(Constants.SYNTHETIC);
+ }
+ if (signatureIndex != 0) {
+ ++attributesCount;
+ size += 8;
+ symbolTable.addConstantUtf8(Constants.SIGNATURE);
+ }
+ if (sourceFileIndex != 0) {
+ ++attributesCount;
+ size += 8;
+ symbolTable.addConstantUtf8(Constants.SOURCE_FILE);
+ }
+ if (debugExtension != null) {
+ ++attributesCount;
+ size += 6 + debugExtension.length;
+ symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION);
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributesCount;
+ size += 6;
+ symbolTable.addConstantUtf8(Constants.DEPRECATED);
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeVisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (symbolTable.computeBootstrapMethodsSize() > 0) {
+ ++attributesCount;
+ size += symbolTable.computeBootstrapMethodsSize();
+ }
+ if (moduleWriter != null) {
+ attributesCount += moduleWriter.getAttributeCount();
+ size += moduleWriter.computeAttributesSize();
+ }
+ if (nestHostClassIndex != 0) {
+ ++attributesCount;
+ size += 8;
+ symbolTable.addConstantUtf8(Constants.NEST_HOST);
+ }
+ if (nestMemberClasses != null) {
+ ++attributesCount;
+ size += 8 + nestMemberClasses.length;
+ symbolTable.addConstantUtf8(Constants.NEST_MEMBERS);
+ }
+ if (firstAttribute != null) {
+ attributesCount += firstAttribute.getAttributeCount();
+ size += firstAttribute.computeAttributesSize(symbolTable);
+ }
+ // IMPORTANT: this must be the last part of the ClassFile size computation, because the previous
+ // statements can add attribute names to the constant pool, thereby changing its size!
+ size += symbolTable.getConstantPoolLength();
+ if (symbolTable.getConstantPoolCount() > 0xFFFF) {
+ throw new IndexOutOfBoundsException("Class file too large!");
+ }
+
+ // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
+ // dynamic resizes) and fill it with the ClassFile content.
+ ByteVector result = new ByteVector(size);
+ result.putInt(0xCAFEBABE).putInt(version);
+ symbolTable.putConstantPool(result);
+ int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
+ result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
+ result.putShort(interfaceCount);
+ for (int i = 0; i < interfaceCount; ++i) {
+ result.putShort(interfaces[i]);
+ }
+ result.putShort(fieldsCount);
+ fieldWriter = firstField;
+ while (fieldWriter != null) {
+ fieldWriter.putFieldInfo(result);
+ fieldWriter = (FieldWriter) fieldWriter.fv;
+ }
+ result.putShort(methodsCount);
+ boolean hasFrames = false;
+ boolean hasAsmInstructions = false;
+ methodWriter = firstMethod;
+ while (methodWriter != null) {
+ hasFrames |= methodWriter.hasFrames();
+ hasAsmInstructions |= methodWriter.hasAsmInstructions();
+ methodWriter.putMethodInfo(result);
+ methodWriter = (MethodWriter) methodWriter.mv;
+ }
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ result.putShort(attributesCount);
+ if (innerClasses != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES))
+ .putInt(innerClasses.length + 2)
+ .putShort(numberOfInnerClasses)
+ .putByteArray(innerClasses.data, 0, innerClasses.length);
+ }
+ if (enclosingClassIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD))
+ .putInt(4)
+ .putShort(enclosingClassIndex)
+ .putShort(enclosingMethodIndex);
+ }
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
+ result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
+ }
+ if (signatureIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
+ .putInt(2)
+ .putShort(signatureIndex);
+ }
+ if (sourceFileIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE))
+ .putInt(2)
+ .putShort(sourceFileIndex);
+ }
+ if (debugExtension != null) {
+ int length = debugExtension.length;
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION))
+ .putInt(length)
+ .putByteArray(debugExtension.data, 0, length);
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ lastRuntimeVisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), result);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ lastRuntimeInvisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), result);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ lastRuntimeVisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), result);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ lastRuntimeInvisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), result);
+ }
+ symbolTable.putBootstrapMethods(result);
+ if (moduleWriter != null) {
+ moduleWriter.putAttributes(result);
+ }
+ if (nestHostClassIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST))
+ .putInt(2)
+ .putShort(nestHostClassIndex);
+ }
+ if (nestMemberClasses != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS))
+ .putInt(nestMemberClasses.length + 2)
+ .putShort(numberOfNestMemberClasses)
+ .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length);
+ }
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, result);
+ }
+
+ // Third step: replace the ASM specific instructions, if any.
+ if (hasAsmInstructions) {
+ return replaceAsmInstructions(result.data, hasFrames);
+ } else {
+ return result.data;
+ }
+ }
+
+ /**
+ * Returns the equivalent of the given class file, with the ASM specific instructions replaced
+ * with standard ones. This is done with a ClassReader -> ClassWriter round trip.
+ *
+ * @param classFile a class file containing ASM specific instructions, generated by this
+ * ClassWriter.
+ * @param hasFrames whether there is at least one stack map frames in 'classFile'.
+ * @return an equivalent of 'classFile', with the ASM specific instructions replaced with standard
+ * ones.
+ */
+ private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) {
+ Attribute[] attributes = getAttributePrototypes();
+ firstField = null;
+ lastField = null;
+ firstMethod = null;
+ lastMethod = null;
+ lastRuntimeVisibleAnnotation = null;
+ lastRuntimeInvisibleAnnotation = null;
+ lastRuntimeVisibleTypeAnnotation = null;
+ lastRuntimeInvisibleTypeAnnotation = null;
+ moduleWriter = null;
+ nestHostClassIndex = 0;
+ numberOfNestMemberClasses = 0;
+ nestMemberClasses = null;
+ firstAttribute = null;
+ compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING;
+ new ClassReader(classFile, 0, /* checkClassVersion = */ false)
+ .accept(
+ this,
+ attributes,
+ (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS);
+ return toByteArray();
+ }
+
+ /**
+ * Returns the prototypes of the attributes used by this class, its fields and its methods.
+ *
+ * @return the prototypes of the attributes used by this class, its fields and its methods.
+ */
+ private Attribute[] getAttributePrototypes() {
+ Attribute.Set attributePrototypes = new Attribute.Set();
+ attributePrototypes.addAttributes(firstAttribute);
+ FieldWriter fieldWriter = firstField;
+ while (fieldWriter != null) {
+ fieldWriter.collectAttributePrototypes(attributePrototypes);
+ fieldWriter = (FieldWriter) fieldWriter.fv;
+ }
+ MethodWriter methodWriter = firstMethod;
+ while (methodWriter != null) {
+ methodWriter.collectAttributePrototypes(attributePrototypes);
+ methodWriter = (MethodWriter) methodWriter.mv;
+ }
+ return attributePrototypes.toArray();
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: constant pool management for Attribute sub classes
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a number or string constant to the constant pool of the class being build. Does nothing if
+ * the constant pool already contains a similar item. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param value the value of the constant to be added to the constant pool. This parameter must be
+ * an {@link Integer}, a {@link Float}, a {@link Long}, a {@link Double} or a {@link String}.
+ * @return the index of a new or already existing constant item with the given value.
+ */
+ public int newConst(final Object value) {
+ return symbolTable.addConstant(value).index;
+ }
+
+ /**
+ * Adds an UTF8 string to the constant pool of the class being build. Does nothing if the constant
+ * pool already contains a similar item. This method is intended for {@link Attribute} sub
+ * classes, and is normally not needed by class generators or adapters.
+ *
+ * @param value the String value.
+ * @return the index of a new or already existing UTF8 item.
+ */
+ public int newUTF8(final String value) {
+ return symbolTable.addConstantUtf8(value);
+ }
+
+ /**
+ * Adds a class reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param value the internal name of the class.
+ * @return the index of a new or already existing class reference item.
+ */
+ public int newClass(final String value) {
+ return symbolTable.addConstantClass(value).index;
+ }
+
+ /**
+ * Adds a method type reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param methodDescriptor method descriptor of the method type.
+ * @return the index of a new or already existing method type reference item.
+ */
+ public int newMethodType(final String methodDescriptor) {
+ return symbolTable.addConstantMethodType(methodDescriptor).index;
+ }
+
+ /**
+ * Adds a module reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param moduleName name of the module.
+ * @return the index of a new or already existing module reference item.
+ */
+ public int newModule(final String moduleName) {
+ return symbolTable.addConstantModule(moduleName).index;
+ }
+
+ /**
+ * Adds a package reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param packageName name of the package in its internal form.
+ * @return the index of a new or already existing module reference item.
+ */
+ public int newPackage(final String packageName) {
+ return symbolTable.addConstantPackage(packageName).index;
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool
+ * already contains a similar item. This method is intended for {@link Attribute} sub classes,
+ * and is normally not needed by class generators or adapters.
+ *
+ * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link
+ * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
+ * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the field or method owner class.
+ * @param name the name of the field or method.
+ * @param descriptor the descriptor of the field or method.
+ * @return the index of a new or already existing method type reference item.
+ * @deprecated this method is superseded by {@link #newHandle(int, String, String, String,
+ * boolean)}.
+ */
+ @Deprecated
+ public int newHandle(
+ final int tag, final String owner, final String name, final String descriptor) {
+ return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool
+ * already contains a similar item. This method is intended for {@link Attribute} sub classes,
+ * and is normally not needed by class generators or adapters.
+ *
+ * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link
+ * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
+ * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the field or method owner class.
+ * @param name the name of the field or method.
+ * @param descriptor the descriptor of the field or method.
+ * @param isInterface true if the owner is an interface.
+ * @return the index of a new or already existing method type reference item.
+ */
+ public int newHandle(
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index;
+ }
+
+ /**
+ * Adds a dynamic constant reference to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param name name of the invoked method.
+ * @param descriptor field descriptor of the constant type.
+ * @param bootstrapMethodHandle the bootstrap method.
+ * @param bootstrapMethodArguments the bootstrap method constant arguments.
+ * @return the index of a new or already existing dynamic constant reference item.
+ */
+ public int newConstantDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ return symbolTable.addConstantDynamic(
+ name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments)
+ .index;
+ }
+
+ /**
+ * Adds an invokedynamic reference to the constant pool of the class being build. Does nothing if
+ * the constant pool already contains a similar item. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param name name of the invoked method.
+ * @param descriptor descriptor of the invoke method.
+ * @param bootstrapMethodHandle the bootstrap method.
+ * @param bootstrapMethodArguments the bootstrap method constant arguments.
+ * @return the index of a new or already existing invokedynamic reference item.
+ */
+ public int newInvokeDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ return symbolTable.addConstantInvokeDynamic(
+ name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments)
+ .index;
+ }
+
+ /**
+ * Adds a field reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param owner the internal name of the field's owner class.
+ * @param name the field's name.
+ * @param descriptor the field's descriptor.
+ * @return the index of a new or already existing field reference item.
+ */
+ public int newField(final String owner, final String name, final String descriptor) {
+ return symbolTable.addConstantFieldref(owner, name, descriptor).index;
+ }
+
+ /**
+ * Adds a method reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param owner the internal name of the method's owner class.
+ * @param name the method's name.
+ * @param descriptor the method's descriptor.
+ * @param isInterface true if owner is an interface.
+ * @return the index of a new or already existing method reference item.
+ */
+ public int newMethod(
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
+ return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index;
+ }
+
+ /**
+ * Adds a name and type to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param name a name.
+ * @param descriptor a type descriptor.
+ * @return the index of a new or already existing name and type item.
+ */
+ public int newNameType(final String name, final String descriptor) {
+ return symbolTable.addConstantNameAndType(name, descriptor);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Default method to compute common super classes when computing stack map frames
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the common super type of the two given types. The default implementation of this method
+ * loads the two given classes and uses the java.lang.Class methods to find the common
+ * super class. It can be overridden to compute this common super type in other ways, in
+ * particular without actually loading any class, or to take into account the class that is
+ * currently being generated by this ClassWriter, which can of course not be loaded since it is
+ * under construction.
+ *
+ * @param type1 the internal name of a class.
+ * @param type2 the internal name of another class.
+ * @return the internal name of the common super class of the two given classes.
+ */
+ protected String getCommonSuperClass(final String type1, final String type2) {
+ ClassLoader classLoader = getClass().getClassLoader();
+ Class> class1;
+ try {
+ class1 = Class.forName(type1.replace('/', '.'), false, classLoader);
+ } catch (Exception e) {
+ throw new TypeNotPresentException(type1, e);
+ }
+ Class> class2;
+ try {
+ class2 = Class.forName(type2.replace('/', '.'), false, classLoader);
+ } catch (Exception e) {
+ throw new TypeNotPresentException(type2, e);
+ }
+ if (class1.isAssignableFrom(class2)) {
+ return type1;
+ }
+ if (class2.isAssignableFrom(class1)) {
+ return type2;
+ }
+ if (class1.isInterface() || class2.isInterface()) {
+ return "java/lang/Object";
+ } else {
+ do {
+ class1 = class1.getSuperclass();
+ } while (!class1.isAssignableFrom(class2));
+ return class1.getName().replace('.', '/');
+ }
+ }
}
diff --git a/src/jvm/clojure/asm/ConstantDynamic.java b/src/jvm/clojure/asm/ConstantDynamic.java
new file mode 100644
index 0000000000..d5306caf8a
--- /dev/null
+++ b/src/jvm/clojure/asm/ConstantDynamic.java
@@ -0,0 +1,147 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+import java.util.Arrays;
+
+/**
+ * A constant whose value is computed at runtime, with a bootstrap method.
+ *
+ * @author Remi Forax
+ * @deprecated This API is experimental.
+ */
+@Deprecated
+public final class ConstantDynamic {
+
+ /** The constant name (can be arbitrary). */
+ private final String name;
+
+ /** The constant type (must be a field descriptor). */
+ private final String descriptor;
+
+ /** The bootstrap method to use to compute the constant value at runtime. */
+ private final Handle bootstrapMethod;
+
+ /**
+ * The arguments to pass to the bootstrap method, in order to compute the constant value at
+ * runtime.
+ */
+ private final Object[] bootstrapMethodArguments;
+
+ /**
+ * Constructs a new {@link ConstantDynamic}.
+ *
+ * @param name the constant name (can be arbitrary).
+ * @param descriptor the constant type (must be a field descriptor).
+ * @param bootstrapMethod the bootstrap method to use to compute the constant value at runtime.
+ * @param bootstrapMethodArguments the arguments to pass to the bootstrap method, in order to
+ * compute the constant value at runtime.
+ */
+ public ConstantDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethod,
+ final Object... bootstrapMethodArguments) {
+ this.name = name;
+ this.descriptor = descriptor;
+ this.bootstrapMethod = bootstrapMethod;
+ this.bootstrapMethodArguments = bootstrapMethodArguments;
+ }
+
+ /**
+ * Returns the name of this constant.
+ *
+ * @return the name of this constant.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the type of this constant.
+ *
+ * @return the type of this constant, as a field descriptor.
+ */
+ public String getDescriptor() {
+ return descriptor;
+ }
+
+ /**
+ * Returns the bootstrap method used to compute the value of this constant.
+ *
+ * @return the bootstrap method used to compute the value of this constant.
+ */
+ public Handle getBootstrapMethod() {
+ return bootstrapMethod;
+ }
+
+ /**
+ * Returns the arguments to pass to the bootstrap method, in order to compute the value of this
+ * constant.
+ *
+ * @return the arguments to pass to the bootstrap method, in order to compute the value of this
+ * constant.
+ */
+ public Object[] getBootstrapMethodArguments() {
+ return bootstrapMethodArguments;
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (!(object instanceof ConstantDynamic)) {
+ return false;
+ }
+ ConstantDynamic constantDynamic = (ConstantDynamic) object;
+ return name.equals(constantDynamic.name)
+ && descriptor.equals(constantDynamic.descriptor)
+ && bootstrapMethod.equals(constantDynamic.bootstrapMethod)
+ && Arrays.equals(bootstrapMethodArguments, constantDynamic.bootstrapMethodArguments);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode()
+ ^ Integer.rotateLeft(descriptor.hashCode(), 8)
+ ^ Integer.rotateLeft(bootstrapMethod.hashCode(), 16)
+ ^ Integer.rotateLeft(Arrays.hashCode(bootstrapMethodArguments), 24);
+ }
+
+ @Override
+ public String toString() {
+ return name
+ + " : "
+ + descriptor
+ + ' '
+ + bootstrapMethod
+ + ' '
+ + Arrays.toString(bootstrapMethodArguments);
+ }
+}
diff --git a/src/jvm/clojure/asm/Constants.java b/src/jvm/clojure/asm/Constants.java
new file mode 100644
index 0000000000..38c5b87513
--- /dev/null
+++ b/src/jvm/clojure/asm/Constants.java
@@ -0,0 +1,177 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+/**
+ * Defines additional JVM opcodes, access flags and constants which are not part of the ASM public
+ * API.
+ *
+ * @see JVMS 6
+ * @author Eric Bruneton
+ */
+final class Constants implements Opcodes {
+
+ private Constants() {}
+
+ // The ClassFile attribute names, in the order they are defined in
+ // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300.
+
+ static final String CONSTANT_VALUE = "ConstantValue";
+ static final String CODE = "Code";
+ static final String STACK_MAP_TABLE = "StackMapTable";
+ static final String EXCEPTIONS = "Exceptions";
+ static final String INNER_CLASSES = "InnerClasses";
+ static final String ENCLOSING_METHOD = "EnclosingMethod";
+ static final String SYNTHETIC = "Synthetic";
+ static final String SIGNATURE = "Signature";
+ static final String SOURCE_FILE = "SourceFile";
+ static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension";
+ static final String LINE_NUMBER_TABLE = "LineNumberTable";
+ static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable";
+ static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable";
+ static final String DEPRECATED = "Deprecated";
+ static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations";
+ static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations";
+ static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations";
+ static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS =
+ "RuntimeInvisibleParameterAnnotations";
+ static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations";
+ static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations";
+ static final String ANNOTATION_DEFAULT = "AnnotationDefault";
+ static final String BOOTSTRAP_METHODS = "BootstrapMethods";
+ static final String METHOD_PARAMETERS = "MethodParameters";
+ static final String MODULE = "Module";
+ static final String MODULE_PACKAGES = "ModulePackages";
+ static final String MODULE_MAIN_CLASS = "ModuleMainClass";
+ static final String NEST_HOST = "NestHost";
+ static final String NEST_MEMBERS = "NestMembers";
+
+ // ASM specific access flags.
+ // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard
+ // access flags, and also to make sure that these flags are automatically filtered out when
+ // written in class files (because access flags are stored using 16 bits only).
+
+ static final int ACC_CONSTRUCTOR = 0x40000; // method access flag.
+
+ // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}.
+
+ /**
+ * A frame inserted between already existing frames. This internal stack map frame type (in
+ * addition to the ones declared in {@link Opcodes}) can only be used if the frame content can be
+ * computed from the previous existing frame and from the instructions between this existing frame
+ * and the inserted one, without any knowledge of the type hierarchy. This kind of frame is only
+ * used when an unconditional jump is inserted in a method while expanding an ASM specific
+ * instruction. Keep in sync with Opcodes.java.
+ */
+ static final int F_INSERT = 256;
+
+ // The JVM opcode values which are not part of the ASM public API.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html.
+
+ static final int LDC_W = 19;
+ static final int LDC2_W = 20;
+ static final int ILOAD_0 = 26;
+ static final int ILOAD_1 = 27;
+ static final int ILOAD_2 = 28;
+ static final int ILOAD_3 = 29;
+ static final int LLOAD_0 = 30;
+ static final int LLOAD_1 = 31;
+ static final int LLOAD_2 = 32;
+ static final int LLOAD_3 = 33;
+ static final int FLOAD_0 = 34;
+ static final int FLOAD_1 = 35;
+ static final int FLOAD_2 = 36;
+ static final int FLOAD_3 = 37;
+ static final int DLOAD_0 = 38;
+ static final int DLOAD_1 = 39;
+ static final int DLOAD_2 = 40;
+ static final int DLOAD_3 = 41;
+ static final int ALOAD_0 = 42;
+ static final int ALOAD_1 = 43;
+ static final int ALOAD_2 = 44;
+ static final int ALOAD_3 = 45;
+ static final int ISTORE_0 = 59;
+ static final int ISTORE_1 = 60;
+ static final int ISTORE_2 = 61;
+ static final int ISTORE_3 = 62;
+ static final int LSTORE_0 = 63;
+ static final int LSTORE_1 = 64;
+ static final int LSTORE_2 = 65;
+ static final int LSTORE_3 = 66;
+ static final int FSTORE_0 = 67;
+ static final int FSTORE_1 = 68;
+ static final int FSTORE_2 = 69;
+ static final int FSTORE_3 = 70;
+ static final int DSTORE_0 = 71;
+ static final int DSTORE_1 = 72;
+ static final int DSTORE_2 = 73;
+ static final int DSTORE_3 = 74;
+ static final int ASTORE_0 = 75;
+ static final int ASTORE_1 = 76;
+ static final int ASTORE_2 = 77;
+ static final int ASTORE_3 = 78;
+ static final int WIDE = 196;
+ static final int GOTO_W = 200;
+ static final int JSR_W = 201;
+
+ // Constants to convert between normal and wide jump instructions.
+
+ // The delta between the GOTO_W and JSR_W opcodes and GOTO and JUMP.
+ static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - GOTO;
+
+ // Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa.
+
+ // The delta between the ASM_IFEQ, ..., ASM_IF_ACMPNE, ASM_GOTO and ASM_JSR opcodes
+ // and IFEQ, ..., IF_ACMPNE, GOTO and JSR.
+ static final int ASM_OPCODE_DELTA = 49;
+
+ // The delta between the ASM_IFNULL and ASM_IFNONNULL opcodes and IFNULL and IFNONNULL.
+ static final int ASM_IFNULL_OPCODE_DELTA = 20;
+
+ // ASM specific opcodes, used for long forward jump instructions.
+
+ static final int ASM_IFEQ = IFEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IFNE = IFNE + ASM_OPCODE_DELTA;
+ static final int ASM_IFLT = IFLT + ASM_OPCODE_DELTA;
+ static final int ASM_IFGE = IFGE + ASM_OPCODE_DELTA;
+ static final int ASM_IFGT = IFGT + ASM_OPCODE_DELTA;
+ static final int ASM_IFLE = IFLE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPEQ = IF_ICMPEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPNE = IF_ICMPNE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPLT = IF_ICMPLT + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPGE = IF_ICMPGE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPGT = IF_ICMPGT + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPLE = IF_ICMPLE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ACMPEQ = IF_ACMPEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ACMPNE = IF_ACMPNE + ASM_OPCODE_DELTA;
+ static final int ASM_GOTO = GOTO + ASM_OPCODE_DELTA;
+ static final int ASM_JSR = JSR + ASM_OPCODE_DELTA;
+ static final int ASM_IFNULL = IFNULL + ASM_IFNULL_OPCODE_DELTA;
+ static final int ASM_IFNONNULL = IFNONNULL + ASM_IFNULL_OPCODE_DELTA;
+ static final int ASM_GOTO_W = 220;
+}
diff --git a/src/jvm/clojure/asm/Context.java b/src/jvm/clojure/asm/Context.java
index d113ed96a9..5af8943ed7 100644
--- a/src/jvm/clojure/asm/Context.java
+++ b/src/jvm/clojure/asm/Context.java
@@ -1,32 +1,30 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
@@ -35,76 +33,105 @@
*
* @author Eric Bruneton
*/
-class Context {
-
- /**
- * Prototypes of the attributes that must be parsed for this class.
- */
- Attribute[] attrs;
-
- /**
- * The {@link ClassReader} option flags for the parsing of this class.
- */
- int flags;
-
- /**
- * The buffer used to read strings.
- */
- char[] buffer;
-
- /**
- * The start index of each bootstrap method.
- */
- int[] bootstrapMethods;
-
- /**
- * The access flags of the method currently being parsed.
- */
- int access;
-
- /**
- * The name of the method currently being parsed.
- */
- String name;
-
- /**
- * The descriptor of the method currently being parsed.
- */
- String desc;
-
- /**
- * The offset of the latest stack map frame that has been parsed.
- */
- int offset;
-
- /**
- * The encoding of the latest stack map frame that has been parsed.
- */
- int mode;
-
- /**
- * The number of locals in the latest stack map frame that has been parsed.
- */
- int localCount;
-
- /**
- * The number locals in the latest stack map frame that has been parsed,
- * minus the number of locals in the previous frame.
- */
- int localDiff;
-
- /**
- * The local values of the latest stack map frame that has been parsed.
- */
- Object[] local;
-
- /**
- * The stack size of the latest stack map frame that has been parsed.
- */
- int stackCount;
-
- /**
- * The stack values of the latest stack map frame that has been parsed.
- */
- Object[] stack;
-}
\ No newline at end of file
+final class Context {
+
+ /** The prototypes of the attributes that must be parsed in this class. */
+ Attribute[] attributePrototypes;
+
+ /**
+ * The options used to parse this class. One or more of {@link ClassReader#SKIP_CODE}, {@link
+ * ClassReader#SKIP_DEBUG}, {@link ClassReader#SKIP_FRAMES}, {@link ClassReader#EXPAND_FRAMES} or
+ * {@link ClassReader#EXPAND_ASM_INSNS}.
+ */
+ int parsingOptions;
+
+ /** The buffer used to read strings in the constant pool. */
+ char[] charBuffer;
+
+ // Information about the current method, i.e. the one read in the current (or latest) call
+ // to {@link ClassReader#readMethod()}.
+
+ /** The access flags of the current method. */
+ int currentMethodAccessFlags;
+
+ /** The name of the current method. */
+ String currentMethodName;
+
+ /** The descriptor of the current method. */
+ String currentMethodDescriptor;
+
+ /**
+ * The labels of the current method, indexed by bytecode offset (only bytecode offsets for which a
+ * label is needed have a non null associated Label).
+ */
+ Label[] currentMethodLabels;
+
+ // Information about the current type annotation target, i.e. the one read in the current
+ // (or latest) call to {@link ClassReader#readAnnotationTarget()}.
+
+ /**
+ * The target_type and target_info of the current type annotation target, encoded as described in
+ * {@link TypeReference}.
+ */
+ int currentTypeAnnotationTarget;
+
+ /** The target_path of the current type annotation target. */
+ TypePath currentTypeAnnotationTargetPath;
+
+ /** The start of each local variable range in the current local variable annotation. */
+ Label[] currentLocalVariableAnnotationRangeStarts;
+
+ /** The end of each local variable range in the current local variable annotation. */
+ Label[] currentLocalVariableAnnotationRangeEnds;
+
+ /**
+ * The local variable index of each local variable range in the current local variable annotation.
+ */
+ int[] currentLocalVariableAnnotationRangeIndices;
+
+ // Information about the current stack map frame, i.e. the one read in the current (or latest)
+ // call to {@link ClassReader#readFrame()}.
+
+ /** The bytecode offset of the current stack map frame. */
+ int currentFrameOffset;
+
+ /**
+ * The type of the current stack map frame. One of {@link Opcodes#F_FULL}, {@link
+ * Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or {@link Opcodes#F_SAME1}.
+ */
+ int currentFrameType;
+
+ /**
+ * The number of local variable types in the current stack map frame. Each type is represented
+ * with a single array element (even long and double).
+ */
+ int currentFrameLocalCount;
+
+ /**
+ * The delta number of local variable types in the current stack map frame (each type is
+ * represented with a single array element - even long and double). This is the number of local
+ * variable types in this frame, minus the number of local variable types in the previous frame.
+ */
+ int currentFrameLocalCountDelta;
+
+ /**
+ * The types of the local variables in the current stack map frame. Each type is represented with
+ * a single array element (even long and double), using the format described in {@link
+ * MethodVisitor#visitFrame}. Depending on {@link #currentFrameType}, this contains the types of
+ * all the local variables, or only those of the additional ones (compared to the previous frame).
+ */
+ Object[] currentFrameLocalTypes;
+
+ /**
+ * The number stack element types in the current stack map frame. Each type is represented with a
+ * single array element (even long and double).
+ */
+ int currentFrameStackCount;
+
+ /**
+ * The types of the stack elements in the current stack map frame. Each type is represented with a
+ * single array element (even long and double), using the format described in {@link
+ * MethodVisitor#visitFrame}.
+ */
+ Object[] currentFrameStackTypes;
+}
diff --git a/src/jvm/clojure/asm/CurrentFrame.java b/src/jvm/clojure/asm/CurrentFrame.java
new file mode 100644
index 0000000000..f37b51cfe8
--- /dev/null
+++ b/src/jvm/clojure/asm/CurrentFrame.java
@@ -0,0 +1,56 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+package clojure.asm;
+
+/**
+ * Information about the input stack map frame at the "current" instruction of a method. This is
+ * implemented as a Frame subclass for a "basic block" containing only one instruction.
+ *
+ * @author Eric Bruneton
+ */
+final class CurrentFrame extends Frame {
+
+ CurrentFrame(final Label owner) {
+ super(owner);
+ }
+
+ /**
+ * Sets this CurrentFrame to the input stack map frame of the next "current" instruction, i.e. the
+ * instruction just after the given one. It is assumed that the value of this object when this
+ * method is called is the stack map frame status just before the given instruction is executed.
+ */
+ @Override
+ void execute(
+ final int opcode, final int arg, final Symbol symbolArg, final SymbolTable symbolTable) {
+ super.execute(opcode, arg, symbolArg, symbolTable);
+ Frame successor = new Frame(null);
+ merge(symbolTable, successor, 0);
+ copyFrom(successor);
+ }
+}
diff --git a/src/jvm/clojure/asm/Edge.java b/src/jvm/clojure/asm/Edge.java
index 045cd46fc1..f9bdadf90b 100644
--- a/src/jvm/clojure/asm/Edge.java
+++ b/src/jvm/clojure/asm/Edge.java
@@ -1,75 +1,91 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * An edge in the control flow graph of a method body. See {@link Label Label}.
+ * An edge in the control flow graph of a method. Each node of this graph is a basic block,
+ * represented with the Label corresponding to its first instruction. Each edge goes from one node
+ * to another, i.e. from one basic block to another (called the predecessor and successor blocks,
+ * respectively). An edge corresponds either to a jump or ret instruction or to an exception
+ * handler.
*
+ * @see Label
* @author Eric Bruneton
*/
-class Edge {
+final class Edge {
+
+ /**
+ * A control flow graph edge corresponding to a jump or ret instruction. Only used with {@link
+ * ClassWriter#COMPUTE_FRAMES}.
+ */
+ static final int JUMP = 0;
- /**
- * Denotes a normal control flow graph edge.
- */
- static final int NORMAL = 0;
+ /**
+ * A control flow graph edge corresponding to an exception handler. Only used with {@link
+ * ClassWriter#COMPUTE_MAXS}.
+ */
+ static final int EXCEPTION = 0x7FFFFFFF;
- /**
- * Denotes a control flow graph edge corresponding to an exception handler.
- * More precisely any {@link Edge} whose {@link #info} is strictly positive
- * corresponds to an exception handler. The actual value of {@link #info} is
- * the index, in the {@link ClassWriter} type table, of the exception that
- * is catched.
- */
- static final int EXCEPTION = 0x7FFFFFFF;
+ /**
+ * Information about this control flow graph edge.
+ *
+ *
+ *
If {@link ClassWriter#COMPUTE_MAXS} is used, this field contains either a stack size
+ * delta (for an edge corresponding to a jump instruction), or the value EXCEPTION (for an
+ * edge corresponding to an exception handler). The stack size delta is the stack size just
+ * after the jump instruction, minus the stack size at the beginning of the predecessor
+ * basic block, i.e. the one containing the jump instruction.
+ *
If {@link ClassWriter#COMPUTE_FRAMES} is used, this field contains either the value JUMP
+ * (for an edge corresponding to a jump instruction), or the index, in the {@link
+ * ClassWriter} type table, of the exception type that is handled (for an edge corresponding
+ * to an exception handler).
+ *
+ */
+ final int info;
- /**
- * Information about this control flow graph edge. If
- * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative)
- * stack size in the basic block from which this edge originates. This size
- * is equal to the stack size at the "jump" instruction to which this edge
- * corresponds, relatively to the stack size at the beginning of the
- * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used,
- * this field is the kind of this control flow graph edge (i.e. NORMAL or
- * EXCEPTION).
- */
- int info;
+ /** The successor block of this control flow graph edge. */
+ final Label successor;
- /**
- * The successor block of the basic block from which this edge originates.
- */
- Label successor;
+ /**
+ * The next edge in the list of outgoing edges of a basic block. See {@link Label#outgoingEdges}.
+ */
+ Edge nextEdge;
- /**
- * The next edge in the list of successors of the originating basic block.
- * See {@link Label#successors successors}.
- */
- Edge next;
+ /**
+ * Constructs a new Edge.
+ *
+ * @param info see {@link #info}.
+ * @param successor see {@link #successor}.
+ * @param nextEdge see {@link #nextEdge}.
+ */
+ Edge(final int info, final Label successor, final Edge nextEdge) {
+ this.info = info;
+ this.successor = successor;
+ this.nextEdge = nextEdge;
+ }
}
diff --git a/src/jvm/clojure/asm/FieldVisitor.java b/src/jvm/clojure/asm/FieldVisitor.java
index b9df7385fe..f2f0e2ce8d 100644
--- a/src/jvm/clojure/asm/FieldVisitor.java
+++ b/src/jvm/clojure/asm/FieldVisitor.java
@@ -1,121 +1,138 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A visitor to visit a Java field. The methods of this class must be called in
- * the following order: ( visitAnnotation | visitAttribute )*
+ * A visitor to visit a Java field. The methods of this class must be called in the following order:
+ * ( visitAnnotation | visitTypeAnnotation | visitAttribute )*
* visitEnd.
*
* @author Eric Bruneton
*/
public abstract class FieldVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ protected final int api;
- /**
- * The field visitor to which this visitor must delegate method calls. May
- * be null.
- */
- protected FieldVisitor fv;
+ /** The field visitor to which this visitor must delegate method calls. May be null. */
+ protected FieldVisitor fv;
- /**
- * Constructs a new {@link FieldVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public FieldVisitor(final int api) {
- this(api, null);
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ public FieldVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be
+ * null.
+ */
+ public FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
+ if (api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM7_EXPERIMENTAL) {
+ throw new IllegalArgumentException();
}
+ this.api = api;
+ this.fv = fieldVisitor;
+ }
- /**
- * Constructs a new {@link FieldVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param fv
- * the field visitor to which this visitor must delegate method
- * calls. May be null.
- */
- public FieldVisitor(final int api, final FieldVisitor fv) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.fv = fv;
+ /**
+ * Visits an annotation of the field.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (fv != null) {
+ return fv.visitAnnotation(descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits an annotation of the field.
- *
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (fv != null) {
- return fv.visitAnnotation(desc, visible);
- }
- return null;
+ /**
+ * Visits an annotation on the type of the field.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#FIELD}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be null if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException();
+ }
+ if (fv != null) {
+ return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits a non standard attribute of the field.
- *
- * @param attr
- * an attribute.
- */
- public void visitAttribute(Attribute attr) {
- if (fv != null) {
- fv.visitAttribute(attr);
- }
+ /**
+ * Visits a non standard attribute of the field.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (fv != null) {
+ fv.visitAttribute(attribute);
}
+ }
- /**
- * Visits the end of the field. This method, which is the last one to be
- * called, is used to inform the visitor that all the annotations and
- * attributes of the field have been visited.
- */
- public void visitEnd() {
- if (fv != null) {
- fv.visitEnd();
- }
+ /**
+ * Visits the end of the field. This method, which is the last one to be called, is used to inform
+ * the visitor that all the annotations and attributes of the field have been visited.
+ */
+ public void visitEnd() {
+ if (fv != null) {
+ fv.visitEnd();
}
+ }
}
diff --git a/src/jvm/clojure/asm/FieldWriter.java b/src/jvm/clojure/asm/FieldWriter.java
index ccd32ec0a8..ed6449bf69 100644
--- a/src/jvm/clojure/asm/FieldWriter.java
+++ b/src/jvm/clojure/asm/FieldWriter.java
@@ -1,273 +1,346 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * An {@link FieldVisitor} that generates Java fields in bytecode form.
+ * A {@link FieldVisitor} that generates a corresponding 'field_info' structure, as defined in the
+ * Java Virtual Machine Specification (JVMS).
*
+ * @see JVMS
+ * 4.5
* @author Eric Bruneton
*/
final class FieldWriter extends FieldVisitor {
- /**
- * The class writer to which this field must be added.
- */
- private final ClassWriter cw;
+ /** Where the constants used in this FieldWriter must be stored. */
+ private final SymbolTable symbolTable;
- /**
- * Access flags of this field.
- */
- private final int access;
+ // Note: fields are ordered as in the field_info structure, and those related to attributes are
+ // ordered as in Section 4.7 of the JVMS.
- /**
- * The index of the constant pool item that contains the name of this
- * method.
- */
- private final int name;
+ /**
+ * The access_flags field of the field_info JVMS structure. This field can contain ASM specific
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
+ * ClassFile structure.
+ */
+ private final int accessFlags;
- /**
- * The index of the constant pool item that contains the descriptor of this
- * field.
- */
- private final int desc;
+ /** The name_index field of the field_info JVMS structure. */
+ private final int nameIndex;
- /**
- * The index of the constant pool item that contains the signature of this
- * field.
- */
- private int signature;
+ /** The descriptor_index field of the field_info JVMS structure. */
+ private final int descriptorIndex;
- /**
- * The index of the constant pool item that contains the constant value of
- * this field.
- */
- private int value;
+ /**
+ * The signature_index field of the Signature attribute of this field_info, or 0 if there is no
+ * Signature attribute.
+ */
+ private int signatureIndex;
- /**
- * The runtime visible annotations of this field. May be null.
- */
- private AnnotationWriter anns;
+ /**
+ * The constantvalue_index field of the ConstantValue attribute of this field_info, or 0 if there
+ * is no ConstantValue attribute.
+ */
+ private int constantValueIndex;
- /**
- * The runtime invisible annotations of this field. May be null.
- */
- private AnnotationWriter ianns;
+ /**
+ * The last runtime visible annotation of this field. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
- /**
- * The non standard attributes of this field. May be null.
- */
- private Attribute attrs;
+ /**
+ * The last runtime invisible annotation of this field. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
+ /**
+ * The last runtime visible type annotation of this field. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
- /**
- * Constructs a new {@link FieldWriter}.
- *
- * @param cw
- * the class writer to which this field must be added.
- * @param access
- * the field's access flags (see {@link Opcodes}).
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor (see {@link Type}).
- * @param signature
- * the field's signature. May be null.
- * @param value
- * the field's constant value. May be null.
- */
- FieldWriter(final ClassWriter cw, final int access, final String name,
- final String desc, final String signature, final Object value) {
- super(Opcodes.ASM4);
- if (cw.firstField == null) {
- cw.firstField = this;
- } else {
- cw.lastField.fv = this;
- }
- cw.lastField = this;
- this.cw = cw;
- this.access = access;
- this.name = cw.newUTF8(name);
- this.desc = cw.newUTF8(desc);
- if (ClassReader.SIGNATURES && signature != null) {
- this.signature = cw.newUTF8(signature);
- }
- if (value != null) {
- this.value = cw.newConstItem(value).index;
- }
- }
+ /**
+ * The last runtime invisible type annotation of this field. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /**
+ * The first non standard attribute of this field. The next ones can be accessed with the {@link
+ * Attribute#nextAttribute} field. May be null.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #putFieldInfo} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
- // ------------------------------------------------------------------------
- // Implementation of the FieldVisitor abstract class
- // ------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------------------------
+ // Constructor
+ // -----------------------------------------------------------------------------------------------
- @Override
- public AnnotationVisitor visitAnnotation(final String desc,
- final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- // write type, and reserve space for values count
- bv.putShort(cw.newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
- if (visible) {
- aw.next = anns;
- anns = aw;
- } else {
- aw.next = ianns;
- ianns = aw;
- }
- return aw;
+ /**
+ * Constructs a new {@link FieldWriter}.
+ *
+ * @param symbolTable where the constants used in this FieldWriter must be stored.
+ * @param access the field's access flags (see {@link Opcodes}).
+ * @param name the field's name.
+ * @param descriptor the field's descriptor (see {@link Type}).
+ * @param signature the field's signature. May be null.
+ * @param constantValue the field's constant value. May be null.
+ */
+ FieldWriter(
+ final SymbolTable symbolTable,
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Object constantValue) {
+ super(Opcodes.ASM6);
+ this.symbolTable = symbolTable;
+ this.accessFlags = access;
+ this.nameIndex = symbolTable.addConstantUtf8(name);
+ this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
+ if (signature != null) {
+ this.signatureIndex = symbolTable.addConstantUtf8(signature);
}
+ if (constantValue != null) {
+ this.constantValueIndex = symbolTable.addConstant(constantValue).index;
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the FieldVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
- @Override
- public void visitAttribute(final Attribute attr) {
- attr.next = attrs;
- attrs = attr;
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold an 'annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
+ ByteVector annotation = new ByteVector();
+ // Write type_index and reserve space for num_element_value_pairs.
+ annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
}
+ }
- @Override
- public void visitEnd() {
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ TypeReference.putTarget(typeRef, typeAnnotation);
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
}
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
- // ------------------------------------------------------------------------
- // Utility methods
- // ------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
- /**
- * Returns the size of this field.
- *
- * @return the size of this field.
- */
- int getSize() {
- int size = 8;
- if (value != 0) {
- cw.newUTF8("ConstantValue");
- size += 8;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- cw.newUTF8("Synthetic");
- size += 6;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- cw.newUTF8("Deprecated");
- size += 6;
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- cw.newUTF8("Signature");
- size += 8;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- cw.newUTF8("RuntimeVisibleAnnotations");
- size += 8 + anns.getSize();
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- cw.newUTF8("RuntimeInvisibleAnnotations");
- size += 8 + ianns.getSize();
- }
- if (attrs != null) {
- size += attrs.getSize(cw, null, 0, -1, -1);
- }
- return size;
+ /**
+ * Returns the size of the field_info JVMS structure generated by this FieldWriter. Also adds the
+ * names of the attributes of this field in the constant pool.
+ *
+ * @return the size in bytes of the field_info JVMS structure.
+ */
+ int computeFieldInfoSize() {
+ // The access_flags, name_index, descriptor_index and attributes_count fields use 8 bytes.
+ int size = 8;
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ if (constantValueIndex != 0) {
+ // ConstantValue attributes always use 8 bytes.
+ symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE);
+ size += 8;
}
+ // Before Java 1.5, synthetic fields are represented with a Synthetic attribute.
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0
+ && symbolTable.getMajorVersion() < Opcodes.V1_5) {
+ // Synthetic attributes always use 6 bytes.
+ symbolTable.addConstantUtf8(Constants.SYNTHETIC);
+ size += 6;
+ }
+ if (signatureIndex != 0) {
+ // Signature attributes always use 8 bytes.
+ symbolTable.addConstantUtf8(Constants.SIGNATURE);
+ size += 8;
+ }
+ // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead.
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ // Deprecated attributes always use 6 bytes.
+ symbolTable.addConstantUtf8(Constants.DEPRECATED);
+ size += 6;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ size +=
+ lastRuntimeVisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (firstAttribute != null) {
+ size += firstAttribute.computeAttributesSize(symbolTable);
+ }
+ return size;
+ }
- /**
- * Puts the content of this field into the given byte vector.
- *
- * @param out
- * where the content of this field must be put.
- */
- void put(final ByteVector out) {
- final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
- int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
- | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
- out.putShort(access & ~mask).putShort(name).putShort(desc);
- int attributeCount = 0;
- if (value != 0) {
- ++attributeCount;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- ++attributeCount;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- ++attributeCount;
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- ++attributeCount;
- }
- if (attrs != null) {
- attributeCount += attrs.getCount();
- }
- out.putShort(attributeCount);
- if (value != 0) {
- out.putShort(cw.newUTF8("ConstantValue"));
- out.putInt(2).putShort(value);
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- out.putShort(cw.newUTF8("Synthetic")).putInt(0);
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- out.putShort(cw.newUTF8("Deprecated")).putInt(0);
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- out.putShort(cw.newUTF8("Signature"));
- out.putInt(2).putShort(signature);
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
- anns.put(out);
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
- ianns.put(out);
- }
- if (attrs != null) {
- attrs.put(cw, null, 0, -1, -1, out);
- }
+ /**
+ * Puts the content of the field_info JVMS structure generated by this FieldWriter into the given
+ * ByteVector.
+ *
+ * @param output where the field_info structure must be put.
+ */
+ void putFieldInfo(final ByteVector output) {
+ boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
+ // Put the access_flags, name_index and descriptor_index fields.
+ int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
+ output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
+ // Compute and put the attributes_count field.
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributesCount = 0;
+ if (constantValueIndex != 0) {
+ ++attributesCount;
+ }
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
+ ++attributesCount;
}
+ if (signatureIndex != 0) {
+ ++attributesCount;
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (firstAttribute != null) {
+ attributesCount += firstAttribute.getAttributeCount();
+ }
+ output.putShort(attributesCount);
+ // Put the field_info attributes.
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ if (constantValueIndex != 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE))
+ .putInt(2)
+ .putShort(constantValueIndex);
+ }
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
+ }
+ if (signatureIndex != 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
+ .putInt(2)
+ .putShort(signatureIndex);
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ lastRuntimeVisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ lastRuntimeInvisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ lastRuntimeVisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ lastRuntimeInvisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, output);
+ }
+ }
+
+ /**
+ * Collects the attributes of this field into the given set of attribute prototypes.
+ *
+ * @param attributePrototypes a set of attribute prototypes.
+ */
+ final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
+ attributePrototypes.addAttributes(firstAttribute);
+ }
}
diff --git a/src/jvm/clojure/asm/Frame.java b/src/jvm/clojure/asm/Frame.java
index 9c7e61dc8d..2c67f8cce2 100644
--- a/src/jvm/clojure/asm/Frame.java
+++ b/src/jvm/clojure/asm/Frame.java
@@ -1,1453 +1,1467 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * Information about the input and output stack map frames of a basic block.
+ * The input and output stack map frames of a basic block.
+ *
+ *
Stack map frames are computed in two steps:
+ *
+ *
+ *
During the visit of each instruction in MethodWriter, the state of the frame at the end of
+ * the current basic block is updated by simulating the action of the instruction on the
+ * previous state of this so called "output frame".
+ *
After all instructions have been visited, a fix point algorithm is used in MethodWriter to
+ * compute the "input frame" of each basic block (i.e. the stack map frame at the beginning of
+ * the basic block). See {@link MethodWriter#computeAllFrames}.
+ *
+ *
+ *
Output stack map frames are computed relatively to the input frame of the basic block, which
+ * is not yet known when output frames are computed. It is therefore necessary to be able to
+ * represent abstract types such as "the type at position x in the input frame locals" or "the type
+ * at position x from the top of the input frame stack" or even "the type at position x in the input
+ * frame, with y more (or less) array dimensions". This explains the rather complicated type format
+ * used in this class, explained below.
+ *
+ *
The local variables and the operand stack of input and output frames contain values called
+ * "abstract types" hereafter. An abstract type is represented with 4 fields named DIM, KIND, FLAGS
+ * and VALUE, packed in a single int value for better performance and memory efficiency:
+ *
+ *
the DIM field, stored in the 4 most significant bits, is a signed number of array
+ * dimensions (from -8 to 7, included). It can be retrieved with {@link #DIM_MASK} and a right
+ * shift of {@link #DIM_SHIFT}.
+ *
the KIND field, stored in 4 bits, indicates the kind of VALUE used. These 4 bits can be
+ * retrieved with {@link #KIND_MASK} and, without any shift, must be equal to {@link
+ * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link #LOCAL_KIND}
+ * or {@link #STACK_KIND}.
+ *
the FLAGS field, stored in 4 bits, contains up to 4 boolean flags. Currently only one flag
+ * is defined, namely {@link #TOP_IF_LONG_OR_DOUBLE_FLAG}.
+ *
the VALUE field, stored in the remaining 20 bits, contains either
+ *
+ *
one of the constants {@link #ITEM_TOP}, {@link #ITEM_ASM_BOOLEAN}, {@link
+ * #ITEM_ASM_BYTE}, {@link #ITEM_ASM_CHAR} or {@link #ITEM_ASM_SHORT}, {@link
+ * #ITEM_INTEGER}, {@link #ITEM_FLOAT}, {@link #ITEM_LONG}, {@link #ITEM_DOUBLE}, {@link
+ * #ITEM_NULL} or {@link #ITEM_UNINITIALIZED_THIS}, if KIND is equal to {@link
+ * #CONSTANT_KIND}.
+ *
the index of a {@link Symbol#TYPE_TAG} {@link Symbol} in the type table of a {@link
+ * SymbolTable}, if KIND is equal to {@link #REFERENCE_KIND}.
+ *
the index of an {@link Symbol#UNINITIALIZED_TYPE_TAG} {@link Symbol} in the type
+ * table of a SymbolTable, if KIND is equal to {@link #UNINITIALIZED_KIND}.
+ *
the index of a local variable in the input stack frame, if KIND is equal to {@link
+ * #LOCAL_KIND}.
+ *
a position relatively to the top of the stack of the input stack frame, if KIND is
+ * equal to {@link #STACK_KIND},
+ *
+ *
+ *
+ *
Output frames can contain abstract types of any kind and with a positive or negative array
+ * dimension (and even unassigned types, represented by 0 - which does not correspond to any valid
+ * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND or
+ * UNINITIALIZED_KIND abstract types of positive or null array dimension. In all cases the type
+ * table contains only internal type names (array type descriptors are forbidden - array dimensions
+ * must be represented through the DIM field).
+ *
+ *
The LONG and DOUBLE types are always represented by using two slots (LONG + TOP or DOUBLE +
+ * TOP), for local variables as well as in the operand stack. This is necessary to be able to
+ * simulate DUPx_y instructions, whose effect would be dependent on the concrete types represented
+ * by the abstract types in the stack (which are not always known).
*
* @author Eric Bruneton
*/
-final class Frame {
+class Frame {
- /*
- * Frames are computed in a two steps process: during the visit of each
- * instruction, the state of the frame at the end of current basic block is
- * updated by simulating the action of the instruction on the previous state
- * of this so called "output frame". In visitMaxs, a fix point algorithm is
- * used to compute the "input frame" of each basic block, i.e. the stack map
- * frame at the beginning of the basic block, starting from the input frame
- * of the first basic block (which is computed from the method descriptor),
- * and by using the previously computed output frames to compute the input
- * state of the other blocks.
- *
- * All output and input frames are stored as arrays of integers. Reference
- * and array types are represented by an index into a type table (which is
- * not the same as the constant pool of the class, in order to avoid adding
- * unnecessary constants in the pool - not all computed frames will end up
- * being stored in the stack map table). This allows very fast type
- * comparisons.
- *
- * Output stack map frames are computed relatively to the input frame of the
- * basic block, which is not yet known when output frames are computed. It
- * is therefore necessary to be able to represent abstract types such as
- * "the type at position x in the input frame locals" or "the type at
- * position x from the top of the input frame stack" or even "the type at
- * position x in the input frame, with y more (or less) array dimensions".
- * This explains the rather complicated type format used in output frames.
- *
- * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a
- * signed number of array dimensions (from -8 to 7). KIND is either BASE,
- * LOCAL or STACK. BASE is used for types that are not relative to the input
- * frame. LOCAL is used for types that are relative to the input local
- * variable types. STACK is used for types that are relative to the input
- * stack types. VALUE depends on KIND. For LOCAL types, it is an index in
- * the input local variable types. For STACK types, it is a position
- * relatively to the top of input frame stack. For BASE types, it is either
- * one of the constants defined in FrameVisitor, or for OBJECT and
- * UNINITIALIZED types, a tag and an index in the type table.
- *
- * Output frames can contain types of any kind and with a positive or
- * negative dimension (and even unassigned types, represented by 0 - which
- * does not correspond to any valid type value). Input frames can only
- * contain BASE types of positive or null dimension. In all cases the type
- * table contains only internal type names (array type descriptors are
- * forbidden - dimensions must be represented through the DIM field).
- *
- * The LONG and DOUBLE types are always represented by using two slots (LONG
- * + TOP or DOUBLE + TOP), for local variable types as well as in the
- * operand stack. This is necessary to be able to simulate DUPx_y
- * instructions, whose effect would be dependent on the actual type values
- * if types were always represented by a single slot in the stack (and this
- * is not possible, since actual type values are not always known - cf LOCAL
- * and STACK type kinds).
- */
+ // Constants used in the StackMapTable attribute.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.4.
- /**
- * Mask to get the dimension of a frame type. This dimension is a signed
- * integer between -8 and 7.
- */
- static final int DIM = 0xF0000000;
+ static final int SAME_FRAME = 0;
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64;
+ static final int RESERVED = 128;
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247;
+ static final int CHOP_FRAME = 248;
+ static final int SAME_FRAME_EXTENDED = 251;
+ static final int APPEND_FRAME = 252;
+ static final int FULL_FRAME = 255;
- /**
- * Constant to be added to a type to get a type with one more dimension.
- */
- static final int ARRAY_OF = 0x10000000;
+ static final int ITEM_TOP = 0;
+ static final int ITEM_INTEGER = 1;
+ static final int ITEM_FLOAT = 2;
+ static final int ITEM_DOUBLE = 3;
+ static final int ITEM_LONG = 4;
+ static final int ITEM_NULL = 5;
+ static final int ITEM_UNINITIALIZED_THIS = 6;
+ static final int ITEM_OBJECT = 7;
+ static final int ITEM_UNINITIALIZED = 8;
+ // Additional, ASM specific constants used in abstract types below.
+ private static final int ITEM_ASM_BOOLEAN = 9;
+ private static final int ITEM_ASM_BYTE = 10;
+ private static final int ITEM_ASM_CHAR = 11;
+ private static final int ITEM_ASM_SHORT = 12;
- /**
- * Constant to be added to a type to get a type with one less dimension.
- */
- static final int ELEMENT_OF = 0xF0000000;
+ // Bitmasks to get each field of an abstract type.
- /**
- * Mask to get the kind of a frame type.
- *
- * @see #BASE
- * @see #LOCAL
- * @see #STACK
- */
- static final int KIND = 0xF000000;
+ private static final int DIM_MASK = 0xF0000000;
+ private static final int KIND_MASK = 0x0F000000;
+ private static final int FLAGS_MASK = 0x00F00000;
+ private static final int VALUE_MASK = 0x000FFFFF;
- /**
- * Flag used for LOCAL and STACK types. Indicates that if this type happens
- * to be a long or double type (during the computations of input frames),
- * then it must be set to TOP because the second word of this value has been
- * reused to store other data in the basic block. Hence the first word no
- * longer stores a valid long or double value.
- */
- static final int TOP_IF_LONG_OR_DOUBLE = 0x800000;
+ // Constants to manipulate the DIM field of an abstract type.
- /**
- * Mask to get the value of a frame type.
- */
- static final int VALUE = 0x7FFFFF;
+ /** The number of right shift bits to use to get the array dimensions of an abstract type. */
+ private static final int DIM_SHIFT = 28;
- /**
- * Mask to get the kind of base types.
- */
- static final int BASE_KIND = 0xFF00000;
+ /** The constant to be added to an abstract type to get one with one more array dimension. */
+ private static final int ARRAY_OF = +1 << DIM_SHIFT;
- /**
- * Mask to get the value of base types.
- */
- static final int BASE_VALUE = 0xFFFFF;
+ /** The constant to be added to an abstract type to get one with one less array dimension. */
+ private static final int ELEMENT_OF = -1 << DIM_SHIFT;
- /**
- * Kind of the types that are not relative to an input stack map frame.
- */
- static final int BASE = 0x1000000;
+ // Possible values for the KIND field of an abstract type.
- /**
- * Base kind of the base reference types. The BASE_VALUE of such types is an
- * index into the type table.
- */
- static final int OBJECT = BASE | 0x700000;
+ private static final int CONSTANT_KIND = 0x01000000;
+ private static final int REFERENCE_KIND = 0x02000000;
+ private static final int UNINITIALIZED_KIND = 0x03000000;
+ private static final int LOCAL_KIND = 0x04000000;
+ private static final int STACK_KIND = 0x05000000;
- /**
- * Base kind of the uninitialized base types. The BASE_VALUE of such types
- * in an index into the type table (the Item at that index contains both an
- * instruction offset and an internal class name).
- */
- static final int UNINITIALIZED = BASE | 0x800000;
+ // Possible flags for the FLAGS field of an abstract type.
- /**
- * Kind of the types that are relative to the local variable types of an
- * input stack map frame. The value of such types is a local variable index.
- */
- private static final int LOCAL = 0x2000000;
+ /**
+ * A flag used for LOCAL_KIND and STACK_KIND abstract types, indicating that if the resolved,
+ * concrete type is LONG or DOUBLE, TOP should be used instead (because the value has been
+ * partially overridden with an xSTORE instruction).
+ */
+ private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 0x00100000 & FLAGS_MASK;
- /**
- * Kind of the the types that are relative to the stack of an input stack
- * map frame. The value of such types is a position relatively to the top of
- * this stack.
- */
- private static final int STACK = 0x3000000;
+ // Useful predefined abstract types (all the possible CONSTANT_KIND types).
- /**
- * The TOP type. This is a BASE type.
- */
- static final int TOP = BASE | 0;
+ private static final int TOP = CONSTANT_KIND | ITEM_TOP;
+ private static final int BOOLEAN = CONSTANT_KIND | ITEM_ASM_BOOLEAN;
+ private static final int BYTE = CONSTANT_KIND | ITEM_ASM_BYTE;
+ private static final int CHAR = CONSTANT_KIND | ITEM_ASM_CHAR;
+ private static final int SHORT = CONSTANT_KIND | ITEM_ASM_SHORT;
+ private static final int INTEGER = CONSTANT_KIND | ITEM_INTEGER;
+ private static final int FLOAT = CONSTANT_KIND | ITEM_FLOAT;
+ private static final int LONG = CONSTANT_KIND | ITEM_LONG;
+ private static final int DOUBLE = CONSTANT_KIND | ITEM_DOUBLE;
+ private static final int NULL = CONSTANT_KIND | ITEM_NULL;
+ private static final int UNINITIALIZED_THIS = CONSTANT_KIND | ITEM_UNINITIALIZED_THIS;
- /**
- * The BOOLEAN type. This is a BASE type mainly used for array types.
- */
- static final int BOOLEAN = BASE | 9;
+ // -----------------------------------------------------------------------------------------------
+ // Instance fields
+ // -----------------------------------------------------------------------------------------------
- /**
- * The BYTE type. This is a BASE type mainly used for array types.
- */
- static final int BYTE = BASE | 10;
+ /** The basic block to which these input and output stack map frames correspond. */
+ Label owner;
- /**
- * The CHAR type. This is a BASE type mainly used for array types.
- */
- static final int CHAR = BASE | 11;
+ /** The input stack map frame locals. This is an array of abstract types. */
+ private int[] inputLocals;
- /**
- * The SHORT type. This is a BASE type mainly used for array types.
- */
- static final int SHORT = BASE | 12;
+ /** The input stack map frame stack. This is an array of abstract types. */
+ private int[] inputStack;
- /**
- * The INTEGER type. This is a BASE type.
- */
- static final int INTEGER = BASE | 1;
+ /** The output stack map frame locals. This is an array of abstract types. */
+ private int[] outputLocals;
- /**
- * The FLOAT type. This is a BASE type.
- */
- static final int FLOAT = BASE | 2;
+ /** The output stack map frame stack. This is an array of abstract types. */
+ private int[] outputStack;
- /**
- * The DOUBLE type. This is a BASE type.
- */
- static final int DOUBLE = BASE | 3;
+ /**
+ * The start of the output stack, relatively to the input stack. This offset is always negative or
+ * null. A null offset means that the output stack must be appended to the input stack. A -n
+ * offset means that the first n output stack elements must replace the top n input stack
+ * elements, and that the other elements must be appended to the input stack.
+ */
+ private short outputStackStart;
- /**
- * The LONG type. This is a BASE type.
- */
- static final int LONG = BASE | 4;
+ /** The index of the top stack element in {@link #outputStack}. */
+ private short outputStackTop;
- /**
- * The NULL type. This is a BASE type.
- */
- static final int NULL = BASE | 5;
+ /** The number of types that are initialized in the basic block. See {@link #initializations}. */
+ private int initializationCount;
- /**
- * The UNINITIALIZED_THIS type. This is a BASE type.
- */
- static final int UNINITIALIZED_THIS = BASE | 6;
+ /**
+ * The abstract types that are initialized in the basic block. A constructor invocation on an
+ * UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every occurrence of this
+ * type in the local variables and in the operand stack. This cannot be done during the first step
+ * of the algorithm since, during this step, the local variables and the operand stack types are
+ * still abstract. It is therefore necessary to store the abstract types of the constructors which
+ * are invoked in the basic block, in order to do this replacement during the second step of the
+ * algorithm, where the frames are fully computed. Note that this array can contain abstract types
+ * that are relative to the input locals or to the input stack.
+ */
+ private int[] initializations;
- /**
- * The stack size variation corresponding to each JVM instruction. This
- * stack variation is equal to the size of the values produced by an
- * instruction, minus the size of the values consumed by this instruction.
- */
- static final int[] SIZE;
+ // -----------------------------------------------------------------------------------------------
+ // Static methods to get abstract types from other type formats
+ // -----------------------------------------------------------------------------------------------
- /**
- * Computes the stack size variation corresponding to each JVM instruction.
- */
- static {
- int i;
- int[] b = new int[202];
- String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD"
- + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD"
- + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED"
- + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE";
- for (i = 0; i < b.length; ++i) {
- b[i] = s.charAt(i) - 'E';
- }
- SIZE = b;
+ /**
+ * Returns the abstract type corresponding to the given public API frame element type.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param type a frame element type described using the same format as in {@link
+ * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
+ * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or
+ * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating
+ * a NEW instruction (for uninitialized types).
+ * @return the abstract type corresponding to the given frame element type.
+ */
+ static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Object type) {
+ if (type instanceof Integer) {
+ return CONSTANT_KIND | ((Integer) type).intValue();
+ } else if (type instanceof String) {
+ String descriptor = Type.getObjectType((String) type).getDescriptor();
+ return getAbstractTypeFromDescriptor(symbolTable, descriptor, 0);
+ } else {
+ return UNINITIALIZED_KIND
+ | symbolTable.addUninitializedType("", ((Label) type).bytecodeOffset);
+ }
+ }
+
+ /**
+ * Returns the abstract type corresponding to the internal name of a class.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param internalName the internal name of a class. This must not be an array type
+ * descriptor.
+ * @return the abstract type value corresponding to the given internal name.
+ */
+ static int getAbstractTypeFromInternalName(
+ final SymbolTable symbolTable, final String internalName) {
+ return REFERENCE_KIND | symbolTable.addType(internalName);
+ }
- // code to generate the above string
- //
- // int NA = 0; // not applicable (unused opcode or variable size opcode)
- //
- // b = new int[] {
- // 0, //NOP, // visitInsn
- // 1, //ACONST_NULL, // -
- // 1, //ICONST_M1, // -
- // 1, //ICONST_0, // -
- // 1, //ICONST_1, // -
- // 1, //ICONST_2, // -
- // 1, //ICONST_3, // -
- // 1, //ICONST_4, // -
- // 1, //ICONST_5, // -
- // 2, //LCONST_0, // -
- // 2, //LCONST_1, // -
- // 1, //FCONST_0, // -
- // 1, //FCONST_1, // -
- // 1, //FCONST_2, // -
- // 2, //DCONST_0, // -
- // 2, //DCONST_1, // -
- // 1, //BIPUSH, // visitIntInsn
- // 1, //SIPUSH, // -
- // 1, //LDC, // visitLdcInsn
- // NA, //LDC_W, // -
- // NA, //LDC2_W, // -
- // 1, //ILOAD, // visitVarInsn
- // 2, //LLOAD, // -
- // 1, //FLOAD, // -
- // 2, //DLOAD, // -
- // 1, //ALOAD, // -
- // NA, //ILOAD_0, // -
- // NA, //ILOAD_1, // -
- // NA, //ILOAD_2, // -
- // NA, //ILOAD_3, // -
- // NA, //LLOAD_0, // -
- // NA, //LLOAD_1, // -
- // NA, //LLOAD_2, // -
- // NA, //LLOAD_3, // -
- // NA, //FLOAD_0, // -
- // NA, //FLOAD_1, // -
- // NA, //FLOAD_2, // -
- // NA, //FLOAD_3, // -
- // NA, //DLOAD_0, // -
- // NA, //DLOAD_1, // -
- // NA, //DLOAD_2, // -
- // NA, //DLOAD_3, // -
- // NA, //ALOAD_0, // -
- // NA, //ALOAD_1, // -
- // NA, //ALOAD_2, // -
- // NA, //ALOAD_3, // -
- // -1, //IALOAD, // visitInsn
- // 0, //LALOAD, // -
- // -1, //FALOAD, // -
- // 0, //DALOAD, // -
- // -1, //AALOAD, // -
- // -1, //BALOAD, // -
- // -1, //CALOAD, // -
- // -1, //SALOAD, // -
- // -1, //ISTORE, // visitVarInsn
- // -2, //LSTORE, // -
- // -1, //FSTORE, // -
- // -2, //DSTORE, // -
- // -1, //ASTORE, // -
- // NA, //ISTORE_0, // -
- // NA, //ISTORE_1, // -
- // NA, //ISTORE_2, // -
- // NA, //ISTORE_3, // -
- // NA, //LSTORE_0, // -
- // NA, //LSTORE_1, // -
- // NA, //LSTORE_2, // -
- // NA, //LSTORE_3, // -
- // NA, //FSTORE_0, // -
- // NA, //FSTORE_1, // -
- // NA, //FSTORE_2, // -
- // NA, //FSTORE_3, // -
- // NA, //DSTORE_0, // -
- // NA, //DSTORE_1, // -
- // NA, //DSTORE_2, // -
- // NA, //DSTORE_3, // -
- // NA, //ASTORE_0, // -
- // NA, //ASTORE_1, // -
- // NA, //ASTORE_2, // -
- // NA, //ASTORE_3, // -
- // -3, //IASTORE, // visitInsn
- // -4, //LASTORE, // -
- // -3, //FASTORE, // -
- // -4, //DASTORE, // -
- // -3, //AASTORE, // -
- // -3, //BASTORE, // -
- // -3, //CASTORE, // -
- // -3, //SASTORE, // -
- // -1, //POP, // -
- // -2, //POP2, // -
- // 1, //DUP, // -
- // 1, //DUP_X1, // -
- // 1, //DUP_X2, // -
- // 2, //DUP2, // -
- // 2, //DUP2_X1, // -
- // 2, //DUP2_X2, // -
- // 0, //SWAP, // -
- // -1, //IADD, // -
- // -2, //LADD, // -
- // -1, //FADD, // -
- // -2, //DADD, // -
- // -1, //ISUB, // -
- // -2, //LSUB, // -
- // -1, //FSUB, // -
- // -2, //DSUB, // -
- // -1, //IMUL, // -
- // -2, //LMUL, // -
- // -1, //FMUL, // -
- // -2, //DMUL, // -
- // -1, //IDIV, // -
- // -2, //LDIV, // -
- // -1, //FDIV, // -
- // -2, //DDIV, // -
- // -1, //IREM, // -
- // -2, //LREM, // -
- // -1, //FREM, // -
- // -2, //DREM, // -
- // 0, //INEG, // -
- // 0, //LNEG, // -
- // 0, //FNEG, // -
- // 0, //DNEG, // -
- // -1, //ISHL, // -
- // -1, //LSHL, // -
- // -1, //ISHR, // -
- // -1, //LSHR, // -
- // -1, //IUSHR, // -
- // -1, //LUSHR, // -
- // -1, //IAND, // -
- // -2, //LAND, // -
- // -1, //IOR, // -
- // -2, //LOR, // -
- // -1, //IXOR, // -
- // -2, //LXOR, // -
- // 0, //IINC, // visitIincInsn
- // 1, //I2L, // visitInsn
- // 0, //I2F, // -
- // 1, //I2D, // -
- // -1, //L2I, // -
- // -1, //L2F, // -
- // 0, //L2D, // -
- // 0, //F2I, // -
- // 1, //F2L, // -
- // 1, //F2D, // -
- // -1, //D2I, // -
- // 0, //D2L, // -
- // -1, //D2F, // -
- // 0, //I2B, // -
- // 0, //I2C, // -
- // 0, //I2S, // -
- // -3, //LCMP, // -
- // -1, //FCMPL, // -
- // -1, //FCMPG, // -
- // -3, //DCMPL, // -
- // -3, //DCMPG, // -
- // -1, //IFEQ, // visitJumpInsn
- // -1, //IFNE, // -
- // -1, //IFLT, // -
- // -1, //IFGE, // -
- // -1, //IFGT, // -
- // -1, //IFLE, // -
- // -2, //IF_ICMPEQ, // -
- // -2, //IF_ICMPNE, // -
- // -2, //IF_ICMPLT, // -
- // -2, //IF_ICMPGE, // -
- // -2, //IF_ICMPGT, // -
- // -2, //IF_ICMPLE, // -
- // -2, //IF_ACMPEQ, // -
- // -2, //IF_ACMPNE, // -
- // 0, //GOTO, // -
- // 1, //JSR, // -
- // 0, //RET, // visitVarInsn
- // -1, //TABLESWITCH, // visiTableSwitchInsn
- // -1, //LOOKUPSWITCH, // visitLookupSwitch
- // -1, //IRETURN, // visitInsn
- // -2, //LRETURN, // -
- // -1, //FRETURN, // -
- // -2, //DRETURN, // -
- // -1, //ARETURN, // -
- // 0, //RETURN, // -
- // NA, //GETSTATIC, // visitFieldInsn
- // NA, //PUTSTATIC, // -
- // NA, //GETFIELD, // -
- // NA, //PUTFIELD, // -
- // NA, //INVOKEVIRTUAL, // visitMethodInsn
- // NA, //INVOKESPECIAL, // -
- // NA, //INVOKESTATIC, // -
- // NA, //INVOKEINTERFACE, // -
- // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn
- // 1, //NEW, // visitTypeInsn
- // 0, //NEWARRAY, // visitIntInsn
- // 0, //ANEWARRAY, // visitTypeInsn
- // 0, //ARRAYLENGTH, // visitInsn
- // NA, //ATHROW, // -
- // 0, //CHECKCAST, // visitTypeInsn
- // 0, //INSTANCEOF, // -
- // -1, //MONITORENTER, // visitInsn
- // -1, //MONITOREXIT, // -
- // NA, //WIDE, // NOT VISITED
- // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn
- // -1, //IFNULL, // visitJumpInsn
- // -1, //IFNONNULL, // -
- // NA, //GOTO_W, // -
- // NA, //JSR_W, // -
- // };
- // for (i = 0; i < b.length; ++i) {
- // System.err.print((char)('E' + b[i]));
- // }
- // System.err.println();
+ /**
+ * Returns the abstract type corresponding to the given type descriptor.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param buffer a string ending with a type descriptor.
+ * @param offset the start offset of the type descriptor in buffer.
+ * @return the abstract type corresponding to the given type descriptor.
+ */
+ private static int getAbstractTypeFromDescriptor(
+ final SymbolTable symbolTable, final String buffer, final int offset) {
+ String internalName;
+ switch (buffer.charAt(offset)) {
+ case 'V':
+ return 0;
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ return INTEGER;
+ case 'F':
+ return FLOAT;
+ case 'J':
+ return LONG;
+ case 'D':
+ return DOUBLE;
+ case 'L':
+ internalName = buffer.substring(offset + 1, buffer.length() - 1);
+ return REFERENCE_KIND | symbolTable.addType(internalName);
+ case '[':
+ int elementDescriptorOffset = offset + 1;
+ while (buffer.charAt(elementDescriptorOffset) == '[') {
+ ++elementDescriptorOffset;
+ }
+ int typeValue;
+ switch (buffer.charAt(elementDescriptorOffset)) {
+ case 'Z':
+ typeValue = BOOLEAN;
+ break;
+ case 'C':
+ typeValue = CHAR;
+ break;
+ case 'B':
+ typeValue = BYTE;
+ break;
+ case 'S':
+ typeValue = SHORT;
+ break;
+ case 'I':
+ typeValue = INTEGER;
+ break;
+ case 'F':
+ typeValue = FLOAT;
+ break;
+ case 'J':
+ typeValue = LONG;
+ break;
+ case 'D':
+ typeValue = DOUBLE;
+ break;
+ case 'L':
+ internalName = buffer.substring(elementDescriptorOffset + 1, buffer.length() - 1);
+ typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
+ default:
+ throw new IllegalArgumentException();
}
+ }
- /**
- * The label (i.e. basic block) to which these input and output stack map
- * frames correspond.
- */
- Label owner;
+ // -----------------------------------------------------------------------------------------------
+ // Constructor
+ // -----------------------------------------------------------------------------------------------
- /**
- * The input stack map frame locals.
- */
- int[] inputLocals;
+ /**
+ * Constructs a new Frame.
+ *
+ * @param owner the basic block to which these input and output stack map frames correspond.
+ */
+ Frame(final Label owner) {
+ this.owner = owner;
+ }
- /**
- * The input stack map frame stack.
- */
- int[] inputStack;
+ /**
+ * Sets this frame to the value of the given frame.
+ *
+ *
WARNING: after this method is called the two frames share the same data structures. It is
+ * recommended to discard the given frame to avoid unexpected side effects.
+ *
+ * @param frame The new frame value.
+ */
+ final void copyFrom(final Frame frame) {
+ inputLocals = frame.inputLocals;
+ inputStack = frame.inputStack;
+ outputStackStart = 0;
+ outputLocals = frame.outputLocals;
+ outputStack = frame.outputStack;
+ outputStackTop = frame.outputStackTop;
+ initializationCount = frame.initializationCount;
+ initializations = frame.initializations;
+ }
- /**
- * The output stack map frame locals.
- */
- private int[] outputLocals;
+ // -----------------------------------------------------------------------------------------------
+ // Methods related to the input frame
+ // -----------------------------------------------------------------------------------------------
- /**
- * The output stack map frame stack.
- */
- private int[] outputStack;
+ /**
+ * Sets the input frame from the given method description. This method is used to initialize the
+ * first frame of a method, which is implicit (i.e. not stored explicitly in the StackMapTable
+ * attribute).
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param access the method's access flags.
+ * @param descriptor the method descriptor.
+ * @param maxLocals the maximum number of local variables of the method.
+ */
+ final void setInputFrameFromDescriptor(
+ final SymbolTable symbolTable,
+ final int access,
+ final String descriptor,
+ final int maxLocals) {
+ inputLocals = new int[maxLocals];
+ inputStack = new int[0];
+ int inputLocalIndex = 0;
+ if ((access & Opcodes.ACC_STATIC) == 0) {
+ if ((access & Constants.ACC_CONSTRUCTOR) == 0) {
+ inputLocals[inputLocalIndex++] =
+ REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());
+ } else {
+ inputLocals[inputLocalIndex++] = UNINITIALIZED_THIS;
+ }
+ }
+ for (Type argumentType : Type.getArgumentTypes(descriptor)) {
+ int abstractType =
+ getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0);
+ inputLocals[inputLocalIndex++] = abstractType;
+ if (abstractType == LONG || abstractType == DOUBLE) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
+ }
+ while (inputLocalIndex < maxLocals) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
+ }
- /**
- * Relative size of the output stack. The exact semantics of this field
- * depends on the algorithm that is used.
- *
- * When only the maximum stack size is computed, this field is the size of
- * the output stack relatively to the top of the input stack.
- *
- * When the stack map frames are completely computed, this field is the
- * actual number of types in {@link #outputStack}.
- */
- private int outputStackTop;
+ /**
+ * Sets the input frame from the given public API frame description.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param nLocal the number of local variables.
+ * @param local the local variable types, described using the same format as in {@link
+ * MethodVisitor#visitFrame}.
+ * @param nStack the number of operand stack elements.
+ * @param stack the operand stack types, described using the same format as in {@link
+ * MethodVisitor#visitFrame}.
+ */
+ final void setInputFrameFromApiFormat(
+ final SymbolTable symbolTable,
+ final int nLocal,
+ final Object[] local,
+ final int nStack,
+ final Object[] stack) {
+ int inputLocalIndex = 0;
+ for (int i = 0; i < nLocal; ++i) {
+ inputLocals[inputLocalIndex++] = getAbstractTypeFromApiFormat(symbolTable, local[i]);
+ if (local[i] == Opcodes.LONG || local[i] == Opcodes.DOUBLE) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
+ }
+ while (inputLocalIndex < inputLocals.length) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
+ int nStackTop = 0;
+ for (int i = 0; i < nStack; ++i) {
+ if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
+ ++nStackTop;
+ }
+ }
+ inputStack = new int[nStack + nStackTop];
+ int inputStackIndex = 0;
+ for (int i = 0; i < nStack; ++i) {
+ inputStack[inputStackIndex++] = getAbstractTypeFromApiFormat(symbolTable, stack[i]);
+ if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
+ inputStack[inputStackIndex++] = TOP;
+ }
+ }
+ outputStackTop = 0;
+ initializationCount = 0;
+ }
- /**
- * Number of types that are initialized in the basic block.
- *
- * @see #initializations
- */
- private int initializationCount;
+ final int getInputStackSize() {
+ return inputStack.length;
+ }
- /**
- * The types that are initialized in the basic block. A constructor
- * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace
- * every occurence of this type in the local variables and in the
- * operand stack. This cannot be done during the first phase of the
- * algorithm since, during this phase, the local variables and the operand
- * stack are not completely computed. It is therefore necessary to store the
- * types on which constructors are invoked in the basic block, in order to
- * do this replacement during the second phase of the algorithm, where the
- * frames are fully computed. Note that this array can contain types that
- * are relative to input locals or to the input stack (see below for the
- * description of the algorithm).
- */
- private int[] initializations;
+ // -----------------------------------------------------------------------------------------------
+ // Methods related to the output frame
+ // -----------------------------------------------------------------------------------------------
- /**
- * Returns the output frame local variable type at the given index.
- *
- * @param local
- * the index of the local that must be returned.
- * @return the output frame local variable type at the given index.
- */
- private int get(final int local) {
- if (outputLocals == null || local >= outputLocals.length) {
- // this local has never been assigned in this basic block,
- // so it is still equal to its value in the input frame
- return LOCAL | local;
- } else {
- int type = outputLocals[local];
- if (type == 0) {
- // this local has never been assigned in this basic block,
- // so it is still equal to its value in the input frame
- type = outputLocals[local] = LOCAL | local;
- }
- return type;
- }
+ /**
+ * Returns the abstract type stored at the given local variable index in the output frame.
+ *
+ * @param localIndex the index of the local variable whose value must be returned.
+ * @return the abstract type stored at the given local variable index in the output frame.
+ */
+ private int getLocal(final int localIndex) {
+ if (outputLocals == null || localIndex >= outputLocals.length) {
+ // If this local has never been assigned in this basic block, it is still equal to its value
+ // in the input frame.
+ return LOCAL_KIND | localIndex;
+ } else {
+ int abstractType = outputLocals[localIndex];
+ if (abstractType == 0) {
+ // If this local has never been assigned in this basic block, so it is still equal to its
+ // value in the input frame.
+ abstractType = outputLocals[localIndex] = LOCAL_KIND | localIndex;
+ }
+ return abstractType;
}
+ }
- /**
- * Sets the output frame local variable type at the given index.
- *
- * @param local
- * the index of the local that must be set.
- * @param type
- * the value of the local that must be set.
- */
- private void set(final int local, final int type) {
- // creates and/or resizes the output local variables array if necessary
- if (outputLocals == null) {
- outputLocals = new int[10];
- }
- int n = outputLocals.length;
- if (local >= n) {
- int[] t = new int[Math.max(local + 1, 2 * n)];
- System.arraycopy(outputLocals, 0, t, 0, n);
- outputLocals = t;
- }
- // sets the local variable
- outputLocals[local] = type;
+ /**
+ * Replaces the abstract type stored at the given local variable index in the output frame.
+ *
+ * @param localIndex the index of the output frame local variable that must be set.
+ * @param abstractType the value that must be set.
+ */
+ private void setLocal(final int localIndex, final int abstractType) {
+ // Create and/or resize the output local variables array if necessary.
+ if (outputLocals == null) {
+ outputLocals = new int[10];
}
-
- /**
- * Pushes a new type onto the output frame stack.
- *
- * @param type
- * the type that must be pushed.
- */
- private void push(final int type) {
- // creates and/or resizes the output stack array if necessary
- if (outputStack == null) {
- outputStack = new int[10];
- }
- int n = outputStack.length;
- if (outputStackTop >= n) {
- int[] t = new int[Math.max(outputStackTop + 1, 2 * n)];
- System.arraycopy(outputStack, 0, t, 0, n);
- outputStack = t;
- }
- // pushes the type on the output stack
- outputStack[outputStackTop++] = type;
- // updates the maximun height reached by the output stack, if needed
- int top = owner.inputStackTop + outputStackTop;
- if (top > owner.outputStackMax) {
- owner.outputStackMax = top;
- }
+ int outputLocalsLength = outputLocals.length;
+ if (localIndex >= outputLocalsLength) {
+ int[] newOutputLocals = new int[Math.max(localIndex + 1, 2 * outputLocalsLength)];
+ System.arraycopy(outputLocals, 0, newOutputLocals, 0, outputLocalsLength);
+ outputLocals = newOutputLocals;
}
+ // Set the local variable.
+ outputLocals[localIndex] = abstractType;
+ }
- /**
- * Pushes a new type onto the output frame stack.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param desc
- * the descriptor of the type to be pushed. Can also be a method
- * descriptor (in this case this method pushes its return type
- * onto the output frame stack).
- */
- private void push(final ClassWriter cw, final String desc) {
- int type = type(cw, desc);
- if (type != 0) {
- push(type);
- if (type == LONG || type == DOUBLE) {
- push(TOP);
- }
- }
+ /**
+ * Pushes the given abstract type on the output frame stack.
+ *
+ * @param abstractType an abstract type.
+ */
+ private void push(final int abstractType) {
+ // Create and/or resize the output stack array if necessary.
+ if (outputStack == null) {
+ outputStack = new int[10];
}
-
- /**
- * Returns the int encoding of the given type.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param desc
- * a type descriptor.
- * @return the int encoding of the given type.
- */
- private static int type(final ClassWriter cw, final String desc) {
- String t;
- int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0;
- switch (desc.charAt(index)) {
- case 'V':
- return 0;
- case 'Z':
- case 'C':
- case 'B':
- case 'S':
- case 'I':
- return INTEGER;
- case 'F':
- return FLOAT;
- case 'J':
- return LONG;
- case 'D':
- return DOUBLE;
- case 'L':
- // stores the internal name, not the descriptor!
- t = desc.substring(index + 1, desc.length() - 1);
- return OBJECT | cw.addType(t);
- // case '[':
- default:
- // extracts the dimensions and the element type
- int data;
- int dims = index + 1;
- while (desc.charAt(dims) == '[') {
- ++dims;
- }
- switch (desc.charAt(dims)) {
- case 'Z':
- data = BOOLEAN;
- break;
- case 'C':
- data = CHAR;
- break;
- case 'B':
- data = BYTE;
- break;
- case 'S':
- data = SHORT;
- break;
- case 'I':
- data = INTEGER;
- break;
- case 'F':
- data = FLOAT;
- break;
- case 'J':
- data = LONG;
- break;
- case 'D':
- data = DOUBLE;
- break;
- // case 'L':
- default:
- // stores the internal name, not the descriptor
- t = desc.substring(dims + 1, desc.length() - 1);
- data = OBJECT | cw.addType(t);
- }
- return (dims - index) << 28 | data;
- }
+ int outputStackLength = outputStack.length;
+ if (outputStackTop >= outputStackLength) {
+ int[] newOutputStack = new int[Math.max(outputStackTop + 1, 2 * outputStackLength)];
+ System.arraycopy(outputStack, 0, newOutputStack, 0, outputStackLength);
+ outputStack = newOutputStack;
}
+ // Pushes the abstract type on the output stack.
+ outputStack[outputStackTop++] = abstractType;
+ // Updates the maximum size reached by the output stack, if needed (note that this size is
+ // relative to the input stack size, which is not known yet).
+ short outputStackSize = (short) (outputStackStart + outputStackTop);
+ if (outputStackSize > owner.outputStackMax) {
+ owner.outputStackMax = outputStackSize;
+ }
+ }
- /**
- * Pops a type from the output frame stack and returns its value.
- *
- * @return the type that has been popped from the output frame stack.
- */
- private int pop() {
- if (outputStackTop > 0) {
- return outputStack[--outputStackTop];
- } else {
- // if the output frame stack is empty, pops from the input stack
- return STACK | -(--owner.inputStackTop);
- }
+ /**
+ * Pushes the abstract type corresponding to the given descriptor on the output frame stack.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param descriptor a type or method descriptor (in which case its return type is pushed).
+ */
+ private void push(final SymbolTable symbolTable, final String descriptor) {
+ int typeDescriptorOffset = descriptor.charAt(0) == '(' ? descriptor.indexOf(')') + 1 : 0;
+ int abstractType = getAbstractTypeFromDescriptor(symbolTable, descriptor, typeDescriptorOffset);
+ if (abstractType != 0) {
+ push(abstractType);
+ if (abstractType == LONG || abstractType == DOUBLE) {
+ push(TOP);
+ }
}
+ }
- /**
- * Pops the given number of types from the output frame stack.
- *
- * @param elements
- * the number of types that must be popped.
- */
- private void pop(final int elements) {
- if (outputStackTop >= elements) {
- outputStackTop -= elements;
- } else {
- // if the number of elements to be popped is greater than the number
- // of elements in the output stack, clear it, and pops the remaining
- // elements from the input stack.
- owner.inputStackTop -= elements - outputStackTop;
- outputStackTop = 0;
- }
+ /**
+ * Pops an abstract type from the output frame stack and returns its value.
+ *
+ * @return the abstract type that has been popped from the output frame stack.
+ */
+ private int pop() {
+ if (outputStackTop > 0) {
+ return outputStack[--outputStackTop];
+ } else {
+ // If the output frame stack is empty, pop from the input stack.
+ return STACK_KIND | -(--outputStackStart);
}
+ }
- /**
- * Pops a type from the output frame stack.
- *
- * @param desc
- * the descriptor of the type to be popped. Can also be a method
- * descriptor (in this case this method pops the types
- * corresponding to the method arguments).
- */
- private void pop(final String desc) {
- char c = desc.charAt(0);
- if (c == '(') {
- pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1);
- } else if (c == 'J' || c == 'D') {
- pop(2);
- } else {
- pop(1);
- }
+ /**
+ * Pops the given number of abstract types from the output frame stack.
+ *
+ * @param elements the number of abstract types that must be popped.
+ */
+ private void pop(final int elements) {
+ if (outputStackTop >= elements) {
+ outputStackTop -= elements;
+ } else {
+ // If the number of elements to be popped is greater than the number of elements in the output
+ // stack, clear it, and pop the remaining elements from the input stack.
+ outputStackStart -= elements - outputStackTop;
+ outputStackTop = 0;
}
+ }
- /**
- * Adds a new type to the list of types on which a constructor is invoked in
- * the basic block.
- *
- * @param var
- * a type on a which a constructor is invoked.
- */
- private void init(final int var) {
- // creates and/or resizes the initializations array if necessary
- if (initializations == null) {
- initializations = new int[2];
- }
- int n = initializations.length;
- if (initializationCount >= n) {
- int[] t = new int[Math.max(initializationCount + 1, 2 * n)];
- System.arraycopy(initializations, 0, t, 0, n);
- initializations = t;
- }
- // stores the type to be initialized
- initializations[initializationCount++] = var;
+ /**
+ * Pops as many abstract types from the output frame stack as described by the given descriptor.
+ *
+ * @param descriptor a type or method descriptor (in which case its argument types are popped).
+ */
+ private void pop(final String descriptor) {
+ char firstDescriptorChar = descriptor.charAt(0);
+ if (firstDescriptorChar == '(') {
+ pop((Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1);
+ } else if (firstDescriptorChar == 'J' || firstDescriptorChar == 'D') {
+ pop(2);
+ } else {
+ pop(1);
}
+ }
- /**
- * Replaces the given type with the appropriate type if it is one of the
- * types on which a constructor is invoked in the basic block.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param t
- * a type
- * @return t or, if t is one of the types on which a constructor is invoked
- * in the basic block, the type corresponding to this constructor.
- */
- private int init(final ClassWriter cw, final int t) {
- int s;
- if (t == UNINITIALIZED_THIS) {
- s = OBJECT | cw.addType(cw.thisName);
- } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) {
- String type = cw.typeTable[t & BASE_VALUE].strVal1;
- s = OBJECT | cw.addType(type);
- } else {
- return t;
- }
- for (int j = 0; j < initializationCount; ++j) {
- int u = initializations[j];
- int dim = u & DIM;
- int kind = u & KIND;
- if (kind == LOCAL) {
- u = dim + inputLocals[u & VALUE];
- } else if (kind == STACK) {
- u = dim + inputStack[inputStack.length - (u & VALUE)];
- }
- if (t == u) {
- return s;
- }
- }
- return t;
+ // -----------------------------------------------------------------------------------------------
+ // Methods to handle uninitialized types
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds an abstract type to the list of types on which a constructor is invoked in the basic
+ * block.
+ *
+ * @param abstractType an abstract type on a which a constructor is invoked.
+ */
+ private void addInitializedType(final int abstractType) {
+ // Create and/or resize the initializations array if necessary.
+ if (initializations == null) {
+ initializations = new int[2];
}
+ int initializationsLength = initializations.length;
+ if (initializationCount >= initializationsLength) {
+ int[] newInitializations =
+ new int[Math.max(initializationCount + 1, 2 * initializationsLength)];
+ System.arraycopy(initializations, 0, newInitializations, 0, initializationsLength);
+ initializations = newInitializations;
+ }
+ // Store the abstract type.
+ initializations[initializationCount++] = abstractType;
+ }
- /**
- * Initializes the input frame of the first basic block from the method
- * descriptor.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param access
- * the access flags of the method to which this label belongs.
- * @param args
- * the formal parameter types of this method.
- * @param maxLocals
- * the maximum number of local variables of this method.
- */
- void initInputFrame(final ClassWriter cw, final int access,
- final Type[] args, final int maxLocals) {
- inputLocals = new int[maxLocals];
- inputStack = new int[0];
- int i = 0;
- if ((access & Opcodes.ACC_STATIC) == 0) {
- if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) {
- inputLocals[i++] = OBJECT | cw.addType(cw.thisName);
- } else {
- inputLocals[i++] = UNINITIALIZED_THIS;
- }
+ /**
+ * Returns the "initialized" abstract type corresponding to the given abstract type.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param abstractType an abstract type.
+ * @return the REFERENCE_KIND abstract type corresponding to abstractType if it is
+ * UNINITIALIZED_THIS or an UNINITIALIZED_KIND abstract type for one of the types on which a
+ * constructor is invoked in the basic block. Otherwise returns abstractType.
+ */
+ private int getInitializedType(final SymbolTable symbolTable, final int abstractType) {
+ if (abstractType == UNINITIALIZED_THIS
+ || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND) {
+ for (int i = 0; i < initializationCount; ++i) {
+ int initializedType = initializations[i];
+ int dim = initializedType & DIM_MASK;
+ int kind = initializedType & KIND_MASK;
+ int value = initializedType & VALUE_MASK;
+ if (kind == LOCAL_KIND) {
+ initializedType = dim + inputLocals[value];
+ } else if (kind == STACK_KIND) {
+ initializedType = dim + inputStack[inputStack.length - value];
}
- for (int j = 0; j < args.length; ++j) {
- int t = type(cw, args[j].getDescriptor());
- inputLocals[i++] = t;
- if (t == LONG || t == DOUBLE) {
- inputLocals[i++] = TOP;
- }
- }
- while (i < maxLocals) {
- inputLocals[i++] = TOP;
+ if (abstractType == initializedType) {
+ if (abstractType == UNINITIALIZED_THIS) {
+ return REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());
+ } else {
+ return REFERENCE_KIND
+ | symbolTable.addType(symbolTable.getType(abstractType & VALUE_MASK).value);
+ }
}
+ }
}
+ return abstractType;
+ }
- /**
- * Simulates the action of the given instruction on the output stack frame.
- *
- * @param opcode
- * the opcode of the instruction.
- * @param arg
- * the operand of the instruction, if any.
- * @param cw
- * the class writer to which this label belongs.
- * @param item
- * the operand of the instructions, if any.
- */
- void execute(final int opcode, final int arg, final ClassWriter cw,
- final Item item) {
- int t1, t2, t3, t4;
- switch (opcode) {
- case Opcodes.NOP:
- case Opcodes.INEG:
- case Opcodes.LNEG:
- case Opcodes.FNEG:
- case Opcodes.DNEG:
- case Opcodes.I2B:
- case Opcodes.I2C:
- case Opcodes.I2S:
- case Opcodes.GOTO:
- case Opcodes.RETURN:
- break;
- case Opcodes.ACONST_NULL:
- push(NULL);
- break;
- case Opcodes.ICONST_M1:
- case Opcodes.ICONST_0:
- case Opcodes.ICONST_1:
- case Opcodes.ICONST_2:
- case Opcodes.ICONST_3:
- case Opcodes.ICONST_4:
- case Opcodes.ICONST_5:
- case Opcodes.BIPUSH:
- case Opcodes.SIPUSH:
- case Opcodes.ILOAD:
- push(INTEGER);
- break;
- case Opcodes.LCONST_0:
- case Opcodes.LCONST_1:
- case Opcodes.LLOAD:
- push(LONG);
- push(TOP);
- break;
- case Opcodes.FCONST_0:
- case Opcodes.FCONST_1:
- case Opcodes.FCONST_2:
- case Opcodes.FLOAD:
- push(FLOAT);
- break;
- case Opcodes.DCONST_0:
- case Opcodes.DCONST_1:
- case Opcodes.DLOAD:
- push(DOUBLE);
- push(TOP);
- break;
- case Opcodes.LDC:
- switch (item.type) {
- case ClassWriter.INT:
- push(INTEGER);
- break;
- case ClassWriter.LONG:
- push(LONG);
- push(TOP);
- break;
- case ClassWriter.FLOAT:
- push(FLOAT);
- break;
- case ClassWriter.DOUBLE:
- push(DOUBLE);
- push(TOP);
- break;
- case ClassWriter.CLASS:
- push(OBJECT | cw.addType("java/lang/Class"));
- break;
- case ClassWriter.STR:
- push(OBJECT | cw.addType("java/lang/String"));
- break;
- case ClassWriter.MTYPE:
- push(OBJECT | cw.addType("java/lang/invoke/MethodType"));
- break;
- // case ClassWriter.HANDLE_BASE + [1..9]:
- default:
- push(OBJECT | cw.addType("java/lang/invoke/MethodHandle"));
- }
- break;
- case Opcodes.ALOAD:
- push(get(arg));
- break;
- case Opcodes.IALOAD:
- case Opcodes.BALOAD:
- case Opcodes.CALOAD:
- case Opcodes.SALOAD:
- pop(2);
- push(INTEGER);
- break;
- case Opcodes.LALOAD:
- case Opcodes.D2L:
- pop(2);
- push(LONG);
- push(TOP);
- break;
- case Opcodes.FALOAD:
- pop(2);
- push(FLOAT);
- break;
- case Opcodes.DALOAD:
- case Opcodes.L2D:
- pop(2);
- push(DOUBLE);
- push(TOP);
- break;
- case Opcodes.AALOAD:
- pop(1);
- t1 = pop();
- push(ELEMENT_OF + t1);
- break;
- case Opcodes.ISTORE:
- case Opcodes.FSTORE:
- case Opcodes.ASTORE:
- t1 = pop();
- set(arg, t1);
- if (arg > 0) {
- t2 = get(arg - 1);
- // if t2 is of kind STACK or LOCAL we cannot know its size!
- if (t2 == LONG || t2 == DOUBLE) {
- set(arg - 1, TOP);
- } else if ((t2 & KIND) != BASE) {
- set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
- }
- }
- break;
- case Opcodes.LSTORE:
- case Opcodes.DSTORE:
- pop(1);
- t1 = pop();
- set(arg, t1);
- set(arg + 1, TOP);
- if (arg > 0) {
- t2 = get(arg - 1);
- // if t2 is of kind STACK or LOCAL we cannot know its size!
- if (t2 == LONG || t2 == DOUBLE) {
- set(arg - 1, TOP);
- } else if ((t2 & KIND) != BASE) {
- set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
- }
- }
- break;
- case Opcodes.IASTORE:
- case Opcodes.BASTORE:
- case Opcodes.CASTORE:
- case Opcodes.SASTORE:
- case Opcodes.FASTORE:
- case Opcodes.AASTORE:
- pop(3);
- break;
- case Opcodes.LASTORE:
- case Opcodes.DASTORE:
- pop(4);
- break;
- case Opcodes.POP:
- case Opcodes.IFEQ:
- case Opcodes.IFNE:
- case Opcodes.IFLT:
- case Opcodes.IFGE:
- case Opcodes.IFGT:
- case Opcodes.IFLE:
- case Opcodes.IRETURN:
- case Opcodes.FRETURN:
- case Opcodes.ARETURN:
- case Opcodes.TABLESWITCH:
- case Opcodes.LOOKUPSWITCH:
- case Opcodes.ATHROW:
- case Opcodes.MONITORENTER:
- case Opcodes.MONITOREXIT:
- case Opcodes.IFNULL:
- case Opcodes.IFNONNULL:
- pop(1);
- break;
- case Opcodes.POP2:
- case Opcodes.IF_ICMPEQ:
- case Opcodes.IF_ICMPNE:
- case Opcodes.IF_ICMPLT:
- case Opcodes.IF_ICMPGE:
- case Opcodes.IF_ICMPGT:
- case Opcodes.IF_ICMPLE:
- case Opcodes.IF_ACMPEQ:
- case Opcodes.IF_ACMPNE:
- case Opcodes.LRETURN:
- case Opcodes.DRETURN:
- pop(2);
- break;
- case Opcodes.DUP:
- t1 = pop();
- push(t1);
- push(t1);
- break;
- case Opcodes.DUP_X1:
- t1 = pop();
- t2 = pop();
- push(t1);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP_X2:
- t1 = pop();
- t2 = pop();
- t3 = pop();
- push(t1);
- push(t3);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP2:
- t1 = pop();
- t2 = pop();
- push(t2);
- push(t1);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP2_X1:
- t1 = pop();
- t2 = pop();
- t3 = pop();
- push(t2);
- push(t1);
- push(t3);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP2_X2:
- t1 = pop();
- t2 = pop();
- t3 = pop();
- t4 = pop();
- push(t2);
- push(t1);
- push(t4);
- push(t3);
- push(t2);
- push(t1);
- break;
- case Opcodes.SWAP:
- t1 = pop();
- t2 = pop();
- push(t1);
- push(t2);
- break;
- case Opcodes.IADD:
- case Opcodes.ISUB:
- case Opcodes.IMUL:
- case Opcodes.IDIV:
- case Opcodes.IREM:
- case Opcodes.IAND:
- case Opcodes.IOR:
- case Opcodes.IXOR:
- case Opcodes.ISHL:
- case Opcodes.ISHR:
- case Opcodes.IUSHR:
- case Opcodes.L2I:
- case Opcodes.D2I:
- case Opcodes.FCMPL:
- case Opcodes.FCMPG:
- pop(2);
+ // -----------------------------------------------------------------------------------------------
+ // Main method, to simulate the execution of each instruction on the output frame
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Simulates the action of the given instruction on the output stack frame.
+ *
+ * @param opcode the opcode of the instruction.
+ * @param arg the numeric operand of the instruction, if any.
+ * @param argSymbol the Symbol operand of the instruction, if any.
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ */
+ void execute(
+ final int opcode, final int arg, final Symbol argSymbol, final SymbolTable symbolTable) {
+ // Abstract types popped from the stack or read from local variables.
+ int abstractType1;
+ int abstractType2;
+ int abstractType3;
+ int abstractType4;
+ switch (opcode) {
+ case Opcodes.NOP:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.GOTO:
+ case Opcodes.RETURN:
+ break;
+ case Opcodes.ACONST_NULL:
+ push(NULL);
+ break;
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.BIPUSH:
+ case Opcodes.SIPUSH:
+ case Opcodes.ILOAD:
+ push(INTEGER);
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.LLOAD:
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.FLOAD:
+ push(FLOAT);
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.DLOAD:
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LDC:
+ switch (argSymbol.tag) {
+ case Symbol.CONSTANT_INTEGER_TAG:
push(INTEGER);
break;
- case Opcodes.LADD:
- case Opcodes.LSUB:
- case Opcodes.LMUL:
- case Opcodes.LDIV:
- case Opcodes.LREM:
- case Opcodes.LAND:
- case Opcodes.LOR:
- case Opcodes.LXOR:
- pop(4);
+ case Symbol.CONSTANT_LONG_TAG:
push(LONG);
push(TOP);
break;
- case Opcodes.FADD:
- case Opcodes.FSUB:
- case Opcodes.FMUL:
- case Opcodes.FDIV:
- case Opcodes.FREM:
- case Opcodes.L2F:
- case Opcodes.D2F:
- pop(2);
+ case Symbol.CONSTANT_FLOAT_TAG:
push(FLOAT);
break;
- case Opcodes.DADD:
- case Opcodes.DSUB:
- case Opcodes.DMUL:
- case Opcodes.DDIV:
- case Opcodes.DREM:
- pop(4);
+ case Symbol.CONSTANT_DOUBLE_TAG:
push(DOUBLE);
push(TOP);
break;
- case Opcodes.LSHL:
- case Opcodes.LSHR:
- case Opcodes.LUSHR:
- pop(3);
- push(LONG);
- push(TOP);
+ case Symbol.CONSTANT_CLASS_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/Class"));
break;
- case Opcodes.IINC:
- set(arg, INTEGER);
+ case Symbol.CONSTANT_STRING_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/String"));
break;
- case Opcodes.I2L:
- case Opcodes.F2L:
- pop(1);
- push(LONG);
- push(TOP);
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodType"));
break;
- case Opcodes.I2F:
- pop(1);
- push(FLOAT);
- break;
- case Opcodes.I2D:
- case Opcodes.F2D:
- pop(1);
- push(DOUBLE);
- push(TOP);
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodHandle"));
break;
- case Opcodes.F2I:
- case Opcodes.ARRAYLENGTH:
- case Opcodes.INSTANCEOF:
- pop(1);
- push(INTEGER);
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ push(symbolTable, argSymbol.value);
break;
- case Opcodes.LCMP:
- case Opcodes.DCMPL:
- case Opcodes.DCMPG:
- pop(4);
- push(INTEGER);
- break;
- case Opcodes.JSR:
- case Opcodes.RET:
- throw new RuntimeException(
- "JSR/RET are not supported with computeFrames option");
- case Opcodes.GETSTATIC:
- push(cw, item.strVal3);
- break;
- case Opcodes.PUTSTATIC:
- pop(item.strVal3);
- break;
- case Opcodes.GETFIELD:
- pop(1);
- push(cw, item.strVal3);
- break;
- case Opcodes.PUTFIELD:
- pop(item.strVal3);
- pop();
+ default:
+ throw new AssertionError();
+ }
+ break;
+ case Opcodes.ALOAD:
+ push(getLocal(arg));
+ break;
+ case Opcodes.LALOAD:
+ case Opcodes.D2L:
+ pop(2);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.DALOAD:
+ case Opcodes.L2D:
+ pop(2);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.AALOAD:
+ pop(1);
+ abstractType1 = pop();
+ push(abstractType1 == NULL ? abstractType1 : ELEMENT_OF + abstractType1);
+ break;
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ abstractType1 = pop();
+ setLocal(arg, abstractType1);
+ if (arg > 0) {
+ int previousLocalType = getLocal(arg - 1);
+ if (previousLocalType == LONG || previousLocalType == DOUBLE) {
+ setLocal(arg - 1, TOP);
+ } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND
+ || (previousLocalType & KIND_MASK) == STACK_KIND) {
+ // The type of the previous local variable is not known yet, but if it later appears
+ // to be LONG or DOUBLE, we should then use TOP instead.
+ setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG);
+ }
+ }
+ break;
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ pop(1);
+ abstractType1 = pop();
+ setLocal(arg, abstractType1);
+ setLocal(arg + 1, TOP);
+ if (arg > 0) {
+ int previousLocalType = getLocal(arg - 1);
+ if (previousLocalType == LONG || previousLocalType == DOUBLE) {
+ setLocal(arg - 1, TOP);
+ } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND
+ || (previousLocalType & KIND_MASK) == STACK_KIND) {
+ // The type of the previous local variable is not known yet, but if it later appears
+ // to be LONG or DOUBLE, we should then use TOP instead.
+ setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG);
+ }
+ }
+ break;
+ case Opcodes.IASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.AASTORE:
+ pop(3);
+ break;
+ case Opcodes.LASTORE:
+ case Opcodes.DASTORE:
+ pop(4);
+ break;
+ case Opcodes.POP:
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.TABLESWITCH:
+ case Opcodes.LOOKUPSWITCH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
+ pop(1);
+ break;
+ case Opcodes.POP2:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.LRETURN:
+ case Opcodes.DRETURN:
+ pop(2);
+ break;
+ case Opcodes.DUP:
+ abstractType1 = pop();
+ push(abstractType1);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP_X1:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ push(abstractType1);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP_X2:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ abstractType3 = pop();
+ push(abstractType1);
+ push(abstractType3);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP2:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ push(abstractType2);
+ push(abstractType1);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP2_X1:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ abstractType3 = pop();
+ push(abstractType2);
+ push(abstractType1);
+ push(abstractType3);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP2_X2:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ abstractType3 = pop();
+ abstractType4 = pop();
+ push(abstractType2);
+ push(abstractType1);
+ push(abstractType4);
+ push(abstractType3);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.SWAP:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ push(abstractType1);
+ push(abstractType2);
+ break;
+ case Opcodes.IALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ case Opcodes.IADD:
+ case Opcodes.ISUB:
+ case Opcodes.IMUL:
+ case Opcodes.IDIV:
+ case Opcodes.IREM:
+ case Opcodes.IAND:
+ case Opcodes.IOR:
+ case Opcodes.IXOR:
+ case Opcodes.ISHL:
+ case Opcodes.ISHR:
+ case Opcodes.IUSHR:
+ case Opcodes.L2I:
+ case Opcodes.D2I:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ pop(2);
+ push(INTEGER);
+ break;
+ case Opcodes.LADD:
+ case Opcodes.LSUB:
+ case Opcodes.LMUL:
+ case Opcodes.LDIV:
+ case Opcodes.LREM:
+ case Opcodes.LAND:
+ case Opcodes.LOR:
+ case Opcodes.LXOR:
+ pop(4);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FALOAD:
+ case Opcodes.FADD:
+ case Opcodes.FSUB:
+ case Opcodes.FMUL:
+ case Opcodes.FDIV:
+ case Opcodes.FREM:
+ case Opcodes.L2F:
+ case Opcodes.D2F:
+ pop(2);
+ push(FLOAT);
+ break;
+ case Opcodes.DADD:
+ case Opcodes.DSUB:
+ case Opcodes.DMUL:
+ case Opcodes.DDIV:
+ case Opcodes.DREM:
+ pop(4);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LSHL:
+ case Opcodes.LSHR:
+ case Opcodes.LUSHR:
+ pop(3);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.IINC:
+ setLocal(arg, INTEGER);
+ break;
+ case Opcodes.I2L:
+ case Opcodes.F2L:
+ pop(1);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.I2F:
+ pop(1);
+ push(FLOAT);
+ break;
+ case Opcodes.I2D:
+ case Opcodes.F2D:
+ pop(1);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.F2I:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.INSTANCEOF:
+ pop(1);
+ push(INTEGER);
+ break;
+ case Opcodes.LCMP:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ pop(4);
+ push(INTEGER);
+ break;
+ case Opcodes.JSR:
+ case Opcodes.RET:
+ throw new IllegalArgumentException("JSR/RET are not supported with computeFrames option");
+ case Opcodes.GETSTATIC:
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.PUTSTATIC:
+ pop(argSymbol.value);
+ break;
+ case Opcodes.GETFIELD:
+ pop(1);
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.PUTFIELD:
+ pop(argSymbol.value);
+ pop();
+ break;
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.INVOKEINTERFACE:
+ pop(argSymbol.value);
+ if (opcode != Opcodes.INVOKESTATIC) {
+ abstractType1 = pop();
+ if (opcode == Opcodes.INVOKESPECIAL && argSymbol.name.charAt(0) == '<') {
+ addInitializedType(abstractType1);
+ }
+ }
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.INVOKEDYNAMIC:
+ pop(argSymbol.value);
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.NEW:
+ push(UNINITIALIZED_KIND | symbolTable.addUninitializedType(argSymbol.value, arg));
+ break;
+ case Opcodes.NEWARRAY:
+ pop();
+ switch (arg) {
+ case Opcodes.T_BOOLEAN:
+ push(ARRAY_OF | BOOLEAN);
break;
- case Opcodes.INVOKEVIRTUAL:
- case Opcodes.INVOKESPECIAL:
- case Opcodes.INVOKESTATIC:
- case Opcodes.INVOKEINTERFACE:
- pop(item.strVal3);
- if (opcode != Opcodes.INVOKESTATIC) {
- t1 = pop();
- if (opcode == Opcodes.INVOKESPECIAL
- && item.strVal2.charAt(0) == '<') {
- init(t1);
- }
- }
- push(cw, item.strVal3);
+ case Opcodes.T_CHAR:
+ push(ARRAY_OF | CHAR);
break;
- case Opcodes.INVOKEDYNAMIC:
- pop(item.strVal2);
- push(cw, item.strVal2);
+ case Opcodes.T_BYTE:
+ push(ARRAY_OF | BYTE);
break;
- case Opcodes.NEW:
- push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg));
+ case Opcodes.T_SHORT:
+ push(ARRAY_OF | SHORT);
break;
- case Opcodes.NEWARRAY:
- pop();
- switch (arg) {
- case Opcodes.T_BOOLEAN:
- push(ARRAY_OF | BOOLEAN);
- break;
- case Opcodes.T_CHAR:
- push(ARRAY_OF | CHAR);
- break;
- case Opcodes.T_BYTE:
- push(ARRAY_OF | BYTE);
- break;
- case Opcodes.T_SHORT:
- push(ARRAY_OF | SHORT);
- break;
- case Opcodes.T_INT:
- push(ARRAY_OF | INTEGER);
- break;
- case Opcodes.T_FLOAT:
- push(ARRAY_OF | FLOAT);
- break;
- case Opcodes.T_DOUBLE:
- push(ARRAY_OF | DOUBLE);
- break;
- // case Opcodes.T_LONG:
- default:
- push(ARRAY_OF | LONG);
- break;
- }
+ case Opcodes.T_INT:
+ push(ARRAY_OF | INTEGER);
break;
- case Opcodes.ANEWARRAY:
- String s = item.strVal1;
- pop();
- if (s.charAt(0) == '[') {
- push(cw, '[' + s);
- } else {
- push(ARRAY_OF | OBJECT | cw.addType(s));
- }
+ case Opcodes.T_FLOAT:
+ push(ARRAY_OF | FLOAT);
break;
- case Opcodes.CHECKCAST:
- s = item.strVal1;
- pop();
- if (s.charAt(0) == '[') {
- push(cw, s);
- } else {
- push(OBJECT | cw.addType(s));
- }
+ case Opcodes.T_DOUBLE:
+ push(ARRAY_OF | DOUBLE);
break;
- // case Opcodes.MULTIANEWARRAY:
- default:
- pop(arg);
- push(cw, item.strVal1);
+ case Opcodes.T_LONG:
+ push(ARRAY_OF | LONG);
break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ break;
+ case Opcodes.ANEWARRAY:
+ String arrayElementType = argSymbol.value;
+ pop();
+ if (arrayElementType.charAt(0) == '[') {
+ push(symbolTable, '[' + arrayElementType);
+ } else {
+ push(ARRAY_OF | REFERENCE_KIND | symbolTable.addType(arrayElementType));
+ }
+ break;
+ case Opcodes.CHECKCAST:
+ String castType = argSymbol.value;
+ pop();
+ if (castType.charAt(0) == '[') {
+ push(symbolTable, castType);
+ } else {
+ push(REFERENCE_KIND | symbolTable.addType(castType));
}
+ break;
+ case Opcodes.MULTIANEWARRAY:
+ pop(arg);
+ push(symbolTable, argSymbol.value);
+ break;
+ default:
+ throw new IllegalArgumentException();
}
+ }
- /**
- * Merges the input frame of the given basic block with the input and output
- * frames of this basic block. Returns true if the input frame of
- * the given label has been changed by this operation.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param frame
- * the basic block whose input frame must be updated.
- * @param edge
- * the kind of the {@link Edge} between this label and 'label'.
- * See {@link Edge#info}.
- * @return true if the input frame of the given label has been
- * changed by this operation.
- */
- boolean merge(final ClassWriter cw, final Frame frame, final int edge) {
- boolean changed = false;
- int i, s, dim, kind, t;
-
- int nLocal = inputLocals.length;
- int nStack = inputStack.length;
- if (frame.inputLocals == null) {
- frame.inputLocals = new int[nLocal];
- changed = true;
- }
+ // -----------------------------------------------------------------------------------------------
+ // Frame merging methods, used in the second step of the stack map frame computation algorithm
+ // -----------------------------------------------------------------------------------------------
- for (i = 0; i < nLocal; ++i) {
- if (outputLocals != null && i < outputLocals.length) {
- s = outputLocals[i];
- if (s == 0) {
- t = inputLocals[i];
- } else {
- dim = s & DIM;
- kind = s & KIND;
- if (kind == BASE) {
- t = s;
- } else {
- if (kind == LOCAL) {
- t = dim + inputLocals[s & VALUE];
- } else {
- t = dim + inputStack[nStack - (s & VALUE)];
- }
- if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
- && (t == LONG || t == DOUBLE)) {
- t = TOP;
- }
- }
- }
- } else {
- t = inputLocals[i];
- }
- if (initializations != null) {
- t = init(cw, t);
- }
- changed |= merge(cw, t, frame.inputLocals, i);
- }
+ /**
+ * Merges the input frame of the given {@link Frame} with the input and output frames of this
+ * {@link Frame}. Returns true if the given frame has been changed by this operation (the
+ * input and output frames of this {@link Frame} are never changed).
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param dstFrame the {@link Frame} whose input frame must be updated. This should be the frame
+ * of a successor, in the control flow graph, of the basic block corresponding to this frame.
+ * @param catchTypeIndex if 'frame' corresponds to an exception handler basic block, the type
+ * table index of the caught exception type, otherwise 0.
+ * @return true if the input frame of 'frame' has been changed by this operation.
+ */
+ final boolean merge(
+ final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) {
+ boolean frameChanged = false;
- if (edge > 0) {
- for (i = 0; i < nLocal; ++i) {
- t = inputLocals[i];
- changed |= merge(cw, t, frame.inputLocals, i);
+ // Compute the concrete types of the local variables at the end of the basic block corresponding
+ // to this frame, by resolving its abstract output types, and merge these concrete types with
+ // those of the local variables in the input frame of dstFrame.
+ int nLocal = inputLocals.length;
+ int nStack = inputStack.length;
+ if (dstFrame.inputLocals == null) {
+ dstFrame.inputLocals = new int[nLocal];
+ frameChanged = true;
+ }
+ for (int i = 0; i < nLocal; ++i) {
+ int concreteOutputType;
+ if (outputLocals != null && i < outputLocals.length) {
+ int abstractOutputType = outputLocals[i];
+ if (abstractOutputType == 0) {
+ // If the local variable has never been assigned in this basic block, it is equal to its
+ // value at the beginning of the block.
+ concreteOutputType = inputLocals[i];
+ } else {
+ int dim = abstractOutputType & DIM_MASK;
+ int kind = abstractOutputType & KIND_MASK;
+ if (kind == LOCAL_KIND) {
+ // By definition, a LOCAL_KIND type designates the concrete type of a local variable at
+ // the beginning of the basic block corresponding to this frame (which is known when
+ // this method is called, but was not when the abstract type was computed).
+ concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
}
- if (frame.inputStack == null) {
- frame.inputStack = new int[1];
- changed = true;
+ } else if (kind == STACK_KIND) {
+ // By definition, a STACK_KIND type designates the concrete type of a local variable at
+ // the beginning of the basic block corresponding to this frame (which is known when
+ // this method is called, but was not when the abstract type was computed).
+ concreteOutputType = dim + inputStack[nStack - (abstractOutputType & VALUE_MASK)];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
}
- changed |= merge(cw, edge, frame.inputStack, 0);
- return changed;
+ } else {
+ concreteOutputType = abstractOutputType;
+ }
}
+ } else {
+ // If the local variable has never been assigned in this basic block, it is equal to its
+ // value at the beginning of the block.
+ concreteOutputType = inputLocals[i];
+ }
+ // concreteOutputType might be an uninitialized type from the input locals or from the input
+ // stack. However, if a constructor has been called for this class type in the basic block,
+ // then this type is no longer uninitialized at the end of basic block.
+ if (initializations != null) {
+ concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
+ }
+ frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputLocals, i);
+ }
- int nInputStack = inputStack.length + owner.inputStackTop;
- if (frame.inputStack == null) {
- frame.inputStack = new int[nInputStack + outputStackTop];
- changed = true;
- }
+ // If dstFrame is an exception handler block, it can be reached from any instruction of the
+ // basic block corresponding to this frame, in particular from the first one. Therefore, the
+ // input locals of dstFrame should be compatible (i.e. merged) with the input locals of this
+ // frame (and the input stack of dstFrame should be compatible, i.e. merged, with a one
+ // element stack containing the caught exception type).
+ if (catchTypeIndex > 0) {
+ for (int i = 0; i < nLocal; ++i) {
+ frameChanged |= merge(symbolTable, inputLocals[i], dstFrame.inputLocals, i);
+ }
+ if (dstFrame.inputStack == null) {
+ dstFrame.inputStack = new int[1];
+ frameChanged = true;
+ }
+ frameChanged |= merge(symbolTable, catchTypeIndex, dstFrame.inputStack, 0);
+ return frameChanged;
+ }
- for (i = 0; i < nInputStack; ++i) {
- t = inputStack[i];
- if (initializations != null) {
- t = init(cw, t);
- }
- changed |= merge(cw, t, frame.inputStack, i);
+ // Compute the concrete types of the stack operands at the end of the basic block corresponding
+ // to this frame, by resolving its abstract output types, and merge these concrete types with
+ // those of the stack operands in the input frame of dstFrame.
+ int nInputStack = inputStack.length + outputStackStart;
+ if (dstFrame.inputStack == null) {
+ dstFrame.inputStack = new int[nInputStack + outputStackTop];
+ frameChanged = true;
+ }
+ // First, do this for the stack operands that have not been popped in the basic block
+ // corresponding to this frame, and which are therefore equal to their value in the input
+ // frame (except for uninitialized types, which may have been initialized).
+ for (int i = 0; i < nInputStack; ++i) {
+ int concreteOutputType = inputStack[i];
+ if (initializations != null) {
+ concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
+ }
+ frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, i);
+ }
+ // Then, do this for the stack operands that have pushed in the basic block (this code is the
+ // same as the one above for local variables).
+ for (int i = 0; i < outputStackTop; ++i) {
+ int concreteOutputType;
+ int abstractOutputType = outputStack[i];
+ int dim = abstractOutputType & DIM_MASK;
+ int kind = abstractOutputType & KIND_MASK;
+ if (kind == LOCAL_KIND) {
+ concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
}
- for (i = 0; i < outputStackTop; ++i) {
- s = outputStack[i];
- dim = s & DIM;
- kind = s & KIND;
- if (kind == BASE) {
- t = s;
- } else {
- if (kind == LOCAL) {
- t = dim + inputLocals[s & VALUE];
- } else {
- t = dim + inputStack[nStack - (s & VALUE)];
- }
- if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
- && (t == LONG || t == DOUBLE)) {
- t = TOP;
- }
- }
- if (initializations != null) {
- t = init(cw, t);
- }
- changed |= merge(cw, t, frame.inputStack, nInputStack + i);
+ } else if (kind == STACK_KIND) {
+ concreteOutputType = dim + inputStack[nStack - (abstractOutputType & VALUE_MASK)];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
}
- return changed;
+ } else {
+ concreteOutputType = abstractOutputType;
+ }
+ if (initializations != null) {
+ concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
+ }
+ frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, nInputStack + i);
}
+ return frameChanged;
+ }
- /**
- * Merges the type at the given index in the given type array with the given
- * type. Returns true if the type array has been modified by this
- * operation.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param t
- * the type with which the type array element must be merged.
- * @param types
- * an array of types.
- * @param index
- * the index of the type that must be merged in 'types'.
- * @return true if the type array has been modified by this
- * operation.
- */
- private static boolean merge(final ClassWriter cw, int t,
- final int[] types, final int index) {
- int u = types[index];
- if (u == t) {
- // if the types are equal, merge(u,t)=u, so there is no change
- return false;
- }
- if ((t & ~DIM) == NULL) {
- if (u == NULL) {
- return false;
- }
- t = NULL;
+ /**
+ * Merges the type at the given index in the given abstract type array with the given type.
+ * Returns true if the type array has been modified by this operation.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param sourceType the abstract type with which the abstract type array element must be merged.
+ * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND} or {@link
+ * #UNINITIALIZED_KIND} kind, with positive or null array dimensions.
+ * @param dstTypes an array of abstract types. These types should be of {@link #CONSTANT_KIND},
+ * {@link #REFERENCE_KIND} or {@link #UNINITIALIZED_KIND} kind, with positive or null array
+ * dimensions.
+ * @param dstIndex the index of the type that must be merged in dstTypes.
+ * @return true if the type array has been modified by this operation.
+ */
+ private static boolean merge(
+ final SymbolTable symbolTable,
+ final int sourceType,
+ final int[] dstTypes,
+ final int dstIndex) {
+ int dstType = dstTypes[dstIndex];
+ if (dstType == sourceType) {
+ // If the types are equal, merge(sourceType, dstType) = dstType, so there is no change.
+ return false;
+ }
+ int srcType = sourceType;
+ if ((sourceType & ~DIM_MASK) == NULL) {
+ if (dstType == NULL) {
+ return false;
+ }
+ srcType = NULL;
+ }
+ if (dstType == 0) {
+ // If dstTypes[dstIndex] has never been assigned, merge(srcType, dstType) = srcType.
+ dstTypes[dstIndex] = srcType;
+ return true;
+ }
+ int mergedType;
+ if ((dstType & DIM_MASK) != 0 || (dstType & KIND_MASK) == REFERENCE_KIND) {
+ // If dstType is a reference type of any array dimension.
+ if (srcType == NULL) {
+ // If srcType is the NULL type, merge(srcType, dstType) = dstType, so there is no change.
+ return false;
+ } else if ((srcType & (DIM_MASK | KIND_MASK)) == (dstType & (DIM_MASK | KIND_MASK))) {
+ // If srcType has the same array dimension and the same kind as dstType.
+ if ((dstType & KIND_MASK) == REFERENCE_KIND) {
+ // If srcType and dstType are reference types with the same array dimension,
+ // merge(srcType, dstType) = dim(srcType) | common super class of srcType and dstType.
+ mergedType =
+ (srcType & DIM_MASK)
+ | REFERENCE_KIND
+ | symbolTable.addMergedType(srcType & VALUE_MASK, dstType & VALUE_MASK);
+ } else {
+ // If srcType and dstType are array types of equal dimension but different element types,
+ // merge(srcType, dstType) = dim(srcType) - 1 | java/lang/Object.
+ int mergedDim = ELEMENT_OF + (srcType & DIM_MASK);
+ mergedType = mergedDim | REFERENCE_KIND | symbolTable.addType("java/lang/Object");
}
- if (u == 0) {
- // if types[index] has never been assigned, merge(u,t)=t
- types[index] = t;
- return true;
+ } else if ((srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND) {
+ // If srcType is any other reference or array type,
+ // merge(srcType, dstType) = min(srcDdim, dstDim) | java/lang/Object
+ // where srcDim is the array dimension of srcType, minus 1 if srcType is an array type
+ // with a non reference element type (and similarly for dstDim).
+ int srcDim = srcType & DIM_MASK;
+ if (srcDim != 0 && (srcType & KIND_MASK) != REFERENCE_KIND) {
+ srcDim = ELEMENT_OF + srcDim;
}
- int v;
- if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) {
- // if u is a reference type of any dimension
- if (t == NULL) {
- // if t is the NULL type, merge(u,t)=u, so there is no change
- return false;
- } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) {
- if ((u & BASE_KIND) == OBJECT) {
- // if t is also a reference type, and if u and t have the
- // same dimension merge(u,t) = dim(t) | common parent of the
- // element types of u and t
- v = (t & DIM) | OBJECT
- | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE);
- } else {
- // if u and t are array types, but not with the same element
- // type, merge(u,t)=java/lang/Object
- v = OBJECT | cw.addType("java/lang/Object");
- }
- } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) {
- // if t is any other reference or array type,
- // merge(u,t)=java/lang/Object
- v = OBJECT | cw.addType("java/lang/Object");
- } else {
- // if t is any other type, merge(u,t)=TOP
- v = TOP;
- }
- } else if (u == NULL) {
- // if u is the NULL type, merge(u,t)=t,
- // or TOP if t is not a reference type
- v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP;
- } else {
- // if u is any other type, merge(u,t)=TOP whatever t
- v = TOP;
+ int dstDim = dstType & DIM_MASK;
+ if (dstDim != 0 && (dstType & KIND_MASK) != REFERENCE_KIND) {
+ dstDim = ELEMENT_OF + dstDim;
}
- if (u != v) {
- types[index] = v;
- return true;
+ mergedType =
+ Math.min(srcDim, dstDim) | REFERENCE_KIND | symbolTable.addType("java/lang/Object");
+ } else {
+ // If srcType is any other type, merge(srcType, dstType) = TOP.
+ mergedType = TOP;
+ }
+ } else if (dstType == NULL) {
+ // If dstType is the NULL type, merge(srcType, dstType) = srcType, or TOP if srcType is not a
+ // an array type or a reference type.
+ mergedType =
+ (srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND ? srcType : TOP;
+ } else {
+ // If dstType is any other type, merge(srcType, dstType) = TOP whatever srcType.
+ mergedType = TOP;
+ }
+ if (mergedType != dstType) {
+ dstTypes[dstIndex] = mergedType;
+ return true;
+ }
+ return false;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Frame output methods, to generate StackMapFrame attributes
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Makes the given {@link MethodWriter} visit the input frame of this {@link Frame}. The visit is
+ * done with the {@link MethodWriter#visitFrameStart}, {@link MethodWriter#visitAbstractType} and
+ * {@link MethodWriter#visitFrameEnd} methods.
+ *
+ * @param methodWriter the {@link MethodWriter} that should visit the input frame of this {@link
+ * Frame}.
+ */
+ final void accept(final MethodWriter methodWriter) {
+ // Compute the number of locals, ignoring TOP types that are just after a LONG or a DOUBLE, and
+ // all trailing TOP types.
+ int[] localTypes = inputLocals;
+ int nLocal = 0;
+ int nTrailingTop = 0;
+ int i = 0;
+ while (i < localTypes.length) {
+ int localType = localTypes[i];
+ i += (localType == LONG || localType == DOUBLE) ? 2 : 1;
+ if (localType == TOP) {
+ nTrailingTop++;
+ } else {
+ nLocal += nTrailingTop + 1;
+ nTrailingTop = 0;
+ }
+ }
+ // Compute the stack size, ignoring TOP types that are just after a LONG or a DOUBLE.
+ int[] stackTypes = inputStack;
+ int nStack = 0;
+ i = 0;
+ while (i < stackTypes.length) {
+ int stackType = stackTypes[i];
+ i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1;
+ nStack++;
+ }
+ // Visit the frame and its content.
+ int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, nLocal, nStack);
+ i = 0;
+ while (nLocal-- > 0) {
+ int localType = localTypes[i];
+ i += (localType == LONG || localType == DOUBLE) ? 2 : 1;
+ methodWriter.visitAbstractType(frameIndex++, localType);
+ }
+ i = 0;
+ while (nStack-- > 0) {
+ int stackType = stackTypes[i];
+ i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1;
+ methodWriter.visitAbstractType(frameIndex++, stackType);
+ }
+ methodWriter.visitFrameEnd();
+ }
+
+ /**
+ * Put the given abstract type in the given ByteVector, using the JVMS verification_type_info
+ * format used in StackMapTable attributes.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param abstractType an abstract type, restricted to {@link Frame#CONSTANT_KIND}, {@link
+ * Frame#REFERENCE_KIND} or {@link Frame#UNINITIALIZED_KIND} types.
+ * @param output where the abstract type must be put.
+ * @see JVMS
+ * 4.7.4
+ */
+ static void putAbstractType(
+ final SymbolTable symbolTable, final int abstractType, final ByteVector output) {
+ int arrayDimensions = (abstractType & Frame.DIM_MASK) >> DIM_SHIFT;
+ if (arrayDimensions == 0) {
+ int typeValue = abstractType & VALUE_MASK;
+ switch (abstractType & KIND_MASK) {
+ case CONSTANT_KIND:
+ output.putByte(typeValue);
+ break;
+ case REFERENCE_KIND:
+ output
+ .putByte(ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass(symbolTable.getType(typeValue).value).index);
+ break;
+ case UNINITIALIZED_KIND:
+ output.putByte(ITEM_UNINITIALIZED).putShort((int) symbolTable.getType(typeValue).data);
+ break;
+ default:
+ throw new AssertionError();
+ }
+ } else {
+ // Case of an array type, we need to build its descriptor first.
+ StringBuilder typeDescriptor = new StringBuilder();
+ while (arrayDimensions-- > 0) {
+ typeDescriptor.append('[');
+ }
+ if ((abstractType & KIND_MASK) == REFERENCE_KIND) {
+ typeDescriptor
+ .append('L')
+ .append(symbolTable.getType(abstractType & VALUE_MASK).value)
+ .append(';');
+ } else {
+ switch (abstractType & VALUE_MASK) {
+ case Frame.ITEM_ASM_BOOLEAN:
+ typeDescriptor.append('Z');
+ break;
+ case Frame.ITEM_ASM_BYTE:
+ typeDescriptor.append('B');
+ break;
+ case Frame.ITEM_ASM_CHAR:
+ typeDescriptor.append('C');
+ break;
+ case Frame.ITEM_ASM_SHORT:
+ typeDescriptor.append('S');
+ break;
+ case Frame.ITEM_INTEGER:
+ typeDescriptor.append('I');
+ break;
+ case Frame.ITEM_FLOAT:
+ typeDescriptor.append('F');
+ break;
+ case Frame.ITEM_LONG:
+ typeDescriptor.append('J');
+ break;
+ case Frame.ITEM_DOUBLE:
+ typeDescriptor.append('D');
+ break;
+ default:
+ throw new AssertionError();
}
- return false;
+ }
+ output
+ .putByte(ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass(typeDescriptor.toString()).index);
}
+ }
}
diff --git a/src/jvm/clojure/asm/Handle.java b/src/jvm/clojure/asm/Handle.java
index a147cf1af8..9121744932 100644
--- a/src/jvm/clojure/asm/Handle.java
+++ b/src/jvm/clojure/asm/Handle.java
@@ -1,32 +1,30 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
@@ -38,130 +36,154 @@
*/
public final class Handle {
- /**
- * The kind of field or method designated by this Handle. Should be
- * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
- * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
- * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- */
- final int tag;
-
- /**
- * The internal name of the field or method designed by this handle.
- */
- final String owner;
-
- /**
- * The name of the field or method designated by this handle.
- */
- final String name;
-
- /**
- * The descriptor of the field or method designated by this handle.
- */
- final String desc;
-
- /**
- * Constructs a new field or method handle.
- *
- * @param tag
- * the kind of field or method designated by this Handle. Must be
- * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
- * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
- * {@link Opcodes#H_INVOKEVIRTUAL},
- * {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- * @param owner
- * the internal name of the field or method designed by this
- * handle.
- * @param name
- * the name of the field or method designated by this handle.
- * @param desc
- * the descriptor of the field or method designated by this
- * handle.
- */
- public Handle(int tag, String owner, String name, String desc) {
- this.tag = tag;
- this.owner = owner;
- this.name = name;
- this.desc = desc;
- }
+ /**
+ * The kind of field or method designated by this Handle. Should be {@link Opcodes#H_GETFIELD},
+ * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
+ * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ private final int tag;
- /**
- * Returns the kind of field or method designated by this handle.
- *
- * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
- * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
- * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- */
- public int getTag() {
- return tag;
- }
+ /** The internal name of the class that owns the field or method designated by this handle. */
+ private final String owner;
- /**
- * Returns the internal name of the field or method designed by this handle.
- *
- * @return the internal name of the field or method designed by this handle.
- */
- public String getOwner() {
- return owner;
- }
+ /** The name of the field or method designated by this handle. */
+ private final String name;
- /**
- * Returns the name of the field or method designated by this handle.
- *
- * @return the name of the field or method designated by this handle.
- */
- public String getName() {
- return name;
- }
+ /** The descriptor of the field or method designated by this handle. */
+ private final String descriptor;
- /**
- * Returns the descriptor of the field or method designated by this handle.
- *
- * @return the descriptor of the field or method designated by this handle.
- */
- public String getDesc() {
- return desc;
- }
+ /** Whether the owner is an interface or not. */
+ private final boolean isInterface;
- @Override
- public boolean equals(Object obj) {
- if (obj == this) {
- return true;
- }
- if (!(obj instanceof Handle)) {
- return false;
- }
- Handle h = (Handle) obj;
- return tag == h.tag && owner.equals(h.owner) && name.equals(h.name)
- && desc.equals(h.desc);
- }
+ /**
+ * Constructs a new field or method handle.
+ *
+ * @param tag the kind of field or method designated by this Handle. Must be {@link
+ * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link
+ * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
+ * Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the class that owns the field or method designated by this
+ * handle.
+ * @param name the name of the field or method designated by this handle.
+ * @param descriptor the descriptor of the field or method designated by this handle.
+ * @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String,
+ * boolean)}.
+ */
+ @Deprecated
+ public Handle(final int tag, final String owner, final String name, final String descriptor) {
+ this(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
+ }
- @Override
- public int hashCode() {
- return tag + owner.hashCode() * name.hashCode() * desc.hashCode();
- }
+ /**
+ * Constructs a new field or method handle.
+ *
+ * @param tag the kind of field or method designated by this Handle. Must be {@link
+ * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link
+ * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
+ * Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the class that owns the field or method designated by this
+ * handle.
+ * @param name the name of the field or method designated by this handle.
+ * @param descriptor the descriptor of the field or method designated by this handle.
+ * @param isInterface whether the owner is an interface or not.
+ */
+ public Handle(
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ this.tag = tag;
+ this.owner = owner;
+ this.name = name;
+ this.descriptor = descriptor;
+ this.isInterface = isInterface;
+ }
+
+ /**
+ * Returns the kind of field or method designated by this handle.
+ *
+ * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
+ * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
+ * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
+ * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * Returns the internal name of the class that owns the field or method designated by this handle.
+ *
+ * @return the internal name of the class that owns the field or method designated by this handle.
+ */
+ public String getOwner() {
+ return owner;
+ }
+
+ /**
+ * Returns the name of the field or method designated by this handle.
+ *
+ * @return the name of the field or method designated by this handle.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the descriptor of the field or method designated by this handle.
+ *
+ * @return the descriptor of the field or method designated by this handle.
+ */
+ public String getDesc() {
+ return descriptor;
+ }
- /**
- * Returns the textual representation of this handle. The textual
- * representation is:
- *
- *
- * owner '.' name desc ' ' '(' tag ')'
- *
- *
- * . As this format is unambiguous, it can be parsed if necessary.
- */
- @Override
- public String toString() {
- return owner + '.' + name + desc + " (" + tag + ')';
+ /**
+ * Returns true if the owner of the field or method designated by this handle is an interface.
+ *
+ * @return true if the owner of the field or method designated by this handle is an interface.
+ */
+ public boolean isInterface() {
+ return isInterface;
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (!(object instanceof Handle)) {
+ return false;
}
+ Handle handle = (Handle) object;
+ return tag == handle.tag
+ && isInterface == handle.isInterface
+ && owner.equals(handle.owner)
+ && name.equals(handle.name)
+ && descriptor.equals(handle.descriptor);
+ }
+
+ @Override
+ public int hashCode() {
+ return tag
+ + (isInterface ? 64 : 0)
+ + owner.hashCode() * name.hashCode() * descriptor.hashCode();
+ }
+
+ /**
+ * Returns the textual representation of this handle. The textual representation is:
+ *
+ *
+ *
for a reference to a class: owner "." name descriptor " (" tag ")",
+ *
for a reference to an interface: owner "." name descriptor " (" tag " itf)".
+ *
+ */
+ @Override
+ public String toString() {
+ return owner + '.' + name + descriptor + " (" + tag + (isInterface ? " itf" : "") + ')';
+ }
}
diff --git a/src/jvm/clojure/asm/Handler.java b/src/jvm/clojure/asm/Handler.java
index bc0579e01b..41002b62be 100644
--- a/src/jvm/clojure/asm/Handler.java
+++ b/src/jvm/clojure/asm/Handler.java
@@ -1,121 +1,198 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * Information about an exception handler block.
+ * Information about an exception handler. Corresponds to an element of the exception_table array of
+ * a Code attribute, as defined in the Java Virtual Machine Specification (JVMS). Handler instances
+ * can be chained together, with their {@link #nextHandler} field, to describe a full JVMS
+ * exception_table array.
*
+ * @see JVMS
+ * 4.7.3
* @author Eric Bruneton
*/
-class Handler {
+final class Handler {
+
+ /**
+ * The start_pc field of this JVMS exception_table entry. Corresponds to the beginning of the
+ * exception handler's scope (inclusive).
+ */
+ final Label startPc;
+
+ /**
+ * The end_pc field of this JVMS exception_table entry. Corresponds to the end of the exception
+ * handler's scope (exclusive).
+ */
+ final Label endPc;
+
+ /**
+ * The handler_pc field of this JVMS exception_table entry. Corresponding to the beginning of the
+ * exception handler's code.
+ */
+ final Label handlerPc;
- /**
- * Beginning of the exception handler's scope (inclusive).
- */
- Label start;
+ /**
+ * The catch_type field of this JVMS exception_table entry. This is the constant pool index of the
+ * internal name of the type of exceptions handled by this handler, or 0 to catch any exceptions.
+ */
+ final int catchType;
- /**
- * End of the exception handler's scope (exclusive).
- */
- Label end;
+ /**
+ * The internal name of the type of exceptions handled by this handler, or null to catch
+ * any exceptions.
+ */
+ final String catchTypeDescriptor;
- /**
- * Beginning of the exception handler's code.
- */
- Label handler;
+ /** The next exception handler. */
+ Handler nextHandler;
- /**
- * Internal name of the type of exceptions handled by this handler, or
- * null to catch any exceptions.
- */
- String desc;
+ /**
+ * Constructs a new Handler.
+ *
+ * @param startPc the start_pc field of this JVMS exception_table entry.
+ * @param endPc the end_pc field of this JVMS exception_table entry.
+ * @param handlerPc the handler_pc field of this JVMS exception_table entry.
+ * @param catchType The catch_type field of this JVMS exception_table entry.
+ * @param catchTypeDescriptor The internal name of the type of exceptions handled by this handler,
+ * or null to catch any exceptions.
+ */
+ Handler(
+ final Label startPc,
+ final Label endPc,
+ final Label handlerPc,
+ final int catchType,
+ final String catchTypeDescriptor) {
+ this.startPc = startPc;
+ this.endPc = endPc;
+ this.handlerPc = handlerPc;
+ this.catchType = catchType;
+ this.catchTypeDescriptor = catchTypeDescriptor;
+ }
- /**
- * Constant pool index of the internal name of the type of exceptions
- * handled by this handler, or 0 to catch any exceptions.
- */
- int type;
+ /**
+ * Constructs a new Handler from the given one, with a different scope.
+ *
+ * @param handler an existing Handler.
+ * @param startPc the start_pc field of this JVMS exception_table entry.
+ * @param endPc the end_pc field of this JVMS exception_table entry.
+ */
+ Handler(final Handler handler, final Label startPc, final Label endPc) {
+ this(startPc, endPc, handler.handlerPc, handler.catchType, handler.catchTypeDescriptor);
+ this.nextHandler = handler.nextHandler;
+ }
+
+ /**
+ * Removes the range between start and end from the Handler list that begins with the given
+ * element.
+ *
+ * @param firstHandler the beginning of a Handler list. May be null.
+ * @param start the start of the range to be removed.
+ * @param end the end of the range to be removed. Maybe null.
+ * @return the exception handler list with the start-end range removed.
+ */
+ static Handler removeRange(final Handler firstHandler, final Label start, final Label end) {
+ if (firstHandler == null) {
+ return null;
+ } else {
+ firstHandler.nextHandler = removeRange(firstHandler.nextHandler, start, end);
+ }
+ int handlerStart = firstHandler.startPc.bytecodeOffset;
+ int handlerEnd = firstHandler.endPc.bytecodeOffset;
+ int rangeStart = start.bytecodeOffset;
+ int rangeEnd = end == null ? Integer.MAX_VALUE : end.bytecodeOffset;
+ // Return early if [handlerStart,handlerEnd[ and [rangeStart,rangeEnd[ don't intersect.
+ if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) {
+ return firstHandler;
+ }
+ if (rangeStart <= handlerStart) {
+ if (rangeEnd >= handlerEnd) {
+ // If [handlerStart,handlerEnd[ is included in [rangeStart,rangeEnd[, remove firstHandler.
+ return firstHandler.nextHandler;
+ } else {
+ // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [rangeEnd,handlerEnd[
+ return new Handler(firstHandler, end, firstHandler.endPc);
+ }
+ } else if (rangeEnd >= handlerEnd) {
+ // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [handlerStart,rangeStart[
+ return new Handler(firstHandler, firstHandler.startPc, start);
+ } else {
+ // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ =
+ // [handlerStart,rangeStart[ + [rangeEnd,handerEnd[
+ firstHandler.nextHandler = new Handler(firstHandler, end, firstHandler.endPc);
+ return new Handler(firstHandler, firstHandler.startPc, start);
+ }
+ }
+
+ /**
+ * Returns the number of elements of the Handler list that begins with the given element.
+ *
+ * @param firstHandler the beginning of a Handler list. May be null.
+ * @return the number of elements of the Handler list that begins with 'handler'.
+ */
+ static int getExceptionTableLength(final Handler firstHandler) {
+ int length = 0;
+ Handler handler = firstHandler;
+ while (handler != null) {
+ length++;
+ handler = handler.nextHandler;
+ }
+ return length;
+ }
- /**
- * Next exception handler block info.
- */
- Handler next;
+ /**
+ * Returns the size in bytes of the JVMS exception_table corresponding to the Handler list that
+ * begins with the given element. This includes the exception_table_length field.
+ *
+ * @param firstHandler the beginning of a Handler list. May be null.
+ * @return the size in bytes of the exception_table_length and exception_table structures.
+ */
+ static int getExceptionTableSize(final Handler firstHandler) {
+ return 2 + 8 * getExceptionTableLength(firstHandler);
+ }
- /**
- * Removes the range between start and end from the given exception
- * handlers.
- *
- * @param h
- * an exception handler list.
- * @param start
- * the start of the range to be removed.
- * @param end
- * the end of the range to be removed. Maybe null.
- * @return the exception handler list with the start-end range removed.
- */
- static Handler remove(Handler h, Label start, Label end) {
- if (h == null) {
- return null;
- } else {
- h.next = remove(h.next, start, end);
- }
- int hstart = h.start.position;
- int hend = h.end.position;
- int s = start.position;
- int e = end == null ? Integer.MAX_VALUE : end.position;
- // if [hstart,hend[ and [s,e[ intervals intersect...
- if (s < hend && e > hstart) {
- if (s <= hstart) {
- if (e >= hend) {
- // [hstart,hend[ fully included in [s,e[, h removed
- h = h.next;
- } else {
- // [hstart,hend[ minus [s,e[ = [e,hend[
- h.start = end;
- }
- } else if (e >= hend) {
- // [hstart,hend[ minus [s,e[ = [hstart,s[
- h.end = start;
- } else {
- // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[
- Handler g = new Handler();
- g.start = end;
- g.end = h.end;
- g.handler = h.handler;
- g.desc = h.desc;
- g.type = h.type;
- g.next = h.next;
- h.end = start;
- h.next = g;
- }
- }
- return h;
+ /**
+ * Puts the JVMS exception_table corresponding to the Handler list that begins with the given
+ * element. This includes the exception_table_length field.
+ *
+ * @param firstHandler the beginning of a Handler list. May be null.
+ * @param output where the exception_table_length and exception_table structures must be put.
+ */
+ static void putExceptionTable(final Handler firstHandler, final ByteVector output) {
+ output.putShort(getExceptionTableLength(firstHandler));
+ Handler handler = firstHandler;
+ while (handler != null) {
+ output
+ .putShort(handler.startPc.bytecodeOffset)
+ .putShort(handler.endPc.bytecodeOffset)
+ .putShort(handler.handlerPc.bytecodeOffset)
+ .putShort(handler.catchType);
+ handler = handler.nextHandler;
}
+ }
}
diff --git a/src/jvm/clojure/asm/Item.java b/src/jvm/clojure/asm/Item.java
deleted file mode 100644
index 274a548319..0000000000
--- a/src/jvm/clojure/asm/Item.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-package clojure.asm;
-
-/**
- * A constant pool item. Constant pool items can be created with the 'newXXX'
- * methods in the {@link ClassWriter} class.
- *
- * @author Eric Bruneton
- */
-final class Item {
-
- /**
- * Index of this item in the constant pool.
- */
- int index;
-
- /**
- * Type of this constant pool item. A single class is used to represent all
- * constant pool item types, in order to minimize the bytecode size of this
- * package. The value of this field is one of {@link ClassWriter#INT},
- * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT},
- * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8},
- * {@link ClassWriter#STR}, {@link ClassWriter#CLASS},
- * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD},
- * {@link ClassWriter#METH}, {@link ClassWriter#IMETH},
- * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}.
- *
- * MethodHandle constant 9 variations are stored using a range of 9 values
- * from {@link ClassWriter#HANDLE_BASE} + 1 to
- * {@link ClassWriter#HANDLE_BASE} + 9.
- *
- * Special Item types are used for Items that are stored in the ClassWriter
- * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
- * avoid clashes with normal constant pool items in the ClassWriter constant
- * pool's hash table. These special item types are
- * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and
- * {@link ClassWriter#TYPE_MERGED}.
- */
- int type;
-
- /**
- * Value of this item, for an integer item.
- */
- int intVal;
-
- /**
- * Value of this item, for a long item.
- */
- long longVal;
-
- /**
- * First part of the value of this item, for items that do not hold a
- * primitive value.
- */
- String strVal1;
-
- /**
- * Second part of the value of this item, for items that do not hold a
- * primitive value.
- */
- String strVal2;
-
- /**
- * Third part of the value of this item, for items that do not hold a
- * primitive value.
- */
- String strVal3;
-
- /**
- * The hash code value of this constant pool item.
- */
- int hashCode;
-
- /**
- * Link to another constant pool item, used for collision lists in the
- * constant pool's hash table.
- */
- Item next;
-
- /**
- * Constructs an uninitialized {@link Item}.
- */
- Item() {
- }
-
- /**
- * Constructs an uninitialized {@link Item} for constant pool element at
- * given position.
- *
- * @param index
- * index of the item to be constructed.
- */
- Item(final int index) {
- this.index = index;
- }
-
- /**
- * Constructs a copy of the given item.
- *
- * @param index
- * index of the item to be constructed.
- * @param i
- * the item that must be copied into the item to be constructed.
- */
- Item(final int index, final Item i) {
- this.index = index;
- type = i.type;
- intVal = i.intVal;
- longVal = i.longVal;
- strVal1 = i.strVal1;
- strVal2 = i.strVal2;
- strVal3 = i.strVal3;
- hashCode = i.hashCode;
- }
-
- /**
- * Sets this item to an integer item.
- *
- * @param intVal
- * the value of this item.
- */
- void set(final int intVal) {
- this.type = ClassWriter.INT;
- this.intVal = intVal;
- this.hashCode = 0x7FFFFFFF & (type + intVal);
- }
-
- /**
- * Sets this item to a long item.
- *
- * @param longVal
- * the value of this item.
- */
- void set(final long longVal) {
- this.type = ClassWriter.LONG;
- this.longVal = longVal;
- this.hashCode = 0x7FFFFFFF & (type + (int) longVal);
- }
-
- /**
- * Sets this item to a float item.
- *
- * @param floatVal
- * the value of this item.
- */
- void set(final float floatVal) {
- this.type = ClassWriter.FLOAT;
- this.intVal = Float.floatToRawIntBits(floatVal);
- this.hashCode = 0x7FFFFFFF & (type + (int) floatVal);
- }
-
- /**
- * Sets this item to a double item.
- *
- * @param doubleVal
- * the value of this item.
- */
- void set(final double doubleVal) {
- this.type = ClassWriter.DOUBLE;
- this.longVal = Double.doubleToRawLongBits(doubleVal);
- this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal);
- }
-
- /**
- * Sets this item to an item that do not hold a primitive value.
- *
- * @param type
- * the type of this item.
- * @param strVal1
- * first part of the value of this item.
- * @param strVal2
- * second part of the value of this item.
- * @param strVal3
- * third part of the value of this item.
- */
- void set(final int type, final String strVal1, final String strVal2,
- final String strVal3) {
- this.type = type;
- this.strVal1 = strVal1;
- this.strVal2 = strVal2;
- this.strVal3 = strVal3;
- switch (type) {
- case ClassWriter.UTF8:
- case ClassWriter.STR:
- case ClassWriter.CLASS:
- case ClassWriter.MTYPE:
- case ClassWriter.TYPE_NORMAL:
- hashCode = 0x7FFFFFFF & (type + strVal1.hashCode());
- return;
- case ClassWriter.NAME_TYPE: {
- hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
- * strVal2.hashCode());
- return;
- }
- // ClassWriter.FIELD:
- // ClassWriter.METH:
- // ClassWriter.IMETH:
- // ClassWriter.HANDLE_BASE + 1..9
- default:
- hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
- * strVal2.hashCode() * strVal3.hashCode());
- }
- }
-
- /**
- * Sets the item to an InvokeDynamic item.
- *
- * @param name
- * invokedynamic's name.
- * @param desc
- * invokedynamic's desc.
- * @param bsmIndex
- * zero based index into the class attribute BootrapMethods.
- */
- void set(String name, String desc, int bsmIndex) {
- this.type = ClassWriter.INDY;
- this.longVal = bsmIndex;
- this.strVal1 = name;
- this.strVal2 = desc;
- this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex
- * strVal1.hashCode() * strVal2.hashCode());
- }
-
- /**
- * Sets the item to a BootstrapMethod item.
- *
- * @param position
- * position in byte in the class attribute BootrapMethods.
- * @param hashCode
- * hashcode of the item. This hashcode is processed from the
- * hashcode of the bootstrap method and the hashcode of all
- * bootstrap arguments.
- */
- void set(int position, int hashCode) {
- this.type = ClassWriter.BSM;
- this.intVal = position;
- this.hashCode = hashCode;
- }
-
- /**
- * Indicates if the given item is equal to this one. This method assumes
- * that the two items have the same {@link #type}.
- *
- * @param i
- * the item to be compared to this one. Both items must have the
- * same {@link #type}.
- * @return true if the given item if equal to this one,
- * false otherwise.
- */
- boolean isEqualTo(final Item i) {
- switch (type) {
- case ClassWriter.UTF8:
- case ClassWriter.STR:
- case ClassWriter.CLASS:
- case ClassWriter.MTYPE:
- case ClassWriter.TYPE_NORMAL:
- return i.strVal1.equals(strVal1);
- case ClassWriter.TYPE_MERGED:
- case ClassWriter.LONG:
- case ClassWriter.DOUBLE:
- return i.longVal == longVal;
- case ClassWriter.INT:
- case ClassWriter.FLOAT:
- return i.intVal == intVal;
- case ClassWriter.TYPE_UNINIT:
- return i.intVal == intVal && i.strVal1.equals(strVal1);
- case ClassWriter.NAME_TYPE:
- return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2);
- case ClassWriter.INDY: {
- return i.longVal == longVal && i.strVal1.equals(strVal1)
- && i.strVal2.equals(strVal2);
- }
- // case ClassWriter.FIELD:
- // case ClassWriter.METH:
- // case ClassWriter.IMETH:
- // case ClassWriter.HANDLE_BASE + 1..9
- default:
- return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2)
- && i.strVal3.equals(strVal3);
- }
- }
-
-}
diff --git a/src/jvm/clojure/asm/Label.java b/src/jvm/clojure/asm/Label.java
index 735e1b91ec..86afe087de 100644
--- a/src/jvm/clojure/asm/Label.java
+++ b/src/jvm/clojure/asm/Label.java
@@ -1,560 +1,621 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A label represents a position in the bytecode of a method. Labels are used
- * for jump, goto, and switch instructions, and for try catch blocks. A label
- * designates the instruction that is just after. Note however that there
- * can be other elements between a label and the instruction it designates (such
+ * A position in the bytecode of a method. Labels are used for jump, goto, and switch instructions,
+ * and for try catch blocks. A label designates the instruction that is just after. Note
+ * however that there can be other elements between a label and the instruction it designates (such
* as other labels, stack map frames, line numbers, etc.).
*
* @author Eric Bruneton
*/
public class Label {
- /**
- * Indicates if this label is only used for debug attributes. Such a label
- * is not the start of a basic block, the target of a jump instruction, or
- * an exception handler. It can be safely ignored in control flow graph
- * analysis algorithms (for optimization purposes).
- */
- static final int DEBUG = 1;
-
- /**
- * Indicates if the position of this label is known.
- */
- static final int RESOLVED = 2;
-
- /**
- * Indicates if this label has been updated, after instruction resizing.
- */
- static final int RESIZED = 4;
-
- /**
- * Indicates if this basic block has been pushed in the basic block stack.
- * See {@link MethodWriter#visitMaxs visitMaxs}.
- */
- static final int PUSHED = 8;
-
- /**
- * Indicates if this label is the target of a jump instruction, or the start
- * of an exception handler.
- */
- static final int TARGET = 16;
-
- /**
- * Indicates if a stack map frame must be stored for this label.
- */
- static final int STORE = 32;
-
- /**
- * Indicates if this label corresponds to a reachable basic block.
- */
- static final int REACHABLE = 64;
-
- /**
- * Indicates if this basic block ends with a JSR instruction.
- */
- static final int JSR = 128;
-
- /**
- * Indicates if this basic block ends with a RET instruction.
- */
- static final int RET = 256;
-
- /**
- * Indicates if this basic block is the start of a subroutine.
- */
- static final int SUBROUTINE = 512;
-
- /**
- * Indicates if this subroutine basic block has been visited by a
- * visitSubroutine(null, ...) call.
- */
- static final int VISITED = 1024;
-
- /**
- * Indicates if this subroutine basic block has been visited by a
- * visitSubroutine(!null, ...) call.
- */
- static final int VISITED2 = 2048;
-
- /**
- * Field used to associate user information to a label. Warning: this field
- * is used by the ASM tree package. In order to use it with the ASM tree
- * package you must override the
- * {@link clojure.asm.tree.MethodNode#getLabelNode} method.
- */
- public Object info;
-
- /**
- * Flags that indicate the status of this label.
- *
- * @see #DEBUG
- * @see #RESOLVED
- * @see #RESIZED
- * @see #PUSHED
- * @see #TARGET
- * @see #STORE
- * @see #REACHABLE
- * @see #JSR
- * @see #RET
- */
- int status;
-
- /**
- * The line number corresponding to this label, if known.
- */
- int line;
-
- /**
- * The position of this label in the code, if known.
- */
- int position;
-
- /**
- * Number of forward references to this label, times two.
- */
- private int referenceCount;
-
- /**
- * Informations about forward references. Each forward reference is
- * described by two consecutive integers in this array: the first one is the
- * position of the first byte of the bytecode instruction that contains the
- * forward reference, while the second is the position of the first byte of
- * the forward reference itself. In fact the sign of the first integer
- * indicates if this reference uses 2 or 4 bytes, and its absolute value
- * gives the position of the bytecode instruction. This array is also used
- * as a bitset to store the subroutines to which a basic block belongs. This
- * information is needed in {@linked MethodWriter#visitMaxs}, after all
- * forward references have been resolved. Hence the same array can be used
- * for both purposes without problems.
- */
- private int[] srcAndRefPositions;
-
- // ------------------------------------------------------------------------
-
- /*
- * Fields for the control flow and data flow graph analysis algorithms (used
- * to compute the maximum stack size or the stack map frames). A control
- * flow graph contains one node per "basic block", and one edge per "jump"
- * from one basic block to another. Each node (i.e., each basic block) is
- * represented by the Label object that corresponds to the first instruction
- * of this basic block. Each node also stores the list of its successors in
- * the graph, as a linked list of Edge objects.
- *
- * The control flow analysis algorithms used to compute the maximum stack
- * size or the stack map frames are similar and use two steps. The first
- * step, during the visit of each instruction, builds information about the
- * state of the local variables and the operand stack at the end of each
- * basic block, called the "output frame", relatively to the frame
- * state at the beginning of the basic block, which is called the "input
- * frame", and which is unknown during this step. The second step, in
- * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes
- * information about the input frame of each basic block, from the input
- * state of the first basic block (known from the method signature), and by
- * the using the previously computed relative output frames.
- *
- * The algorithm used to compute the maximum stack size only computes the
- * relative output and absolute input stack heights, while the algorithm
- * used to compute stack map frames computes relative output frames and
- * absolute input frames.
- */
-
- /**
- * Start of the output stack relatively to the input stack. The exact
- * semantics of this field depends on the algorithm that is used.
- *
- * When only the maximum stack size is computed, this field is the number of
- * elements in the input stack.
- *
- * When the stack map frames are completely computed, this field is the
- * offset of the first output stack element relatively to the top of the
- * input stack. This offset is always negative or null. A null offset means
- * that the output stack must be appended to the input stack. A -n offset
- * means that the first n output stack elements must replace the top n input
- * stack elements, and that the other elements must be appended to the input
- * stack.
- */
- int inputStackTop;
-
- /**
- * Maximum height reached by the output stack, relatively to the top of the
- * input stack. This maximum is always positive or null.
- */
- int outputStackMax;
-
- /**
- * Information about the input and output stack map frames of this basic
- * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES}
- * option is used.
- */
- Frame frame;
-
- /**
- * The successor of this label, in the order they are visited. This linked
- * list does not include labels used for debug info only. If
- * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it
- * does not contain successive labels that denote the same bytecode position
- * (in this case only the first label appears in this list).
- */
- Label successor;
-
- /**
- * The successors of this node in the control flow graph. These successors
- * are stored in a linked list of {@link Edge Edge} objects, linked to each
- * other by their {@link Edge#next} field.
- */
- Edge successors;
-
- /**
- * The next basic block in the basic block stack. This stack is used in the
- * main loop of the fix point algorithm used in the second step of the
- * control flow analysis algorithms. It is also used in
- * {@link #visitSubroutine} to avoid using a recursive method.
- *
- * @see MethodWriter#visitMaxs
- */
- Label next;
-
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new label.
- */
- public Label() {
+ /**
+ * A flag indicating that a label is only used for debug attributes. Such a label is not the start
+ * of a basic block, the target of a jump instruction, or an exception handler. It can be safely
+ * ignored in control flow graph analysis algorithms (for optimization purposes).
+ */
+ static final int FLAG_DEBUG_ONLY = 1;
+
+ /**
+ * A flag indicating that a label is the target of a jump instruction, or the start of an
+ * exception handler.
+ */
+ static final int FLAG_JUMP_TARGET = 2;
+
+ /** A flag indicating that the bytecode offset of a label is known. */
+ static final int FLAG_RESOLVED = 4;
+
+ /** A flag indicating that a label corresponds to a reachable basic block. */
+ static final int FLAG_REACHABLE = 8;
+
+ /**
+ * A flag indicating that the basic block corresponding to a label ends with a subroutine call. By
+ * construction in {@link MethodWriter#visitJumpInsn}, labels with this flag set have at least two
+ * outgoing edges:
+ *
+ *
+ *
the first one corresponds to the instruction that follows the jsr instruction in the
+ * bytecode, i.e. where execution continues when it returns from the jsr call. This is a
+ * virtual control flow edge, since execution never goes directly from the jsr to the next
+ * instruction. Instead, it goes to the subroutine and eventually returns to the instruction
+ * following the jsr. This virtual edge is used to compute the real outgoing edges of the
+ * basic blocks ending with a ret instruction, in {@link #addSubroutineRetSuccessors}.
+ *
the second one corresponds to the target of the jsr instruction,
+ *
+ */
+ static final int FLAG_SUBROUTINE_CALLER = 16;
+
+ /**
+ * A flag indicating that the basic block corresponding to a label is the start of a subroutine.
+ */
+ static final int FLAG_SUBROUTINE_START = 32;
+
+ /** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */
+ static final int FLAG_SUBROUTINE_END = 64;
+
+ /**
+ * The number of elements to add to the {@link #otherLineNumbers} array when it needs to be
+ * resized to store a new source line number.
+ */
+ static final int LINE_NUMBERS_CAPACITY_INCREMENT = 4;
+
+ /**
+ * The number of elements to add to the {@link #forwardReferences} array when it needs to be
+ * resized to store a new forward reference.
+ */
+ static final int FORWARD_REFERENCES_CAPACITY_INCREMENT = 6;
+
+ /**
+ * The bit mask to extract the type of a forward reference to this label. The extracted type is
+ * either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link #FORWARD_REFERENCE_TYPE_WIDE}.
+ *
+ * @see #forwardReferences
+ */
+ static final int FORWARD_REFERENCE_TYPE_MASK = 0xF0000000;
+
+ /**
+ * The type of forward references stored with two bytes in the bytecode. This is the case, for
+ * instance, of a forward reference from an ifnull instruction.
+ */
+ static final int FORWARD_REFERENCE_TYPE_SHORT = 0x10000000;
+
+ /**
+ * The type of forward references stored in four bytes in the bytecode. This is the case, for
+ * instance, of a forward reference from a lookupswitch instruction.
+ */
+ static final int FORWARD_REFERENCE_TYPE_WIDE = 0x20000000;
+
+ /**
+ * The bit mask to extract the 'handle' of a forward reference to this label. The extracted handle
+ * is the bytecode offset where the forward reference value is stored (using either 2 or 4 bytes,
+ * as indicated by the {@link #FORWARD_REFERENCE_TYPE_MASK}).
+ *
+ * @see #forwardReferences
+ */
+ static final int FORWARD_REFERENCE_HANDLE_MASK = 0x0FFFFFFF;
+
+ /**
+ * A sentinel element used to indicate the end of a list of labels.
+ *
+ * @see #nextListElement
+ */
+ static final Label EMPTY_LIST = new Label();
+
+ /**
+ * A user managed state associated with this label. Warning: this field is used by the ASM tree
+ * package. In order to use it with the ASM tree package you must override the getLabelNode method
+ * in MethodNode.
+ */
+ public Object info;
+
+ /**
+ * The type and status of this label or its corresponding basic block. Must be zero or more of
+ * {@link #FLAG_DEBUG_ONLY}, {@link #FLAG_JUMP_TARGET}, {@link #FLAG_RESOLVED}, {@link
+ * #FLAG_REACHABLE}, {@link #FLAG_SUBROUTINE_CALLER}, {@link #FLAG_SUBROUTINE_START}, {@link
+ * #FLAG_SUBROUTINE_END}.
+ */
+ short flags;
+
+ /**
+ * The source line number corresponding to this label, or 0. If there are several source line
+ * numbers corresponding to this label, the first one is stored in this field, and the remaining
+ * ones are stored in {@link #otherLineNumbers}.
+ */
+ private short lineNumber;
+
+ /**
+ * The source line numbers corresponding to this label, in addition to {@link #lineNumber}, or
+ * null. The first element of this array is the number n of source line numbers it contains, which
+ * are stored between indices 1 and n (inclusive).
+ */
+ private int[] otherLineNumbers;
+
+ /**
+ * The offset of this label in the bytecode of its method, in bytes. This value is set if and only
+ * if the {@link #FLAG_RESOLVED} flag is set.
+ */
+ int bytecodeOffset;
+
+ /**
+ * The forward references to this label. The first element is the number of forward references,
+ * times 2 (this corresponds to the index of the last element actually used in this array). Then,
+ * each forward reference is described with two consecutive integers noted
+ * 'sourceInsnBytecodeOffset' and 'reference':
+ *
+ *
+ *
'sourceInsnBytecodeOffset' is the bytecode offset of the instruction that contains the
+ * forward reference,
+ *
'reference' contains the type and the offset in the bytecode where the forward reference
+ * value must be stored, which can be extracted with {@link #FORWARD_REFERENCE_TYPE_MASK}
+ * and {@link #FORWARD_REFERENCE_HANDLE_MASK}.
+ *
+ *
+ * For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is
+ * equal to x, and 'reference' is of type {@link #FORWARD_REFERENCE_TYPE_SHORT} with value x + 1
+ * (because the ifnull instruction uses a 2 bytes bytecode offset operand stored one byte after
+ * the start of the instruction itself). For the default case of a lookupswitch instruction at
+ * bytecode offset x, 'sourceInsnBytecodeOffset' is equal to x, and 'reference' is of type {@link
+ * #FORWARD_REFERENCE_TYPE_WIDE} with value between x + 1 and x + 4 (because the lookupswitch
+ * instruction uses a 4 bytes bytecode offset operand stored one to four bytes after the start of
+ * the instruction itself).
+ */
+ private int[] forwardReferences;
+
+ // -----------------------------------------------------------------------------------------------
+
+ // Fields for the control flow and data flow graph analysis algorithms (used to compute the
+ // maximum stack size or the stack map frames). A control flow graph contains one node per "basic
+ // block", and one edge per "jump" from one basic block to another. Each node (i.e., each basic
+ // block) is represented with the Label object that corresponds to the first instruction of this
+ // basic block. Each node also stores the list of its successors in the graph, as a linked list of
+ // Edge objects.
+ //
+ // The control flow analysis algorithms used to compute the maximum stack size or the stack map
+ // frames are similar and use two steps. The first step, during the visit of each instruction,
+ // builds information about the state of the local variables and the operand stack at the end of
+ // each basic block, called the "output frame", relatively to the frame state at the
+ // beginning of the basic block, which is called the "input frame", and which is unknown
+ // during this step. The second step, in {@link MethodWriter#computeAllFrames} and {@link
+ // MethodWriter#computeMaxStackAndLocal}, is a fix point algorithm
+ // that computes information about the input frame of each basic block, from the input state of
+ // the first basic block (known from the method signature), and by the using the previously
+ // computed relative output frames.
+ //
+ // The algorithm used to compute the maximum stack size only computes the relative output and
+ // absolute input stack heights, while the algorithm used to compute stack map frames computes
+ // relative output frames and absolute input frames.
+
+ /**
+ * The number of elements in the input stack of the basic block corresponding to this label. This
+ * field is computed in {@link MethodWriter#computeMaxStackAndLocal}.
+ */
+ short inputStackSize;
+
+ /**
+ * The number of elements in the output stack, at the end of the basic block corresponding to this
+ * label. This field is only computed for basic blocks that end with a RET instruction.
+ */
+ short outputStackSize;
+
+ /**
+ * The maximum height reached by the output stack, relatively to the top of the input stack, in
+ * the basic block corresponding to this label. This maximum is always positive or null.
+ */
+ short outputStackMax;
+
+ /**
+ * The id of the subroutine to which this basic block belongs, or 0. If the basic block belongs to
+ * several subroutines, this is the id of the "oldest" subroutine that contains it (with the
+ * convention that a subroutine calling another one is "older" than the callee). This field is
+ * computed in {@link MethodWriter#computeMaxStackAndLocal}, if the method contains JSR
+ * instructions.
+ */
+ short subroutineId;
+
+ /**
+ * The input and output stack map frames of the basic block corresponding to this label. This
+ * field is only used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} or {@link
+ * MethodWriter#COMPUTE_INSERTED_FRAMES} option is used.
+ */
+ Frame frame;
+
+ /**
+ * The successor of this label, in the order they are visited in {@link MethodVisitor#visitLabel}.
+ * This linked list does not include labels used for debug info only. If the {@link
+ * MethodWriter#COMPUTE_ALL_FRAMES} or {@link MethodWriter#COMPUTE_INSERTED_FRAMES} option is used
+ * then it does not contain either successive labels that denote the same bytecode offset (in this
+ * case only the first label appears in this list).
+ */
+ Label nextBasicBlock;
+
+ /**
+ * The outgoing edges of the basic block corresponding to this label, in the control flow graph of
+ * its method. These edges are stored in a linked list of {@link Edge} objects, linked to each
+ * other by their {@link Edge#nextEdge} field.
+ */
+ Edge outgoingEdges;
+
+ /**
+ * The next element in the list of labels to which this label belongs, or null if it does not
+ * belong to any list. All lists of labels must end with the {@link #EMPTY_LIST} sentinel, in
+ * order to ensure that this field is null if and only if this label does not belong to a list of
+ * labels. Note that there can be several lists of labels at the same time, but that a label can
+ * belong to at most one list at a time (unless some lists share a common tail, but this is not
+ * used in practice).
+ *
+ *
List of labels are used in {@link MethodWriter#computeAllFrames} and {@link
+ * MethodWriter#computeMaxStackAndLocal} to compute stack map frames and the maximum stack size,
+ * respectively, as well as in {@link #markSubroutine} and {@link #addSubroutineRetSuccessors} to
+ * compute the basic blocks belonging to subroutines and their outgoing edges. Outside of these
+ * methods, this field should be null (this property is a precondition and a postcondition of
+ * these methods).
+ */
+ Label nextListElement;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructor and accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /** Constructs a new label. */
+ public Label() {
+ // Nothing to do.
+ }
+
+ /**
+ * Returns the bytecode offset corresponding to this label. This offset is computed from the start
+ * of the method's bytecode. This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @return the bytecode offset corresponding to this label.
+ * @throws IllegalStateException if this label is not resolved yet.
+ */
+ public int getOffset() {
+ if ((flags & FLAG_RESOLVED) == 0) {
+ throw new IllegalStateException("Label offset position has not been resolved yet");
}
-
- // ------------------------------------------------------------------------
- // Methods to compute offsets and to manage forward references
- // ------------------------------------------------------------------------
-
- /**
- * Returns the offset corresponding to this label. This offset is computed
- * from the start of the method's bytecode. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @return the offset corresponding to this label.
- * @throws IllegalStateException
- * if this label is not resolved yet.
- */
- public int getOffset() {
- if ((status & RESOLVED) == 0) {
- throw new IllegalStateException(
- "Label offset position has not been resolved yet");
- }
- return position;
+ return bytecodeOffset;
+ }
+
+ /**
+ * Returns the "canonical" {@link Label} instance corresponding to this label's bytecode offset,
+ * if known, otherwise the label itself. The canonical instance is the first label (in the order
+ * of their visit by {@link MethodVisitor#visitLabel}) corresponding to this bytecode offset. It
+ * cannot be known for labels which have not been visited yet.
+ *
+ *
This method should only be used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} option
+ * is used.
+ *
+ * @return the label itself if {@link #frame} is null, otherwise the Label's frame owner. This
+ * corresponds to the "canonical" label instance described above thanks to the way the label
+ * frame is set in {@link MethodWriter#visitLabel}.
+ */
+ final Label getCanonicalInstance() {
+ return frame == null ? this : frame.owner;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods to manage line numbers
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a source line number corresponding to this label.
+ *
+ * @param lineNumber a source line number (which should be strictly positive).
+ */
+ final void addLineNumber(final int lineNumber) {
+ if (this.lineNumber == 0) {
+ this.lineNumber = (short) lineNumber;
+ } else {
+ if (otherLineNumbers == null) {
+ otherLineNumbers = new int[LINE_NUMBERS_CAPACITY_INCREMENT];
+ }
+ int otherLineNumberIndex = ++otherLineNumbers[0];
+ if (otherLineNumberIndex >= otherLineNumbers.length) {
+ int[] newLineNumbers = new int[otherLineNumbers.length + LINE_NUMBERS_CAPACITY_INCREMENT];
+ System.arraycopy(otherLineNumbers, 0, newLineNumbers, 0, otherLineNumbers.length);
+ otherLineNumbers = newLineNumbers;
+ }
+ otherLineNumbers[otherLineNumberIndex] = lineNumber;
}
-
- /**
- * Puts a reference to this label in the bytecode of a method. If the
- * position of the label is known, the offset is computed and written
- * directly. Otherwise, a null offset is written and a new forward reference
- * is declared for this label.
- *
- * @param owner
- * the code writer that calls this method.
- * @param out
- * the bytecode of the method.
- * @param source
- * the position of first byte of the bytecode instruction that
- * contains this label.
- * @param wideOffset
- * true if the reference must be stored in 4 bytes, or
- * false if it must be stored with 2 bytes.
- * @throws IllegalArgumentException
- * if this label has not been created by the given code writer.
- */
- void put(final MethodWriter owner, final ByteVector out, final int source,
- final boolean wideOffset) {
- if ((status & RESOLVED) == 0) {
- if (wideOffset) {
- addReference(-1 - source, out.length);
- out.putInt(-1);
- } else {
- addReference(source, out.length);
- out.putShort(-1);
- }
- } else {
- if (wideOffset) {
- out.putInt(position - source);
- } else {
- out.putShort(position - source);
- }
+ }
+
+ /**
+ * Makes the given visitor visit this label and its source line numbers, if applicable.
+ *
+ * @param methodVisitor a method visitor.
+ * @param visitLineNumbers whether to visit of the label's source line numbers, if any.
+ */
+ final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) {
+ methodVisitor.visitLabel(this);
+ if (visitLineNumbers && lineNumber != 0) {
+ methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this);
+ if (otherLineNumbers != null) {
+ for (int i = 1; i <= otherLineNumbers[0]; ++i) {
+ methodVisitor.visitLineNumber(otherLineNumbers[i], this);
}
+ }
}
-
- /**
- * Adds a forward reference to this label. This method must be called only
- * for a true forward reference, i.e. only if this label is not resolved
- * yet. For backward references, the offset of the reference can be, and
- * must be, computed and stored directly.
- *
- * @param sourcePosition
- * the position of the referencing instruction. This position
- * will be used to compute the offset of this forward reference.
- * @param referencePosition
- * the position where the offset for this forward reference must
- * be stored.
- */
- private void addReference(final int sourcePosition,
- final int referencePosition) {
- if (srcAndRefPositions == null) {
- srcAndRefPositions = new int[6];
- }
- if (referenceCount >= srcAndRefPositions.length) {
- int[] a = new int[srcAndRefPositions.length + 6];
- System.arraycopy(srcAndRefPositions, 0, a, 0,
- srcAndRefPositions.length);
- srcAndRefPositions = a;
- }
- srcAndRefPositions[referenceCount++] = sourcePosition;
- srcAndRefPositions[referenceCount++] = referencePosition;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods to compute offsets and to manage forward references
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Puts a reference to this label in the bytecode of a method. If the bytecode offset of the label
+ * is known, the relative bytecode offset between the label and the instruction referencing it is
+ * computed and written directly. Otherwise, a null relative offset is written and a new forward
+ * reference is declared for this label.
+ *
+ * @param code the bytecode of the method. This is where the reference is appended.
+ * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the
+ * reference to be appended.
+ * @param wideReference whether the reference must be stored in 4 bytes (instead of 2 bytes).
+ */
+ final void put(
+ final ByteVector code, final int sourceInsnBytecodeOffset, final boolean wideReference) {
+ if ((flags & FLAG_RESOLVED) == 0) {
+ if (wideReference) {
+ addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_WIDE, code.length);
+ code.putInt(-1);
+ } else {
+ addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_SHORT, code.length);
+ code.putShort(-1);
+ }
+ } else {
+ if (wideReference) {
+ code.putInt(bytecodeOffset - sourceInsnBytecodeOffset);
+ } else {
+ code.putShort(bytecodeOffset - sourceInsnBytecodeOffset);
+ }
}
-
- /**
- * Resolves all forward references to this label. This method must be called
- * when this label is added to the bytecode of the method, i.e. when its
- * position becomes known. This method fills in the blanks that where left
- * in the bytecode by each forward reference previously added to this label.
- *
- * @param owner
- * the code writer that calls this method.
- * @param position
- * the position of this label in the bytecode.
- * @param data
- * the bytecode of the method.
- * @return true if a blank that was left for this label was to
- * small to store the offset. In such a case the corresponding jump
- * instruction is replaced with a pseudo instruction (using unused
- * opcodes) using an unsigned two bytes offset. These pseudo
- * instructions will need to be replaced with true instructions with
- * wider offsets (4 bytes instead of 2). This is done in
- * {@link MethodWriter#resizeInstructions}.
- * @throws IllegalArgumentException
- * if this label has already been resolved, or if it has not
- * been created by the given code writer.
- */
- boolean resolve(final MethodWriter owner, final int position,
- final byte[] data) {
- boolean needUpdate = false;
- this.status |= RESOLVED;
- this.position = position;
- int i = 0;
- while (i < referenceCount) {
- int source = srcAndRefPositions[i++];
- int reference = srcAndRefPositions[i++];
- int offset;
- if (source >= 0) {
- offset = position - source;
- if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
- /*
- * changes the opcode of the jump instruction, in order to
- * be able to find it later (see resizeInstructions in
- * MethodWriter). These temporary opcodes are similar to
- * jump instruction opcodes, except that the 2 bytes offset
- * is unsigned (and can therefore represent values from 0 to
- * 65535, which is sufficient since the size of a method is
- * limited to 65535 bytes).
- */
- int opcode = data[reference - 1] & 0xFF;
- if (opcode <= Opcodes.JSR) {
- // changes IFEQ ... JSR to opcodes 202 to 217
- data[reference - 1] = (byte) (opcode + 49);
- } else {
- // changes IFNULL and IFNONNULL to opcodes 218 and 219
- data[reference - 1] = (byte) (opcode + 20);
- }
- needUpdate = true;
- }
- data[reference++] = (byte) (offset >>> 8);
- data[reference] = (byte) offset;
- } else {
- offset = position + source + 1;
- data[reference++] = (byte) (offset >>> 24);
- data[reference++] = (byte) (offset >>> 16);
- data[reference++] = (byte) (offset >>> 8);
- data[reference] = (byte) offset;
- }
- }
- return needUpdate;
+ }
+
+ /**
+ * Adds a forward reference to this label. This method must be called only for a true forward
+ * reference, i.e. only if this label is not resolved yet. For backward references, the relative
+ * bytecode offset of the reference can be, and must be, computed and stored directly.
+ *
+ * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the
+ * reference stored at referenceHandle.
+ * @param referenceType either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link
+ * #FORWARD_REFERENCE_TYPE_WIDE}.
+ * @param referenceHandle the offset in the bytecode where the forward reference value must be
+ * stored.
+ */
+ private void addForwardReference(
+ final int sourceInsnBytecodeOffset, final int referenceType, final int referenceHandle) {
+ if (forwardReferences == null) {
+ forwardReferences = new int[FORWARD_REFERENCES_CAPACITY_INCREMENT];
}
-
- /**
- * Returns the first label of the series to which this label belongs. For an
- * isolated label or for the first label in a series of successive labels,
- * this method returns the label itself. For other labels it returns the
- * first label of the series.
- *
- * @return the first label of the series to which this label belongs.
- */
- Label getFirst() {
- return !ClassReader.FRAMES || frame == null ? this : frame.owner;
+ int lastElementIndex = forwardReferences[0];
+ if (lastElementIndex + 2 >= forwardReferences.length) {
+ int[] newValues = new int[forwardReferences.length + FORWARD_REFERENCES_CAPACITY_INCREMENT];
+ System.arraycopy(forwardReferences, 0, newValues, 0, forwardReferences.length);
+ forwardReferences = newValues;
}
-
- // ------------------------------------------------------------------------
- // Methods related to subroutines
- // ------------------------------------------------------------------------
-
- /**
- * Returns true is this basic block belongs to the given subroutine.
- *
- * @param id
- * a subroutine id.
- * @return true is this basic block belongs to the given subroutine.
- */
- boolean inSubroutine(final long id) {
- if ((status & Label.VISITED) != 0) {
- return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0;
- }
- return false;
+ forwardReferences[++lastElementIndex] = sourceInsnBytecodeOffset;
+ forwardReferences[++lastElementIndex] = referenceType | referenceHandle;
+ forwardReferences[0] = lastElementIndex;
+ }
+
+ /**
+ * Sets the bytecode offset of this label to the given value and resolves the forward references
+ * to this label, if any. This method must be called when this label is added to the bytecode of
+ * the method, i.e. when its bytecode offset becomes known. This method fills in the blanks that
+ * where left in the bytecode by each forward reference previously added to this label.
+ *
+ * @param code the bytecode of the method.
+ * @param bytecodeOffset the bytecode offset of this label.
+ * @return true if a blank that was left for this label was too small to store the
+ * offset. In such a case the corresponding jump instruction is replaced with an equivalent
+ * ASM specific instruction using an unsigned two bytes offset. These ASM specific
+ * instructions are later replaced with standard bytecode instructions with wider offsets (4
+ * bytes instead of 2), in ClassReader.
+ */
+ final boolean resolve(final byte[] code, final int bytecodeOffset) {
+ this.flags |= FLAG_RESOLVED;
+ this.bytecodeOffset = bytecodeOffset;
+ if (forwardReferences == null) {
+ return false;
}
-
- /**
- * Returns true if this basic block and the given one belong to a common
- * subroutine.
- *
- * @param block
- * another basic block.
- * @return true if this basic block and the given one belong to a common
- * subroutine.
- */
- boolean inSameSubroutine(final Label block) {
- if ((status & VISITED) == 0 || (block.status & VISITED) == 0) {
- return false;
- }
- for (int i = 0; i < srcAndRefPositions.length; ++i) {
- if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) {
- return true;
- }
+ boolean hasAsmInstructions = false;
+ for (int i = forwardReferences[0]; i > 0; i -= 2) {
+ final int sourceInsnBytecodeOffset = forwardReferences[i - 1];
+ final int reference = forwardReferences[i];
+ final int relativeOffset = bytecodeOffset - sourceInsnBytecodeOffset;
+ int handle = reference & FORWARD_REFERENCE_HANDLE_MASK;
+ if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_SHORT) {
+ if (relativeOffset < Short.MIN_VALUE || relativeOffset > Short.MAX_VALUE) {
+ // Change the opcode of the jump instruction, in order to be able to find it later in
+ // ClassReader. These ASM specific opcodes are similar to jump instruction opcodes, except
+ // that the 2 bytes offset is unsigned (and can therefore represent values from 0 to
+ // 65535, which is sufficient since the size of a method is limited to 65535 bytes).
+ int opcode = code[sourceInsnBytecodeOffset] & 0xFF;
+ if (opcode < Opcodes.IFNULL) {
+ // Change IFEQ ... JSR to ASM_IFEQ ... ASM_JSR.
+ code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_OPCODE_DELTA);
+ } else {
+ // Change IFNULL and IFNONNULL to ASM_IFNULL and ASM_IFNONNULL.
+ code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_IFNULL_OPCODE_DELTA);
+ }
+ hasAsmInstructions = true;
}
- return false;
+ code[handle++] = (byte) (relativeOffset >>> 8);
+ code[handle] = (byte) relativeOffset;
+ } else {
+ code[handle++] = (byte) (relativeOffset >>> 24);
+ code[handle++] = (byte) (relativeOffset >>> 16);
+ code[handle++] = (byte) (relativeOffset >>> 8);
+ code[handle] = (byte) relativeOffset;
+ }
}
-
- /**
- * Marks this basic block as belonging to the given subroutine.
- *
- * @param id
- * a subroutine id.
- * @param nbSubroutines
- * the total number of subroutines in the method.
- */
- void addToSubroutine(final long id, final int nbSubroutines) {
- if ((status & VISITED) == 0) {
- status |= VISITED;
- srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1];
- }
- srcAndRefPositions[(int) (id >>> 32)] |= (int) id;
+ return hasAsmInstructions;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods related to subroutines
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Finds the basic blocks that belong to the subroutine starting with the basic block
+ * corresponding to this label, and marks these blocks as belonging to this subroutine. This
+ * method follows the control flow graph to find all the blocks that are reachable from the
+ * current basic block WITHOUT following any jsr target.
+ *
+ *
Note: a precondition and postcondition of this method is that all labels must have a null
+ * {@link #nextListElement}.
+ *
+ * @param subroutineId the id of the subroutine starting with the basic block corresponding to
+ * this label.
+ */
+ final void markSubroutine(final short subroutineId) {
+ // Data flow algorithm: put this basic block in a list of blocks to process (which are blocks
+ // belonging to subroutine subroutineId) and, while there are blocks to process, remove one from
+ // the list, mark it as belonging to the subroutine, and add its successor basic blocks in the
+ // control flow graph to the list of blocks to process (if not already done).
+ Label listOfBlocksToProcess = this;
+ listOfBlocksToProcess.nextListElement = EMPTY_LIST;
+ while (listOfBlocksToProcess != EMPTY_LIST) {
+ // Remove a basic block from the list of blocks to process.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
+ basicBlock.nextListElement = null;
+
+ // If it is not already marked as belonging to a subroutine, mark it as belonging to
+ // subroutineId and add its successors to the list of blocks to process (unless already done).
+ if (basicBlock.subroutineId == 0) {
+ basicBlock.subroutineId = subroutineId;
+ listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess);
+ }
}
-
- /**
- * Finds the basic blocks that belong to a given subroutine, and marks these
- * blocks as belonging to this subroutine. This method follows the control
- * flow graph to find all the blocks that are reachable from the current
- * block WITHOUT following any JSR target.
- *
- * @param JSR
- * a JSR block that jumps to this subroutine. If this JSR is not
- * null it is added to the successor of the RET blocks found in
- * the subroutine.
- * @param id
- * the id of this subroutine.
- * @param nbSubroutines
- * the total number of subroutines in the method.
- */
- void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) {
- // user managed stack of labels, to avoid using a recursive method
- // (recursivity can lead to stack overflow with very large methods)
- Label stack = this;
- while (stack != null) {
- // removes a label l from the stack
- Label l = stack;
- stack = l.next;
- l.next = null;
-
- if (JSR != null) {
- if ((l.status & VISITED2) != 0) {
- continue;
- }
- l.status |= VISITED2;
- // adds JSR to the successors of l, if it is a RET block
- if ((l.status & RET) != 0) {
- if (!l.inSameSubroutine(JSR)) {
- Edge e = new Edge();
- e.info = l.inputStackTop;
- e.successor = JSR.successors.successor;
- e.next = l.successors;
- l.successors = e;
- }
- }
- } else {
- // if the l block already belongs to subroutine 'id', continue
- if (l.inSubroutine(id)) {
- continue;
- }
- // marks the l block as belonging to subroutine 'id'
- l.addToSubroutine(id, nbSubroutines);
- }
- // pushes each successor of l on the stack, except JSR targets
- Edge e = l.successors;
- while (e != null) {
- // if the l block is a JSR block, then 'l.successors.next' leads
- // to the JSR target (see {@link #visitJumpInsn}) and must
- // therefore not be followed
- if ((l.status & Label.JSR) == 0 || e != l.successors.next) {
- // pushes e.successor on the stack if it not already added
- if (e.successor.next == null) {
- e.successor.next = stack;
- stack = e.successor;
- }
- }
- e = e.next;
- }
- }
+ }
+
+ /**
+ * Finds the basic blocks that end a subroutine starting with the basic block corresponding to
+ * this label and, for each one of them, adds an outgoing edge to the basic block following the
+ * given subroutine call. In other words, completes the control flow graph by adding the edges
+ * corresponding to the return from this subroutine, when called from the given caller basic
+ * block.
+ *
+ *
Note: a precondition and postcondition of this method is that all labels must have a null
+ * {@link #nextListElement}.
+ *
+ * @param subroutineCaller a basic block that ends with a jsr to the basic block corresponding to
+ * this label. This label is supposed to correspond to the start of a subroutine.
+ */
+ final void addSubroutineRetSuccessors(final Label subroutineCaller) {
+ // Data flow algorithm: put this basic block in a list blocks to process (which are blocks
+ // belonging to a subroutine starting with this label) and, while there are blocks to process,
+ // remove one from the list, put it in a list of blocks that have been processed, add a return
+ // edge to the successor of subroutineCaller if applicable, and add its successor basic blocks
+ // in the control flow graph to the list of blocks to process (if not already done).
+ Label listOfProcessedBlocks = EMPTY_LIST;
+ Label listOfBlocksToProcess = this;
+ listOfBlocksToProcess.nextListElement = EMPTY_LIST;
+ while (listOfBlocksToProcess != EMPTY_LIST) {
+ // Move a basic block from the list of blocks to process to the list of processed blocks.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = basicBlock.nextListElement;
+ basicBlock.nextListElement = listOfProcessedBlocks;
+ listOfProcessedBlocks = basicBlock;
+
+ // Add an edge from this block to the successor of the caller basic block, if this block is
+ // the end of a subroutine and if this block and subroutineCaller do not belong to the same
+ // subroutine.
+ if ((basicBlock.flags & FLAG_SUBROUTINE_END) != 0
+ && basicBlock.subroutineId != subroutineCaller.subroutineId) {
+ basicBlock.outgoingEdges =
+ new Edge(
+ basicBlock.outputStackSize,
+ // By construction, the first outgoing edge of a basic block that ends with a jsr
+ // instruction leads to the jsr continuation block, i.e. where execution continues
+ // when ret is called (see {@link #FLAG_SUBROUTINE_CALLER}).
+ subroutineCaller.outgoingEdges.successor,
+ basicBlock.outgoingEdges);
+ }
+ // Add its successors to the list of blocks to process. Note that {@link #pushSuccessors} does
+ // not push basic blocks which are already in a list. Here this means either in the list of
+ // blocks to process, or in the list of already processed blocks. This second list is
+ // important to make sure we don't reprocess an already processed block.
+ listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess);
}
-
- // ------------------------------------------------------------------------
- // Overriden Object methods
- // ------------------------------------------------------------------------
-
- /**
- * Returns a string representation of this label.
- *
- * @return a string representation of this label.
- */
- @Override
- public String toString() {
- return "L" + System.identityHashCode(this);
+ // Reset the {@link #nextListElement} of all the basic blocks that have been processed to null,
+ // so that this method can be called again with a different subroutine or subroutine caller.
+ while (listOfProcessedBlocks != EMPTY_LIST) {
+ Label newListOfProcessedBlocks = listOfProcessedBlocks.nextListElement;
+ listOfProcessedBlocks.nextListElement = null;
+ listOfProcessedBlocks = newListOfProcessedBlocks;
+ }
+ }
+
+ /**
+ * Adds the successors of this label in the method's control flow graph (except those
+ * corresponding to a jsr target, and those already in a list of labels) to the given list of
+ * blocks to process, and returns the new list.
+ *
+ * @param listOfLabelsToProcess a list of basic blocks to process, linked together with their
+ * {@link #nextListElement} field.
+ * @return the new list of blocks to process.
+ */
+ private Label pushSuccessors(final Label listOfLabelsToProcess) {
+ Label newListOfLabelsToProcess = listOfLabelsToProcess;
+ Edge outgoingEdge = outgoingEdges;
+ while (outgoingEdge != null) {
+ // By construction, the second outgoing edge of a basic block that ends with a jsr instruction
+ // leads to the jsr target (see {@link #FLAG_SUBROUTINE_CALLER}).
+ boolean isJsrTarget =
+ (flags & Label.FLAG_SUBROUTINE_CALLER) != 0 && outgoingEdge == outgoingEdges.nextEdge;
+ if (!isJsrTarget && outgoingEdge.successor.nextListElement == null) {
+ // Add this successor to the list of blocks to process, if it does not already belong to a
+ // list of labels.
+ outgoingEdge.successor.nextListElement = newListOfLabelsToProcess;
+ newListOfLabelsToProcess = outgoingEdge.successor;
+ }
+ outgoingEdge = outgoingEdge.nextEdge;
}
+ return newListOfLabelsToProcess;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Overridden Object methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns a string representation of this label.
+ *
+ * @return a string representation of this label.
+ */
+ @Override
+ public String toString() {
+ return "L" + System.identityHashCode(this);
+ }
}
diff --git a/src/jvm/clojure/asm/MethodVisitor.java b/src/jvm/clojure/asm/MethodVisitor.java
index 347455e284..4489612134 100644
--- a/src/jvm/clojure/asm/MethodVisitor.java
+++ b/src/jvm/clojure/asm/MethodVisitor.java
@@ -1,662 +1,786 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A visitor to visit a Java method. The methods of this class must be called in
- * the following order: [ visitAnnotationDefault ] (
- * visitAnnotation | visitParameterAnnotation |
- * visitAttribute )* [ visitCode ( visitFrame |
- * visitXInsn | visitLabel |
- * visitTryCatchBlock | visitLocalVariable |
- * visitLineNumber )* visitMaxs ] visitEnd. In
- * addition, the visitXInsn and visitLabel methods
- * must be called in the sequential order of the bytecode instructions of the
- * visited code, visitTryCatchBlock must be called before the
- * labels passed as arguments have been visited, and the
- * visitLocalVariable and visitLineNumber methods must be
- * called after the labels passed as arguments have been visited.
+ * A visitor to visit a Java method. The methods of this class must be called in the following
+ * order: ( visitParameter )* [ visitAnnotationDefault ] (
+ * visitAnnotation | visitAnnotableParameterCount |
+ * visitParameterAnnotationvisitTypeAnnotation | visitAttribute )* [
+ * visitCode ( visitFrame | visitXInsn | visitLabel |
+ * visitInsnAnnotation | visitTryCatchBlock | visitTryCatchAnnotation |
+ * visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )*
+ * visitMaxs ] visitEnd. In addition, the visitXInsn and
+ * visitLabel methods must be called in the sequential order of the bytecode instructions
+ * of the visited code, visitInsnAnnotation must be called after the annotated
+ * instruction, visitTryCatchBlock must be called before the labels passed as
+ * arguments have been visited, visitTryCatchBlockAnnotation must be called after
+ * the corresponding try catch block has been visited, and the visitLocalVariable,
+ * visitLocalVariableAnnotation and visitLineNumber methods must be called
+ * after the labels passed as arguments have been visited.
*
* @author Eric Bruneton
*/
public abstract class MethodVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
-
- /**
- * The method visitor to which this visitor must delegate method calls. May
- * be null.
- */
- protected MethodVisitor mv;
-
- /**
- * Constructs a new {@link MethodVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public MethodVisitor(final int api) {
- this(api, null);
- }
-
- /**
- * Constructs a new {@link MethodVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param mv
- * the method visitor to which this visitor must delegate method
- * calls. May be null.
- */
- public MethodVisitor(final int api, final MethodVisitor mv) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.mv = mv;
- }
-
- // -------------------------------------------------------------------------
- // Annotations and non standard attributes
- // -------------------------------------------------------------------------
-
- /**
- * Visits the default value of this annotation interface method.
- *
- * @return a visitor to the visit the actual default value of this
- * annotation interface method, or null if this visitor is
- * not interested in visiting this default value. The 'name'
- * parameters passed to the methods of this annotation visitor are
- * ignored. Moreover, exacly one visit method must be called on this
- * annotation visitor, followed by visitEnd.
- */
- public AnnotationVisitor visitAnnotationDefault() {
- if (mv != null) {
- return mv.visitAnnotationDefault();
- }
- return null;
- }
-
- /**
- * Visits an annotation of this method.
- *
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (mv != null) {
- return mv.visitAnnotation(desc, visible);
- }
- return null;
- }
-
- /**
- * Visits an annotation of a parameter this method.
- *
- * @param parameter
- * the parameter index.
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitParameterAnnotation(int parameter,
- String desc, boolean visible) {
- if (mv != null) {
- return mv.visitParameterAnnotation(parameter, desc, visible);
- }
- return null;
- }
-
- /**
- * Visits a non standard attribute of this method.
- *
- * @param attr
- * an attribute.
- */
- public void visitAttribute(Attribute attr) {
- if (mv != null) {
- mv.visitAttribute(attr);
- }
- }
-
- /**
- * Starts the visit of the method's code, if any (i.e. non abstract method).
- */
- public void visitCode() {
- if (mv != null) {
- mv.visitCode();
- }
- }
-
- /**
- * Visits the current state of the local variables and operand stack
- * elements. This method must(*) be called just before any
- * instruction i that follows an unconditional branch instruction
- * such as GOTO or THROW, that is the target of a jump instruction, or that
- * starts an exception handler block. The visited types must describe the
- * values of the local variables and of the operand stack elements just
- * beforei is executed.
- *
- * (*) this is mandatory only for classes whose version is greater than or
- * equal to {@link Opcodes#V1_6 V1_6}.
- *
- * The frames of a method must be given either in expanded form, or in
- * compressed form (all frames must use the same format, i.e. you must not
- * mix expanded and compressed frames within a single method):
- *
- *
In expanded form, all frames must have the F_NEW type.
- *
In compressed form, frames are basically "deltas" from the state of
- * the previous frame:
- *
- *
{@link Opcodes#F_SAME} representing frame with exactly the same
- * locals as the previous frame and with the empty stack.
- *
{@link Opcodes#F_SAME1} representing frame with exactly the same
- * locals as the previous frame and with single value on the stack (
- * nStack is 1 and stack[0] contains value for the
- * type of the stack item).
- *
{@link Opcodes#F_APPEND} representing frame with current locals are
- * the same as the locals in the previous frame, except that additional
- * locals are defined (nLocal is 1, 2 or 3 and
- * local elements contains values representing added types).
- *
{@link Opcodes#F_CHOP} representing frame with current locals are the
- * same as the locals in the previous frame, except that the last 1-3 locals
- * are absent and with the empty stack (nLocals is 1, 2 or 3).
- *
- * In both cases the first frame, corresponding to the method's parameters
- * and access flags, is implicit and must not be visited. Also, it is
- * illegal to visit two or more frames for the same code location (i.e., at
- * least one instruction must be visited between two calls to visitFrame).
- *
- * @param type
- * the type of this stack map frame. Must be
- * {@link Opcodes#F_NEW} for expanded frames, or
- * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND},
- * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or
- * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for
- * compressed frames.
- * @param nLocal
- * the number of local variables in the visited frame.
- * @param local
- * the local variable types in this frame. This array must not be
- * modified. Primitive types are represented by
- * {@link Opcodes#TOP}, {@link Opcodes#INTEGER},
- * {@link Opcodes#FLOAT}, {@link Opcodes#LONG},
- * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or
- * {@link Opcodes#UNINITIALIZED_THIS} (long and double are
- * represented by a single element). Reference types are
- * represented by String objects (representing internal names),
- * and uninitialized types by Label objects (this label
- * designates the NEW instruction that created this uninitialized
- * value).
- * @param nStack
- * the number of operand stack elements in the visited frame.
- * @param stack
- * the operand stack types in this frame. This array must not be
- * modified. Its content has the same format as the "local"
- * array.
- * @throws IllegalStateException
- * if a frame is visited just after another one, without any
- * instruction between the two (unless this frame is a
- * Opcodes#F_SAME frame, in which case it is silently ignored).
- */
- public void visitFrame(int type, int nLocal, Object[] local, int nStack,
- Object[] stack) {
- if (mv != null) {
- mv.visitFrame(type, nLocal, local, nStack, stack);
- }
- }
-
- // -------------------------------------------------------------------------
- // Normal instructions
- // -------------------------------------------------------------------------
-
- /**
- * Visits a zero operand instruction.
- *
- * @param opcode
- * the opcode of the instruction to be visited. This opcode is
- * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1,
- * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1,
- * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD,
- * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD,
- * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE,
- * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1,
- * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB,
- * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM,
- * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR,
- * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D,
- * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S,
- * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
- * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER,
- * or MONITOREXIT.
- */
- public void visitInsn(int opcode) {
- if (mv != null) {
- mv.visitInsn(opcode);
- }
- }
-
- /**
- * Visits an instruction with a single int operand.
- *
- * @param opcode
- * the opcode of the instruction to be visited. This opcode is
- * either BIPUSH, SIPUSH or NEWARRAY.
- * @param operand
- * the operand of the instruction to be visited.
- * When opcode is BIPUSH, operand value should be between
- * Byte.MIN_VALUE and Byte.MAX_VALUE.
- * When opcode is SIPUSH, operand value should be between
- * Short.MIN_VALUE and Short.MAX_VALUE.
- * When opcode is NEWARRAY, operand value should be one of
- * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR},
- * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE},
- * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT},
- * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
- */
- public void visitIntInsn(int opcode, int operand) {
- if (mv != null) {
- mv.visitIntInsn(opcode, operand);
- }
- }
-
- /**
- * Visits a local variable instruction. A local variable instruction is an
- * instruction that loads or stores the value of a local variable.
- *
- * @param opcode
- * the opcode of the local variable instruction to be visited.
- * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD,
- * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
- * @param var
- * the operand of the instruction to be visited. This operand is
- * the index of a local variable.
- */
- public void visitVarInsn(int opcode, int var) {
- if (mv != null) {
- mv.visitVarInsn(opcode, var);
- }
- }
-
- /**
- * Visits a type instruction. A type instruction is an instruction that
- * takes the internal name of a class as parameter.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
- * @param type
- * the operand of the instruction to be visited. This operand
- * must be the internal name of an object or array class (see
- * {@link Type#getInternalName() getInternalName}).
- */
- public void visitTypeInsn(int opcode, String type) {
- if (mv != null) {
- mv.visitTypeInsn(opcode, type);
- }
- }
-
- /**
- * Visits a field instruction. A field instruction is an instruction that
- * loads or stores the value of a field of an object.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
- * @param owner
- * the internal name of the field's owner class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor (see {@link Type Type}).
- */
- public void visitFieldInsn(int opcode, String owner, String name,
- String desc) {
- if (mv != null) {
- mv.visitFieldInsn(opcode, owner, name, desc);
- }
- }
-
- /**
- * Visits a method instruction. A method instruction is an instruction that
- * invokes a method.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
- * INVOKEINTERFACE.
- * @param owner
- * the internal name of the method's owner class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type Type}).
- */
- public void visitMethodInsn(int opcode, String owner, String name,
- String desc) {
- if (mv != null) {
- mv.visitMethodInsn(opcode, owner, name, desc);
- }
- }
-
- /**
- * Visits an invokedynamic instruction.
- *
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type Type}).
- * @param bsm
- * the bootstrap method.
- * @param bsmArgs
- * the bootstrap method constant arguments. Each argument must be
- * an {@link Integer}, {@link Float}, {@link Long},
- * {@link Double}, {@link String}, {@link Type} or {@link Handle}
- * value. This method is allowed to modify the content of the
- * array so a caller should expect that this array may change.
- */
- public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
- Object... bsmArgs) {
- if (mv != null) {
- mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
- }
- }
-
- /**
- * Visits a jump instruction. A jump instruction is an instruction that may
- * jump to another instruction.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ,
- * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE,
- * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
- * @param label
- * the operand of the instruction to be visited. This operand is
- * a label that designates the instruction to which the jump
- * instruction may jump.
- */
- public void visitJumpInsn(int opcode, Label label) {
- if (mv != null) {
- mv.visitJumpInsn(opcode, label);
- }
- }
-
- /**
- * Visits a label. A label designates the instruction that will be visited
- * just after it.
- *
- * @param label
- * a {@link Label Label} object.
- */
- public void visitLabel(Label label) {
- if (mv != null) {
- mv.visitLabel(label);
- }
- }
-
- // -------------------------------------------------------------------------
- // Special instructions
- // -------------------------------------------------------------------------
-
- /**
- * Visits a LDC instruction. Note that new constant types may be added in
- * future versions of the Java Virtual Machine. To easily detect new
- * constant types, implementations of this method should check for
- * unexpected constant types, like this:
- *
- *
- *
- * @param cst
- * the constant to be loaded on the stack. This parameter must be
- * a non null {@link Integer}, a {@link Float}, a {@link Long}, a
- * {@link Double}, a {@link String}, a {@link Type} of OBJECT or
- * ARRAY sort for .class constants, for classes whose
- * version is 49.0, a {@link Type} of METHOD sort or a
- * {@link Handle} for MethodType and MethodHandle constants, for
- * classes whose version is 51.0.
- */
- public void visitLdcInsn(Object cst) {
- if (mv != null) {
- mv.visitLdcInsn(cst);
- }
- }
-
- /**
- * Visits an IINC instruction.
- *
- * @param var
- * index of the local variable to be incremented.
- * @param increment
- * amount to increment the local variable by.
- */
- public void visitIincInsn(int var, int increment) {
- if (mv != null) {
- mv.visitIincInsn(var, increment);
- }
- }
-
- /**
- * Visits a TABLESWITCH instruction.
- *
- * @param min
- * the minimum key value.
- * @param max
- * the maximum key value.
- * @param dflt
- * beginning of the default handler block.
- * @param labels
- * beginnings of the handler blocks. labels[i] is the
- * beginning of the handler block for the min + i key.
- */
- public void visitTableSwitchInsn(int min, int max, Label dflt,
- Label... labels) {
- if (mv != null) {
- mv.visitTableSwitchInsn(min, max, dflt, labels);
- }
- }
-
- /**
- * Visits a LOOKUPSWITCH instruction.
- *
- * @param dflt
- * beginning of the default handler block.
- * @param keys
- * the values of the keys.
- * @param labels
- * beginnings of the handler blocks. labels[i] is the
- * beginning of the handler block for the keys[i] key.
- */
- public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
- if (mv != null) {
- mv.visitLookupSwitchInsn(dflt, keys, labels);
- }
- }
-
- /**
- * Visits a MULTIANEWARRAY instruction.
- *
- * @param desc
- * an array type descriptor (see {@link Type Type}).
- * @param dims
- * number of dimensions of the array to allocate.
- */
- public void visitMultiANewArrayInsn(String desc, int dims) {
- if (mv != null) {
- mv.visitMultiANewArrayInsn(desc, dims);
- }
- }
-
- // -------------------------------------------------------------------------
- // Exceptions table entries, debug information, max stack and max locals
- // -------------------------------------------------------------------------
-
- /**
- * Visits a try catch block.
- *
- * @param start
- * beginning of the exception handler's scope (inclusive).
- * @param end
- * end of the exception handler's scope (exclusive).
- * @param handler
- * beginning of the exception handler's code.
- * @param type
- * internal name of the type of exceptions handled by the
- * handler, or null to catch any exceptions (for
- * "finally" blocks).
- * @throws IllegalArgumentException
- * if one of the labels has already been visited by this visitor
- * (by the {@link #visitLabel visitLabel} method).
- */
- public void visitTryCatchBlock(Label start, Label end, Label handler,
- String type) {
- if (mv != null) {
- mv.visitTryCatchBlock(start, end, handler, type);
- }
- }
-
- /**
- * Visits a local variable declaration.
- *
- * @param name
- * the name of a local variable.
- * @param desc
- * the type descriptor of this local variable.
- * @param signature
- * the type signature of this local variable. May be
- * null if the local variable type does not use generic
- * types.
- * @param start
- * the first instruction corresponding to the scope of this local
- * variable (inclusive).
- * @param end
- * the last instruction corresponding to the scope of this local
- * variable (exclusive).
- * @param index
- * the local variable's index.
- * @throws IllegalArgumentException
- * if one of the labels has not already been visited by this
- * visitor (by the {@link #visitLabel visitLabel} method).
- */
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- if (mv != null) {
- mv.visitLocalVariable(name, desc, signature, start, end, index);
- }
- }
-
- /**
- * Visits a line number declaration.
- *
- * @param line
- * a line number. This number refers to the source file from
- * which the class was compiled.
- * @param start
- * the first instruction corresponding to this line number.
- * @throws IllegalArgumentException
- * if start has not already been visited by this
- * visitor (by the {@link #visitLabel visitLabel} method).
- */
- public void visitLineNumber(int line, Label start) {
- if (mv != null) {
- mv.visitLineNumber(line, start);
- }
- }
-
- /**
- * Visits the maximum stack size and the maximum number of local variables
- * of the method.
- *
- * @param maxStack
- * maximum stack size of the method.
- * @param maxLocals
- * maximum number of local variables for the method.
- */
- public void visitMaxs(int maxStack, int maxLocals) {
- if (mv != null) {
- mv.visitMaxs(maxStack, maxLocals);
- }
- }
-
- /**
- * Visits the end of the method. This method, which is the last one to be
- * called, is used to inform the visitor that all the annotations and
- * attributes of the method have been visited.
- */
- public void visitEnd() {
- if (mv != null) {
- mv.visitEnd();
- }
+ private static final String REQUIRES_ASM5 = "This feature requires ASM5";
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ protected final int api;
+
+ /** The method visitor to which this visitor must delegate method calls. May be null. */
+ protected MethodVisitor mv;
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ public MethodVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
+ * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param methodVisitor the method visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
+ if (api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM7_EXPERIMENTAL) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.mv = methodVisitor;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Parameters, annotations and non standard attributes
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a parameter of this method.
+ *
+ * @param name parameter name or null if none is provided.
+ * @param access the parameter's access flags, only ACC_FINAL, ACC_SYNTHETIC
+ * or/and ACC_MANDATED are allowed (see {@link Opcodes}).
+ */
+ public void visitParameter(final String name, final int access) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ mv.visitParameter(name, access);
+ }
+ }
+
+ /**
+ * Visits the default value of this annotation interface method.
+ *
+ * @return a visitor to the visit the actual default value of this annotation interface method, or
+ * null if this visitor is not interested in visiting this default value. The 'name'
+ * parameters passed to the methods of this annotation visitor are ignored. Moreover, exacly
+ * one visit method must be called on this annotation visitor, followed by visitEnd.
+ */
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (mv != null) {
+ return mv.visitAnnotationDefault();
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation of this method.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (mv != null) {
+ return mv.visitAnnotation(descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the method signature.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#METHOD_TYPE_PARAMETER}, {@link
+ * TypeReference#METHOD_TYPE_PARAMETER_BOUND}, {@link TypeReference#METHOD_RETURN}, {@link
+ * TypeReference#METHOD_RECEIVER}, {@link TypeReference#METHOD_FORMAL_PARAMETER} or {@link
+ * TypeReference#THROWS}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be null if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits the number of method parameters that can have annotations. By default (i.e. when this
+ * method is not called), all the method parameters defined by the method descriptor can have
+ * annotations.
+ *
+ * @param parameterCount the number of method parameters than can have annotations. This number
+ * must be less or equal than the number of parameter types in the method descriptor. It can
+ * be strictly less when a method has synthetic parameters and when these parameters are
+ * ignored when computing parameter indices for the purpose of parameter annotations (see
+ * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
+ * @param visible true to define the number of method parameters that can have
+ * annotations visible at runtime, false to define the number of method parameters
+ * that can have annotations invisible at runtime.
+ */
+ public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
+ if (mv != null) {
+ mv.visitAnnotableParameterCount(parameterCount, visible);
+ }
+ }
+
+ /**
+ * Visits an annotation of a parameter this method.
+ *
+ * @param parameter the parameter index. This index must be strictly smaller than the number of
+ * parameters in the method descriptor, and strictly smaller than the parameter count
+ * specified in {@link #visitAnnotableParameterCount}. Important note: a parameter index i
+ * is not required to correspond to the i'th parameter descriptor in the method
+ * descriptor, in particular in case of synthetic parameters (see
+ * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitParameterAnnotation(
+ final int parameter, final String descriptor, final boolean visible) {
+ if (mv != null) {
+ return mv.visitParameterAnnotation(parameter, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of this method.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (mv != null) {
+ mv.visitAttribute(attribute);
+ }
+ }
+
+ /** Starts the visit of the method's code, if any (i.e. non abstract method). */
+ public void visitCode() {
+ if (mv != null) {
+ mv.visitCode();
+ }
+ }
+
+ /**
+ * Visits the current state of the local variables and operand stack elements. This method must(*)
+ * be called just before any instruction i that follows an unconditional branch
+ * instruction such as GOTO or THROW, that is the target of a jump instruction, or that starts an
+ * exception handler block. The visited types must describe the values of the local variables and
+ * of the operand stack elements just beforei is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or equal to {@link
+ * Opcodes#V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in compressed form (all frames
+ * must use the same format, i.e. you must not mix expanded and compressed frames within a single
+ * method):
+ *
+ *
+ *
In expanded form, all frames must have the F_NEW type.
+ *
In compressed form, frames are basically "deltas" from the state of the previous frame:
+ *
+ *
{@link Opcodes#F_SAME} representing frame with exactly the same locals as the
+ * previous frame and with the empty stack.
+ *
{@link Opcodes#F_SAME1} representing frame with exactly the same locals as the
+ * previous frame and with single value on the stack ( nStack is 1 and
+ * stack[0] contains value for the type of the stack item).
+ *
{@link Opcodes#F_APPEND} representing frame with current locals are the same as the
+ * locals in the previous frame, except that additional locals are defined (
+ * nLocal is 1, 2 or 3 and local elements contains values
+ * representing added types).
+ *
{@link Opcodes#F_CHOP} representing frame with current locals are the same as the
+ * locals in the previous frame, except that the last 1-3 locals are absent and with
+ * the empty stack (nLocals is 1, 2 or 3).
+ *
+ *
+ *
+ * In both cases the first frame, corresponding to the method's parameters and access flags, is
+ * implicit and must not be visited. Also, it is illegal to visit two or more frames for the same
+ * code location (i.e., at least one instruction must be visited between two calls to visitFrame).
+ *
+ * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded
+ * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link
+ * Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames.
+ * @param nLocal the number of local variables in the visited frame.
+ * @param local the local variable types in this frame. This array must not be modified. Primitive
+ * types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
+ * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or
+ * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element).
+ * Reference types are represented by String objects (representing internal names), and
+ * uninitialized types by Label objects (this label designates the NEW instruction that
+ * created this uninitialized value).
+ * @param nStack the number of operand stack elements in the visited frame.
+ * @param stack the operand stack types in this frame. This array must not be modified. Its
+ * content has the same format as the "local" array.
+ * @throws IllegalStateException if a frame is visited just after another one, without any
+ * instruction between the two (unless this frame is a Opcodes#F_SAME frame, in which case it
+ * is silently ignored).
+ */
+ public void visitFrame(
+ final int type,
+ final int nLocal,
+ final Object[] local,
+ final int nStack,
+ final Object[] stack) {
+ if (mv != null) {
+ mv.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Normal instructions
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a zero operand instruction.
+ *
+ * @param opcode the opcode of the instruction to be visited. This opcode is either NOP,
+ * ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5,
+ * LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD,
+ * FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE,
+ * AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2,
+ * SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV,
+ * FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR,
+ * LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I,
+ * D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
+ * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT.
+ */
+ public void visitInsn(final int opcode) {
+ if (mv != null) {
+ mv.visitInsn(opcode);
+ }
+ }
+
+ /**
+ * Visits an instruction with a single int operand.
+ *
+ * @param opcode the opcode of the instruction to be visited. This opcode is either BIPUSH, SIPUSH
+ * or NEWARRAY.
+ * @param operand the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between Byte.MIN_VALUE and Byte.MAX_VALUE.
+ *
+ * When opcode is SIPUSH, operand value should be between Short.MIN_VALUE and Short.MAX_VALUE.
+ *
+ * When opcode is NEWARRAY, operand value should be one of {@link Opcodes#T_BOOLEAN}, {@link
+ * Opcodes#T_CHAR}, {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, {@link Opcodes#T_BYTE},
+ * {@link Opcodes#T_SHORT}, {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
+ */
+ public void visitIntInsn(final int opcode, final int operand) {
+ if (mv != null) {
+ mv.visitIntInsn(opcode, operand);
+ }
+ }
+
+ /**
+ * Visits a local variable instruction. A local variable instruction is an instruction that loads
+ * or stores the value of a local variable.
+ *
+ * @param opcode the opcode of the local variable instruction to be visited. This opcode is either
+ * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
+ * @param var the operand of the instruction to be visited. This operand is the index of a local
+ * variable.
+ */
+ public void visitVarInsn(final int opcode, final int var) {
+ if (mv != null) {
+ mv.visitVarInsn(opcode, var);
+ }
+ }
+
+ /**
+ * Visits a type instruction. A type instruction is an instruction that takes the internal name of
+ * a class as parameter.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW,
+ * ANEWARRAY, CHECKCAST or INSTANCEOF.
+ * @param type the operand of the instruction to be visited. This operand must be the internal
+ * name of an object or array class (see {@link Type#getInternalName()}).
+ */
+ public void visitTypeInsn(final int opcode, final String type) {
+ if (mv != null) {
+ mv.visitTypeInsn(opcode, type);
+ }
+ }
+
+ /**
+ * Visits a field instruction. A field instruction is an instruction that loads or stores the
+ * value of a field of an object.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either
+ * GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
+ * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}).
+ * @param name the field's name.
+ * @param descriptor the field's descriptor (see {@link Type}).
+ */
+ public void visitFieldInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ if (mv != null) {
+ mv.visitFieldInsn(opcode, owner, name, descriptor);
+ }
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that invokes a method.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either
+ * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @deprecated
+ */
+ @Deprecated
+ public void visitMethodInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ if (api >= Opcodes.ASM5) {
+ boolean isInterface = opcode == Opcodes.INVOKEINTERFACE;
+ visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ return;
+ }
+ if (mv != null) {
+ mv.visitMethodInsn(opcode, owner, name, descriptor);
+ }
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that invokes a method.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either
+ * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param isInterface if the method's owner class is an interface.
+ */
+ public void visitMethodInsn(
+ final int opcode,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ if (api < Opcodes.ASM5) {
+ if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) {
+ throw new IllegalArgumentException("INVOKESPECIAL/STATIC on interfaces requires ASM5");
+ }
+ visitMethodInsn(opcode, owner, name, descriptor);
+ return;
+ }
+ if (mv != null) {
+ mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ }
+
+ /**
+ * Visits an invokedynamic instruction.
+ *
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param bootstrapMethodHandle the bootstrap method.
+ * @param bootstrapMethodArguments the bootstrap method constant arguments. Each argument must be
+ * an {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, {@link
+ * Type}, {@link Handle} or {@link ConstantDynamic} value. This method is allowed to modify
+ * the content of the array so a caller should expect that this array may change.
+ */
+ public void visitInvokeDynamicInsn(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+ }
+ }
+
+ /**
+ * Visits a jump instruction. A jump instruction is an instruction that may jump to another
+ * instruction.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either IFEQ,
+ * IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT,
+ * IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
+ * @param label the operand of the instruction to be visited. This operand is a label that
+ * designates the instruction to which the jump instruction may jump.
+ */
+ public void visitJumpInsn(final int opcode, final Label label) {
+ if (mv != null) {
+ mv.visitJumpInsn(opcode, label);
+ }
+ }
+
+ /**
+ * Visits a label. A label designates the instruction that will be visited just after it.
+ *
+ * @param label a {@link Label} object.
+ */
+ public void visitLabel(final Label label) {
+ if (mv != null) {
+ mv.visitLabel(label);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Special instructions
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a LDC instruction. Note that new constant types may be added in future versions of the
+ * Java Virtual Machine. To easily detect new constant types, implementations of this method
+ * should check for unexpected constant types, like this:
+ *
+ *
+ *
+ * @param value the constant to be loaded on the stack. This parameter must be a non null {@link
+ * Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link
+ * Type} of OBJECT or ARRAY sort for .class constants, for classes whose version is
+ * 49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle
+ * constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant
+ * dynamic for classes whose version is 55.
+ */
+ public void visitLdcInsn(final Object value) {
+ if (api < Opcodes.ASM5
+ && (value instanceof Handle
+ || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (api != Opcodes.ASM7_EXPERIMENTAL && value instanceof ConstantDynamic) {
+ throw new UnsupportedOperationException("This feature requires ASM7");
+ }
+ if (mv != null) {
+ mv.visitLdcInsn(value);
+ }
+ }
+
+ /**
+ * Visits an IINC instruction.
+ *
+ * @param var index of the local variable to be incremented.
+ * @param increment amount to increment the local variable by.
+ */
+ public void visitIincInsn(final int var, final int increment) {
+ if (mv != null) {
+ mv.visitIincInsn(var, increment);
+ }
+ }
+
+ /**
+ * Visits a TABLESWITCH instruction.
+ *
+ * @param min the minimum key value.
+ * @param max the maximum key value.
+ * @param dflt beginning of the default handler block.
+ * @param labels beginnings of the handler blocks. labels[i] is the beginning of the
+ * handler block for the min + i key.
+ */
+ public void visitTableSwitchInsn(
+ final int min, final int max, final Label dflt, final Label... labels) {
+ if (mv != null) {
+ mv.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ /**
+ * Visits a LOOKUPSWITCH instruction.
+ *
+ * @param dflt beginning of the default handler block.
+ * @param keys the values of the keys.
+ * @param labels beginnings of the handler blocks. labels[i] is the beginning of the
+ * handler block for the keys[i] key.
+ */
+ public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
+ if (mv != null) {
+ mv.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ /**
+ * Visits a MULTIANEWARRAY instruction.
+ *
+ * @param descriptor an array type descriptor (see {@link Type}).
+ * @param numDimensions the number of dimensions of the array to allocate.
+ */
+ public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
+ if (mv != null) {
+ mv.visitMultiANewArrayInsn(descriptor, numDimensions);
+ }
+ }
+
+ /**
+ * Visits an annotation on an instruction. This method must be called just after the
+ * annotated instruction. It can be called several times for the same instruction.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#INSTANCEOF}, {@link TypeReference#NEW}, {@link
+ * TypeReference#CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE}, {@link
+ * TypeReference#CAST}, {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link
+ * TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT}, {@link
+ * TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link
+ * TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be null if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitInsnAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitInsnAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Exceptions table entries, debug information, max stack and max locals
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a try catch block.
+ *
+ * @param start the beginning of the exception handler's scope (inclusive).
+ * @param end the end of the exception handler's scope (exclusive).
+ * @param handler the beginning of the exception handler's code.
+ * @param type the internal name of the type of exceptions handled by the handler, or
+ * null to catch any exceptions (for "finally" blocks).
+ * @throws IllegalArgumentException if one of the labels has already been visited by this visitor
+ * (by the {@link #visitLabel} method).
+ */
+ public void visitTryCatchBlock(
+ final Label start, final Label end, final Label handler, final String type) {
+ if (mv != null) {
+ mv.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ /**
+ * Visits an annotation on an exception handler type. This method must be called after the
+ * {@link #visitTryCatchBlock} for the annotated exception handler. It can be called several times
+ * for the same exception handler.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#EXCEPTION_PARAMETER}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be null if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTryCatchAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a local variable declaration.
+ *
+ * @param name the name of a local variable.
+ * @param descriptor the type descriptor of this local variable.
+ * @param signature the type signature of this local variable. May be null if the local
+ * variable type does not use generic types.
+ * @param start the first instruction corresponding to the scope of this local variable
+ * (inclusive).
+ * @param end the last instruction corresponding to the scope of this local variable (exclusive).
+ * @param index the local variable's index.
+ * @throws IllegalArgumentException if one of the labels has not already been visited by this
+ * visitor (by the {@link #visitLabel} method).
+ */
+ public void visitLocalVariable(
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Label start,
+ final Label end,
+ final int index) {
+ if (mv != null) {
+ mv.visitLocalVariable(name, descriptor, signature, start, end, index);
+ }
+ }
+
+ /**
+ * Visits an annotation on a local variable type.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE}. See {@link
+ * TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be null if the annotation targets
+ * 'typeRef' as a whole.
+ * @param start the fist instructions corresponding to the continuous ranges that make the scope
+ * of this local variable (inclusive).
+ * @param end the last instructions corresponding to the continuous ranges that make the scope of
+ * this local variable (exclusive). This array must have the same size as the 'start' array.
+ * @param index the local variable's index in each range. This array must have the same size as
+ * the 'start' array.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitLocalVariableAnnotation(
+ final int typeRef,
+ final TypePath typePath,
+ final Label[] start,
+ final Label[] end,
+ final int[] index,
+ final String descriptor,
+ final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitLocalVariableAnnotation(
+ typeRef, typePath, start, end, index, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a line number declaration.
+ *
+ * @param line a line number. This number refers to the source file from which the class was
+ * compiled.
+ * @param start the first instruction corresponding to this line number.
+ * @throws IllegalArgumentException if start has not already been visited by this visitor
+ * (by the {@link #visitLabel} method).
+ */
+ public void visitLineNumber(final int line, final Label start) {
+ if (mv != null) {
+ mv.visitLineNumber(line, start);
+ }
+ }
+
+ /**
+ * Visits the maximum stack size and the maximum number of local variables of the method.
+ *
+ * @param maxStack maximum stack size of the method.
+ * @param maxLocals maximum number of local variables for the method.
+ */
+ public void visitMaxs(final int maxStack, final int maxLocals) {
+ if (mv != null) {
+ mv.visitMaxs(maxStack, maxLocals);
+ }
+ }
+
+ /**
+ * Visits the end of the method. This method, which is the last one to be called, is used to
+ * inform the visitor that all the annotations and attributes of the method have been visited.
+ */
+ public void visitEnd() {
+ if (mv != null) {
+ mv.visitEnd();
}
+ }
}
diff --git a/src/jvm/clojure/asm/MethodWriter.java b/src/jvm/clojure/asm/MethodWriter.java
index 91973188c8..2ced84a191 100644
--- a/src/jvm/clojure/asm/MethodWriter.java
+++ b/src/jvm/clojure/asm/MethodWriter.java
@@ -1,2685 +1,2413 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * A {@link MethodVisitor} that generates methods in bytecode form. Each visit
- * method of this class appends the bytecode corresponding to the visited
- * instruction to a byte vector, in the order these methods are called.
+ * A {@link MethodVisitor} that generates a corresponding 'method_info' structure, as defined in the
+ * Java Virtual Machine Specification (JVMS).
*
+ * @see JVMS
+ * 4.6
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
-class MethodWriter extends MethodVisitor {
-
- /**
- * Pseudo access flag used to denote constructors.
- */
- static final int ACC_CONSTRUCTOR = 0x80000;
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is zero.
- */
- static final int SAME_FRAME = 0; // to 63 (0-3f)
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is 1
- */
- static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f)
-
- /**
- * Reserved for future use
- */
- static final int RESERVED = 128;
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is 1. Offset is bigger then 63;
- */
- static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7
-
- /**
- * Frame where current locals are the same as the locals in the previous
- * frame, except that the k last locals are absent. The value of k is given
- * by the formula 251-frame_type.
- */
- static final int CHOP_FRAME = 248; // to 250 (f8-fA)
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is zero. Offset is bigger then 63;
- */
- static final int SAME_FRAME_EXTENDED = 251; // fb
-
- /**
- * Frame where current locals are the same as the locals in the previous
- * frame, except that k additional locals are defined. The value of k is
- * given by the formula frame_type-251.
- */
- static final int APPEND_FRAME = 252; // to 254 // fc-fe
-
- /**
- * Full frame
- */
- static final int FULL_FRAME = 255; // ff
-
- /**
- * Indicates that the stack map frames must be recomputed from scratch. In
- * this case the maximum stack size and number of local variables is also
- * recomputed from scratch.
- *
- * @see #compute
- */
- private static final int FRAMES = 0;
-
- /**
- * Indicates that the maximum stack size and number of local variables must
- * be automatically computed.
- *
- * @see #compute
- */
- private static final int MAXS = 1;
-
- /**
- * Indicates that nothing must be automatically computed.
- *
- * @see #compute
- */
- private static final int NOTHING = 2;
-
- /**
- * The class writer to which this method must be added.
- */
- final ClassWriter cw;
-
- /**
- * Access flags of this method.
- */
- private int access;
-
- /**
- * The index of the constant pool item that contains the name of this
- * method.
- */
- private final int name;
-
- /**
- * The index of the constant pool item that contains the descriptor of this
- * method.
- */
- private final int desc;
-
- /**
- * The descriptor of this method.
- */
- private final String descriptor;
-
- /**
- * The signature of this method.
- */
- String signature;
-
- /**
- * If not zero, indicates that the code of this method must be copied from
- * the ClassReader associated to this writer in cw.cr. More
- * precisely, this field gives the index of the first byte to copied from
- * cw.cr.b.
- */
- int classReaderOffset;
-
- /**
- * If not zero, indicates that the code of this method must be copied from
- * the ClassReader associated to this writer in cw.cr. More
- * precisely, this field gives the number of bytes to copied from
- * cw.cr.b.
- */
- int classReaderLength;
-
- /**
- * Number of exceptions that can be thrown by this method.
- */
- int exceptionCount;
-
- /**
- * The exceptions that can be thrown by this method. More precisely, this
- * array contains the indexes of the constant pool items that contain the
- * internal names of these exception classes.
- */
- int[] exceptions;
-
- /**
- * The annotation default attribute of this method. May be null.
- */
- private ByteVector annd;
-
- /**
- * The runtime visible annotations of this method. May be null.
- */
- private AnnotationWriter anns;
-
- /**
- * The runtime invisible annotations of this method. May be null.
- */
- private AnnotationWriter ianns;
-
- /**
- * The runtime visible parameter annotations of this method. May be
- * null.
- */
- private AnnotationWriter[] panns;
-
- /**
- * The runtime invisible parameter annotations of this method. May be
- * null.
- */
- private AnnotationWriter[] ipanns;
-
- /**
- * The number of synthetic parameters of this method.
- */
- private int synthetics;
-
- /**
- * The non standard attributes of the method.
- */
- private Attribute attrs;
-
- /**
- * The bytecode of this method.
- */
- private ByteVector code = new ByteVector();
-
- /**
- * Maximum stack size of this method.
- */
- private int maxStack;
-
- /**
- * Maximum number of local variables for this method.
- */
- private int maxLocals;
-
- /**
- * Number of local variables in the current stack map frame.
- */
- private int currentLocals;
-
- /**
- * Number of stack map frames in the StackMapTable attribute.
- */
- private int frameCount;
-
- /**
- * The StackMapTable attribute.
- */
- private ByteVector stackMap;
-
- /**
- * The offset of the last frame that was written in the StackMapTable
- * attribute.
- */
- private int previousFrameOffset;
-
- /**
- * The last frame that was written in the StackMapTable attribute.
- *
- * @see #frame
- */
- private int[] previousFrame;
-
- /**
- * The current stack map frame. The first element contains the offset of the
- * instruction to which the frame corresponds, the second element is the
- * number of locals and the third one is the number of stack elements. The
- * local variables start at index 3 and are followed by the operand stack
- * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] =
- * nStack, frame[3] = nLocal. All types are encoded as integers, with the
- * same format as the one used in {@link Label}, but limited to BASE types.
- */
- private int[] frame;
-
- /**
- * Number of elements in the exception handler list.
- */
- private int handlerCount;
-
- /**
- * The first element in the exception handler list.
- */
- private Handler firstHandler;
-
- /**
- * The last element in the exception handler list.
- */
- private Handler lastHandler;
-
- /**
- * Number of entries in the LocalVariableTable attribute.
- */
- private int localVarCount;
-
- /**
- * The LocalVariableTable attribute.
- */
- private ByteVector localVar;
-
- /**
- * Number of entries in the LocalVariableTypeTable attribute.
- */
- private int localVarTypeCount;
-
- /**
- * The LocalVariableTypeTable attribute.
- */
- private ByteVector localVarType;
-
- /**
- * Number of entries in the LineNumberTable attribute.
- */
- private int lineNumberCount;
-
- /**
- * The LineNumberTable attribute.
- */
- private ByteVector lineNumber;
-
- /**
- * The non standard attributes of the method's code.
- */
- private Attribute cattrs;
-
- /**
- * Indicates if some jump instructions are too small and need to be resized.
- */
- private boolean resize;
-
- /**
- * The number of subroutines in this method.
- */
- private int subroutines;
-
- // ------------------------------------------------------------------------
-
- /*
- * Fields for the control flow graph analysis algorithm (used to compute the
- * maximum stack size). A control flow graph contains one node per "basic
- * block", and one edge per "jump" from one basic block to another. Each
- * node (i.e., each basic block) is represented by the Label object that
- * corresponds to the first instruction of this basic block. Each node also
- * stores the list of its successors in the graph, as a linked list of Edge
- * objects.
- */
-
- /**
- * Indicates what must be automatically computed.
- *
- * @see #FRAMES
- * @see #MAXS
- * @see #NOTHING
- */
- private final int compute;
-
- /**
- * A list of labels. This list is the list of basic blocks in the method,
- * i.e. a list of Label objects linked to each other by their
- * {@link Label#successor} field, in the order they are visited by
- * {@link MethodVisitor#visitLabel}, and starting with the first basic
- * block.
- */
- private Label labels;
-
- /**
- * The previous basic block.
- */
- private Label previousBlock;
-
- /**
- * The current basic block.
- */
- private Label currentBlock;
-
- /**
- * The (relative) stack size after the last visited instruction. This size
- * is relative to the beginning of the current basic block, i.e., the true
- * stack size after the last visited instruction is equal to the
- * {@link Label#inputStackTop beginStackSize} of the current basic block
- * plus stackSize.
- */
- private int stackSize;
-
- /**
- * The (relative) maximum stack size after the last visited instruction.
- * This size is relative to the beginning of the current basic block, i.e.,
- * the true maximum stack size after the last visited instruction is equal
- * to the {@link Label#inputStackTop beginStackSize} of the current basic
- * block plus stackSize.
- */
- private int maxStackSize;
-
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new {@link MethodWriter}.
- *
- * @param cw
- * the class writer in which the method must be added.
- * @param access
- * the method's access flags (see {@link Opcodes}).
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type}).
- * @param signature
- * the method's signature. May be null.
- * @param exceptions
- * the internal names of the method's exceptions. May be
- * null.
- * @param computeMaxs
- * true if the maximum stack size and number of local
- * variables must be automatically computed.
- * @param computeFrames
- * true if the stack map tables must be recomputed from
- * scratch.
- */
- MethodWriter(final ClassWriter cw, final int access, final String name,
- final String desc, final String signature,
- final String[] exceptions, final boolean computeMaxs,
- final boolean computeFrames) {
- super(Opcodes.ASM4);
- if (cw.firstMethod == null) {
- cw.firstMethod = this;
- } else {
- cw.lastMethod.mv = this;
- }
- cw.lastMethod = this;
- this.cw = cw;
- this.access = access;
- if ("".equals(name)) {
- this.access |= ACC_CONSTRUCTOR;
- }
- this.name = cw.newUTF8(name);
- this.desc = cw.newUTF8(desc);
- this.descriptor = desc;
- if (ClassReader.SIGNATURES) {
- this.signature = signature;
- }
- if (exceptions != null && exceptions.length > 0) {
- exceptionCount = exceptions.length;
- this.exceptions = new int[exceptionCount];
- for (int i = 0; i < exceptionCount; ++i) {
- this.exceptions[i] = cw.newClass(exceptions[i]);
- }
- }
- this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING);
- if (computeMaxs || computeFrames) {
- // updates maxLocals
- int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
- if ((access & Opcodes.ACC_STATIC) != 0) {
- --size;
- }
- maxLocals = size;
- currentLocals = size;
- // creates and visits the label for the first basic block
- labels = new Label();
- labels.status |= Label.PUSHED;
- visitLabel(labels);
- }
+final class MethodWriter extends MethodVisitor {
+
+ /** Indicates that nothing must be computed. */
+ static final int COMPUTE_NOTHING = 0;
+
+ /**
+ * Indicates that the maximum stack size and the maximum number of local variables must be
+ * computed, from scratch.
+ */
+ static final int COMPUTE_MAX_STACK_AND_LOCAL = 1;
+
+ /**
+ * Indicates that the maximum stack size and the maximum number of local variables must be
+ * computed, from the existing stack map frames. This can be done more efficiently than with the
+ * control flow graph algorithm used for {@link #COMPUTE_MAX_STACK_AND_LOCAL}, by using a linear
+ * scan of the bytecode instructions.
+ */
+ static final int COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES = 2;
+
+ /**
+ * Indicates that the stack map frames of type F_INSERT must be computed. The other frames are not
+ * computed. They should all be of type F_NEW and should be sufficient to compute the content of
+ * the F_INSERT frames, together with the bytecode instructions between a F_NEW and a F_INSERT
+ * frame - and without any knowledge of the type hierarchy (by definition of F_INSERT).
+ */
+ static final int COMPUTE_INSERTED_FRAMES = 3;
+
+ /**
+ * Indicates that all the stack map frames must be computed. In this case the maximum stack size
+ * and the maximum number of local variables is also computed.
+ */
+ static final int COMPUTE_ALL_FRAMES = 4;
+
+ /** Indicates that {@link #STACK_SIZE_DELTA} is not applicable (not constant or never used). */
+ private static final int NA = 0;
+
+ /**
+ * The stack size variation corresponding to each JVM opcode. The stack size variation for opcode
+ * 'o' is given by the array element at index 'o'.
+ *
+ * @see JVMS 6
+ */
+ private static final int[] STACK_SIZE_DELTA = {
+ 0, // nop = 0 (0x0)
+ 1, // aconst_null = 1 (0x1)
+ 1, // iconst_m1 = 2 (0x2)
+ 1, // iconst_0 = 3 (0x3)
+ 1, // iconst_1 = 4 (0x4)
+ 1, // iconst_2 = 5 (0x5)
+ 1, // iconst_3 = 6 (0x6)
+ 1, // iconst_4 = 7 (0x7)
+ 1, // iconst_5 = 8 (0x8)
+ 2, // lconst_0 = 9 (0x9)
+ 2, // lconst_1 = 10 (0xa)
+ 1, // fconst_0 = 11 (0xb)
+ 1, // fconst_1 = 12 (0xc)
+ 1, // fconst_2 = 13 (0xd)
+ 2, // dconst_0 = 14 (0xe)
+ 2, // dconst_1 = 15 (0xf)
+ 1, // bipush = 16 (0x10)
+ 1, // sipush = 17 (0x11)
+ 1, // ldc = 18 (0x12)
+ NA, // ldc_w = 19 (0x13)
+ NA, // ldc2_w = 20 (0x14)
+ 1, // iload = 21 (0x15)
+ 2, // lload = 22 (0x16)
+ 1, // fload = 23 (0x17)
+ 2, // dload = 24 (0x18)
+ 1, // aload = 25 (0x19)
+ NA, // iload_0 = 26 (0x1a)
+ NA, // iload_1 = 27 (0x1b)
+ NA, // iload_2 = 28 (0x1c)
+ NA, // iload_3 = 29 (0x1d)
+ NA, // lload_0 = 30 (0x1e)
+ NA, // lload_1 = 31 (0x1f)
+ NA, // lload_2 = 32 (0x20)
+ NA, // lload_3 = 33 (0x21)
+ NA, // fload_0 = 34 (0x22)
+ NA, // fload_1 = 35 (0x23)
+ NA, // fload_2 = 36 (0x24)
+ NA, // fload_3 = 37 (0x25)
+ NA, // dload_0 = 38 (0x26)
+ NA, // dload_1 = 39 (0x27)
+ NA, // dload_2 = 40 (0x28)
+ NA, // dload_3 = 41 (0x29)
+ NA, // aload_0 = 42 (0x2a)
+ NA, // aload_1 = 43 (0x2b)
+ NA, // aload_2 = 44 (0x2c)
+ NA, // aload_3 = 45 (0x2d)
+ -1, // iaload = 46 (0x2e)
+ 0, // laload = 47 (0x2f)
+ -1, // faload = 48 (0x30)
+ 0, // daload = 49 (0x31)
+ -1, // aaload = 50 (0x32)
+ -1, // baload = 51 (0x33)
+ -1, // caload = 52 (0x34)
+ -1, // saload = 53 (0x35)
+ -1, // istore = 54 (0x36)
+ -2, // lstore = 55 (0x37)
+ -1, // fstore = 56 (0x38)
+ -2, // dstore = 57 (0x39)
+ -1, // astore = 58 (0x3a)
+ NA, // istore_0 = 59 (0x3b)
+ NA, // istore_1 = 60 (0x3c)
+ NA, // istore_2 = 61 (0x3d)
+ NA, // istore_3 = 62 (0x3e)
+ NA, // lstore_0 = 63 (0x3f)
+ NA, // lstore_1 = 64 (0x40)
+ NA, // lstore_2 = 65 (0x41)
+ NA, // lstore_3 = 66 (0x42)
+ NA, // fstore_0 = 67 (0x43)
+ NA, // fstore_1 = 68 (0x44)
+ NA, // fstore_2 = 69 (0x45)
+ NA, // fstore_3 = 70 (0x46)
+ NA, // dstore_0 = 71 (0x47)
+ NA, // dstore_1 = 72 (0x48)
+ NA, // dstore_2 = 73 (0x49)
+ NA, // dstore_3 = 74 (0x4a)
+ NA, // astore_0 = 75 (0x4b)
+ NA, // astore_1 = 76 (0x4c)
+ NA, // astore_2 = 77 (0x4d)
+ NA, // astore_3 = 78 (0x4e)
+ -3, // iastore = 79 (0x4f)
+ -4, // lastore = 80 (0x50)
+ -3, // fastore = 81 (0x51)
+ -4, // dastore = 82 (0x52)
+ -3, // aastore = 83 (0x53)
+ -3, // bastore = 84 (0x54)
+ -3, // castore = 85 (0x55)
+ -3, // sastore = 86 (0x56)
+ -1, // pop = 87 (0x57)
+ -2, // pop2 = 88 (0x58)
+ 1, // dup = 89 (0x59)
+ 1, // dup_x1 = 90 (0x5a)
+ 1, // dup_x2 = 91 (0x5b)
+ 2, // dup2 = 92 (0x5c)
+ 2, // dup2_x1 = 93 (0x5d)
+ 2, // dup2_x2 = 94 (0x5e)
+ 0, // swap = 95 (0x5f)
+ -1, // iadd = 96 (0x60)
+ -2, // ladd = 97 (0x61)
+ -1, // fadd = 98 (0x62)
+ -2, // dadd = 99 (0x63)
+ -1, // isub = 100 (0x64)
+ -2, // lsub = 101 (0x65)
+ -1, // fsub = 102 (0x66)
+ -2, // dsub = 103 (0x67)
+ -1, // imul = 104 (0x68)
+ -2, // lmul = 105 (0x69)
+ -1, // fmul = 106 (0x6a)
+ -2, // dmul = 107 (0x6b)
+ -1, // idiv = 108 (0x6c)
+ -2, // ldiv = 109 (0x6d)
+ -1, // fdiv = 110 (0x6e)
+ -2, // ddiv = 111 (0x6f)
+ -1, // irem = 112 (0x70)
+ -2, // lrem = 113 (0x71)
+ -1, // frem = 114 (0x72)
+ -2, // drem = 115 (0x73)
+ 0, // ineg = 116 (0x74)
+ 0, // lneg = 117 (0x75)
+ 0, // fneg = 118 (0x76)
+ 0, // dneg = 119 (0x77)
+ -1, // ishl = 120 (0x78)
+ -1, // lshl = 121 (0x79)
+ -1, // ishr = 122 (0x7a)
+ -1, // lshr = 123 (0x7b)
+ -1, // iushr = 124 (0x7c)
+ -1, // lushr = 125 (0x7d)
+ -1, // iand = 126 (0x7e)
+ -2, // land = 127 (0x7f)
+ -1, // ior = 128 (0x80)
+ -2, // lor = 129 (0x81)
+ -1, // ixor = 130 (0x82)
+ -2, // lxor = 131 (0x83)
+ 0, // iinc = 132 (0x84)
+ 1, // i2l = 133 (0x85)
+ 0, // i2f = 134 (0x86)
+ 1, // i2d = 135 (0x87)
+ -1, // l2i = 136 (0x88)
+ -1, // l2f = 137 (0x89)
+ 0, // l2d = 138 (0x8a)
+ 0, // f2i = 139 (0x8b)
+ 1, // f2l = 140 (0x8c)
+ 1, // f2d = 141 (0x8d)
+ -1, // d2i = 142 (0x8e)
+ 0, // d2l = 143 (0x8f)
+ -1, // d2f = 144 (0x90)
+ 0, // i2b = 145 (0x91)
+ 0, // i2c = 146 (0x92)
+ 0, // i2s = 147 (0x93)
+ -3, // lcmp = 148 (0x94)
+ -1, // fcmpl = 149 (0x95)
+ -1, // fcmpg = 150 (0x96)
+ -3, // dcmpl = 151 (0x97)
+ -3, // dcmpg = 152 (0x98)
+ -1, // ifeq = 153 (0x99)
+ -1, // ifne = 154 (0x9a)
+ -1, // iflt = 155 (0x9b)
+ -1, // ifge = 156 (0x9c)
+ -1, // ifgt = 157 (0x9d)
+ -1, // ifle = 158 (0x9e)
+ -2, // if_icmpeq = 159 (0x9f)
+ -2, // if_icmpne = 160 (0xa0)
+ -2, // if_icmplt = 161 (0xa1)
+ -2, // if_icmpge = 162 (0xa2)
+ -2, // if_icmpgt = 163 (0xa3)
+ -2, // if_icmple = 164 (0xa4)
+ -2, // if_acmpeq = 165 (0xa5)
+ -2, // if_acmpne = 166 (0xa6)
+ 0, // goto = 167 (0xa7)
+ 1, // jsr = 168 (0xa8)
+ 0, // ret = 169 (0xa9)
+ -1, // tableswitch = 170 (0xaa)
+ -1, // lookupswitch = 171 (0xab)
+ -1, // ireturn = 172 (0xac)
+ -2, // lreturn = 173 (0xad)
+ -1, // freturn = 174 (0xae)
+ -2, // dreturn = 175 (0xaf)
+ -1, // areturn = 176 (0xb0)
+ 0, // return = 177 (0xb1)
+ NA, // getstatic = 178 (0xb2)
+ NA, // putstatic = 179 (0xb3)
+ NA, // getfield = 180 (0xb4)
+ NA, // putfield = 181 (0xb5)
+ NA, // invokevirtual = 182 (0xb6)
+ NA, // invokespecial = 183 (0xb7)
+ NA, // invokestatic = 184 (0xb8)
+ NA, // invokeinterface = 185 (0xb9)
+ NA, // invokedynamic = 186 (0xba)
+ 1, // new = 187 (0xbb)
+ 0, // newarray = 188 (0xbc)
+ 0, // anewarray = 189 (0xbd)
+ 0, // arraylength = 190 (0xbe)
+ NA, // athrow = 191 (0xbf)
+ 0, // checkcast = 192 (0xc0)
+ 0, // instanceof = 193 (0xc1)
+ -1, // monitorenter = 194 (0xc2)
+ -1, // monitorexit = 195 (0xc3)
+ NA, // wide = 196 (0xc4)
+ NA, // multianewarray = 197 (0xc5)
+ -1, // ifnull = 198 (0xc6)
+ -1, // ifnonnull = 199 (0xc7)
+ NA, // goto_w = 200 (0xc8)
+ NA // jsr_w = 201 (0xc9)
+ };
+
+ /** Where the constants used in this MethodWriter must be stored. */
+ private final SymbolTable symbolTable;
+
+ // Note: fields are ordered as in the method_info structure, and those related to attributes are
+ // ordered as in Section 4.7 of the JVMS.
+
+ /**
+ * The access_flags field of the method_info JVMS structure. This field can contain ASM specific
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
+ * ClassFile structure.
+ */
+ private final int accessFlags;
+
+ /** The name_index field of the method_info JVMS structure. */
+ private final int nameIndex;
+
+ /** The descriptor_index field of the method_info JVMS structure. */
+ private final int descriptorIndex;
+
+ /** The descriptor of this method. */
+ private final String descriptor;
+
+ // Code attribute fields and sub attributes:
+
+ /** The max_stack field of the Code attribute. */
+ private int maxStack;
+
+ /** The max_locals field of the Code attribute. */
+ private int maxLocals;
+
+ /** The 'code' field of the Code attribute. */
+ private final ByteVector code = new ByteVector();
+
+ /**
+ * The first element in the exception handler list (used to generate the exception_table of the
+ * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May
+ * be null.
+ */
+ private Handler firstHandler;
+
+ /**
+ * The last element in the exception handler list (used to generate the exception_table of the
+ * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May
+ * be null.
+ */
+ private Handler lastHandler;
+
+ /** The line_number_table_length field of the LineNumberTable code attribute. */
+ private int lineNumberTableLength;
+
+ /** The line_number_table array of the LineNumberTable code attribute, or null. */
+ private ByteVector lineNumberTable;
+
+ /** The local_variable_table_length field of the LocalVariableTable code attribute. */
+ private int localVariableTableLength;
+
+ /** The local_variable_table array of the LocalVariableTable code attribute, or null. */
+ private ByteVector localVariableTable;
+
+ /** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */
+ private int localVariableTypeTableLength;
+
+ /**
+ * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or
+ * null.
+ */
+ private ByteVector localVariableTypeTable;
+
+ /** The number_of_entries field of the StackMapTable code attribute. */
+ private int stackMapTableNumberOfEntries;
+
+ /** The 'entries' array of the StackMapTable code attribute. */
+ private ByteVector stackMapTableEntries;
+
+ /**
+ * The last runtime visible type annotation of the Code attribute. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of the Code attribute. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation;
+
+ /**
+ * The first non standard attribute of the Code attribute. The next ones can be accessed with the
+ * {@link Attribute#nextAttribute} field. May be null.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstCodeAttribute;
+
+ // Other method_info attributes:
+
+ /** The number_of_exceptions field of the Exceptions attribute. */
+ private final int numberOfExceptions;
+
+ /** The exception_index_table array of the Exceptions attribute, or null. */
+ private final int[] exceptionIndexTable;
+
+ /** The signature_index field of the Signature attribute. */
+ private final int signatureIndex;
+
+ /**
+ * The last runtime visible annotation of this method. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
+
+ /**
+ * The last runtime invisible annotation of this method. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
+
+ /** The number of method parameters that can have runtime visible annotations, or 0. */
+ private int visibleAnnotableParameterCount;
+
+ /**
+ * The runtime visible parameter annotations of this method. Each array element contains the last
+ * annotation of a parameter (which can be null - the previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field). May be null.
+ */
+ private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations;
+
+ /** The number of method parameters that can have runtime visible annotations, or 0. */
+ private int invisibleAnnotableParameterCount;
+
+ /**
+ * The runtime invisible parameter annotations of this method. Each array element contains the
+ * last annotation of a parameter (which can be null - the previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field). May be null.
+ */
+ private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations;
+
+ /**
+ * The last runtime visible type annotation of this method. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of this method. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /** The default_value field of the AnnotationDefault attribute, or null. */
+ private ByteVector defaultValue;
+
+ /** The parameters_count field of the MethodParameters attribute. */
+ private int parametersCount;
+
+ /** The 'parameters' array of the MethodParameters attribute, or null. */
+ private ByteVector parameters;
+
+ /**
+ * The first non standard attribute of this method. The next ones can be accessed with the {@link
+ * Attribute#nextAttribute} field. May be null.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
+
+ // -----------------------------------------------------------------------------------------------
+ // Fields used to compute the maximum stack size and number of locals, and the stack map frames
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link
+ * #COMPUTE_INSERTED_FRAMES}, {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
+ */
+ private final int compute;
+
+ /**
+ * The first basic block of the method. The next ones (in bytecode offset order) can be accessed
+ * with the {@link Label#nextBasicBlock} field.
+ */
+ private Label firstBasicBlock;
+
+ /**
+ * The last basic block of the method (in bytecode offset order). This field is updated each time
+ * a basic block is encountered, and is used to append it at the end of the basic block list.
+ */
+ private Label lastBasicBlock;
+
+ /**
+ * The current basic block, i.e. the basic block of the last visited instruction. When {@link
+ * #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this
+ * field is null for unreachable code. When {@link #compute} is equal to {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays
+ * unchanged throughout the whole method (i.e. the whole code is seen as a single basic block;
+ * indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame -
+ * and the maximum stack size as well - without using any control flow graph).
+ */
+ private Label currentBasicBlock;
+
+ /**
+ * The relative stack size after the last visited instruction. This size is relative to the
+ * beginning of {@link #currentBasicBlock}, i.e. the true stack size after the last visited
+ * instruction is equal to the {@link Label#inputStackSize} of the current basic block plus {@link
+ * #relativeStackSize}. When {@link #compute} is equal to {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
+ * the method, so this relative size is also equal to the absolute stack size after the last
+ * visited instruction.
+ */
+ private int relativeStackSize;
+
+ /**
+ * The maximum relative stack size after the last visited instruction. This size is relative to
+ * the beginning of {@link #currentBasicBlock}, i.e. the true maximum stack size after the last
+ * visited instruction is equal to the {@link Label#inputStackSize} of the current basic block
+ * plus {@link #maxRelativeStackSize}.When {@link #compute} is equal to {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
+ * the method, so this relative size is also equal to the absolute maximum stack size after the
+ * last visited instruction.
+ */
+ private int maxRelativeStackSize;
+
+ /** The number of local variables in the last visited stack map frame. */
+ private int currentLocals;
+
+ /** The bytecode offset of the last frame that was written in {@link #stackMapTableEntries}. */
+ private int previousFrameOffset;
+
+ /**
+ * The last frame that was written in {@link #stackMapTableEntries}. This field has the same
+ * format as {@link #currentFrame}.
+ */
+ private int[] previousFrame;
+
+ /**
+ * The current stack map frame. The first element contains the bytecode offset of the instruction
+ * to which the frame corresponds, the second element is the number of locals and the third one is
+ * the number of stack elements. The local variables start at index 3 and are followed by the
+ * operand stack elements. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = nStack,
+ * frame[3] = nLocal. Local variables and operand stack entries contain abstract types, as defined
+ * in {@link Frame}, but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND}
+ * or {@link Frame#UNINITIALIZED_KIND} abstract types. Long and double types use only one array
+ * entry.
+ */
+ private int[] currentFrame;
+
+ /** Whether this method contains subroutines. */
+ private boolean hasSubroutines;
+
+ // -----------------------------------------------------------------------------------------------
+ // Other miscellaneous status fields
+ // -----------------------------------------------------------------------------------------------
+
+ /** Whether the bytecode of this method contains ASM specific instructions. */
+ private boolean hasAsmInstructions;
+
+ /**
+ * The start offset of the last visited instruction. Used to set the offset field of type
+ * annotations of type 'offset_target' (see JVMS
+ * 4.7.20.1).
+ */
+ private int lastBytecodeOffset;
+
+ /**
+ * The offset in bytes in {@link SymbolTable#getSource} from which the method_info for this method
+ * (excluding its first 6 bytes) must be copied, or 0.
+ */
+ private int sourceOffset;
+
+ /**
+ * The length in bytes in {@link SymbolTable#getSource} which must be copied to get the
+ * method_info for this method (excluding its first 6 bytes for access_flags, name_index and
+ * descriptor_index).
+ */
+ private int sourceLength;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructor and accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link MethodWriter}.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param access the method's access flags (see {@link Opcodes}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param signature the method's signature. May be null.
+ * @param exceptions the internal names of the method's exceptions. May be null.
+ * @param compute indicates what must be computed (see #compute).
+ */
+ MethodWriter(
+ final SymbolTable symbolTable,
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions,
+ final int compute) {
+ super(Opcodes.ASM6);
+ this.symbolTable = symbolTable;
+ this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
+ this.nameIndex = symbolTable.addConstantUtf8(name);
+ this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
+ this.descriptor = descriptor;
+ this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature);
+ if (exceptions != null && exceptions.length > 0) {
+ numberOfExceptions = exceptions.length;
+ this.exceptionIndexTable = new int[numberOfExceptions];
+ for (int i = 0; i < numberOfExceptions; ++i) {
+ this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index;
+ }
+ } else {
+ numberOfExceptions = 0;
+ this.exceptionIndexTable = null;
}
+ this.compute = compute;
+ if (compute != COMPUTE_NOTHING) {
+ // Update maxLocals and currentLocals.
+ int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
+ if ((access & Opcodes.ACC_STATIC) != 0) {
+ --argumentsSize;
+ }
+ maxLocals = argumentsSize;
+ currentLocals = argumentsSize;
+ // Create and visit the label for the first basic block.
+ firstBasicBlock = new Label();
+ visitLabel(firstBasicBlock);
+ }
+ }
- // ------------------------------------------------------------------------
- // Implementation of the MethodVisitor abstract class
- // ------------------------------------------------------------------------
+ boolean hasFrames() {
+ return stackMapTableNumberOfEntries > 0;
+ }
- @Override
- public AnnotationVisitor visitAnnotationDefault() {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- annd = new ByteVector();
- return new AnnotationWriter(cw, false, annd, null, 0);
- }
+ boolean hasAsmInstructions() {
+ return hasAsmInstructions;
+ }
- @Override
- public AnnotationVisitor visitAnnotation(final String desc,
- final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- // write type, and reserve space for values count
- bv.putShort(cw.newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
- if (visible) {
- aw.next = anns;
- anns = aw;
- } else {
- aw.next = ianns;
- ianns = aw;
- }
- return aw;
- }
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the MethodVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
- @Override
- public AnnotationVisitor visitParameterAnnotation(final int parameter,
- final String desc, final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- if ("Ljava/lang/Synthetic;".equals(desc)) {
- // workaround for a bug in javac with synthetic parameters
- // see ClassReader.readParameterAnnotations
- synthetics = Math.max(synthetics, parameter + 1);
- return new AnnotationWriter(cw, false, bv, null, 0);
- }
- // write type, and reserve space for values count
- bv.putShort(cw.newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
- if (visible) {
- if (panns == null) {
- panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
- }
- aw.next = panns[parameter];
- panns[parameter] = aw;
- } else {
- if (ipanns == null) {
- ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
- }
- aw.next = ipanns[parameter];
- ipanns[parameter] = aw;
- }
- return aw;
+ @Override
+ public void visitParameter(final String name, final int access) {
+ if (parameters == null) {
+ parameters = new ByteVector();
}
-
- @Override
- public void visitAttribute(final Attribute attr) {
- if (attr.isCodeAttribute()) {
- attr.next = cattrs;
- cattrs = attr;
- } else {
- attr.next = attrs;
- attrs = attr;
- }
+ ++parametersCount;
+ parameters.putShort((name == null) ? 0 : symbolTable.addConstantUtf8(name)).putShort(access);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ defaultValue = new ByteVector();
+ return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold an 'annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
+ ByteVector annotation = new ByteVector();
+ // Write type_index and reserve space for num_element_value_pairs.
+ annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
}
-
- @Override
- public void visitCode() {
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ TypeReference.putTarget(typeRef, typeAnnotation);
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
+ if (visible) {
+ visibleAnnotableParameterCount = parameterCount;
+ } else {
+ invisibleAnnotableParameterCount = parameterCount;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(
+ final int parameter, final String annotationDescriptor, final boolean visible) {
+ // Create a ByteVector to hold an 'annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
+ ByteVector annotation = new ByteVector();
+ // Write type_index and reserve space for num_element_value_pairs.
+ annotation.putShort(symbolTable.addConstantUtf8(annotationDescriptor)).putShort(0);
+ if (visible) {
+ if (lastRuntimeVisibleParameterAnnotations == null) {
+ lastRuntimeVisibleParameterAnnotations =
+ new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ return lastRuntimeVisibleParameterAnnotations[parameter] =
+ new AnnotationWriter(
+ symbolTable, annotation, lastRuntimeVisibleParameterAnnotations[parameter]);
+ } else {
+ if (lastRuntimeInvisibleParameterAnnotations == null) {
+ lastRuntimeInvisibleParameterAnnotations =
+ new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ return lastRuntimeInvisibleParameterAnnotations[parameter] =
+ new AnnotationWriter(
+ symbolTable, annotation, lastRuntimeInvisibleParameterAnnotations[parameter]);
+ }
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ if (attribute.isCodeAttribute()) {
+ attribute.nextAttribute = firstCodeAttribute;
+ firstCodeAttribute = attribute;
+ } else {
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
+ }
+
+ @Override
+ public void visitCode() {
+ // Nothing to do.
+ }
+
+ @Override
+ public void visitFrame(
+ final int type,
+ final int nLocal,
+ final Object[] local,
+ final int nStack,
+ final Object[] stack) {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ return;
}
- @Override
- public void visitFrame(final int type, final int nLocal,
- final Object[] local, final int nStack, final Object[] stack) {
- if (!ClassReader.FRAMES || compute == FRAMES) {
- return;
- }
-
+ if (compute == COMPUTE_INSERTED_FRAMES) {
+ if (currentBasicBlock.frame == null) {
+ // This should happen only once, for the implicit first frame (which is explicitly visited
+ // in ClassReader if the EXPAND_ASM_INSNS option is used - and COMPUTE_INSERTED_FRAMES
+ // can't be set if EXPAND_ASM_INSNS is not used).
+ currentBasicBlock.frame = new CurrentFrame(currentBasicBlock);
+ currentBasicBlock.frame.setInputFrameFromDescriptor(
+ symbolTable, accessFlags, descriptor, nLocal);
+ currentBasicBlock.frame.accept(this);
+ } else {
if (type == Opcodes.F_NEW) {
- if (previousFrame == null) {
- visitImplicitFirstFrame();
- }
- currentLocals = nLocal;
- int frameIndex = startFrame(code.length, nLocal, nStack);
- for (int i = 0; i < nLocal; ++i) {
- if (local[i] instanceof String) {
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType((String) local[i]);
- } else if (local[i] instanceof Integer) {
- frame[frameIndex++] = ((Integer) local[i]).intValue();
- } else {
- frame[frameIndex++] = Frame.UNINITIALIZED
- | cw.addUninitializedType("",
- ((Label) local[i]).position);
- }
- }
- for (int i = 0; i < nStack; ++i) {
- if (stack[i] instanceof String) {
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType((String) stack[i]);
- } else if (stack[i] instanceof Integer) {
- frame[frameIndex++] = ((Integer) stack[i]).intValue();
- } else {
- frame[frameIndex++] = Frame.UNINITIALIZED
- | cw.addUninitializedType("",
- ((Label) stack[i]).position);
- }
- }
- endFrame();
+ currentBasicBlock.frame.setInputFrameFromApiFormat(
+ symbolTable, nLocal, local, nStack, stack);
} else {
- int delta;
- if (stackMap == null) {
- stackMap = new ByteVector();
- delta = code.length;
- } else {
- delta = code.length - previousFrameOffset - 1;
- if (delta < 0) {
- if (type == Opcodes.F_SAME) {
- return;
- } else {
- throw new IllegalStateException();
- }
- }
- }
-
- switch (type) {
- case Opcodes.F_FULL:
- currentLocals = nLocal;
- stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal);
- for (int i = 0; i < nLocal; ++i) {
- writeFrameType(local[i]);
- }
- stackMap.putShort(nStack);
- for (int i = 0; i < nStack; ++i) {
- writeFrameType(stack[i]);
- }
- break;
- case Opcodes.F_APPEND:
- currentLocals += nLocal;
- stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta);
- for (int i = 0; i < nLocal; ++i) {
- writeFrameType(local[i]);
- }
- break;
- case Opcodes.F_CHOP:
- currentLocals -= nLocal;
- stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta);
- break;
- case Opcodes.F_SAME:
- if (delta < 64) {
- stackMap.putByte(delta);
- } else {
- stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
- }
- break;
- case Opcodes.F_SAME1:
- if (delta < 64) {
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
- } else {
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
- .putShort(delta);
- }
- writeFrameType(stack[0]);
- break;
- }
-
- previousFrameOffset = code.length;
- ++frameCount;
- }
+ // In this case type is equal to F_INSERT by hypothesis, and currentBlock.frame contains
+ // the stack map frame at the current instruction, computed from the last F_NEW frame
+ // and the bytecode instructions in between (via calls to CurrentFrame#execute).
+ }
+ currentBasicBlock.frame.accept(this);
+ }
+ } else if (type == Opcodes.F_NEW) {
+ if (previousFrame == null) {
+ int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
+ Frame implicitFirstFrame = new Frame(new Label());
+ implicitFirstFrame.setInputFrameFromDescriptor(
+ symbolTable, accessFlags, descriptor, argumentsSize);
+ implicitFirstFrame.accept(this);
+ }
+ currentLocals = nLocal;
+ int frameIndex = visitFrameStart(code.length, nLocal, nStack);
+ for (int i = 0; i < nLocal; ++i) {
+ currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]);
+ }
+ for (int i = 0; i < nStack; ++i) {
+ currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]);
+ }
+ visitFrameEnd();
+ } else {
+ int offsetDelta;
+ if (stackMapTableEntries == null) {
+ stackMapTableEntries = new ByteVector();
+ offsetDelta = code.length;
+ } else {
+ offsetDelta = code.length - previousFrameOffset - 1;
+ if (offsetDelta < 0) {
+ if (type == Opcodes.F_SAME) {
+ return;
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ switch (type) {
+ case Opcodes.F_FULL:
+ currentLocals = nLocal;
+ stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal);
+ for (int i = 0; i < nLocal; ++i) {
+ putFrameType(local[i]);
+ }
+ stackMapTableEntries.putShort(nStack);
+ for (int i = 0; i < nStack; ++i) {
+ putFrameType(stack[i]);
+ }
+ break;
+ case Opcodes.F_APPEND:
+ currentLocals += nLocal;
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocal).putShort(offsetDelta);
+ for (int i = 0; i < nLocal; ++i) {
+ putFrameType(local[i]);
+ }
+ break;
+ case Opcodes.F_CHOP:
+ currentLocals -= nLocal;
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - nLocal).putShort(offsetDelta);
+ break;
+ case Opcodes.F_SAME:
+ if (offsetDelta < 64) {
+ stackMapTableEntries.putByte(offsetDelta);
+ } else {
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
+ }
+ break;
+ case Opcodes.F_SAME1:
+ if (offsetDelta < 64) {
+ stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
+ } else {
+ stackMapTableEntries
+ .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(offsetDelta);
+ }
+ putFrameType(stack[0]);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
- maxStack = Math.max(maxStack, nStack);
- maxLocals = Math.max(maxLocals, currentLocals);
- }
-
- @Override
- public void visitInsn(final int opcode) {
- // adds the instruction to the bytecode of the method
- code.putByte(opcode);
- // update currentBlock
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, null, null);
- } else {
- // updates current and max stack sizes
- int size = stackSize + Frame.SIZE[opcode];
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- // if opcode == ATHROW or xRETURN, ends current block (no successor)
- if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
- || opcode == Opcodes.ATHROW) {
- noSuccessor();
- }
- }
+ previousFrameOffset = code.length;
+ ++stackMapTableNumberOfEntries;
}
- @Override
- public void visitIntInsn(final int opcode, final int operand) {
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, operand, null, null);
- } else if (opcode != Opcodes.NEWARRAY) {
- // updates current and max stack sizes only for NEWARRAY
- // (stack size variation = 0 for BIPUSH or SIPUSH)
- int size = stackSize + 1;
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- if (opcode == Opcodes.SIPUSH) {
- code.put12(opcode, operand);
- } else { // BIPUSH or NEWARRAY
- code.put11(opcode, operand);
- }
+ if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
+ relativeStackSize = nStack;
+ if (nStack > maxRelativeStackSize) {
+ maxRelativeStackSize = relativeStackSize;
+ }
}
- @Override
- public void visitVarInsn(final int opcode, final int var) {
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, var, null, null);
- } else {
- // updates current and max stack sizes
- if (opcode == Opcodes.RET) {
- // no stack change, but end of current block (no successor)
- currentBlock.status |= Label.RET;
- // save 'stackSize' here for future use
- // (see {@link #findSubroutineSuccessors})
- currentBlock.inputStackTop = stackSize;
- noSuccessor();
- } else { // xLOAD or xSTORE
- int size = stackSize + Frame.SIZE[opcode];
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
+ maxStack = Math.max(maxStack, nStack);
+ maxLocals = Math.max(maxLocals, currentLocals);
+ }
+
+ @Override
+ public void visitInsn(final int opcode) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ code.putByte(opcode);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, 0, null, null);
+ } else {
+ int size = relativeStackSize + STACK_SIZE_DELTA[opcode];
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
+ endCurrentBasicBlockWithNoSuccessor();
+ }
+ }
+ }
+
+ @Override
+ public void visitIntInsn(final int opcode, final int operand) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ if (opcode == Opcodes.SIPUSH) {
+ code.put12(opcode, operand);
+ } else { // BIPUSH or NEWARRAY
+ code.put11(opcode, operand);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, operand, null, null);
+ } else if (opcode != Opcodes.NEWARRAY) {
+ // The stack size delta is 1 for BIPUSH or SIPUSH, and 0 for NEWARRAY.
+ int size = relativeStackSize + 1;
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitVarInsn(final int opcode, final int var) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ if (var < 4 && opcode != Opcodes.RET) {
+ int optimizedOpcode;
+ if (opcode < Opcodes.ISTORE) {
+ optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var;
+ } else {
+ optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var;
+ }
+ code.putByte(optimizedOpcode);
+ } else if (var >= 256) {
+ code.putByte(Constants.WIDE).put12(opcode, var);
+ } else {
+ code.put11(opcode, var);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, var, null, null);
+ } else {
+ if (opcode == Opcodes.RET) {
+ // No stack size delta.
+ currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_END;
+ currentBasicBlock.outputStackSize = (short) relativeStackSize;
+ endCurrentBasicBlockWithNoSuccessor();
+ } else { // xLOAD or xSTORE
+ int size = relativeStackSize + STACK_SIZE_DELTA[opcode];
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+ if (compute != COMPUTE_NOTHING) {
+ int currentMaxLocals;
+ if (opcode == Opcodes.LLOAD
+ || opcode == Opcodes.DLOAD
+ || opcode == Opcodes.LSTORE
+ || opcode == Opcodes.DSTORE) {
+ currentMaxLocals = var + 2;
+ } else {
+ currentMaxLocals = var + 1;
+ }
+ if (currentMaxLocals > maxLocals) {
+ maxLocals = currentMaxLocals;
+ }
+ }
+ if (opcode >= Opcodes.ISTORE && compute == COMPUTE_ALL_FRAMES && firstHandler != null) {
+ // If there are exception handler blocks, each instruction within a handler range is, in
+ // theory, a basic block (since execution can jump from this instruction to the exception
+ // handler). As a consequence, the local variable types at the beginning of the handler
+ // block should be the merge of the local variable types at all the instructions within the
+ // handler range. However, instead of creating a basic block for each instruction, we can
+ // get the same result in a more efficient way. Namely, by starting a new basic block after
+ // each xSTORE instruction, which is what we do here.
+ visitLabel(new Label());
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(final int opcode, final String type) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol typeSymbol = symbolTable.addConstantClass(type);
+ code.put12(opcode, typeSymbol.index);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, lastBytecodeOffset, typeSymbol, symbolTable);
+ } else if (opcode == Opcodes.NEW) {
+ // The stack size delta is 1 for NEW, and 0 for ANEWARRAY, CHECKCAST, or INSTANCEOF.
+ int size = relativeStackSize + 1;
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol fieldrefSymbol = symbolTable.addConstantFieldref(owner, name, descriptor);
+ code.put12(opcode, fieldrefSymbol.index);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, 0, fieldrefSymbol, symbolTable);
+ } else {
+ int size;
+ char firstDescChar = descriptor.charAt(0);
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 2 : 1);
+ break;
+ case Opcodes.PUTSTATIC:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -2 : -1);
+ break;
+ case Opcodes.GETFIELD:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 1 : 0);
+ break;
+ case Opcodes.PUTFIELD:
+ default:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -3 : -2);
+ break;
}
- if (compute != NOTHING) {
- // updates max locals
- int n;
- if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD
- || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) {
- n = var + 2;
- } else {
- n = var + 1;
- }
- if (n > maxLocals) {
- maxLocals = n;
- }
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
}
- // adds the instruction to the bytecode of the method
- if (var < 4 && opcode != Opcodes.RET) {
- int opt;
- if (opcode < Opcodes.ISTORE) {
- /* ILOAD_0 */
- opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var;
- } else {
- /* ISTORE_0 */
- opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var;
- }
- code.putByte(opt);
- } else if (var >= 256) {
- code.putByte(196 /* WIDE */).put12(opcode, var);
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(
+ final int opcode,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol methodrefSymbol = symbolTable.addConstantMethodref(owner, name, descriptor, isInterface);
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ code.put12(Opcodes.INVOKEINTERFACE, methodrefSymbol.index)
+ .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0);
+ } else {
+ code.put12(opcode, methodrefSymbol.index);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, 0, methodrefSymbol, symbolTable);
+ } else {
+ int argumentsAndReturnSize = methodrefSymbol.getArgumentsAndReturnSizes();
+ int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2);
+ int size;
+ if (opcode == Opcodes.INVOKESTATIC) {
+ size = relativeStackSize + stackSizeDelta + 1;
} else {
- code.put11(opcode, var);
+ size = relativeStackSize + stackSizeDelta;
}
- if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) {
- visitLabel(new Label());
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
}
+ relativeStackSize = size;
+ }
}
-
- @Override
- public void visitTypeInsn(final int opcode, final String type) {
- Item i = cw.newClassItem(type);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, code.length, cw, i);
- } else if (opcode == Opcodes.NEW) {
- // updates current and max stack sizes only if opcode == NEW
- // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
- int size = stackSize + 1;
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(opcode, i.index);
- }
-
- @Override
- public void visitFieldInsn(final int opcode, final String owner,
- final String name, final String desc) {
- Item i = cw.newFieldItem(owner, name, desc);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, cw, i);
- } else {
- int size;
- // computes the stack size variation
- char c = desc.charAt(0);
- switch (opcode) {
- case Opcodes.GETSTATIC:
- size = stackSize + (c == 'D' || c == 'J' ? 2 : 1);
- break;
- case Opcodes.PUTSTATIC:
- size = stackSize + (c == 'D' || c == 'J' ? -2 : -1);
- break;
- case Opcodes.GETFIELD:
- size = stackSize + (c == 'D' || c == 'J' ? 1 : 0);
- break;
- // case Constants.PUTFIELD:
- default:
- size = stackSize + (c == 'D' || c == 'J' ? -3 : -2);
- break;
- }
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(opcode, i.index);
- }
-
- @Override
- public void visitMethodInsn(final int opcode, final String owner,
- final String name, final String desc) {
- boolean itf = opcode == Opcodes.INVOKEINTERFACE;
- Item i = cw.newMethodItem(owner, name, desc, itf);
- int argSize = i.intVal;
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, cw, i);
- } else {
- /*
- * computes the stack size variation. In order not to recompute
- * several times this variation for the same Item, we use the
- * intVal field of this item to store this variation, once it
- * has been computed. More precisely this intVal field stores
- * the sizes of the arguments and of the return value
- * corresponding to desc.
- */
- if (argSize == 0) {
- // the above sizes have not been computed yet,
- // so we compute them...
- argSize = Type.getArgumentsAndReturnSizes(desc);
- // ... and we save them in order
- // not to recompute them in the future
- i.intVal = argSize;
- }
- int size;
- if (opcode == Opcodes.INVOKESTATIC) {
- size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
- } else {
- size = stackSize - (argSize >> 2) + (argSize & 0x03);
- }
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- if (itf) {
- if (argSize == 0) {
- argSize = Type.getArgumentsAndReturnSizes(desc);
- i.intVal = argSize;
- }
- code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0);
- } else {
- code.put12(opcode, i.index);
- }
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol invokeDynamicSymbol =
+ symbolTable.addConstantInvokeDynamic(
+ name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+ code.put12(Opcodes.INVOKEDYNAMIC, invokeDynamicSymbol.index);
+ code.putShort(0);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, invokeDynamicSymbol, symbolTable);
+ } else {
+ int argumentsAndReturnSize = invokeDynamicSymbol.getArgumentsAndReturnSizes();
+ int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2) + 1;
+ int size = relativeStackSize + stackSizeDelta;
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(final int opcode, final Label label) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ // Compute the 'base' opcode, i.e. GOTO or JSR if opcode is GOTO_W or JSR_W, otherwise opcode.
+ int baseOpcode =
+ opcode >= Constants.GOTO_W ? opcode - Constants.WIDE_JUMP_OPCODE_DELTA : opcode;
+ boolean nextInsnIsJumpTarget = false;
+ if ((label.flags & Label.FLAG_RESOLVED) != 0
+ && label.bytecodeOffset - code.length < Short.MIN_VALUE) {
+ // Case of a backward jump with an offset < -32768. In this case we automatically replace GOTO
+ // with GOTO_W, JSR with JSR_W and IFxxx with IFNOTxxx GOTO_W L:..., where
+ // IFNOTxxx is the "opposite" opcode of IFxxx (e.g. IFNE for IFEQ) and where designates
+ // the instruction just after the GOTO_W.
+ if (baseOpcode == Opcodes.GOTO) {
+ code.putByte(Constants.GOTO_W);
+ } else if (baseOpcode == Opcodes.JSR) {
+ code.putByte(Constants.JSR_W);
+ } else {
+ // Put the "opposite" opcode of baseOpcode. This can be done by flipping the least
+ // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ (with a
+ // pre and post offset by 1). The jump offset is 8 bytes (3 for IFNOTxxx, 5 for GOTO_W).
+ code.putByte(baseOpcode >= Opcodes.IFNULL ? baseOpcode ^ 1 : ((baseOpcode + 1) ^ 1) - 1);
+ code.putShort(8);
+ // Here we could put a GOTO_W in theory, but if ASM specific instructions are used in this
+ // method or another one, and if the class has frames, we will need to insert a frame after
+ // this GOTO_W during the additional ClassReader -> ClassWriter round trip to remove the ASM
+ // specific instructions. To not miss this additional frame, we need to use an ASM_GOTO_W
+ // here, which has the unfortunate effect of forcing this additional round trip (which in
+ // some case would not have been really necessary, but we can't know this at this point).
+ code.putByte(Constants.ASM_GOTO_W);
+ hasAsmInstructions = true;
+ // The instruction after the GOTO_W becomes the target of the IFNOT instruction.
+ nextInsnIsJumpTarget = true;
+ }
+ label.put(code, code.length - 1, true);
+ } else if (baseOpcode != opcode) {
+ // Case of a GOTO_W or JSR_W specified by the user (normally ClassReader when used to remove
+ // ASM specific instructions). In this case we keep the original instruction.
+ code.putByte(opcode);
+ label.put(code, code.length - 1, true);
+ } else {
+ // Case of a jump with an offset >= -32768, or of a jump with an unknown offset. In these
+ // cases we store the offset in 2 bytes (which will be increased via a ClassReader ->
+ // ClassWriter round trip if it turns out that 2 bytes are not sufficient).
+ code.putByte(baseOpcode);
+ label.put(code, code.length - 1, false);
}
- @Override
- public void visitInvokeDynamicInsn(final String name, final String desc,
- final Handle bsm, final Object... bsmArgs) {
- Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs);
- int argSize = i.intVal;
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i);
- } else {
- /*
- * computes the stack size variation. In order not to recompute
- * several times this variation for the same Item, we use the
- * intVal field of this item to store this variation, once it
- * has been computed. More precisely this intVal field stores
- * the sizes of the arguments and of the return value
- * corresponding to desc.
- */
- if (argSize == 0) {
- // the above sizes have not been computed yet,
- // so we compute them...
- argSize = Type.getArgumentsAndReturnSizes(desc);
- // ... and we save them in order
- // not to recompute them in the future
- i.intVal = argSize;
- }
- int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
-
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(Opcodes.INVOKEDYNAMIC, i.index);
- code.putShort(0);
- }
-
- @Override
- public void visitJumpInsn(final int opcode, final Label label) {
- Label nextInsn = null;
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, null, null);
- // 'label' is the target of a jump instruction
- label.getFirst().status |= Label.TARGET;
- // adds 'label' as a successor of this basic block
- addSuccessor(Edge.NORMAL, label);
- if (opcode != Opcodes.GOTO) {
- // creates a Label for the next basic block
- nextInsn = new Label();
- }
- } else {
- if (opcode == Opcodes.JSR) {
- if ((label.status & Label.SUBROUTINE) == 0) {
- label.status |= Label.SUBROUTINE;
- ++subroutines;
- }
- currentBlock.status |= Label.JSR;
- addSuccessor(stackSize + 1, label);
- // creates a Label for the next basic block
- nextInsn = new Label();
- /*
- * note that, by construction in this method, a JSR block
- * has at least two successors in the control flow graph:
- * the first one leads the next instruction after the JSR,
- * while the second one leads to the JSR target.
- */
- } else {
- // updates current stack size (max stack size unchanged
- // because stack size variation always negative in this
- // case)
- stackSize += Frame.SIZE[opcode];
- addSuccessor(stackSize, label);
- }
- }
- }
- // adds the instruction to the bytecode of the method
- if ((label.status & Label.RESOLVED) != 0
- && label.position - code.length < Short.MIN_VALUE) {
- /*
- * case of a backward jump with an offset < -32768. In this case we
- * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx
- * with IFNOTxxx GOTO_W , where IFNOTxxx is the
- * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where
- * designates the instruction just after the GOTO_W.
- */
- if (opcode == Opcodes.GOTO) {
- code.putByte(200); // GOTO_W
- } else if (opcode == Opcodes.JSR) {
- code.putByte(201); // JSR_W
- } else {
- // if the IF instruction is transformed into IFNOT GOTO_W the
- // next instruction becomes the target of the IFNOT instruction
- if (nextInsn != null) {
- nextInsn.status |= Label.TARGET;
- }
- code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
- : opcode ^ 1);
- code.putShort(8); // jump offset
- code.putByte(200); // GOTO_W
- }
- label.put(this, code, code.length - 1, true);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ Label nextBasicBlock = null;
+ if (compute == COMPUTE_ALL_FRAMES) {
+ currentBasicBlock.frame.execute(baseOpcode, 0, null, null);
+ // Record the fact that 'label' is the target of a jump instruction.
+ label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
+ // Add 'label' as a successor of the current basic block.
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
+ if (baseOpcode != Opcodes.GOTO) {
+ // The next instruction starts a new basic block (except for GOTO: by default the code
+ // following a goto is unreachable - unless there is an explicit label for it - and we
+ // should not compute stack frame types for its instructions).
+ nextBasicBlock = new Label();
+ }
+ } else if (compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(baseOpcode, 0, null, null);
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ relativeStackSize += STACK_SIZE_DELTA[baseOpcode];
+ } else {
+ if (baseOpcode == Opcodes.JSR) {
+ // Record the fact that 'label' designates a subroutine, if not already done.
+ if ((label.flags & Label.FLAG_SUBROUTINE_START) == 0) {
+ label.flags |= Label.FLAG_SUBROUTINE_START;
+ hasSubroutines = true;
+ }
+ currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_CALLER;
+ // Note that, by construction in this method, a block which calls a subroutine has at
+ // least two successors in the control flow graph: the first one (added below) leads to
+ // the instruction after the JSR, while the second one (added here) leads to the JSR
+ // target. Note that the first successor is virtual (it does not correspond to a possible
+ // execution path): it is only used to compute the successors of the basic blocks ending
+ // with a ret, in {@link Label#addSubroutineRetSuccessors}.
+ addSuccessorToCurrentBasicBlock(relativeStackSize + 1, label);
+ // The instruction after the JSR starts a new basic block.
+ nextBasicBlock = new Label();
} else {
- /*
- * case of a backward jump with an offset >= -32768, or of a forward
- * jump with, of course, an unknown offset. In these cases we store
- * the offset in 2 bytes (which will be increased in
- * resizeInstructions, if needed).
- */
- code.putByte(opcode);
- label.put(this, code, code.length - 1, false);
- }
- if (currentBlock != null) {
- if (nextInsn != null) {
- // if the jump instruction is not a GOTO, the next instruction
- // is also a successor of this instruction. Calling visitLabel
- // adds the label of this next instruction as a successor of the
- // current block, and starts a new basic block
- visitLabel(nextInsn);
- }
- if (opcode == Opcodes.GOTO) {
- noSuccessor();
- }
- }
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ relativeStackSize += STACK_SIZE_DELTA[baseOpcode];
+ addSuccessorToCurrentBasicBlock(relativeStackSize, label);
+ }
+ }
+ // If the next instruction starts a new basic block, call visitLabel to add the label of this
+ // instruction as a successor of the current block, and to start a new basic block.
+ if (nextBasicBlock != null) {
+ if (nextInsnIsJumpTarget) {
+ nextBasicBlock.flags |= Label.FLAG_JUMP_TARGET;
+ }
+ visitLabel(nextBasicBlock);
+ }
+ if (baseOpcode == Opcodes.GOTO) {
+ endCurrentBasicBlockWithNoSuccessor();
+ }
}
-
- @Override
- public void visitLabel(final Label label) {
- // resolves previous forward references to label, if any
- resize |= label.resolve(this, code.length, code.data);
- // updates currentBlock
- if ((label.status & Label.DEBUG) != 0) {
- return;
- }
- if (compute == FRAMES) {
- if (currentBlock != null) {
- if (label.position == currentBlock.position) {
- // successive labels, do not start a new basic block
- currentBlock.status |= (label.status & Label.TARGET);
- label.frame = currentBlock.frame;
- return;
- }
- // ends current block (with one new successor)
- addSuccessor(Edge.NORMAL, label);
- }
- // begins a new current block
- currentBlock = label;
- if (label.frame == null) {
- label.frame = new Frame();
- label.frame.owner = label;
- }
- // updates the basic block list
- if (previousBlock != null) {
- if (label.position == previousBlock.position) {
- previousBlock.status |= (label.status & Label.TARGET);
- label.frame = previousBlock.frame;
- currentBlock = previousBlock;
- return;
- }
- previousBlock.successor = label;
- }
- previousBlock = label;
- } else if (compute == MAXS) {
- if (currentBlock != null) {
- // ends current block (with one new successor)
- currentBlock.outputStackMax = maxStackSize;
- addSuccessor(stackSize, label);
- }
- // begins a new current block
- currentBlock = label;
- // resets the relative current and max stack sizes
- stackSize = 0;
- maxStackSize = 0;
- // updates the basic block list
- if (previousBlock != null) {
- previousBlock.successor = label;
- }
- previousBlock = label;
- }
+ }
+
+ @Override
+ public void visitLabel(final Label label) {
+ // Resolve the forward references to this label, if any.
+ hasAsmInstructions |= label.resolve(code.data, code.length);
+ // visitLabel starts a new basic block (except for debug only labels), so we need to update the
+ // previous and current block references and list of successors.
+ if ((label.flags & Label.FLAG_DEBUG_ONLY) != 0) {
+ return;
+ }
+ if (compute == COMPUTE_ALL_FRAMES) {
+ if (currentBasicBlock != null) {
+ if (label.bytecodeOffset == currentBasicBlock.bytecodeOffset) {
+ // We use {@link Label#getCanonicalInstance} to store the state of a basic block in only
+ // one place, but this does not work for labels which have not been visited yet.
+ // Therefore, when we detect here two labels having the same bytecode offset, we need to
+ // - consolidate the state scattered in these two instances into the canonical instance:
+ currentBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET);
+ // - make sure the two instances share the same Frame instance (the implementation of
+ // {@link Label#getCanonicalInstance} relies on this property; here label.frame should be
+ // null):
+ label.frame = currentBasicBlock.frame;
+ // - and make sure to NOT assign 'label' into 'currentBasicBlock' or 'lastBasicBlock', so
+ // that they still refer to the canonical instance for this bytecode offset.
+ return;
+ }
+ // End the current basic block (with one new successor).
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
+ }
+ // Append 'label' at the end of the basic block list.
+ if (lastBasicBlock != null) {
+ if (label.bytecodeOffset == lastBasicBlock.bytecodeOffset) {
+ // Same comment as above.
+ lastBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET);
+ // Here label.frame should be null.
+ label.frame = lastBasicBlock.frame;
+ currentBasicBlock = lastBasicBlock;
+ return;
+ }
+ lastBasicBlock.nextBasicBlock = label;
+ }
+ lastBasicBlock = label;
+ // Make it the new current basic block.
+ currentBasicBlock = label;
+ // Here label.frame should be null.
+ label.frame = new Frame(label);
+ } else if (compute == COMPUTE_INSERTED_FRAMES) {
+ if (currentBasicBlock == null) {
+ // This case should happen only once, for the visitLabel call in the constructor. Indeed, if
+ // compute is equal to COMPUTE_INSERTED_FRAMES, currentBasicBlock stays unchanged.
+ currentBasicBlock = label;
+ } else {
+ // Update the frame owner so that a correct frame offset is computed in Frame.accept().
+ currentBasicBlock.frame.owner = label;
+ }
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ if (currentBasicBlock != null) {
+ // End the current basic block (with one new successor).
+ currentBasicBlock.outputStackMax = (short) maxRelativeStackSize;
+ addSuccessorToCurrentBasicBlock(relativeStackSize, label);
+ }
+ // Start a new current basic block, and reset the current and maximum relative stack sizes.
+ currentBasicBlock = label;
+ relativeStackSize = 0;
+ maxRelativeStackSize = 0;
+ // Append the new basic block at the end of the basic block list.
+ if (lastBasicBlock != null) {
+ lastBasicBlock.nextBasicBlock = label;
+ }
+ lastBasicBlock = label;
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES && currentBasicBlock == null) {
+ // This case should happen only once, for the visitLabel call in the constructor. Indeed, if
+ // compute is equal to COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES, currentBasicBlock stays
+ // unchanged.
+ currentBasicBlock = label;
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(final Object value) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol constantSymbol = symbolTable.addConstant(value);
+ int constantIndex = constantSymbol.index;
+ boolean isLongOrDouble =
+ constantSymbol.tag == Symbol.CONSTANT_LONG_TAG
+ || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG;
+ if (isLongOrDouble) {
+ code.put12(Constants.LDC2_W, constantIndex);
+ } else if (constantIndex >= 256) {
+ code.put12(Constants.LDC_W, constantIndex);
+ } else {
+ code.put11(Opcodes.LDC, constantIndex);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(Opcodes.LDC, 0, constantSymbol, symbolTable);
+ } else {
+ int size = relativeStackSize + (isLongOrDouble ? 2 : 1);
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitIincInsn(final int var, final int increment) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ if ((var > 255) || (increment > 127) || (increment < -128)) {
+ code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment);
+ } else {
+ code.putByte(Opcodes.IINC).put11(var, increment);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null
+ && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) {
+ currentBasicBlock.frame.execute(Opcodes.IINC, var, null, null);
+ }
+ if (compute != COMPUTE_NOTHING) {
+ int currentMaxLocals = var + 1;
+ if (currentMaxLocals > maxLocals) {
+ maxLocals = currentMaxLocals;
+ }
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(
+ final int min, final int max, final Label dflt, final Label... labels) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ code.putByte(Opcodes.TABLESWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(code, lastBytecodeOffset, true);
+ code.putInt(min).putInt(max);
+ for (Label label : labels) {
+ label.put(code, lastBytecodeOffset, true);
}
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ visitSwitchInsn(dflt, labels);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ code.putByte(Opcodes.LOOKUPSWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(code, lastBytecodeOffset, true);
+ code.putInt(labels.length);
+ for (int i = 0; i < labels.length; ++i) {
+ code.putInt(keys[i]);
+ labels[i].put(code, lastBytecodeOffset, true);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ visitSwitchInsn(dflt, labels);
+ }
+
+ private void visitSwitchInsn(final Label dflt, final Label[] labels) {
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ currentBasicBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
+ // Add all the labels as successors of the current basic block.
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, dflt);
+ dflt.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
+ for (Label label : labels) {
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
+ label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
+ }
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ --relativeStackSize;
+ // Add all the labels as successors of the current basic block.
+ addSuccessorToCurrentBasicBlock(relativeStackSize, dflt);
+ for (Label label : labels) {
+ addSuccessorToCurrentBasicBlock(relativeStackSize, label);
+ }
+ }
+ // End the current basic block.
+ endCurrentBasicBlockWithNoSuccessor();
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol descSymbol = symbolTable.addConstantClass(descriptor);
+ code.put12(Opcodes.MULTIANEWARRAY, descSymbol.index).putByte(numDimensions);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(
+ Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable);
+ } else {
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ relativeStackSize += 1 - numDimensions;
+ }
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitInsnAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ TypeReference.putTarget((typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), typeAnnotation);
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastCodeRuntimeVisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastCodeRuntimeInvisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitTryCatchBlock(
+ final Label start, final Label end, final Label handler, final String type) {
+ Handler newHandler =
+ new Handler(
+ start, end, handler, type != null ? symbolTable.addConstantClass(type).index : 0, type);
+ if (firstHandler == null) {
+ firstHandler = newHandler;
+ } else {
+ lastHandler.nextHandler = newHandler;
+ }
+ lastHandler = newHandler;
+ }
+
+ @Override
+ public AnnotationVisitor visitTryCatchAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ TypeReference.putTarget(typeRef, typeAnnotation);
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastCodeRuntimeVisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastCodeRuntimeInvisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Label start,
+ final Label end,
+ final int index) {
+ if (signature != null) {
+ if (localVariableTypeTable == null) {
+ localVariableTypeTable = new ByteVector();
+ }
+ ++localVariableTypeTableLength;
+ localVariableTypeTable
+ .putShort(start.bytecodeOffset)
+ .putShort(end.bytecodeOffset - start.bytecodeOffset)
+ .putShort(symbolTable.addConstantUtf8(name))
+ .putShort(symbolTable.addConstantUtf8(signature))
+ .putShort(index);
+ }
+ if (localVariableTable == null) {
+ localVariableTable = new ByteVector();
+ }
+ ++localVariableTableLength;
+ localVariableTable
+ .putShort(start.bytecodeOffset)
+ .putShort(end.bytecodeOffset - start.bytecodeOffset)
+ .putShort(symbolTable.addConstantUtf8(name))
+ .putShort(symbolTable.addConstantUtf8(descriptor))
+ .putShort(index);
+ if (compute != COMPUTE_NOTHING) {
+ char firstDescChar = descriptor.charAt(0);
+ int currentMaxLocals = index + (firstDescChar == 'J' || firstDescChar == 'D' ? 2 : 1);
+ if (currentMaxLocals > maxLocals) {
+ maxLocals = currentMaxLocals;
+ }
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitLocalVariableAnnotation(
+ final int typeRef,
+ final TypePath typePath,
+ final Label[] start,
+ final Label[] end,
+ final int[] index,
+ final String descriptor,
+ final boolean visible) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ typeAnnotation.putByte(typeRef >>> 24).putShort(start.length);
+ for (int i = 0; i < start.length; ++i) {
+ typeAnnotation
+ .putShort(start[i].bytecodeOffset)
+ .putShort(end[i].bytecodeOffset - start[i].bytecodeOffset)
+ .putShort(index[i]);
+ }
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastCodeRuntimeVisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastCodeRuntimeInvisibleTypeAnnotation =
+ new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation);
+ }
+ }
- @Override
- public void visitLdcInsn(final Object cst) {
- Item i = cw.newConstItem(cst);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.LDC, 0, cw, i);
- } else {
- int size;
- // computes the stack size variation
- if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
- size = stackSize + 2;
- } else {
- size = stackSize + 1;
- }
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- int index = i.index;
- if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
- code.put12(20 /* LDC2_W */, index);
- } else if (index >= 256) {
- code.put12(19 /* LDC_W */, index);
- } else {
- code.put11(Opcodes.LDC, index);
- }
+ @Override
+ public void visitLineNumber(final int line, final Label start) {
+ if (lineNumberTable == null) {
+ lineNumberTable = new ByteVector();
+ }
+ ++lineNumberTableLength;
+ lineNumberTable.putShort(start.bytecodeOffset);
+ lineNumberTable.putShort(line);
+ }
+
+ @Override
+ public void visitMaxs(final int maxStack, final int maxLocals) {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ computeAllFrames();
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ computeMaxStackAndLocal();
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
+ this.maxStack = maxRelativeStackSize;
+ } else {
+ this.maxStack = maxStack;
+ this.maxLocals = maxLocals;
+ }
+ }
+
+ /** Computes all the stack map frames of the method, from scratch. */
+ private void computeAllFrames() {
+ // Complete the control flow graph with exception handler blocks.
+ Handler handler = firstHandler;
+ while (handler != null) {
+ String catchTypeDescriptor =
+ handler.catchTypeDescriptor == null ? "java/lang/Throwable" : handler.catchTypeDescriptor;
+ int catchType = Frame.getAbstractTypeFromInternalName(symbolTable, catchTypeDescriptor);
+ // Mark handlerBlock as an exception handler.
+ Label handlerBlock = handler.handlerPc.getCanonicalInstance();
+ handlerBlock.flags |= Label.FLAG_JUMP_TARGET;
+ // Add handlerBlock as a successor of all the basic blocks in the exception handler range.
+ Label handlerRangeBlock = handler.startPc.getCanonicalInstance();
+ Label handlerRangeEnd = handler.endPc.getCanonicalInstance();
+ while (handlerRangeBlock != handlerRangeEnd) {
+ handlerRangeBlock.outgoingEdges =
+ new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges);
+ handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
+ }
+ handler = handler.nextHandler;
}
- @Override
- public void visitIincInsn(final int var, final int increment) {
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.IINC, var, null, null);
- }
- }
- if (compute != NOTHING) {
- // updates max locals
- int n = var + 1;
- if (n > maxLocals) {
- maxLocals = n;
- }
- }
- // adds the instruction to the bytecode of the method
- if ((var > 255) || (increment > 127) || (increment < -128)) {
- code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var)
- .putShort(increment);
- } else {
- code.putByte(Opcodes.IINC).put11(var, increment);
- }
+ // Create and visit the first (implicit) frame.
+ Frame firstFrame = firstBasicBlock.frame;
+ firstFrame.setInputFrameFromDescriptor(symbolTable, accessFlags, descriptor, this.maxLocals);
+ firstFrame.accept(this);
+
+ // Fix point algorithm: add the first basic block to a list of blocks to process (i.e. blocks
+ // whose stack map frame has changed) and, while there are blocks to process, remove one from
+ // the list and update the stack map frames of its successor blocks in the control flow graph
+ // (which might change them, in which case these blocks must be processed too, and are thus
+ // added to the list of blocks to process). Also compute the maximum stack size of the method,
+ // as a by-product.
+ Label listOfBlocksToProcess = firstBasicBlock;
+ listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST;
+ int maxStackSize = 0;
+ while (listOfBlocksToProcess != Label.EMPTY_LIST) {
+ // Remove a basic block from the list of blocks to process.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
+ basicBlock.nextListElement = null;
+ // By definition, basicBlock is reachable.
+ basicBlock.flags |= Label.FLAG_REACHABLE;
+ // Update the (absolute) maximum stack size.
+ int maxBlockStackSize = basicBlock.frame.getInputStackSize() + basicBlock.outputStackMax;
+ if (maxBlockStackSize > maxStackSize) {
+ maxStackSize = maxBlockStackSize;
+ }
+ // Update the successor blocks of basicBlock in the control flow graph.
+ Edge outgoingEdge = basicBlock.outgoingEdges;
+ while (outgoingEdge != null) {
+ Label successorBlock = outgoingEdge.successor.getCanonicalInstance();
+ boolean successorBlockChanged =
+ basicBlock.frame.merge(symbolTable, successorBlock.frame, outgoingEdge.info);
+ if (successorBlockChanged && successorBlock.nextListElement == null) {
+ // If successorBlock has changed it must be processed. Thus, if it is not already in the
+ // list of blocks to process, add it to this list.
+ successorBlock.nextListElement = listOfBlocksToProcess;
+ listOfBlocksToProcess = successorBlock;
+ }
+ outgoingEdge = outgoingEdge.nextEdge;
+ }
}
- @Override
- public void visitTableSwitchInsn(final int min, final int max,
- final Label dflt, final Label... labels) {
- // adds the instruction to the bytecode of the method
- int source = code.length;
- code.putByte(Opcodes.TABLESWITCH);
- code.putByteArray(null, 0, (4 - code.length % 4) % 4);
- dflt.put(this, code, source, true);
- code.putInt(min).putInt(max);
- for (int i = 0; i < labels.length; ++i) {
- labels[i].put(this, code, source, true);
- }
- // updates currentBlock
- visitSwitchInsn(dflt, labels);
- }
-
- @Override
- public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
- final Label[] labels) {
- // adds the instruction to the bytecode of the method
- int source = code.length;
- code.putByte(Opcodes.LOOKUPSWITCH);
- code.putByteArray(null, 0, (4 - code.length % 4) % 4);
- dflt.put(this, code, source, true);
- code.putInt(labels.length);
- for (int i = 0; i < labels.length; ++i) {
- code.putInt(keys[i]);
- labels[i].put(this, code, source, true);
- }
- // updates currentBlock
- visitSwitchInsn(dflt, labels);
- }
-
- private void visitSwitchInsn(final Label dflt, final Label[] labels) {
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
- // adds current block successors
- addSuccessor(Edge.NORMAL, dflt);
- dflt.getFirst().status |= Label.TARGET;
- for (int i = 0; i < labels.length; ++i) {
- addSuccessor(Edge.NORMAL, labels[i]);
- labels[i].getFirst().status |= Label.TARGET;
- }
- } else {
- // updates current stack size (max stack size unchanged)
- --stackSize;
- // adds current block successors
- addSuccessor(stackSize, dflt);
- for (int i = 0; i < labels.length; ++i) {
- addSuccessor(stackSize, labels[i]);
- }
- }
- // ends current block
- noSuccessor();
- }
+ // Loop over all the basic blocks and visit the stack map frames that must be stored in the
+ // StackMapTable attribute. Also replace unreachable code with NOP* ATHROW, and remove it from
+ // exception handler ranges.
+ Label basicBlock = firstBasicBlock;
+ while (basicBlock != null) {
+ if ((basicBlock.flags & (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE))
+ == (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) {
+ basicBlock.frame.accept(this);
+ }
+ if ((basicBlock.flags & Label.FLAG_REACHABLE) == 0) {
+ // Find the start and end bytecode offsets of this unreachable block.
+ Label nextBasicBlock = basicBlock.nextBasicBlock;
+ int startOffset = basicBlock.bytecodeOffset;
+ int endOffset = (nextBasicBlock == null ? code.length : nextBasicBlock.bytecodeOffset) - 1;
+ if (endOffset >= startOffset) {
+ // Replace its instructions with NOP ... NOP ATHROW.
+ for (int i = startOffset; i < endOffset; ++i) {
+ code.data[i] = Opcodes.NOP;
+ }
+ code.data[endOffset] = (byte) Opcodes.ATHROW;
+ // Emit a frame for this unreachable block, with no local and a Throwable on the stack
+ // (so that the ATHROW could consume this Throwable if it were reachable).
+ int frameIndex = visitFrameStart(startOffset, /* nLocal = */ 0, /* nStack = */ 1);
+ currentFrame[frameIndex] =
+ Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable");
+ visitFrameEnd();
+ // Remove this unreachable basic block from the exception handler ranges.
+ firstHandler = Handler.removeRange(firstHandler, basicBlock, nextBasicBlock);
+ // The maximum stack size is now at least one, because of the Throwable declared above.
+ maxStackSize = Math.max(maxStackSize, 1);
+ }
+ }
+ basicBlock = basicBlock.nextBasicBlock;
}
- @Override
- public void visitMultiANewArrayInsn(final String desc, final int dims) {
- Item i = cw.newClassItem(desc);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i);
- } else {
- // updates current stack size (max stack size unchanged because
- // stack size variation always negative or null)
- stackSize += 1 - dims;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims);
- }
-
- @Override
- public void visitTryCatchBlock(final Label start, final Label end,
- final Label handler, final String type) {
- ++handlerCount;
- Handler h = new Handler();
- h.start = start;
- h.end = end;
- h.handler = handler;
- h.desc = type;
- h.type = type != null ? cw.newClass(type) : 0;
- if (lastHandler == null) {
- firstHandler = h;
+ this.maxStack = maxStackSize;
+ }
+
+ /** Computes the maximum stack size of the method. */
+ private void computeMaxStackAndLocal() {
+ // Complete the control flow graph with exception handler blocks.
+ Handler handler = firstHandler;
+ while (handler != null) {
+ Label handlerBlock = handler.handlerPc;
+ Label handlerRangeBlock = handler.startPc;
+ Label handlerRangeEnd = handler.endPc;
+ // Add handlerBlock as a successor of all the basic blocks in the exception handler range.
+ while (handlerRangeBlock != handlerRangeEnd) {
+ if ((handlerRangeBlock.flags & Label.FLAG_SUBROUTINE_CALLER) == 0) {
+ handlerRangeBlock.outgoingEdges =
+ new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges);
} else {
- lastHandler.next = h;
- }
- lastHandler = h;
+ // If handlerRangeBlock is a JSR block, add handlerBlock after the first two outgoing
+ // edges to preserve the hypothesis about JSR block successors order (see
+ // {@link #visitJumpInsn}).
+ handlerRangeBlock.outgoingEdges.nextEdge.nextEdge =
+ new Edge(
+ Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge);
+ }
+ handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
+ }
+ handler = handler.nextHandler;
}
- @Override
- public void visitLocalVariable(final String name, final String desc,
- final String signature, final Label start, final Label end,
- final int index) {
- if (signature != null) {
- if (localVarType == null) {
- localVarType = new ByteVector();
- }
- ++localVarTypeCount;
- localVarType.putShort(start.position)
- .putShort(end.position - start.position)
- .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature))
- .putShort(index);
- }
- if (localVar == null) {
- localVar = new ByteVector();
- }
- ++localVarCount;
- localVar.putShort(start.position)
- .putShort(end.position - start.position)
- .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc))
- .putShort(index);
- if (compute != NOTHING) {
- // updates max locals
- char c = desc.charAt(0);
- int n = index + (c == 'J' || c == 'D' ? 2 : 1);
- if (n > maxLocals) {
- maxLocals = n;
- }
- }
+ // Complete the control flow graph with the successor blocks of subroutines, if needed.
+ if (hasSubroutines) {
+ // First step: find the subroutines. This step determines, for each basic block, to which
+ // subroutine(s) it belongs. Start with the main "subroutine":
+ short numSubroutines = 1;
+ firstBasicBlock.markSubroutine(numSubroutines);
+ // Then, mark the subroutines called by the main subroutine, then the subroutines called by
+ // those called by the main subroutine, etc.
+ for (short currentSubroutine = 1; currentSubroutine <= numSubroutines; ++currentSubroutine) {
+ Label basicBlock = firstBasicBlock;
+ while (basicBlock != null) {
+ if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0
+ && basicBlock.subroutineId == currentSubroutine) {
+ Label jsrTarget = basicBlock.outgoingEdges.nextEdge.successor;
+ if (jsrTarget.subroutineId == 0) {
+ // If this subroutine has not been marked yet, find its basic blocks.
+ jsrTarget.markSubroutine(++numSubroutines);
+ }
+ }
+ basicBlock = basicBlock.nextBasicBlock;
+ }
+ }
+ // Second step: find the successors in the control flow graph of each subroutine basic block
+ // 'r' ending with a RET instruction. These successors are the virtual successors of the basic
+ // blocks ending with JSR instructions (see {@link #visitJumpInsn)} that can reach 'r'.
+ Label basicBlock = firstBasicBlock;
+ while (basicBlock != null) {
+ if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) {
+ // By construction, jsr targets are stored in the second outgoing edge of basic blocks
+ // that ends with a jsr instruction (see {@link #FLAG_SUBROUTINE_CALLER}).
+ Label subroutine = basicBlock.outgoingEdges.nextEdge.successor;
+ subroutine.addSubroutineRetSuccessors(basicBlock);
+ }
+ basicBlock = basicBlock.nextBasicBlock;
+ }
}
- @Override
- public void visitLineNumber(final int line, final Label start) {
- if (lineNumber == null) {
- lineNumber = new ByteVector();
- }
- ++lineNumberCount;
- lineNumber.putShort(start.position);
- lineNumber.putShort(line);
- }
-
- @Override
- public void visitMaxs(final int maxStack, final int maxLocals) {
- if (ClassReader.FRAMES && compute == FRAMES) {
- // completes the control flow graph with exception handler blocks
- Handler handler = firstHandler;
- while (handler != null) {
- Label l = handler.start.getFirst();
- Label h = handler.handler.getFirst();
- Label e = handler.end.getFirst();
- // computes the kind of the edges to 'h'
- String t = handler.desc == null ? "java/lang/Throwable"
- : handler.desc;
- int kind = Frame.OBJECT | cw.addType(t);
- // h is an exception handler
- h.status |= Label.TARGET;
- // adds 'h' as a successor of labels between 'start' and 'end'
- while (l != e) {
- // creates an edge to 'h'
- Edge b = new Edge();
- b.info = kind;
- b.successor = h;
- // adds it to the successors of 'l'
- b.next = l.successors;
- l.successors = b;
- // goes to the next label
- l = l.successor;
- }
- handler = handler.next;
- }
-
- // creates and visits the first (implicit) frame
- Frame f = labels.frame;
- Type[] args = Type.getArgumentTypes(descriptor);
- f.initInputFrame(cw, access, args, this.maxLocals);
- visitFrame(f);
-
- /*
- * fix point algorithm: mark the first basic block as 'changed'
- * (i.e. put it in the 'changed' list) and, while there are changed
- * basic blocks, choose one, mark it as unchanged, and update its
- * successors (which can be changed in the process).
- */
- int max = 0;
- Label changed = labels;
- while (changed != null) {
- // removes a basic block from the list of changed basic blocks
- Label l = changed;
- changed = changed.next;
- l.next = null;
- f = l.frame;
- // a reachable jump target must be stored in the stack map
- if ((l.status & Label.TARGET) != 0) {
- l.status |= Label.STORE;
- }
- // all visited labels are reachable, by definition
- l.status |= Label.REACHABLE;
- // updates the (absolute) maximum stack size
- int blockMax = f.inputStack.length + l.outputStackMax;
- if (blockMax > max) {
- max = blockMax;
- }
- // updates the successors of the current basic block
- Edge e = l.successors;
- while (e != null) {
- Label n = e.successor.getFirst();
- boolean change = f.merge(cw, n.frame, e.info);
- if (change && n.next == null) {
- // if n has changed and is not already in the 'changed'
- // list, adds it to this list
- n.next = changed;
- changed = n;
- }
- e = e.next;
- }
- }
-
- // visits all the frames that must be stored in the stack map
- Label l = labels;
- while (l != null) {
- f = l.frame;
- if ((l.status & Label.STORE) != 0) {
- visitFrame(f);
- }
- if ((l.status & Label.REACHABLE) == 0) {
- // finds start and end of dead basic block
- Label k = l.successor;
- int start = l.position;
- int end = (k == null ? code.length : k.position) - 1;
- // if non empty basic block
- if (end >= start) {
- max = Math.max(max, 1);
- // replaces instructions with NOP ... NOP ATHROW
- for (int i = start; i < end; ++i) {
- code.data[i] = Opcodes.NOP;
- }
- code.data[end] = (byte) Opcodes.ATHROW;
- // emits a frame for this unreachable block
- int frameIndex = startFrame(start, 0, 1);
- frame[frameIndex] = Frame.OBJECT
- | cw.addType("java/lang/Throwable");
- endFrame();
- // removes the start-end range from the exception
- // handlers
- firstHandler = Handler.remove(firstHandler, l, k);
- }
- }
- l = l.successor;
- }
-
- handler = firstHandler;
- handlerCount = 0;
- while (handler != null) {
- handlerCount += 1;
- handler = handler.next;
- }
-
- this.maxStack = max;
- } else if (compute == MAXS) {
- // completes the control flow graph with exception handler blocks
- Handler handler = firstHandler;
- while (handler != null) {
- Label l = handler.start;
- Label h = handler.handler;
- Label e = handler.end;
- // adds 'h' as a successor of labels between 'start' and 'end'
- while (l != e) {
- // creates an edge to 'h'
- Edge b = new Edge();
- b.info = Edge.EXCEPTION;
- b.successor = h;
- // adds it to the successors of 'l'
- if ((l.status & Label.JSR) == 0) {
- b.next = l.successors;
- l.successors = b;
- } else {
- // if l is a JSR block, adds b after the first two edges
- // to preserve the hypothesis about JSR block successors
- // order (see {@link #visitJumpInsn})
- b.next = l.successors.next.next;
- l.successors.next.next = b;
- }
- // goes to the next label
- l = l.successor;
- }
- handler = handler.next;
- }
-
- if (subroutines > 0) {
- // completes the control flow graph with the RET successors
- /*
- * first step: finds the subroutines. This step determines, for
- * each basic block, to which subroutine(s) it belongs.
- */
- // finds the basic blocks that belong to the "main" subroutine
- int id = 0;
- labels.visitSubroutine(null, 1, subroutines);
- // finds the basic blocks that belong to the real subroutines
- Label l = labels;
- while (l != null) {
- if ((l.status & Label.JSR) != 0) {
- // the subroutine is defined by l's TARGET, not by l
- Label subroutine = l.successors.next.successor;
- // if this subroutine has not been visited yet...
- if ((subroutine.status & Label.VISITED) == 0) {
- // ...assigns it a new id and finds its basic blocks
- id += 1;
- subroutine.visitSubroutine(null, (id / 32L) << 32
- | (1L << (id % 32)), subroutines);
- }
- }
- l = l.successor;
- }
- // second step: finds the successors of RET blocks
- l = labels;
- while (l != null) {
- if ((l.status & Label.JSR) != 0) {
- Label L = labels;
- while (L != null) {
- L.status &= ~Label.VISITED2;
- L = L.successor;
- }
- // the subroutine is defined by l's TARGET, not by l
- Label subroutine = l.successors.next.successor;
- subroutine.visitSubroutine(l, 0, subroutines);
- }
- l = l.successor;
- }
- }
-
- /*
- * control flow analysis algorithm: while the block stack is not
- * empty, pop a block from this stack, update the max stack size,
- * compute the true (non relative) begin stack size of the
- * successors of this block, and push these successors onto the
- * stack (unless they have already been pushed onto the stack).
- * Note: by hypothesis, the {@link Label#inputStackTop} of the
- * blocks in the block stack are the true (non relative) beginning
- * stack sizes of these blocks.
- */
- int max = 0;
- Label stack = labels;
- while (stack != null) {
- // pops a block from the stack
- Label l = stack;
- stack = stack.next;
- // computes the true (non relative) max stack size of this block
- int start = l.inputStackTop;
- int blockMax = start + l.outputStackMax;
- // updates the global max stack size
- if (blockMax > max) {
- max = blockMax;
- }
- // analyzes the successors of the block
- Edge b = l.successors;
- if ((l.status & Label.JSR) != 0) {
- // ignores the first edge of JSR blocks (virtual successor)
- b = b.next;
- }
- while (b != null) {
- l = b.successor;
- // if this successor has not already been pushed...
- if ((l.status & Label.PUSHED) == 0) {
- // computes its true beginning stack size...
- l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start
- + b.info;
- // ...and pushes it onto the stack
- l.status |= Label.PUSHED;
- l.next = stack;
- stack = l;
- }
- b = b.next;
- }
- }
- this.maxStack = Math.max(maxStack, max);
- } else {
- this.maxStack = maxStack;
- this.maxLocals = maxLocals;
- }
+ // Data flow algorithm: put the first basic block in a list of blocks to process (i.e. blocks
+ // whose input stack size has changed) and, while there are blocks to process, remove one
+ // from the list, update the input stack size of its successor blocks in the control flow
+ // graph, and add these blocks to the list of blocks to process (if not already done).
+ Label listOfBlocksToProcess = firstBasicBlock;
+ listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST;
+ int maxStackSize = maxStack;
+ while (listOfBlocksToProcess != Label.EMPTY_LIST) {
+ // Remove a basic block from the list of blocks to process. Note that we don't reset
+ // basicBlock.nextListElement to null on purpose, to make sure we don't reprocess already
+ // processed basic blocks.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
+ // Compute the (absolute) input stack size and maximum stack size of this block.
+ int inputStackTop = basicBlock.inputStackSize;
+ int maxBlockStackSize = inputStackTop + basicBlock.outputStackMax;
+ // Update the absolute maximum stack size of the method.
+ if (maxBlockStackSize > maxStackSize) {
+ maxStackSize = maxBlockStackSize;
+ }
+ // Update the input stack size of the successor blocks of basicBlock in the control flow
+ // graph, and add these blocks to the list of blocks to process, if not already done.
+ Edge outgoingEdge = basicBlock.outgoingEdges;
+ if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) {
+ // Ignore the first outgoing edge of the basic blocks ending with a jsr: these are virtual
+ // edges which lead to the instruction just after the jsr, and do not correspond to a
+ // possible execution path (see {@link #visitJumpInsn} and
+ // {@link Label#FLAG_SUBROUTINE_CALLER}).
+ outgoingEdge = outgoingEdge.nextEdge;
+ }
+ while (outgoingEdge != null) {
+ Label successorBlock = outgoingEdge.successor;
+ if (successorBlock.nextListElement == null) {
+ successorBlock.inputStackSize =
+ (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info);
+ successorBlock.nextListElement = listOfBlocksToProcess;
+ listOfBlocksToProcess = successorBlock;
+ }
+ outgoingEdge = outgoingEdge.nextEdge;
+ }
}
-
- @Override
- public void visitEnd() {
- }
-
- // ------------------------------------------------------------------------
- // Utility methods: control flow analysis algorithm
- // ------------------------------------------------------------------------
-
- /**
- * Adds a successor to the {@link #currentBlock currentBlock} block.
- *
- * @param info
- * information about the control flow edge to be added.
- * @param successor
- * the successor block to be added to the current block.
- */
- private void addSuccessor(final int info, final Label successor) {
- // creates and initializes an Edge object...
- Edge b = new Edge();
- b.info = info;
- b.successor = successor;
- // ...and adds it to the successor list of the currentBlock block
- b.next = currentBlock.successors;
- currentBlock.successors = b;
- }
-
- /**
- * Ends the current basic block. This method must be used in the case where
- * the current basic block does not have any successor.
- */
- private void noSuccessor() {
- if (compute == FRAMES) {
- Label l = new Label();
- l.frame = new Frame();
- l.frame.owner = l;
- l.resolve(this, code.length, code.data);
- previousBlock.successor = l;
- previousBlock = l;
- } else {
- currentBlock.outputStackMax = maxStackSize;
- }
- currentBlock = null;
- }
-
- // ------------------------------------------------------------------------
- // Utility methods: stack map frames
- // ------------------------------------------------------------------------
-
- /**
- * Visits a frame that has been computed from scratch.
- *
- * @param f
- * the frame that must be visited.
- */
- private void visitFrame(final Frame f) {
- int i, t;
- int nTop = 0;
- int nLocal = 0;
- int nStack = 0;
- int[] locals = f.inputLocals;
- int[] stacks = f.inputStack;
- // computes the number of locals (ignores TOP types that are just after
- // a LONG or a DOUBLE, and all trailing TOP types)
- for (i = 0; i < locals.length; ++i) {
- t = locals[i];
- if (t == Frame.TOP) {
- ++nTop;
- } else {
- nLocal += nTop + 1;
- nTop = 0;
- }
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- // computes the stack size (ignores TOP types that are just after
- // a LONG or a DOUBLE)
- for (i = 0; i < stacks.length; ++i) {
- t = stacks[i];
- ++nStack;
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- // visits the frame and its content
- int frameIndex = startFrame(f.owner.position, nLocal, nStack);
- for (i = 0; nLocal > 0; ++i, --nLocal) {
- t = locals[i];
- frame[frameIndex++] = t;
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- for (i = 0; i < stacks.length; ++i) {
- t = stacks[i];
- frame[frameIndex++] = t;
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- endFrame();
- }
-
- /**
- * Visit the implicit first frame of this method.
- */
- private void visitImplicitFirstFrame() {
- // There can be at most descriptor.length() + 1 locals
- int frameIndex = startFrame(0, descriptor.length() + 1, 0);
- if ((access & Opcodes.ACC_STATIC) == 0) {
- if ((access & ACC_CONSTRUCTOR) == 0) {
- frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName);
- } else {
- frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS;
- }
- }
- int i = 1;
- loop: while (true) {
- int j = i;
- switch (descriptor.charAt(i++)) {
- case 'Z':
- case 'C':
- case 'B':
- case 'S':
- case 'I':
- frame[frameIndex++] = 1; // Opcodes.INTEGER;
- break;
- case 'F':
- frame[frameIndex++] = 2; // Opcodes.FLOAT;
- break;
- case 'J':
- frame[frameIndex++] = 4; // Opcodes.LONG;
- break;
- case 'D':
- frame[frameIndex++] = 3; // Opcodes.DOUBLE;
- break;
- case '[':
- while (descriptor.charAt(i) == '[') {
- ++i;
- }
- if (descriptor.charAt(i) == 'L') {
- ++i;
- while (descriptor.charAt(i) != ';') {
- ++i;
- }
- }
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType(descriptor.substring(j, ++i));
- break;
- case 'L':
- while (descriptor.charAt(i) != ';') {
- ++i;
- }
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType(descriptor.substring(j + 1, i++));
- break;
- default:
- break loop;
- }
- }
- frame[1] = frameIndex - 3;
- endFrame();
- }
-
- /**
- * Starts the visit of a stack map frame.
- *
- * @param offset
- * the offset of the instruction to which the frame corresponds.
- * @param nLocal
- * the number of local variables in the frame.
- * @param nStack
- * the number of stack elements in the frame.
- * @return the index of the next element to be written in this frame.
- */
- private int startFrame(final int offset, final int nLocal, final int nStack) {
- int n = 3 + nLocal + nStack;
- if (frame == null || frame.length < n) {
- frame = new int[n];
- }
- frame[0] = offset;
- frame[1] = nLocal;
- frame[2] = nStack;
- return 3;
- }
-
- /**
- * Checks if the visit of the current frame {@link #frame} is finished, and
- * if yes, write it in the StackMapTable attribute.
- */
- private void endFrame() {
- if (previousFrame != null) { // do not write the first frame
- if (stackMap == null) {
- stackMap = new ByteVector();
- }
- writeFrame();
- ++frameCount;
- }
- previousFrame = frame;
- frame = null;
- }
-
- /**
- * Compress and writes the current frame {@link #frame} in the StackMapTable
- * attribute.
- */
- private void writeFrame() {
- int clocalsSize = frame[1];
- int cstackSize = frame[2];
- if ((cw.version & 0xFFFF) < Opcodes.V1_6) {
- stackMap.putShort(frame[0]).putShort(clocalsSize);
- writeFrameTypes(3, 3 + clocalsSize);
- stackMap.putShort(cstackSize);
- writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
- return;
- }
- int localsSize = previousFrame[1];
- int type = FULL_FRAME;
- int k = 0;
- int delta;
- if (frameCount == 0) {
- delta = frame[0];
- } else {
- delta = frame[0] - previousFrame[0] - 1;
- }
- if (cstackSize == 0) {
- k = clocalsSize - localsSize;
- switch (k) {
- case -3:
- case -2:
- case -1:
- type = CHOP_FRAME;
- localsSize = clocalsSize;
- break;
- case 0:
- type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
- break;
- case 1:
- case 2:
- case 3:
- type = APPEND_FRAME;
- break;
- }
- } else if (clocalsSize == localsSize && cstackSize == 1) {
- type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME
- : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
- }
- if (type != FULL_FRAME) {
- // verify if locals are the same
- int l = 3;
- for (int j = 0; j < localsSize; j++) {
- if (frame[l] != previousFrame[l]) {
- type = FULL_FRAME;
- break;
- }
- l++;
- }
- }
- switch (type) {
- case SAME_FRAME:
- stackMap.putByte(delta);
- break;
- case SAME_LOCALS_1_STACK_ITEM_FRAME:
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
- writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
- break;
- case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort(
- delta);
- writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
- break;
- case SAME_FRAME_EXTENDED:
- stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
- break;
- case CHOP_FRAME:
- stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
- break;
- case APPEND_FRAME:
- stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
- writeFrameTypes(3 + localsSize, 3 + clocalsSize);
- break;
- // case FULL_FRAME:
+ this.maxStack = maxStackSize;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: control flow analysis algorithm
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a successor to {@link #currentBasicBlock} in the control flow graph.
+ *
+ * @param info information about the control flow edge to be added.
+ * @param successor the successor block to be added to the current basic block.
+ */
+ private void addSuccessorToCurrentBasicBlock(final int info, final Label successor) {
+ currentBasicBlock.outgoingEdges = new Edge(info, successor, currentBasicBlock.outgoingEdges);
+ }
+
+ /**
+ * Ends the current basic block. This method must be used in the case where the current basic
+ * block does not have any successor.
+ *
+ *
WARNING: this method must be called after the currently visited instruction has been put in
+ * {@link #code} (if frames are computed, this method inserts a new Label to start a new basic
+ * block after the current instruction).
+ */
+ private void endCurrentBasicBlockWithNoSuccessor() {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ Label nextBasicBlock = new Label();
+ nextBasicBlock.frame = new Frame(nextBasicBlock);
+ nextBasicBlock.resolve(code.data, code.length);
+ lastBasicBlock.nextBasicBlock = nextBasicBlock;
+ lastBasicBlock = nextBasicBlock;
+ currentBasicBlock = null;
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ currentBasicBlock.outputStackMax = (short) maxRelativeStackSize;
+ currentBasicBlock = null;
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: stack map frames
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Starts the visit of a new stack map frame, stored in {@link #currentFrame}.
+ *
+ * @param offset the bytecode offset of the instruction to which the frame corresponds.
+ * @param nLocal the number of local variables in the frame.
+ * @param nStack the number of stack elements in the frame.
+ * @return the index of the next element to be written in this frame.
+ */
+ int visitFrameStart(final int offset, final int nLocal, final int nStack) {
+ int frameLength = 3 + nLocal + nStack;
+ if (currentFrame == null || currentFrame.length < frameLength) {
+ currentFrame = new int[frameLength];
+ }
+ currentFrame[0] = offset;
+ currentFrame[1] = nLocal;
+ currentFrame[2] = nStack;
+ return 3;
+ }
+
+ /**
+ * Sets an abstract type in {@link #currentFrame}.
+ *
+ * @param frameIndex the index of the element to be set in {@link #currentFrame}.
+ * @param abstractType an abstract type.
+ */
+ void visitAbstractType(final int frameIndex, final int abstractType) {
+ currentFrame[frameIndex] = abstractType;
+ }
+
+ /**
+ * Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by
+ * updating the StackMapTable number_of_entries (except if the current frame is the first one,
+ * which is implicit in StackMapTable). Then resets {@link #currentFrame} to null.
+ */
+ void visitFrameEnd() {
+ if (previousFrame != null) {
+ if (stackMapTableEntries == null) {
+ stackMapTableEntries = new ByteVector();
+ }
+ putFrame();
+ ++stackMapTableNumberOfEntries;
+ }
+ previousFrame = currentFrame;
+ currentFrame = null;
+ }
+
+ /** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */
+ private void putFrame() {
+ final int nLocal = currentFrame[1];
+ final int nStack = currentFrame[2];
+ if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
+ // Generate a StackMap attribute entry, which are always uncompressed.
+ stackMapTableEntries.putShort(currentFrame[0]).putShort(nLocal);
+ putAbstractTypes(3, 3 + nLocal);
+ stackMapTableEntries.putShort(nStack);
+ putAbstractTypes(3 + nLocal, 3 + nLocal + nStack);
+ return;
+ }
+ final int offsetDelta =
+ stackMapTableNumberOfEntries == 0
+ ? currentFrame[0]
+ : currentFrame[0] - previousFrame[0] - 1;
+ final int previousNlocal = previousFrame[1];
+ final int nLocalDelta = nLocal - previousNlocal;
+ int type = Frame.FULL_FRAME;
+ if (nStack == 0) {
+ switch (nLocalDelta) {
+ case -3:
+ case -2:
+ case -1:
+ type = Frame.CHOP_FRAME;
+ break;
+ case 0:
+ type = offsetDelta < 64 ? Frame.SAME_FRAME : Frame.SAME_FRAME_EXTENDED;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ type = Frame.APPEND_FRAME;
+ break;
default:
- stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize);
- writeFrameTypes(3, 3 + clocalsSize);
- stackMap.putShort(cstackSize);
- writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
- }
+ // Keep the FULL_FRAME type.
+ break;
+ }
+ } else if (nLocalDelta == 0 && nStack == 1) {
+ type =
+ offsetDelta < 63
+ ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME
+ : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
}
-
- /**
- * Writes some types of the current frame {@link #frame} into the
- * StackMapTableAttribute. This method converts types from the format used
- * in {@link Label} to the format used in StackMapTable attributes. In
- * particular, it converts type table indexes to constant pool indexes.
- *
- * @param start
- * index of the first type in {@link #frame} to write.
- * @param end
- * index of last type in {@link #frame} to write (exclusive).
- */
- private void writeFrameTypes(final int start, final int end) {
- for (int i = start; i < end; ++i) {
- int t = frame[i];
- int d = t & Frame.DIM;
- if (d == 0) {
- int v = t & Frame.BASE_VALUE;
- switch (t & Frame.BASE_KIND) {
- case Frame.OBJECT:
- stackMap.putByte(7).putShort(
- cw.newClass(cw.typeTable[v].strVal1));
- break;
- case Frame.UNINITIALIZED:
- stackMap.putByte(8).putShort(cw.typeTable[v].intVal);
- break;
- default:
- stackMap.putByte(v);
- }
- } else {
- StringBuffer buf = new StringBuffer();
- d >>= 28;
- while (d-- > 0) {
- buf.append('[');
- }
- if ((t & Frame.BASE_KIND) == Frame.OBJECT) {
- buf.append('L');
- buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1);
- buf.append(';');
- } else {
- switch (t & 0xF) {
- case 1:
- buf.append('I');
- break;
- case 2:
- buf.append('F');
- break;
- case 3:
- buf.append('D');
- break;
- case 9:
- buf.append('Z');
- break;
- case 10:
- buf.append('B');
- break;
- case 11:
- buf.append('C');
- break;
- case 12:
- buf.append('S');
- break;
- default:
- buf.append('J');
- }
- }
- stackMap.putByte(7).putShort(cw.newClass(buf.toString()));
- }
- }
+ if (type != Frame.FULL_FRAME) {
+ // Verify if locals are the same as in the previous frame.
+ int frameIndex = 3;
+ for (int i = 0; i < previousNlocal && i < nLocal; i++) {
+ if (currentFrame[frameIndex] != previousFrame[frameIndex]) {
+ type = Frame.FULL_FRAME;
+ break;
+ }
+ frameIndex++;
+ }
}
-
- private void writeFrameType(final Object type) {
- if (type instanceof String) {
- stackMap.putByte(7).putShort(cw.newClass((String) type));
- } else if (type instanceof Integer) {
- stackMap.putByte(((Integer) type).intValue());
- } else {
- stackMap.putByte(8).putShort(((Label) type).position);
- }
+ switch (type) {
+ case Frame.SAME_FRAME:
+ stackMapTableEntries.putByte(offsetDelta);
+ break;
+ case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME:
+ stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
+ putAbstractTypes(3 + nLocal, 4 + nLocal);
+ break;
+ case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
+ stackMapTableEntries
+ .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(offsetDelta);
+ putAbstractTypes(3 + nLocal, 4 + nLocal);
+ break;
+ case Frame.SAME_FRAME_EXTENDED:
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
+ break;
+ case Frame.CHOP_FRAME:
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta);
+ break;
+ case Frame.APPEND_FRAME:
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta);
+ putAbstractTypes(3 + previousNlocal, 3 + nLocal);
+ break;
+ case Frame.FULL_FRAME:
+ default:
+ stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal);
+ putAbstractTypes(3, 3 + nLocal);
+ stackMapTableEntries.putShort(nStack);
+ putAbstractTypes(3 + nLocal, 3 + nLocal + nStack);
}
-
- // ------------------------------------------------------------------------
- // Utility methods: dump bytecode array
- // ------------------------------------------------------------------------
-
- /**
- * Returns the size of the bytecode of this method.
- *
- * @return the size of the bytecode of this method.
- */
- final int getSize() {
- if (classReaderOffset != 0) {
- return 6 + classReaderLength;
- }
- if (resize) {
- // replaces the temporary jump opcodes introduced by Label.resolve.
- if (ClassReader.RESIZE) {
- resizeInstructions();
- } else {
- throw new RuntimeException("Method code too large!");
- }
- }
- int size = 8;
- if (code.length > 0) {
- if (code.length > 65536) {
- throw new RuntimeException("Method code too large!");
- }
- cw.newUTF8("Code");
- size += 18 + code.length + 8 * handlerCount;
- if (localVar != null) {
- cw.newUTF8("LocalVariableTable");
- size += 8 + localVar.length;
- }
- if (localVarType != null) {
- cw.newUTF8("LocalVariableTypeTable");
- size += 8 + localVarType.length;
- }
- if (lineNumber != null) {
- cw.newUTF8("LineNumberTable");
- size += 8 + lineNumber.length;
- }
- if (stackMap != null) {
- boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
- cw.newUTF8(zip ? "StackMapTable" : "StackMap");
- size += 8 + stackMap.length;
- }
- if (cattrs != null) {
- size += cattrs.getSize(cw, code.data, code.length, maxStack,
- maxLocals);
- }
- }
- if (exceptionCount > 0) {
- cw.newUTF8("Exceptions");
- size += 8 + 2 * exceptionCount;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- cw.newUTF8("Synthetic");
- size += 6;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- cw.newUTF8("Deprecated");
- size += 6;
- }
- if (ClassReader.SIGNATURES && signature != null) {
- cw.newUTF8("Signature");
- cw.newUTF8(signature);
- size += 8;
- }
- if (ClassReader.ANNOTATIONS && annd != null) {
- cw.newUTF8("AnnotationDefault");
- size += 6 + annd.length;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- cw.newUTF8("RuntimeVisibleAnnotations");
- size += 8 + anns.getSize();
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- cw.newUTF8("RuntimeInvisibleAnnotations");
- size += 8 + ianns.getSize();
- }
- if (ClassReader.ANNOTATIONS && panns != null) {
- cw.newUTF8("RuntimeVisibleParameterAnnotations");
- size += 7 + 2 * (panns.length - synthetics);
- for (int i = panns.length - 1; i >= synthetics; --i) {
- size += panns[i] == null ? 0 : panns[i].getSize();
- }
- }
- if (ClassReader.ANNOTATIONS && ipanns != null) {
- cw.newUTF8("RuntimeInvisibleParameterAnnotations");
- size += 7 + 2 * (ipanns.length - synthetics);
- for (int i = ipanns.length - 1; i >= synthetics; --i) {
- size += ipanns[i] == null ? 0 : ipanns[i].getSize();
- }
- }
- if (attrs != null) {
- size += attrs.getSize(cw, null, 0, -1, -1);
- }
- return size;
- }
-
- /**
- * Puts the bytecode of this method in the given byte vector.
- *
- * @param out
- * the byte vector into which the bytecode of this method must be
- * copied.
- */
- final void put(final ByteVector out) {
- final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
- int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
- | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
- out.putShort(access & ~mask).putShort(name).putShort(desc);
- if (classReaderOffset != 0) {
- out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength);
- return;
- }
- int attributeCount = 0;
- if (code.length > 0) {
- ++attributeCount;
- }
- if (exceptionCount > 0) {
- ++attributeCount;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- ++attributeCount;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- ++attributeCount;
- }
- if (ClassReader.SIGNATURES && signature != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && annd != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && panns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && ipanns != null) {
- ++attributeCount;
- }
- if (attrs != null) {
- attributeCount += attrs.getCount();
- }
- out.putShort(attributeCount);
- if (code.length > 0) {
- int size = 12 + code.length + 8 * handlerCount;
- if (localVar != null) {
- size += 8 + localVar.length;
- }
- if (localVarType != null) {
- size += 8 + localVarType.length;
- }
- if (lineNumber != null) {
- size += 8 + lineNumber.length;
- }
- if (stackMap != null) {
- size += 8 + stackMap.length;
- }
- if (cattrs != null) {
- size += cattrs.getSize(cw, code.data, code.length, maxStack,
- maxLocals);
- }
- out.putShort(cw.newUTF8("Code")).putInt(size);
- out.putShort(maxStack).putShort(maxLocals);
- out.putInt(code.length).putByteArray(code.data, 0, code.length);
- out.putShort(handlerCount);
- if (handlerCount > 0) {
- Handler h = firstHandler;
- while (h != null) {
- out.putShort(h.start.position).putShort(h.end.position)
- .putShort(h.handler.position).putShort(h.type);
- h = h.next;
- }
- }
- attributeCount = 0;
- if (localVar != null) {
- ++attributeCount;
- }
- if (localVarType != null) {
- ++attributeCount;
- }
- if (lineNumber != null) {
- ++attributeCount;
- }
- if (stackMap != null) {
- ++attributeCount;
- }
- if (cattrs != null) {
- attributeCount += cattrs.getCount();
- }
- out.putShort(attributeCount);
- if (localVar != null) {
- out.putShort(cw.newUTF8("LocalVariableTable"));
- out.putInt(localVar.length + 2).putShort(localVarCount);
- out.putByteArray(localVar.data, 0, localVar.length);
- }
- if (localVarType != null) {
- out.putShort(cw.newUTF8("LocalVariableTypeTable"));
- out.putInt(localVarType.length + 2).putShort(localVarTypeCount);
- out.putByteArray(localVarType.data, 0, localVarType.length);
- }
- if (lineNumber != null) {
- out.putShort(cw.newUTF8("LineNumberTable"));
- out.putInt(lineNumber.length + 2).putShort(lineNumberCount);
- out.putByteArray(lineNumber.data, 0, lineNumber.length);
- }
- if (stackMap != null) {
- boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
- out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap"));
- out.putInt(stackMap.length + 2).putShort(frameCount);
- out.putByteArray(stackMap.data, 0, stackMap.length);
- }
- if (cattrs != null) {
- cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out);
- }
- }
- if (exceptionCount > 0) {
- out.putShort(cw.newUTF8("Exceptions")).putInt(
- 2 * exceptionCount + 2);
- out.putShort(exceptionCount);
- for (int i = 0; i < exceptionCount; ++i) {
- out.putShort(exceptions[i]);
- }
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- out.putShort(cw.newUTF8("Synthetic")).putInt(0);
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- out.putShort(cw.newUTF8("Deprecated")).putInt(0);
- }
- if (ClassReader.SIGNATURES && signature != null) {
- out.putShort(cw.newUTF8("Signature")).putInt(2)
- .putShort(cw.newUTF8(signature));
- }
- if (ClassReader.ANNOTATIONS && annd != null) {
- out.putShort(cw.newUTF8("AnnotationDefault"));
- out.putInt(annd.length);
- out.putByteArray(annd.data, 0, annd.length);
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
- anns.put(out);
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
- ianns.put(out);
- }
- if (ClassReader.ANNOTATIONS && panns != null) {
- out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations"));
- AnnotationWriter.put(panns, synthetics, out);
- }
- if (ClassReader.ANNOTATIONS && ipanns != null) {
- out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations"));
- AnnotationWriter.put(ipanns, synthetics, out);
- }
- if (attrs != null) {
- attrs.put(cw, null, 0, -1, -1, out);
- }
+ }
+
+ /**
+ * Puts some abstract types of {@link #currentFrame} in {@link #stackMapTableEntries} , using the
+ * JVMS verification_type_info format used in StackMapTable attributes.
+ *
+ * @param start index of the first type in {@link #currentFrame} to write.
+ * @param end index of last type in {@link #currentFrame} to write (exclusive).
+ */
+ private void putAbstractTypes(final int start, final int end) {
+ for (int i = start; i < end; ++i) {
+ Frame.putAbstractType(symbolTable, currentFrame[i], stackMapTableEntries);
}
-
- // ------------------------------------------------------------------------
- // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W)
- // ------------------------------------------------------------------------
-
- /**
- * Resizes and replaces the temporary instructions inserted by
- * {@link Label#resolve} for wide forward jumps, while keeping jump offsets
- * and instruction addresses consistent. This may require to resize other
- * existing instructions, or even to introduce new instructions: for
- * example, increasing the size of an instruction by 2 at the middle of a
- * method can increases the offset of an IFEQ instruction from 32766 to
- * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W
- * 32765. This, in turn, may require to increase the size of another jump
- * instruction, and so on... All these operations are handled automatically
- * by this method.
- *
- * This method must be called after all the method that is being built
- * has been visited. In particular, the {@link Label Label} objects used
- * to construct the method are no longer valid after this method has been
- * called.
- */
- private void resizeInstructions() {
- byte[] b = code.data; // bytecode of the method
- int u, v, label; // indexes in b
- int i, j; // loop indexes
- /*
- * 1st step: As explained above, resizing an instruction may require to
- * resize another one, which may require to resize yet another one, and
- * so on. The first step of the algorithm consists in finding all the
- * instructions that need to be resized, without modifying the code.
- * This is done by the following "fix point" algorithm:
- *
- * Parse the code to find the jump instructions whose offset will need
- * more than 2 bytes to be stored (the future offset is computed from
- * the current offset and from the number of bytes that will be inserted
- * or removed between the source and target instructions). For each such
- * instruction, adds an entry in (a copy of) the indexes and sizes
- * arrays (if this has not already been done in a previous iteration!).
- *
- * If at least one entry has been added during the previous step, go
- * back to the beginning, otherwise stop.
- *
- * In fact the real algorithm is complicated by the fact that the size
- * of TABLESWITCH and LOOKUPSWITCH instructions depends on their
- * position in the bytecode (because of padding). In order to ensure the
- * convergence of the algorithm, the number of bytes to be added or
- * removed from these instructions is over estimated during the previous
- * loop, and computed exactly only after the loop is finished (this
- * requires another pass to parse the bytecode of the method).
- */
- int[] allIndexes = new int[0]; // copy of indexes
- int[] allSizes = new int[0]; // copy of sizes
- boolean[] resize; // instructions to be resized
- int newOffset; // future offset of a jump instruction
-
- resize = new boolean[code.length];
-
- // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done
- int state = 3;
- do {
- if (state == 3) {
- state = 2;
- }
- u = 0;
- while (u < b.length) {
- int opcode = b[u] & 0xFF; // opcode of current instruction
- int insert = 0; // bytes to be added after this instruction
-
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- case ClassWriter.IMPLVAR_INSN:
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- if (opcode > 201) {
- // converts temporary opcodes 202 to 217, 218 and
- // 219 to IFEQ ... JSR (inclusive), IFNULL and
- // IFNONNULL
- opcode = opcode < 218 ? opcode - 49 : opcode - 20;
- label = u + readUnsignedShort(b, u + 1);
- } else {
- label = u + readShort(b, u + 1);
- }
- newOffset = getNewOffset(allIndexes, allSizes, u, label);
- if (newOffset < Short.MIN_VALUE
- || newOffset > Short.MAX_VALUE) {
- if (!resize[u]) {
- if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
- // two additional bytes will be required to
- // replace this GOTO or JSR instruction with
- // a GOTO_W or a JSR_W
- insert = 2;
- } else {
- // five additional bytes will be required to
- // replace this IFxxx instruction with
- // IFNOTxxx GOTO_W , where IFNOTxxx
- // is the "opposite" opcode of IFxxx (i.e.,
- // IFNE for IFEQ) and where designates
- // the instruction just after the GOTO_W.
- insert = 5;
- }
- resize[u] = true;
- }
- }
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- u += 5;
- break;
- case ClassWriter.TABL_INSN:
- if (state == 1) {
- // true number of bytes to be added (or removed)
- // from this instruction = (future number of padding
- // bytes - current number of padding byte) -
- // previously over estimated variation =
- // = ((3 - newOffset%4) - (3 - u%4)) - u%4
- // = (-newOffset%4 + u%4) - u%4
- // = -(newOffset & 3)
- newOffset = getNewOffset(allIndexes, allSizes, 0, u);
- insert = -(newOffset & 3);
- } else if (!resize[u]) {
- // over estimation of the number of bytes to be
- // added to this instruction = 3 - current number
- // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3
- insert = u & 3;
- resize[u] = true;
- }
- // skips instruction
- u = u + 4 - (u & 3);
- u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12;
- break;
- case ClassWriter.LOOK_INSN:
- if (state == 1) {
- // like TABL_INSN
- newOffset = getNewOffset(allIndexes, allSizes, 0, u);
- insert = -(newOffset & 3);
- } else if (!resize[u]) {
- // like TABL_INSN
- insert = u & 3;
- resize[u] = true;
- }
- // skips instruction
- u = u + 4 - (u & 3);
- u += 8 * readInt(b, u + 4) + 8;
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- u += 6;
- } else {
- u += 4;
- }
- break;
- case ClassWriter.VAR_INSN:
- case ClassWriter.SBYTE_INSN:
- case ClassWriter.LDC_INSN:
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- case ClassWriter.LDCW_INSN:
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.TYPE_INSN:
- case ClassWriter.IINC_INSN:
- u += 3;
- break;
- case ClassWriter.ITFMETH_INSN:
- case ClassWriter.INDYMETH_INSN:
- u += 5;
- break;
- // case ClassWriter.MANA_INSN:
- default:
- u += 4;
- break;
- }
- if (insert != 0) {
- // adds a new (u, insert) entry in the allIndexes and
- // allSizes arrays
- int[] newIndexes = new int[allIndexes.length + 1];
- int[] newSizes = new int[allSizes.length + 1];
- System.arraycopy(allIndexes, 0, newIndexes, 0,
- allIndexes.length);
- System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length);
- newIndexes[allIndexes.length] = u;
- newSizes[allSizes.length] = insert;
- allIndexes = newIndexes;
- allSizes = newSizes;
- if (insert > 0) {
- state = 3;
- }
- }
- }
- if (state < 3) {
- --state;
- }
- } while (state != 0);
-
- // 2nd step:
- // copies the bytecode of the method into a new bytevector, updates the
- // offsets, and inserts (or removes) bytes as requested.
-
- ByteVector newCode = new ByteVector(code.length);
-
- u = 0;
- while (u < code.length) {
- int opcode = b[u] & 0xFF;
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- case ClassWriter.IMPLVAR_INSN:
- newCode.putByte(opcode);
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- if (opcode > 201) {
- // changes temporary opcodes 202 to 217 (inclusive), 218
- // and 219 to IFEQ ... JSR (inclusive), IFNULL and
- // IFNONNULL
- opcode = opcode < 218 ? opcode - 49 : opcode - 20;
- label = u + readUnsignedShort(b, u + 1);
- } else {
- label = u + readShort(b, u + 1);
- }
- newOffset = getNewOffset(allIndexes, allSizes, u, label);
- if (resize[u]) {
- // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx
- // with IFNOTxxx GOTO_W , where IFNOTxxx is
- // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
- // and where designates the instruction just after
- // the GOTO_W.
- if (opcode == Opcodes.GOTO) {
- newCode.putByte(200); // GOTO_W
- } else if (opcode == Opcodes.JSR) {
- newCode.putByte(201); // JSR_W
- } else {
- newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
- : opcode ^ 1);
- newCode.putShort(8); // jump offset
- newCode.putByte(200); // GOTO_W
- // newOffset now computed from start of GOTO_W
- newOffset -= 3;
- }
- newCode.putInt(newOffset);
- } else {
- newCode.putByte(opcode);
- newCode.putShort(newOffset);
- }
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- label = u + readInt(b, u + 1);
- newOffset = getNewOffset(allIndexes, allSizes, u, label);
- newCode.putByte(opcode);
- newCode.putInt(newOffset);
- u += 5;
- break;
- case ClassWriter.TABL_INSN:
- // skips 0 to 3 padding bytes
- v = u;
- u = u + 4 - (v & 3);
- // reads and copies instruction
- newCode.putByte(Opcodes.TABLESWITCH);
- newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- j = readInt(b, u);
- u += 4;
- newCode.putInt(j);
- j = readInt(b, u) - j + 1;
- u += 4;
- newCode.putInt(readInt(b, u - 4));
- for (; j > 0; --j) {
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- }
- break;
- case ClassWriter.LOOK_INSN:
- // skips 0 to 3 padding bytes
- v = u;
- u = u + 4 - (v & 3);
- // reads and copies instruction
- newCode.putByte(Opcodes.LOOKUPSWITCH);
- newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- j = readInt(b, u);
- u += 4;
- newCode.putInt(j);
- for (; j > 0; --j) {
- newCode.putInt(readInt(b, u));
- u += 4;
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- }
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- newCode.putByteArray(b, u, 6);
- u += 6;
- } else {
- newCode.putByteArray(b, u, 4);
- u += 4;
- }
- break;
- case ClassWriter.VAR_INSN:
- case ClassWriter.SBYTE_INSN:
- case ClassWriter.LDC_INSN:
- newCode.putByteArray(b, u, 2);
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- case ClassWriter.LDCW_INSN:
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.TYPE_INSN:
- case ClassWriter.IINC_INSN:
- newCode.putByteArray(b, u, 3);
- u += 3;
- break;
- case ClassWriter.ITFMETH_INSN:
- case ClassWriter.INDYMETH_INSN:
- newCode.putByteArray(b, u, 5);
- u += 5;
- break;
- // case MANA_INSN:
- default:
- newCode.putByteArray(b, u, 4);
- u += 4;
- break;
- }
- }
-
- // recomputes the stack map frames
- if (frameCount > 0) {
- if (compute == FRAMES) {
- frameCount = 0;
- stackMap = null;
- previousFrame = null;
- frame = null;
- Frame f = new Frame();
- f.owner = labels;
- Type[] args = Type.getArgumentTypes(descriptor);
- f.initInputFrame(cw, access, args, maxLocals);
- visitFrame(f);
- Label l = labels;
- while (l != null) {
- /*
- * here we need the original label position. getNewOffset
- * must therefore never have been called for this label.
- */
- u = l.position - 3;
- if ((l.status & Label.STORE) != 0 || (u >= 0 && resize[u])) {
- getNewOffset(allIndexes, allSizes, l);
- // TODO update offsets in UNINITIALIZED values
- visitFrame(l.frame);
- }
- l = l.successor;
- }
- } else {
- /*
- * Resizing an existing stack map frame table is really hard.
- * Not only the table must be parsed to update the offets, but
- * new frames may be needed for jump instructions that were
- * inserted by this method. And updating the offsets or
- * inserting frames can change the format of the following
- * frames, in case of packed frames. In practice the whole table
- * must be recomputed. For this the frames are marked as
- * potentially invalid. This will cause the whole class to be
- * reread and rewritten with the COMPUTE_FRAMES option (see the
- * ClassWriter.toByteArray method). This is not very efficient
- * but is much easier and requires much less code than any other
- * method I can think of.
- */
- cw.invalidFrames = true;
- }
- }
- // updates the exception handler block labels
- Handler h = firstHandler;
- while (h != null) {
- getNewOffset(allIndexes, allSizes, h.start);
- getNewOffset(allIndexes, allSizes, h.end);
- getNewOffset(allIndexes, allSizes, h.handler);
- h = h.next;
- }
- // updates the instructions addresses in the
- // local var and line number tables
- for (i = 0; i < 2; ++i) {
- ByteVector bv = i == 0 ? localVar : localVarType;
- if (bv != null) {
- b = bv.data;
- u = 0;
- while (u < bv.length) {
- label = readUnsignedShort(b, u);
- newOffset = getNewOffset(allIndexes, allSizes, 0, label);
- writeShort(b, u, newOffset);
- label += readUnsignedShort(b, u + 2);
- newOffset = getNewOffset(allIndexes, allSizes, 0, label)
- - newOffset;
- writeShort(b, u + 2, newOffset);
- u += 10;
- }
- }
- }
- if (lineNumber != null) {
- b = lineNumber.data;
- u = 0;
- while (u < lineNumber.length) {
- writeShort(
- b,
- u,
- getNewOffset(allIndexes, allSizes, 0,
- readUnsignedShort(b, u)));
- u += 4;
- }
- }
- // updates the labels of the other attributes
- Attribute attr = cattrs;
- while (attr != null) {
- Label[] labels = attr.getLabels();
- if (labels != null) {
- for (i = labels.length - 1; i >= 0; --i) {
- getNewOffset(allIndexes, allSizes, labels[i]);
- }
- }
- attr = attr.next;
- }
-
- // replaces old bytecodes with new ones
- code = newCode;
- }
-
- /**
- * Reads an unsigned short value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * the start index of the value to be read.
- * @return the read value.
- */
- static int readUnsignedShort(final byte[] b, final int index) {
- return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
- }
-
- /**
- * Reads a signed short value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * the start index of the value to be read.
- * @return the read value.
- */
- static short readShort(final byte[] b, final int index) {
- return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
- }
-
- /**
- * Reads a signed int value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * the start index of the value to be read.
- * @return the read value.
- */
- static int readInt(final byte[] b, final int index) {
- return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
- | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
- }
-
- /**
- * Writes a short value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * where the first byte of the short value must be written.
- * @param s
- * the value to be written in the given byte array.
- */
- static void writeShort(final byte[] b, final int index, final int s) {
- b[index] = (byte) (s >>> 8);
- b[index + 1] = (byte) s;
- }
-
- /**
- * Computes the future value of a bytecode offset.
- *
- * Note: it is possible to have several entries for the same instruction in
- * the indexes and sizes: two entries (index=a,size=b) and
- * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b').
- *
- * @param indexes
- * current positions of the instructions to be resized. Each
- * instruction must be designated by the index of its last
- * byte, plus one (or, in other words, by the index of the
- * first byte of the next instruction).
- * @param sizes
- * the number of bytes to be added to the above
- * instructions. More precisely, for each i < len,
- * sizes[i] bytes will be added at the end of the
- * instruction designated by indexes[i] or, if
- * sizes[i] is negative, the last |
- * sizes[i]| bytes of the instruction will be removed
- * (the instruction size must not become negative or
- * null).
- * @param begin
- * index of the first byte of the source instruction.
- * @param end
- * index of the first byte of the target instruction.
- * @return the future value of the given bytecode offset.
- */
- static int getNewOffset(final int[] indexes, final int[] sizes,
- final int begin, final int end) {
- int offset = end - begin;
- for (int i = 0; i < indexes.length; ++i) {
- if (begin < indexes[i] && indexes[i] <= end) {
- // forward jump
- offset += sizes[i];
- } else if (end < indexes[i] && indexes[i] <= begin) {
- // backward jump
- offset -= sizes[i];
- }
- }
- return offset;
- }
-
- /**
- * Updates the offset of the given label.
- *
- * @param indexes
- * current positions of the instructions to be resized. Each
- * instruction must be designated by the index of its last
- * byte, plus one (or, in other words, by the index of the
- * first byte of the next instruction).
- * @param sizes
- * the number of bytes to be added to the above
- * instructions. More precisely, for each i < len,
- * sizes[i] bytes will be added at the end of the
- * instruction designated by indexes[i] or, if
- * sizes[i] is negative, the last |
- * sizes[i]| bytes of the instruction will be removed
- * (the instruction size must not become negative or
- * null).
- * @param label
- * the label whose offset must be updated.
- */
- static void getNewOffset(final int[] indexes, final int[] sizes,
- final Label label) {
- if ((label.status & Label.RESIZED) == 0) {
- label.position = getNewOffset(indexes, sizes, 0, label.position);
- label.status |= Label.RESIZED;
- }
+ }
+
+ /**
+ * Puts the given public API frame element type in {@link #stackMapTableEntries} , using the JVMS
+ * verification_type_info format used in StackMapTable attributes.
+ *
+ * @param type a frame element type described using the same format as in {@link
+ * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
+ * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or
+ * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating
+ * a NEW instruction (for uninitialized types).
+ */
+ private void putFrameType(final Object type) {
+ if (type instanceof Integer) {
+ stackMapTableEntries.putByte(((Integer) type).intValue());
+ } else if (type instanceof String) {
+ stackMapTableEntries
+ .putByte(Frame.ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass((String) type).index);
+ } else {
+ stackMapTableEntries
+ .putByte(Frame.ITEM_UNINITIALIZED)
+ .putShort(((Label) type).bytecodeOffset);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns whether the attributes of this method can be copied from the attributes of the given
+ * method (assuming there is no method visitor between the given ClassReader and this
+ * MethodWriter). This method should only be called just after this MethodWriter has been created,
+ * and before any content is visited. It returns true if the attributes corresponding to the
+ * constructor arguments (at most a Signature, an Exception, a Deprecated and a Synthetic
+ * attribute) are the same as the corresponding attributes in the given method.
+ *
+ * @param source the source ClassReader from which the attributes of this method might be copied.
+ * @param methodInfoOffset the offset in 'source.b' of the method_info JVMS structure from which
+ * the attributes of this method might be copied.
+ * @param methodInfoLength the length in 'source.b' of the method_info JVMS structure from which
+ * the attributes of this method might be copied.
+ * @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes
+ * of this method might be copied contains a Synthetic attribute.
+ * @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes
+ * of this method might be copied contains a Deprecated attribute.
+ * @param signatureIndex the constant pool index contained in the Signature attribute of the
+ * method_info JVMS structure from which the attributes of this method might be copied, or 0.
+ * @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info
+ * JVMS structure from which the attributes of this method might be copied, or 0.
+ * @return whether the attributes of this method can be copied from the attributes of the
+ * method_info JVMS structure in 'source.b', between 'methodInfoOffset' and 'methodInfoOffset'
+ * + 'methodInfoLength'.
+ */
+ boolean canCopyMethodAttributes(
+ final ClassReader source,
+ final int methodInfoOffset,
+ final int methodInfoLength,
+ final boolean hasSyntheticAttribute,
+ final boolean hasDeprecatedAttribute,
+ final int signatureIndex,
+ final int exceptionsOffset) {
+ if (source != symbolTable.getSource()
+ || signatureIndex != this.signatureIndex
+ || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) {
+ return false;
+ }
+ boolean needSyntheticAttribute =
+ symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0;
+ if (hasSyntheticAttribute != needSyntheticAttribute) {
+ return false;
+ }
+ if (exceptionsOffset == 0) {
+ if (numberOfExceptions != 0) {
+ return false;
+ }
+ } else if (source.readUnsignedShort(exceptionsOffset) == numberOfExceptions) {
+ int currentExceptionOffset = exceptionsOffset + 2;
+ for (int i = 0; i < numberOfExceptions; ++i) {
+ if (source.readUnsignedShort(currentExceptionOffset) != exceptionIndexTable[i]) {
+ return false;
+ }
+ currentExceptionOffset += 2;
+ }
+ }
+ // Don't copy the attributes yet, instead store their location in the source class reader so
+ // they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes
+ // of the method_info JVMS structure.
+ this.sourceOffset = methodInfoOffset + 6;
+ this.sourceLength = methodInfoLength - 6;
+ return true;
+ }
+
+ /**
+ * Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the
+ * names of the attributes of this method in the constant pool.
+ *
+ * @return the size in bytes of the method_info JVMS structure.
+ */
+ int computeMethodInfoSize() {
+ // If this method_info must be copied from an existing one, the size computation is trivial.
+ if (sourceOffset != 0) {
+ // sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index.
+ return 6 + sourceLength;
+ }
+ // 2 bytes each for access_flags, name_index, descriptor_index and attributes_count.
+ int size = 8;
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ if (code.length > 0) {
+ if (code.length > 65535) {
+ throw new IndexOutOfBoundsException("Method code too large!");
+ }
+ symbolTable.addConstantUtf8(Constants.CODE);
+ // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
+ // max_locals, code_length and attributes_count, plus the bytecode and the exception table.
+ size += 16 + code.length + Handler.getExceptionTableSize(firstHandler);
+ if (stackMapTableEntries != null) {
+ boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
+ symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap");
+ // 6 header bytes and 2 bytes for number_of_entries.
+ size += 8 + stackMapTableEntries.length;
+ }
+ if (lineNumberTable != null) {
+ symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE);
+ // 6 header bytes and 2 bytes for line_number_table_length.
+ size += 8 + lineNumberTable.length;
+ }
+ if (localVariableTable != null) {
+ symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE);
+ // 6 header bytes and 2 bytes for local_variable_table_length.
+ size += 8 + localVariableTable.length;
+ }
+ if (localVariableTypeTable != null) {
+ symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE);
+ // 6 header bytes and 2 bytes for local_variable_type_table_length.
+ size += 8 + localVariableTypeTable.length;
+ }
+ if (lastCodeRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (firstCodeAttribute != null) {
+ size +=
+ firstCodeAttribute.computeAttributesSize(
+ symbolTable, code.data, code.length, maxStack, maxLocals);
+ }
+ }
+ if (numberOfExceptions > 0) {
+ symbolTable.addConstantUtf8(Constants.EXCEPTIONS);
+ size += 8 + 2 * numberOfExceptions;
+ }
+ boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
+ symbolTable.addConstantUtf8(Constants.SYNTHETIC);
+ size += 6;
+ }
+ if (signatureIndex != 0) {
+ symbolTable.addConstantUtf8(Constants.SIGNATURE);
+ size += 8;
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ symbolTable.addConstantUtf8(Constants.DEPRECATED);
+ size += 6;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ size +=
+ lastRuntimeVisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeVisibleParameterAnnotations != null) {
+ size +=
+ AnnotationWriter.computeParameterAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
+ lastRuntimeVisibleParameterAnnotations,
+ visibleAnnotableParameterCount == 0
+ ? lastRuntimeVisibleParameterAnnotations.length
+ : visibleAnnotableParameterCount);
+ }
+ if (lastRuntimeInvisibleParameterAnnotations != null) {
+ size +=
+ AnnotationWriter.computeParameterAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS,
+ lastRuntimeInvisibleParameterAnnotations,
+ invisibleAnnotableParameterCount == 0
+ ? lastRuntimeInvisibleParameterAnnotations.length
+ : invisibleAnnotableParameterCount);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (defaultValue != null) {
+ symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT);
+ size += 6 + defaultValue.length;
+ }
+ if (parameters != null) {
+ symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS);
+ // 6 header bytes and 1 byte for parameters_count.
+ size += 7 + parameters.length;
+ }
+ if (firstAttribute != null) {
+ size += firstAttribute.computeAttributesSize(symbolTable);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the content of the method_info JVMS structure generated by this MethodWriter into the
+ * given ByteVector.
+ *
+ * @param output where the method_info structure must be put.
+ */
+ void putMethodInfo(final ByteVector output) {
+ boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
+ int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
+ output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
+ // If this method_info must be copied from an existing one, copy it now and return early.
+ if (sourceOffset != 0) {
+ output.putByteArray(symbolTable.getSource().b, sourceOffset, sourceLength);
+ return;
+ }
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributeCount = 0;
+ if (code.length > 0) {
+ ++attributeCount;
+ }
+ if (numberOfExceptions > 0) {
+ ++attributeCount;
+ }
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
+ ++attributeCount;
+ }
+ if (signatureIndex != 0) {
+ ++attributeCount;
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeVisibleParameterAnnotations != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeInvisibleParameterAnnotations != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributeCount;
+ }
+ if (defaultValue != null) {
+ ++attributeCount;
+ }
+ if (parameters != null) {
+ ++attributeCount;
+ }
+ if (firstAttribute != null) {
+ attributeCount += firstAttribute.getAttributeCount();
+ }
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ output.putShort(attributeCount);
+ if (code.length > 0) {
+ // 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and
+ // attributes_count, plus the bytecode and the exception table.
+ int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler);
+ int codeAttributeCount = 0;
+ if (stackMapTableEntries != null) {
+ // 6 header bytes and 2 bytes for number_of_entries.
+ size += 8 + stackMapTableEntries.length;
+ ++codeAttributeCount;
+ }
+ if (lineNumberTable != null) {
+ // 6 header bytes and 2 bytes for line_number_table_length.
+ size += 8 + lineNumberTable.length;
+ ++codeAttributeCount;
+ }
+ if (localVariableTable != null) {
+ // 6 header bytes and 2 bytes for local_variable_table_length.
+ size += 8 + localVariableTable.length;
+ ++codeAttributeCount;
+ }
+ if (localVariableTypeTable != null) {
+ // 6 header bytes and 2 bytes for local_variable_type_table_length.
+ size += 8 + localVariableTypeTable.length;
+ ++codeAttributeCount;
+ }
+ if (lastCodeRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ ++codeAttributeCount;
+ }
+ if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ ++codeAttributeCount;
+ }
+ if (firstCodeAttribute != null) {
+ size +=
+ firstCodeAttribute.computeAttributesSize(
+ symbolTable, code.data, code.length, maxStack, maxLocals);
+ codeAttributeCount += firstCodeAttribute.getAttributeCount();
+ }
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.CODE))
+ .putInt(size)
+ .putShort(maxStack)
+ .putShort(maxLocals)
+ .putInt(code.length)
+ .putByteArray(code.data, 0, code.length);
+ Handler.putExceptionTable(firstHandler, output);
+ output.putShort(codeAttributeCount);
+ if (stackMapTableEntries != null) {
+ boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
+ output
+ .putShort(
+ symbolTable.addConstantUtf8(
+ useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap"))
+ .putInt(2 + stackMapTableEntries.length)
+ .putShort(stackMapTableNumberOfEntries)
+ .putByteArray(stackMapTableEntries.data, 0, stackMapTableEntries.length);
+ }
+ if (lineNumberTable != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE))
+ .putInt(2 + lineNumberTable.length)
+ .putShort(lineNumberTableLength)
+ .putByteArray(lineNumberTable.data, 0, lineNumberTable.length);
+ }
+ if (localVariableTable != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE))
+ .putInt(2 + localVariableTable.length)
+ .putShort(localVariableTableLength)
+ .putByteArray(localVariableTable.data, 0, localVariableTable.length);
+ }
+ if (localVariableTypeTable != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE))
+ .putInt(2 + localVariableTypeTable.length)
+ .putShort(localVariableTypeTableLength)
+ .putByteArray(localVariableTypeTable.data, 0, localVariableTypeTable.length);
+ }
+ if (lastCodeRuntimeVisibleTypeAnnotation != null) {
+ lastCodeRuntimeVisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
+ lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (firstCodeAttribute != null) {
+ firstCodeAttribute.putAttributes(
+ symbolTable, code.data, code.length, maxStack, maxLocals, output);
+ }
+ }
+ if (numberOfExceptions > 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS))
+ .putInt(2 + 2 * numberOfExceptions)
+ .putShort(numberOfExceptions);
+ for (int exceptionIndex : exceptionIndexTable) {
+ output.putShort(exceptionIndex);
+ }
+ }
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
+ }
+ if (signatureIndex != 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
+ .putInt(2)
+ .putShort(signatureIndex);
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ lastRuntimeVisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ lastRuntimeInvisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeVisibleParameterAnnotations != null) {
+ AnnotationWriter.putParameterAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS),
+ lastRuntimeVisibleParameterAnnotations,
+ visibleAnnotableParameterCount == 0
+ ? lastRuntimeVisibleParameterAnnotations.length
+ : visibleAnnotableParameterCount,
+ output);
+ }
+ if (lastRuntimeInvisibleParameterAnnotations != null) {
+ AnnotationWriter.putParameterAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS),
+ lastRuntimeInvisibleParameterAnnotations,
+ invisibleAnnotableParameterCount == 0
+ ? lastRuntimeInvisibleParameterAnnotations.length
+ : invisibleAnnotableParameterCount,
+ output);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ lastRuntimeVisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ lastRuntimeInvisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (defaultValue != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT))
+ .putInt(defaultValue.length)
+ .putByteArray(defaultValue.data, 0, defaultValue.length);
+ }
+ if (parameters != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS))
+ .putInt(1 + parameters.length)
+ .putByte(parametersCount)
+ .putByteArray(parameters.data, 0, parameters.length);
+ }
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, output);
}
+ }
+
+ /**
+ * Collects the attributes of this method into the given set of attribute prototypes.
+ *
+ * @param attributePrototypes a set of attribute prototypes.
+ */
+ final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
+ attributePrototypes.addAttributes(firstAttribute);
+ attributePrototypes.addAttributes(firstCodeAttribute);
+ }
}
diff --git a/src/jvm/clojure/asm/ModuleVisitor.java b/src/jvm/clojure/asm/ModuleVisitor.java
new file mode 100644
index 0000000000..26d2e365cd
--- /dev/null
+++ b/src/jvm/clojure/asm/ModuleVisitor.java
@@ -0,0 +1,175 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+/**
+ * A visitor to visit a Java module. The methods of this class must be called in the following
+ * order: visitMainClass | ( visitPackage | visitRequire |
+ * visitExport | visitOpen | visitUse | visitProvide )*
+ * visitEnd.
+ *
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+public abstract class ModuleVisitor {
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ protected final int api;
+
+ /** The module visitor to which this visitor must delegate method calls. May be null. */
+ protected ModuleVisitor mv;
+
+ /**
+ * Constructs a new {@link ModuleVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
+ * or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ */
+ public ModuleVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link ModuleVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
+ * or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ * @param moduleVisitor the module visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
+ if (api != Opcodes.ASM6 && api != Opcodes.ASM7_EXPERIMENTAL) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.mv = moduleVisitor;
+ }
+
+ /**
+ * Visit the main class of the current module.
+ *
+ * @param mainClass the internal name of the main class of the current module.
+ */
+ public void visitMainClass(final String mainClass) {
+ if (mv != null) {
+ mv.visitMainClass(mainClass);
+ }
+ }
+
+ /**
+ * Visit a package of the current module.
+ *
+ * @param packaze the internal name of a package.
+ */
+ public void visitPackage(final String packaze) {
+ if (mv != null) {
+ mv.visitPackage(packaze);
+ }
+ }
+
+ /**
+ * Visits a dependence of the current module.
+ *
+ * @param module the fully qualified name (using dots) of the dependence.
+ * @param access the access flag of the dependence among {@code ACC_TRANSITIVE}, {@code
+ * ACC_STATIC_PHASE}, {@code ACC_SYNTHETIC} and {@code ACC_MANDATED}.
+ * @param version the module version at compile time, or null.
+ */
+ public void visitRequire(final String module, final int access, final String version) {
+ if (mv != null) {
+ mv.visitRequire(module, access, version);
+ }
+ }
+
+ /**
+ * Visit an exported package of the current module.
+ *
+ * @param packaze the internal name of the exported package.
+ * @param access the access flag of the exported package, valid values are among {@code
+ * ACC_SYNTHETIC} and {@code ACC_MANDATED}.
+ * @param modules the fully qualified names (using dots) of the modules that can access the public
+ * classes of the exported package, or null.
+ */
+ public void visitExport(final String packaze, final int access, final String... modules) {
+ if (mv != null) {
+ mv.visitExport(packaze, access, modules);
+ }
+ }
+
+ /**
+ * Visit an open package of the current module.
+ *
+ * @param packaze the internal name of the opened package.
+ * @param access the access flag of the opened package, valid values are among {@code
+ * ACC_SYNTHETIC} and {@code ACC_MANDATED}.
+ * @param modules the fully qualified names (using dots) of the modules that can use deep
+ * reflection to the classes of the open package, or null.
+ */
+ public void visitOpen(final String packaze, final int access, final String... modules) {
+ if (mv != null) {
+ mv.visitOpen(packaze, access, modules);
+ }
+ }
+
+ /**
+ * Visit a service used by the current module. The name must be the internal name of an interface
+ * or a class.
+ *
+ * @param service the internal name of the service.
+ */
+ public void visitUse(final String service) {
+ if (mv != null) {
+ mv.visitUse(service);
+ }
+ }
+
+ /**
+ * Visit an implementation of a service.
+ *
+ * @param service the internal name of the service.
+ * @param providers the internal names of the implementations of the service (there is at least
+ * one provider).
+ */
+ public void visitProvide(final String service, final String... providers) {
+ if (mv != null) {
+ mv.visitProvide(service, providers);
+ }
+ }
+
+ /**
+ * Visits the end of the module. This method, which is the last one to be called, is used to
+ * inform the visitor that everything have been visited.
+ */
+ public void visitEnd() {
+ if (mv != null) {
+ mv.visitEnd();
+ }
+ }
+}
diff --git a/src/jvm/clojure/asm/ModuleWriter.java b/src/jvm/clojure/asm/ModuleWriter.java
new file mode 100644
index 0000000000..7eb7abda91
--- /dev/null
+++ b/src/jvm/clojure/asm/ModuleWriter.java
@@ -0,0 +1,253 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+/**
+ * A {@link ModuleVisitor} that generates the corresponding Module, ModulePackages and
+ * ModuleMainClass attributes, as defined in the Java Virtual Machine Specification (JVMS).
+ *
+ * @see JVMS
+ * 4.7.25
+ * @see JVMS
+ * 4.7.26
+ * @see JVMS
+ * 4.7.27
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+final class ModuleWriter extends ModuleVisitor {
+
+ /** Where the constants used in this AnnotationWriter must be stored. */
+ private final SymbolTable symbolTable;
+
+ /** The module_name_index field of the JVMS Module attribute. */
+ private final int moduleNameIndex;
+
+ /** The module_flags field of the JVMS Module attribute. */
+ private final int moduleFlags;
+
+ /** The module_version_index field of the JVMS Module attribute. */
+ private final int moduleVersionIndex;
+
+ /** The requires_count field of the JVMS Module attribute. */
+ private int requiresCount;
+
+ /** The binary content of the 'requires' array of the JVMS Module attribute. */
+ private final ByteVector requires;
+
+ /** The exports_count field of the JVMS Module attribute. */
+ private int exportsCount;
+
+ /** The binary content of the 'exports' array of the JVMS Module attribute. */
+ private final ByteVector exports;
+
+ /** The opens_count field of the JVMS Module attribute. */
+ private int opensCount;
+
+ /** The binary content of the 'opens' array of the JVMS Module attribute. */
+ private final ByteVector opens;
+
+ /** The uses_count field of the JVMS Module attribute. */
+ private int usesCount;
+
+ /** The binary content of the 'uses_index' array of the JVMS Module attribute. */
+ private final ByteVector usesIndex;
+
+ /** The provides_count field of the JVMS Module attribute. */
+ private int providesCount;
+
+ /** The binary content of the 'provides' array of the JVMS Module attribute. */
+ private final ByteVector provides;
+
+ /** The provides_count field of the JVMS ModulePackages attribute. */
+ private int packageCount;
+
+ /** The binary content of the 'package_index' array of the JVMS ModulePackages attribute. */
+ private final ByteVector packageIndex;
+
+ /** The main_class_index field of the JVMS ModuleMainClass attribute, or 0. */
+ private int mainClassIndex;
+
+ ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
+ super(Opcodes.ASM6);
+ this.symbolTable = symbolTable;
+ this.moduleNameIndex = name;
+ this.moduleFlags = access;
+ this.moduleVersionIndex = version;
+ this.requires = new ByteVector();
+ this.exports = new ByteVector();
+ this.opens = new ByteVector();
+ this.usesIndex = new ByteVector();
+ this.provides = new ByteVector();
+ this.packageIndex = new ByteVector();
+ }
+
+ @Override
+ public void visitMainClass(final String mainClass) {
+ this.mainClassIndex = symbolTable.addConstantClass(mainClass).index;
+ }
+
+ @Override
+ public void visitPackage(final String packaze) {
+ packageIndex.putShort(symbolTable.addConstantPackage(packaze).index);
+ packageCount++;
+ }
+
+ @Override
+ public void visitRequire(final String module, final int access, final String version) {
+ requires
+ .putShort(symbolTable.addConstantModule(module).index)
+ .putShort(access)
+ .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version));
+ requiresCount++;
+ }
+
+ @Override
+ public void visitExport(final String packaze, final int access, final String... modules) {
+ exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
+ if (modules == null) {
+ exports.putShort(0);
+ } else {
+ exports.putShort(modules.length);
+ for (String module : modules) {
+ exports.putShort(symbolTable.addConstantModule(module).index);
+ }
+ }
+ exportsCount++;
+ }
+
+ @Override
+ public void visitOpen(final String packaze, final int access, final String... modules) {
+ opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
+ if (modules == null) {
+ opens.putShort(0);
+ } else {
+ opens.putShort(modules.length);
+ for (String module : modules) {
+ opens.putShort(symbolTable.addConstantModule(module).index);
+ }
+ }
+ opensCount++;
+ }
+
+ @Override
+ public void visitUse(final String service) {
+ usesIndex.putShort(symbolTable.addConstantClass(service).index);
+ usesCount++;
+ }
+
+ @Override
+ public void visitProvide(final String service, final String... providers) {
+ provides.putShort(symbolTable.addConstantClass(service).index);
+ provides.putShort(providers.length);
+ for (String provider : providers) {
+ provides.putShort(symbolTable.addConstantClass(provider).index);
+ }
+ providesCount++;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
+
+ /**
+ * Returns the number of Module, ModulePackages and ModuleMainClass attributes generated by this
+ * ModuleWriter.
+ *
+ * @return the number of Module, ModulePackages and ModuleMainClass attributes (between 1 and 3).
+ */
+ int getAttributeCount() {
+ return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0);
+ }
+
+ /**
+ * Returns the size of the Module, ModulePackages and ModuleMainClass attributes generated by this
+ * ModuleWriter. Also add the names of these attributes in the constant pool.
+ *
+ * @return the size in bytes of the Module, ModulePackages and ModuleMainClass attributes.
+ */
+ int computeAttributesSize() {
+ symbolTable.addConstantUtf8(Constants.MODULE);
+ // 6 attribute header bytes, 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
+ int size =
+ 22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
+ if (packageCount > 0) {
+ symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES);
+ // 6 attribute header bytes, and 2 bytes for package_count.
+ size += 8 + packageIndex.length;
+ }
+ if (mainClassIndex > 0) {
+ symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS);
+ // 6 attribute header bytes, and 2 bytes for main_class_index.
+ size += 8;
+ }
+ return size;
+ }
+
+ /**
+ * Puts the Module, ModulePackages and ModuleMainClass attributes generated by this ModuleWriter
+ * in the given ByteVector.
+ *
+ * @param output where the attributes must be put.
+ */
+ void putAttributes(final ByteVector output) {
+ // 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
+ int moduleAttributeLength =
+ 16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.MODULE))
+ .putInt(moduleAttributeLength)
+ .putShort(moduleNameIndex)
+ .putShort(moduleFlags)
+ .putShort(moduleVersionIndex)
+ .putShort(requiresCount)
+ .putByteArray(requires.data, 0, requires.length)
+ .putShort(exportsCount)
+ .putByteArray(exports.data, 0, exports.length)
+ .putShort(opensCount)
+ .putByteArray(opens.data, 0, opens.length)
+ .putShort(usesCount)
+ .putByteArray(usesIndex.data, 0, usesIndex.length)
+ .putShort(providesCount)
+ .putByteArray(provides.data, 0, provides.length);
+ if (packageCount > 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES))
+ .putInt(2 + packageIndex.length)
+ .putShort(packageCount)
+ .putByteArray(packageIndex.data, 0, packageIndex.length);
+ }
+ if (mainClassIndex > 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS))
+ .putInt(2)
+ .putShort(mainClassIndex);
+ }
+ }
+}
diff --git a/src/jvm/clojure/asm/Opcodes.java b/src/jvm/clojure/asm/Opcodes.java
index b1268072f8..5694eb3aa0 100644
--- a/src/jvm/clojure/asm/Opcodes.java
+++ b/src/jvm/clojure/asm/Opcodes.java
@@ -1,358 +1,347 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
/**
- * Defines the JVM opcodes, access flags and array type codes. This interface
- * does not define all the JVM opcodes because some opcodes are automatically
- * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced
- * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n
- * opcodes are therefore not defined in this interface. Likewise for LDC,
- * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and
- * JSR_W.
+ * The JVM opcodes, access flags and array type codes. This interface does not define all the JVM
+ * opcodes because some opcodes are automatically handled. For example, the xLOAD and xSTORE opcodes
+ * are automatically replaced by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and
+ * xSTORE_n opcodes are therefore not defined in this interface. Likewise for LDC, automatically
+ * replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and JSR_W.
*
+ * @see JVMS 6
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public interface Opcodes {
- // ASM API versions
+ // ASM API versions.
+
+ int ASM4 = 4 << 16 | 0 << 8;
+ int ASM5 = 5 << 16 | 0 << 8;
+ int ASM6 = 6 << 16 | 0 << 8;
+
+ /**
+ * Experimental, use at your own risk. This field will be renamed when it becomes stable, this
+ * will break existing code using it.
+ *
+ * @deprecated This API is experimental.
+ */
+ @Deprecated int ASM7_EXPERIMENTAL = 1 << 24 | 7 << 16 | 0 << 8;
+
+ // Java ClassFile versions (the minor version is stored in the 16 most
+ // significant bits, and the
+ // major version in the 16 least significant bits).
- int ASM4 = 4 << 16 | 0 << 8 | 0;
+ int V1_1 = 3 << 16 | 45;
+ int V1_2 = 0 << 16 | 46;
+ int V1_3 = 0 << 16 | 47;
+ int V1_4 = 0 << 16 | 48;
+ int V1_5 = 0 << 16 | 49;
+ int V1_6 = 0 << 16 | 50;
+ int V1_7 = 0 << 16 | 51;
+ int V1_8 = 0 << 16 | 52;
+ int V9 = 0 << 16 | 53;
+ int V10 = 0 << 16 | 54;
+ int V11 = 0 << 16 | 55;
- // versions
+ /**
+ * Version flag indicating that the class is using 'preview' features.
+ *
+ *
{@code version & V_PREVIEW_EXPERIMENTAL == V_PREVIEW_EXPERIMENTAL} tests if a version is
+ * flagged with {@code V_PREVIEW_EXPERIMENTAL}.
+ *
+ * @deprecated This API is experimental.
+ */
+ @Deprecated int V_PREVIEW_EXPERIMENTAL = 0xFFFF0000;
- int V1_1 = 3 << 16 | 45;
- int V1_2 = 0 << 16 | 46;
- int V1_3 = 0 << 16 | 47;
- int V1_4 = 0 << 16 | 48;
- int V1_5 = 0 << 16 | 49;
- int V1_6 = 0 << 16 | 50;
- int V1_7 = 0 << 16 | 51;
+ // Access flags values, defined in
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.5-200-A.1
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.6-200-A.1
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.25
- // access flags
+ int ACC_PUBLIC = 0x0001; // class, field, method
+ int ACC_PRIVATE = 0x0002; // class, field, method
+ int ACC_PROTECTED = 0x0004; // class, field, method
+ int ACC_STATIC = 0x0008; // field, method
+ int ACC_FINAL = 0x0010; // class, field, method, parameter
+ int ACC_SUPER = 0x0020; // class
+ int ACC_SYNCHRONIZED = 0x0020; // method
+ int ACC_OPEN = 0x0020; // module
+ int ACC_TRANSITIVE = 0x0020; // module requires
+ int ACC_VOLATILE = 0x0040; // field
+ int ACC_BRIDGE = 0x0040; // method
+ int ACC_STATIC_PHASE = 0x0040; // module requires
+ int ACC_VARARGS = 0x0080; // method
+ int ACC_TRANSIENT = 0x0080; // field
+ int ACC_NATIVE = 0x0100; // method
+ int ACC_INTERFACE = 0x0200; // class
+ int ACC_ABSTRACT = 0x0400; // class, method
+ int ACC_STRICT = 0x0800; // method
+ int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module *
+ int ACC_ANNOTATION = 0x2000; // class
+ int ACC_ENUM = 0x4000; // class(?) field inner
+ int ACC_MANDATED = 0x8000; // parameter, module, module *
+ int ACC_MODULE = 0x8000; // class
- int ACC_PUBLIC = 0x0001; // class, field, method
- int ACC_PRIVATE = 0x0002; // class, field, method
- int ACC_PROTECTED = 0x0004; // class, field, method
- int ACC_STATIC = 0x0008; // field, method
- int ACC_FINAL = 0x0010; // class, field, method
- int ACC_SUPER = 0x0020; // class
- int ACC_SYNCHRONIZED = 0x0020; // method
- int ACC_VOLATILE = 0x0040; // field
- int ACC_BRIDGE = 0x0040; // method
- int ACC_VARARGS = 0x0080; // method
- int ACC_TRANSIENT = 0x0080; // field
- int ACC_NATIVE = 0x0100; // method
- int ACC_INTERFACE = 0x0200; // class
- int ACC_ABSTRACT = 0x0400; // class, method
- int ACC_STRICT = 0x0800; // method
- int ACC_SYNTHETIC = 0x1000; // class, field, method
- int ACC_ANNOTATION = 0x2000; // class
- int ACC_ENUM = 0x4000; // class(?) field inner
+ // ASM specific access flags.
+ // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard
+ // access flags, and also to make sure that these flags are automatically filtered out when
+ // written in class files (because access flags are stored using 16 bits only).
- // ASM specific pseudo access flags
+ int ACC_DEPRECATED = 0x20000; // class, field, method
- int ACC_DEPRECATED = 0x20000; // class, field, method
+ // Possible values for the type operand of the NEWARRAY instruction.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.newarray.
- // types for NEWARRAY
+ int T_BOOLEAN = 4;
+ int T_CHAR = 5;
+ int T_FLOAT = 6;
+ int T_DOUBLE = 7;
+ int T_BYTE = 8;
+ int T_SHORT = 9;
+ int T_INT = 10;
+ int T_LONG = 11;
- int T_BOOLEAN = 4;
- int T_CHAR = 5;
- int T_FLOAT = 6;
- int T_DOUBLE = 7;
- int T_BYTE = 8;
- int T_SHORT = 9;
- int T_INT = 10;
- int T_LONG = 11;
+ // Possible values for the reference_kind field of CONSTANT_MethodHandle_info structures.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4.8.
- // tags for Handle
+ int H_GETFIELD = 1;
+ int H_GETSTATIC = 2;
+ int H_PUTFIELD = 3;
+ int H_PUTSTATIC = 4;
+ int H_INVOKEVIRTUAL = 5;
+ int H_INVOKESTATIC = 6;
+ int H_INVOKESPECIAL = 7;
+ int H_NEWINVOKESPECIAL = 8;
+ int H_INVOKEINTERFACE = 9;
- int H_GETFIELD = 1;
- int H_GETSTATIC = 2;
- int H_PUTFIELD = 3;
- int H_PUTSTATIC = 4;
- int H_INVOKEVIRTUAL = 5;
- int H_INVOKESTATIC = 6;
- int H_INVOKESPECIAL = 7;
- int H_NEWINVOKESPECIAL = 8;
- int H_INVOKEINTERFACE = 9;
+ // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}.
- // stack map frame types
+ /** An expanded frame. See {@link ClassReader#EXPAND_FRAMES}. */
+ int F_NEW = -1;
- /**
- * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}.
- */
- int F_NEW = -1;
+ /** A compressed frame with complete frame data. */
+ int F_FULL = 0;
- /**
- * Represents a compressed frame with complete frame data.
- */
- int F_FULL = 0;
+ /**
+ * A compressed frame where locals are the same as the locals in the previous frame, except that
+ * additional 1-3 locals are defined, and with an empty stack.
+ */
+ int F_APPEND = 1;
- /**
- * Represents a compressed frame where locals are the same as the locals in
- * the previous frame, except that additional 1-3 locals are defined, and
- * with an empty stack.
- */
- int F_APPEND = 1;
+ /**
+ * A compressed frame where locals are the same as the locals in the previous frame, except that
+ * the last 1-3 locals are absent and with an empty stack.
+ */
+ int F_CHOP = 2;
- /**
- * Represents a compressed frame where locals are the same as the locals in
- * the previous frame, except that the last 1-3 locals are absent and with
- * an empty stack.
- */
- int F_CHOP = 2;
+ /**
+ * A compressed frame with exactly the same locals as the previous frame and with an empty stack.
+ */
+ int F_SAME = 3;
- /**
- * Represents a compressed frame with exactly the same locals as the
- * previous frame and with an empty stack.
- */
- int F_SAME = 3;
+ /**
+ * A compressed frame with exactly the same locals as the previous frame and with a single value
+ * on the stack.
+ */
+ int F_SAME1 = 4;
- /**
- * Represents a compressed frame with exactly the same locals as the
- * previous frame and with a single value on the stack.
- */
- int F_SAME1 = 4;
+ // Standard stack map frame element types, used in {@link ClassVisitor#visitFrame}.
- Integer TOP = new Integer(0);
- Integer INTEGER = new Integer(1);
- Integer FLOAT = new Integer(2);
- Integer DOUBLE = new Integer(3);
- Integer LONG = new Integer(4);
- Integer NULL = new Integer(5);
- Integer UNINITIALIZED_THIS = new Integer(6);
+ Integer TOP = Frame.ITEM_TOP;
+ Integer INTEGER = Frame.ITEM_INTEGER;
+ Integer FLOAT = Frame.ITEM_FLOAT;
+ Integer DOUBLE = Frame.ITEM_DOUBLE;
+ Integer LONG = Frame.ITEM_LONG;
+ Integer NULL = Frame.ITEM_NULL;
+ Integer UNINITIALIZED_THIS = Frame.ITEM_UNINITIALIZED_THIS;
- // opcodes // visit method (- = idem)
+ // The JVM opcode values (with the MethodVisitor method name used to visit them in comment, and
+ // where '-' means 'same method name as on the previous line').
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html.
- int NOP = 0; // visitInsn
- int ACONST_NULL = 1; // -
- int ICONST_M1 = 2; // -
- int ICONST_0 = 3; // -
- int ICONST_1 = 4; // -
- int ICONST_2 = 5; // -
- int ICONST_3 = 6; // -
- int ICONST_4 = 7; // -
- int ICONST_5 = 8; // -
- int LCONST_0 = 9; // -
- int LCONST_1 = 10; // -
- int FCONST_0 = 11; // -
- int FCONST_1 = 12; // -
- int FCONST_2 = 13; // -
- int DCONST_0 = 14; // -
- int DCONST_1 = 15; // -
- int BIPUSH = 16; // visitIntInsn
- int SIPUSH = 17; // -
- int LDC = 18; // visitLdcInsn
- // int LDC_W = 19; // -
- // int LDC2_W = 20; // -
- int ILOAD = 21; // visitVarInsn
- int LLOAD = 22; // -
- int FLOAD = 23; // -
- int DLOAD = 24; // -
- int ALOAD = 25; // -
- // int ILOAD_0 = 26; // -
- // int ILOAD_1 = 27; // -
- // int ILOAD_2 = 28; // -
- // int ILOAD_3 = 29; // -
- // int LLOAD_0 = 30; // -
- // int LLOAD_1 = 31; // -
- // int LLOAD_2 = 32; // -
- // int LLOAD_3 = 33; // -
- // int FLOAD_0 = 34; // -
- // int FLOAD_1 = 35; // -
- // int FLOAD_2 = 36; // -
- // int FLOAD_3 = 37; // -
- // int DLOAD_0 = 38; // -
- // int DLOAD_1 = 39; // -
- // int DLOAD_2 = 40; // -
- // int DLOAD_3 = 41; // -
- // int ALOAD_0 = 42; // -
- // int ALOAD_1 = 43; // -
- // int ALOAD_2 = 44; // -
- // int ALOAD_3 = 45; // -
- int IALOAD = 46; // visitInsn
- int LALOAD = 47; // -
- int FALOAD = 48; // -
- int DALOAD = 49; // -
- int AALOAD = 50; // -
- int BALOAD = 51; // -
- int CALOAD = 52; // -
- int SALOAD = 53; // -
- int ISTORE = 54; // visitVarInsn
- int LSTORE = 55; // -
- int FSTORE = 56; // -
- int DSTORE = 57; // -
- int ASTORE = 58; // -
- // int ISTORE_0 = 59; // -
- // int ISTORE_1 = 60; // -
- // int ISTORE_2 = 61; // -
- // int ISTORE_3 = 62; // -
- // int LSTORE_0 = 63; // -
- // int LSTORE_1 = 64; // -
- // int LSTORE_2 = 65; // -
- // int LSTORE_3 = 66; // -
- // int FSTORE_0 = 67; // -
- // int FSTORE_1 = 68; // -
- // int FSTORE_2 = 69; // -
- // int FSTORE_3 = 70; // -
- // int DSTORE_0 = 71; // -
- // int DSTORE_1 = 72; // -
- // int DSTORE_2 = 73; // -
- // int DSTORE_3 = 74; // -
- // int ASTORE_0 = 75; // -
- // int ASTORE_1 = 76; // -
- // int ASTORE_2 = 77; // -
- // int ASTORE_3 = 78; // -
- int IASTORE = 79; // visitInsn
- int LASTORE = 80; // -
- int FASTORE = 81; // -
- int DASTORE = 82; // -
- int AASTORE = 83; // -
- int BASTORE = 84; // -
- int CASTORE = 85; // -
- int SASTORE = 86; // -
- int POP = 87; // -
- int POP2 = 88; // -
- int DUP = 89; // -
- int DUP_X1 = 90; // -
- int DUP_X2 = 91; // -
- int DUP2 = 92; // -
- int DUP2_X1 = 93; // -
- int DUP2_X2 = 94; // -
- int SWAP = 95; // -
- int IADD = 96; // -
- int LADD = 97; // -
- int FADD = 98; // -
- int DADD = 99; // -
- int ISUB = 100; // -
- int LSUB = 101; // -
- int FSUB = 102; // -
- int DSUB = 103; // -
- int IMUL = 104; // -
- int LMUL = 105; // -
- int FMUL = 106; // -
- int DMUL = 107; // -
- int IDIV = 108; // -
- int LDIV = 109; // -
- int FDIV = 110; // -
- int DDIV = 111; // -
- int IREM = 112; // -
- int LREM = 113; // -
- int FREM = 114; // -
- int DREM = 115; // -
- int INEG = 116; // -
- int LNEG = 117; // -
- int FNEG = 118; // -
- int DNEG = 119; // -
- int ISHL = 120; // -
- int LSHL = 121; // -
- int ISHR = 122; // -
- int LSHR = 123; // -
- int IUSHR = 124; // -
- int LUSHR = 125; // -
- int IAND = 126; // -
- int LAND = 127; // -
- int IOR = 128; // -
- int LOR = 129; // -
- int IXOR = 130; // -
- int LXOR = 131; // -
- int IINC = 132; // visitIincInsn
- int I2L = 133; // visitInsn
- int I2F = 134; // -
- int I2D = 135; // -
- int L2I = 136; // -
- int L2F = 137; // -
- int L2D = 138; // -
- int F2I = 139; // -
- int F2L = 140; // -
- int F2D = 141; // -
- int D2I = 142; // -
- int D2L = 143; // -
- int D2F = 144; // -
- int I2B = 145; // -
- int I2C = 146; // -
- int I2S = 147; // -
- int LCMP = 148; // -
- int FCMPL = 149; // -
- int FCMPG = 150; // -
- int DCMPL = 151; // -
- int DCMPG = 152; // -
- int IFEQ = 153; // visitJumpInsn
- int IFNE = 154; // -
- int IFLT = 155; // -
- int IFGE = 156; // -
- int IFGT = 157; // -
- int IFLE = 158; // -
- int IF_ICMPEQ = 159; // -
- int IF_ICMPNE = 160; // -
- int IF_ICMPLT = 161; // -
- int IF_ICMPGE = 162; // -
- int IF_ICMPGT = 163; // -
- int IF_ICMPLE = 164; // -
- int IF_ACMPEQ = 165; // -
- int IF_ACMPNE = 166; // -
- int GOTO = 167; // -
- int JSR = 168; // -
- int RET = 169; // visitVarInsn
- int TABLESWITCH = 170; // visiTableSwitchInsn
- int LOOKUPSWITCH = 171; // visitLookupSwitch
- int IRETURN = 172; // visitInsn
- int LRETURN = 173; // -
- int FRETURN = 174; // -
- int DRETURN = 175; // -
- int ARETURN = 176; // -
- int RETURN = 177; // -
- int GETSTATIC = 178; // visitFieldInsn
- int PUTSTATIC = 179; // -
- int GETFIELD = 180; // -
- int PUTFIELD = 181; // -
- int INVOKEVIRTUAL = 182; // visitMethodInsn
- int INVOKESPECIAL = 183; // -
- int INVOKESTATIC = 184; // -
- int INVOKEINTERFACE = 185; // -
- int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn
- int NEW = 187; // visitTypeInsn
- int NEWARRAY = 188; // visitIntInsn
- int ANEWARRAY = 189; // visitTypeInsn
- int ARRAYLENGTH = 190; // visitInsn
- int ATHROW = 191; // -
- int CHECKCAST = 192; // visitTypeInsn
- int INSTANCEOF = 193; // -
- int MONITORENTER = 194; // visitInsn
- int MONITOREXIT = 195; // -
- // int WIDE = 196; // NOT VISITED
- int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn
- int IFNULL = 198; // visitJumpInsn
- int IFNONNULL = 199; // -
- // int GOTO_W = 200; // -
- // int JSR_W = 201; // -
+ int NOP = 0; // visitInsn
+ int ACONST_NULL = 1; // -
+ int ICONST_M1 = 2; // -
+ int ICONST_0 = 3; // -
+ int ICONST_1 = 4; // -
+ int ICONST_2 = 5; // -
+ int ICONST_3 = 6; // -
+ int ICONST_4 = 7; // -
+ int ICONST_5 = 8; // -
+ int LCONST_0 = 9; // -
+ int LCONST_1 = 10; // -
+ int FCONST_0 = 11; // -
+ int FCONST_1 = 12; // -
+ int FCONST_2 = 13; // -
+ int DCONST_0 = 14; // -
+ int DCONST_1 = 15; // -
+ int BIPUSH = 16; // visitIntInsn
+ int SIPUSH = 17; // -
+ int LDC = 18; // visitLdcInsn
+ int ILOAD = 21; // visitVarInsn
+ int LLOAD = 22; // -
+ int FLOAD = 23; // -
+ int DLOAD = 24; // -
+ int ALOAD = 25; // -
+ int IALOAD = 46; // visitInsn
+ int LALOAD = 47; // -
+ int FALOAD = 48; // -
+ int DALOAD = 49; // -
+ int AALOAD = 50; // -
+ int BALOAD = 51; // -
+ int CALOAD = 52; // -
+ int SALOAD = 53; // -
+ int ISTORE = 54; // visitVarInsn
+ int LSTORE = 55; // -
+ int FSTORE = 56; // -
+ int DSTORE = 57; // -
+ int ASTORE = 58; // -
+ int IASTORE = 79; // visitInsn
+ int LASTORE = 80; // -
+ int FASTORE = 81; // -
+ int DASTORE = 82; // -
+ int AASTORE = 83; // -
+ int BASTORE = 84; // -
+ int CASTORE = 85; // -
+ int SASTORE = 86; // -
+ int POP = 87; // -
+ int POP2 = 88; // -
+ int DUP = 89; // -
+ int DUP_X1 = 90; // -
+ int DUP_X2 = 91; // -
+ int DUP2 = 92; // -
+ int DUP2_X1 = 93; // -
+ int DUP2_X2 = 94; // -
+ int SWAP = 95; // -
+ int IADD = 96; // -
+ int LADD = 97; // -
+ int FADD = 98; // -
+ int DADD = 99; // -
+ int ISUB = 100; // -
+ int LSUB = 101; // -
+ int FSUB = 102; // -
+ int DSUB = 103; // -
+ int IMUL = 104; // -
+ int LMUL = 105; // -
+ int FMUL = 106; // -
+ int DMUL = 107; // -
+ int IDIV = 108; // -
+ int LDIV = 109; // -
+ int FDIV = 110; // -
+ int DDIV = 111; // -
+ int IREM = 112; // -
+ int LREM = 113; // -
+ int FREM = 114; // -
+ int DREM = 115; // -
+ int INEG = 116; // -
+ int LNEG = 117; // -
+ int FNEG = 118; // -
+ int DNEG = 119; // -
+ int ISHL = 120; // -
+ int LSHL = 121; // -
+ int ISHR = 122; // -
+ int LSHR = 123; // -
+ int IUSHR = 124; // -
+ int LUSHR = 125; // -
+ int IAND = 126; // -
+ int LAND = 127; // -
+ int IOR = 128; // -
+ int LOR = 129; // -
+ int IXOR = 130; // -
+ int LXOR = 131; // -
+ int IINC = 132; // visitIincInsn
+ int I2L = 133; // visitInsn
+ int I2F = 134; // -
+ int I2D = 135; // -
+ int L2I = 136; // -
+ int L2F = 137; // -
+ int L2D = 138; // -
+ int F2I = 139; // -
+ int F2L = 140; // -
+ int F2D = 141; // -
+ int D2I = 142; // -
+ int D2L = 143; // -
+ int D2F = 144; // -
+ int I2B = 145; // -
+ int I2C = 146; // -
+ int I2S = 147; // -
+ int LCMP = 148; // -
+ int FCMPL = 149; // -
+ int FCMPG = 150; // -
+ int DCMPL = 151; // -
+ int DCMPG = 152; // -
+ int IFEQ = 153; // visitJumpInsn
+ int IFNE = 154; // -
+ int IFLT = 155; // -
+ int IFGE = 156; // -
+ int IFGT = 157; // -
+ int IFLE = 158; // -
+ int IF_ICMPEQ = 159; // -
+ int IF_ICMPNE = 160; // -
+ int IF_ICMPLT = 161; // -
+ int IF_ICMPGE = 162; // -
+ int IF_ICMPGT = 163; // -
+ int IF_ICMPLE = 164; // -
+ int IF_ACMPEQ = 165; // -
+ int IF_ACMPNE = 166; // -
+ int GOTO = 167; // -
+ int JSR = 168; // -
+ int RET = 169; // visitVarInsn
+ int TABLESWITCH = 170; // visiTableSwitchInsn
+ int LOOKUPSWITCH = 171; // visitLookupSwitch
+ int IRETURN = 172; // visitInsn
+ int LRETURN = 173; // -
+ int FRETURN = 174; // -
+ int DRETURN = 175; // -
+ int ARETURN = 176; // -
+ int RETURN = 177; // -
+ int GETSTATIC = 178; // visitFieldInsn
+ int PUTSTATIC = 179; // -
+ int GETFIELD = 180; // -
+ int PUTFIELD = 181; // -
+ int INVOKEVIRTUAL = 182; // visitMethodInsn
+ int INVOKESPECIAL = 183; // -
+ int INVOKESTATIC = 184; // -
+ int INVOKEINTERFACE = 185; // -
+ int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn
+ int NEW = 187; // visitTypeInsn
+ int NEWARRAY = 188; // visitIntInsn
+ int ANEWARRAY = 189; // visitTypeInsn
+ int ARRAYLENGTH = 190; // visitInsn
+ int ATHROW = 191; // -
+ int CHECKCAST = 192; // visitTypeInsn
+ int INSTANCEOF = 193; // -
+ int MONITORENTER = 194; // visitInsn
+ int MONITOREXIT = 195; // -
+ int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn
+ int IFNULL = 198; // visitJumpInsn
+ int IFNONNULL = 199; // -
}
diff --git a/src/jvm/clojure/asm/Symbol.java b/src/jvm/clojure/asm/Symbol.java
new file mode 100644
index 0000000000..aa56a4d022
--- /dev/null
+++ b/src/jvm/clojure/asm/Symbol.java
@@ -0,0 +1,240 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+/**
+ * An entry of the constant pool, of the BootstrapMethods attribute, or of the (ASM specific) type
+ * table of a class.
+ *
+ * @see JVMS
+ * 4.4
+ * @see JVMS
+ * 4.7.23
+ * @author Eric Bruneton
+ */
+abstract class Symbol {
+
+ // Tag values for the constant pool entries (using the same order as in the JVMS).
+
+ /** The tag value of CONSTANT_Class_info JVMS structures. */
+ static final int CONSTANT_CLASS_TAG = 7;
+
+ /** The tag value of CONSTANT_Fieldref_info JVMS structures. */
+ static final int CONSTANT_FIELDREF_TAG = 9;
+
+ /** The tag value of CONSTANT_Methodref_info JVMS structures. */
+ static final int CONSTANT_METHODREF_TAG = 10;
+
+ /** The tag value of CONSTANT_InterfaceMethodref_info JVMS structures. */
+ static final int CONSTANT_INTERFACE_METHODREF_TAG = 11;
+
+ /** The tag value of CONSTANT_String_info JVMS structures. */
+ static final int CONSTANT_STRING_TAG = 8;
+
+ /** The tag value of CONSTANT_Integer_info JVMS structures. */
+ static final int CONSTANT_INTEGER_TAG = 3;
+
+ /** The tag value of CONSTANT_Float_info JVMS structures. */
+ static final int CONSTANT_FLOAT_TAG = 4;
+
+ /** The tag value of CONSTANT_Long_info JVMS structures. */
+ static final int CONSTANT_LONG_TAG = 5;
+
+ /** The tag value of CONSTANT_Double_info JVMS structures. */
+ static final int CONSTANT_DOUBLE_TAG = 6;
+
+ /** The tag value of CONSTANT_NameAndType_info JVMS structures. */
+ static final int CONSTANT_NAME_AND_TYPE_TAG = 12;
+
+ /** The tag value of CONSTANT_Utf8_info JVMS structures. */
+ static final int CONSTANT_UTF8_TAG = 1;
+
+ /** The tag value of CONSTANT_MethodHandle_info JVMS structures. */
+ static final int CONSTANT_METHOD_HANDLE_TAG = 15;
+
+ /** The tag value of CONSTANT_MethodType_info JVMS structures. */
+ static final int CONSTANT_METHOD_TYPE_TAG = 16;
+
+ /** The tag value of CONSTANT_Dynamic_info JVMS structures. */
+ static final int CONSTANT_DYNAMIC_TAG = 17;
+
+ /** The tag value of CONSTANT_InvokeDynamic_info JVMS structures. */
+ static final int CONSTANT_INVOKE_DYNAMIC_TAG = 18;
+
+ /** The tag value of CONSTANT_Module_info JVMS structures. */
+ static final int CONSTANT_MODULE_TAG = 19;
+
+ /** The tag value of CONSTANT_Package_info JVMS structures. */
+ static final int CONSTANT_PACKAGE_TAG = 20;
+
+ // Tag values for the BootstrapMethods attribute entries (ASM specific tag).
+
+ /** The tag value of the BootstrapMethods attribute entries. */
+ static final int BOOTSTRAP_METHOD_TAG = 64;
+
+ // Tag values for the type table entries (ASM specific tags).
+
+ /** The tag value of a normal type entry in the (ASM specific) type table of a class. */
+ static final int TYPE_TAG = 128;
+
+ /**
+ * The tag value of an {@link Frame#ITEM_UNINITIALIZED} type entry in the type table of a class.
+ */
+ static final int UNINITIALIZED_TYPE_TAG = 129;
+
+ /** The tag value of a merged type entry in the (ASM specific) type table of a class. */
+ static final int MERGED_TYPE_TAG = 130;
+
+ // Instance fields.
+
+ /**
+ * The index of this symbol in the constant pool, in the BootstrapMethods attribute, or in the
+ * (ASM specific) type table of a class (depending on the {@link #tag} value).
+ */
+ final int index;
+
+ /**
+ * A tag indicating the type of this symbol. Must be one of the static tag values defined in this
+ * class.
+ */
+ final int tag;
+
+ /**
+ * The internal name of the owner class of this symbol. Only used for {@link
+ * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link
+ * #CONSTANT_INTERFACE_METHODREF_TAG}, and {@link #CONSTANT_METHOD_HANDLE_TAG} symbols.
+ */
+ final String owner;
+
+ /**
+ * The name of the class field or method corresponding to this symbol. Only used for {@link
+ * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link
+ * #CONSTANT_INTERFACE_METHODREF_TAG}, {@link #CONSTANT_NAME_AND_TYPE_TAG}, {@link
+ * #CONSTANT_METHOD_HANDLE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols.
+ */
+ final String name;
+
+ /**
+ * The string value of this symbol. This is:
+ *
+ *
+ *
a field or method descriptor for {@link #CONSTANT_FIELDREF_TAG}, {@link
+ * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG}, {@link
+ * #CONSTANT_NAME_AND_TYPE_TAG}, {@link #CONSTANT_METHOD_HANDLE_TAG}, {@link
+ * #CONSTANT_METHOD_TYPE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols,
+ *
an arbitrary string for {@link #CONSTANT_UTF8_TAG} and {@link #CONSTANT_STRING_TAG}
+ * symbols,
+ *
an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG} and {@link
+ * #UNINITIALIZED_TYPE_TAG} symbols,
+ *
null for the other types of symbol.
+ *
+ */
+ final String value;
+
+ /**
+ * The numeric value of this symbol. This is:
+ *
+ *
+ *
the symbol's value for {@link #CONSTANT_INTEGER_TAG},{@link #CONSTANT_FLOAT_TAG}, {@link
+ * #CONSTANT_LONG_TAG}, {@link #CONSTANT_DOUBLE_TAG},
+ *
the CONSTANT_MethodHandle_info reference_kind field value for {@link
+ * #CONSTANT_METHOD_HANDLE_TAG} symbols,
+ *
the CONSTANT_InvokeDynamic_info bootstrap_method_attr_index field value for {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols,
+ *
the offset of a bootstrap method in the BootstrapMethods boostrap_methods array, for
+ * {@link #CONSTANT_DYNAMIC_TAG} or {@link #BOOTSTRAP_METHOD_TAG} symbols,
+ *
the bytecode offset of the NEW instruction that created an {@link
+ * Frame#ITEM_UNINITIALIZED} type for {@link #UNINITIALIZED_TYPE_TAG} symbols,
+ *
the indices (in the class' type table) of two {@link #TYPE_TAG} source types for {@link
+ * #MERGED_TYPE_TAG} symbols,
+ *
0 for the other types of symbol.
+ *
+ */
+ final long data;
+
+ /**
+ * Additional information about this symbol, generally computed lazily. Warning: the value of
+ * this field is ignored when comparing Symbol instances (to avoid duplicate entries in a
+ * SymbolTable). Therefore, this field should only contain data that can be computed from the
+ * other fields of this class. It contains:
+ *
+ *
+ *
the {@link Type#getArgumentsAndReturnSizes} of the symbol's method descriptor for {@link
+ * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols,
+ *
the index in the InnerClasses_attribute 'classes' array (plus one) corresponding to this
+ * class, for {@link #CONSTANT_CLASS_TAG} symbols,
+ *
the index (in the class' type table) of the merged type of the two source types for
+ * {@link #MERGED_TYPE_TAG} symbols,
+ *
0 for the other types of symbol, or if this field has not been computed yet.
+ *
+ */
+ int info;
+
+ /**
+ * Constructs a new Symbol. This constructor can't be used directly because the Symbol class is
+ * abstract. Instead, use the factory methods of the {@link SymbolTable} class.
+ *
+ * @param index the symbol index in the constant pool, in the BootstrapMethods attribute, or in
+ * the (ASM specific) type table of a class (depending on 'tag').
+ * @param tag the symbol type. Must be one of the static tag values defined in this class.
+ * @param owner The internal name of the symbol's owner class. Maybe null.
+ * @param name The name of the symbol's corresponding class field or method. Maybe null.
+ * @param value The string value of this symbol. Maybe null.
+ * @param data The numeric value of this symbol.
+ */
+ Symbol(
+ final int index,
+ final int tag,
+ final String owner,
+ final String name,
+ final String value,
+ final long data) {
+ this.index = index;
+ this.tag = tag;
+ this.owner = owner;
+ this.name = name;
+ this.value = value;
+ this.data = data;
+ }
+
+ /**
+ * @return the result {@link Type#getArgumentsAndReturnSizes} on {@link #value} (memoized in
+ * {@link #info} for efficiency). This should only be used for {@link
+ * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols.
+ */
+ int getArgumentsAndReturnSizes() {
+ if (info == 0) {
+ info = Type.getArgumentsAndReturnSizes(value);
+ }
+ return info;
+ }
+}
diff --git a/src/jvm/clojure/asm/SymbolTable.java b/src/jvm/clojure/asm/SymbolTable.java
new file mode 100644
index 0000000000..30caf77af8
--- /dev/null
+++ b/src/jvm/clojure/asm/SymbolTable.java
@@ -0,0 +1,1277 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+/**
+ * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type
+ * table entries of a class.
+ *
+ * @see JVMS
+ * 4.4
+ * @see JVMS
+ * 4.7.23
+ * @author Eric Bruneton
+ */
+final class SymbolTable {
+
+ /**
+ * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields
+ * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid
+ * duplicate symbols). See {@link #entries}.
+ *
+ * @author Eric Bruneton
+ */
+ private static class Entry extends Symbol {
+
+ /** The hash code of this entry. */
+ final int hashCode;
+
+ /**
+ * Another entry (and so on recursively) having the same hash code (modulo the size of {@link
+ * #entries}) as this one.
+ */
+ Entry next;
+
+ Entry(
+ final int index,
+ final int tag,
+ final String owner,
+ final String name,
+ final String value,
+ final long data,
+ final int hashCode) {
+ super(index, tag, owner, name, value, data);
+ this.hashCode = hashCode;
+ }
+
+ Entry(final int index, final int tag, final String value, final int hashCode) {
+ super(index, tag, /* owner = */ null, /* name = */ null, value, /* data = */ 0);
+ this.hashCode = hashCode;
+ }
+
+ Entry(final int index, final int tag, final String value, final long data, final int hashCode) {
+ super(index, tag, /* owner = */ null, /* name = */ null, value, data);
+ this.hashCode = hashCode;
+ }
+
+ Entry(
+ final int index, final int tag, final String name, final String value, final int hashCode) {
+ super(index, tag, /* owner = */ null, name, value, /* data = */ 0);
+ this.hashCode = hashCode;
+ }
+
+ Entry(final int index, final int tag, final long data, final int hashCode) {
+ super(index, tag, /* owner = */ null, /* name = */ null, /* value = */ null, data);
+ this.hashCode = hashCode;
+ }
+ }
+
+ /**
+ * The ClassWriter to which this SymbolTable belongs. This is only used to get access to {@link
+ * ClassWriter#getCommonSuperClass} and to serialize custom attributes with {@link
+ * Attribute#write}.
+ */
+ final ClassWriter classWriter;
+
+ /**
+ * The ClassReader from which this SymbolTable was constructed, or null if it was
+ * constructed from scratch.
+ */
+ private final ClassReader sourceClassReader;
+
+ /** The major version number of the class to which this symbol table belongs. */
+ private int majorVersion;
+
+ /** The internal name of the class to which this symbol table belongs. */
+ private String className;
+
+ /**
+ * The total number of {@link Entry} instances in {@link #entries}. This includes entries that are
+ * accessible (recursively) via {@link Entry#next}.
+ */
+ private int entryCount;
+
+ /**
+ * A hash set of all the entries in this SymbolTable (this includes the constant pool entries, the
+ * bootstrap method entries and the type table entries). Each {@link Entry} instance is stored at
+ * the array index given by its hash code modulo the array size. If several entries must be stored
+ * at the same array index, they are linked together via their {@link Entry#next} field. The
+ * factory methods of this class make sure that this table does not contain duplicated entries.
+ */
+ private Entry[] entries;
+
+ /**
+ * The number of constant pool items in {@link #constantPool}, plus 1. The first constant pool
+ * item has index 1, and long and double items count for two items.
+ */
+ private int constantPoolCount;
+
+ /**
+ * The content of the ClassFile's constant_pool JVMS structure corresponding to this SymbolTable.
+ * The ClassFile's constant_pool_count field is not included.
+ */
+ private ByteVector constantPool;
+
+ /**
+ * The number of bootstrap methods in {@link #bootstrapMethods}. Corresponds to the
+ * BootstrapMethods_attribute's num_bootstrap_methods field value.
+ */
+ private int bootstrapMethodCount;
+
+ /**
+ * The content of the BootstrapMethods attribute 'bootstrap_methods' array corresponding to this
+ * SymbolTable. Note that the first 6 bytes of the BootstrapMethods_attribute, and its
+ * num_bootstrap_methods field, are not included.
+ */
+ private ByteVector bootstrapMethods;
+
+ /**
+ * The actual number of elements in {@link #typeTable}. These elements are stored from index 0 to
+ * typeCount (excluded). The other array entries are empty.
+ */
+ private int typeCount;
+
+ /**
+ * An ASM specific type table used to temporarily store internal names that will not necessarily
+ * be stored in the constant pool. This type table is used by the control flow and data flow
+ * analysis algorithm used to compute stack map frames from scratch. This array stores {@link
+ * Symbol#TYPE_TAG} and {@link Symbol#UNINITIALIZED_TYPE_TAG}) Symbol. The type symbol at index
+ * i has its {@link Symbol#index} equal to i (and vice versa).
+ */
+ private Entry[] typeTable;
+
+ /**
+ * Constructs a new, empty SymbolTable for the given ClassWriter.
+ *
+ * @param classWriter a ClassWriter.
+ */
+ SymbolTable(final ClassWriter classWriter) {
+ this.classWriter = classWriter;
+ this.sourceClassReader = null;
+ this.entries = new Entry[256];
+ this.constantPoolCount = 1;
+ this.constantPool = new ByteVector();
+ }
+
+ /**
+ * Constructs a new SymbolTable for the given ClassWriter, initialized with the constant pool and
+ * bootstrap methods of the given ClassReader.
+ *
+ * @param classWriter a ClassWriter.
+ * @param classReader the ClassReader whose constant pool and bootstrap methods must be copied to
+ * initialize the SymbolTable.
+ */
+ SymbolTable(final ClassWriter classWriter, final ClassReader classReader) {
+ this.classWriter = classWriter;
+ this.sourceClassReader = classReader;
+
+ // Copy the constant pool binary content.
+ byte[] inputBytes = classReader.b;
+ int constantPoolOffset = classReader.getItem(1) - 1;
+ int constantPoolLength = classReader.header - constantPoolOffset;
+ constantPoolCount = classReader.getItemCount();
+ constantPool = new ByteVector(constantPoolLength);
+ constantPool.putByteArray(inputBytes, constantPoolOffset, constantPoolLength);
+
+ // Add the constant pool items in the symbol table entries. Reserve enough space in 'entries' to
+ // avoid too many hash set collisions (entries is not dynamically resized by the addConstant*
+ // method calls below), and to account for bootstrap method entries.
+ entries = new Entry[constantPoolCount * 2];
+ char[] charBuffer = new char[classReader.getMaxStringLength()];
+ int itemIndex = 1;
+ while (itemIndex < constantPoolCount) {
+ int itemOffset = classReader.getItem(itemIndex);
+ int itemTag = inputBytes[itemOffset - 1];
+ int nameAndTypeItemOffset;
+ switch (itemTag) {
+ case Symbol.CONSTANT_FIELDREF_TAG:
+ case Symbol.CONSTANT_METHODREF_TAG:
+ case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
+ nameAndTypeItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
+ addConstantMemberReference(
+ itemIndex,
+ itemTag,
+ classReader.readClass(itemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
+ break;
+ case Symbol.CONSTANT_INTEGER_TAG:
+ case Symbol.CONSTANT_FLOAT_TAG:
+ addConstantInteger(itemIndex, itemTag, classReader.readInt(itemOffset));
+ break;
+ case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
+ addConstantNameAndType(
+ itemIndex,
+ classReader.readUTF8(itemOffset, charBuffer),
+ classReader.readUTF8(itemOffset + 2, charBuffer));
+ break;
+ case Symbol.CONSTANT_LONG_TAG:
+ case Symbol.CONSTANT_DOUBLE_TAG:
+ addConstantLong(itemIndex, itemTag, classReader.readLong(itemOffset));
+ break;
+ case Symbol.CONSTANT_UTF8_TAG:
+ addConstantUtf8(itemIndex, classReader.readUTF(itemIndex, charBuffer));
+ break;
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ int memberRefItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 1));
+ nameAndTypeItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2));
+ addConstantMethodHandle(
+ itemIndex,
+ classReader.readByte(itemOffset),
+ classReader.readClass(memberRefItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
+ break;
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
+ nameAndTypeItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
+ addConstantDynamicOrInvokeDynamicReference(
+ itemTag,
+ itemIndex,
+ classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer),
+ classReader.readUnsignedShort(itemOffset));
+ break;
+ case Symbol.CONSTANT_STRING_TAG:
+ case Symbol.CONSTANT_CLASS_TAG:
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ case Symbol.CONSTANT_MODULE_TAG:
+ case Symbol.CONSTANT_PACKAGE_TAG:
+ addConstantUtf8Reference(
+ itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer));
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ itemIndex +=
+ (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1;
+ }
+
+ // Copy the BootstrapMethods 'bootstrap_methods' array binary content, if any.
+ int currentAttributeOffset = classReader.getFirstAttributeOffset();
+ for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
+ String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer);
+ if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ bootstrapMethodCount = classReader.readUnsignedShort(currentAttributeOffset + 6);
+ break;
+ }
+ currentAttributeOffset += 6 + classReader.readInt(currentAttributeOffset + 2);
+ }
+ if (bootstrapMethodCount > 0) {
+ // Compute the offset and the length of the BootstrapMethods 'bootstrap_methods' array.
+ int bootstrapMethodsOffset = currentAttributeOffset + 8;
+ int bootstrapMethodsLength = classReader.readInt(currentAttributeOffset + 2) - 2;
+ bootstrapMethods = new ByteVector(bootstrapMethodsLength);
+ bootstrapMethods.putByteArray(inputBytes, bootstrapMethodsOffset, bootstrapMethodsLength);
+
+ // Add each bootstrap method in the symbol table entries.
+ int currentOffset = bootstrapMethodsOffset;
+ for (int i = 0; i < bootstrapMethodCount; i++) {
+ int offset = currentOffset - bootstrapMethodsOffset;
+ int bootstrapMethodRef = classReader.readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ int numBootstrapArguments = classReader.readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ int hashCode = classReader.readConst(bootstrapMethodRef, charBuffer).hashCode();
+ while (numBootstrapArguments-- > 0) {
+ int bootstrapArgument = classReader.readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ hashCode ^= classReader.readConst(bootstrapArgument, charBuffer).hashCode();
+ }
+ add(new Entry(i, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode & 0x7FFFFFFF));
+ }
+ }
+ }
+
+ /**
+ * @return the ClassReader from which this SymbolTable was constructed, or null if it was
+ * constructed from scratch.
+ */
+ ClassReader getSource() {
+ return sourceClassReader;
+ }
+
+ /** @return the major version of the class to which this symbol table belongs. */
+ int getMajorVersion() {
+ return majorVersion;
+ }
+
+ /** @return the internal name of the class to which this symbol table belongs. */
+ String getClassName() {
+ return className;
+ }
+
+ /**
+ * Sets the major version and the name of the class to which this symbol table belongs. Also adds
+ * the class name to the constant pool.
+ *
+ * @param majorVersion a major ClassFile version number.
+ * @param className an internal class name.
+ * @return the constant pool index of a new or already existing Symbol with the given class name.
+ */
+ int setMajorVersionAndClassName(final int majorVersion, final String className) {
+ this.majorVersion = majorVersion;
+ this.className = className;
+ return addConstantClass(className).index;
+ }
+
+ /** @return the number of items in this symbol table's constant_pool array (plus 1). */
+ int getConstantPoolCount() {
+ return constantPoolCount;
+ }
+
+ /** @return the length in bytes of this symbol table's constant_pool array. */
+ int getConstantPoolLength() {
+ return constantPool.length;
+ }
+
+ /**
+ * Puts this symbol table's constant_pool array in the given ByteVector, preceded by the
+ * constant_pool_count value.
+ *
+ * @param output where the JVMS ClassFile's constant_pool array must be put.
+ */
+ void putConstantPool(final ByteVector output) {
+ output.putShort(constantPoolCount).putByteArray(constantPool.data, 0, constantPool.length);
+ }
+
+ /**
+ * Returns the size in bytes of this symbol table's BootstrapMethods attribute. Also adds the
+ * attribute name in the constant pool.
+ *
+ * @return the size in bytes of this symbol table's BootstrapMethods attribute.
+ */
+ int computeBootstrapMethodsSize() {
+ if (bootstrapMethods != null) {
+ addConstantUtf8(Constants.BOOTSTRAP_METHODS);
+ return 8 + bootstrapMethods.length;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Puts this symbol table's BootstrapMethods attribute in the given ByteVector. This includes the
+ * 6 attribute header bytes and the num_bootstrap_methods value.
+ *
+ * @param output where the JVMS BootstrapMethods attribute must be put.
+ */
+ void putBootstrapMethods(final ByteVector output) {
+ if (bootstrapMethods != null) {
+ output
+ .putShort(addConstantUtf8(Constants.BOOTSTRAP_METHODS))
+ .putInt(bootstrapMethods.length + 2)
+ .putShort(bootstrapMethodCount)
+ .putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Generic symbol table entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * @param hashCode a {@link Entry#hashCode} value.
+ * @return the list of entries which can potentially have the given hash code. The list is stored
+ * via the {@link Entry#next} field.
+ */
+ private Entry get(final int hashCode) {
+ return entries[hashCode % entries.length];
+ }
+
+ /**
+ * Puts the given entry in the {@link #entries} hash set. This method does not check
+ * whether {@link #entries} already contains a similar entry or not. {@link #entries} is resized
+ * if necessary to avoid hash collisions (multiple entries needing to be stored at the same {@link
+ * #entries} array index) as much as possible, with reasonable memory usage.
+ *
+ * @param entry an Entry (which must not already be contained in {@link #entries}).
+ * @return the given entry
+ */
+ private Entry put(final Entry entry) {
+ if (entryCount > (entries.length * 3) / 4) {
+ int currentCapacity = entries.length;
+ int newCapacity = currentCapacity * 2 + 1;
+ Entry[] newEntries = new Entry[newCapacity];
+ for (int i = currentCapacity - 1; i >= 0; --i) {
+ Entry currentEntry = entries[i];
+ while (currentEntry != null) {
+ int newCurrentEntryIndex = currentEntry.hashCode % newCapacity;
+ Entry nextEntry = currentEntry.next;
+ currentEntry.next = newEntries[newCurrentEntryIndex];
+ newEntries[newCurrentEntryIndex] = currentEntry;
+ currentEntry = nextEntry;
+ }
+ }
+ entries = newEntries;
+ }
+ entryCount++;
+ int index = entry.hashCode % entries.length;
+ entry.next = entries[index];
+ return entries[index] = entry;
+ }
+
+ /**
+ * Adds the given entry in the {@link #entries} hash set. This method does not check
+ * whether {@link #entries} already contains a similar entry or not, and does not resize
+ * {@link #entries} if necessary.
+ *
+ * @param entry an Entry (which must not already be contained in {@link #entries}).
+ */
+ private void add(final Entry entry) {
+ entryCount++;
+ int index = entry.hashCode % entries.length;
+ entry.next = entries[index];
+ entries[index] = entry;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Constant pool entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a number or string constant to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value the value of the constant to be added to the constant pool. This parameter must be
+ * an {@link Integer}, {@link Byte}, {@link Character}, {@link Short}, {@link Boolean}, {@link
+ * Float}, {@link Long}, {@link Double}, {@link String}, {@link Type} or {@link Handle}.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstant(final Object value) {
+ if (value instanceof Integer) {
+ return addConstantInteger(((Integer) value).intValue());
+ } else if (value instanceof Byte) {
+ return addConstantInteger(((Byte) value).intValue());
+ } else if (value instanceof Character) {
+ return addConstantInteger(((Character) value).charValue());
+ } else if (value instanceof Short) {
+ return addConstantInteger(((Short) value).intValue());
+ } else if (value instanceof Boolean) {
+ return addConstantInteger(((Boolean) value).booleanValue() ? 1 : 0);
+ } else if (value instanceof Float) {
+ return addConstantFloat(((Float) value).floatValue());
+ } else if (value instanceof Long) {
+ return addConstantLong(((Long) value).longValue());
+ } else if (value instanceof Double) {
+ return addConstantDouble(((Double) value).doubleValue());
+ } else if (value instanceof String) {
+ return addConstantString((String) value);
+ } else if (value instanceof Type) {
+ Type type = (Type) value;
+ int typeSort = type.getSort();
+ if (typeSort == Type.OBJECT) {
+ return addConstantClass(type.getInternalName());
+ } else if (typeSort == Type.METHOD) {
+ return addConstantMethodType(type.getDescriptor());
+ } else { // type is a primitive or array type.
+ return addConstantClass(type.getDescriptor());
+ }
+ } else if (value instanceof Handle) {
+ Handle handle = (Handle) value;
+ return addConstantMethodHandle(
+ handle.getTag(),
+ handle.getOwner(),
+ handle.getName(),
+ handle.getDesc(),
+ handle.isInterface());
+ } else if (value instanceof ConstantDynamic) {
+ ConstantDynamic constantDynamic = (ConstantDynamic) value;
+ return addConstantDynamic(
+ constantDynamic.getName(),
+ constantDynamic.getDescriptor(),
+ constantDynamic.getBootstrapMethod(),
+ constantDynamic.getBootstrapMethodArguments());
+ } else {
+ throw new IllegalArgumentException("value " + value);
+ }
+ }
+
+ /**
+ * Adds a CONSTANT_Class_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value the internal name of a class.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantClass(final String value) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_CLASS_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Fieldref_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param owner the internal name of a class.
+ * @param name a field name.
+ * @param descriptor a field descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantFieldref(final String owner, final String name, final String descriptor) {
+ return addConstantMemberReference(Symbol.CONSTANT_FIELDREF_TAG, owner, name, descriptor);
+ }
+
+ /**
+ * Adds a CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to the constant pool of this
+ * symbol table. Does nothing if the constant pool already contains a similar item.
+ *
+ * @param owner the internal name of a class.
+ * @param name a method name.
+ * @param descriptor a method descriptor.
+ * @param isInterface whether owner is an interface or not.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantMethodref(
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
+ int tag = isInterface ? Symbol.CONSTANT_INTERFACE_METHODREF_TAG : Symbol.CONSTANT_METHODREF_TAG;
+ return addConstantMemberReference(tag, owner, name, descriptor);
+ }
+
+ /**
+ * Adds a CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to
+ * the constant pool of this symbol table. Does nothing if the constant pool already contains a
+ * similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG}
+ * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}.
+ * @param owner the internal name of a class.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Entry addConstantMemberReference(
+ final int tag, final String owner, final String name, final String descriptor) {
+ int hashCode = hash(tag, owner, name, descriptor);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.owner.equals(owner)
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.put122(
+ tag, addConstantClass(owner).index, addConstantNameAndType(name, descriptor));
+ return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, 0, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info
+ * to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG}
+ * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}.
+ * @param owner the internal name of a class.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ */
+ private void addConstantMemberReference(
+ final int index,
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor) {
+ add(new Entry(index, tag, owner, name, descriptor, 0, hash(tag, owner, name, descriptor)));
+ }
+
+ /**
+ * Adds a CONSTANT_String_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a string.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantString(final String value) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_STRING_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Integer_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value an int.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantInteger(final int value) {
+ return addConstantInteger(Symbol.CONSTANT_INTEGER_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Float_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a float.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantFloat(final float value) {
+ return addConstantInteger(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value));
+ }
+
+ /**
+ * Adds a CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol table.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}.
+ * @param value an int or float.
+ * @return a constant pool constant with the given tag and primitive values.
+ */
+ private Symbol addConstantInteger(final int tag, final int value) {
+ int hashCode = hash(tag, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.putByte(tag).putInt(value);
+ return put(new Entry(constantPoolCount++, tag, value, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol
+ * table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}.
+ * @param value an int or float.
+ */
+ private void addConstantInteger(final int index, final int tag, final int value) {
+ add(new Entry(index, tag, value, hash(tag, value)));
+ }
+
+ /**
+ * Adds a CONSTANT_Long_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a long.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantLong(final long value) {
+ return addConstantLong(Symbol.CONSTANT_LONG_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Double_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a double.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantDouble(final double value) {
+ return addConstantLong(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value));
+ }
+
+ /**
+ * Adds a CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol table.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}.
+ * @param value a long or double.
+ * @return a constant pool constant with the given tag and primitive values.
+ */
+ private Symbol addConstantLong(final int tag, final long value) {
+ int hashCode = hash(tag, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ int index = constantPoolCount;
+ constantPool.putByte(tag).putLong(value);
+ constantPoolCount += 2;
+ return put(new Entry(index, tag, value, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Double_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}.
+ * @param value a long or double.
+ */
+ private void addConstantLong(final int index, final int tag, final long value) {
+ add(new Entry(index, tag, value, hash(tag, value)));
+ }
+
+ /**
+ * Adds a CONSTANT_NameAndType_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ int addConstantNameAndType(final String name, final String descriptor) {
+ final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG;
+ int hashCode = hash(tag, name, descriptor);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ constantPool.put122(tag, addConstantUtf8(name), addConstantUtf8(descriptor));
+ return put(new Entry(constantPoolCount++, tag, name, descriptor, hashCode)).index;
+ }
+
+ /**
+ * Adds a new CONSTANT_NameAndType_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ */
+ private void addConstantNameAndType(final int index, final String name, final String descriptor) {
+ final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG;
+ add(new Entry(index, tag, name, descriptor, hash(tag, name, descriptor)));
+ }
+
+ /**
+ * Adds a CONSTANT_Utf8_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a string.
+ * @return a new or already existing Symbol with the given value.
+ */
+ int addConstantUtf8(final String value) {
+ int hashCode = hash(Symbol.CONSTANT_UTF8_TAG, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.CONSTANT_UTF8_TAG
+ && entry.hashCode == hashCode
+ && entry.value.equals(value)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ constantPool.putByte(Symbol.CONSTANT_UTF8_TAG).putUTF8(value);
+ return put(new Entry(constantPoolCount++, Symbol.CONSTANT_UTF8_TAG, value, hashCode)).index;
+ }
+
+ /**
+ * Adds a new CONSTANT_String_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param value a string.
+ */
+ private void addConstantUtf8(final int index, final String value) {
+ add(new Entry(index, Symbol.CONSTANT_UTF8_TAG, value, hash(Symbol.CONSTANT_UTF8_TAG, value)));
+ }
+
+ /**
+ * Adds a CONSTANT_MethodHandle_info to the constant pool of this symbol table. Does nothing if
+ * the constant pool already contains a similar item.
+ *
+ * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link
+ * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
+ * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
+ * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of a class of interface.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ * @param isInterface whether owner is an interface or not.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantMethodHandle(
+ final int referenceKind,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG;
+ // Note that we don't need to include isInterface in the hash computation, because it is
+ // redundant with owner (we can't have the same owner with different isInterface values).
+ int hashCode = hash(tag, owner, name, descriptor, referenceKind);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.data == referenceKind
+ && entry.owner.equals(owner)
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ if (referenceKind <= Opcodes.H_PUTSTATIC) {
+ constantPool.put112(tag, referenceKind, addConstantFieldref(owner, name, descriptor).index);
+ } else {
+ constantPool.put112(
+ tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index);
+ }
+ return put(
+ new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_MethodHandle_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link
+ * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
+ * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
+ * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of a class of interface.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ */
+ private void addConstantMethodHandle(
+ final int index,
+ final int referenceKind,
+ final String owner,
+ final String name,
+ final String descriptor) {
+ final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG;
+ int hashCode = hash(tag, owner, name, descriptor, referenceKind);
+ add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode));
+ }
+
+ /**
+ * Adds a CONSTANT_MethodType_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantMethodType(final String methodDescriptor) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_METHOD_TYPE_TAG, methodDescriptor);
+ }
+
+ /**
+ * Adds a CONSTANT_Dynamic_info to the constant pool of this symbol table. Also adds the related
+ * bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the constant
+ * pool already contains a similar item.
+ *
+ * @param name a method name.
+ * @param descriptor a field descriptor.
+ * @param bootstrapMethodHandle a bootstrap method handle.
+ * @param bootstrapMethodArguments the bootstrap method arguments.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments);
+ return addConstantDynamicOrInvokeDynamicReference(
+ Symbol.CONSTANT_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
+ }
+
+ /**
+ * Adds a CONSTANT_InvokeDynamic_info to the constant pool of this symbol table. Also adds the
+ * related bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param name a method name.
+ * @param descriptor a method descriptor.
+ * @param bootstrapMethodHandle a bootstrap method handle.
+ * @param bootstrapMethodArguments the bootstrap method arguments.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantInvokeDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments);
+ return addConstantDynamicOrInvokeDynamicReference(
+ Symbol.CONSTANT_INVOKE_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
+ }
+
+ /**
+ * Adds a CONSTANT_Dynamic or a CONSTANT_InvokeDynamic_info to the constant pool of this symbol
+ * table. Does nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link
+ * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}.
+ * @param name a method name.
+ * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG) or a method descriptor for
+ * CONSTANT_INVOKE_DYNAMIC_TAG.
+ * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Symbol addConstantDynamicOrInvokeDynamicReference(
+ final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) {
+ int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.data == bootstrapMethodIndex
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor));
+ return put(
+ new Entry(
+ constantPoolCount++, tag, null, name, descriptor, bootstrapMethodIndex, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Dynamic_info or CONSTANT_InvokeDynamic_info to the constant pool of this
+ * symbol table.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link
+ * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}.
+ * @param index the constant pool index of the new Symbol.
+ * @param name a method name.
+ * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG or a method descriptor for
+ * CONSTANT_INVOKE_DYNAMIC_TAG.
+ * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute.
+ */
+ private void addConstantDynamicOrInvokeDynamicReference(
+ final int tag,
+ final int index,
+ final String name,
+ final String descriptor,
+ final int bootstrapMethodIndex) {
+ int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex);
+ add(new Entry(index, tag, null, name, descriptor, bootstrapMethodIndex, hashCode));
+ }
+
+ /**
+ * Adds a CONSTANT_Module_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param moduleName a fully qualified name (using dots) of a module.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantModule(final String moduleName) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_MODULE_TAG, moduleName);
+ }
+
+ /**
+ * Adds a CONSTANT_Package_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param packageName the internal name of a package.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantPackage(final String packageName) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_PACKAGE_TAG, packageName);
+ }
+
+ /**
+ * Adds a CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info,
+ * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. Does
+ * nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link
+ * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link
+ * Symbol#CONSTANT_PACKAGE_TAG}.
+ * @param value an internal class name, an arbitrary string, a method descriptor, a module or a
+ * package name, depending on tag.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Symbol addConstantUtf8Reference(final int tag, final String value) {
+ int hashCode = hash(tag, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag && entry.hashCode == hashCode && entry.value.equals(value)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.put12(tag, addConstantUtf8(value));
+ return put(new Entry(constantPoolCount++, tag, value, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info,
+ * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link
+ * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link
+ * Symbol#CONSTANT_PACKAGE_TAG}.
+ * @param value an internal class name, an arbitrary string, a method descriptor, a module or a
+ * package name, depending on tag.
+ */
+ private void addConstantUtf8Reference(final int index, final int tag, final String value) {
+ add(new Entry(index, tag, value, hash(tag, value)));
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Bootstrap method entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if
+ * the BootstrapMethods already contains a similar bootstrap method.
+ *
+ * @param bootstrapMethodHandle a bootstrap method handle.
+ * @param bootstrapMethodArguments the bootstrap method arguments.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addBootstrapMethod(
+ final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
+ ByteVector bootstrapMethodsAttribute = bootstrapMethods;
+ if (bootstrapMethodsAttribute == null) {
+ bootstrapMethodsAttribute = bootstrapMethods = new ByteVector();
+ }
+
+ // The bootstrap method arguments can be Constant_Dynamic values, which reference other
+ // bootstrap methods. We must therefore add the bootstrap method arguments to the constant pool
+ // and BootstrapMethods attribute first, so that the BootstrapMethods attribute is not modified
+ // while adding the given bootstrap method to it, in the rest of this method.
+ for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
+ addConstant(bootstrapMethodArgument);
+ }
+
+ // Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to
+ // compare it with existing ones, and will be reverted below if there is already a similar
+ // bootstrap method.
+ int bootstrapMethodOffset = bootstrapMethodsAttribute.length;
+ bootstrapMethodsAttribute.putShort(
+ addConstantMethodHandle(
+ bootstrapMethodHandle.getTag(),
+ bootstrapMethodHandle.getOwner(),
+ bootstrapMethodHandle.getName(),
+ bootstrapMethodHandle.getDesc(),
+ bootstrapMethodHandle.isInterface())
+ .index);
+ int numBootstrapArguments = bootstrapMethodArguments.length;
+ bootstrapMethodsAttribute.putShort(numBootstrapArguments);
+ for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
+ bootstrapMethodsAttribute.putShort(addConstant(bootstrapMethodArgument).index);
+ }
+
+ // Compute the length and the hash code of the bootstrap method.
+ int bootstrapMethodlength = bootstrapMethodsAttribute.length - bootstrapMethodOffset;
+ int hashCode = bootstrapMethodHandle.hashCode();
+ for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
+ hashCode ^= bootstrapMethodArgument.hashCode();
+ }
+ hashCode &= 0x7FFFFFFF;
+
+ // Add the bootstrap method to the symbol table or revert the above changes.
+ return addBootstrapMethod(bootstrapMethodOffset, bootstrapMethodlength, hashCode);
+ }
+
+ /**
+ * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if
+ * the BootstrapMethods already contains a similar bootstrap method (more precisely, reverts the
+ * content of {@link #bootstrapMethods} to remove the last, duplicate bootstrap method).
+ *
+ * @param offset the offset of the last bootstrap method in {@link #bootstrapMethods}, in bytes.
+ * @param length the length of this bootstrap method in {@link #bootstrapMethods}, in bytes.
+ * @param hashCode the hash code of this bootstrap method.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) {
+ final byte[] bootstrapMethodsData = bootstrapMethods.data;
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) {
+ int otherOffset = (int) entry.data;
+ boolean isSameBootstrapMethod = true;
+ for (int i = 0; i < length; ++i) {
+ if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) {
+ isSameBootstrapMethod = false;
+ break;
+ }
+ }
+ if (isSameBootstrapMethod) {
+ bootstrapMethods.length = offset; // Revert to old position.
+ return entry;
+ }
+ }
+ entry = entry.next;
+ }
+ return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode));
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Type table entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * @param typeIndex a type table index.
+ * @return the type table element whose index is given.
+ */
+ Symbol getType(final int typeIndex) {
+ return typeTable[typeIndex];
+ }
+
+ /**
+ * Adds a type in the type table of this symbol table. Does nothing if the type table already
+ * contains a similar type.
+ *
+ * @param value an internal class name.
+ * @return the index of a new or already existing type Symbol with the given value.
+ */
+ int addType(final String value) {
+ int hashCode = hash(Symbol.TYPE_TAG, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.TYPE_TAG && entry.hashCode == hashCode && entry.value.equals(value)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ return addType(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode));
+ }
+
+ /**
+ * Adds an {@link Frame#ITEM_UNINITIALIZED} type in the type table of this symbol table. Does
+ * nothing if the type table already contains a similar type.
+ *
+ * @param value an internal class name.
+ * @param bytecodeOffset the bytecode offset of the NEW instruction that created this {@link
+ * Frame#ITEM_UNINITIALIZED} type value.
+ * @return the index of a new or already existing type Symbol with the given value.
+ */
+ int addUninitializedType(final String value, final int bytecodeOffset) {
+ int hashCode = hash(Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.UNINITIALIZED_TYPE_TAG
+ && entry.hashCode == hashCode
+ && entry.data == bytecodeOffset
+ && entry.value.equals(value)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ return addType(
+ new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode));
+ }
+
+ /**
+ * Adds a merged type in the type table of this symbol table. Does nothing if the type table
+ * already contains a similar type.
+ *
+ * @param typeTableIndex1 a {@link Symbol#TYPE_TAG} type, specified by its index in the type
+ * table.
+ * @param typeTableIndex2 another {@link Symbol#TYPE_TAG} type, specified by its index in the type
+ * table.
+ * @return the index of a new or already existing {@link Symbol#TYPE_TAG} type Symbol,
+ * corresponding to the common super class of the given types.
+ */
+ int addMergedType(final int typeTableIndex1, final int typeTableIndex2) {
+ // TODO sort the arguments? The merge result should be independent of their order.
+ long data = typeTableIndex1 | (((long) typeTableIndex2) << 32);
+ int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.MERGED_TYPE_TAG && entry.hashCode == hashCode && entry.data == data) {
+ return entry.info;
+ }
+ entry = entry.next;
+ }
+ String type1 = typeTable[typeTableIndex1].value;
+ String type2 = typeTable[typeTableIndex2].value;
+ int commonSuperTypeIndex = addType(classWriter.getCommonSuperClass(type1, type2));
+ put(new Entry(typeCount, Symbol.MERGED_TYPE_TAG, data, hashCode)).info = commonSuperTypeIndex;
+ return commonSuperTypeIndex;
+ }
+
+ /**
+ * Adds the given type Symbol to {@link #typeTable}.
+ *
+ * @param entry a {@link Symbol#TYPE_TAG} or {@link Symbol#UNINITIALIZED_TYPE_TAG} type symbol.
+ * The index of this Symbol must be equal to the current value of {@link #typeCount}.
+ * @return the index in {@link #typeTable} where the given type was added, which is also equal to
+ * entry's index by hypothesis.
+ */
+ private int addType(final Entry entry) {
+ if (typeTable == null) {
+ typeTable = new Entry[16];
+ }
+ if (typeCount == typeTable.length) {
+ Entry[] newTypeTable = new Entry[2 * typeTable.length];
+ System.arraycopy(typeTable, 0, newTypeTable, 0, typeTable.length);
+ typeTable = newTypeTable;
+ }
+ typeTable[typeCount++] = entry;
+ return put(entry).index;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Static helper methods to compute hash codes.
+ // -----------------------------------------------------------------------------------------------
+
+ private static int hash(final int tag, final int value) {
+ return 0x7FFFFFFF & (tag + value);
+ }
+
+ private static int hash(final int tag, final long value) {
+ return 0x7FFFFFFF & (tag + (int) value + (int) (value >>> 32));
+ }
+
+ private static int hash(final int tag, final String value) {
+ return 0x7FFFFFFF & (tag + value.hashCode());
+ }
+
+ private static int hash(final int tag, final String value1, final int value2) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() + value2);
+ }
+
+ private static int hash(final int tag, final String value1, final String value2) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode());
+ }
+
+ private static int hash(
+ final int tag, final String value1, final String value2, final int value3) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * (value3 + 1));
+ }
+
+ private static int hash(
+ final int tag, final String value1, final String value2, final String value3) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode());
+ }
+
+ private static int hash(
+ final int tag,
+ final String value1,
+ final String value2,
+ final String value3,
+ final int value4) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4);
+ }
+}
diff --git a/src/jvm/clojure/asm/Type.java b/src/jvm/clojure/asm/Type.java
index 31db08d861..bcd173d097 100644
--- a/src/jvm/clojure/asm/Type.java
+++ b/src/jvm/clojure/asm/Type.java
@@ -1,895 +1,906 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
- * A Java field or method type. This class can be used to make it easier to
- * manipulate type and method descriptors.
+ * A Java field or method type. This class can be used to make it easier to manipulate type and
+ * method descriptors.
*
* @author Eric Bruneton
* @author Chris Nokleberg
*/
public class Type {
- /**
- * The sort of the void type. See {@link #getSort getSort}.
- */
- public static final int VOID = 0;
-
- /**
- * The sort of the boolean type. See {@link #getSort getSort}.
- */
- public static final int BOOLEAN = 1;
-
- /**
- * The sort of the char type. See {@link #getSort getSort}.
- */
- public static final int CHAR = 2;
-
- /**
- * The sort of the byte type. See {@link #getSort getSort}.
- */
- public static final int BYTE = 3;
-
- /**
- * The sort of the short type. See {@link #getSort getSort}.
- */
- public static final int SHORT = 4;
-
- /**
- * The sort of the int type. See {@link #getSort getSort}.
- */
- public static final int INT = 5;
-
- /**
- * The sort of the float type. See {@link #getSort getSort}.
- */
- public static final int FLOAT = 6;
-
- /**
- * The sort of the long type. See {@link #getSort getSort}.
- */
- public static final int LONG = 7;
-
- /**
- * The sort of the double type. See {@link #getSort getSort}.
- */
- public static final int DOUBLE = 8;
-
- /**
- * The sort of array reference types. See {@link #getSort getSort}.
- */
- public static final int ARRAY = 9;
-
- /**
- * The sort of object reference types. See {@link #getSort getSort}.
- */
- public static final int OBJECT = 10;
-
- /**
- * The sort of method types. See {@link #getSort getSort}.
- */
- public static final int METHOD = 11;
-
- /**
- * The void type.
- */
- public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24)
- | (5 << 16) | (0 << 8) | 0, 1);
-
- /**
- * The boolean type.
- */
- public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24)
- | (0 << 16) | (5 << 8) | 1, 1);
-
- /**
- * The char type.
- */
- public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24)
- | (0 << 16) | (6 << 8) | 1, 1);
-
- /**
- * The byte type.
- */
- public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24)
- | (0 << 16) | (5 << 8) | 1, 1);
-
- /**
- * The short type.
- */
- public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24)
- | (0 << 16) | (7 << 8) | 1, 1);
-
- /**
- * The int type.
- */
- public static final Type INT_TYPE = new Type(INT, null, ('I' << 24)
- | (0 << 16) | (0 << 8) | 1, 1);
-
- /**
- * The float type.
- */
- public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24)
- | (2 << 16) | (2 << 8) | 1, 1);
-
- /**
- * The long type.
- */
- public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24)
- | (1 << 16) | (1 << 8) | 2, 1);
-
- /**
- * The double type.
- */
- public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24)
- | (3 << 16) | (3 << 8) | 2, 1);
-
- // ------------------------------------------------------------------------
- // Fields
- // ------------------------------------------------------------------------
-
- /**
- * The sort of this Java type.
- */
- private final int sort;
-
- /**
- * A buffer containing the internal name of this Java type. This field is
- * only used for reference types.
- */
- private final char[] buf;
-
- /**
- * The offset of the internal name of this Java type in {@link #buf buf} or,
- * for primitive types, the size, descriptor and getOpcode offsets for this
- * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset
- * for IALOAD or IASTORE, byte 3 the offset for all other instructions).
- */
- private final int off;
-
- /**
- * The length of the internal name of this Java type.
- */
- private final int len;
-
- // ------------------------------------------------------------------------
- // Constructors
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a reference type.
- *
- * @param sort
- * the sort of the reference type to be constructed.
- * @param buf
- * a buffer containing the descriptor of the previous type.
- * @param off
- * the offset of this descriptor in the previous buffer.
- * @param len
- * the length of this descriptor.
- */
- private Type(final int sort, final char[] buf, final int off, final int len) {
- this.sort = sort;
- this.buf = buf;
- this.off = off;
- this.len = len;
- }
-
- /**
- * Returns the Java type corresponding to the given type descriptor.
- *
- * @param typeDescriptor
- * a field or method type descriptor.
- * @return the Java type corresponding to the given type descriptor.
- */
- public static Type getType(final String typeDescriptor) {
- return getType(typeDescriptor.toCharArray(), 0);
- }
+ /** The sort of the void type. See {@link #getSort}. */
+ public static final int VOID = 0;
- /**
- * Returns the Java type corresponding to the given internal name.
- *
- * @param internalName
- * an internal name.
- * @return the Java type corresponding to the given internal name.
- */
- public static Type getObjectType(final String internalName) {
- char[] buf = internalName.toCharArray();
- return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length);
- }
+ /** The sort of the boolean type. See {@link #getSort}. */
+ public static final int BOOLEAN = 1;
- /**
- * Returns the Java type corresponding to the given method descriptor.
- * Equivalent to Type.getType(methodDescriptor).
- *
- * @param methodDescriptor
- * a method descriptor.
- * @return the Java type corresponding to the given method descriptor.
- */
- public static Type getMethodType(final String methodDescriptor) {
- return getType(methodDescriptor.toCharArray(), 0);
- }
+ /** The sort of the char type. See {@link #getSort}. */
+ public static final int CHAR = 2;
- /**
- * Returns the Java method type corresponding to the given argument and
- * return types.
- *
- * @param returnType
- * the return type of the method.
- * @param argumentTypes
- * the argument types of the method.
- * @return the Java type corresponding to the given argument and return
- * types.
- */
- public static Type getMethodType(final Type returnType,
- final Type... argumentTypes) {
- return getType(getMethodDescriptor(returnType, argumentTypes));
- }
+ /** The sort of the byte type. See {@link #getSort}. */
+ public static final int BYTE = 3;
- /**
- * Returns the Java type corresponding to the given class.
- *
- * @param c
- * a class.
- * @return the Java type corresponding to the given class.
- */
- public static Type getType(final Class> c) {
- if (c.isPrimitive()) {
- if (c == Integer.TYPE) {
- return INT_TYPE;
- } else if (c == Void.TYPE) {
- return VOID_TYPE;
- } else if (c == Boolean.TYPE) {
- return BOOLEAN_TYPE;
- } else if (c == Byte.TYPE) {
- return BYTE_TYPE;
- } else if (c == Character.TYPE) {
- return CHAR_TYPE;
- } else if (c == Short.TYPE) {
- return SHORT_TYPE;
- } else if (c == Double.TYPE) {
- return DOUBLE_TYPE;
- } else if (c == Float.TYPE) {
- return FLOAT_TYPE;
- } else /* if (c == Long.TYPE) */{
- return LONG_TYPE;
- }
- } else {
- return getType(getDescriptor(c));
- }
- }
+ /** The sort of the short type. See {@link #getSort}. */
+ public static final int SHORT = 4;
- /**
- * Returns the Java method type corresponding to the given constructor.
- *
- * @param c
- * a {@link Constructor Constructor} object.
- * @return the Java method type corresponding to the given constructor.
- */
- public static Type getType(final Constructor> c) {
- return getType(getConstructorDescriptor(c));
- }
+ /** The sort of the int type. See {@link #getSort}. */
+ public static final int INT = 5;
- /**
- * Returns the Java method type corresponding to the given method.
- *
- * @param m
- * a {@link Method Method} object.
- * @return the Java method type corresponding to the given method.
- */
- public static Type getType(final Method m) {
- return getType(getMethodDescriptor(m));
- }
+ /** The sort of the float type. See {@link #getSort}. */
+ public static final int FLOAT = 6;
- /**
- * Returns the Java types corresponding to the argument types of the given
- * method descriptor.
- *
- * @param methodDescriptor
- * a method descriptor.
- * @return the Java types corresponding to the argument types of the given
- * method descriptor.
- */
- public static Type[] getArgumentTypes(final String methodDescriptor) {
- char[] buf = methodDescriptor.toCharArray();
- int off = 1;
- int size = 0;
- while (true) {
- char car = buf[off++];
- if (car == ')') {
- break;
- } else if (car == 'L') {
- while (buf[off++] != ';') {
- }
- ++size;
- } else if (car != '[') {
- ++size;
- }
+ /** The sort of the long type. See {@link #getSort}. */
+ public static final int LONG = 7;
+
+ /** The sort of the double type. See {@link #getSort}. */
+ public static final int DOUBLE = 8;
+
+ /** The sort of array reference types. See {@link #getSort}. */
+ public static final int ARRAY = 9;
+
+ /** The sort of object reference types. See {@link #getSort}. */
+ public static final int OBJECT = 10;
+
+ /** The sort of method types. See {@link #getSort}. */
+ public static final int METHOD = 11;
+
+ /** The (private) sort of object reference types represented with an internal name. */
+ private static final int INTERNAL = 12;
+
+ /** The descriptors of the primitive types. */
+ private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD";
+
+ /** The void type. */
+ public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1);
+
+ /** The boolean type. */
+ public static final Type BOOLEAN_TYPE =
+ new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1);
+
+ /** The char type. */
+ public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1);
+
+ /** The byte type. */
+ public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1);
+
+ /** The short type. */
+ public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1);
+
+ /** The int type. */
+ public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1);
+
+ /** The float type. */
+ public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1);
+
+ /** The long type. */
+ public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1);
+
+ /** The double type. */
+ public static final Type DOUBLE_TYPE =
+ new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1);
+
+ // -----------------------------------------------------------------------------------------------
+ // Fields
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * The sort of this type. Either {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE},
+ * {@link #SHORT}, {@link #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY},
+ * {@link #OBJECT}, {@link #METHOD} or {@link #INTERNAL}.
+ */
+ private final int sort;
+
+ /**
+ * A buffer containing the value of this field or method type. This value is an internal name for
+ * {@link #OBJECT} and {@link #INTERNAL} types, and a field or method descriptor in the other
+ * cases.
+ *
+ *
For {@link #OBJECT} types, this field also contains the descriptor: the characters in
+ * [{@link #valueBegin},{@link #valueEnd}) contain the internal name, and those in [{@link
+ * #valueBegin} - 1, {@link #valueEnd} + 1) contain the descriptor.
+ */
+ private final String valueBuffer;
+
+ /**
+ * The beginning index, inclusive, of the value of this Java field or method type in {@link
+ * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types,
+ * and a field or method descriptor in the other cases.
+ */
+ private final int valueBegin;
+
+ /**
+ * The end index, exclusive, of the value of this Java field or method type in {@link
+ * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types,
+ * and a field or method descriptor in the other cases.
+ */
+ private final int valueEnd;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a reference type.
+ *
+ * @param sort the sort of this type, see {@link #sort}.
+ * @param valueBuffer a buffer containing the value of this field or method type.
+ * @param valueBegin the beginning index, inclusive, of the value of this field or method type in
+ * valueBuffer.
+ * @param valueEnd tne end index, exclusive, of the value of this field or method type in
+ * valueBuffer.
+ */
+ private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) {
+ this.sort = sort;
+ this.valueBuffer = valueBuffer;
+ this.valueBegin = valueBegin;
+ this.valueEnd = valueEnd;
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given type descriptor.
+ *
+ * @param typeDescriptor a field or method type descriptor.
+ * @return the {@link Type} corresponding to the given type descriptor.
+ */
+ public static Type getType(final String typeDescriptor) {
+ return getType(typeDescriptor, 0, typeDescriptor.length());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given internal name.
+ *
+ * @param internalName an internal name.
+ * @return the {@link Type} corresponding to the given internal name.
+ */
+ public static Type getObjectType(final String internalName) {
+ return new Type(
+ internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to
+ * Type.getType(methodDescriptor).
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the {@link Type} corresponding to the given method descriptor.
+ */
+ public static Type getMethodType(final String methodDescriptor) {
+ return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length());
+ }
+
+ /**
+ * Returns the method {@link Type} corresponding to the given argument and return types.
+ *
+ * @param returnType the return type of the method.
+ * @param argumentTypes the argument types of the method.
+ * @return the method {@link Type} corresponding to the given argument and return types.
+ */
+ public static Type getMethodType(final Type returnType, final Type... argumentTypes) {
+ return getType(getMethodDescriptor(returnType, argumentTypes));
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given class.
+ *
+ * @param clazz a class.
+ * @return the {@link Type} corresponding to the given class.
+ */
+ public static Type getType(final Class> clazz) {
+ if (clazz.isPrimitive()) {
+ if (clazz == Integer.TYPE) {
+ return INT_TYPE;
+ } else if (clazz == Void.TYPE) {
+ return VOID_TYPE;
+ } else if (clazz == Boolean.TYPE) {
+ return BOOLEAN_TYPE;
+ } else if (clazz == Byte.TYPE) {
+ return BYTE_TYPE;
+ } else if (clazz == Character.TYPE) {
+ return CHAR_TYPE;
+ } else if (clazz == Short.TYPE) {
+ return SHORT_TYPE;
+ } else if (clazz == Double.TYPE) {
+ return DOUBLE_TYPE;
+ } else if (clazz == Float.TYPE) {
+ return FLOAT_TYPE;
+ } else if (clazz == Long.TYPE) {
+ return LONG_TYPE;
+ } else {
+ throw new AssertionError();
+ }
+ } else {
+ return getType(getDescriptor(clazz));
+ }
+ }
+
+ /**
+ * Returns the method {@link Type} corresponding to the given constructor.
+ *
+ * @param constructor a {@link Constructor} object.
+ * @return the method {@link Type} corresponding to the given constructor.
+ */
+ public static Type getType(final Constructor> constructor) {
+ return getType(getConstructorDescriptor(constructor));
+ }
+
+ /**
+ * Returns the method {@link Type} corresponding to the given method.
+ *
+ * @param method a {@link Method} object.
+ * @return the method {@link Type} corresponding to the given method.
+ */
+ public static Type getType(final Method method) {
+ return getType(getMethodDescriptor(method));
+ }
+
+ /**
+ * Returns the {@link Type} values corresponding to the argument types of the given method
+ * descriptor.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the {@link Type} values corresponding to the argument types of the given method
+ * descriptor.
+ */
+ public static Type[] getArgumentTypes(final String methodDescriptor) {
+ // First step: compute the number of argument types in methodDescriptor.
+ int numArgumentTypes = 0;
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ // Parse the argument types, one at a each loop iteration.
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ while (methodDescriptor.charAt(currentOffset++) != ';') {
+ // Skip the argument descriptor content.
}
- Type[] args = new Type[size];
- off = 1;
- size = 0;
- while (buf[off] != ')') {
- args[size] = getType(buf, off);
- off += args[size].len + (args[size].sort == OBJECT ? 2 : 0);
- size += 1;
+ }
+ ++numArgumentTypes;
+ }
+
+ // Second step: create a Type instance for each argument type.
+ Type[] argumentTypes = new Type[numArgumentTypes];
+ // Skip the first character, which is always a '('.
+ currentOffset = 1;
+ // Parse and create the argument types, one at each loop iteration.
+ int currentArgumentTypeIndex = 0;
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ final int currentArgumentTypeOffset = currentOffset;
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ while (methodDescriptor.charAt(currentOffset++) != ';') {
+ // Skip the argument descriptor content.
}
- return args;
- }
-
- /**
- * Returns the Java types corresponding to the argument types of the given
- * method.
- *
- * @param method
- * a method.
- * @return the Java types corresponding to the argument types of the given
- * method.
- */
- public static Type[] getArgumentTypes(final Method method) {
- Class>[] classes = method.getParameterTypes();
- Type[] types = new Type[classes.length];
- for (int i = classes.length - 1; i >= 0; --i) {
- types[i] = getType(classes[i]);
+ }
+ argumentTypes[currentArgumentTypeIndex++] =
+ getType(methodDescriptor, currentArgumentTypeOffset, currentOffset);
+ }
+ return argumentTypes;
+ }
+
+ /**
+ * Returns the {@link Type} values corresponding to the argument types of the given method.
+ *
+ * @param method a method.
+ * @return the {@link Type} values corresponding to the argument types of the given method.
+ */
+ public static Type[] getArgumentTypes(final Method method) {
+ Class>[] classes = method.getParameterTypes();
+ Type[] types = new Type[classes.length];
+ for (int i = classes.length - 1; i >= 0; --i) {
+ types[i] = getType(classes[i]);
+ }
+ return types;
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the return type of the given method descriptor.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the {@link Type} corresponding to the return type of the given method descriptor.
+ */
+ public static Type getReturnType(final String methodDescriptor) {
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ // Skip the argument types, one at a each loop iteration.
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ while (methodDescriptor.charAt(currentOffset++) != ';') {
+ // Skip the argument descriptor content.
}
- return types;
- }
-
- /**
- * Returns the Java type corresponding to the return type of the given
- * method descriptor.
- *
- * @param methodDescriptor
- * a method descriptor.
- * @return the Java type corresponding to the return type of the given
- * method descriptor.
- */
- public static Type getReturnType(final String methodDescriptor) {
- char[] buf = methodDescriptor.toCharArray();
- return getType(buf, methodDescriptor.indexOf(')') + 1);
- }
-
- /**
- * Returns the Java type corresponding to the return type of the given
- * method.
- *
- * @param method
- * a method.
- * @return the Java type corresponding to the return type of the given
- * method.
- */
- public static Type getReturnType(final Method method) {
- return getType(method.getReturnType());
- }
-
- /**
- * Computes the size of the arguments and of the return value of a method.
- *
- * @param desc
- * the descriptor of a method.
- * @return the size of the arguments of the method (plus one for the
- * implicit this argument), argSize, and the size of its return
- * value, retSize, packed into a single int i =
- * (argSize << 2) | retSize (argSize is therefore equal to
- * i >> 2, and retSize to i & 0x03).
- */
- public static int getArgumentsAndReturnSizes(final String desc) {
- int n = 1;
- int c = 1;
- while (true) {
- char car = desc.charAt(c++);
- if (car == ')') {
- car = desc.charAt(c);
- return n << 2
- | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1));
- } else if (car == 'L') {
- while (desc.charAt(c++) != ';') {
- }
- n += 1;
- } else if (car == '[') {
- while ((car = desc.charAt(c)) == '[') {
- ++c;
- }
- if (car == 'D' || car == 'J') {
- n -= 1;
- }
- } else if (car == 'D' || car == 'J') {
- n += 2;
- } else {
- n += 1;
- }
+ }
+ }
+ return getType(methodDescriptor, currentOffset + 1, methodDescriptor.length());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the return type of the given method.
+ *
+ * @param method a method.
+ * @return the {@link Type} corresponding to the return type of the given method.
+ */
+ public static Type getReturnType(final Method method) {
+ return getType(method.getReturnType());
+ }
+
+ /**
+ * Computes the size of the arguments and of the return value of a method.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the size of the arguments of the method (plus one for the implicit this argument),
+ * argumentsSize, and the size of its return value, returnSize, packed into a single int i =
+ * (argumentsSize << 2) | returnSize (argumentsSize is therefore equal to i
+ * >> 2, and returnSize to i & 0x03).
+ */
+ public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
+ int argumentsSize = 1;
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ int currentChar = methodDescriptor.charAt(currentOffset);
+ // Parse the argument types and compute their size, one at a each loop iteration.
+ while (currentChar != ')') {
+ if (currentChar == 'J' || currentChar == 'D') {
+ currentOffset++;
+ argumentsSize += 2;
+ } else {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
}
- }
-
- /**
- * Returns the Java type corresponding to the given type descriptor. For
- * method descriptors, buf is supposed to contain nothing more than the
- * descriptor itself.
- *
- * @param buf
- * a buffer containing a type descriptor.
- * @param off
- * the offset of this descriptor in the previous buffer.
- * @return the Java type corresponding to the given type descriptor.
- */
- private static Type getType(final char[] buf, final int off) {
- int len;
- switch (buf[off]) {
- case 'V':
- return VOID_TYPE;
- case 'Z':
- return BOOLEAN_TYPE;
- case 'C':
- return CHAR_TYPE;
- case 'B':
- return BYTE_TYPE;
- case 'S':
- return SHORT_TYPE;
- case 'I':
- return INT_TYPE;
- case 'F':
- return FLOAT_TYPE;
- case 'J':
- return LONG_TYPE;
- case 'D':
- return DOUBLE_TYPE;
- case '[':
- len = 1;
- while (buf[off + len] == '[') {
- ++len;
- }
- if (buf[off + len] == 'L') {
- ++len;
- while (buf[off + len] != ';') {
- ++len;
- }
- }
- return new Type(ARRAY, buf, off, len + 1);
- case 'L':
- len = 1;
- while (buf[off + len] != ';') {
- ++len;
- }
- return new Type(OBJECT, buf, off + 1, len - 1);
- // case '(':
- default:
- return new Type(METHOD, buf, off, buf.length - off);
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ while (methodDescriptor.charAt(currentOffset++) != ';') {
+ // Skip the argument descriptor content.
+ }
}
- }
-
- // ------------------------------------------------------------------------
- // Accessors
- // ------------------------------------------------------------------------
-
- /**
- * Returns the sort of this Java type.
- *
- * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR},
- * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT},
- * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE},
- * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD
- * METHOD}.
- */
- public int getSort() {
- return sort;
- }
-
- /**
- * Returns the number of dimensions of this array type. This method should
- * only be used for an array type.
- *
- * @return the number of dimensions of this array type.
- */
- public int getDimensions() {
- int i = 1;
- while (buf[off + i] == '[') {
- ++i;
+ argumentsSize += 1;
+ }
+ currentChar = methodDescriptor.charAt(currentOffset);
+ }
+ currentChar = methodDescriptor.charAt(currentOffset + 1);
+ if (currentChar == 'V') {
+ return argumentsSize << 2;
+ } else {
+ int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1;
+ return argumentsSize << 2 | returnSize;
+ }
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given field or method descriptor.
+ *
+ * @param descriptorBuffer a buffer containing the field or method descriptor.
+ * @param descriptorBegin the beginning index, inclusive, of the field or method descriptor in
+ * descriptorBuffer.
+ * @param descriptorEnd the end index, exclusive, of the field or method descriptor in
+ * descriptorBuffer.
+ * @return the {@link Type} corresponding to the given type descriptor.
+ */
+ private static Type getType(
+ final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) {
+ switch (descriptorBuffer.charAt(descriptorBegin)) {
+ case 'V':
+ return VOID_TYPE;
+ case 'Z':
+ return BOOLEAN_TYPE;
+ case 'C':
+ return CHAR_TYPE;
+ case 'B':
+ return BYTE_TYPE;
+ case 'S':
+ return SHORT_TYPE;
+ case 'I':
+ return INT_TYPE;
+ case 'F':
+ return FLOAT_TYPE;
+ case 'J':
+ return LONG_TYPE;
+ case 'D':
+ return DOUBLE_TYPE;
+ case '[':
+ return new Type(ARRAY, descriptorBuffer, descriptorBegin, descriptorEnd);
+ case 'L':
+ return new Type(OBJECT, descriptorBuffer, descriptorBegin + 1, descriptorEnd - 1);
+ case '(':
+ return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the sort of this type.
+ *
+ * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link
+ * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or
+ * {@link #METHOD}.
+ */
+ public int getSort() {
+ return sort == INTERNAL ? OBJECT : sort;
+ }
+
+ /**
+ * Returns the number of dimensions of this array type. This method should only be used for an
+ * array type.
+ *
+ * @return the number of dimensions of this array type.
+ */
+ public int getDimensions() {
+ int numDimensions = 1;
+ while (valueBuffer.charAt(valueBegin + numDimensions) == '[') {
+ numDimensions++;
+ }
+ return numDimensions;
+ }
+
+ /**
+ * Returns the type of the elements of this array type. This method should only be used for an
+ * array type.
+ *
+ * @return Returns the type of the elements of this array type.
+ */
+ public Type getElementType() {
+ final int numDimensions = getDimensions();
+ return getType(valueBuffer, valueBegin + numDimensions, valueEnd);
+ }
+
+ /**
+ * Returns the binary name of the class corresponding to this type. This method must not be used
+ * on method types.
+ *
+ * @return the binary name of the class corresponding to this type.
+ */
+ public String getClassName() {
+ switch (sort) {
+ case VOID:
+ return "void";
+ case BOOLEAN:
+ return "boolean";
+ case CHAR:
+ return "char";
+ case BYTE:
+ return "byte";
+ case SHORT:
+ return "short";
+ case INT:
+ return "int";
+ case FLOAT:
+ return "float";
+ case LONG:
+ return "long";
+ case DOUBLE:
+ return "double";
+ case ARRAY:
+ StringBuilder stringBuilder = new StringBuilder(getElementType().getClassName());
+ for (int i = getDimensions(); i > 0; --i) {
+ stringBuilder.append("[]");
}
- return i;
- }
-
- /**
- * Returns the type of the elements of this array type. This method should
- * only be used for an array type.
- *
- * @return Returns the type of the elements of this array type.
- */
- public Type getElementType() {
- return getType(buf, off + getDimensions());
- }
-
- /**
- * Returns the binary name of the class corresponding to this type. This
- * method must not be used on method types.
- *
- * @return the binary name of the class corresponding to this type.
- */
- public String getClassName() {
- switch (sort) {
- case VOID:
- return "void";
+ return stringBuilder.toString();
+ case OBJECT:
+ case INTERNAL:
+ return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.');
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Returns the internal name of the class corresponding to this object or array type. The internal
+ * name of a class is its fully qualified name (as returned by Class.getName(), where '.' are
+ * replaced by '/'). This method should only be used for an object or array type.
+ *
+ * @return the internal name of the class corresponding to this object type.
+ */
+ public String getInternalName() {
+ return valueBuffer.substring(valueBegin, valueEnd);
+ }
+
+ /**
+ * Returns the argument types of methods of this type. This method should only be used for method
+ * types.
+ *
+ * @return the argument types of methods of this type.
+ */
+ public Type[] getArgumentTypes() {
+ return getArgumentTypes(getDescriptor());
+ }
+
+ /**
+ * Returns the return type of methods of this type. This method should only be used for method
+ * types.
+ *
+ * @return the return type of methods of this type.
+ */
+ public Type getReturnType() {
+ return getReturnType(getDescriptor());
+ }
+
+ /**
+ * Returns the size of the arguments and of the return value of methods of this type. This method
+ * should only be used for method types.
+ *
+ * @return the size of the arguments of the method (plus one for the implicit this argument),
+ * argumentsSize, and the size of its return value, returnSize, packed into a single int i =
+ * (argumentsSize << 2) | returnSize (argumentsSize is therefore equal to i
+ * >> 2, and returnSize to i & 0x03).
+ */
+ public int getArgumentsAndReturnSizes() {
+ return getArgumentsAndReturnSizes(getDescriptor());
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Conversion to type descriptors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the descriptor corresponding to this type.
+ *
+ * @return the descriptor corresponding to this type.
+ */
+ public String getDescriptor() {
+ if (sort == OBJECT) {
+ return valueBuffer.substring(valueBegin - 1, valueEnd + 1);
+ } else if (sort == INTERNAL) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('L');
+ stringBuilder.append(valueBuffer, valueBegin, valueEnd);
+ stringBuilder.append(';');
+ return stringBuilder.toString();
+ } else {
+ return valueBuffer.substring(valueBegin, valueEnd);
+ }
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given argument and return types.
+ *
+ * @param returnType the return type of the method.
+ * @param argumentTypes the argument types of the method.
+ * @return the descriptor corresponding to the given argument and return types.
+ */
+ public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('(');
+ for (int i = 0; i < argumentTypes.length; ++i) {
+ argumentTypes[i].appendDescriptor(stringBuilder);
+ }
+ stringBuilder.append(')');
+ returnType.appendDescriptor(stringBuilder);
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Appends the descriptor corresponding to this type to the given string buffer.
+ *
+ * @param stringBuilder the string builder to which the descriptor must be appended.
+ */
+ private void appendDescriptor(final StringBuilder stringBuilder) {
+ if (sort == OBJECT) {
+ stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1);
+ } else if (sort == INTERNAL) {
+ stringBuilder.append('L');
+ stringBuilder.append(valueBuffer, valueBegin, valueEnd);
+ stringBuilder.append(';');
+ } else {
+ stringBuilder.append(valueBuffer, valueBegin, valueEnd);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Direct conversion from classes to type descriptors,
+ // without intermediate Type objects
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the internal name of the given class. The internal name of a class is its fully
+ * qualified name, as returned by Class.getName(), where '.' are replaced by '/'.
+ *
+ * @param clazz an object or array class.
+ * @return the internal name of the given class.
+ */
+ public static String getInternalName(final Class> clazz) {
+ return clazz.getName().replace('.', '/');
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given class.
+ *
+ * @param clazz an object class, a primitive class or an array class.
+ * @return the descriptor corresponding to the given class.
+ */
+ public static String getDescriptor(final Class> clazz) {
+ StringBuilder stringBuilder = new StringBuilder();
+ appendDescriptor(stringBuilder, clazz);
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given constructor.
+ *
+ * @param constructor a {@link Constructor} object.
+ * @return the descriptor of the given constructor.
+ */
+ public static String getConstructorDescriptor(final Constructor> constructor) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('(');
+ Class>[] parameters = constructor.getParameterTypes();
+ for (int i = 0; i < parameters.length; ++i) {
+ appendDescriptor(stringBuilder, parameters[i]);
+ }
+ return stringBuilder.append(")V").toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given method.
+ *
+ * @param method a {@link Method} object.
+ * @return the descriptor of the given method.
+ */
+ public static String getMethodDescriptor(final Method method) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('(');
+ Class>[] parameters = method.getParameterTypes();
+ for (int i = 0; i < parameters.length; ++i) {
+ appendDescriptor(stringBuilder, parameters[i]);
+ }
+ stringBuilder.append(')');
+ appendDescriptor(stringBuilder, method.getReturnType());
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Appends the descriptor of the given class to the given string builder.
+ *
+ * @param stringBuilder the string builder to which the descriptor must be appended.
+ * @param clazz the class whose descriptor must be computed.
+ */
+ private static void appendDescriptor(final StringBuilder stringBuilder, final Class> clazz) {
+ Class> currentClass = clazz;
+ while (currentClass.isArray()) {
+ stringBuilder.append('[');
+ currentClass = currentClass.getComponentType();
+ }
+ if (currentClass.isPrimitive()) {
+ char descriptor;
+ if (currentClass == Integer.TYPE) {
+ descriptor = 'I';
+ } else if (currentClass == Void.TYPE) {
+ descriptor = 'V';
+ } else if (currentClass == Boolean.TYPE) {
+ descriptor = 'Z';
+ } else if (currentClass == Byte.TYPE) {
+ descriptor = 'B';
+ } else if (currentClass == Character.TYPE) {
+ descriptor = 'C';
+ } else if (currentClass == Short.TYPE) {
+ descriptor = 'S';
+ } else if (currentClass == Double.TYPE) {
+ descriptor = 'D';
+ } else if (currentClass == Float.TYPE) {
+ descriptor = 'F';
+ } else if (currentClass == Long.TYPE) {
+ descriptor = 'J';
+ } else {
+ throw new AssertionError();
+ }
+ stringBuilder.append(descriptor);
+ } else {
+ stringBuilder.append('L');
+ String name = currentClass.getName();
+ int nameLength = name.length();
+ for (int i = 0; i < nameLength; ++i) {
+ char car = name.charAt(i);
+ stringBuilder.append(car == '.' ? '/' : car);
+ }
+ stringBuilder.append(';');
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Corresponding size and opcodes
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the size of values of this type. This method must not be used for method types.
+ *
+ * @return the size of values of this type, i.e., 2 for long and double, 0 for
+ * void and 1 otherwise.
+ */
+ public int getSize() {
+ switch (sort) {
+ case VOID:
+ return 0;
+ case BOOLEAN:
+ case CHAR:
+ case BYTE:
+ case SHORT:
+ case INT:
+ case FLOAT:
+ case ARRAY:
+ case OBJECT:
+ case INTERNAL:
+ return 1;
+ case LONG:
+ case DOUBLE:
+ return 2;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for
+ * method types.
+ *
+ * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD,
+ * IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and
+ * IRETURN.
+ * @return an opcode that is similar to the given opcode, but adapted to this {@link Type}. For
+ * example, if this type is float and opcode is IRETURN, this method returns
+ * FRETURN.
+ */
+ public int getOpcode(final int opcode) {
+ if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
+ switch (sort) {
case BOOLEAN:
- return "boolean";
+ case BYTE:
+ return opcode + (Opcodes.BALOAD - Opcodes.IALOAD);
case CHAR:
- return "char";
+ return opcode + (Opcodes.CALOAD - Opcodes.IALOAD);
+ case SHORT:
+ return opcode + (Opcodes.SALOAD - Opcodes.IALOAD);
+ case INT:
+ return opcode;
+ case FLOAT:
+ return opcode + (Opcodes.FALOAD - Opcodes.IALOAD);
+ case LONG:
+ return opcode + (Opcodes.LALOAD - Opcodes.IALOAD);
+ case DOUBLE:
+ return opcode + (Opcodes.DALOAD - Opcodes.IALOAD);
+ case ARRAY:
+ case OBJECT:
+ case INTERNAL:
+ return opcode + (Opcodes.AALOAD - Opcodes.IALOAD);
+ case METHOD:
+ case VOID:
+ throw new UnsupportedOperationException();
+ default:
+ throw new AssertionError();
+ }
+ } else {
+ switch (sort) {
+ case VOID:
+ if (opcode != Opcodes.IRETURN) {
+ throw new UnsupportedOperationException();
+ }
+ return Opcodes.RETURN;
+ case BOOLEAN:
case BYTE:
- return "byte";
+ case CHAR:
case SHORT:
- return "short";
case INT:
- return "int";
+ return opcode;
case FLOAT:
- return "float";
+ return opcode + (Opcodes.FRETURN - Opcodes.IRETURN);
case LONG:
- return "long";
+ return opcode + (Opcodes.LRETURN - Opcodes.IRETURN);
case DOUBLE:
- return "double";
+ return opcode + (Opcodes.DRETURN - Opcodes.IRETURN);
case ARRAY:
- StringBuffer b = new StringBuffer(getElementType().getClassName());
- for (int i = getDimensions(); i > 0; --i) {
- b.append("[]");
- }
- return b.toString();
case OBJECT:
- return new String(buf, off, len).replace('/', '.');
+ case INTERNAL:
+ if (opcode != Opcodes.ILOAD && opcode != Opcodes.ISTORE && opcode != Opcodes.IRETURN) {
+ throw new UnsupportedOperationException();
+ }
+ return opcode + (Opcodes.ARETURN - Opcodes.IRETURN);
+ case METHOD:
+ throw new UnsupportedOperationException();
default:
- return null;
- }
- }
-
- /**
- * Returns the internal name of the class corresponding to this object or
- * array type. The internal name of a class is its fully qualified name (as
- * returned by Class.getName(), where '.' are replaced by '/'. This method
- * should only be used for an object or array type.
- *
- * @return the internal name of the class corresponding to this object type.
- */
- public String getInternalName() {
- return new String(buf, off, len);
- }
-
- /**
- * Returns the argument types of methods of this type. This method should
- * only be used for method types.
- *
- * @return the argument types of methods of this type.
- */
- public Type[] getArgumentTypes() {
- return getArgumentTypes(getDescriptor());
- }
-
- /**
- * Returns the return type of methods of this type. This method should only
- * be used for method types.
- *
- * @return the return type of methods of this type.
- */
- public Type getReturnType() {
- return getReturnType(getDescriptor());
- }
-
- /**
- * Returns the size of the arguments and of the return value of methods of
- * this type. This method should only be used for method types.
- *
- * @return the size of the arguments (plus one for the implicit this
- * argument), argSize, and the size of the return value, retSize,
- * packed into a single int i = (argSize << 2) | retSize
- * (argSize is therefore equal to i >> 2, and retSize to
- * i & 0x03).
- */
- public int getArgumentsAndReturnSizes() {
- return getArgumentsAndReturnSizes(getDescriptor());
- }
-
- // ------------------------------------------------------------------------
- // Conversion to type descriptors
- // ------------------------------------------------------------------------
-
- /**
- * Returns the descriptor corresponding to this Java type.
- *
- * @return the descriptor corresponding to this Java type.
- */
- public String getDescriptor() {
- StringBuffer buf = new StringBuffer();
- getDescriptor(buf);
- return buf.toString();
- }
-
- /**
- * Returns the descriptor corresponding to the given argument and return
- * types.
- *
- * @param returnType
- * the return type of the method.
- * @param argumentTypes
- * the argument types of the method.
- * @return the descriptor corresponding to the given argument and return
- * types.
- */
- public static String getMethodDescriptor(final Type returnType,
- final Type... argumentTypes) {
- StringBuffer buf = new StringBuffer();
- buf.append('(');
- for (int i = 0; i < argumentTypes.length; ++i) {
- argumentTypes[i].getDescriptor(buf);
- }
- buf.append(')');
- returnType.getDescriptor(buf);
- return buf.toString();
- }
-
- /**
- * Appends the descriptor corresponding to this Java type to the given
- * string buffer.
- *
- * @param buf
- * the string buffer to which the descriptor must be appended.
- */
- private void getDescriptor(final StringBuffer buf) {
- if (this.buf == null) {
- // descriptor is in byte 3 of 'off' for primitive types (buf ==
- // null)
- buf.append((char) ((off & 0xFF000000) >>> 24));
- } else if (sort == OBJECT) {
- buf.append('L');
- buf.append(this.buf, off, len);
- buf.append(';');
- } else { // sort == ARRAY || sort == METHOD
- buf.append(this.buf, off, len);
- }
- }
-
- // ------------------------------------------------------------------------
- // Direct conversion from classes to type descriptors,
- // without intermediate Type objects
- // ------------------------------------------------------------------------
-
- /**
- * Returns the internal name of the given class. The internal name of a
- * class is its fully qualified name, as returned by Class.getName(), where
- * '.' are replaced by '/'.
- *
- * @param c
- * an object or array class.
- * @return the internal name of the given class.
- */
- public static String getInternalName(final Class> c) {
- return c.getName().replace('.', '/');
- }
-
- /**
- * Returns the descriptor corresponding to the given Java type.
- *
- * @param c
- * an object class, a primitive class or an array class.
- * @return the descriptor corresponding to the given class.
- */
- public static String getDescriptor(final Class> c) {
- StringBuffer buf = new StringBuffer();
- getDescriptor(buf, c);
- return buf.toString();
- }
-
- /**
- * Returns the descriptor corresponding to the given constructor.
- *
- * @param c
- * a {@link Constructor Constructor} object.
- * @return the descriptor of the given constructor.
- */
- public static String getConstructorDescriptor(final Constructor> c) {
- Class>[] parameters = c.getParameterTypes();
- StringBuffer buf = new StringBuffer();
- buf.append('(');
- for (int i = 0; i < parameters.length; ++i) {
- getDescriptor(buf, parameters[i]);
- }
- return buf.append(")V").toString();
- }
-
- /**
- * Returns the descriptor corresponding to the given method.
- *
- * @param m
- * a {@link Method Method} object.
- * @return the descriptor of the given method.
- */
- public static String getMethodDescriptor(final Method m) {
- Class>[] parameters = m.getParameterTypes();
- StringBuffer buf = new StringBuffer();
- buf.append('(');
- for (int i = 0; i < parameters.length; ++i) {
- getDescriptor(buf, parameters[i]);
- }
- buf.append(')');
- getDescriptor(buf, m.getReturnType());
- return buf.toString();
- }
-
- /**
- * Appends the descriptor of the given class to the given string buffer.
- *
- * @param buf
- * the string buffer to which the descriptor must be appended.
- * @param c
- * the class whose descriptor must be computed.
- */
- private static void getDescriptor(final StringBuffer buf, final Class> c) {
- Class> d = c;
- while (true) {
- if (d.isPrimitive()) {
- char car;
- if (d == Integer.TYPE) {
- car = 'I';
- } else if (d == Void.TYPE) {
- car = 'V';
- } else if (d == Boolean.TYPE) {
- car = 'Z';
- } else if (d == Byte.TYPE) {
- car = 'B';
- } else if (d == Character.TYPE) {
- car = 'C';
- } else if (d == Short.TYPE) {
- car = 'S';
- } else if (d == Double.TYPE) {
- car = 'D';
- } else if (d == Float.TYPE) {
- car = 'F';
- } else /* if (d == Long.TYPE) */{
- car = 'J';
- }
- buf.append(car);
- return;
- } else if (d.isArray()) {
- buf.append('[');
- d = d.getComponentType();
- } else {
- buf.append('L');
- String name = d.getName();
- int len = name.length();
- for (int i = 0; i < len; ++i) {
- char car = name.charAt(i);
- buf.append(car == '.' ? '/' : car);
- }
- buf.append(';');
- return;
- }
- }
- }
-
- // ------------------------------------------------------------------------
- // Corresponding size and opcodes
- // ------------------------------------------------------------------------
-
- /**
- * Returns the size of values of this type. This method must not be used for
- * method types.
- *
- * @return the size of values of this type, i.e., 2 for long and
- * double, 0 for void and 1 otherwise.
- */
- public int getSize() {
- // the size is in byte 0 of 'off' for primitive types (buf == null)
- return buf == null ? (off & 0xFF) : 1;
- }
-
- /**
- * Returns a JVM instruction opcode adapted to this Java type. This method
- * must not be used for method types.
- *
- * @param opcode
- * a JVM instruction opcode. This opcode must be one of ILOAD,
- * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG,
- * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN.
- * @return an opcode that is similar to the given opcode, but adapted to
- * this Java type. For example, if this type is float and
- * opcode is IRETURN, this method returns FRETURN.
- */
- public int getOpcode(final int opcode) {
- if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
- // the offset for IALOAD or IASTORE is in byte 1 of 'off' for
- // primitive types (buf == null)
- return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4);
- } else {
- // the offset for other instructions is in byte 2 of 'off' for
- // primitive types (buf == null)
- return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4);
- }
- }
-
- // ------------------------------------------------------------------------
- // Equals, hashCode and toString
- // ------------------------------------------------------------------------
-
- /**
- * Tests if the given object is equal to this type.
- *
- * @param o
- * the object to be compared to this type.
- * @return true if the given object is equal to this type.
- */
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof Type)) {
- return false;
- }
- Type t = (Type) o;
- if (sort != t.sort) {
- return false;
- }
- if (sort >= ARRAY) {
- if (len != t.len) {
- return false;
- }
- for (int i = off, j = t.off, end = i + len; i < end; i++, j++) {
- if (buf[i] != t.buf[j]) {
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Returns a hash code value for this type.
- *
- * @return a hash code value for this type.
- */
- @Override
- public int hashCode() {
- int hc = 13 * sort;
- if (sort >= ARRAY) {
- for (int i = off, end = i + len; i < end; i++) {
- hc = 17 * (hc + buf[i]);
- }
- }
- return hc;
- }
-
- /**
- * Returns a string representation of this type.
- *
- * @return the descriptor of this type.
- */
- @Override
- public String toString() {
- return getDescriptor();
- }
+ throw new AssertionError();
+ }
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Equals, hashCode and toString
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Tests if the given object is equal to this type.
+ *
+ * @param object the object to be compared to this type.
+ * @return true if the given object is equal to this type.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (!(object instanceof Type)) {
+ return false;
+ }
+ Type other = (Type) object;
+ if ((sort == INTERNAL ? OBJECT : sort) != (other.sort == INTERNAL ? OBJECT : other.sort)) {
+ return false;
+ }
+ int begin = valueBegin;
+ int end = valueEnd;
+ int otherBegin = other.valueBegin;
+ int otherEnd = other.valueEnd;
+ // Compare the values.
+ if (end - begin != otherEnd - otherBegin) {
+ return false;
+ }
+ for (int i = begin, j = otherBegin; i < end; i++, j++) {
+ if (valueBuffer.charAt(i) != other.valueBuffer.charAt(j)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a hash code value for this type.
+ *
+ * @return a hash code value for this type.
+ */
+ @Override
+ public int hashCode() {
+ int hashCode = 13 * (sort == INTERNAL ? OBJECT : sort);
+ if (sort >= ARRAY) {
+ for (int i = valueBegin, end = valueEnd; i < end; i++) {
+ hashCode = 17 * (hashCode + valueBuffer.charAt(i));
+ }
+ }
+ return hashCode;
+ }
+
+ /**
+ * Returns a string representation of this type.
+ *
+ * @return the descriptor of this type.
+ */
+ @Override
+ public String toString() {
+ return getDescriptor();
+ }
}
diff --git a/src/jvm/clojure/asm/TypePath.java b/src/jvm/clojure/asm/TypePath.java
new file mode 100644
index 0000000000..aaffa43b9e
--- /dev/null
+++ b/src/jvm/clojure/asm/TypePath.java
@@ -0,0 +1,201 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+package clojure.asm;
+
+/**
+ * The path to a type argument, wildcard bound, array element type, or static inner type within an
+ * enclosing type.
+ *
+ * @author Eric Bruneton
+ */
+public class TypePath {
+
+ /** A type path step that steps into the element type of an array type. See {@link #getStep}. */
+ public static final int ARRAY_ELEMENT = 0;
+
+ /** A type path step that steps into the nested type of a class type. See {@link #getStep}. */
+ public static final int INNER_TYPE = 1;
+
+ /** A type path step that steps into the bound of a wildcard type. See {@link #getStep}. */
+ public static final int WILDCARD_BOUND = 2;
+
+ /** A type path step that steps into a type argument of a generic type. See {@link #getStep}. */
+ public static final int TYPE_ARGUMENT = 3;
+
+ /**
+ * The byte array where the 'type_path' structure - as defined in the Java Virtual Machine
+ * Specification (JVMS) - corresponding to this TypePath is stored. The first byte of the
+ * structure in this array is given by {@link #typePathOffset}.
+ *
+ * @see JVMS
+ * 4.7.20.2
+ */
+ private final byte[] typePathContainer;
+
+ /** The offset of the first byte of the type_path JVMS structure in {@link #typePathContainer}. */
+ private final int typePathOffset;
+
+ /**
+ * Constructs a new TypePath.
+ *
+ * @param typePathContainer a byte array containing a type_path JVMS structure.
+ * @param typePathOffset the offset of the first byte of the type_path structure in
+ * typePathContainer.
+ */
+ TypePath(final byte[] typePathContainer, final int typePathOffset) {
+ this.typePathContainer = typePathContainer;
+ this.typePathOffset = typePathOffset;
+ }
+
+ /**
+ * Returns the length of this path, i.e. its number of steps.
+ *
+ * @return the length of this path.
+ */
+ public int getLength() {
+ // path_length is stored in the first byte of a type_path.
+ return typePathContainer[typePathOffset];
+ }
+
+ /**
+ * Returns the value of the given step of this path.
+ *
+ * @param index an index between 0 and {@link #getLength()}, exclusive.
+ * @return one of {@link #ARRAY_ELEMENT}, {@link #INNER_TYPE}, {@link #WILDCARD_BOUND}, or {@link
+ * #TYPE_ARGUMENT}.
+ */
+ public int getStep(final int index) {
+ // Returns the type_path_kind of the path element of the given index.
+ return typePathContainer[typePathOffset + 2 * index + 1];
+ }
+
+ /**
+ * Returns the index of the type argument that the given step is stepping into. This method should
+ * only be used for steps whose value is {@link #TYPE_ARGUMENT}.
+ *
+ * @param index an index between 0 and {@link #getLength()}, exclusive.
+ * @return the index of the type argument that the given step is stepping into.
+ */
+ public int getStepArgument(final int index) {
+ // Returns the type_argument_index of the path element of the given index.
+ return typePathContainer[typePathOffset + 2 * index + 2];
+ }
+
+ /**
+ * Converts a type path in string form, in the format used by {@link #toString()}, into a TypePath
+ * object.
+ *
+ * @param typePath a type path in string form, in the format used by {@link #toString()}. May be
+ * null or empty.
+ * @return the corresponding TypePath object, or null if the path is empty.
+ */
+ public static TypePath fromString(final String typePath) {
+ if (typePath == null || typePath.length() == 0) {
+ return null;
+ }
+ int typePathLength = typePath.length();
+ ByteVector output = new ByteVector(typePathLength);
+ output.putByte(0);
+ int typePathIndex = 0;
+ while (typePathIndex < typePathLength) {
+ char c = typePath.charAt(typePathIndex++);
+ if (c == '[') {
+ output.put11(ARRAY_ELEMENT, 0);
+ } else if (c == '.') {
+ output.put11(INNER_TYPE, 0);
+ } else if (c == '*') {
+ output.put11(WILDCARD_BOUND, 0);
+ } else if (c >= '0' && c <= '9') {
+ int typeArg = c - '0';
+ while (typePathIndex < typePathLength) {
+ c = typePath.charAt(typePathIndex++);
+ if (c >= '0' && c <= '9') {
+ typeArg = typeArg * 10 + c - '0';
+ } else if (c == ';') {
+ break;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ output.put11(TYPE_ARGUMENT, typeArg);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ output.data[0] = (byte) (output.length / 2);
+ return new TypePath(output.data, 0);
+ }
+
+ /**
+ * Returns a string representation of this type path. {@link #ARRAY_ELEMENT} steps are represented
+ * with '[', {@link #INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND} steps with '*' and {@link
+ * #TYPE_ARGUMENT} steps with their type argument index in decimal form followed by ';'.
+ */
+ @Override
+ public String toString() {
+ int length = getLength();
+ StringBuilder result = new StringBuilder(length * 2);
+ for (int i = 0; i < length; ++i) {
+ switch (getStep(i)) {
+ case ARRAY_ELEMENT:
+ result.append('[');
+ break;
+ case INNER_TYPE:
+ result.append('.');
+ break;
+ case WILDCARD_BOUND:
+ result.append('*');
+ break;
+ case TYPE_ARGUMENT:
+ result.append(getStepArgument(i)).append(';');
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Puts the type_path JVMS structure corresponding to the given TypePath into the given
+ * ByteVector.
+ *
+ * @param typePath a TypePath instance, or null for empty paths.
+ * @param output where the type path must be put.
+ */
+ static void put(final TypePath typePath, final ByteVector output) {
+ if (typePath == null) {
+ output.putByte(0);
+ } else {
+ int length = typePath.typePathContainer[typePath.typePathOffset] * 2 + 1;
+ output.putByteArray(typePath.typePathContainer, typePath.typePathOffset, length);
+ }
+ }
+}
diff --git a/src/jvm/clojure/asm/TypeReference.java b/src/jvm/clojure/asm/TypeReference.java
new file mode 100644
index 0000000000..8de8fb1972
--- /dev/null
+++ b/src/jvm/clojure/asm/TypeReference.java
@@ -0,0 +1,436 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+package clojure.asm;
+
+/**
+ * A reference to a type appearing in a class, field or method declaration, or on an instruction.
+ * Such a reference designates the part of the class where the referenced type is appearing (e.g. an
+ * 'extends', 'implements' or 'throws' clause, a 'new' instruction, a 'catch' clause, a type cast, a
+ * local variable declaration, etc).
+ *
+ * @author Eric Bruneton
+ */
+public class TypeReference {
+
+ /**
+ * The sort of type references that target a type parameter of a generic class. See {@link
+ * #getSort}.
+ */
+ public static final int CLASS_TYPE_PARAMETER = 0x00;
+
+ /**
+ * The sort of type references that target a type parameter of a generic method. See {@link
+ * #getSort}.
+ */
+ public static final int METHOD_TYPE_PARAMETER = 0x01;
+
+ /**
+ * The sort of type references that target the super class of a class or one of the interfaces it
+ * implements. See {@link #getSort}.
+ */
+ public static final int CLASS_EXTENDS = 0x10;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a generic class. See
+ * {@link #getSort}.
+ */
+ public static final int CLASS_TYPE_PARAMETER_BOUND = 0x11;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a generic method. See
+ * {@link #getSort}.
+ */
+ public static final int METHOD_TYPE_PARAMETER_BOUND = 0x12;
+
+ /** The sort of type references that target the type of a field. See {@link #getSort}. */
+ public static final int FIELD = 0x13;
+
+ /** The sort of type references that target the return type of a method. See {@link #getSort}. */
+ public static final int METHOD_RETURN = 0x14;
+
+ /**
+ * The sort of type references that target the receiver type of a method. See {@link #getSort}.
+ */
+ public static final int METHOD_RECEIVER = 0x15;
+
+ /**
+ * The sort of type references that target the type of a formal parameter of a method. See {@link
+ * #getSort}.
+ */
+ public static final int METHOD_FORMAL_PARAMETER = 0x16;
+
+ /**
+ * The sort of type references that target the type of an exception declared in the throws clause
+ * of a method. See {@link #getSort}.
+ */
+ public static final int THROWS = 0x17;
+
+ /**
+ * The sort of type references that target the type of a local variable in a method. See {@link
+ * #getSort}.
+ */
+ public static final int LOCAL_VARIABLE = 0x40;
+
+ /**
+ * The sort of type references that target the type of a resource variable in a method. See {@link
+ * #getSort}.
+ */
+ public static final int RESOURCE_VARIABLE = 0x41;
+
+ /**
+ * The sort of type references that target the type of the exception of a 'catch' clause in a
+ * method. See {@link #getSort}.
+ */
+ public static final int EXCEPTION_PARAMETER = 0x42;
+
+ /**
+ * The sort of type references that target the type declared in an 'instanceof' instruction. See
+ * {@link #getSort}.
+ */
+ public static final int INSTANCEOF = 0x43;
+
+ /**
+ * The sort of type references that target the type of the object created by a 'new' instruction.
+ * See {@link #getSort}.
+ */
+ public static final int NEW = 0x44;
+
+ /**
+ * The sort of type references that target the receiver type of a constructor reference. See
+ * {@link #getSort}.
+ */
+ public static final int CONSTRUCTOR_REFERENCE = 0x45;
+
+ /**
+ * The sort of type references that target the receiver type of a method reference. See {@link
+ * #getSort}.
+ */
+ public static final int METHOD_REFERENCE = 0x46;
+
+ /**
+ * The sort of type references that target the type declared in an explicit or implicit cast
+ * instruction. See {@link #getSort}.
+ */
+ public static final int CAST = 0x47;
+
+ /**
+ * The sort of type references that target a type parameter of a generic constructor in a
+ * constructor call. See {@link #getSort}.
+ */
+ public static final int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48;
+
+ /**
+ * The sort of type references that target a type parameter of a generic method in a method call.
+ * See {@link #getSort}.
+ */
+ public static final int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49;
+
+ /**
+ * The sort of type references that target a type parameter of a generic constructor in a
+ * constructor reference. See {@link #getSort}.
+ */
+ public static final int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A;
+
+ /**
+ * The sort of type references that target a type parameter of a generic method in a method
+ * reference. See {@link #getSort}.
+ */
+ public static final int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B;
+
+ /**
+ * The target_type and target_info structures - as defined in the Java Virtual Machine
+ * Specification (JVMS) - corresponding to this type reference. target_type uses one byte, and all
+ * the target_info union fields use up to 3 bytes (except localvar_target, handled with the
+ * specific method {@link MethodVisitor#visitLocalVariableAnnotation}). Thus, both structures can
+ * be stored in an int.
+ *
+ *
This int field stores target_type (called the TypeReference 'sort' in the public API of this
+ * class) in its most significant byte, followed by the target_info fields. Depending on
+ * target_type, 1, 2 or even 3 least significant bytes of this field are unused. target_info
+ * fields which reference bytecode offsets are set to 0 (these offsets are ignored in ClassReader,
+ * and recomputed in MethodWriter).
+ *
+ * @see JVMS
+ * 4.7.20
+ * @see JVMS
+ * 4.7.20.1
+ */
+ private final int targetTypeAndInfo;
+
+ /**
+ * Constructs a new TypeReference.
+ *
+ * @param typeRef the int encoded value of the type reference, as received in a visit method
+ * related to type annotations, such as {@link ClassVisitor#visitTypeAnnotation}.
+ */
+ public TypeReference(final int typeRef) {
+ this.targetTypeAndInfo = typeRef;
+ }
+
+ /**
+ * Returns a type reference of the given sort.
+ *
+ * @param sort one of {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link
+ * #LOCAL_VARIABLE}, {@link #RESOURCE_VARIABLE}, {@link #INSTANCEOF}, {@link #NEW}, {@link
+ * #CONSTRUCTOR_REFERENCE}, or {@link #METHOD_REFERENCE}.
+ * @return a type reference of the given sort.
+ */
+ public static TypeReference newTypeReference(final int sort) {
+ return new TypeReference(sort << 24);
+ }
+
+ /**
+ * Returns a reference to a type parameter of a generic class or method.
+ *
+ * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}.
+ * @param paramIndex the type parameter index.
+ * @return a reference to the given generic class or method type parameter.
+ */
+ public static TypeReference newTypeParameterReference(final int sort, final int paramIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to a type parameter bound of a generic class or method.
+ *
+ * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}.
+ * @param paramIndex the type parameter index.
+ * @param boundIndex the type bound index within the above type parameters.
+ * @return a reference to the given generic class or method type parameter bound.
+ */
+ public static TypeReference newTypeParameterBoundReference(
+ final int sort, final int paramIndex, final int boundIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16) | (boundIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the super class or to an interface of the 'implements' clause of a
+ * class.
+ *
+ * @param itfIndex the index of an interface in the 'implements' clause of a class, or -1 to
+ * reference the super class of the class.
+ * @return a reference to the given super type of a class.
+ */
+ public static TypeReference newSuperTypeReference(final int itfIndex) {
+ return new TypeReference((CLASS_EXTENDS << 24) | ((itfIndex & 0xFFFF) << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a formal parameter of a method.
+ *
+ * @param paramIndex the formal parameter index.
+ * @return a reference to the type of the given method formal parameter.
+ */
+ public static TypeReference newFormalParameterReference(final int paramIndex) {
+ return new TypeReference((METHOD_FORMAL_PARAMETER << 24) | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to the type of an exception, in a 'throws' clause of a method.
+ *
+ * @param exceptionIndex the index of an exception in a 'throws' clause of a method.
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newExceptionReference(final int exceptionIndex) {
+ return new TypeReference((THROWS << 24) | (exceptionIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of the exception declared in a 'catch' clause of a method.
+ *
+ * @param tryCatchBlockIndex the index of a try catch block (using the order in which they are
+ * visited with visitTryCatchBlock).
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newTryCatchReference(final int tryCatchBlockIndex) {
+ return new TypeReference((EXCEPTION_PARAMETER << 24) | (tryCatchBlockIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a type argument in a constructor or method call or
+ * reference.
+ *
+ * @param sort one of {@link #CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link
+ * #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link
+ * #METHOD_REFERENCE_TYPE_ARGUMENT}.
+ * @param argIndex the type argument index.
+ * @return a reference to the type of the given type argument.
+ */
+ public static TypeReference newTypeArgumentReference(final int sort, final int argIndex) {
+ return new TypeReference((sort << 24) | argIndex);
+ }
+
+ /**
+ * Returns the sort of this type reference.
+ *
+ * @return one of {@link #CLASS_TYPE_PARAMETER}, {@link #METHOD_TYPE_PARAMETER}, {@link
+ * #CLASS_EXTENDS}, {@link #CLASS_TYPE_PARAMETER_BOUND}, {@link #METHOD_TYPE_PARAMETER_BOUND},
+ * {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link
+ * #METHOD_FORMAL_PARAMETER}, {@link #THROWS}, {@link #LOCAL_VARIABLE}, {@link
+ * #RESOURCE_VARIABLE}, {@link #EXCEPTION_PARAMETER}, {@link #INSTANCEOF}, {@link #NEW},
+ * {@link #CONSTRUCTOR_REFERENCE}, {@link #METHOD_REFERENCE}, {@link #CAST}, {@link
+ * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link
+ * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}.
+ */
+ public int getSort() {
+ return targetTypeAndInfo >>> 24;
+ }
+
+ /**
+ * Returns the index of the type parameter referenced by this type reference. This method must
+ * only be used for type references whose sort is {@link #CLASS_TYPE_PARAMETER}, {@link
+ * #METHOD_TYPE_PARAMETER}, {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link
+ * #METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeParameterIndex() {
+ return (targetTypeAndInfo & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the type parameter bound, within the type parameter {@link
+ * #getTypeParameterIndex}, referenced by this type reference. This method must only be used for
+ * type references whose sort is {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link
+ * #METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter bound index.
+ */
+ public int getTypeParameterBoundIndex() {
+ return (targetTypeAndInfo & 0x0000FF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the "super type" of a class that is referenced by this type reference.
+ * This method must only be used for type references whose sort is {@link #CLASS_EXTENDS}.
+ *
+ * @return the index of an interface in the 'implements' clause of a class, or -1 if this type
+ * reference references the type of the super class.
+ */
+ public int getSuperTypeIndex() {
+ return (short) ((targetTypeAndInfo & 0x00FFFF00) >> 8);
+ }
+
+ /**
+ * Returns the index of the formal parameter whose type is referenced by this type reference. This
+ * method must only be used for type references whose sort is {@link #METHOD_FORMAL_PARAMETER}.
+ *
+ * @return a formal parameter index.
+ */
+ public int getFormalParameterIndex() {
+ return (targetTypeAndInfo & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the exception, in a 'throws' clause of a method, whose type is referenced
+ * by this type reference. This method must only be used for type references whose sort is {@link
+ * #THROWS}.
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getExceptionIndex() {
+ return (targetTypeAndInfo & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the try catch block (using the order in which they are visited with
+ * visitTryCatchBlock), whose 'catch' type is referenced by this type reference. This method must
+ * only be used for type references whose sort is {@link #EXCEPTION_PARAMETER} .
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getTryCatchBlockIndex() {
+ return (targetTypeAndInfo & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the type argument referenced by this type reference. This method must only
+ * be used for type references whose sort is {@link #CAST}, {@link
+ * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link
+ * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeArgumentIndex() {
+ return targetTypeAndInfo & 0xFF;
+ }
+
+ /**
+ * Returns the int encoded value of this type reference, suitable for use in visit methods related
+ * to type annotations, like visitTypeAnnotation.
+ *
+ * @return the int encoded value of this type reference.
+ */
+ public int getValue() {
+ return targetTypeAndInfo;
+ }
+
+ /**
+ * Puts the given target_type and target_info JVMS structures into the given ByteVector.
+ *
+ * @param targetTypeAndInfo a target_type and a target_info structures encoded as in {@link
+ * #targetTypeAndInfo}. LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported.
+ * @param output where the type reference must be put.
+ */
+ static void putTarget(final int targetTypeAndInfo, final ByteVector output) {
+ switch (targetTypeAndInfo >>> 24) {
+ case CLASS_TYPE_PARAMETER:
+ case METHOD_TYPE_PARAMETER:
+ case METHOD_FORMAL_PARAMETER:
+ output.putShort(targetTypeAndInfo >>> 16);
+ break;
+ case FIELD:
+ case METHOD_RETURN:
+ case METHOD_RECEIVER:
+ output.putByte(targetTypeAndInfo >>> 24);
+ break;
+ case CAST:
+ case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+ case METHOD_INVOCATION_TYPE_ARGUMENT:
+ case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+ case METHOD_REFERENCE_TYPE_ARGUMENT:
+ output.putInt(targetTypeAndInfo);
+ break;
+ case CLASS_EXTENDS:
+ case CLASS_TYPE_PARAMETER_BOUND:
+ case METHOD_TYPE_PARAMETER_BOUND:
+ case THROWS:
+ case EXCEPTION_PARAMETER:
+ case INSTANCEOF:
+ case NEW:
+ case CONSTRUCTOR_REFERENCE:
+ case METHOD_REFERENCE:
+ output.put12(targetTypeAndInfo >>> 24, (targetTypeAndInfo & 0xFFFF00) >> 8);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/src/jvm/clojure/asm/commons/AdviceAdapter.java b/src/jvm/clojure/asm/commons/AdviceAdapter.java
deleted file mode 100644
index 8ec38d2281..0000000000
--- a/src/jvm/clojure/asm/commons/AdviceAdapter.java
+++ /dev/null
@@ -1,625 +0,0 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-package clojure.asm.commons;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import clojure.asm.Handle;
-import clojure.asm.Label;
-import clojure.asm.MethodVisitor;
-import clojure.asm.Opcodes;
-import clojure.asm.Type;
-
-/**
- * A {@link clojure.asm.MethodVisitor} to insert before, after and around
- * advices in methods and constructors.
- *
- * The behavior for constructors is like this:
- *
- *
- *
as long as the INVOKESPECIAL for the object initialization has not been
- * reached, every bytecode instruction is dispatched in the ctor code visitor
- *
- *
when this one is reached, it is only added in the ctor code visitor and a
- * JP invoke is added
- *
- *
after that, only the other code visitor receives the instructions
- *
- *
- *
- * @author Eugene Kuleshov
- * @author Eric Bruneton
- */
-public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {
-
- private static final Object THIS = new Object();
-
- private static final Object OTHER = new Object();
-
- protected int methodAccess;
-
- protected String methodDesc;
-
- private boolean constructor;
-
- private boolean superInitialized;
-
- private List