diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml
new file mode 100644
index 0000000000..af34676e5f
--- /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@v5
+ with:
+ java-version: 8
+ distribution: 'temurin'
+
+ - name: Set up Clojure
+ uses: DeLaGuardo/setup-clojure@13.4.0
+ with:
+ cli: 'latest'
+
+ - name: Cache clojure dependencies
+ uses: actions/cache@v6
+ with:
+ path: |
+ ~/.m2/repository
+ ~/.gitlibs
+ key: cljdeps-${{ hashFiles('deps.edn') }}
+ restore-keys: cljdeps-
+
+ - name: Clone clojure api doc repo
+ uses: actions/checkout@v6
+ 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@v6
+ with:
+ path: clojure-api-doc/repo
+ fetch-depth: 0
+
+ - name: Clone clojure gh-pages branch into clojure-api-doc
+ uses: actions/checkout@v6
+ 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..f956b15b4c
--- /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/release17.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..96a7d2b37d
--- /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/snapshot17.yml@master
+ secrets: inherit
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000..042492c5d2
--- /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: ["17", "21", "25"]
+ distribution: ["temurin", "corretto"]
+ profile: ["test-direct", "test-no-direct"]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v6
+ - name: Set up Java
+ uses: actions/setup-java@v5
+ 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/build.xml b/build.xml
index 34fd65b2c6..c2ab3dadb0 100644
--- a/build.xml
+++ b/build.xml
@@ -41,7 +41,7 @@
description="Compile Java sources.">
+ debug="true" release="17"/>
+
+
+
@@ -86,6 +89,8 @@
+
+
@@ -94,7 +99,7 @@
unless="maven.test.skip">
+ debug="true" release="17" includeantruntime="no"/>
Direct linking = ${directlinking}
+# 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)
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/pom.xml b/pom.xml
index 2dd37faaad..cf712bffab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
clojureclojurejar
- 1.12.0-master-SNAPSHOT
+ 1.13.0-master-SNAPSHOThttp://clojure.org/Clojure core environment and runtime library.
@@ -41,17 +41,17 @@
org.clojurespec.alpha
- 0.3.218
+ 0.6.249org.clojurecore.specs.alpha
- 0.2.62
+ 0.6.122-alpha8org.clojuretest.generative
- 1.0.0
+ 1.1.1test
@@ -63,7 +63,7 @@
org.clojuretest.check
- 1.1.1
+ 1.1.3test
@@ -75,16 +75,19 @@
javax.xml.wsjaxws-api
- 2.3.0
+ 2.3.1test
+
+ central
+ https://central.sonatype.com
+
-
- sonatype-nexus-staging
- https://oss.sonatype.org/content/repositories/snapshots
+ central-snapshot
+ https://central.sonatype.com/repository/maven-snapshots/
@@ -103,16 +106,15 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.1
+ 3.13.0
- 1.8
- 1.8
+ 17UTF-8maven-antrun-plugin
- 1.6
+ 3.1.0clojure-compile
@@ -142,10 +144,11 @@
+
org.codehaus.mojobuild-helper-maven-plugin
- 1.5
+ 3.5.0add-clojure-source-dirs
@@ -163,7 +166,7 @@
maven-assembly-plugin
- 2.2
+ 3.7.1clojure-slim-jar
@@ -181,11 +184,11 @@
maven-jar-plugin
- 2.3.1
+ 3.4.1maven-source-plugin
- 2.1.2
+ 3.3.1attach-sources
@@ -207,7 +210,7 @@
instead, push SCM changes in Hudson configuration -->
org.apache.maven.pluginsmaven-release-plugin
- 2.5.3
+ 3.0.1falsetrue
@@ -217,23 +220,39 @@
org.apache.maven.pluginsmaven-surefire-plugin
- 2.6
+ 3.2.5true
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.8
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.11.2
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+ none
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.7.0true
-
- sonatype-nexus-staging
- https://oss.sonatype.org/
- true
+ central
+ true
@@ -261,7 +280,7 @@
maven-assembly-plugin
- 2.2
+ 3.7.1clojure-distribution
@@ -289,7 +308,7 @@
org.apache.maven.pluginsmaven-gpg-plugin
- 1.5
+ 3.2.4sign-artifacts
@@ -309,7 +328,7 @@
org.clojuretest.check
- 0.9.0
+ 1.1.1org.clojure
@@ -323,7 +342,7 @@
org.apache.maven.pluginsmaven-shade-plugin
- 3.1.0
+ 3.5.3package
diff --git a/readme.txt b/readme.txt
index 815cf9126d..12fa75dea8 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,24 +1,21 @@
* 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: https://clojure.org
-Feedback: http://groups.google.com/group/clojure
+Feedback: https://ask.clojure.org
Getting Started: https://clojure.org/guides/getting_started
-To build and run locally with Ant:
-
- One-time setup: ./antsetup.sh
- To build: ant local
- To run: java -jar clojure.jar
-
To build locally with Maven:
+ To test:
+ mvn test
+
To build (output JARs in target/):
mvn package
@@ -76,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 1e48fcc9c6..599afa49fd 100644
--- a/src/clj/clojure/core.clj
+++ b/src/clj/clojure/core.clj
@@ -1460,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."
@@ -1516,6 +1520,14 @@
([map key not-found]
(. clojure.lang.RT (get map key not-found))))
+(defn req!
+ "Like arity-2 'get', but throws if key not present."
+ {:inline (fn [m k] `(clojure.lang.RT/req ~m ~k))
+ :inline-arities #{2}
+ :added "1.13"}
+ [m k]
+ (clojure.lang.RT/req m k))
+
(defn dissoc
"dissoc[iate]. Returns a new map of the same (hashed/sorted) type,
that does not contain a mapping for key(s)."
@@ -1552,21 +1564,6 @@
:static true}
[map key] (. clojure.lang.RT (find map key)))
-(defn select-keys
- "Returns a map containing only those entries in map whose key is in keys"
- {:added "1.0"
- :static true}
- [map keyseq]
- (loop [ret {} keys (seq keyseq)]
- (if keys
- (let [entry (. clojure.lang.RT (find map (first keys)))]
- (recur
- (if entry
- (conj ret entry)
- ret)
- (next keys)))
- (with-meta ret (meta map)))))
-
(defn keys
"Returns a sequence of the map's keys, in the same order as (seq map)."
{:added "1.0"
@@ -2795,7 +2792,7 @@
{:added "1.0"}
[& names] `(do ~@(map #(list 'def (vary-meta % assoc :declared true)) names)))
-(declare cat)
+(declare cat select-keys)
(defn mapcat
"Returns the result of applying concat to the result of applying map
@@ -2942,7 +2939,11 @@
(rf result input))))))))
([n coll]
(if (instance? clojure.lang.IDrop coll)
- (or (.drop ^clojure.lang.IDrop coll n) ())
+ (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)
@@ -3030,7 +3031,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) )
@@ -3045,15 +3047,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))))
@@ -3170,7 +3172,9 @@
:static true}
[coll n]
(if (instance? clojure.lang.IDrop coll)
- (.drop ^clojure.lang.IDrop coll n)
+ (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))
@@ -3181,12 +3185,16 @@
{:added "1.3"
:static true}
[coll n]
- (if (instance? clojure.lang.IDrop coll)
- (or (.drop ^clojure.lang.IDrop 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
@@ -3347,7 +3355,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]
@@ -3852,7 +3868,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"))))
@@ -4395,6 +4411,41 @@
(throw (IllegalArgumentException. (str "No value supplied for key: " (last keyvals))))
(clojure.lang.PersistentArrayMap/createAsIfByAssoc ary)))))
+(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))))
+
+(defn repeatedly
+ "Takes a function of no args, presumably with side effects, and
+ returns an infinite (or length n if supplied) lazy sequence of calls
+ to it"
+ {:added "1.0"
+ :static true}
+ ([f] (lazy-seq (cons (f) (repeatedly f))))
+ ([n f] (take n (repeatedly f))))
+
+(declare reduce-kv)
+
+(defn some-vals
+ "Returns a map with only the non-nil values of map m. Returns nil if
+ m has no non-nil vals."
+ {:added "1.13"
+ :static true}
+ [m]
+ (reduce-kv
+ (fn [m k v] (if (some? v) (assoc m k v) m))
+ nil m))
+
(defn seq-to-map-for-destructuring
"Builds a map from a seq as described in
https://clojure.org/reference/special_forms#keyword-arguments"
@@ -4405,97 +4456,176 @@
(if (seq s) (first s) clojure.lang.PersistentArrayMap/EMPTY)))
;;redefine let and loop with destructuring
+(defn ^:private destvec*
+ [pb 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))))
+
+(defn ^:private destmap*
+ [pb bvec b v]
+ (let [gmap (gensym "map__")
+ gmapseq (with-meta gmap {:tag 'clojure.lang.ISeq})
+ gignore (gensym "ignore__")
+ defaults (:or b)
+ defaults-as (:defaults b)
+ _ (when (and defaults-as (not defaults))
+ (throw (new IllegalArgumentException "Can't specify :defaults without :or")))
+ b (dissoc b :defaults)
+ gdefaults (when defaults (zipmap (keys defaults) (repeatedly #(gensym "default__"))))
+ select (:select b)
+ all (:all b)
+ xf (fn [mk]
+ (let [mkns (namespace mk)
+ mkn (name mk)]
+ (cond (.startsWith mkn "keys") #(keyword (or mkns (namespace %)) (name %))
+ (.startsWith mkn "syms") #(list `quote (symbol (or mkns (namespace %)) (name %)))
+ (.startsWith mkn "strs") str
+ :else (throw (new Exception (str "Unsupported map directive: " mk) )))))
+ ret (reduce1 (fn [ret e]
+ (conj ret (val e) (defaults (key e))))
+ bvec gdefaults)
+ ret (-> ret (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 (dissoc b :as :or :select :all)
+ localize (fn [bb] (if (instance? clojure.lang.Named bb)
+ (with-meta (symbol nil (name bb)) (meta bb)) bb))
+ push1 (fn [ret bb bk req?]
+ (let [getter (if req? `req! `get)
+ local (localize bb)
+ local-default? (contains? defaults local)
+ key-default? (contains? defaults bk)
+ bv (if (or local-default? key-default?)
+ (if (and local-default? key-default?)
+ (throw (new Exception
+ (str "Multiple :or defaults for same key: " bk " '" local "'")))
+ (if req?
+ (throw (new Exception
+ (str "Can't supply default value for required key: " bk)))
+ (list `get gmap bk (if local-default? (gdefaults local) (gdefaults bk)))))
+ (list getter gmap bk))]
+ (if (ident? bb)
+ (-> ret (conj local bv))
+ (pb ret bb bv))))
+ retsel
+ (loop [ret ret, sel #{}, bes bes, b->k {}, subs nil, suba nil]
+ (if (seq bes)
+ (let [be (first bes), bb (key be), bk (val be)]
+ (if (keyword? bb)
+ (let [dir bb
+ tr (xf bb)
+ req? (.endsWith (name bb) "!")
+ retsel
+ (loop [ret ret, sel sel, bbs (seq bk), preamp? true, b->k b->k]
+ (if (seq bbs)
+ (let [bb (first bbs)]
+ (if (= bb '&)
+ (if preamp?
+ (recur ret sel (next bbs) false b->k)
+ (throw (new IllegalArgumentException (str "& can only appear once in " dir))))
+ (let [_
+ (when (and (not preamp?) (symbol? bb))
+ (throw
+ (new IllegalArgumentException
+ (str "'" bb
+ "' - binding symbols can only appear before '&', use keys after"))))
+ bk (if preamp? (tr bb) bb)]
+ (recur (if (or preamp? req?)
+ (push1 ret (if preamp? bb gignore) bk req?)
+ ret)
+ (conj sel bk)
+ (next bbs) preamp?
+ (if preamp? (assoc b->k (localize bb) bk) b->k)))))
+ {:ret ret, :sel sel, :b->k b->k}))]
+ (recur (:ret retsel) (:sel retsel) (next bes) (:b->k retsel) subs suba))
+ (let [subsel? (and select (map? bb))
+ bb (if (or (not subsel?) (:select bb))
+ bb
+ (assoc bb :select (gensym "select__")))
+ subs (if subsel? (assoc subs bk (:select bb)) subs)
+
+ suball? (and all (map? bb))
+ bb (if (or (not suball?) (:all bb))
+ bb
+ (assoc bb :all (gensym "all__")))
+ suba (if suball? (assoc suba bk (:all bb)) suba)
+
+ b->k (if (symbol? bb) (assoc b->k bb bk) b->k)]
+ (recur (push1 ret bb bk false) (conj sel bk) (next bes) b->k subs suba))))
+ {:ret ret, :sel sel, :b->k b->k :subs subs :suba suba}))
+ ret (:ret retsel), sel (:sel retsel), b->k (:b->k retsel)
+ new-or-code (and defaults (or defaults-as select all))
+ bk #(if (symbol? %)
+ (let [bk (b->k %)]
+ (when (and new-or-code (not bk))
+ (throw (new IllegalArgumentException (str "symbol " % " in :or does not refer to a binding"))))
+ bk)
+ %)
+ dm (when defaults (dissoc (zipmap (map bk (keys gdefaults)) (vals gdefaults)) nil))
+ _ (and new-or-code (not= (count (select-keys dm sel)) (count defaults))
+ (throw (new IllegalArgumentException (str "keys "
+ (apply disj (set (keys dm)) sel)
+ " appear only in :or"))))
+ ret (if select
+ (conj ret select `(when-let [mm# (merge (some-vals ~dm) ~gmap (some-vals ~(:subs retsel)))]
+ (select-keys mm# ~sel)))
+ ret)
+ ret (if all
+ (conj ret all `(merge (some-vals ~dm) ~gmap (some-vals ~(:suba retsel))))
+ ret)
+ ret (if defaults-as (conj ret defaults-as dm) ret)]
+ ret))
+
(defn destructure [bindings]
(let [bents (partition 2 bindings)
pb (fn pb [bvec b v]
- (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))))))
+ (cond
+ (symbol? b) (-> bvec (conj b) (conj v))
+ (vector? b) (destvec* pb bvec b v)
+ (map? b) (destmap* pb 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
@@ -4845,8 +4975,11 @@
(.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*
@@ -5171,15 +5304,6 @@
(take-while (mk-bound-fn sc start-test start-key)
(if ((mk-bound-fn sc end-test end-key) e) s (next s))))))
-(defn repeatedly
- "Takes a function of no args, presumably with side effects, and
- returns an infinite (or length n if supplied) lazy sequence of calls
- to it"
- {:added "1.0"
- :static true}
- ([f] (lazy-seq (cons (f) (repeatedly f))))
- ([n f] (take n (repeatedly f))))
-
(defn add-classpath
"DEPRECATED
@@ -5382,17 +5506,17 @@
[xs] `(. clojure.lang.Numbers booleans ~xs))
(definline bytes
- "Casts to bytes[]"
+ "Casts to byte[]"
{:added "1.1"}
[xs] `(. clojure.lang.Numbers bytes ~xs))
(definline chars
- "Casts to chars[]"
+ "Casts to char[]"
{:added "1.1"}
[xs] `(. clojure.lang.Numbers chars ~xs))
(definline shorts
- "Casts to shorts[]"
+ "Casts to short[]"
{:added "1.1"}
[xs] `(. clojure.lang.Numbers shorts ~xs))
@@ -6340,6 +6464,11 @@ fails, attempts to require sym's namespace and retries."
: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
@@ -6574,6 +6703,11 @@ fails, attempts to require sym's namespace and retries."
"
{: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"
@@ -6625,20 +6759,6 @@ fails, attempts to require sym's namespace and retries."
([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]
(-> x (bit-shift-right shift) (bit-and mask)))
@@ -6817,8 +6937,6 @@ fails, attempts to require sym's namespace and retries."
`(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")
@@ -6828,6 +6946,45 @@ fails, attempts to require sym's namespace and retries."
(load "core/protocols")
(load "gvec")
+(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)
@@ -6874,6 +7031,7 @@ fails, attempts to require sym's namespace and retries."
: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
@@ -6957,8 +7115,9 @@ fails, attempts to require sym's namespace and retries."
(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}
([] [])
@@ -7005,6 +7164,19 @@ fails, attempts to require sym's namespace and retries."
coll)
persistent!))
+(defn select-keys
+ "Returns a map containing only those entries in map whose key is in keyseq"
+ {:added "1.0"}
+ [map keyseq]
+ (with-meta
+ (persistent!
+ (reduce (fn [ret k]
+ (if-let [entry (clojure.lang.RT/find map k)]
+ (conj! ret entry)
+ ret))
+ (transient {}) keyseq))
+ (meta map)))
+
(require '[clojure.java.io :as jio])
(defn- normalize-slurp-opts
@@ -7375,7 +7547,7 @@ fails, attempts to require sym's namespace and retries."
(let [p (into [] (take n) s)]
(if (= n (count p))
(cons p (partitionv n step pad (nthrest s step)))
- (into [] (take n) (concat p pad))))))))
+ (list (into [] (take n) (concat p pad)))))))))
(defn partitionv-all
"Returns a lazy sequence of vector partitions, but may include
diff --git a/src/clj/clojure/core/protocols.clj b/src/clj/clojure/core/protocols.clj
index 10c022911d..2ed43152b6 100644
--- a/src/clj/clojure/core/protocols.clj
+++ b/src/clj/clojure/core/protocols.clj
@@ -30,27 +30,26 @@
(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
diff --git a/src/clj/clojure/core/server.clj b/src/clj/clojure/core/server.clj
index 3cda4d1f74..d9b60eefa4 100644
--- a/src/clj/clojure/core/server.clj
+++ b/src/clj/clojure/core/server.clj
@@ -13,9 +13,10 @@
[clojure.edn :as edn]
[clojure.main :as m])
(:import
- [clojure.lang LineNumberingPushbackReader]
+ [clojure.lang LineNumberingPushbackReader DynamicClassLoader]
[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)
@@ -145,14 +146,16 @@
(defn- parse-props
"Parse clojure.server.* from properties to produce a map of server configs."
- [props]
+ [^Properties props]
(reduce
- (fn [acc [^String k ^String v]]
- (let [[k1 k2 k3] (str/split k #"\.")]
- (if (and (= k1 "clojure") (= k2 "server"))
- (conj acc (merge {:name k3} (edn/read-string v)))
- acc)))
- [] props))
+ (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."
@@ -215,13 +218,16 @@
Alpha, subject to change."
{:added "1.10"}
[in-reader out-fn & {:keys [stdin]}]
+ (let [cl (.getContextClassLoader (Thread/currentThread))]
+ (.setContextClassLoader (Thread/currentThread) (DynamicClassLoader. cl)))
(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)
- *err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil)]
+ (binding [*repl* true
+ *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 []
diff --git a/src/clj/clojure/core_deftype.clj b/src/clj/clojure/core_deftype.clj
index c2babab291..3f0ae13298 100644
--- a/src/clj/clojure/core_deftype.clj
+++ b/src/clj/clojure/core_deftype.clj
@@ -16,6 +16,19 @@
[ns]
(.replace (str ns) \- \_))
+(defn- resolve-unqualified-class-tag
+ "Resolves unqualified class tag symbol to fully qualified class tag symbol.
+ Returns all other tags unmodified."
+ [tag]
+ (let [array-tag? '#{ints longs floats doubles chars shorts bytes booleans objects}]
+ (if-let [c (and (instance? clojure.lang.Symbol tag)
+ (= (.indexOf (.getName ^clojure.lang.Symbol tag) ".") -1)
+ (not (array-tag? tag))
+ (let [resolved (resolve tag)]
+ (when (class? resolved) resolved)))]
+ (symbol (.getName ^Class c))
+ tag)))
+
;for now, built on gen-interface
(defmacro definterface
"Creates a new Java interface with the given name and method sigs.
@@ -27,7 +40,7 @@
(^Bar method2 [^Baz b ^Quux q]))"
{:added "1.2"} ;; Present since 1.2, but made public in 1.5.
[name & sigs]
- (let [tag (fn [x] (or (:tag (meta x)) Object))
+ (let [tag (fn [x] (or (resolve-unqualified-class-tag (:tag (meta x))) Object))
psig (fn [[name [& args]]]
(vector name (vec (map tag args)) (tag name) (map meta args)))
cname (with-meta (symbol (str (namespace-munge *ns*) "." name)) (meta name))]
@@ -101,18 +114,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.
@@ -652,15 +666,11 @@
[opts sigs]))
sigs (when sigs
(reduce1 (fn [m s]
- (let [tag-to-class (fn [tag]
- (if-let [c (and (instance? clojure.lang.Symbol tag)
- (= (.indexOf (.getName ^clojure.lang.Symbol tag) ".") -1)
- (not (contains? '#{int long float double char short byte boolean void
- ints longs floats doubles chars shorts bytes booleans objects} tag))
- (resolve tag))]
- (symbol (.getName c))
- tag))
- name-meta (update-in (meta (first s)) [:tag] tag-to-class)
+ (let [disallowed? '#{int long float double char short byte boolean void}
+ resolve-class-symbol (fn [tag]
+ (when-not (disallowed? tag)
+ (resolve-unqualified-class-tag 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)]
diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj
index 18c5101c1d..1ced9d0d5f 100644
--- a/src/clj/clojure/core_print.clj
+++ b/src/clj/clojure/core_print.clj
@@ -380,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
@@ -549,6 +551,8 @@
(.write w " ")
(print-method (:form o) w))
+(defmethod print-dup clojure.lang.TaggedLiteral [o w] (print-method o w))
+
(defmethod print-method clojure.lang.ReaderConditional [o ^Writer w]
(.write w "#?")
(when (:splicing? o) (.write w "@"))
@@ -559,10 +563,13 @@
(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"
+ 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]
- (let [sb (StringBuilder.)]
+ ([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))
@@ -578,4 +585,4 @@
(.append sb ^String str-cbuf ^int off ^int len)
(.append sb ^chars str-cbuf ^int off ^int len)))))
java.io.BufferedWriter.
- java.io.PrintWriter.)))
+ (java.io.PrintWriter. ^boolean autoflush?)))))
diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj
index 46f7b4b868..9624841361 100644
--- a/src/clj/clojure/core_proxy.clj
+++ b/src/clj/clojure/core_proxy.clj
@@ -131,7 +131,7 @@
(. gen (endMethod))))]
;start class definition
- (. cv (visit (. Opcodes V1_8) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
+ (. cv (visit clojure.lang.Compiler/JVM_BYTECODE_VERSION (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
cname nil (iname super)
(into-array (map iname (cons IProxy interfaces)))))
;add field for fn mappings
diff --git a/src/clj/clojure/genclass.clj b/src/clj/clojure/genclass.clj
index a4fa0ec5fb..721e6e4b6a 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
@@ -254,7 +265,7 @@
(. gen (endMethod))))
]
;start class definition
- (. cv (visit (. Opcodes V1_8) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
+ (. cv (visit Compiler/JVM_BYTECODE_VERSION (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))
cname nil (iname super)
(when-let [ifc (seq interfaces)]
(into-array (map iname ifc)))))
@@ -646,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,9 +674,9 @@
(IllegalArgumentException. "Interface methods must not contain '-'")))
(let [iname (.replace (str name) "." "/")
cv (clojure.lang.Compiler/classWriter)]
- (. cv visit Opcodes/V1_8 (+ Opcodes/ACC_PUBLIC
- Opcodes/ACC_ABSTRACT
- Opcodes/ACC_INTERFACE)
+ (. cv visit Compiler/JVM_BYTECODE_VERSION (+ Opcodes/ACC_PUBLIC
+ Opcodes/ACC_ABSTRACT
+ Opcodes/ACC_INTERFACE)
iname nil "java/lang/Object"
(when (seq extends)
(into-array (map #(.getInternalName (asm-type %)) extends))))
diff --git a/src/clj/clojure/gvec.clj b/src/clj/clojure/gvec.clj
index 52075c420e..590cfcecf9 100644
--- a/src/clj/clojure/gvec.clj
+++ b/src/clj/clojure/gvec.clj
@@ -87,7 +87,7 @@
(.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]
@@ -124,7 +124,7 @@
(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 [_]
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/io.clj b/src/clj/clojure/java/io.clj
index 72a30ed7a6..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))))))
@@ -385,7 +388,7 @@
(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
diff --git a/src/clj/clojure/java/javadoc.clj b/src/clj/clojure/java/javadoc.clj
index c36acc3de6..07e305ac34 100644
--- a/src/clj/clojure/java/javadoc.clj
+++ b/src/clj/clojure/java/javadoc.clj
@@ -9,40 +9,33 @@
^{:author "Christophe Grand, Stuart Sierra",
:doc "A repl helper to quickly open javadocs."}
clojure.java.javadoc
- (:use [clojure.java.browse :only (browse-url)] )
+ (:use [clojure.java.browse :only (browse-url)])
+ (:require [clojure.string :as str])
(:import
(java.io File)))
-(def ^:dynamic *feeling-lucky-url* "http://www.google.com/search?btnI=I%27m%20Feeling%20Lucky&q=allinurl:")
-(def ^:dynamic *feeling-lucky* true)
+(def ^{:dynamic true :deprecated "1.13"} *feeling-lucky-url* nil)
+(def ^{:dynamic true :deprecated "1.13"} *feeling-lucky* false)
(def ^:dynamic *local-javadocs* (ref (list)))
(def ^:dynamic *core-java-api*
- (case (System/getProperty "java.specification.version")
- "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/"))
+ (let [java-version (System/getProperty "java.specification.version")]
+ (str "https://docs.oracle.com/en/java/javase/" java-version "/docs/api/%s/")))
(def ^:dynamic *remote-javadocs*
(ref (sorted-map
- "com.google.common." "http://google.github.io/guava/releases/23.0/api/docs/"
+ "com.google.common." "https://guava.dev/releases/snapshot-jre/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/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/")))
+ "org.apache.commons.codec." "https://commons.apache.org/proper/commons-codec/apidocs/"
+ "org.apache.commons.io." "https://commons.apache.org/proper/commons-io/apidocs/"
+ "org.apache.commons.lang." "https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/"
+ "org.apache.commons.lang3." "https://commons.apache.org/proper/commons-lang/apidocs/")))
(defn add-local-javadoc
"Adds to the list of local Javadoc paths."
@@ -67,13 +60,14 @@
(format url module-name))
url))
-(defn- javadoc-url
+(defn javadoc-url
"Searches for a URL for the given class name. Tries
*local-javadocs* first, then *remote-javadocs*. Returns a string."
{:tag String,
:added "1.2"}
[^String classname]
- (let [file-path (.replace classname \. File/separatorChar)
+ (let [classname (str/replace classname #"\$.*" "")
+ file-path (.replace classname \. File/separatorChar)
url-path (.replace classname \. \/)]
(if-let [file ^File (first
(filter #(.exists ^File %)
@@ -81,13 +75,11 @@
@*local-javadocs*)))]
(-> file .toURI str)
;; If no local file, try remote URLs:
- (or (some (fn [[prefix url]]
+ (some (fn [[prefix url]]
(when (.startsWith classname prefix)
(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"))))))
+ @*remote-javadocs*))))
(defn javadoc
"Opens a browser window displaying the javadoc for the argument.
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 e769ee56ea..89baed7f81 100644
--- a/src/clj/clojure/main.clj
+++ b/src/clj/clojure/main.clj
@@ -73,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*
@@ -356,7 +355,8 @@
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,
@@ -446,24 +446,25 @@ by default when a new command-line REPL is started."} repl-requires
(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
@@ -670,6 +671,6 @@ java -cp clojure.jar clojure.main -i init.clj script.clj args...")
(catch Throwable t
(report-error t :target "file")
(System/exit 1))))
- (finally
+ (finally
(flush))))
diff --git a/src/clj/clojure/math.clj b/src/clj/clojure/math.clj
index c5b67b1f1c..ff2f75f3e3 100644
--- a/src/clj/clojure/math.clj
+++ b/src/clj/clojure/math.clj
@@ -200,6 +200,7 @@
{: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
+ 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#ceil-double-"
:inline-arities #{1}
:inline (fn [a] `(Math/ceil (double ~a)))
@@ -211,7 +212,6 @@
{: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)))
@@ -514,7 +514,7 @@
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-"
+ See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#scalb-double-int-"
:inline-arities #{2}
:inline (fn [d scaleFactor] `(Math/scalb (double ~d) (int ~scaleFactor)))
:added "1.11"}
diff --git a/src/clj/clojure/pprint/dispatch.clj b/src/clj/clojure/pprint/dispatch.clj
index 965c0b255a..2fecccde9f 100644
--- a/src/clj/clojure/pprint/dispatch.clj
+++ b/src/clj/clojure/pprint/dispatch.clj
@@ -135,32 +135,10 @@
(pprint-newline :linear)
(recur (next aseq)))))))
-(def ^{:private true}
- type-map {"core$future_call" "Future",
- "core$promise" "Promise"})
-
-(defn- map-ref-type
- "Map ugly type names to something simpler"
- [name]
- (or (when-let [match (re-find #"^[^$]+\$[^$]+" name)]
- (type-map match))
- name))
-
(defn- pprint-ideref [o]
- (let [prefix (format "#<%s@%x%s: "
- (map-ref-type (.getSimpleName (class o)))
- (System/identityHashCode o)
- (if (and (instance? clojure.lang.Agent o)
- (agent-error o))
- " FAILED"
- ""))]
- (pprint-logical-block :prefix prefix :suffix ">"
- (pprint-indent :block (-> (count prefix) (- 2) -))
- (pprint-newline :linear)
- (write-out (cond
- (and (future? o) (not (future-done? o))) :pending
- (and (instance? clojure.lang.IPending o) (not (.isRealized ^clojure.lang.IPending o))) :not-delivered
- :else @o)))))
+ (.write ^java.io.Writer *out* (str "#object[" (.getName (class o)) " " (format "0x%x" (System/identityHashCode o)) " "))
+ (write-out (#'clojure.core/deref-as-map o))
+ (.write ^java.io.Writer *out* "]"))
(def ^{:private true} pprint-pqueue (formatter-out "~<<-(~;~@{~w~^ ~_~}~;)-<~:>"))
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/test.clj b/src/clj/clojure/test.clj
index fc3536d7f3..925ebe0fa9 100644
--- a/src/clj/clojure/test.clj
+++ b/src/clj/clojure/test.clj
@@ -229,7 +229,9 @@
\"with-test-out\" macro, which rebinds *out* to the current value
of *test-out*.
- For additional event types, see the examples in the code.
+ The event types are:
+ :default :pass :fail :error :summary :begin-test-ns :end-test-ns
+ :begin-test-var :end-test-var
"}
clojure.test
(:require [clojure.template :as temp]
@@ -324,10 +326,10 @@
(defmulti
^{:doc "Generic reporting function, may be overridden to plug in
different report formats (e.g., TAP, JUnit). Assertions such as
- 'is' call 'report' to indicate results. The argument given to
- 'report' will be a map with a :type key. See the documentation at
- the top of test_is.clj for more information on the types of
- arguments for 'report'."
+ 'is' call 'report' to indicate results. The argument given to
+ 'report' will be a map with a :type key. See (doc clojure.test)
+ docs under 'EXTENDING TEST-IS' for types of arguments for
+ 'report'."
:dynamic true
:added "1.1"}
report :type)
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 fe8fb631bd..0f027e7ad0 100644
--- a/src/clj/clojure/walk.clj
+++ b/src/clj/clojure/walk.clj
@@ -41,10 +41,10 @@ the sorting function."}
{:added "1.1"}
[inner outer form]
(cond
- (list? form) (outer (apply list (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 (doall (map inner 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/zip.clj b/src/clj/clojure/zip.clj
index 7848ab068c..8d4a78da29 100644
--- a/src/clj/clojure/zip.clj
+++ b/src/clj/clojure/zip.clj
@@ -18,7 +18,7 @@
(defn zipper
"Creates a new zipper structure.
- branch? is a fn that, given a node, returns true if can have
+ branch? is a fn that, given a node, returns true if it can have
children, even if it currently doesn't.
children is a fn that, given a branch node, returns a seq of its
diff --git a/src/jvm/clojure/asm/AnnotationVisitor.java b/src/jvm/clojure/asm/AnnotationVisitor.java
index 940bfb77c6..992a4cd1e8 100644
--- a/src/jvm/clojure/asm/AnnotationVisitor.java
+++ b/src/jvm/clojure/asm/AnnotationVisitor.java
@@ -29,8 +29,8 @@
/**
* A visitor to visit a Java annotation. The methods of this class must be called in the following
- * order: ( visit | visitEnum | visitAnnotation | visitArray )*
- * visitEnd.
+ * order: ( {@code visit} | {@code visitEnum} | {@code visitAnnotation} | {@code visitArray} )*
+ * {@code visitEnd}.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
@@ -38,45 +38,63 @@
public abstract class AnnotationVisitor {
/**
- * The ASM API version implemented by this visitor. The value of this field must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
*/
protected final int api;
- /** The annotation visitor to which this visitor must delegate method calls. May be null. */
+ /**
+ * The annotation visitor to which this visitor must delegate method calls. May be {@literal
+ * null}.
+ */
protected AnnotationVisitor av;
/**
* Constructs a new {@link AnnotationVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
- * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public AnnotationVisitor(final int api) {
+ protected 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}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param annotationVisitor the annotation visitor to which this visitor must delegate method
- * calls. May be null.
+ * calls. May be {@literal 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();
+ protected AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
this.api = api;
this.av = annotationVisitor;
}
+ /**
+ * The annotation visitor to which this visitor must delegate method calls. May be {@literal
+ * null}.
+ *
+ * @return the annotation visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public AnnotationVisitor getDelegate() {
+ return av;
+ }
+
/**
* Visits a primitive value of the annotation.
*
@@ -112,9 +130,9 @@ public void visitEnum(final String name, final String descriptor, final String v
*
* @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.
+ * @return a visitor to visit the actual nested annotation value, or {@literal null} if this
+ * visitor is not interested in visiting this nested annotation. The nested annotation
+ * value must be fully visited before calling other methods on this annotation visitor.
*/
public AnnotationVisitor visitAnnotation(final String name, final String descriptor) {
if (av != null) {
@@ -124,13 +142,13 @@ public AnnotationVisitor visitAnnotation(final String name, final String descrip
}
/**
- * Visits an array value of the annotation. Note that arrays of primitive types (such as byte,
+ * Visits an array value of the annotation. Note that arrays of primitive values (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.
+ * visit}. This is what {@link ClassReader} does for non empty arrays of primitive values.
*
* @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
+ * @return a visitor to visit the actual array value elements, or {@literal null} if this visitor
+ * is not interested in visiting these values. The 'name' parameters passed to the methods of
* this visitor are ignored. All the array values must be visited before calling other
* methods on this annotation visitor.
*/
@@ -147,4 +165,4 @@ public void visitEnd() {
av.visitEnd();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/AnnotationWriter.java b/src/jvm/clojure/asm/AnnotationWriter.java
index 4ee16510a3..95fdb0f8f8 100644
--- a/src/jvm/clojure/asm/AnnotationWriter.java
+++ b/src/jvm/clojure/asm/AnnotationWriter.java
@@ -91,7 +91,7 @@ final class AnnotationWriter extends AnnotationVisitor {
private AnnotationWriter nextAnnotation;
// -----------------------------------------------------------------------------------------------
- // Constructors
+ // Constructors and factories
// -----------------------------------------------------------------------------------------------
/**
@@ -104,15 +104,15 @@ final class AnnotationWriter extends AnnotationVisitor {
* 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).
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or
+ * {@literal null} in other cases (e.g. nested or array annotations).
*/
AnnotationWriter(
- final SymbolTable symbolTable,
- final boolean useNamedValues,
- final ByteVector annotation,
- final AnnotationWriter previousAnnotation) {
- super(Opcodes.ASM6);
+ final SymbolTable symbolTable,
+ final boolean useNamedValues,
+ final ByteVector annotation,
+ final AnnotationWriter previousAnnotation) {
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.useNamedValues = useNamedValues;
this.annotation = annotation;
@@ -125,21 +125,61 @@ final class AnnotationWriter extends AnnotationVisitor {
}
/**
- * Constructs a new {@link AnnotationWriter} using named values.
+ * Creates 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 descriptor the class descriptor of the annotation class.
* @param previousAnnotation the previously visited annotation of the
- * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in
- * other cases (e.g. nested or array annotations).
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or
+ * {@literal null} in other cases (e.g. nested or array annotations).
+ * @return a new {@link AnnotationWriter} for the given annotation descriptor.
*/
- AnnotationWriter(
- final SymbolTable symbolTable,
- final ByteVector annotation,
- final AnnotationWriter previousAnnotation) {
- this(symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation);
+ static AnnotationWriter create(
+ final SymbolTable symbolTable,
+ final String descriptor,
+ final AnnotationWriter previousAnnotation) {
+ // Create a ByteVector to hold an 'annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
+ ByteVector annotation = new ByteVector();
+ // Write type_index and reserve space for num_element_value_pairs.
+ annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ return new AnnotationWriter(
+ symbolTable, /* useNamedValues= */ true, annotation, previousAnnotation);
+ }
+
+ /**
+ * Creates a new {@link AnnotationWriter} using named values.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link
+ * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param previousAnnotation the previously visited annotation of the
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or
+ * {@literal null} in other cases (e.g. nested or array annotations).
+ * @return a new {@link AnnotationWriter} for the given type annotation reference and descriptor.
+ */
+ static AnnotationWriter create(
+ final SymbolTable symbolTable,
+ final int typeRef,
+ final TypePath typePath,
+ final String descriptor,
+ final AnnotationWriter previousAnnotation) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ TypeReference.putTarget(typeRef, typeAnnotation);
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ return new AnnotationWriter(
+ symbolTable, /* useNamedValues= */ true, typeAnnotation, previousAnnotation);
}
// -----------------------------------------------------------------------------------------------
@@ -230,8 +270,8 @@ public void visitEnum(final String name, final String descriptor, final String v
annotation.putShort(symbolTable.addConstantUtf8(name));
}
annotation
- .put12('e', symbolTable.addConstantUtf8(descriptor))
- .putShort(symbolTable.addConstantUtf8(value));
+ .put12('e', symbolTable.addConstantUtf8(descriptor))
+ .putShort(symbolTable.addConstantUtf8(value));
}
@Override
@@ -244,7 +284,7 @@ public AnnotationVisitor visitAnnotation(final String name, final String descrip
}
// 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);
+ return new AnnotationWriter(symbolTable, /* useNamedValues= */ true, annotation, null);
}
@Override
@@ -263,7 +303,7 @@ public AnnotationVisitor visitArray(final String name) {
// 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);
+ return new AnnotationWriter(symbolTable, /* useNamedValues= */ false, annotation, null);
}
@Override
@@ -284,7 +324,7 @@ public void visitEnd() {
* 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.
+ * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or {@literal null}.
* @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this
* annotation and all its predecessors. This includes the size of the attribute_name_index and
* attribute_length fields.
@@ -303,6 +343,56 @@ int computeAnnotationsSize(final String attributeName) {
return attributeSize;
}
+ /**
+ * Returns the size of the Runtime[In]Visible[Type]Annotations attributes containing the given
+ * annotations and all their predecessors (see {@link #previousAnnotation}. Also adds the
+ * attribute names to the constant pool of the class (if not null).
+ *
+ * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or
+ * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be
+ * {@literal null}.
+ * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field,
+ * method or class. The previous ones can be accessed with the {@link #previousAnnotation}
+ * field. May be {@literal null}.
+ * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a
+ * field, method or class. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a
+ * field, method or class field. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing the
+ * given annotations and all their predecessors. This includes the size of the
+ * attribute_name_index and attribute_length fields.
+ */
+ static int computeAnnotationsSize(
+ final AnnotationWriter lastRuntimeVisibleAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleAnnotation,
+ final AnnotationWriter lastRuntimeVisibleTypeAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleTypeAnnotation) {
+ int size = 0;
+ if (lastRuntimeVisibleAnnotation != null) {
+ size +=
+ lastRuntimeVisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
+ return size;
+ }
+
/**
* Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its
* predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are
@@ -335,6 +425,51 @@ void putAnnotations(final int attributeNameIndex, final ByteVector output) {
}
}
+ /**
+ * Puts the Runtime[In]Visible[Type]Annotations attributes containing the given annotations and
+ * all their predecessors (see {@link #previousAnnotation} in the given ByteVector.
+ * Annotations are put in the same order they have been visited.
+ *
+ * @param symbolTable where the constants used in the AnnotationWriter instances are stored.
+ * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or
+ * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be
+ * {@literal null}.
+ * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field,
+ * method or class. The previous ones can be accessed with the {@link #previousAnnotation}
+ * field. May be {@literal null}.
+ * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a
+ * field, method or class. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a
+ * field, method or class field. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @param output where the attributes must be put.
+ */
+ static void putAnnotations(
+ final SymbolTable symbolTable,
+ final AnnotationWriter lastRuntimeVisibleAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleAnnotation,
+ final AnnotationWriter lastRuntimeVisibleTypeAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleTypeAnnotation,
+ final ByteVector output) {
+ if (lastRuntimeVisibleAnnotation != null) {
+ lastRuntimeVisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ lastRuntimeInvisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ lastRuntimeVisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ lastRuntimeInvisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ }
+
/**
* Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the
* annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the
@@ -350,9 +485,9 @@ void putAnnotations(final int attributeNameIndex, final ByteVector output) {
* attribute_name_index and attribute_length fields.
*/
static int computeParameterAnnotationsSize(
- final String attributeName,
- final AnnotationWriter[] annotationWriters,
- final int annotableParameterCount) {
+ 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).
@@ -362,7 +497,7 @@ static int computeParameterAnnotationsSize(
for (int i = 0; i < annotableParameterCount; ++i) {
AnnotationWriter annotationWriter = annotationWriters[i];
attributeSize +=
- annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8;
+ annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8;
}
return attributeSize;
}
@@ -380,17 +515,17 @@ static int computeParameterAnnotationsSize(
* @param output where the attribute must be put.
*/
static void putParameterAnnotations(
- final int attributeNameIndex,
- final AnnotationWriter[] annotationWriters,
- final int annotableParameterCount,
- final ByteVector output) {
+ 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;
+ annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8;
}
output.putShort(attributeNameIndex);
output.putInt(attributeLength);
@@ -410,9 +545,9 @@ static void putParameterAnnotations(
annotationWriter = firstAnnotation;
while (annotationWriter != null) {
output.putByteArray(
- annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
+ annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
annotationWriter = annotationWriter.nextAnnotation;
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Attribute.java b/src/jvm/clojure/asm/Attribute.java
index dcbe935cd7..7b3951eece 100644
--- a/src/jvm/clojure/asm/Attribute.java
+++ b/src/jvm/clojure/asm/Attribute.java
@@ -28,12 +28,12 @@
package clojure.asm;
/**
- * A non standard class, field, method or code attribute, as defined in the Java Virtual Machine
+ * A non standard class, field, method or Code attribute, as defined in the Java Virtual Machine
* Specification (JVMS).
*
- * @see JVMS
+ * @see JVMS
* 4.7
- * @see JVMS
+ * @see JVMS
* 4.7.3
* @author Eric Bruneton
* @author Eugene Kuleshov
@@ -44,15 +44,15 @@ public class Attribute {
public final String type;
/**
- * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}).
- * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not
- * included.
+ * The raw content of this attribute, as returned by {@link
+ * #write(ClassWriter,byte[],int,int,int)}. The 6 header bytes of the attribute
+ * (attribute_name_index and attribute_length) are not included.
*/
- private byte[] content;
+ private ByteVector cachedContent;
/**
* 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.
+ * store a list of class, field, method or Code attributes). May be {@literal null}.
*/
Attribute nextAttribute;
@@ -66,23 +66,23 @@ protected Attribute(final String type) {
}
/**
- * Returns true if this type of attribute is unknown. This means that the attribute
+ * Returns {@literal true} if this type of attribute is unknown. This means that the attribute
* content can't be parsed to extract constant pool references, labels, etc. Instead, the
* attribute content is read as an opaque byte array, and written back as is. This can lead to
* invalid attributes, if the content actually contains constant pool references, labels, or other
* symbolic references that need to be updated when there are changes to the constant pool, the
- * method bytecode, etc. The default implementation of this method always returns true.
+ * method bytecode, etc. The default implementation of this method always returns {@literal true}.
*
- * @return true if this type of attribute is unknown.
+ * @return {@literal true} if this type of attribute is unknown.
*/
public boolean isUnknown() {
return true;
}
/**
- * Returns true if this type of attribute is a code attribute.
+ * Returns {@literal true} if this type of attribute is a Code attribute.
*
- * @return true if this type of attribute is a code attribute.
+ * @return {@literal true} if this type of attribute is a Code attribute.
*/
public boolean isCodeAttribute() {
return false;
@@ -91,9 +91,11 @@ public boolean isCodeAttribute() {
/**
* 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.
+ * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not
+ * a Code attribute that contains labels.
+ * @deprecated no longer used by ASM.
*/
+ @Deprecated
protected Label[] getLabels() {
return new Label[0];
}
@@ -104,59 +106,176 @@ protected Label[] getLabels() {
* 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
+ * @param offset index of the first byte of the attribute's content in {@link ClassReader}. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
* account here.
* @param length the length of the attribute's content (excluding the 6 attribute header bytes).
* @param charBuffer the buffer to be used to call the ClassReader methods requiring a
* 'charBuffer' parameter.
* @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute
- * in {@link ClassReader#b}, or -1 if the attribute to be read is not a code attribute. The 6
+ * in {@link ClassReader}, or -1 if the attribute to be read is not a Code attribute. The 6
* attribute header bytes (attribute_name_index and attribute_length) are not taken into
* account here.
- * @param labels the labels of the method's code, or null if the attribute to be read is
- * not a code attribute.
+ * @param labels the labels of the method's code, or {@literal null} if the attribute to be read
+ * is not a Code attribute. Labels defined in the attribute must be created and added to this
+ * array, if not already present, by calling the {@link #readLabel} method (do not create
+ * {@link Label} instances directly).
* @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) {
+ 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);
+ attribute.cachedContent = new ByteVector(classReader.readBytes(offset, length));
return attribute;
}
+ /**
+ * Reads an attribute with the same {@link #type} as the given attribute. This method returns a
+ * new {@link Attribute} object, corresponding to the 'length' bytes starting at 'offset', in the
+ * given ClassReader.
+ *
+ * @param attribute The attribute prototype that is used for reading.
+ * @param classReader the class that contains the attribute to be read.
+ * @param offset index of the first byte of the attribute's content in {@link ClassReader}. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param length the length of the attribute's content (excluding the 6 attribute header bytes).
+ * @param charBuffer the buffer to be used to call the ClassReader methods requiring a
+ * 'charBuffer' parameter.
+ * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute
+ * in {@link ClassReader}, or -1 if the attribute to be read is not a Code attribute. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param labels the labels of the method's code, or {@literal null} if the attribute to be read
+ * is not a Code attribute. Labels defined in the attribute are added to this array, if not
+ * already present.
+ * @return a new {@link Attribute} object corresponding to the specified bytes.
+ */
+ public static Attribute read(
+ final Attribute attribute,
+ final ClassReader classReader,
+ final int offset,
+ final int length,
+ final char[] charBuffer,
+ final int codeAttributeOffset,
+ final Label[] labels) {
+ return attribute.read(classReader, offset, length, charBuffer, codeAttributeOffset, labels);
+ }
+
+ /**
+ * Returns the label corresponding to the given bytecode offset by calling {@link
+ * ClassReader#readLabel}. This creates and adds the label to the given array if it is not already
+ * present. Note that this created label may be a {@link Label} subclass instance, if the given
+ * ClassReader overrides {@link ClassReader#readLabel}. Hence {@link #read(ClassReader, int, int,
+ * char[], int, Label[])} must not manually create {@link Label} instances.
+ *
+ * @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 does not create a new one. Otherwise it stores the new label
+ * in this array.
+ * @return a label for the given bytecode offset.
+ */
+ public static Label readLabel(
+ final ClassReader classReader, final int bytecodeOffset, final Label[] labels) {
+ return classReader.readLabel(bytecodeOffset, labels);
+ }
+
+ /**
+ * Calls {@link #write(ClassWriter,byte[],int,int,int)} if it has not already been called and
+ * returns its result or its (cached) previous result.
+ *
+ * @param classWriter the class to which this attribute must be added. This parameter can be used
+ * to add the items that corresponds to this attribute to the constant pool of this class.
+ * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null}
+ * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code
+ * attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to this code
+ * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length'
+ * field of the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or
+ * -1 if this attribute is not a Code attribute.
+ * @param maxLocals the maximum number of local variables of the method corresponding to this code
+ * attribute, or -1 if this attribute is not a Code attribute.
+ * @return the byte array form of this attribute.
+ */
+ private ByteVector maybeWrite(
+ final ClassWriter classWriter,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals) {
+ if (cachedContent == null) {
+ cachedContent = write(classWriter, code, codeLength, maxStack, maxLocals);
+ }
+ return cachedContent;
+ }
+
/**
* 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.
*
+ *
This method is only invoked once to compute the binary form of this attribute. Subsequent
+ * changes to the attribute after it was written for the first time will not be considered.
+ *
* @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
+ * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null}
+ * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code
* attribute.
* @param codeLength the length of the bytecode of the method corresponding to this code
- * attribute, or 0 if this attribute is not a code attribute. Corresponds to the 'code_length'
+ * 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 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.
+ * 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);
+ final ClassWriter classWriter,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals) {
+ return cachedContent;
+ }
+
+ /**
+ * Returns the byte array form of the content of the given attribute. The 6 header bytes
+ * (attribute_name_index and attribute_length) are not added in the returned byte array.
+ *
+ * @param attribute The attribute that should be written.
+ * @param classWriter the class to which this attribute must be added. This parameter can be used
+ * to add the items that corresponds to this attribute to the constant pool of this class.
+ * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null}
+ * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code
+ * attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to this code
+ * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length'
+ * field of the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or
+ * -1 if this attribute is not a Code attribute.
+ * @param maxLocals the maximum number of local variables of the method corresponding to this code
+ * attribute, or -1 if this attribute is not a Code attribute.
+ * @return the byte array form of this attribute.
+ */
+ public static byte[] write(
+ final Attribute attribute,
+ final ClassWriter classWriter,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals) {
+ ByteVector content = attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals);
+ byte[] result = new byte[content.length];
+ System.arraycopy(content.data, 0, result, 0, content.length);
+ return result;
}
/**
@@ -197,35 +316,72 @@ final int computeAttributesSize(final SymbolTable symbolTable) {
* 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 code the bytecode of the method corresponding to these Code attributes, or {@literal
+ * null} if they are not Code attributes. Corresponds to the 'code' field of the Code
+ * attribute.
* @param codeLength the length of the bytecode of the method corresponding to these code
- * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of
+ * 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 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.
+ * 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 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;
+ size += 6 + attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals).length;
attribute = attribute.nextAttribute;
}
return size;
}
+ /**
+ * Returns the total size in bytes of all the attributes that correspond to the given field,
+ * method or class access flags and signature. This size includes the 6 header bytes
+ * (attribute_name_index and attribute_length) per attribute. Also adds the attribute type names
+ * to the constant pool.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param accessFlags some field, method or class access flags.
+ * @param signatureIndex the constant pool index of a field, method of class signature.
+ * @return the size of all the attributes in bytes. This size includes the size of the attribute
+ * headers.
+ */
+ static int computeAttributesSize(
+ final SymbolTable symbolTable, final int accessFlags, final int signatureIndex) {
+ int size = 0;
+ // Before Java 1.5, synthetic fields are represented with a Synthetic attribute.
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0
+ && symbolTable.getMajorVersion() < Opcodes.V1_5) {
+ // Synthetic attributes always use 6 bytes.
+ symbolTable.addConstantUtf8(Constants.SYNTHETIC);
+ size += 6;
+ }
+ if (signatureIndex != 0) {
+ // Signature attributes always use 8 bytes.
+ symbolTable.addConstantUtf8(Constants.SIGNATURE);
+ size += 8;
+ }
+ // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead.
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ // Deprecated attributes always use 6 bytes.
+ symbolTable.addConstantUtf8(Constants.DEPRECATED);
+ size += 6;
+ }
+ return size;
+ }
+
/**
* Puts all the attributes of the attribute list that begins with this attribute, in the given
* byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per
@@ -248,29 +404,30 @@ final void putAttributes(final SymbolTable symbolTable, final ByteVector output)
* 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 code the bytecode of the method corresponding to these Code attributes, or {@literal
+ * null} if they are not Code attributes. Corresponds to the 'code' field of the Code
+ * attribute.
* @param codeLength the length of the bytecode of the method corresponding to these code
- * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of
+ * 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 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.
+ * 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 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);
+ attribute.maybeWrite(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);
@@ -278,6 +435,37 @@ final void putAttributes(
}
}
+ /**
+ * Puts all the attributes that correspond to the given field, method or class access flags and
+ * signature, in the given byte vector. This includes the 6 header bytes (attribute_name_index and
+ * attribute_length) per attribute.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param accessFlags some field, method or class access flags.
+ * @param signatureIndex the constant pool index of a field, method of class signature.
+ * @param output where the attributes must be written.
+ */
+ static void putAttributes(
+ final SymbolTable symbolTable,
+ final int accessFlags,
+ final int signatureIndex,
+ final ByteVector output) {
+ // Before Java 1.5, synthetic fields are represented with a Synthetic attribute.
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0
+ && symbolTable.getMajorVersion() < Opcodes.V1_5) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
+ }
+ if (signatureIndex != 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
+ .putInt(2)
+ .putShort(signatureIndex);
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
+ }
+ }
+
/** A set of attribute prototypes (attributes with the same type are considered equal). */
static final class Set {
@@ -320,4 +508,4 @@ private void add(final Attribute attribute) {
data[size++] = attribute;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ByteVector.java b/src/jvm/clojure/asm/ByteVector.java
index 066dec8a62..2d35b06ed7 100644
--- a/src/jvm/clojure/asm/ByteVector.java
+++ b/src/jvm/clojure/asm/ByteVector.java
@@ -65,6 +65,15 @@ public ByteVector(final int initialCapacity) {
this.length = data.length;
}
+ /**
+ * Returns the actual number of bytes in this vector.
+ *
+ * @return the actual number of bytes in this vector.
+ */
+ public int size() {
+ return length;
+ }
+
/**
* Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary.
*
@@ -239,10 +248,11 @@ public ByteVector putLong(final long longValue) {
* @param stringValue a String whose UTF8 encoded length must be less than 65536.
* @return this byte vector.
*/
+ // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
public ByteVector putUTF8(final String stringValue) {
int charLength = stringValue.length();
if (charLength > 65535) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("UTF8 string too large");
}
int currentLength = length;
if (currentLength + 2 + charLength > data.length) {
@@ -261,7 +271,7 @@ public ByteVector putUTF8(final String stringValue) {
currentData[currentLength++] = (byte) charValue;
} else {
length = currentLength;
- return encodeUTF8(stringValue, i, 65535);
+ return encodeUtf8(stringValue, i, 65535);
}
}
length = currentLength;
@@ -280,21 +290,21 @@ public ByteVector putUTF8(final String stringValue) {
* encoded characters.
* @return this byte vector.
*/
- final ByteVector encodeUTF8(final String stringValue, final int offset, final int maxByteLength) {
+ 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') {
+ if (charValue >= 0x0001 && charValue <= 0x007F) {
byteLength++;
- } else if (charValue <= '\u07FF') {
+ } else if (charValue <= 0x07FF) {
byteLength += 2;
} else {
byteLength += 3;
}
}
if (byteLength > maxByteLength) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("UTF8 string too large");
}
// Compute where 'byteLength' must be stored in 'data', and store it at this location.
int byteLengthOffset = length - offset - 2;
@@ -308,9 +318,9 @@ final ByteVector encodeUTF8(final String stringValue, final int offset, final in
int currentLength = length;
for (int i = offset; i < charLength; ++i) {
char charValue = stringValue.charAt(i);
- if (charValue >= '\u0001' && charValue <= '\u007F') {
+ if (charValue >= 0x0001 && charValue <= 0x007F) {
data[currentLength++] = (byte) charValue;
- } else if (charValue <= '\u07FF') {
+ } else if (charValue <= 0x07FF) {
data[currentLength++] = (byte) (0xC0 | charValue >> 6 & 0x1F);
data[currentLength++] = (byte) (0x80 | charValue & 0x3F);
} else {
@@ -327,14 +337,14 @@ final ByteVector encodeUTF8(final String stringValue, final int offset, final in
* 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
+ * @param byteArrayValue an array of bytes. May be {@literal null} to put {@code byteLength} null
* bytes into this byte vector.
* @param byteOffset index of the first byte of byteArrayValue that must be copied.
* @param byteLength number of bytes of byteArrayValue that must be copied.
* @return this byte vector.
*/
public ByteVector putByteArray(
- final byte[] byteArrayValue, final int byteOffset, final int byteLength) {
+ final byte[] byteArrayValue, final int byteOffset, final int byteLength) {
if (length + byteLength > data.length) {
enlarge(byteLength);
}
@@ -351,10 +361,13 @@ public ByteVector putByteArray(
* @param size number of additional bytes that this byte vector should be able to receive.
*/
private void enlarge(final int size) {
+ if (length > data.length) {
+ throw new AssertionError("Internal error");
+ }
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;
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ClassReader.java b/src/jvm/clojure/asm/ClassReader.java
index 6502e86d3a..3bb0870f08 100644
--- a/src/jvm/clojure/asm/ClassReader.java
+++ b/src/jvm/clojure/asm/ClassReader.java
@@ -50,10 +50,11 @@ public class ClassReader {
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).
+ * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable,
+ * LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set
+ * these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link
+ * MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link
+ * MethodVisitor#visitParameter} are not called).
*/
public static final int SKIP_DEBUG = 2;
@@ -87,9 +88,25 @@ public class ClassReader {
*/
static final int EXPAND_ASM_INSNS = 256;
+ /** The maximum size of array to allocate. */
+ private static final int MAX_BUFFER_SIZE = 1024 * 1024;
+
/** The size of the temporary byte array used to read class input streams chunk by chunk. */
private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
+ /**
+ * A byte array containing the JVMS ClassFile structure to be parsed.
+ *
+ * @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will
+ * eventually be deleted.
+ */
+ @Deprecated
+ // DontCheck(MemberName): can't be renamed (for backward binary compatibility).
+ public final byte[] b;
+
+ /** The offset in bytes of the ClassFile's access_flags field. */
+ public final int header;
+
/**
* A byte array containing the JVMS ClassFile structure to be parsed. The content of this array
* must not be modified. This field is intended for {@link Attribute} sub classes, and is normally
@@ -99,25 +116,31 @@ public class ClassReader {
* 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;
+ final byte[] classFileBuffer;
/**
- * 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].
+ * The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's
+ * constant_pool array, plus one. In other words, the offset of constant pool entry i is
+ * given by cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] -
+ * 1].
*/
private final int[] cpInfoOffsets;
/**
- * The 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.
+ * The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids
+ * multiple parsing of a given CONSTANT_Utf8 constant pool item.
*/
- private final Object[] cpInfoValues;
+ private final String[] constantUtf8Values;
/**
- * The start offsets in {@link #b} of each element of the bootstrap_methods array (in the
- * BootstrapMethods attribute).
+ * The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This
+ * cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item.
+ */
+ private final ConstantDynamic[] constantDynamicValues;
+
+ /**
+ * The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array
+ * (in the BootstrapMethods attribute).
*
* @see JVMS
* 4.7.23
@@ -130,9 +153,6 @@ public class ClassReader {
*/
private final int maxStringLength;
- /** The offset in bytes, in {@link #b}, of the ClassFile's access_flags field. */
- public final int header;
-
// -----------------------------------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------------------------------
@@ -154,8 +174,10 @@ public ClassReader(final byte[] classFile) {
* @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);
+ final byte[] classFileBuffer,
+ final int classFileOffset,
+ final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility.
+ this(classFileBuffer, classFileOffset, /* checkClassVersion= */ true);
}
/**
@@ -166,20 +188,22 @@ public ClassReader(
* @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.
*/
+ @SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
ClassReader(
- final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
+ final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
+ this.classFileBuffer = classFileBuffer;
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
- if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V11) {
+ if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
throw new IllegalArgumentException(
- "Unsupported class file major version " + readShort(classFileOffset + 6));
+ "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];
+ constantUtf8Values = new String[constantPoolCount];
// Compute the offset of each constant pool entry, as well as a conservative estimate of the
// maximum length of the constant pool strings. The first constant pool entry is after the
// magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2
@@ -187,6 +211,8 @@ public ClassReader(
int currentCpInfoIndex = 1;
int currentCpInfoOffset = classFileOffset + 10;
int currentMaxStringLength = 0;
+ boolean hasBootstrapMethods = false;
+ boolean hasConstantDynamic = false;
// The offset of the other entries depend on the total size of all the previous entries.
while (currentCpInfoIndex < constantPoolCount) {
cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1;
@@ -198,9 +224,16 @@ public ClassReader(
case Symbol.CONSTANT_INTEGER_TAG:
case Symbol.CONSTANT_FLOAT_TAG:
case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
- case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
+ cpInfoSize = 5;
+ break;
case Symbol.CONSTANT_DYNAMIC_TAG:
cpInfoSize = 5;
+ hasBootstrapMethods = true;
+ hasConstantDynamic = true;
+ break;
+ case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
+ cpInfoSize = 5;
+ hasBootstrapMethods = true;
break;
case Symbol.CONSTANT_LONG_TAG:
case Symbol.CONSTANT_DOUBLE_TAG:
@@ -231,34 +264,16 @@ public ClassReader(
}
currentCpInfoOffset += cpInfoSize;
}
- this.maxStringLength = currentMaxStringLength;
+ maxStringLength = currentMaxStringLength;
// The Classfile's access_flags field is just after the last constant pool entry.
- this.header = currentCpInfoOffset;
+ header = currentCpInfoOffset;
+
+ // Allocate the cache of ConstantDynamic values, if there is at least one.
+ constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null;
// Read the BootstrapMethods attribute, if any (only get the offset of each method).
- 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;
- }
- }
- currentAttributeOffset += attributeLength;
- }
- this.bootstrapMethodOffsets = currentBootstrapMethodOffsets;
+ bootstrapMethodOffsets =
+ hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null;
}
/**
@@ -282,8 +297,8 @@ public ClassReader(final InputStream inputStream) throws IOException {
*/
public ClassReader(final String className) throws IOException {
this(
- readStream(
- ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
+ readStream(
+ ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
}
/**
@@ -294,19 +309,25 @@ public ClassReader(final String className) throws IOException {
* @return the content of the given input stream.
* @throws IOException if a problem occurs during reading.
*/
+ @SuppressWarnings("PMD.UseTryWithResources")
private static byte[] readStream(final InputStream inputStream, final boolean close)
- throws IOException {
+ 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 bufferSize = computeBufferSize(inputStream);
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ byte[] data = new byte[bufferSize];
int bytesRead;
- while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
+ int readCount = 0;
+ while ((bytesRead = inputStream.read(data, 0, bufferSize)) != -1) {
outputStream.write(data, 0, bytesRead);
+ readCount++;
}
outputStream.flush();
+ if (readCount == 1) {
+ return data;
+ }
return outputStream.toByteArray();
} finally {
if (close) {
@@ -315,6 +336,19 @@ private static byte[] readStream(final InputStream inputStream, final boolean cl
}
}
+ private static int computeBufferSize(final InputStream inputStream) throws IOException {
+ int expectedLength = inputStream.available();
+ /*
+ * Some implementations can return 0 while holding available data (e.g. new
+ * FileInputStream("/proc/a_file")). Also in some pathological cases a very small number might
+ * be returned, and in this case we use a default size.
+ */
+ if (expectedLength < 256) {
+ return INPUT_STREAM_DATA_CHUNK_SIZE;
+ }
+ return Math.min(expectedLength, MAX_BUFFER_SIZE);
+ }
+
// -----------------------------------------------------------------------------------------------
// Accessors
// -----------------------------------------------------------------------------------------------
@@ -342,10 +376,10 @@ public String getClassName() {
}
/**
- * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
+ * Returns the internal 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.
+ * @return the internal name of the super class, or {@literal null} for {@link Object} class.
* @see ClassVisitor#visit(int, int, String, String, String, String[])
*/
public String getSuperName() {
@@ -406,9 +440,9 @@ public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
* #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
*/
public void accept(
- final ClassVisitor classVisitor,
- final Attribute[] attributePrototypes,
- final int parsingOptions) {
+ final ClassVisitor classVisitor,
+ final Attribute[] attributePrototypes,
+ final int parsingOptions) {
Context context = new Context();
context.attributePrototypes = attributePrototypes;
context.parsingOptions = parsingOptions;
@@ -457,6 +491,10 @@ public void accept(
String nestHostClass = null;
// - The offset of the NestMembers attribute, or 0.
int nestMembersOffset = 0;
+ // - The offset of the PermittedSubclasses attribute, or 0
+ int permittedSubclassesOffset = 0;
+ // - The offset of the Record attribute, or 0.
+ int recordOffset = 0;
// - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
// This list in the reverse order or their order in the ClassFile structure.
Attribute attributes = null;
@@ -479,6 +517,8 @@ public void accept(
nestHostClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.NEST_MEMBERS.equals(attributeName)) {
nestMembersOffset = currentAttributeOffset;
+ } else if (Constants.PERMITTED_SUBCLASSES.equals(attributeName)) {
+ permittedSubclassesOffset = currentAttributeOffset;
} else if (Constants.SIGNATURE.equals(attributeName)) {
signature = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
@@ -490,30 +530,35 @@ public void accept(
} else if (Constants.SYNTHETIC.equals(attributeName)) {
accessFlags |= Opcodes.ACC_SYNTHETIC;
} else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
+ if (attributeLength > classFileBuffer.length - currentAttributeOffset) {
+ throw new IllegalArgumentException();
+ }
sourceDebugExtension =
- readUTF(currentAttributeOffset, attributeLength, new char[attributeLength]);
+ readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.RECORD.equals(attributeName)) {
+ recordOffset = currentAttributeOffset;
+ accessFlags |= Opcodes.ACC_RECORD;
} else if (Constants.MODULE.equals(attributeName)) {
moduleOffset = currentAttributeOffset;
} else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
moduleMainClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
modulePackagesOffset = currentAttributeOffset;
- } else if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
- // This attribute is read in the constructor.
- } else {
+ } else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ // The BootstrapMethods attribute is read in the constructor.
Attribute attribute =
- readAttribute(
- attributePrototypes,
- attributeName,
- currentAttributeOffset,
- attributeLength,
- charBuffer,
- -1,
- null);
+ readAttribute(
+ attributePrototypes,
+ attributeName,
+ currentAttributeOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
@@ -523,22 +568,23 @@ public void accept(
// 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);
+ readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);
// Visit the SourceFile and SourceDebugExtenstion attributes.
if ((parsingOptions & SKIP_DEBUG) == 0
- && (sourceFile != null || sourceDebugExtension != null)) {
+ && (sourceFile != null || sourceDebugExtension != null)) {
classVisitor.visitSource(sourceFile, sourceDebugExtension);
}
// Visit the Module, ModulePackages and ModuleMainClass attributes.
if (moduleOffset != 0) {
- readModule(classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass);
+ readModuleAttributes(
+ classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass);
}
// Visit the NestHost attribute.
if (nestHostClass != null) {
- classVisitor.visitNestHostExperimental(nestHostClass);
+ classVisitor.visitNestHost(nestHostClass);
}
// Visit the EnclosingMethod attribute.
@@ -560,11 +606,11 @@ public void accept(
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);
+ readElementValues(
+ classVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -578,11 +624,11 @@ public void accept(
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);
+ readElementValues(
+ classVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -598,15 +644,15 @@ public void accept(
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);
+ readElementValues(
+ classVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -622,15 +668,15 @@ public void accept(
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);
+ readElementValues(
+ classVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -648,25 +694,45 @@ public void accept(
int numberOfNestMembers = readUnsignedShort(nestMembersOffset);
int currentNestMemberOffset = nestMembersOffset + 2;
while (numberOfNestMembers-- > 0) {
- classVisitor.visitNestMemberExperimental(readClass(currentNestMemberOffset, charBuffer));
+ classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer));
currentNestMemberOffset += 2;
}
}
+ // Visit the PermittedSubclasses attribute.
+ if (permittedSubclassesOffset != 0) {
+ int numberOfPermittedSubclasses = readUnsignedShort(permittedSubclassesOffset);
+ int currentPermittedSubclassesOffset = permittedSubclassesOffset + 2;
+ while (numberOfPermittedSubclasses-- > 0) {
+ classVisitor.visitPermittedSubclass(
+ readClass(currentPermittedSubclassesOffset, charBuffer));
+ currentPermittedSubclassesOffset += 2;
+ }
+ }
+
// Visit the InnerClasses attribute.
if (innerClassesOffset != 0) {
int numberOfClasses = readUnsignedShort(innerClassesOffset);
int currentClassesOffset = innerClassesOffset + 2;
while (numberOfClasses-- > 0) {
classVisitor.visitInnerClass(
- readClass(currentClassesOffset, charBuffer),
- readClass(currentClassesOffset + 2, charBuffer),
- readUTF8(currentClassesOffset + 4, charBuffer),
- readUnsignedShort(currentClassesOffset + 6));
+ readClass(currentClassesOffset, charBuffer),
+ readClass(currentClassesOffset + 2, charBuffer),
+ readUTF8(currentClassesOffset + 4, charBuffer),
+ readUnsignedShort(currentClassesOffset + 6));
currentClassesOffset += 8;
}
}
+ // Visit Record components.
+ if (recordOffset != 0) {
+ int recordComponentsCount = readUnsignedShort(recordOffset);
+ recordOffset += 2;
+ while (recordComponentsCount-- > 0) {
+ recordOffset = readRecordComponent(classVisitor, context, recordOffset);
+ }
+ }
+
// Visit the fields and methods.
int fieldsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
@@ -688,7 +754,7 @@ public void accept(
// ----------------------------------------------------------------------------------------------
/**
- * Reads the module attribute and visit it.
+ * Reads the Module, ModulePackages and ModuleMainClass attributes and visit them.
*
* @param classVisitor the current class visitor
* @param context information about the class being parsed.
@@ -696,14 +762,15 @@ public void accept(
* 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.
+ * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or {@literal
+ * null}.
*/
- private void readModule(
- final ClassVisitor classVisitor,
- final Context context,
- final int moduleOffset,
- final int modulePackagesOffset,
- final String moduleMainClass) {
+ private void readModuleAttributes(
+ final ClassVisitor classVisitor,
+ final Context context,
+ final int moduleOffset,
+ final int modulePackagesOffset,
+ final String moduleMainClass) {
char[] buffer = context.charBuffer;
// Read the module_name_index, module_flags and module_version_index fields and visit them.
@@ -793,7 +860,7 @@ private void readModule(
currentOffset += 2;
}
- // Read the 'provides_count' and 'provides' fields.
+ // Read the 'provides_count' and 'provides' fields.
int providesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (providesCount-- > 0) {
@@ -813,6 +880,180 @@ private void readModule(
moduleVisitor.visitEnd();
}
+ /**
+ * Reads a record component and visit it.
+ *
+ * @param classVisitor the current class visitor
+ * @param context information about the class being parsed.
+ * @param recordComponentOffset the offset of the current record component.
+ * @return the offset of the first byte following the record component.
+ */
+ private int readRecordComponent(
+ final ClassVisitor classVisitor, final Context context, final int recordComponentOffset) {
+ char[] charBuffer = context.charBuffer;
+
+ int currentOffset = recordComponentOffset;
+ String name = readUTF8(currentOffset, charBuffer);
+ String descriptor = readUTF8(currentOffset + 2, charBuffer);
+ currentOffset += 4;
+
+ // Read the record component attributes (the variables are ordered as in Section 4.7 of the
+ // JVMS).
+
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The string corresponding to the Signature attribute, or null.
+ String signature = null;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.SIGNATURE.equals(attributeName)) {
+ signature = readUTF8(currentOffset, charBuffer);
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentOffset;
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
+
+ RecordComponentVisitor recordComponentVisitor =
+ classVisitor.visitRecordComponent(name, descriptor, signature);
+ if (recordComponentVisitor == null) {
+ return currentOffset;
+ }
+
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in FieldWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ recordComponentVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
+
+ // Visit the end of the field.
+ recordComponentVisitor.visitEnd();
+ return currentOffset;
+ }
+
/**
* Reads a JVMS field_info structure and makes the given visitor visit it.
*
@@ -822,7 +1063,7 @@ private void readModule(
* @return the offset of the first byte following the field_info structure.
*/
private int readField(
- final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) {
+ final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) {
char[] charBuffer = context.charBuffer;
// Read the access_flags, name_index and descriptor_index fields.
@@ -878,14 +1119,14 @@ private int readField(
runtimeInvisibleTypeAnnotationsOffset = currentOffset;
} else {
Attribute attribute =
- readAttribute(
- context.attributePrototypes,
- attributeName,
- currentOffset,
- attributeLength,
- charBuffer,
- -1,
- null);
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
@@ -894,7 +1135,7 @@ private int readField(
// Visit the field declaration.
FieldVisitor fieldVisitor =
- classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue);
+ classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue);
if (fieldVisitor == null) {
return currentOffset;
}
@@ -909,11 +1150,11 @@ private int readField(
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);
+ readElementValues(
+ fieldVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -927,11 +1168,11 @@ private int readField(
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);
+ readElementValues(
+ fieldVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -947,15 +1188,15 @@ private int readField(
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);
+ readElementValues(
+ fieldVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -971,15 +1212,15 @@ private int readField(
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);
+ readElementValues(
+ fieldVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -1006,7 +1247,7 @@ private int readField(
* @return the offset of the first byte following the method_info structure.
*/
private int readMethod(
- final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) {
+ final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) {
char[] charBuffer = context.charBuffer;
// Read the access_flags, name_index and descriptor_index fields.
@@ -1094,14 +1335,14 @@ private int readMethod(
methodParametersOffset = currentOffset;
} else {
Attribute attribute =
- readAttribute(
- context.attributePrototypes,
- attributeName,
- currentOffset,
- attributeLength,
- charBuffer,
- -1,
- null);
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
@@ -1110,12 +1351,12 @@ private int readMethod(
// Visit the method declaration.
MethodVisitor methodVisitor =
- classVisitor.visitMethod(
- context.currentMethodAccessFlags,
- context.currentMethodName,
- context.currentMethodDescriptor,
- signatureIndex == 0 ? null : readUTF(signatureIndex, charBuffer),
- exceptions);
+ classVisitor.visitMethod(
+ context.currentMethodAccessFlags,
+ context.currentMethodName,
+ context.currentMethodDescriptor,
+ signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer),
+ exceptions);
if (methodVisitor == null) {
return currentOffset;
}
@@ -1127,26 +1368,26 @@ private int readMethod(
if (methodVisitor instanceof MethodWriter) {
MethodWriter methodWriter = (MethodWriter) methodVisitor;
if (methodWriter.canCopyMethodAttributes(
- this,
- methodInfoOffset,
- currentOffset - methodInfoOffset,
- synthetic,
- (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
- signatureIndex,
- exceptionsOffset)) {
+ this,
+ synthetic,
+ (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
+ readUnsignedShort(methodInfoOffset + 4),
+ signatureIndex,
+ exceptionsOffset)) {
+ methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset);
return currentOffset;
}
}
// Visit the MethodParameters attribute.
- if (methodParametersOffset != 0) {
+ if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) {
int parametersCount = readByte(methodParametersOffset);
int currentParameterOffset = methodParametersOffset + 1;
while (parametersCount-- > 0) {
// Read the name_index and access_flags fields and visit them.
methodVisitor.visitParameter(
- readUTF8(currentParameterOffset, charBuffer),
- readUnsignedShort(currentParameterOffset + 2));
+ readUTF8(currentParameterOffset, charBuffer),
+ readUnsignedShort(currentParameterOffset + 2));
currentParameterOffset += 4;
}
}
@@ -1170,11 +1411,11 @@ private int readMethod(
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);
+ readElementValues(
+ methodVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -1188,11 +1429,11 @@ private int readMethod(
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);
+ readElementValues(
+ methodVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -1208,15 +1449,15 @@ private int readMethod(
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);
+ readElementValues(
+ methodVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
}
@@ -1232,31 +1473,28 @@ private int readMethod(
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);
+ 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);
+ methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible= */ true);
}
// Visit the RuntimeInvisibleParameterAnnotations attribute.
if (runtimeInvisibleParameterAnnotationsOffset != 0) {
readParameterAnnotations(
- methodVisitor,
- context,
- runtimeInvisibleParameterAnnotationsOffset,
- /* visible = */ false);
+ methodVisitor, context, runtimeInvisibleParameterAnnotationsOffset, /* visible= */ false);
}
// Visit the non standard attributes.
@@ -1288,20 +1526,23 @@ private int readMethod(
*
* @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.
+ * @param codeOffset the start offset in {@link #classFileBuffer} of the Code attribute, excluding
+ * its attribute_name_index and attribute_length fields.
*/
private void readCode(
- final MethodVisitor methodVisitor, final Context context, final int codeOffset) {
+ 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 byte[] classBuffer = classFileBuffer;
final char[] charBuffer = context.charBuffer;
final int maxStack = readUnsignedShort(currentOffset);
final int maxLocals = readUnsignedShort(currentOffset + 2);
final int codeLength = readInt(currentOffset + 4);
currentOffset += 8;
+ if (codeLength > classFileBuffer.length - currentOffset) {
+ throw new IllegalArgumentException();
+ }
// Read the bytecode 'code' array to create a label for each referenced instruction.
final int bytecodeStartOffset = currentOffset;
@@ -1309,115 +1550,115 @@ private void readCode(
final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1];
while (currentOffset < bytecodeEndOffset) {
final int bytecodeOffset = currentOffset - bytecodeStartOffset;
- final int opcode = classFileBuffer[currentOffset] & 0xFF;
+ final int opcode = classBuffer[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 Opcodes.NOP:
+ case Opcodes.ACONST_NULL:
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.IALOAD:
+ case Opcodes.LALOAD:
+ case Opcodes.FALOAD:
+ case Opcodes.DALOAD:
+ case Opcodes.AALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ case Opcodes.IASTORE:
+ case Opcodes.LASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.DASTORE:
+ case Opcodes.AASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.POP:
+ case Opcodes.POP2:
+ case Opcodes.DUP:
+ case Opcodes.DUP_X1:
+ case Opcodes.DUP_X2:
+ case Opcodes.DUP2:
+ case Opcodes.DUP2_X1:
+ case Opcodes.DUP2_X2:
+ case Opcodes.SWAP:
+ case Opcodes.IADD:
+ case Opcodes.LADD:
+ case Opcodes.FADD:
+ case Opcodes.DADD:
+ case Opcodes.ISUB:
+ case Opcodes.LSUB:
+ case Opcodes.FSUB:
+ case Opcodes.DSUB:
+ case Opcodes.IMUL:
+ case Opcodes.LMUL:
+ case Opcodes.FMUL:
+ case Opcodes.DMUL:
+ case Opcodes.IDIV:
+ case Opcodes.LDIV:
+ case Opcodes.FDIV:
+ case Opcodes.DDIV:
+ case Opcodes.IREM:
+ case Opcodes.LREM:
+ case Opcodes.FREM:
+ case Opcodes.DREM:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.ISHL:
+ case Opcodes.LSHL:
+ case Opcodes.ISHR:
+ case Opcodes.LSHR:
+ case Opcodes.IUSHR:
+ case Opcodes.LUSHR:
+ case Opcodes.IAND:
+ case Opcodes.LAND:
+ case Opcodes.IOR:
+ case Opcodes.LOR:
+ case Opcodes.IXOR:
+ case Opcodes.LXOR:
+ case Opcodes.I2L:
+ case Opcodes.I2F:
+ case Opcodes.I2D:
+ case Opcodes.L2I:
+ case Opcodes.L2F:
+ case Opcodes.L2D:
+ case Opcodes.F2I:
+ case Opcodes.F2L:
+ case Opcodes.F2D:
+ case Opcodes.D2I:
+ case Opcodes.D2L:
+ case Opcodes.D2F:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.LCMP:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.RETURN:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
case Constants.ILOAD_0:
case Constants.ILOAD_1:
case Constants.ILOAD_2:
@@ -1460,24 +1701,24 @@ private void readCode(
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:
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.GOTO:
+ case Opcodes.JSR:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
createLabel(bytecodeOffset + readShort(currentOffset + 1), labels);
currentOffset += 3;
break;
@@ -1509,28 +1750,28 @@ private void readCode(
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:
+ switch (classBuffer[currentOffset + 1] & 0xFF) {
+ case Opcodes.ILOAD:
+ case Opcodes.FLOAD:
+ case Opcodes.ALOAD:
+ case Opcodes.LLOAD:
+ case Opcodes.DLOAD:
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ case Opcodes.RET:
currentOffset += 4;
break;
- case Constants.IINC:
+ case Opcodes.IINC:
currentOffset += 6;
break;
default:
throw new IllegalArgumentException();
}
break;
- case Constants.TABLESWITCH:
+ case Opcodes.TABLESWITCH:
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (bytecodeOffset & 3);
// Read the default label and the number of table entries.
@@ -1543,7 +1784,7 @@ private void readCode(
currentOffset += 4;
}
break;
- case Constants.LOOKUPSWITCH:
+ case Opcodes.LOOKUPSWITCH:
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (bytecodeOffset & 3);
// Read the default label and the number of switch cases.
@@ -1556,44 +1797,44 @@ private void readCode(
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:
+ case Opcodes.ILOAD:
+ case Opcodes.LLOAD:
+ case Opcodes.FLOAD:
+ case Opcodes.DLOAD:
+ case Opcodes.ALOAD:
+ case Opcodes.ISTORE:
+ case Opcodes.LSTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.DSTORE:
+ case Opcodes.ASTORE:
+ case Opcodes.RET:
+ case Opcodes.BIPUSH:
+ case Opcodes.NEWARRAY:
+ case Opcodes.LDC:
currentOffset += 2;
break;
- case Constants.SIPUSH:
+ case Opcodes.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:
+ case Opcodes.GETSTATIC:
+ case Opcodes.PUTSTATIC:
+ case Opcodes.GETFIELD:
+ case Opcodes.PUTFIELD:
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.NEW:
+ case Opcodes.ANEWARRAY:
+ case Opcodes.CHECKCAST:
+ case Opcodes.INSTANCEOF:
+ case Opcodes.IINC:
currentOffset += 3;
break;
- case Constants.INVOKEINTERFACE:
- case Constants.INVOKEDYNAMIC:
+ case Opcodes.INVOKEINTERFACE:
+ case Opcodes.INVOKEDYNAMIC:
currentOffset += 5;
break;
- case Constants.MULTIANEWARRAY:
+ case Opcodes.MULTIANEWARRAY:
currentOffset += 4;
break;
default:
@@ -1603,18 +1844,15 @@ private void readCode(
// 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);
- }
+ 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
@@ -1685,7 +1923,7 @@ private void readCode(
}
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
visibleTypeAnnotationOffsets =
- readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true);
+ 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
@@ -1694,7 +1932,7 @@ private void readCode(
// 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);
+ 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) {
@@ -1722,14 +1960,14 @@ private void readCode(
// StackMapTable attribute).
} else {
Attribute attribute =
- readAttribute(
- context.attributePrototypes,
- attributeName,
- currentOffset,
- attributeLength,
- charBuffer,
- codeOffset,
- labels);
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ codeOffset,
+ labels);
attribute.nextAttribute = attributes;
attributes = attribute;
}
@@ -1761,11 +1999,11 @@ private void readCode(
// 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) {
+ if (classBuffer[offset] == Frame.ITEM_UNINITIALIZED) {
int potentialBytecodeOffset = readUnsignedShort(offset + 1);
if (potentialBytecodeOffset >= 0
- && potentialBytecodeOffset < codeLength
- && (classFileBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF)
+ && potentialBytecodeOffset < codeLength
+ && (classBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF)
== Opcodes.NEW) {
createLabel(potentialBytecodeOffset, labels);
}
@@ -1790,13 +2028,13 @@ private void readCode(
int currentVisibleTypeAnnotationIndex = 0;
// The bytecode offset of the next runtime visible type annotation to read, or -1.
int currentVisibleTypeAnnotationBytecodeOffset =
- getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0);
+ 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);
+ getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0);
// Whether a F_INSERT stack map frame must be inserted before the current instruction.
boolean insertFrame = false;
@@ -1805,11 +2043,12 @@ private void readCode(
// 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;
+ (context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0;
currentOffset = bytecodeStartOffset;
while (currentOffset < bytecodeEndOffset) {
final int currentBytecodeOffset = currentOffset - bytecodeStartOffset;
+ readBytecodeInstructionOffset(currentBytecodeOffset);
// Visit the label and the line number(s) for this bytecode offset, if any.
Label currentLabel = labels[currentBytecodeOffset];
@@ -1819,25 +2058,25 @@ private void readCode(
// Visit the stack map frame for this bytecode offset, if any.
while (stackMapFrameOffset != 0
- && (context.currentFrameOffset == currentBytecodeOffset
+ && (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);
+ Opcodes.F_NEW,
+ context.currentFrameLocalCount,
+ context.currentFrameLocalTypes,
+ context.currentFrameStackCount,
+ context.currentFrameStackTypes);
} else {
methodVisitor.visitFrame(
- context.currentFrameType,
- context.currentFrameLocalCountDelta,
- context.currentFrameLocalTypes,
- context.currentFrameStackCount,
- context.currentFrameStackTypes);
+ 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.
@@ -1845,7 +2084,7 @@ private void readCode(
}
if (stackMapFrameOffset < stackMapTableEndOffset) {
stackMapFrameOffset =
- readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context);
+ readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context);
} else {
stackMapFrameOffset = 0;
}
@@ -1861,115 +2100,115 @@ private void readCode(
}
// Visit the instruction at this bytecode offset.
- int opcode = classFileBuffer[currentOffset] & 0xFF;
+ int opcode = classBuffer[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 Opcodes.NOP:
+ case Opcodes.ACONST_NULL:
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.IALOAD:
+ case Opcodes.LALOAD:
+ case Opcodes.FALOAD:
+ case Opcodes.DALOAD:
+ case Opcodes.AALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ case Opcodes.IASTORE:
+ case Opcodes.LASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.DASTORE:
+ case Opcodes.AASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.POP:
+ case Opcodes.POP2:
+ case Opcodes.DUP:
+ case Opcodes.DUP_X1:
+ case Opcodes.DUP_X2:
+ case Opcodes.DUP2:
+ case Opcodes.DUP2_X1:
+ case Opcodes.DUP2_X2:
+ case Opcodes.SWAP:
+ case Opcodes.IADD:
+ case Opcodes.LADD:
+ case Opcodes.FADD:
+ case Opcodes.DADD:
+ case Opcodes.ISUB:
+ case Opcodes.LSUB:
+ case Opcodes.FSUB:
+ case Opcodes.DSUB:
+ case Opcodes.IMUL:
+ case Opcodes.LMUL:
+ case Opcodes.FMUL:
+ case Opcodes.DMUL:
+ case Opcodes.IDIV:
+ case Opcodes.LDIV:
+ case Opcodes.FDIV:
+ case Opcodes.DDIV:
+ case Opcodes.IREM:
+ case Opcodes.LREM:
+ case Opcodes.FREM:
+ case Opcodes.DREM:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.ISHL:
+ case Opcodes.LSHL:
+ case Opcodes.ISHR:
+ case Opcodes.LSHR:
+ case Opcodes.IUSHR:
+ case Opcodes.LUSHR:
+ case Opcodes.IAND:
+ case Opcodes.LAND:
+ case Opcodes.IOR:
+ case Opcodes.LOR:
+ case Opcodes.IXOR:
+ case Opcodes.LXOR:
+ case Opcodes.I2L:
+ case Opcodes.I2F:
+ case Opcodes.I2D:
+ case Opcodes.L2I:
+ case Opcodes.L2F:
+ case Opcodes.L2D:
+ case Opcodes.F2I:
+ case Opcodes.F2L:
+ case Opcodes.F2D:
+ case Opcodes.D2I:
+ case Opcodes.D2L:
+ case Opcodes.D2F:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.LCMP:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.RETURN:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
methodVisitor.visitInsn(opcode);
currentOffset += 1;
break;
@@ -2021,33 +2260,33 @@ private void readCode(
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:
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.GOTO:
+ case Opcodes.JSR:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
methodVisitor.visitJumpInsn(
- opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]);
+ 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)]);
+ opcode - wideJumpOpcodeDelta,
+ labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
currentOffset += 5;
break;
case Constants.ASM_IFEQ:
@@ -2068,120 +2307,117 @@ private void readCode(
case Constants.ASM_JSR:
case Constants.ASM_IFNULL:
case Constants.ASM_IFNONNULL:
- {
- // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO
- // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:...,
- // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and
- // where designates the instruction just after the GOTO_W.
- // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and
- // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL.
- opcode =
- opcode < Constants.ASM_IFNULL
- ? opcode - Constants.ASM_OPCODE_DELTA
- : opcode - Constants.ASM_IFNULL_OPCODE_DELTA;
- Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)];
- if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
- // Replace GOTO with GOTO_W and JSR with JSR_W.
- methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target);
- } else {
- // Compute the "opposite" of opcode. This can be done by flipping the least
- // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ
- // (with a pre and post offset by 1).
- opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1;
- Label endif = createLabel(currentBytecodeOffset + 3, labels);
- methodVisitor.visitJumpInsn(opcode, endif);
- methodVisitor.visitJumpInsn(Constants.GOTO_W, target);
- // endif designates the instruction just after GOTO_W, and is visited as part of the
- // next instruction. Since it is a jump target, we need to insert a frame here.
- insertFrame = true;
- }
- currentOffset += 3;
- break;
- }
- case Constants.ASM_GOTO_W:
- {
- // Replace ASM_GOTO_W with GOTO_W.
- methodVisitor.visitJumpInsn(
- Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
- // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns
- // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame
- // here.
+ {
+ // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO
+ // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:...,
+ // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and
+ // where designates the instruction just after the GOTO_W.
+ // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and
+ // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL.
+ opcode =
+ opcode < Constants.ASM_IFNULL
+ ? opcode - Constants.ASM_OPCODE_DELTA
+ : opcode - Constants.ASM_IFNULL_OPCODE_DELTA;
+ Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)];
+ if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
+ // Replace GOTO with GOTO_W and JSR with JSR_W.
+ methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target);
+ } else {
+ // Compute the "opposite" of opcode. This can be done by flipping the least
+ // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ
+ // (with a pre and post offset by 1).
+ opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1;
+ Label endif = createLabel(currentBytecodeOffset + 3, labels);
+ methodVisitor.visitJumpInsn(opcode, endif);
+ methodVisitor.visitJumpInsn(Constants.GOTO_W, target);
+ // endif designates the instruction just after GOTO_W, and is visited as part of the
+ // next instruction. Since it is a jump target, we need to insert a frame here.
insertFrame = true;
- currentOffset += 5;
- 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;
+ opcode = classBuffer[currentOffset + 1] & 0xFF;
if (opcode == Opcodes.IINC) {
methodVisitor.visitIincInsn(
- readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4));
+ 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;
- }
- methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table);
- break;
+ case Opcodes.TABLESWITCH:
+ {
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (currentBytecodeOffset & 3);
+ // Read the instruction.
+ Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
+ int low = readInt(currentOffset + 4);
+ int high = readInt(currentOffset + 8);
+ currentOffset += 12;
+ Label[] table = new Label[high - low + 1];
+ for (int i = 0; i < table.length; ++i) {
+ table[i] = labels[currentBytecodeOffset + readInt(currentOffset)];
+ currentOffset += 4;
}
- 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);
+ methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table);
+ break;
+ }
+ case Opcodes.LOOKUPSWITCH:
+ {
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (currentBytecodeOffset & 3);
+ // Read the instruction.
+ Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
+ int numPairs = readInt(currentOffset + 4);
+ currentOffset += 8;
+ int[] keys = new int[numPairs];
+ Label[] values = new Label[numPairs];
+ for (int i = 0; i < numPairs; ++i) {
+ keys[i] = readInt(currentOffset);
+ values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)];
currentOffset += 8;
- 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;
- }
- 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);
+ methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values);
+ break;
+ }
+ case Opcodes.ILOAD:
+ case Opcodes.LLOAD:
+ case Opcodes.FLOAD:
+ case Opcodes.DLOAD:
+ case Opcodes.ALOAD:
+ case Opcodes.ISTORE:
+ case Opcodes.LSTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.DSTORE:
+ case Opcodes.ASTORE:
+ case Opcodes.RET:
+ methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF);
currentOffset += 2;
break;
- case Constants.BIPUSH:
- case Constants.NEWARRAY:
- methodVisitor.visitIntInsn(opcode, classFileBuffer[currentOffset + 1]);
+ case Opcodes.BIPUSH:
+ case Opcodes.NEWARRAY:
+ methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]);
currentOffset += 2;
break;
- case Constants.SIPUSH:
+ case Opcodes.SIPUSH:
methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1));
currentOffset += 3;
break;
- case Constants.LDC:
- methodVisitor.visitLdcInsn(
- readConst(classFileBuffer[currentOffset + 1] & 0xFF, charBuffer));
+ case Opcodes.LDC:
+ methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer));
currentOffset += 2;
break;
case Constants.LDC_W:
@@ -2189,71 +2425,71 @@ private void readCode(
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);
- }
- if (opcode == Opcodes.INVOKEINTERFACE) {
- currentOffset += 5;
- } else {
- currentOffset += 3;
- }
- break;
+ case Opcodes.GETSTATIC:
+ case Opcodes.PUTSTATIC:
+ case Opcodes.GETFIELD:
+ case Opcodes.PUTFIELD:
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.INVOKEINTERFACE:
+ {
+ int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String owner = readClass(cpInfoOffset, charBuffer);
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ if (opcode < Opcodes.INVOKEVIRTUAL) {
+ methodVisitor.visitFieldInsn(opcode, owner, name, descriptor);
+ } else {
+ boolean isInterface =
+ classBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
+ methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
- 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);
+ if (opcode == Opcodes.INVOKEINTERFACE) {
currentOffset += 5;
- break;
+ } else {
+ currentOffset += 3;
}
- case Constants.NEW:
- case Constants.ANEWARRAY:
- case Constants.CHECKCAST:
- case Constants.INSTANCEOF:
+ break;
+ }
+ case Opcodes.INVOKEDYNAMIC:
+ {
+ int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)];
+ Handle handle =
+ (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ Object[] bootstrapMethodArguments =
+ new Object[readUnsignedShort(bootstrapMethodOffset + 2)];
+ bootstrapMethodOffset += 4;
+ for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+ bootstrapMethodArguments[i] =
+ readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ bootstrapMethodOffset += 2;
+ }
+ methodVisitor.visitInvokeDynamicInsn(
+ name, descriptor, handle, bootstrapMethodArguments);
+ currentOffset += 5;
+ break;
+ }
+ case Opcodes.NEW:
+ case Opcodes.ANEWARRAY:
+ case Opcodes.CHECKCAST:
+ case Opcodes.INSTANCEOF:
methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer));
currentOffset += 3;
break;
- case Constants.IINC:
+ case Opcodes.IINC:
methodVisitor.visitIincInsn(
- classFileBuffer[currentOffset + 1] & 0xFF, classFileBuffer[currentOffset + 2]);
+ classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]);
currentOffset += 3;
break;
- case Constants.MULTIANEWARRAY:
+ case Opcodes.MULTIANEWARRAY:
methodVisitor.visitMultiANewArrayInsn(
- readClass(currentOffset + 1, charBuffer), classFileBuffer[currentOffset + 3] & 0xFF);
+ readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF);
currentOffset += 4;
break;
default:
@@ -2262,58 +2498,58 @@ private void readCode(
// Visit the runtime visible instruction annotations, if any.
while (visibleTypeAnnotationOffsets != null
- && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length
- && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
+ && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length
+ && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
// Parse the target_type, target_info and target_path fields.
int currentAnnotationOffset =
- readTypeAnnotationTarget(
- context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]);
+ 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);
+ methodVisitor.visitInsnAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ true),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
currentVisibleTypeAnnotationBytecodeOffset =
- getTypeAnnotationBytecodeOffset(
- visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex);
+ getTypeAnnotationBytecodeOffset(
+ visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex);
}
// Visit the runtime invisible instruction annotations, if any.
while (invisibleTypeAnnotationOffsets != null
- && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length
- && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
+ && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length
+ && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
// Parse the target_type, target_info and target_path fields.
int currentAnnotationOffset =
- readTypeAnnotationTarget(
- context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]);
+ 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);
+ methodVisitor.visitInsnAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible= */ false),
+ currentAnnotationOffset,
+ /* named= */ true,
+ charBuffer);
}
currentInvisibleTypeAnnotationBytecodeOffset =
- getTypeAnnotationBytecodeOffset(
- invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex);
+ getTypeAnnotationBytecodeOffset(
+ invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex);
}
}
if (labels[codeLength] != null) {
@@ -2355,62 +2591,62 @@ private void readCode(
}
}
methodVisitor.visitLocalVariable(
- name, descriptor, signature, labels[startPc], labels[startPc + length], index);
+ name, descriptor, signature, labels[startPc], labels[startPc + length], index);
}
}
// 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]);
+ for (int typeAnnotationOffset : visibleTypeAnnotationOffsets) {
+ int targetType = readByte(typeAnnotationOffset);
if (targetType == TypeReference.LOCAL_VARIABLE
- || targetType == TypeReference.RESOURCE_VARIABLE) {
+ || targetType == TypeReference.RESOURCE_VARIABLE) {
// Parse the target_type, target_info and target_path fields.
- currentOffset = readTypeAnnotationTarget(context, visibleTypeAnnotationOffsets[i]);
+ currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentOffset, charBuffer);
currentOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
readElementValues(
- methodVisitor.visitLocalVariableAnnotation(
- context.currentTypeAnnotationTarget,
- context.currentTypeAnnotationTargetPath,
- context.currentLocalVariableAnnotationRangeStarts,
- context.currentLocalVariableAnnotationRangeEnds,
- context.currentLocalVariableAnnotationRangeIndices,
- annotationDescriptor,
- /* visible = */ true),
- currentOffset,
- /* named = */ true,
- charBuffer);
+ methodVisitor.visitLocalVariableAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ context.currentLocalVariableAnnotationRangeStarts,
+ context.currentLocalVariableAnnotationRangeEnds,
+ context.currentLocalVariableAnnotationRangeIndices,
+ annotationDescriptor,
+ /* visible= */ true),
+ currentOffset,
+ /* named= */ true,
+ charBuffer);
}
}
}
// Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute.
if (invisibleTypeAnnotationOffsets != null) {
- for (int i = 0; i < invisibleTypeAnnotationOffsets.length; ++i) {
- int targetType = readByte(invisibleTypeAnnotationOffsets[i]);
+ for (int typeAnnotationOffset : invisibleTypeAnnotationOffsets) {
+ int targetType = readByte(typeAnnotationOffset);
if (targetType == TypeReference.LOCAL_VARIABLE
- || targetType == TypeReference.RESOURCE_VARIABLE) {
+ || targetType == TypeReference.RESOURCE_VARIABLE) {
// Parse the target_type, target_info and target_path fields.
- currentOffset = readTypeAnnotationTarget(context, invisibleTypeAnnotationOffsets[i]);
+ currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentOffset, charBuffer);
currentOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
readElementValues(
- methodVisitor.visitLocalVariableAnnotation(
- context.currentTypeAnnotationTarget,
- context.currentTypeAnnotationTargetPath,
- context.currentLocalVariableAnnotationRangeStarts,
- context.currentLocalVariableAnnotationRangeEnds,
- context.currentLocalVariableAnnotationRangeIndices,
- annotationDescriptor,
- /* visible = */ false),
- currentOffset,
- /* named = */ true,
- charBuffer);
+ methodVisitor.visitLocalVariableAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ context.currentLocalVariableAnnotationRangeStarts,
+ context.currentLocalVariableAnnotationRangeEnds,
+ context.currentLocalVariableAnnotationRangeIndices,
+ annotationDescriptor,
+ /* visible= */ false),
+ currentOffset,
+ /* named= */ true,
+ charBuffer);
}
}
}
@@ -2428,6 +2664,20 @@ private void readCode(
methodVisitor.visitMaxs(maxStack, maxLocals);
}
+ /**
+ * Handles the bytecode offset of the next instruction to be visited in {@link
+ * #accept(ClassVisitor,int)}. This method is called just before the instruction and before its
+ * associated label and stack map frame, if any. The default implementation of this method does
+ * nothing. Subclasses can override this method to store the argument in a mutable field, for
+ * instance, so that {@link MethodVisitor} instances can get the bytecode offset of each visited
+ * instruction (if so, the usual concurrency issues related to mutable data should be addressed).
+ *
+ * @param bytecodeOffset the bytecode offset of the next instruction to be visited.
+ */
+ protected void readBytecodeInstructionOffset(final int bytecodeOffset) {
+ // Do nothing by default.
+ }
+
/**
* 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.
@@ -2493,10 +2743,10 @@ private void createDebugLabel(final int bytecodeOffset, final Label[] labels) {
* 'annotations' array field.
*/
private int[] readTypeAnnotations(
- final MethodVisitor methodVisitor,
- final Context context,
- final int runtimeTypeAnnotationsOffset,
- final boolean visible) {
+ 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.
@@ -2557,19 +2807,19 @@ private int[] readTypeAnnotations(
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);
+ TypePath path = pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset);
currentOffset += 1 + 2 * pathLength;
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentOffset, charBuffer);
currentOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentOffset =
- readElementValues(
- methodVisitor.visitTryCatchAnnotation(
- targetType & 0xFFFFFF00, path, annotationDescriptor, visible),
- currentOffset,
- /* named = */ true,
- charBuffer);
+ 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,
@@ -2578,8 +2828,8 @@ private int[] readTypeAnnotations(
// 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);
+ readElementValues(
+ /* annotationVisitor= */ null, currentOffset, /* named= */ true, charBuffer);
}
}
return typeAnnotationsOffsets;
@@ -2590,16 +2840,16 @@ private int[] readTypeAnnotations(
* -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.
+ * Runtime[In]VisibleTypeAnnotations attribute, or {@literal null}.
* @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets.
* @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1
* if there is no such type_annotation of if it does not have a bytecode offset.
*/
private int getTypeAnnotationBytecodeOffset(
- final int[] typeAnnotationOffsets, final int typeAnnotationIndex) {
+ final int[] typeAnnotationOffsets, final int typeAnnotationIndex) {
if (typeAnnotationOffsets == null
- || typeAnnotationIndex >= typeAnnotationOffsets.length
- || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) {
+ || typeAnnotationIndex >= typeAnnotationOffsets.length
+ || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) {
return -1;
}
return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1);
@@ -2646,9 +2896,9 @@ private int readTypeAnnotationTarget(final Context context, final int typeAnnota
int index = readUnsignedShort(currentOffset + 4);
currentOffset += 6;
context.currentLocalVariableAnnotationRangeStarts[i] =
- createLabel(startPc, context.currentMethodLabels);
+ createLabel(startPc, context.currentMethodLabels);
context.currentLocalVariableAnnotationRangeEnds[i] =
- createLabel(startPc + length, context.currentMethodLabels);
+ createLabel(startPc + length, context.currentMethodLabels);
context.currentLocalVariableAnnotationRangeIndices[i] = index;
}
break;
@@ -2682,7 +2932,7 @@ private int readTypeAnnotationTarget(final Context context, final int typeAnnota
// Parse and store the target_path structure.
int pathLength = readByte(currentOffset);
context.currentTypeAnnotationTargetPath =
- pathLength == 0 ? null : new TypePath(b, currentOffset);
+ pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset);
// Return the start offset of the rest of the type_annotation structure.
return currentOffset + 1 + 2 * pathLength;
}
@@ -2699,12 +2949,12 @@ private int readTypeAnnotationTarget(final Context context, final int typeAnnota
* attribute, false it is a RuntimeInvisibleParameterAnnotations attribute.
*/
private void readParameterAnnotations(
- final MethodVisitor methodVisitor,
- final Context context,
- final int runtimeParameterAnnotationsOffset,
- final boolean visible) {
+ final MethodVisitor methodVisitor,
+ final Context context,
+ final int runtimeParameterAnnotationsOffset,
+ final boolean visible) {
int currentOffset = runtimeParameterAnnotationsOffset;
- int numParameters = b[currentOffset++] & 0xFF;
+ int numParameters = classFileBuffer[currentOffset++] & 0xFF;
methodVisitor.visitAnnotableParameterCount(numParameters, visible);
char[] charBuffer = context.charBuffer;
for (int i = 0; i < numParameters; ++i) {
@@ -2716,11 +2966,11 @@ private void readParameterAnnotations(
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);
+ readElementValues(
+ methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible),
+ currentOffset,
+ /* named= */ true,
+ charBuffer);
}
}
}
@@ -2740,10 +2990,10 @@ private void readParameterAnnotations(
* @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) {
+ 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);
@@ -2753,13 +3003,13 @@ private int readElementValues(
while (numElementValuePairs-- > 0) {
String elementName = readUTF8(currentOffset, charBuffer);
currentOffset =
- readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer);
+ readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer);
}
} else {
// Parse the array_value array.
while (numElementValuePairs-- > 0) {
currentOffset =
- readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer);
+ readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer);
}
}
if (annotationVisitor != null) {
@@ -2772,39 +3022,39 @@ private int readElementValues(
* 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 elementValueOffset the start offset in {@link #classFileBuffer} of the element_value
+ * structure to be read.
+ * @param elementName the name of the element_value structure to be read, or {@literal null}.
* @param charBuffer the buffer used to read strings in the constant pool.
* @return the end offset of the JVMS 'element_value' structure.
*/
private int readElementValue(
- final AnnotationVisitor annotationVisitor,
- final int elementValueOffset,
- final String elementName,
- final char[] charBuffer) {
+ final AnnotationVisitor annotationVisitor,
+ final int elementValueOffset,
+ final String elementName,
+ final char[] charBuffer) {
int currentOffset = elementValueOffset;
if (annotationVisitor == null) {
- switch (b[currentOffset] & 0xFF) {
+ switch (classFileBuffer[currentOffset] & 0xFF) {
case 'e': // enum_const_value
return currentOffset + 5;
case '@': // annotation_value
- return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer);
+ return readElementValues(null, currentOffset + 3, /* named= */ true, charBuffer);
case '[': // array_value
- return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer);
+ return readElementValues(null, currentOffset + 1, /* named= */ false, charBuffer);
default:
return currentOffset + 3;
}
}
- switch (b[currentOffset++] & 0xFF) {
+ switch (classFileBuffer[currentOffset++] & 0xFF) {
case 'B': // const_value_index, CONSTANT_Integer
annotationVisitor.visit(
- elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
currentOffset += 2;
break;
case 'C': // const_value_index, CONSTANT_Integer
annotationVisitor.visit(
- elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
currentOffset += 2;
break;
case 'D': // const_value_index, CONSTANT_Double
@@ -2812,21 +3062,21 @@ private int readElementValue(
case 'I': // const_value_index, CONSTANT_Integer
case 'J': // const_value_index, CONSTANT_Long
annotationVisitor.visit(
- elementName, readConst(readUnsignedShort(currentOffset), charBuffer));
+ elementName, readConst(readUnsignedShort(currentOffset), charBuffer));
currentOffset += 2;
break;
case 'S': // const_value_index, CONSTANT_Integer
annotationVisitor.visit(
- elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ 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);
+ elementName,
+ readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0
+ ? Boolean.FALSE
+ : Boolean.TRUE);
currentOffset += 2;
break;
case 's': // const_value_index, CONSTANT_Utf8
@@ -2835,9 +3085,9 @@ private int readElementValue(
break;
case 'e': // enum_const_value
annotationVisitor.visitEnum(
- elementName,
- readUTF8(currentOffset, charBuffer),
- readUTF8(currentOffset + 2, charBuffer));
+ elementName,
+ readUTF8(currentOffset, charBuffer),
+ readUTF8(currentOffset + 2, charBuffer));
currentOffset += 4;
break;
case 'c': // class_info
@@ -2846,23 +3096,23 @@ private int readElementValue(
break;
case '@': // annotation_value
currentOffset =
- readElementValues(
- annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)),
- currentOffset + 2,
- true,
- charBuffer);
+ 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);
+ annotationVisitor.visitArray(elementName),
+ currentOffset - 2,
+ /* named= */ false,
+ charBuffer);
}
- switch (b[currentOffset] & 0xFF) {
+ switch (classFileBuffer[currentOffset] & 0xFF) {
case 'B':
byte[] byteValues = new byte[numValues];
for (int i = 0; i < numValues; i++) {
@@ -2915,8 +3165,8 @@ private int readElementValue(
float[] floatValues = new float[numValues];
for (int i = 0; i < numValues; i++) {
floatValues[i] =
- Float.intBitsToFloat(
- readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
+ Float.intBitsToFloat(
+ readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
currentOffset += 3;
}
annotationVisitor.visit(elementName, floatValues);
@@ -2925,19 +3175,19 @@ private int readElementValue(
double[] doubleValues = new double[numValues];
for (int i = 0; i < numValues; i++) {
doubleValues[i] =
- Double.longBitsToDouble(
- readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
+ Double.longBitsToDouble(
+ readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
currentOffset += 3;
}
annotationVisitor.visit(elementName, doubleValues);
break;
default:
currentOffset =
- readElementValues(
- annotationVisitor.visitArray(elementName),
- currentOffset - 2,
- /* named = */ false,
- charBuffer);
+ readElementValues(
+ annotationVisitor.visitArray(elementName),
+ currentOffset - 2,
+ /* named= */ false,
+ charBuffer);
break;
}
break;
@@ -2960,12 +3210,12 @@ private int readElementValue(
private void computeImplicitFrame(final Context context) {
String methodDescriptor = context.currentMethodDescriptor;
Object[] locals = context.currentFrameLocalTypes;
- int nLocal = 0;
+ int numLocal = 0;
if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) {
if ("".equals(context.currentMethodName)) {
- locals[nLocal++] = Opcodes.UNINITIALIZED_THIS;
+ locals[numLocal++] = Opcodes.UNINITIALIZED_THIS;
} else {
- locals[nLocal++] = readClass(header + 2, context.charBuffer);
+ locals[numLocal++] = readClass(header + 2, context.charBuffer);
}
}
// Parse the method descriptor, one argument type descriptor at each iteration. Start by
@@ -2979,16 +3229,16 @@ private void computeImplicitFrame(final Context context) {
case 'B':
case 'S':
case 'I':
- locals[nLocal++] = Opcodes.INTEGER;
+ locals[numLocal++] = Opcodes.INTEGER;
break;
case 'F':
- locals[nLocal++] = Opcodes.FLOAT;
+ locals[numLocal++] = Opcodes.FLOAT;
break;
case 'J':
- locals[nLocal++] = Opcodes.LONG;
+ locals[numLocal++] = Opcodes.LONG;
break;
case 'D':
- locals[nLocal++] = Opcodes.DOUBLE;
+ locals[numLocal++] = Opcodes.DOUBLE;
break;
case '[':
while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') {
@@ -3000,20 +3250,20 @@ private void computeImplicitFrame(final Context context) {
++currentMethodDescritorOffset;
}
}
- locals[nLocal++] =
- methodDescriptor.substring(
- currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset);
+ locals[numLocal++] =
+ methodDescriptor.substring(
+ currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset);
break;
case 'L':
while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') {
++currentMethodDescritorOffset;
}
- locals[nLocal++] =
- methodDescriptor.substring(
- currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++);
+ locals[numLocal++] =
+ methodDescriptor.substring(
+ currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++);
break;
default:
- context.currentFrameLocalCount = nLocal;
+ context.currentFrameLocalCount = numLocal;
return;
}
}
@@ -3024,9 +3274,9 @@ private void computeImplicitFrame(final Context 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 stackMapFrameOffset the start offset in {@link #classFileBuffer} of the
+ * stack_map_frame_value structure to be read, or the start offset of a full_frame structure
+ * (excluding its frame_type field).
* @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame'
* structure without its frame_type field.
* @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}.
@@ -3034,17 +3284,17 @@ private void computeImplicitFrame(final Context context) {
* @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) {
+ 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;
+ frameType = classFileBuffer[currentOffset++] & 0xFF;
} else {
frameType = Frame.FULL_FRAME;
context.currentFrameOffset = -1;
@@ -3058,8 +3308,8 @@ private int readStackMapFrame(
} else if (frameType < Frame.RESERVED) {
offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME;
currentOffset =
- readVerificationTypeInfo(
- currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
+ 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) {
@@ -3067,8 +3317,8 @@ private int readStackMapFrame(
currentOffset += 2;
if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
currentOffset =
- readVerificationTypeInfo(
- currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
+ 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) {
@@ -3083,8 +3333,8 @@ private int readStackMapFrame(
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);
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels);
}
context.currentFrameType = Opcodes.F_APPEND;
context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED;
@@ -3098,16 +3348,16 @@ private int readStackMapFrame(
context.currentFrameLocalCount = numberOfLocals;
for (int local = 0; local < numberOfLocals; ++local) {
currentOffset =
- readVerificationTypeInfo(
- currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels);
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels);
}
final int numberOfStackItems = readUnsignedShort(currentOffset);
currentOffset += 2;
context.currentFrameStackCount = numberOfStackItems;
for (int stack = 0; stack < numberOfStackItems; ++stack) {
currentOffset =
- readVerificationTypeInfo(
- currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels);
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels);
}
}
} else {
@@ -3133,13 +3383,13 @@ private int readStackMapFrame(
* @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) {
+ final int verificationTypeInfoOffset,
+ final Object[] frame,
+ final int index,
+ final char[] charBuffer,
+ final Label[] labels) {
int currentOffset = verificationTypeInfoOffset;
- int tag = b[currentOffset++] & 0xFF;
+ int tag = classFileBuffer[currentOffset++] & 0xFF;
switch (tag) {
case Frame.ITEM_TOP:
frame[index] = Opcodes.TOP;
@@ -3180,7 +3430,13 @@ private int readVerificationTypeInfo(
// Methods to parse attributes
// ----------------------------------------------------------------------------------------------
- /** @return the offset in {@link #b} of the first ClassFile's 'attributes' array field entry. */
+ /**
+ * Returns the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array
+ * field entry.
+ *
+ * @return the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array
+ * field entry.
+ */
final int getFirstAttributeOffset() {
// Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes
// each), as well as the interfaces array field (2 bytes per interface).
@@ -3222,35 +3478,71 @@ final int getFirstAttributeOffset() {
}
/**
- * Reads a non standard JVMS 'attribute' structure in {@link #b}.
+ * Reads the BootstrapMethods attribute to compute the offset of each bootstrap method.
+ *
+ * @param maxStringLength a conservative estimate of the maximum length of the strings contained
+ * in the constant pool of the class.
+ * @return the offsets of the bootstrap methods.
+ */
+ private int[] readBootstrapMethodsAttribute(final int maxStringLength) {
+ char[] charBuffer = new char[maxStringLength];
+ int currentAttributeOffset = getFirstAttributeOffset();
+ for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentAttributeOffset, charBuffer);
+ int attributeLength = readInt(currentAttributeOffset + 2);
+ currentAttributeOffset += 6;
+ if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ // Read the num_bootstrap_methods field and create an array of this size.
+ int[] result = 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 < result.length; ++j) {
+ result[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 result;
+ }
+ currentAttributeOffset += attributeLength;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Reads a non standard JVMS 'attribute' structure in {@link #classFileBuffer}.
*
* @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
* the class. Any attribute whose type is not equal to the type of one the prototypes will not
* be parsed: its byte array value will be passed unchanged to the ClassWriter.
* @param type the type of the attribute.
- * @param offset the start offset of the JVMS 'attribute' structure in {@link #b}. The 6 attribute
- * header bytes (attribute_name_index and attribute_length) are not taken into account here.
+ * @param offset the start offset of the JVMS 'attribute' structure in {@link #classFileBuffer}.
+ * The 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
* @param length the length of the attribute's content (excluding the 6 attribute header bytes).
* @param charBuffer the buffer to be used to read strings in the constant pool.
- * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link #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.
+ * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link
+ * #classFileBuffer}, or -1 if the attribute to be read is not a code attribute. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param labels the labels of the method's code, or {@literal null} if the attribute to be read
+ * is not a code attribute.
* @return the attribute that has been read.
*/
private Attribute readAttribute(
- final Attribute[] attributePrototypes,
- final String type,
- final int offset,
- final int length,
- final char[] charBuffer,
- final int codeAttributeOffset,
- final Label[] labels) {
- for (int i = 0; i < attributePrototypes.length; ++i) {
- if (attributePrototypes[i].type.equals(type)) {
- return attributePrototypes[i].read(
- this, offset, length, charBuffer, codeAttributeOffset, labels);
+ final Attribute[] attributePrototypes,
+ final String type,
+ final int offset,
+ final int length,
+ final char[] charBuffer,
+ final int codeAttributeOffset,
+ final Label[] labels) {
+ for (Attribute attributePrototype : attributePrototypes) {
+ if (attributePrototype.type.equals(type)) {
+ return attributePrototype.read(
+ this, offset, length, charBuffer, codeAttributeOffset, labels);
}
}
return new Attribute(type).read(this, offset, length, null, -1, null);
@@ -3270,13 +3562,14 @@ public int getItemCount() {
}
/**
- * 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.
+ * Returns the start offset in this {@link ClassReader} of a JVMS 'cp_info' structure (i.e. a
+ * constant pool entry), plus one. This method is intended for {@link Attribute} sub classes,
+ * and is normally not needed by class generators or adapters.
*
* @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool
* table.
- * @return the start offset in {@link #b} of the corresponding JVMS 'cp_info' structure, plus one.
+ * @return the start offset in this {@link ClassReader} of the corresponding JVMS 'cp_info'
+ * structure, plus one.
*/
public int getItem(final int constantPoolEntryIndex) {
return cpInfoOffsets[constantPoolEntryIndex];
@@ -3294,60 +3587,74 @@ public int getMaxStringLength() {
}
/**
- * 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.
+ * Reads a byte value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
*
- * @param offset the start offset of the value to be read in {@link #b}.
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
* @return the read value.
*/
public int readByte(final int offset) {
- return b[offset] & 0xFF;
+ return classFileBuffer[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.
+ * Reads several bytes in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the bytes to be read in this {@link ClassReader}.
+ * @param length the number of bytes to read.
+ * @return the read bytes.
+ */
+ public byte[] readBytes(final int offset, final int length) {
+ byte[] result = new byte[length];
+ System.arraycopy(classFileBuffer, offset, result, 0, length);
+ return result;
+ }
+
+ /**
+ * Reads an unsigned short value in this {@link ClassReader}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.
*
- * @param offset the start index of the value to be read in {@link #b}.
+ * @param offset the start index of the value to be read in this {@link ClassReader}.
* @return the read value.
*/
public int readUnsignedShort(final int offset) {
- byte[] classFileBuffer = b;
- return ((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF);
+ byte[] classBuffer = classFileBuffer;
+ return ((classBuffer[offset] & 0xFF) << 8) | (classBuffer[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.
+ * Reads a signed short value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
*
- * @param offset the start offset of the value to be read in {@link #b}.
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
* @return the read value.
*/
public short readShort(final int offset) {
- byte[] classFileBuffer = b;
- return (short) (((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF));
+ byte[] classBuffer = classFileBuffer;
+ return (short) (((classBuffer[offset] & 0xFF) << 8) | (classBuffer[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.
+ * Reads a signed int value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
*
- * @param offset the start offset of the value to be read in {@link #b}.
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
* @return the read value.
*/
public int readInt(final int offset) {
- byte[] classFileBuffer = b;
- return ((classFileBuffer[offset] & 0xFF) << 24)
- | ((classFileBuffer[offset + 1] & 0xFF) << 16)
- | ((classFileBuffer[offset + 2] & 0xFF) << 8)
- | (classFileBuffer[offset + 3] & 0xFF);
+ byte[] classBuffer = classFileBuffer;
+ return ((classBuffer[offset] & 0xFF) << 24)
+ | ((classBuffer[offset + 1] & 0xFF) << 16)
+ | ((classBuffer[offset + 2] & 0xFF) << 8)
+ | (classBuffer[offset + 3] & 0xFF);
}
/**
- * Reads a signed long value in {@link #b}. This method is intended for {@link Attribute} sub
- * classes, and is normally not needed by class generators or adapters.
+ * Reads a signed long value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
*
- * @param offset the start offset of the value to be read in {@link #b}.
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
* @return the read value.
*/
public long readLong(final int offset) {
@@ -3357,25 +3664,27 @@ public long readLong(final int offset) {
}
/**
- * 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.
+ * Reads a CONSTANT_Utf8 constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
*
- * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
- * index of a CONSTANT_Utf8 entry in the class's constant pool table.
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Utf8 entry in the class's constant pool table.
* @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Utf8 entry.
*/
+ // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
public String readUTF8(final int offset, final char[] charBuffer) {
int constantPoolEntryIndex = readUnsignedShort(offset);
if (offset == 0 || constantPoolEntryIndex == 0) {
return null;
}
- return readUTF(constantPoolEntryIndex, charBuffer);
+ return readUtf(constantPoolEntryIndex, charBuffer);
}
/**
- * Reads a CONSTANT_Utf8 constant pool entry in {@link #b}.
+ * Reads a CONSTANT_Utf8 constant pool entry in {@link #classFileBuffer}.
*
* @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool
* table.
@@ -3383,19 +3692,18 @@ public String readUTF8(final int offset, final char[] charBuffer) {
* 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];
+ final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) {
+ String value = constantUtf8Values[constantPoolEntryIndex];
if (value != null) {
return value;
}
int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
- value = readUTF(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer);
- cpInfoValues[constantPoolEntryIndex] = value;
- return value;
+ return constantUtf8Values[constantPoolEntryIndex] =
+ readUtf(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer);
}
/**
- * Reads an UTF8 string in {@link #b}.
+ * Reads an UTF8 string in {@link #classFileBuffer}.
*
* @param utfOffset the start offset of the UTF8 string to be read.
* @param utfLength the length of the UTF8 string to be read.
@@ -3403,24 +3711,24 @@ final String readUTF(final int constantPoolEntryIndex, final char[] charBuffer)
* 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) {
+ 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;
+ byte[] classBuffer = classFileBuffer;
while (currentOffset < endOffset) {
- int currentByte = classFileBuffer[currentOffset++];
+ int currentByte = classBuffer[currentOffset++];
if ((currentByte & 0x80) == 0) {
charBuffer[strLength++] = (char) (currentByte & 0x7F);
} else if ((currentByte & 0xE0) == 0xC0) {
charBuffer[strLength++] =
- (char) (((currentByte & 0x1F) << 6) + (classFileBuffer[currentOffset++] & 0x3F));
+ (char) (((currentByte & 0x1F) << 6) + (classBuffer[currentOffset++] & 0x3F));
} else {
charBuffer[strLength++] =
- (char)
- (((currentByte & 0xF) << 12)
- + ((classFileBuffer[currentOffset++] & 0x3F) << 6)
- + (classFileBuffer[currentOffset++] & 0x3F));
+ (char)
+ (((currentByte & 0xF) << 12)
+ + ((classBuffer[currentOffset++] & 0x3F) << 6)
+ + (classBuffer[currentOffset++] & 0x3F));
}
}
return new String(charBuffer, 0, strLength);
@@ -3428,12 +3736,13 @@ private String readUTF(final int utfOffset, final int utfLength, final char[] ch
/**
* 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.
+ * CONSTANT_Package constant pool entry in {@link #classFileBuffer}. This method is intended
+ * for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
*
- * @param offset the start offset of an unsigned short value in {@link #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 offset the start offset of an unsigned short value in {@link #classFileBuffer}, whose
+ * value is the index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType,
+ * CONSTANT_Module or CONSTANT_Package entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified constant pool entry.
@@ -3445,11 +3754,12 @@ private String readStringish(final int offset, final char[] 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.
+ * Reads a CONSTANT_Class constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
*
- * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
- * index of a CONSTANT_Class entry in class's constant pool table.
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Class entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Class entry.
@@ -3459,11 +3769,12 @@ public String readClass(final int offset, final char[] 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.
+ * Reads a CONSTANT_Module constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
*
- * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
- * index of a CONSTANT_Module entry in class's constant pool table.
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Module entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Module entry.
@@ -3473,11 +3784,12 @@ public String readModule(final int offset, final char[] 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.
+ * Reads a CONSTANT_Package constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
*
- * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
- * index of a CONSTANT_Package entry in class's constant pool table.
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Package entry in class's constant pool table.
* @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
* large. It is not automatically resized.
* @return the String corresponding to the specified CONSTANT_Package entry.
@@ -3487,7 +3799,7 @@ public String readPackage(final int offset, final char[] charBuffer) {
}
/**
- * Reads a CONSTANT_Dynamic constant pool entry in {@link #b}.
+ * Reads a CONSTANT_Dynamic constant pool entry in {@link #classFileBuffer}.
*
* @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant
* pool table.
@@ -3496,8 +3808,8 @@ public String readPackage(final int offset, final char[] charBuffer) {
* @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry.
*/
private ConstantDynamic readConstantDynamic(
- final int constantPoolEntryIndex, final char[] charBuffer) {
- ConstantDynamic constantDynamic = (ConstantDynamic) cpInfoValues[constantPoolEntryIndex];
+ final int constantPoolEntryIndex, final char[] charBuffer) {
+ ConstantDynamic constantDynamic = constantDynamicValues[constantPoolEntryIndex];
if (constantDynamic != null) {
return constantDynamic;
}
@@ -3513,14 +3825,14 @@ private ConstantDynamic readConstantDynamic(
bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
bootstrapMethodOffset += 2;
}
- constantDynamic = new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments);
- cpInfoValues[constantPoolEntryIndex] = constantDynamic;
- return constantDynamic;
+ return constantDynamicValues[constantPoolEntryIndex] =
+ new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments);
}
/**
- * 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.
+ * Reads a numeric or string constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
*
* @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long,
* CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType,
@@ -3533,7 +3845,7 @@ private ConstantDynamic readConstantDynamic(
*/
public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) {
int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
- switch (b[cpInfoOffset - 1]) {
+ switch (classFileBuffer[cpInfoOffset - 1]) {
case Symbol.CONSTANT_INTEGER_TAG:
return readInt(cpInfoOffset);
case Symbol.CONSTANT_FLOAT_TAG:
@@ -3556,7 +3868,7 @@ public Object readConst(final int constantPoolEntryIndex, final char[] charBuffe
String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
boolean isInterface =
- b[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
+ classFileBuffer[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
return new Handle(referenceKind, owner, name, descriptor, isInterface);
case Symbol.CONSTANT_DYNAMIC_TAG:
return readConstantDynamic(constantPoolEntryIndex, charBuffer);
@@ -3564,4 +3876,4 @@ public Object readConst(final int constantPoolEntryIndex, final char[] charBuffe
throw new IllegalArgumentException();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ClassTooLargeException.java b/src/jvm/clojure/asm/ClassTooLargeException.java
new file mode 100644
index 0000000000..e245c7313f
--- /dev/null
+++ b/src/jvm/clojure/asm/ClassTooLargeException.java
@@ -0,0 +1,72 @@
+// 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;
+
+/**
+ * Exception thrown when the constant pool of a class produced by a {@link ClassWriter} is too
+ * large.
+ *
+ * @author Jason Zaugg
+ */
+public final class ClassTooLargeException extends IndexOutOfBoundsException {
+ private static final long serialVersionUID = 160715609518896765L;
+
+ private final String className;
+ private final int constantPoolCount;
+
+ /**
+ * Constructs a new {@link ClassTooLargeException}.
+ *
+ * @param className the internal name of the class (see {@link
+ * org.objectweb.asm.Type#getInternalName()}).
+ * @param constantPoolCount the number of constant pool items of the class.
+ */
+ public ClassTooLargeException(final String className, final int constantPoolCount) {
+ super("Class too large: " + className);
+ this.className = className;
+ this.constantPoolCount = constantPoolCount;
+ }
+
+ /**
+ * Returns the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()}).
+ *
+ * @return the internal name of the class.
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ /**
+ * Returns the number of constant pool items of the class.
+ *
+ * @return the number of constant pool items of the class.
+ */
+ public int getConstantPoolCount() {
+ return constantPoolCount;
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ClassVisitor.java b/src/jvm/clojure/asm/ClassVisitor.java
index dc20a8107a..8f18835213 100644
--- a/src/jvm/clojure/asm/ClassVisitor.java
+++ b/src/jvm/clojure/asm/ClassVisitor.java
@@ -29,78 +29,96 @@
/**
* 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.
+ * {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code
+ * visitOuterClass} ] ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code
+ * visitAttribute} )* ( {@code visitNestMember} | [ {@code * visitPermittedSubclass} ] | {@code
+ * visitInnerClass} | {@code visitRecordComponent} | {@code visitField} | {@code visitMethod} )*
+ * {@code visitEnd}.
*
* @author Eric Bruneton
*/
public abstract class ClassVisitor {
/**
- * The ASM API version implemented by this visitor. The value of this field must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
*/
protected final int api;
- /** The class visitor to which this visitor must delegate method calls. May be null. */
+ /** The class visitor to which this visitor must delegate method calls. May be {@literal null}. */
protected ClassVisitor cv;
/**
* Constructs a new {@link ClassVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
- * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public ClassVisitor(final int api) {
+ protected 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 api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @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();
+ protected ClassVisitor(final int api, final ClassVisitor classVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
this.api = api;
this.cv = classVisitor;
}
+ /**
+ * The class visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the class visitor to which this visitor must delegate method calls, or {@literal null}.
+ */
+ public ClassVisitor getDelegate() {
+ return cv;
+ }
+
/**
* 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.
+ * the class is deprecated {@link Opcodes#ACC_DEPRECATED} or a record {@link
+ * Opcodes#ACC_RECORD}.
* @param name the internal name of the class (see {@link Type#getInternalName()}).
- * @param signature the signature of this class. May be null if the class is not a
+ * @param signature the signature of this class. May be {@literal null} if the class is not a
* generic one, and does not extend or implement generic classes or interfaces.
* @param superName the internal of name of the super class (see {@link Type#getInternalName()}).
- * For interfaces, the super class is {@link Object}. May be null, but only for the
+ * For interfaces, the super class is {@link Object}. May be {@literal null}, but only for the
* {@link Object} class.
* @param interfaces the internal names of the class's interfaces (see {@link
- * Type#getInternalName()}). May be null.
+ * Type#getInternalName()}). May be {@literal null}.
*/
public void visit(
- final int version,
- final int access,
- final String name,
- final String signature,
- final String superName,
- final String[] interfaces) {
+ final int version,
+ final int access,
+ final String name,
+ final String signature,
+ final String superName,
+ final String[] interfaces) {
+ if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) {
+ throw new UnsupportedOperationException("Records requires ASM8");
+ }
if (cv != null) {
cv.visit(version, access, name, signature, superName, interfaces);
}
@@ -109,10 +127,10 @@ public void visit(
/**
* Visits the source of the class.
*
- * @param source the name of the source file from which the class was compiled. May be
- * null.
+ * @param source the name of the source file from which the class was compiled. May be {@literal
+ * null}.
* @param debug additional debug information to compute the correspondence between source and
- * compiled elements of the class. May be null.
+ * compiled elements of the class. May be {@literal null}.
*/
public void visitSource(final String source, final String debug) {
if (cv != null) {
@@ -126,13 +144,13 @@ public void visitSource(final String source, final String debug) {
* @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
+ * @param version the module version, or {@literal null}.
+ * @return a visitor to visit the module values, or {@literal null} if this visitor is not
* interested in visiting this module.
*/
public ModuleVisitor visitModule(final String name, final int access, final String version) {
if (api < Opcodes.ASM6) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Module requires ASM6");
}
if (cv != null) {
return cv.visitModule(name, access, version);
@@ -141,36 +159,39 @@ public ModuleVisitor visitModule(final String name, final int access, final Stri
}
/**
- * 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.
+ * 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.
+ * @param nestHost the internal name of the host class of the nest (see {@link
+ * Type#getInternalName()}).
*/
- @Deprecated
- public void visitNestHostExperimental(final String nestHost) {
- if (api < Opcodes.ASM7_EXPERIMENTAL) {
- throw new UnsupportedOperationException();
+ public void visitNestHost(final String nestHost) {
+ if (api < Opcodes.ASM7) {
+ throw new UnsupportedOperationException("NestHost requires ASM7");
}
if (cv != null) {
- cv.visitNestHostExperimental(nestHost);
+ cv.visitNestHost(nestHost);
}
}
/**
- * Visits the enclosing class of the class. This method must be called only if the class has an
- * enclosing class.
+ * Visits the enclosing class of the class. This method must be called only if this class is a
+ * local or anonymous class. See the JVMS 4.7.7 section for more details.
*
- * @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.
+ * @param owner internal name of the enclosing class of the class (see {@link
+ * Type#getInternalName()}).
+ * @param name the name of the method that contains the class, or {@literal null} if the class is
+ * not enclosed in a method or constructor of its enclosing class (e.g. if it is enclosed in
+ * an instance initializer, static initializer, instance variable initializer, or class
+ * variable initializer).
+ * @param descriptor the descriptor of the method that contains the class, or {@literal null} if
+ * the class is not enclosed in a method or constructor of its enclosing class (e.g. if it is
+ * enclosed in an instance initializer, static initializer, instance variable initializer, or
+ * class variable initializer).
*/
public void visitOuterClass(final String owner, final String name, final String descriptor) {
if (cv != null) {
@@ -182,8 +203,8 @@ public void visitOuterClass(final String owner, final String name, final String
* 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
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
@@ -201,17 +222,17 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean
* 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
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
* 'typeRef' as a whole.
* @param descriptor the class descriptor of the annotation class.
- * @param visible true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if this visitor is not
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitTypeAnnotation(
- final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (api < Opcodes.ASM5) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("TypeAnnotation requires ASM5");
}
if (cv != null) {
return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
@@ -231,46 +252,82 @@ public void visitAttribute(final Attribute attribute) {
}
/**
- * 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.
+ * 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 (see {@link Type#getInternalName()}).
+ */
+ public void visitNestMember(final String nestMember) {
+ if (api < Opcodes.ASM7) {
+ throw new UnsupportedOperationException("NestMember requires ASM7");
+ }
+ if (cv != null) {
+ cv.visitNestMember(nestMember);
+ }
+ }
+
+ /**
+ * Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the
+ * current class.
*
- * @param nestMember the internal name of a nest member.
- * @deprecated This API is experimental.
+ * @param permittedSubclass the internal name of a permitted subclass (see {@link
+ * Type#getInternalName()}).
*/
- @Deprecated
- public void visitNestMemberExperimental(final String nestMember) {
- if (api < Opcodes.ASM7_EXPERIMENTAL) {
- throw new UnsupportedOperationException();
+ public void visitPermittedSubclass(final String permittedSubclass) {
+ if (api < Opcodes.ASM9) {
+ throw new UnsupportedOperationException("PermittedSubclasses requires ASM9");
}
if (cv != null) {
- cv.visitNestMemberExperimental(nestMember);
+ cv.visitPermittedSubclass(permittedSubclass);
}
}
/**
* Visits information about an inner class. This inner class is not necessarily a member of the
- * class being visited.
+ * class being visited. More precisely, every class or interface C which is referenced by this
+ * class and which is not a package member must be visited with this method. This class must
+ * reference its nested class or interface members, and its enclosing class, if any. See the JVMS
+ * 4.7.6 section for more details.
*
- * @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.
+ * @param name the internal name of C (see {@link Type#getInternalName()}).
+ * @param outerName the internal name of the class or interface C is a member of (see {@link
+ * Type#getInternalName()}). Must be {@literal null} if C is not the member of a class or
+ * interface (e.g. for local or anonymous classes).
+ * @param innerName the (simple) name of C. Must be {@literal null} for anonymous inner classes.
+ * @param access the access flags of C originally declared in the source code from which this
+ * class was compiled.
*/
public void visitInnerClass(
- final String name, final String outerName, final String innerName, final int access) {
+ final String name, final String outerName, final String innerName, final int access) {
if (cv != null) {
cv.visitInnerClass(name, outerName, innerName, access);
}
}
+ /**
+ * Visits a record component of the class.
+ *
+ * @param name the record component name.
+ * @param descriptor the record component descriptor (see {@link Type}).
+ * @param signature the record component signature. May be {@literal null} if the record component
+ * type does not use generic types.
+ * @return a visitor to visit this record component annotations and attributes, or {@literal null}
+ * if this class visitor is not interested in visiting these annotations and attributes.
+ */
+ public RecordComponentVisitor visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ if (api < Opcodes.ASM8) {
+ throw new UnsupportedOperationException("Record requires ASM8");
+ }
+ if (cv != null) {
+ return cv.visitRecordComponent(name, descriptor, signature);
+ }
+ return null;
+ }
+
/**
* Visits a field of the class.
*
@@ -278,23 +335,23 @@ public void visitInnerClass(
* 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
+ * @param signature the field's signature. May be {@literal null} if the field's type does not use
* generic types.
- * @param value the field's initial value. This parameter, which may be 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
+ * @param value the field's initial value. This parameter, which may be {@literal null} if the
+ * field does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link
+ * Long}, a {@link Double} or a {@link String} (for {@code int}, {@code float}, {@code long}
+ * or {@code String} fields respectively). This parameter is only used for static
+ * fields. Its value is ignored for non static fields, which must be initialized through
+ * bytecode instructions in constructors or methods.
+ * @return a visitor to visit field annotations and attributes, or {@literal null} if this class
* visitor is not interested in visiting these annotations and attributes.
*/
public FieldVisitor visitField(
- final int access,
- final String name,
- final String descriptor,
- final String signature,
- final Object value) {
+ 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);
}
@@ -303,26 +360,26 @@ public FieldVisitor visitField(
/**
* 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
+ * instance (or {@literal null}) each time it is called, i.e., it should not return a previously
* returned visitor.
*
* @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if
* the method is synthetic and/or deprecated.
* @param name the method's name.
* @param descriptor the method's descriptor (see {@link Type}).
- * @param signature the method's signature. May be null if the method parameters, return
- * type and exceptions do not use generic types.
+ * @param signature the method's signature. May be {@literal null} if the method parameters,
+ * return type and exceptions do not use generic types.
* @param exceptions the internal names of the method's exception classes (see {@link
- * Type#getInternalName()}). May be 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.
+ * Type#getInternalName()}). May be {@literal null}.
+ * @return an object to visit the byte code of the method, or {@literal null} if this class
+ * visitor is not interested in visiting the code of this method.
*/
public MethodVisitor visitMethod(
- final int access,
- final String name,
- final String descriptor,
- final String signature,
- final String[] exceptions) {
+ 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);
}
@@ -338,4 +395,4 @@ public void visitEnd() {
cv.visitEnd();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ClassWriter.java b/src/jvm/clojure/asm/ClassWriter.java
index 4727fe376c..758ab9af9f 100644
--- a/src/jvm/clojure/asm/ClassWriter.java
+++ b/src/jvm/clojure/asm/ClassWriter.java
@@ -65,6 +65,12 @@ public class ClassWriter extends ClassVisitor {
*/
public static final int COMPUTE_FRAMES = 2;
+ /**
+ * The flags passed to the constructor. Must be zero or more of {@link #COMPUTE_MAXS} and {@link
+ * #COMPUTE_FRAMES}.
+ */
+ private final int flags;
+
// Note: fields are ordered as in the ClassFile structure, and those related to attributes are
// ordered as in Section 4.7 of the JVMS.
@@ -79,8 +85,8 @@ public class ClassWriter extends ClassVisitor {
/**
* 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.
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED} or {@link Opcodes#ACC_RECORD}, which are
+ * removed when generating the ClassFile structure.
*/
private int accessFlags;
@@ -123,7 +129,7 @@ public class ClassWriter extends ClassVisitor {
/** The number_of_classes field of the InnerClasses attribute, or 0. */
private int numberOfInnerClasses;
- /** The 'classes' array of the InnerClasses attribute, or null. */
+ /** The 'classes' array of the InnerClasses attribute, or {@literal null}. */
private ByteVector innerClasses;
/** The class_index field of the EnclosingMethod attribute, or 0. */
@@ -138,34 +144,34 @@ public class ClassWriter extends ClassVisitor {
/** The source_file_index field of the SourceFile attribute, or 0. */
private int sourceFileIndex;
- /** The debug_extension field of the SourceDebugExtension attribute, or null. */
+ /** The debug_extension field of the SourceDebugExtension attribute, or {@literal null}. */
private ByteVector debugExtension;
/**
* The last runtime visible annotation of this class. The previous ones can be accessed with the
- * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleAnnotation;
/**
* The last runtime invisible annotation of this class. The previous ones can be accessed with the
- * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleAnnotation;
/**
* The last runtime visible type annotation of this class. The previous ones can be accessed with
- * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
/**
* The last runtime invisible type annotation of this class. The previous ones can be accessed
- * with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
- /** The Module attribute of this class, or null. */
+ /** The Module attribute of this class, or {@literal null}. */
private ModuleWriter moduleWriter;
/** The host_class_index field of the NestHost attribute, or 0. */
@@ -174,12 +180,32 @@ public class ClassWriter extends ClassVisitor {
/** The number_of_classes field of the NestMembers attribute, or 0. */
private int numberOfNestMemberClasses;
- /** The 'classes' array of the NestMembers attribute, or null. */
+ /** The 'classes' array of the NestMembers attribute, or {@literal null}. */
private ByteVector nestMemberClasses;
+ /** The number_of_classes field of the PermittedSubclasses attribute, or 0. */
+ private int numberOfPermittedSubclasses;
+
+ /** The 'classes' array of the PermittedSubclasses attribute, or {@literal null}. */
+ private ByteVector permittedSubclasses;
+
+ /**
+ * The record components of this class, stored in a linked list of {@link RecordComponentWriter}
+ * linked via their {@link RecordComponentWriter#delegate} field. This field stores the first
+ * element of this list.
+ */
+ private RecordComponentWriter firstRecordComponent;
+
+ /**
+ * The record components of this class, stored in a linked list of {@link RecordComponentWriter}
+ * linked via their {@link RecordComponentWriter#delegate} field. This field stores the last
+ * element of this list.
+ */
+ private RecordComponentWriter lastRecordComponent;
+
/**
* The first non standard attribute of this class. The next ones can be accessed with the {@link
- * Attribute#nextAttribute} field. May be null.
+ * Attribute#nextAttribute} field. May be {@literal null}.
*
*
WARNING: this list stores the attributes in the reverse order of their visit.
* firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
@@ -191,6 +217,7 @@ public class ClassWriter extends ClassVisitor {
/**
* 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_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link
* MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}.
*/
private int compute;
@@ -228,21 +255,31 @@ public ClassWriter(final int flags) {
* @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
+ * @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);
+ super(/* latest api = */ Opcodes.ASM9);
+ this.flags = flags;
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;
- }
+ setFlags(flags);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns true if all the given flags were passed to the constructor.
+ *
+ * @param flags some option flags. Must be zero or more of {@link #COMPUTE_MAXS} and {@link
+ * #COMPUTE_FRAMES}.
+ * @return true if all the given flags, or more, were passed to the constructor.
+ */
+ public boolean hasFlags(final int flags) {
+ return (this.flags & flags) == flags;
}
// -----------------------------------------------------------------------------------------------
@@ -251,12 +288,12 @@ public ClassWriter(final ClassReader classReader, final int flags) {
@Override
public final void visit(
- final int version,
- final int access,
- final String name,
- final String signature,
- final String superName,
- final String[] interfaces) {
+ 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);
@@ -282,29 +319,29 @@ public final void visitSource(final String file, final String debug) {
sourceFileIndex = symbolTable.addConstantUtf8(file);
}
if (debug != null) {
- debugExtension = new ByteVector().encodeUTF8(debug, 0, Integer.MAX_VALUE);
+ debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE);
}
}
@Override
public final ModuleVisitor visitModule(
- final String name, final int access, final String version) {
+ 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));
+ new ModuleWriter(
+ symbolTable,
+ symbolTable.addConstantModule(name).index,
+ access,
+ version == null ? 0 : symbolTable.addConstantUtf8(version));
}
@Override
- public void visitNestHostExperimental(final String nestHost) {
+ public final void visitNestHost(final String nestHost) {
nestHostClassIndex = symbolTable.addConstantClass(nestHost).index;
}
@Override
public final void visitOuterClass(
- final String owner, final String name, final String descriptor) {
+ final String owner, final String name, final String descriptor) {
enclosingClassIndex = symbolTable.addConstantClass(owner).index;
if (name != null && descriptor != null) {
enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor);
@@ -313,37 +350,26 @@ public final void visitOuterClass(
@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);
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
} else {
return lastRuntimeInvisibleAnnotation =
- new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
+ AnnotationWriter.create(symbolTable, descriptor, 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);
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (visible) {
return lastRuntimeVisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
} else {
return lastRuntimeInvisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
}
}
@@ -355,7 +381,7 @@ public final void visitAttribute(final Attribute attribute) {
}
@Override
- public void visitNestMemberExperimental(final String nestMember) {
+ public final void visitNestMember(final String nestMember) {
if (nestMemberClasses == null) {
nestMemberClasses = new ByteVector();
}
@@ -363,9 +389,18 @@ public void visitNestMemberExperimental(final String nestMember) {
nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index);
}
+ @Override
+ public final void visitPermittedSubclass(final String permittedSubclass) {
+ if (permittedSubclasses == null) {
+ permittedSubclasses = new ByteVector();
+ }
+ ++numberOfPermittedSubclasses;
+ permittedSubclasses.putShort(symbolTable.addConstantClass(permittedSubclass).index);
+ }
+
@Override
public final void visitInnerClass(
- final String name, final String outerName, final String innerName, final int access) {
+ final String name, final String outerName, final String innerName, final int access) {
if (innerClasses == null) {
innerClasses = new ByteVector();
}
@@ -383,21 +418,33 @@ public final void visitInnerClass(
innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName));
innerClasses.putShort(access);
nameSymbol.info = numberOfInnerClasses;
+ }
+ // Else, compare the inner classes entry nameSymbol.info - 1 with the arguments of this method
+ // and throw an exception if there is a difference?
+ }
+
+ @Override
+ public final RecordComponentVisitor visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ RecordComponentWriter recordComponentWriter =
+ new RecordComponentWriter(symbolTable, name, descriptor, signature);
+ if (firstRecordComponent == null) {
+ firstRecordComponent = recordComponentWriter;
} else {
- // Compare the inner classes entry nameSymbol.info - 1 with the arguments of this method and
- // throw an exception if there is a difference?
+ lastRecordComponent.delegate = recordComponentWriter;
}
+ return lastRecordComponent = recordComponentWriter;
}
@Override
public final FieldVisitor visitField(
- final int access,
- final String name,
- final String descriptor,
- final String signature,
- final Object value) {
+ 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);
+ new FieldWriter(symbolTable, access, name, descriptor, signature, value);
if (firstField == null) {
firstField = fieldWriter;
} else {
@@ -408,13 +455,13 @@ public final FieldVisitor visitField(
@Override
public final MethodVisitor visitMethod(
- final int access,
- final String name,
- final String descriptor,
- final String signature,
- final String[] exceptions) {
+ 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);
+ new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
if (firstMethod == null) {
firstMethod = methodWriter;
} else {
@@ -436,6 +483,8 @@ public final void visitEnd() {
* Returns the content of the class file that was built by this ClassWriter.
*
* @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter.
+ * @throws ClassTooLargeException if the constant pool of the class is too large.
+ * @throws MethodTooLargeException if the Code attribute of a method is too large.
*/
public byte[] toByteArray() {
// First step: compute the size in bytes of the ClassFile structure.
@@ -457,6 +506,7 @@ public byte[] toByteArray() {
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) {
@@ -497,26 +547,26 @@ public byte[] toByteArray() {
if (lastRuntimeVisibleAnnotation != null) {
++attributesCount;
size +=
- lastRuntimeVisibleAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_VISIBLE_ANNOTATIONS);
+ lastRuntimeVisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_ANNOTATIONS);
}
if (lastRuntimeInvisibleAnnotation != null) {
++attributesCount;
size +=
- lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
+ lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
}
if (lastRuntimeVisibleTypeAnnotation != null) {
++attributesCount;
size +=
- lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
}
if (lastRuntimeInvisibleTypeAnnotation != null) {
++attributesCount;
size +=
- lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
}
if (symbolTable.computeBootstrapMethodsSize() > 0) {
++attributesCount;
@@ -536,6 +586,24 @@ public byte[] toByteArray() {
size += 8 + nestMemberClasses.length;
symbolTable.addConstantUtf8(Constants.NEST_MEMBERS);
}
+ if (permittedSubclasses != null) {
+ ++attributesCount;
+ size += 8 + permittedSubclasses.length;
+ symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES);
+ }
+ int recordComponentCount = 0;
+ int recordSize = 0;
+ if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) {
+ RecordComponentWriter recordComponentWriter = firstRecordComponent;
+ while (recordComponentWriter != null) {
+ ++recordComponentCount;
+ recordSize += recordComponentWriter.computeRecordComponentInfoSize();
+ recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
+ }
+ ++attributesCount;
+ size += 8 + recordSize;
+ symbolTable.addConstantUtf8(Constants.RECORD);
+ }
if (firstAttribute != null) {
attributesCount += firstAttribute.getAttributeCount();
size += firstAttribute.computeAttributesSize(symbolTable);
@@ -543,8 +611,9 @@ public byte[] toByteArray() {
// 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!");
+ int constantPoolCount = symbolTable.getConstantPoolCount();
+ if (constantPoolCount > 0xFFFF) {
+ throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount);
}
// Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
@@ -578,75 +647,84 @@ public byte[] toByteArray() {
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);
+ .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);
+ .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);
+ .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
+ .putInt(2)
+ .putShort(signatureIndex);
}
if (sourceFileIndex != 0) {
result
- .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE))
- .putInt(2)
- .putShort(sourceFileIndex);
+ .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);
+ .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);
- }
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ result);
symbolTable.putBootstrapMethods(result);
if (moduleWriter != null) {
moduleWriter.putAttributes(result);
}
if (nestHostClassIndex != 0) {
result
- .putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST))
- .putInt(2)
- .putShort(nestHostClassIndex);
+ .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);
+ .putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS))
+ .putInt(nestMemberClasses.length + 2)
+ .putShort(numberOfNestMemberClasses)
+ .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length);
+ }
+ if (permittedSubclasses != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES))
+ .putInt(permittedSubclasses.length + 2)
+ .putShort(numberOfPermittedSubclasses)
+ .putByteArray(permittedSubclasses.data, 0, permittedSubclasses.length);
+ }
+ if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.RECORD))
+ .putInt(recordSize + 2)
+ .putShort(recordComponentCount);
+ RecordComponentWriter recordComponentWriter = firstRecordComponent;
+ while (recordComponentWriter != null) {
+ recordComponentWriter.putRecordComponentInfo(result);
+ recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
+ }
}
if (firstAttribute != null) {
firstAttribute.putAttributes(symbolTable, result);
@@ -671,7 +749,7 @@ public byte[] toByteArray() {
* ones.
*/
private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) {
- Attribute[] attributes = getAttributePrototypes();
+ final Attribute[] attributes = getAttributePrototypes();
firstField = null;
lastField = null;
firstMethod = null;
@@ -684,13 +762,17 @@ private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasF
nestHostClassIndex = 0;
numberOfNestMemberClasses = 0;
nestMemberClasses = null;
+ numberOfPermittedSubclasses = 0;
+ permittedSubclasses = null;
+ firstRecordComponent = null;
+ lastRecordComponent = null;
firstAttribute = null;
compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING;
- new ClassReader(classFile, 0, /* checkClassVersion = */ false)
- .accept(
- this,
- attributes,
- (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS);
+ new ClassReader(classFile, 0, /* checkClassVersion= */ false)
+ .accept(
+ this,
+ attributes,
+ (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS);
return toByteArray();
}
@@ -712,6 +794,11 @@ private Attribute[] getAttributePrototypes() {
methodWriter.collectAttributePrototypes(attributePrototypes);
methodWriter = (MethodWriter) methodWriter.mv;
}
+ RecordComponentWriter recordComponentWriter = firstRecordComponent;
+ while (recordComponentWriter != null) {
+ recordComponentWriter.collectAttributePrototypes(attributePrototypes);
+ recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
+ }
return attributePrototypes.toArray();
}
@@ -740,6 +827,7 @@ public int newConst(final Object value) {
* @param value the String value.
* @return the index of a new or already existing UTF8 item.
*/
+ // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
public int newUTF8(final String value) {
return symbolTable.addConstantUtf8(value);
}
@@ -749,7 +837,7 @@ public int newUTF8(final String value) {
* 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.
+ * @param value the internal name of the class (see {@link Type#getInternalName()}).
* @return the index of a new or already existing class reference item.
*/
public int newClass(final String value) {
@@ -801,7 +889,8 @@ public int newPackage(final String packageName) {
* 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 owner the internal name of the field or method owner class (see {@link
+ * Type#getInternalName()}).
* @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.
@@ -810,7 +899,7 @@ public int newPackage(final String packageName) {
*/
@Deprecated
public int newHandle(
- final int tag, final String owner, final String name, final String descriptor) {
+ final int tag, final String owner, final String name, final String descriptor) {
return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
}
@@ -823,18 +912,19 @@ public int newHandle(
* 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 owner the internal name of the field or method owner class (see {@link
+ * Type#getInternalName()}).
* @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) {
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index;
}
@@ -850,13 +940,13 @@ public int newHandle(
* @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) {
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
return symbolTable.addConstantDynamic(
name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments)
- .index;
+ .index;
}
/**
@@ -871,13 +961,13 @@ public int newConstantDynamic(
* @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) {
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
return symbolTable.addConstantInvokeDynamic(
name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments)
- .index;
+ .index;
}
/**
@@ -885,7 +975,7 @@ public int newInvokeDynamic(
* 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 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.
* @return the index of a new or already existing field reference item.
@@ -899,14 +989,15 @@ public int newField(final String owner, final String name, final String descript
* 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 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.
- * @param isInterface true if owner is an interface.
+ * @param isInterface {@literal true} if {@code owner} is an interface.
* @return the index of a new or already existing method reference item.
*/
public int newMethod(
- final String owner, final String name, final String descriptor, final boolean isInterface) {
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index;
}
@@ -923,6 +1014,28 @@ public int newNameType(final String name, final String descriptor) {
return symbolTable.addConstantNameAndType(name, descriptor);
}
+ /**
+ * Changes the computation strategy of method properties like max stack size, max number of local
+ * variables, and frames.
+ *
+ *
WARNING: {@link #setFlags(int)} method changes the behavior of new method visitors
+ * returned from {@link #visitMethod(int, String, String, String, String[])}. The behavior will be
+ * changed only after the next method visitor is returned. All the previously returned method
+ * visitors keep their previous behavior.
+ *
+ * @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 final void setFlags(final int flags) {
+ if ((flags & ClassWriter.COMPUTE_FRAMES) != 0) {
+ compute = MethodWriter.COMPUTE_ALL_FRAMES;
+ } else if ((flags & ClassWriter.COMPUTE_MAXS) != 0) {
+ compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL;
+ } else {
+ compute = MethodWriter.COMPUTE_NOTHING;
+ }
+ }
+
// -----------------------------------------------------------------------------------------------
// Default method to compute common super classes when computing stack map frames
// -----------------------------------------------------------------------------------------------
@@ -935,22 +1048,23 @@ public int newNameType(final String name, final String descriptor) {
* 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.
+ * @param type1 the internal name of a class (see {@link Type#getInternalName()}).
+ * @param type2 the internal name of another class (see {@link Type#getInternalName()}).
+ * @return the internal name of the common super class of the two given classes (see {@link
+ * Type#getInternalName()}).
*/
protected String getCommonSuperClass(final String type1, final String type2) {
- ClassLoader classLoader = getClass().getClassLoader();
+ ClassLoader classLoader = getClassLoader();
Class> class1;
try {
class1 = Class.forName(type1.replace('/', '.'), false, classLoader);
- } catch (Exception e) {
+ } catch (ClassNotFoundException e) {
throw new TypeNotPresentException(type1, e);
}
Class> class2;
try {
class2 = Class.forName(type2.replace('/', '.'), false, classLoader);
- } catch (Exception e) {
+ } catch (ClassNotFoundException e) {
throw new TypeNotPresentException(type2, e);
}
if (class1.isAssignableFrom(class2)) {
@@ -968,4 +1082,15 @@ protected String getCommonSuperClass(final String type1, final String type2) {
return class1.getName().replace('.', '/');
}
}
-}
+
+ /**
+ * Returns the {@link ClassLoader} to be used by the default implementation of {@link
+ * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by
+ * default.
+ *
+ * @return ClassLoader
+ */
+ protected ClassLoader getClassLoader() {
+ return getClass().getClassLoader();
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ConstantDynamic.java b/src/jvm/clojure/asm/ConstantDynamic.java
index d5306caf8a..ddffcaefec 100644
--- a/src/jvm/clojure/asm/ConstantDynamic.java
+++ b/src/jvm/clojure/asm/ConstantDynamic.java
@@ -33,9 +33,7 @@
* 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). */
@@ -63,10 +61,10 @@ public final class ConstantDynamic {
* compute the constant value at runtime.
*/
public ConstantDynamic(
- final String name,
- final String descriptor,
- final Handle bootstrapMethod,
- final Object... bootstrapMethodArguments) {
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethod,
+ final Object... bootstrapMethodArguments) {
this.name = name;
this.descriptor = descriptor;
this.bootstrapMethod = bootstrapMethod;
@@ -101,16 +99,49 @@ public Handle getBootstrapMethod() {
}
/**
- * Returns the arguments to pass to the bootstrap method, in order to compute the value of this
+ * Returns the number of arguments passed to the bootstrap method, in order to compute the value
+ * of this constant.
+ *
+ * @return the number of arguments passed to the bootstrap method, in order to compute the value
+ * of this constant.
+ */
+ public int getBootstrapMethodArgumentCount() {
+ return bootstrapMethodArguments.length;
+ }
+
+ /**
+ * Returns an argument passed to the bootstrap method, in order to compute the value of this
* constant.
*
+ * @param index an argument index, between 0 and {@link #getBootstrapMethodArgumentCount()}
+ * (exclusive).
+ * @return the argument passed to the bootstrap method, with the given index.
+ */
+ public Object getBootstrapMethodArgument(final int index) {
+ return bootstrapMethodArguments[index];
+ }
+
+ /**
+ * Returns the arguments to pass to the bootstrap method, in order to compute the value of this
+ * constant. WARNING: this array must not be modified, and must not be returned to the user.
+ *
* @return the arguments to pass to the bootstrap method, in order to compute the value of this
* constant.
*/
- public Object[] getBootstrapMethodArguments() {
+ Object[] getBootstrapMethodArgumentsUnsafe() {
return bootstrapMethodArguments;
}
+ /**
+ * Returns the size of this constant.
+ *
+ * @return the size of this constant, i.e., 2 for {@code long} and {@code double}, 1 otherwise.
+ */
+ public int getSize() {
+ char firstCharOfDescriptor = descriptor.charAt(0);
+ return (firstCharOfDescriptor == 'J' || firstCharOfDescriptor == 'D') ? 2 : 1;
+ }
+
@Override
public boolean equals(final Object object) {
if (object == this) {
@@ -121,27 +152,27 @@ public boolean equals(final Object object) {
}
ConstantDynamic constantDynamic = (ConstantDynamic) object;
return name.equals(constantDynamic.name)
- && descriptor.equals(constantDynamic.descriptor)
- && bootstrapMethod.equals(constantDynamic.bootstrapMethod)
- && Arrays.equals(bootstrapMethodArguments, constantDynamic.bootstrapMethodArguments);
+ && 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);
+ ^ 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);
+ + " : "
+ + descriptor
+ + ' '
+ + bootstrapMethod
+ + ' '
+ + Arrays.toString(bootstrapMethodArguments);
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Constants.java b/src/jvm/clojure/asm/Constants.java
index 38c5b87513..133e3065ee 100644
--- a/src/jvm/clojure/asm/Constants.java
+++ b/src/jvm/clojure/asm/Constants.java
@@ -27,6 +27,11 @@
// THE POSSIBILITY OF SUCH DAMAGE.
package clojure.asm;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Pattern;
+
/**
* Defines additional JVM opcodes, access flags and constants which are not part of the ASM public
* API.
@@ -34,9 +39,7 @@
* @see JVMS 6
* @author Eric Bruneton
*/
-final class Constants implements Opcodes {
-
- private Constants() {}
+final class Constants {
// The ClassFile attribute names, in the order they are defined in
// https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300.
@@ -59,7 +62,7 @@ private Constants() {}
static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations";
static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations";
static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS =
- "RuntimeInvisibleParameterAnnotations";
+ "RuntimeInvisibleParameterAnnotations";
static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations";
static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations";
static final String ANNOTATION_DEFAULT = "AnnotationDefault";
@@ -70,6 +73,8 @@ private Constants() {}
static final String MODULE_MAIN_CLASS = "ModuleMainClass";
static final String NEST_HOST = "NestHost";
static final String NEST_MEMBERS = "NestMembers";
+ static final String PERMITTED_SUBCLASSES = "PermittedSubclasses";
+ static final String RECORD = "Record";
// ASM specific access flags.
// WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard
@@ -142,7 +147,7 @@ private Constants() {}
// 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;
+ static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - Opcodes.GOTO;
// Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa.
@@ -155,23 +160,62 @@ private Constants() {}
// 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_IFEQ = Opcodes.IFEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IFNE = Opcodes.IFNE + ASM_OPCODE_DELTA;
+ static final int ASM_IFLT = Opcodes.IFLT + ASM_OPCODE_DELTA;
+ static final int ASM_IFGE = Opcodes.IFGE + ASM_OPCODE_DELTA;
+ static final int ASM_IFGT = Opcodes.IFGT + ASM_OPCODE_DELTA;
+ static final int ASM_IFLE = Opcodes.IFLE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPEQ = Opcodes.IF_ICMPEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPNE = Opcodes.IF_ICMPNE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPLT = Opcodes.IF_ICMPLT + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPGE = Opcodes.IF_ICMPGE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPGT = Opcodes.IF_ICMPGT + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPLE = Opcodes.IF_ICMPLE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ACMPEQ = Opcodes.IF_ACMPEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ACMPNE = Opcodes.IF_ACMPNE + ASM_OPCODE_DELTA;
+ static final int ASM_GOTO = Opcodes.GOTO + ASM_OPCODE_DELTA;
+ static final int ASM_JSR = Opcodes.JSR + ASM_OPCODE_DELTA;
+ static final int ASM_IFNULL = Opcodes.IFNULL + ASM_IFNULL_OPCODE_DELTA;
+ static final int ASM_IFNONNULL = Opcodes.IFNONNULL + ASM_IFNULL_OPCODE_DELTA;
static final int ASM_GOTO_W = 220;
-}
+
+ private Constants() {}
+
+ static void checkAsmExperimental(final Object caller) {
+ Class> callerClass = caller.getClass();
+ String internalName = callerClass.getName().replace('.', '/');
+ if (!isWhitelisted(internalName)) {
+ checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class"));
+ }
+ }
+
+ static boolean isWhitelisted(final String internalName) {
+ if (!internalName.startsWith("org/objectweb/asm/")) {
+ return false;
+ }
+ String member = "(Annotation|Class|Field|Method|Module|RecordComponent|Signature)";
+ return internalName.contains("Test$")
+ || Pattern.matches(
+ "org/objectweb/asm/util/Trace" + member + "Visitor(\\$.*)?", internalName)
+ || Pattern.matches(
+ "org/objectweb/asm/util/Check" + member + "Adapter(\\$.*)?", internalName);
+ }
+
+ static void checkIsPreview(final InputStream classInputStream) {
+ if (classInputStream == null) {
+ throw new IllegalStateException("Bytecode not available, can't check class version");
+ }
+ int minorVersion;
+ try (DataInputStream callerClassStream = new DataInputStream(classInputStream); ) {
+ callerClassStream.readInt();
+ minorVersion = callerClassStream.readUnsignedShort();
+ } catch (IOException ioe) {
+ throw new IllegalStateException("I/O error, can't check class version", ioe);
+ }
+ if (minorVersion != 0xFFFF) {
+ throw new IllegalStateException(
+ "ASM10_EXPERIMENTAL can only be used by classes compiled with --enable-preview");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Context.java b/src/jvm/clojure/asm/Context.java
index 5af8943ed7..3a3ecf9c19 100644
--- a/src/jvm/clojure/asm/Context.java
+++ b/src/jvm/clojure/asm/Context.java
@@ -134,4 +134,4 @@ final class Context {
* MethodVisitor#visitFrame}.
*/
Object[] currentFrameStackTypes;
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/CurrentFrame.java b/src/jvm/clojure/asm/CurrentFrame.java
index f37b51cfe8..ed604c90d6 100644
--- a/src/jvm/clojure/asm/CurrentFrame.java
+++ b/src/jvm/clojure/asm/CurrentFrame.java
@@ -47,10 +47,10 @@ final class CurrentFrame extends Frame {
*/
@Override
void execute(
- final int opcode, final int arg, final Symbol symbolArg, final SymbolTable symbolTable) {
+ 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);
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Edge.java b/src/jvm/clojure/asm/Edge.java
index f9bdadf90b..2bab87188d 100644
--- a/src/jvm/clojure/asm/Edge.java
+++ b/src/jvm/clojure/asm/Edge.java
@@ -88,4 +88,4 @@ final class Edge {
this.successor = successor;
this.nextEdge = nextEdge;
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/FieldVisitor.java b/src/jvm/clojure/asm/FieldVisitor.java
index f2f0e2ce8d..02f2afb4be 100644
--- a/src/jvm/clojure/asm/FieldVisitor.java
+++ b/src/jvm/clojure/asm/FieldVisitor.java
@@ -29,59 +29,72 @@
/**
* A visitor to visit a Java field. The methods of this class must be called in the following order:
- * ( visitAnnotation | visitTypeAnnotation | visitAttribute )*
- * visitEnd.
+ * ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code
+ * visitEnd}.
*
* @author Eric Bruneton
*/
public abstract class FieldVisitor {
/**
- * The ASM API version implemented by this visitor. The value of this field must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
*/
protected final int api;
- /** The field visitor to which this visitor must delegate method calls. May be null. */
+ /** The field visitor to which this visitor must delegate method calls. May be {@literal null}. */
protected FieldVisitor fv;
/**
* Constructs a new {@link FieldVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
- * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public FieldVisitor(final int api) {
+ protected 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 api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @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();
+ protected FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
this.api = api;
this.fv = fieldVisitor;
}
+ /**
+ * The field visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the field visitor to which this visitor must delegate method calls, or {@literal null}.
+ */
+ public FieldVisitor getDelegate() {
+ return 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
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
@@ -97,17 +110,17 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean
* @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
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
* 'typeRef' as a whole.
* @param descriptor the class descriptor of the annotation class.
- * @param visible true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if this visitor is not
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitTypeAnnotation(
- final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (api < Opcodes.ASM5) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("This feature requires ASM5");
}
if (fv != null) {
return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
@@ -135,4 +148,4 @@ public void visitEnd() {
fv.visitEnd();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/FieldWriter.java b/src/jvm/clojure/asm/FieldWriter.java
index ed6449bf69..5c0aa618bd 100644
--- a/src/jvm/clojure/asm/FieldWriter.java
+++ b/src/jvm/clojure/asm/FieldWriter.java
@@ -70,31 +70,31 @@ final class FieldWriter extends FieldVisitor {
/**
* The last runtime visible annotation of this field. The previous ones can be accessed with the
- * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleAnnotation;
/**
* The last runtime invisible annotation of this field. The previous ones can be accessed with the
- * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleAnnotation;
/**
* The last runtime visible type annotation of this field. The previous ones can be accessed with
- * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
/**
* The last runtime invisible type annotation of this field. The previous ones can be accessed
- * with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
/**
* The first non standard attribute of this field. The next ones can be accessed with the {@link
- * Attribute#nextAttribute} field. May be null.
+ * Attribute#nextAttribute} field. May be {@literal null}.
*
*
WARNING: this list stores the attributes in the reverse order of their visit.
* firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
@@ -114,17 +114,17 @@ final class FieldWriter extends FieldVisitor {
* @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.
+ * @param signature the field's signature. May be {@literal null}.
+ * @param constantValue the field's constant value. May be {@literal null}.
*/
FieldWriter(
- final SymbolTable symbolTable,
- final int access,
- final String name,
- final String descriptor,
- final String signature,
- final Object constantValue) {
- super(Opcodes.ASM6);
+ final SymbolTable symbolTable,
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Object constantValue) {
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.accessFlags = access;
this.nameIndex = symbolTable.addConstantUtf8(name);
@@ -143,37 +143,26 @@ final class FieldWriter extends FieldVisitor {
@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);
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
} else {
return lastRuntimeInvisibleAnnotation =
- new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
}
}
@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);
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (visible) {
return lastRuntimeVisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
} else {
return lastRuntimeInvisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
}
}
@@ -208,44 +197,13 @@ int computeFieldInfoSize() {
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);
- }
+ size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex);
+ size +=
+ AnnotationWriter.computeAnnotationsSize(
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation);
if (firstAttribute != null) {
size += firstAttribute.computeAttributesSize(symbolTable);
}
@@ -298,38 +256,18 @@ void putFieldInfo(final ByteVector output) {
// 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);
+ .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE))
+ .putInt(2)
+ .putShort(constantValueIndex);
}
+ Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output);
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ output);
if (firstAttribute != null) {
firstAttribute.putAttributes(symbolTable, output);
}
@@ -343,4 +281,4 @@ void putFieldInfo(final ByteVector output) {
final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
attributePrototypes.addAttributes(firstAttribute);
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Frame.java b/src/jvm/clojure/asm/Frame.java
index 2c67f8cce2..e18e983a04 100644
--- a/src/jvm/clojure/asm/Frame.java
+++ b/src/jvm/clojure/asm/Frame.java
@@ -54,19 +54,19 @@
*
*
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 DIM field, stored in the 6 most significant bits, is a signed number of array
+ * dimensions (from -32 to 31, included). It can be retrieved with {@link #DIM_MASK} and a
+ * right shift of {@link #DIM_SHIFT}.
*
the KIND field, stored in 4 bits, indicates the kind of VALUE used. These 4 bits can be
* retrieved with {@link #KIND_MASK} and, without any shift, must be equal to {@link
- * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link #LOCAL_KIND}
- * or {@link #STACK_KIND}.
- *
the FLAGS field, stored in 4 bits, contains up to 4 boolean flags. Currently only one flag
+ * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link
+ * #FORWARD_UNINITIALIZED_KIND},{@link #LOCAL_KIND} or {@link #STACK_KIND}.
+ *
the FLAGS field, stored in 2 bits, contains up to 2 boolean flags. Currently only one flag
* is defined, namely {@link #TOP_IF_LONG_OR_DOUBLE_FLAG}.
*
the VALUE field, stored in the remaining 20 bits, contains either
*
@@ -78,7 +78,10 @@
*
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}.
+ * table of a {@link SymbolTable}, if KIND is equal to {@link #UNINITIALIZED_KIND}.
+ *
the index of a {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG} {@link Symbol} in the
+ * type table of a {@link SymbolTable}, if KIND is equal to {@link
+ * #FORWARD_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
@@ -88,10 +91,10 @@
*
*
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).
+ * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND,
+ * UNINITIALIZED_KIND or FORWARD_UNINITIALIZED_KIND abstract types of positive or {@literal null}
+ * array dimension. In all cases the type table contains only internal type names (array type
+ * descriptors are forbidden - array dimensions must be represented through the DIM field).
*
*
The LONG and DOUBLE types are always represented by using two slots (LONG + TOP or DOUBLE +
* TOP), for local variables as well as in the operand stack. This is necessary to be able to
@@ -129,18 +132,25 @@ class Frame {
private static final int ITEM_ASM_CHAR = 11;
private static final int ITEM_ASM_SHORT = 12;
+ // The size and offset in bits of each field of an abstract type.
+
+ private static final int DIM_SIZE = 6;
+ private static final int KIND_SIZE = 4;
+ private static final int FLAGS_SIZE = 2;
+ private static final int VALUE_SIZE = 32 - DIM_SIZE - KIND_SIZE - FLAGS_SIZE;
+
+ private static final int DIM_SHIFT = KIND_SIZE + FLAGS_SIZE + VALUE_SIZE;
+ private static final int KIND_SHIFT = FLAGS_SIZE + VALUE_SIZE;
+ private static final int FLAGS_SHIFT = VALUE_SIZE;
+
// Bitmasks to get each field of an abstract type.
- private static final int DIM_MASK = 0xF0000000;
- private static final int KIND_MASK = 0x0F000000;
- private static final int FLAGS_MASK = 0x00F00000;
- private static final int VALUE_MASK = 0x000FFFFF;
+ private static final int DIM_MASK = ((1 << DIM_SIZE) - 1) << DIM_SHIFT;
+ private static final int KIND_MASK = ((1 << KIND_SIZE) - 1) << KIND_SHIFT;
+ private static final int VALUE_MASK = (1 << VALUE_SIZE) - 1;
// Constants to manipulate the DIM field of an abstract type.
- /** The number of right shift bits to use to get the array dimensions of an abstract type. */
- private static final int DIM_SHIFT = 28;
-
/** 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;
@@ -149,11 +159,12 @@ class Frame {
// Possible values for the KIND field of an abstract type.
- 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;
+ private static final int CONSTANT_KIND = 1 << KIND_SHIFT;
+ private static final int REFERENCE_KIND = 2 << KIND_SHIFT;
+ private static final int UNINITIALIZED_KIND = 3 << KIND_SHIFT;
+ private static final int FORWARD_UNINITIALIZED_KIND = 4 << KIND_SHIFT;
+ private static final int LOCAL_KIND = 5 << KIND_SHIFT;
+ private static final int STACK_KIND = 6 << KIND_SHIFT;
// Possible flags for the FLAGS field of an abstract type.
@@ -162,7 +173,7 @@ class Frame {
* 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;
+ private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 1 << FLAGS_SHIFT;
// Useful predefined abstract types (all the possible CONSTANT_KIND types).
@@ -213,16 +224,48 @@ class Frame {
/**
* 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.
+ * UNINITIALIZED, FORWARD_UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every
+ * occurrence of this type in the local variables and in the operand stack. This cannot be
+ * done during the first step of the algorithm since, during this step, the local variables and
+ * the operand stack types are still abstract. It is therefore necessary to store the abstract
+ * types of the constructors which are invoked in the basic block, in order to do this replacement
+ * during the second step of the algorithm, where the frames are fully computed. Note that this
+ * array can contain abstract types that are relative to the input locals or to the input stack.
*/
private int[] initializations;
+ // -----------------------------------------------------------------------------------------------
+ // Constructor
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a new Frame.
+ *
+ * @param owner the basic block to which these input and output stack map frames correspond.
+ */
+ Frame(final Label owner) {
+ this.owner = owner;
+ }
+
+ /**
+ * Sets this frame to the value of the given frame.
+ *
+ *
WARNING: after this method is called the two frames share the same data structures. It is
+ * recommended to discard the given frame to avoid unexpected side effects.
+ *
+ * @param frame The new frame value.
+ */
+ final void copyFrom(final Frame frame) {
+ inputLocals = frame.inputLocals;
+ inputStack = frame.inputStack;
+ outputStackStart = 0;
+ outputLocals = frame.outputLocals;
+ outputStack = frame.outputStack;
+ outputStackTop = frame.outputStackTop;
+ initializationCount = frame.initializationCount;
+ initializations = frame.initializations;
+ }
+
// -----------------------------------------------------------------------------------------------
// Static methods to get abstract types from other type formats
// -----------------------------------------------------------------------------------------------
@@ -245,8 +288,12 @@ static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Obj
String descriptor = Type.getObjectType((String) type).getDescriptor();
return getAbstractTypeFromDescriptor(symbolTable, descriptor, 0);
} else {
- return UNINITIALIZED_KIND
- | symbolTable.addUninitializedType("", ((Label) type).bytecodeOffset);
+ Label label = (Label) type;
+ if ((label.flags & Label.FLAG_RESOLVED) != 0) {
+ return UNINITIALIZED_KIND | symbolTable.addUninitializedType("", label.bytecodeOffset);
+ } else {
+ return FORWARD_UNINITIALIZED_KIND | symbolTable.addForwardUninitializedType("", label);
+ }
}
}
@@ -259,7 +306,7 @@ static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Obj
* @return the abstract type value corresponding to the given internal name.
*/
static int getAbstractTypeFromInternalName(
- final SymbolTable symbolTable, final String internalName) {
+ final SymbolTable symbolTable, final String internalName) {
return REFERENCE_KIND | symbolTable.addType(internalName);
}
@@ -272,7 +319,7 @@ static int getAbstractTypeFromInternalName(
* @return the abstract type corresponding to the given type descriptor.
*/
private static int getAbstractTypeFromDescriptor(
- final SymbolTable symbolTable, final String buffer, final int offset) {
+ final SymbolTable symbolTable, final String buffer, final int offset) {
String internalName;
switch (buffer.charAt(offset)) {
case 'V':
@@ -328,46 +375,15 @@ private static int getAbstractTypeFromDescriptor(
typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
break;
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException(
+ "Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset));
}
return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset));
}
}
- // -----------------------------------------------------------------------------------------------
- // Constructor
- // -----------------------------------------------------------------------------------------------
-
- /**
- * Constructs a new Frame.
- *
- * @param owner the basic block to which these input and output stack map frames correspond.
- */
- Frame(final Label owner) {
- this.owner = owner;
- }
-
- /**
- * Sets this frame to the value of the given frame.
- *
- *
WARNING: after this method is called the two frames share the same data structures. It is
- * recommended to discard the given frame to avoid unexpected side effects.
- *
- * @param frame The new frame value.
- */
- final void copyFrom(final Frame frame) {
- inputLocals = frame.inputLocals;
- inputStack = frame.inputStack;
- outputStackStart = 0;
- outputLocals = frame.outputLocals;
- outputStack = frame.outputStack;
- outputStackTop = frame.outputStackTop;
- initializationCount = frame.initializationCount;
- initializations = frame.initializations;
- }
-
// -----------------------------------------------------------------------------------------------
// Methods related to the input frame
// -----------------------------------------------------------------------------------------------
@@ -383,24 +399,24 @@ final void copyFrom(final Frame frame) {
* @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) {
+ 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());
+ REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());
} else {
inputLocals[inputLocalIndex++] = UNINITIALIZED_THIS;
}
}
for (Type argumentType : Type.getArgumentTypes(descriptor)) {
int abstractType =
- getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0);
+ getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0);
inputLocals[inputLocalIndex++] = abstractType;
if (abstractType == LONG || abstractType == DOUBLE) {
inputLocals[inputLocalIndex++] = TOP;
@@ -415,21 +431,21 @@ final void setInputFrameFromDescriptor(
* 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 numLocal 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 numStack the number of operand stack elements.
* @param stack the operand stack types, described using the same format as in {@link
* MethodVisitor#visitFrame}.
*/
final void setInputFrameFromApiFormat(
- final SymbolTable symbolTable,
- final int nLocal,
- final Object[] local,
- final int nStack,
- final Object[] stack) {
+ final SymbolTable symbolTable,
+ final int numLocal,
+ final Object[] local,
+ final int numStack,
+ final Object[] stack) {
int inputLocalIndex = 0;
- for (int i = 0; i < nLocal; ++i) {
+ for (int i = 0; i < numLocal; ++i) {
inputLocals[inputLocalIndex++] = getAbstractTypeFromApiFormat(symbolTable, local[i]);
if (local[i] == Opcodes.LONG || local[i] == Opcodes.DOUBLE) {
inputLocals[inputLocalIndex++] = TOP;
@@ -438,15 +454,15 @@ final void setInputFrameFromApiFormat(
while (inputLocalIndex < inputLocals.length) {
inputLocals[inputLocalIndex++] = TOP;
}
- int nStackTop = 0;
- for (int i = 0; i < nStack; ++i) {
+ int numStackTop = 0;
+ for (int i = 0; i < numStack; ++i) {
if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
- ++nStackTop;
+ ++numStackTop;
}
}
- inputStack = new int[nStack + nStackTop];
+ inputStack = new int[numStack + numStackTop];
int inputStackIndex = 0;
- for (int i = 0; i < nStack; ++i) {
+ for (int i = 0; i < numStack; ++i) {
inputStack[inputStackIndex++] = getAbstractTypeFromApiFormat(symbolTable, stack[i]);
if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
inputStack[inputStackIndex++] = TOP;
@@ -540,7 +556,8 @@ private void push(final int abstractType) {
* @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 typeDescriptorOffset =
+ descriptor.charAt(0) == '(' ? Type.getReturnTypeOffset(descriptor) : 0;
int abstractType = getAbstractTypeFromDescriptor(symbolTable, descriptor, typeDescriptorOffset);
if (abstractType != 0) {
push(abstractType);
@@ -614,7 +631,7 @@ private void addInitializedType(final int abstractType) {
int initializationsLength = initializations.length;
if (initializationCount >= initializationsLength) {
int[] newInitializations =
- new int[Math.max(initializationCount + 1, 2 * initializationsLength)];
+ new int[Math.max(initializationCount + 1, 2 * initializationsLength)];
System.arraycopy(initializations, 0, newInitializations, 0, initializationsLength);
initializations = newInitializations;
}
@@ -628,12 +645,14 @@ private void addInitializedType(final int abstractType) {
* @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.
+ * UNINITIALIZED_THIS or an UNINITIALIZED_KIND or FORWARD_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) {
+ || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND
+ || (abstractType & (DIM_MASK | KIND_MASK)) == FORWARD_UNINITIALIZED_KIND) {
for (int i = 0; i < initializationCount; ++i) {
int initializedType = initializations[i];
int dim = initializedType & DIM_MASK;
@@ -649,7 +668,7 @@ private int getInitializedType(final SymbolTable symbolTable, final int abstract
return REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());
} else {
return REFERENCE_KIND
- | symbolTable.addType(symbolTable.getType(abstractType & VALUE_MASK).value);
+ | symbolTable.addType(symbolTable.getType(abstractType & VALUE_MASK).value);
}
}
}
@@ -670,7 +689,7 @@ private int getInitializedType(final SymbolTable symbolTable, final int abstract
* @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) {
+ 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;
@@ -786,7 +805,7 @@ void execute(
if (previousLocalType == LONG || previousLocalType == DOUBLE) {
setLocal(arg - 1, TOP);
} else if ((previousLocalType & KIND_MASK) == LOCAL_KIND
- || (previousLocalType & KIND_MASK) == STACK_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);
@@ -804,7 +823,7 @@ void execute(
if (previousLocalType == LONG || previousLocalType == DOUBLE) {
setLocal(arg - 1, TOP);
} else if ((previousLocalType & KIND_MASK) == LOCAL_KIND
- || (previousLocalType & KIND_MASK) == STACK_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);
@@ -1103,32 +1122,68 @@ void execute(
// Frame merging methods, used in the second step of the stack map frame computation algorithm
// -----------------------------------------------------------------------------------------------
+ /**
+ * Computes the concrete output type corresponding to a given abstract output type.
+ *
+ * @param abstractOutputType an abstract output type.
+ * @param numStack the size of the input stack, used to resolve abstract output types of
+ * STACK_KIND kind.
+ * @return the concrete output type corresponding to 'abstractOutputType'.
+ */
+ private int getConcreteOutputType(final int abstractOutputType, final int numStack) {
+ int dim = abstractOutputType & DIM_MASK;
+ int kind = abstractOutputType & KIND_MASK;
+ if (kind == LOCAL_KIND) {
+ // By definition, a LOCAL_KIND type designates the concrete type of a local variable at
+ // the beginning of the basic block corresponding to this frame (which is known when
+ // this method is called, but was not when the abstract type was computed).
+ int concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
+ }
+ return concreteOutputType;
+ } else if (kind == STACK_KIND) {
+ // By definition, a STACK_KIND type designates the concrete type of a local variable at
+ // the beginning of the basic block corresponding to this frame (which is known when
+ // this method is called, but was not when the abstract type was computed).
+ int concreteOutputType = dim + inputStack[numStack - (abstractOutputType & VALUE_MASK)];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
+ }
+ return concreteOutputType;
+ } else {
+ return abstractOutputType;
+ }
+ }
+
/**
* Merges the input frame of the given {@link Frame} with the input and output frames of this
- * {@link Frame}. Returns true if the given frame has been changed by this operation (the
- * input and output frames of this {@link Frame} are never changed).
+ * {@link Frame}. Returns {@literal true} if the given frame has been changed by this operation
+ * (the input and output frames of this {@link Frame} are never changed).
*
* @param symbolTable the type table to use to lookup and store type {@link Symbol}.
* @param dstFrame the {@link Frame} whose input frame must be updated. This should be the frame
* of a successor, in the control flow graph, of the basic block corresponding to this frame.
* @param catchTypeIndex if 'frame' corresponds to an exception handler basic block, the type
* table index of the caught exception type, otherwise 0.
- * @return true if the input frame of 'frame' has been changed by this operation.
+ * @return {@literal true} if the input frame of 'frame' has been changed by this operation.
*/
final boolean merge(
- final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) {
+ final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) {
boolean frameChanged = false;
// Compute the concrete types of the local variables at the end of the basic block corresponding
// to this frame, by resolving its abstract output types, and merge these concrete types with
// those of the local variables in the input frame of dstFrame.
- int nLocal = inputLocals.length;
- int nStack = inputStack.length;
+ int numLocal = inputLocals.length;
+ int numStack = inputStack.length;
if (dstFrame.inputLocals == null) {
- dstFrame.inputLocals = new int[nLocal];
+ dstFrame.inputLocals = new int[numLocal];
frameChanged = true;
}
- for (int i = 0; i < nLocal; ++i) {
+ for (int i = 0; i < numLocal; ++i) {
int concreteOutputType;
if (outputLocals != null && i < outputLocals.length) {
int abstractOutputType = outputLocals[i];
@@ -1137,29 +1192,7 @@ final boolean merge(
// 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;
- }
- } 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;
- }
- } else {
- concreteOutputType = abstractOutputType;
- }
+ concreteOutputType = getConcreteOutputType(abstractOutputType, numStack);
}
} else {
// If the local variable has never been assigned in this basic block, it is equal to its
@@ -1181,7 +1214,7 @@ final boolean merge(
// 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) {
+ for (int i = 0; i < numLocal; ++i) {
frameChanged |= merge(symbolTable, inputLocals[i], dstFrame.inputLocals, i);
}
if (dstFrame.inputStack == null) {
@@ -1195,15 +1228,15 @@ final boolean merge(
// 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;
+ int numInputStack = inputStack.length + outputStackStart;
if (dstFrame.inputStack == null) {
- dstFrame.inputStack = new int[nInputStack + outputStackTop];
+ dstFrame.inputStack = new int[numInputStack + outputStackTop];
frameChanged = true;
}
// First, do this for the stack operands that have not been popped in the basic block
// corresponding to this frame, and which are therefore equal to their value in the input
// frame (except for uninitialized types, which may have been initialized).
- for (int i = 0; i < nInputStack; ++i) {
+ for (int i = 0; i < numInputStack; ++i) {
int concreteOutputType = inputStack[i];
if (initializations != null) {
concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
@@ -1213,52 +1246,37 @@ final boolean merge(
// 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;
- }
- } 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;
- }
- } else {
- concreteOutputType = abstractOutputType;
- }
+ int concreteOutputType = getConcreteOutputType(abstractOutputType, numStack);
if (initializations != null) {
concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
}
- frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, nInputStack + i);
+ frameChanged |=
+ merge(symbolTable, concreteOutputType, dstFrame.inputStack, numInputStack + i);
}
return frameChanged;
}
/**
* Merges the type at the given index in the given abstract type array with the given type.
- * Returns true if the type array has been modified by this operation.
+ * Returns {@literal true} if the type array has been modified by this operation.
*
* @param symbolTable the type table to use to lookup and store type {@link Symbol}.
* @param sourceType the abstract type with which the abstract type array element must be merged.
- * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND} or {@link
- * #UNINITIALIZED_KIND} kind, with positive or null array dimensions.
+ * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link
+ * #UNINITIALIZED_KIND} or {@link #FORWARD_UNINITIALIZED_KIND} kind, with positive or
+ * {@literal null} array dimensions.
* @param dstTypes an array of abstract types. These types should be of {@link #CONSTANT_KIND},
- * {@link #REFERENCE_KIND} or {@link #UNINITIALIZED_KIND} kind, with positive or null array
- * dimensions.
+ * {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND} or {@link #FORWARD_UNINITIALIZED_KIND}
+ * kind, with positive or {@literal 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.
+ * @return {@literal true} if the type array has been modified by this operation.
*/
private static boolean merge(
- final SymbolTable symbolTable,
- final int sourceType,
- final int[] dstTypes,
- final int dstIndex) {
+ 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.
@@ -1288,9 +1306,9 @@ private static boolean merge(
// 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);
+ (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.
@@ -1311,7 +1329,7 @@ private static boolean merge(
dstDim = ELEMENT_OF + dstDim;
}
mergedType =
- Math.min(srcDim, dstDim) | REFERENCE_KIND | symbolTable.addType("java/lang/Object");
+ Math.min(srcDim, dstDim) | REFERENCE_KIND | symbolTable.addType("java/lang/Object");
} else {
// If srcType is any other type, merge(srcType, dstType) = TOP.
mergedType = TOP;
@@ -1320,7 +1338,7 @@ private static boolean merge(
// 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;
+ (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;
@@ -1348,38 +1366,38 @@ 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 numLocal = 0;
+ int numTrailingTop = 0;
int i = 0;
while (i < localTypes.length) {
int localType = localTypes[i];
i += (localType == LONG || localType == DOUBLE) ? 2 : 1;
if (localType == TOP) {
- nTrailingTop++;
+ numTrailingTop++;
} else {
- nLocal += nTrailingTop + 1;
- nTrailingTop = 0;
+ numLocal += numTrailingTop + 1;
+ numTrailingTop = 0;
}
}
// Compute the stack size, ignoring TOP types that are just after a LONG or a DOUBLE.
int[] stackTypes = inputStack;
- int nStack = 0;
+ int numStack = 0;
i = 0;
while (i < stackTypes.length) {
int stackType = stackTypes[i];
i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1;
- nStack++;
+ numStack++;
}
// Visit the frame and its content.
- int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, nLocal, nStack);
+ int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, numLocal, numStack);
i = 0;
- while (nLocal-- > 0) {
+ while (numLocal-- > 0) {
int localType = localTypes[i];
i += (localType == LONG || localType == DOUBLE) ? 2 : 1;
methodWriter.visitAbstractType(frameIndex++, localType);
}
i = 0;
- while (nStack-- > 0) {
+ while (numStack-- > 0) {
int stackType = stackTypes[i];
i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1;
methodWriter.visitAbstractType(frameIndex++, stackType);
@@ -1393,13 +1411,14 @@ final void accept(final MethodWriter methodWriter) {
*
* @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.
+ * Frame#REFERENCE_KIND}, {@link Frame#UNINITIALIZED_KIND} or {@link
+ * Frame#FORWARD_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) {
+ 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;
@@ -1409,12 +1428,16 @@ static void putAbstractType(
break;
case REFERENCE_KIND:
output
- .putByte(ITEM_OBJECT)
- .putShort(symbolTable.addConstantClass(symbolTable.getType(typeValue).value).index);
+ .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;
+ case FORWARD_UNINITIALIZED_KIND:
+ output.putByte(ITEM_UNINITIALIZED);
+ symbolTable.getForwardUninitializedLabel(typeValue).put(output);
+ break;
default:
throw new AssertionError();
}
@@ -1426,9 +1449,9 @@ static void putAbstractType(
}
if ((abstractType & KIND_MASK) == REFERENCE_KIND) {
typeDescriptor
- .append('L')
- .append(symbolTable.getType(abstractType & VALUE_MASK).value)
- .append(';');
+ .append('L')
+ .append(symbolTable.getType(abstractType & VALUE_MASK).value)
+ .append(';');
} else {
switch (abstractType & VALUE_MASK) {
case Frame.ITEM_ASM_BOOLEAN:
@@ -1460,8 +1483,8 @@ static void putAbstractType(
}
}
output
- .putByte(ITEM_OBJECT)
- .putShort(symbolTable.addConstantClass(typeDescriptor.toString()).index);
+ .putByte(ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass(typeDescriptor.toString()).index);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Handle.java b/src/jvm/clojure/asm/Handle.java
index 9121744932..a154a074e1 100644
--- a/src/jvm/clojure/asm/Handle.java
+++ b/src/jvm/clojure/asm/Handle.java
@@ -65,7 +65,7 @@ public final class Handle {
* {@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.
+ * handle (see {@link Type#getInternalName()}).
* @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,
@@ -85,17 +85,17 @@ public Handle(final int tag, final String owner, final String name, final String
* {@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.
+ * handle (see {@link Type#getInternalName()}).
* @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) {
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
this.tag = tag;
this.owner = owner;
this.name = name;
@@ -118,7 +118,8 @@ public int getTag() {
/**
* 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.
+ * @return the internal name of the class that owns the field or method designated by this handle
+ * (see {@link Type#getInternalName()}).
*/
public String getOwner() {
return owner;
@@ -161,17 +162,17 @@ public boolean equals(final Object object) {
}
Handle handle = (Handle) object;
return tag == handle.tag
- && isInterface == handle.isInterface
- && owner.equals(handle.owner)
- && name.equals(handle.name)
- && descriptor.equals(handle.descriptor);
+ && 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();
+ + (isInterface ? 64 : 0)
+ + owner.hashCode() * name.hashCode() * descriptor.hashCode();
}
/**
@@ -186,4 +187,4 @@ public int hashCode() {
public String toString() {
return owner + '.' + name + descriptor + " (" + tag + (isInterface ? " itf" : "") + ')';
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Handler.java b/src/jvm/clojure/asm/Handler.java
index 41002b62be..e6bc999b8a 100644
--- a/src/jvm/clojure/asm/Handler.java
+++ b/src/jvm/clojure/asm/Handler.java
@@ -64,8 +64,8 @@ final class Handler {
final int catchType;
/**
- * The internal name of the type of exceptions handled by this handler, or null to catch
- * any exceptions.
+ * The internal name of the type of exceptions handled by this handler, or {@literal null} to
+ * catch any exceptions.
*/
final String catchTypeDescriptor;
@@ -80,14 +80,14 @@ final class Handler {
* @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.
+ * or {@literal null} to catch any exceptions.
*/
Handler(
- final Label startPc,
- final Label endPc,
- final Label handlerPc,
- final int catchType,
- final String catchTypeDescriptor) {
+ final Label startPc,
+ final Label endPc,
+ final Label handlerPc,
+ final int catchType,
+ final String catchTypeDescriptor) {
this.startPc = startPc;
this.endPc = endPc;
this.handlerPc = handlerPc;
@@ -111,9 +111,9 @@ final class Handler {
* 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 firstHandler the beginning of a Handler list. May be {@literal null}.
* @param start the start of the range to be removed.
- * @param end the end of the range to be removed. Maybe null.
+ * @param end the end of the range to be removed. Maybe {@literal null}.
* @return the exception handler list with the start-end range removed.
*/
static Handler removeRange(final Handler firstHandler, final Label start, final Label end) {
@@ -152,7 +152,7 @@ static Handler removeRange(final Handler firstHandler, final Label start, final
/**
* 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.
+ * @param firstHandler the beginning of a Handler list. May be {@literal null}.
* @return the number of elements of the Handler list that begins with 'handler'.
*/
static int getExceptionTableLength(final Handler firstHandler) {
@@ -169,7 +169,7 @@ static int getExceptionTableLength(final Handler firstHandler) {
* 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.
+ * @param firstHandler the beginning of a Handler list. May be {@literal null}.
* @return the size in bytes of the exception_table_length and exception_table structures.
*/
static int getExceptionTableSize(final Handler firstHandler) {
@@ -180,7 +180,7 @@ static int getExceptionTableSize(final Handler firstHandler) {
* Puts the JVMS exception_table corresponding to the Handler list that begins with the given
* element. This includes the exception_table_length field.
*
- * @param firstHandler the beginning of a Handler list. May be null.
+ * @param firstHandler the beginning of a Handler list. May be {@literal null}.
* @param output where the exception_table_length and exception_table structures must be put.
*/
static void putExceptionTable(final Handler firstHandler, final ByteVector output) {
@@ -188,11 +188,11 @@ static void putExceptionTable(final Handler firstHandler, final ByteVector outpu
Handler handler = firstHandler;
while (handler != null) {
output
- .putShort(handler.startPc.bytecodeOffset)
- .putShort(handler.endPc.bytecodeOffset)
- .putShort(handler.handlerPc.bytecodeOffset)
- .putShort(handler.catchType);
+ .putShort(handler.startPc.bytecodeOffset)
+ .putShort(handler.endPc.bytecodeOffset)
+ .putShort(handler.handlerPc.bytecodeOffset)
+ .putShort(handler.catchType);
handler = handler.nextHandler;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/InstructionAdapter.java b/src/jvm/clojure/asm/InstructionAdapter.java
new file mode 100644
index 0000000000..b56bdb415c
--- /dev/null
+++ b/src/jvm/clojure/asm/InstructionAdapter.java
@@ -0,0 +1,1303 @@
+// 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 clojure.asm.ConstantDynamic;
+import clojure.asm.Handle;
+import clojure.asm.Label;
+import clojure.asm.MethodVisitor;
+import clojure.asm.Opcodes;
+import clojure.asm.Type;
+
+/**
+ * A {@link MethodVisitor} providing a more detailed API to generate and transform instructions.
+ *
+ * @author Eric Bruneton
+ */
+public class InstructionAdapter extends MethodVisitor {
+
+ /** The type of the java.lang.Object class. */
+ public static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;");
+
+ /**
+ * Constructs a new {@link InstructionAdapter}. Subclasses must not use this constructor.
+ * Instead, they must use the {@link #InstructionAdapter(int, MethodVisitor)} version.
+ *
+ * @param methodVisitor the method visitor to which this adapter delegates calls.
+ * @throws IllegalStateException If a subclass calls this constructor.
+ */
+ public InstructionAdapter(final MethodVisitor methodVisitor) {
+ this(/* latest api = */ Opcodes.ASM9, methodVisitor);
+ if (getClass() != InstructionAdapter.class) {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Constructs a new {@link InstructionAdapter}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ * @param methodVisitor the method visitor to which this adapter delegates calls.
+ */
+ protected InstructionAdapter(final int api, final MethodVisitor methodVisitor) {
+ super(api, methodVisitor);
+ }
+
+ @Override
+ public void visitInsn(final int opcode) {
+ switch (opcode) {
+ case Opcodes.NOP:
+ nop();
+ break;
+ case Opcodes.ACONST_NULL:
+ aconst(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:
+ iconst(opcode - Opcodes.ICONST_0);
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ lconst((long) (opcode - Opcodes.LCONST_0));
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ fconst((float) (opcode - Opcodes.FCONST_0));
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ dconst((double) (opcode - Opcodes.DCONST_0));
+ break;
+ case Opcodes.IALOAD:
+ aload(Type.INT_TYPE);
+ break;
+ case Opcodes.LALOAD:
+ aload(Type.LONG_TYPE);
+ break;
+ case Opcodes.FALOAD:
+ aload(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DALOAD:
+ aload(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.AALOAD:
+ aload(OBJECT_TYPE);
+ break;
+ case Opcodes.BALOAD:
+ aload(Type.BYTE_TYPE);
+ break;
+ case Opcodes.CALOAD:
+ aload(Type.CHAR_TYPE);
+ break;
+ case Opcodes.SALOAD:
+ aload(Type.SHORT_TYPE);
+ break;
+ case Opcodes.IASTORE:
+ astore(Type.INT_TYPE);
+ break;
+ case Opcodes.LASTORE:
+ astore(Type.LONG_TYPE);
+ break;
+ case Opcodes.FASTORE:
+ astore(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DASTORE:
+ astore(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.AASTORE:
+ astore(OBJECT_TYPE);
+ break;
+ case Opcodes.BASTORE:
+ astore(Type.BYTE_TYPE);
+ break;
+ case Opcodes.CASTORE:
+ astore(Type.CHAR_TYPE);
+ break;
+ case Opcodes.SASTORE:
+ astore(Type.SHORT_TYPE);
+ break;
+ case Opcodes.POP:
+ pop();
+ break;
+ case Opcodes.POP2:
+ pop2();
+ break;
+ case Opcodes.DUP:
+ dup();
+ break;
+ case Opcodes.DUP_X1:
+ dupX1();
+ break;
+ case Opcodes.DUP_X2:
+ dupX2();
+ break;
+ case Opcodes.DUP2:
+ dup2();
+ break;
+ case Opcodes.DUP2_X1:
+ dup2X1();
+ break;
+ case Opcodes.DUP2_X2:
+ dup2X2();
+ break;
+ case Opcodes.SWAP:
+ swap();
+ break;
+ case Opcodes.IADD:
+ add(Type.INT_TYPE);
+ break;
+ case Opcodes.LADD:
+ add(Type.LONG_TYPE);
+ break;
+ case Opcodes.FADD:
+ add(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DADD:
+ add(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.ISUB:
+ sub(Type.INT_TYPE);
+ break;
+ case Opcodes.LSUB:
+ sub(Type.LONG_TYPE);
+ break;
+ case Opcodes.FSUB:
+ sub(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DSUB:
+ sub(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.IMUL:
+ mul(Type.INT_TYPE);
+ break;
+ case Opcodes.LMUL:
+ mul(Type.LONG_TYPE);
+ break;
+ case Opcodes.FMUL:
+ mul(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DMUL:
+ mul(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.IDIV:
+ div(Type.INT_TYPE);
+ break;
+ case Opcodes.LDIV:
+ div(Type.LONG_TYPE);
+ break;
+ case Opcodes.FDIV:
+ div(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DDIV:
+ div(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.IREM:
+ rem(Type.INT_TYPE);
+ break;
+ case Opcodes.LREM:
+ rem(Type.LONG_TYPE);
+ break;
+ case Opcodes.FREM:
+ rem(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DREM:
+ rem(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.INEG:
+ neg(Type.INT_TYPE);
+ break;
+ case Opcodes.LNEG:
+ neg(Type.LONG_TYPE);
+ break;
+ case Opcodes.FNEG:
+ neg(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DNEG:
+ neg(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.ISHL:
+ shl(Type.INT_TYPE);
+ break;
+ case Opcodes.LSHL:
+ shl(Type.LONG_TYPE);
+ break;
+ case Opcodes.ISHR:
+ shr(Type.INT_TYPE);
+ break;
+ case Opcodes.LSHR:
+ shr(Type.LONG_TYPE);
+ break;
+ case Opcodes.IUSHR:
+ ushr(Type.INT_TYPE);
+ break;
+ case Opcodes.LUSHR:
+ ushr(Type.LONG_TYPE);
+ break;
+ case Opcodes.IAND:
+ and(Type.INT_TYPE);
+ break;
+ case Opcodes.LAND:
+ and(Type.LONG_TYPE);
+ break;
+ case Opcodes.IOR:
+ or(Type.INT_TYPE);
+ break;
+ case Opcodes.LOR:
+ or(Type.LONG_TYPE);
+ break;
+ case Opcodes.IXOR:
+ xor(Type.INT_TYPE);
+ break;
+ case Opcodes.LXOR:
+ xor(Type.LONG_TYPE);
+ break;
+ case Opcodes.I2L:
+ cast(Type.INT_TYPE, Type.LONG_TYPE);
+ break;
+ case Opcodes.I2F:
+ cast(Type.INT_TYPE, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.I2D:
+ cast(Type.INT_TYPE, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.L2I:
+ cast(Type.LONG_TYPE, Type.INT_TYPE);
+ break;
+ case Opcodes.L2F:
+ cast(Type.LONG_TYPE, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.L2D:
+ cast(Type.LONG_TYPE, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.F2I:
+ cast(Type.FLOAT_TYPE, Type.INT_TYPE);
+ break;
+ case Opcodes.F2L:
+ cast(Type.FLOAT_TYPE, Type.LONG_TYPE);
+ break;
+ case Opcodes.F2D:
+ cast(Type.FLOAT_TYPE, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.D2I:
+ cast(Type.DOUBLE_TYPE, Type.INT_TYPE);
+ break;
+ case Opcodes.D2L:
+ cast(Type.DOUBLE_TYPE, Type.LONG_TYPE);
+ break;
+ case Opcodes.D2F:
+ cast(Type.DOUBLE_TYPE, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.I2B:
+ cast(Type.INT_TYPE, Type.BYTE_TYPE);
+ break;
+ case Opcodes.I2C:
+ cast(Type.INT_TYPE, Type.CHAR_TYPE);
+ break;
+ case Opcodes.I2S:
+ cast(Type.INT_TYPE, Type.SHORT_TYPE);
+ break;
+ case Opcodes.LCMP:
+ lcmp();
+ break;
+ case Opcodes.FCMPL:
+ cmpl(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.FCMPG:
+ cmpg(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DCMPL:
+ cmpl(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.DCMPG:
+ cmpg(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.IRETURN:
+ areturn(Type.INT_TYPE);
+ break;
+ case Opcodes.LRETURN:
+ areturn(Type.LONG_TYPE);
+ break;
+ case Opcodes.FRETURN:
+ areturn(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DRETURN:
+ areturn(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.ARETURN:
+ areturn(OBJECT_TYPE);
+ break;
+ case Opcodes.RETURN:
+ areturn(Type.VOID_TYPE);
+ break;
+ case Opcodes.ARRAYLENGTH:
+ arraylength();
+ break;
+ case Opcodes.ATHROW:
+ athrow();
+ break;
+ case Opcodes.MONITORENTER:
+ monitorenter();
+ break;
+ case Opcodes.MONITOREXIT:
+ monitorexit();
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitIntInsn(final int opcode, final int operand) {
+ switch (opcode) {
+ case Opcodes.BIPUSH:
+ iconst(operand);
+ break;
+ case Opcodes.SIPUSH:
+ iconst(operand);
+ break;
+ case Opcodes.NEWARRAY:
+ switch (operand) {
+ case Opcodes.T_BOOLEAN:
+ newarray(Type.BOOLEAN_TYPE);
+ break;
+ case Opcodes.T_CHAR:
+ newarray(Type.CHAR_TYPE);
+ break;
+ case Opcodes.T_BYTE:
+ newarray(Type.BYTE_TYPE);
+ break;
+ case Opcodes.T_SHORT:
+ newarray(Type.SHORT_TYPE);
+ break;
+ case Opcodes.T_INT:
+ newarray(Type.INT_TYPE);
+ break;
+ case Opcodes.T_FLOAT:
+ newarray(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.T_LONG:
+ newarray(Type.LONG_TYPE);
+ break;
+ case Opcodes.T_DOUBLE:
+ newarray(Type.DOUBLE_TYPE);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitVarInsn(final int opcode, final int varIndex) {
+ switch (opcode) {
+ case Opcodes.ILOAD:
+ load(varIndex, Type.INT_TYPE);
+ break;
+ case Opcodes.LLOAD:
+ load(varIndex, Type.LONG_TYPE);
+ break;
+ case Opcodes.FLOAD:
+ load(varIndex, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DLOAD:
+ load(varIndex, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.ALOAD:
+ load(varIndex, OBJECT_TYPE);
+ break;
+ case Opcodes.ISTORE:
+ store(varIndex, Type.INT_TYPE);
+ break;
+ case Opcodes.LSTORE:
+ store(varIndex, Type.LONG_TYPE);
+ break;
+ case Opcodes.FSTORE:
+ store(varIndex, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DSTORE:
+ store(varIndex, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.ASTORE:
+ store(varIndex, OBJECT_TYPE);
+ break;
+ case Opcodes.RET:
+ ret(varIndex);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(final int opcode, final String type) {
+ Type objectType = Type.getObjectType(type);
+ switch (opcode) {
+ case Opcodes.NEW:
+ anew(objectType);
+ break;
+ case Opcodes.ANEWARRAY:
+ newarray(objectType);
+ break;
+ case Opcodes.CHECKCAST:
+ checkcast(objectType);
+ break;
+ case Opcodes.INSTANCEOF:
+ instanceOf(objectType);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ getstatic(owner, name, descriptor);
+ break;
+ case Opcodes.PUTSTATIC:
+ putstatic(owner, name, descriptor);
+ break;
+ case Opcodes.GETFIELD:
+ getfield(owner, name, descriptor);
+ break;
+ case Opcodes.PUTFIELD:
+ putfield(owner, name, descriptor);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(
+ final int opcodeAndSource,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ if (api < Opcodes.ASM5 && (opcodeAndSource & Opcodes.SOURCE_DEPRECATED) == 0) {
+ // Redirect the call to the deprecated version of this method.
+ super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
+ return;
+ }
+ int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK;
+
+ switch (opcode) {
+ case Opcodes.INVOKESPECIAL:
+ invokespecial(owner, name, descriptor, isInterface);
+ break;
+ case Opcodes.INVOKEVIRTUAL:
+ invokevirtual(owner, name, descriptor, isInterface);
+ break;
+ case Opcodes.INVOKESTATIC:
+ invokestatic(owner, name, descriptor, isInterface);
+ break;
+ case Opcodes.INVOKEINTERFACE:
+ invokeinterface(owner, name, descriptor);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ invokedynamic(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+ }
+
+ @Override
+ public void visitJumpInsn(final int opcode, final Label label) {
+ switch (opcode) {
+ case Opcodes.IFEQ:
+ ifeq(label);
+ break;
+ case Opcodes.IFNE:
+ ifne(label);
+ break;
+ case Opcodes.IFLT:
+ iflt(label);
+ break;
+ case Opcodes.IFGE:
+ ifge(label);
+ break;
+ case Opcodes.IFGT:
+ ifgt(label);
+ break;
+ case Opcodes.IFLE:
+ ifle(label);
+ break;
+ case Opcodes.IF_ICMPEQ:
+ ificmpeq(label);
+ break;
+ case Opcodes.IF_ICMPNE:
+ ificmpne(label);
+ break;
+ case Opcodes.IF_ICMPLT:
+ ificmplt(label);
+ break;
+ case Opcodes.IF_ICMPGE:
+ ificmpge(label);
+ break;
+ case Opcodes.IF_ICMPGT:
+ ificmpgt(label);
+ break;
+ case Opcodes.IF_ICMPLE:
+ ificmple(label);
+ break;
+ case Opcodes.IF_ACMPEQ:
+ ifacmpeq(label);
+ break;
+ case Opcodes.IF_ACMPNE:
+ ifacmpne(label);
+ break;
+ case Opcodes.GOTO:
+ goTo(label);
+ break;
+ case Opcodes.JSR:
+ jsr(label);
+ break;
+ case Opcodes.IFNULL:
+ ifnull(label);
+ break;
+ case Opcodes.IFNONNULL:
+ ifnonnull(label);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitLabel(final Label label) {
+ mark(label);
+ }
+
+ @Override
+ public void visitLdcInsn(final Object value) {
+ if (api < Opcodes.ASM5
+ && (value instanceof Handle
+ || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) {
+ throw new UnsupportedOperationException("This feature requires ASM5");
+ }
+ if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) {
+ throw new UnsupportedOperationException("This feature requires ASM7");
+ }
+ if (value instanceof Integer) {
+ iconst((Integer) value);
+ } else if (value instanceof Byte) {
+ iconst(((Byte) value).intValue());
+ } else if (value instanceof Character) {
+ iconst(((Character) value).charValue());
+ } else if (value instanceof Short) {
+ iconst(((Short) value).intValue());
+ } else if (value instanceof Boolean) {
+ iconst(((Boolean) value).booleanValue() ? 1 : 0);
+ } else if (value instanceof Float) {
+ fconst((Float) value);
+ } else if (value instanceof Long) {
+ lconst((Long) value);
+ } else if (value instanceof Double) {
+ dconst((Double) value);
+ } else if (value instanceof String) {
+ aconst(value);
+ } else if (value instanceof Type) {
+ tconst((Type) value);
+ } else if (value instanceof Handle) {
+ hconst((Handle) value);
+ } else if (value instanceof ConstantDynamic) {
+ cconst((ConstantDynamic) value);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void visitIincInsn(final int varIndex, final int increment) {
+ iinc(varIndex, increment);
+ }
+
+ @Override
+ public void visitTableSwitchInsn(
+ final int min, final int max, final Label dflt, final Label... labels) {
+ tableswitch(min, max, dflt, labels);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
+ lookupswitch(dflt, keys, labels);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
+ multianewarray(descriptor, numDimensions);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+
+ /** Generates a nop instruction. */
+ public void nop() {
+ mv.visitInsn(Opcodes.NOP);
+ }
+
+ /**
+ * Generates the instruction to push the given value on the stack.
+ *
+ * @param value the constant to be pushed on the stack. This parameter must be an {@link Integer},
+ * a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link Type} of
+ * OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is 49, a
+ * {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle constants,
+ * for classes whose version is 51 or a {@link ConstantDynamic} for a constant dynamic for
+ * classes whose version is 55.
+ */
+ public void aconst(final Object value) {
+ if (value == null) {
+ mv.visitInsn(Opcodes.ACONST_NULL);
+ } else {
+ mv.visitLdcInsn(value);
+ }
+ }
+
+ /**
+ * Generates the instruction to push the given value on the stack.
+ *
+ * @param intValue the constant to be pushed on the stack.
+ */
+ public void iconst(final int intValue) {
+ if (intValue >= -1 && intValue <= 5) {
+ mv.visitInsn(Opcodes.ICONST_0 + intValue);
+ } else if (intValue >= Byte.MIN_VALUE && intValue <= Byte.MAX_VALUE) {
+ mv.visitIntInsn(Opcodes.BIPUSH, intValue);
+ } else if (intValue >= Short.MIN_VALUE && intValue <= Short.MAX_VALUE) {
+ mv.visitIntInsn(Opcodes.SIPUSH, intValue);
+ } else {
+ mv.visitLdcInsn(intValue);
+ }
+ }
+
+ /**
+ * Generates the instruction to push the given value on the stack.
+ *
+ * @param longValue the constant to be pushed on the stack.
+ */
+ public void lconst(final long longValue) {
+ if (longValue == 0L || longValue == 1L) {
+ mv.visitInsn(Opcodes.LCONST_0 + (int) longValue);
+ } else {
+ mv.visitLdcInsn(longValue);
+ }
+ }
+
+ /**
+ * Generates the instruction to push the given value on the stack.
+ *
+ * @param floatValue the constant to be pushed on the stack.
+ */
+ public void fconst(final float floatValue) {
+ int bits = Float.floatToIntBits(floatValue);
+ if (bits == 0L || bits == 0x3F800000 || bits == 0x40000000) { // 0..2
+ mv.visitInsn(Opcodes.FCONST_0 + (int) floatValue);
+ } else {
+ mv.visitLdcInsn(floatValue);
+ }
+ }
+
+ /**
+ * Generates the instruction to push the given value on the stack.
+ *
+ * @param doubleValue the constant to be pushed on the stack.
+ */
+ public void dconst(final double doubleValue) {
+ long bits = Double.doubleToLongBits(doubleValue);
+ if (bits == 0L || bits == 0x3FF0000000000000L) { // +0.0d and 1.0d
+ mv.visitInsn(Opcodes.DCONST_0 + (int) doubleValue);
+ } else {
+ mv.visitLdcInsn(doubleValue);
+ }
+ }
+
+ /**
+ * Generates the instruction to push the given type on the stack.
+ *
+ * @param type the type to be pushed on the stack.
+ */
+ public void tconst(final Type type) {
+ mv.visitLdcInsn(type);
+ }
+
+ /**
+ * Generates the instruction to push the given handle on the stack.
+ *
+ * @param handle the handle to be pushed on the stack.
+ */
+ public void hconst(final Handle handle) {
+ mv.visitLdcInsn(handle);
+ }
+
+ /**
+ * Generates the instruction to push the given constant dynamic on the stack.
+ *
+ * @param constantDynamic the constant dynamic to be pushed on the stack.
+ */
+ public void cconst(final ConstantDynamic constantDynamic) {
+ mv.visitLdcInsn(constantDynamic);
+ }
+
+ public void load(final int varIndex, final Type type) {
+ mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), varIndex);
+ }
+
+ public void aload(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IALOAD));
+ }
+
+ public void store(final int varIndex, final Type type) {
+ mv.visitVarInsn(type.getOpcode(Opcodes.ISTORE), varIndex);
+ }
+
+ public void astore(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IASTORE));
+ }
+
+ public void pop() {
+ mv.visitInsn(Opcodes.POP);
+ }
+
+ public void pop2() {
+ mv.visitInsn(Opcodes.POP2);
+ }
+
+ public void dup() {
+ mv.visitInsn(Opcodes.DUP);
+ }
+
+ public void dup2() {
+ mv.visitInsn(Opcodes.DUP2);
+ }
+
+ public void dupX1() {
+ mv.visitInsn(Opcodes.DUP_X1);
+ }
+
+ public void dupX2() {
+ mv.visitInsn(Opcodes.DUP_X2);
+ }
+
+ public void dup2X1() {
+ mv.visitInsn(Opcodes.DUP2_X1);
+ }
+
+ public void dup2X2() {
+ mv.visitInsn(Opcodes.DUP2_X2);
+ }
+
+ public void swap() {
+ mv.visitInsn(Opcodes.SWAP);
+ }
+
+ public void add(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IADD));
+ }
+
+ public void sub(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.ISUB));
+ }
+
+ public void mul(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IMUL));
+ }
+
+ public void div(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IDIV));
+ }
+
+ public void rem(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IREM));
+ }
+
+ public void neg(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.INEG));
+ }
+
+ public void shl(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.ISHL));
+ }
+
+ public void shr(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.ISHR));
+ }
+
+ public void ushr(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IUSHR));
+ }
+
+ public void and(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IAND));
+ }
+
+ public void or(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IOR));
+ }
+
+ public void xor(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IXOR));
+ }
+
+ public void iinc(final int varIndex, final int increment) {
+ mv.visitIincInsn(varIndex, increment);
+ }
+
+ /**
+ * Generates the instruction to cast from the first given type to the other.
+ *
+ * @param from a Type.
+ * @param to a Type.
+ */
+ public void cast(final Type from, final Type to) {
+ cast(mv, from, to);
+ }
+
+ /**
+ * Generates the instruction to cast from the first given type to the other.
+ *
+ * @param methodVisitor the method visitor to use to generate the instruction.
+ * @param from a Type.
+ * @param to a Type.
+ */
+ public static void cast(final MethodVisitor methodVisitor, final Type from, final Type to) {
+ if (from != to) {
+ if (from == Type.DOUBLE_TYPE) {
+ if (to == Type.FLOAT_TYPE) {
+ methodVisitor.visitInsn(Opcodes.D2F);
+ } else if (to == Type.LONG_TYPE) {
+ methodVisitor.visitInsn(Opcodes.D2L);
+ } else {
+ methodVisitor.visitInsn(Opcodes.D2I);
+ cast(methodVisitor, Type.INT_TYPE, to);
+ }
+ } else if (from == Type.FLOAT_TYPE) {
+ if (to == Type.DOUBLE_TYPE) {
+ methodVisitor.visitInsn(Opcodes.F2D);
+ } else if (to == Type.LONG_TYPE) {
+ methodVisitor.visitInsn(Opcodes.F2L);
+ } else {
+ methodVisitor.visitInsn(Opcodes.F2I);
+ cast(methodVisitor, Type.INT_TYPE, to);
+ }
+ } else if (from == Type.LONG_TYPE) {
+ if (to == Type.DOUBLE_TYPE) {
+ methodVisitor.visitInsn(Opcodes.L2D);
+ } else if (to == Type.FLOAT_TYPE) {
+ methodVisitor.visitInsn(Opcodes.L2F);
+ } else {
+ methodVisitor.visitInsn(Opcodes.L2I);
+ cast(methodVisitor, Type.INT_TYPE, to);
+ }
+ } else {
+ if (to == Type.BYTE_TYPE) {
+ methodVisitor.visitInsn(Opcodes.I2B);
+ } else if (to == Type.CHAR_TYPE) {
+ methodVisitor.visitInsn(Opcodes.I2C);
+ } else if (to == Type.DOUBLE_TYPE) {
+ methodVisitor.visitInsn(Opcodes.I2D);
+ } else if (to == Type.FLOAT_TYPE) {
+ methodVisitor.visitInsn(Opcodes.I2F);
+ } else if (to == Type.LONG_TYPE) {
+ methodVisitor.visitInsn(Opcodes.I2L);
+ } else if (to == Type.SHORT_TYPE) {
+ methodVisitor.visitInsn(Opcodes.I2S);
+ }
+ }
+ }
+ }
+
+ public void lcmp() {
+ mv.visitInsn(Opcodes.LCMP);
+ }
+
+ public void cmpl(final Type type) {
+ mv.visitInsn(type == Type.FLOAT_TYPE ? Opcodes.FCMPL : Opcodes.DCMPL);
+ }
+
+ public void cmpg(final Type type) {
+ mv.visitInsn(type == Type.FLOAT_TYPE ? Opcodes.FCMPG : Opcodes.DCMPG);
+ }
+
+ public void ifeq(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFEQ, label);
+ }
+
+ public void ifne(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFNE, label);
+ }
+
+ public void iflt(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFLT, label);
+ }
+
+ public void ifge(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFGE, label);
+ }
+
+ public void ifgt(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFGT, label);
+ }
+
+ public void ifle(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFLE, label);
+ }
+
+ public void ificmpeq(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ICMPEQ, label);
+ }
+
+ public void ificmpne(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ICMPNE, label);
+ }
+
+ public void ificmplt(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ICMPLT, label);
+ }
+
+ public void ificmpge(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ICMPGE, label);
+ }
+
+ public void ificmpgt(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ICMPGT, label);
+ }
+
+ public void ificmple(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ICMPLE, label);
+ }
+
+ public void ifacmpeq(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ACMPEQ, label);
+ }
+
+ public void ifacmpne(final Label label) {
+ mv.visitJumpInsn(Opcodes.IF_ACMPNE, label);
+ }
+
+ public void goTo(final Label label) {
+ mv.visitJumpInsn(Opcodes.GOTO, label);
+ }
+
+ public void jsr(final Label label) {
+ mv.visitJumpInsn(Opcodes.JSR, label);
+ }
+
+ public void ret(final int varIndex) {
+ mv.visitVarInsn(Opcodes.RET, varIndex);
+ }
+
+ public void tableswitch(final int min, final int max, final Label dflt, final Label... labels) {
+ mv.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+
+ public void lookupswitch(final Label dflt, final int[] keys, final Label[] labels) {
+ mv.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+
+ public void areturn(final Type type) {
+ mv.visitInsn(type.getOpcode(Opcodes.IRETURN));
+ }
+
+ public void getstatic(final String owner, final String name, final String descriptor) {
+ mv.visitFieldInsn(Opcodes.GETSTATIC, owner, name, descriptor);
+ }
+
+ public void putstatic(final String owner, final String name, final String descriptor) {
+ mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, name, descriptor);
+ }
+
+ public void getfield(final String owner, final String name, final String descriptor) {
+ mv.visitFieldInsn(Opcodes.GETFIELD, owner, name, descriptor);
+ }
+
+ public void putfield(final String owner, final String name, final String descriptor) {
+ mv.visitFieldInsn(Opcodes.PUTFIELD, owner, name, descriptor);
+ }
+
+ /**
+ * Deprecated.
+ *
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @deprecated use {@link #invokevirtual(String, String, String, boolean)} instead.
+ */
+ @Deprecated
+ public void invokevirtual(final String owner, final String name, final String descriptor) {
+ if (api >= Opcodes.ASM5) {
+ invokevirtual(owner, name, descriptor, false);
+ return;
+ }
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, descriptor);
+ }
+
+ /**
+ * Generates the instruction to call the given virtual method.
+ *
+ * @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 invokevirtual(
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
+ if (api < Opcodes.ASM5) {
+ if (isInterface) {
+ throw new UnsupportedOperationException("INVOKEVIRTUAL on interfaces require ASM 5");
+ }
+ invokevirtual(owner, name, descriptor);
+ return;
+ }
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
+ }
+
+ /**
+ * Deprecated.
+ *
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @deprecated use {@link #invokespecial(String, String, String, boolean)} instead.
+ */
+ @Deprecated
+ public void invokespecial(final String owner, final String name, final String descriptor) {
+ if (api >= Opcodes.ASM5) {
+ invokespecial(owner, name, descriptor, false);
+ return;
+ }
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, descriptor, false);
+ }
+
+ /**
+ * Generates the instruction to call the given special method.
+ *
+ * @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 invokespecial(
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
+ if (api < Opcodes.ASM5) {
+ if (isInterface) {
+ throw new UnsupportedOperationException("INVOKESPECIAL on interfaces require ASM 5");
+ }
+ invokespecial(owner, name, descriptor);
+ return;
+ }
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, descriptor, isInterface);
+ }
+
+ /**
+ * Deprecated.
+ *
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @deprecated use {@link #invokestatic(String, String, String, boolean)} instead.
+ */
+ @Deprecated
+ public void invokestatic(final String owner, final String name, final String descriptor) {
+ if (api >= Opcodes.ASM5) {
+ invokestatic(owner, name, descriptor, false);
+ return;
+ }
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, descriptor, false);
+ }
+
+ /**
+ * Generates the instruction to call the given static method.
+ *
+ * @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 invokestatic(
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
+ if (api < Opcodes.ASM5) {
+ if (isInterface) {
+ throw new UnsupportedOperationException("INVOKESTATIC on interfaces require ASM 5");
+ }
+ invokestatic(owner, name, descriptor);
+ return;
+ }
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, descriptor, isInterface);
+ }
+
+ /**
+ * Generates the instruction to call the given interface method.
+ *
+ * @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}).
+ */
+ public void invokeinterface(final String owner, final String name, final String descriptor) {
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, descriptor, true);
+ }
+
+ /**
+ * Generates the instruction to call the given dynamic method.
+ *
+ * @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 invokedynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object[] bootstrapMethodArguments) {
+ mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+ }
+
+ public void anew(final Type type) {
+ mv.visitTypeInsn(Opcodes.NEW, type.getInternalName());
+ }
+
+ /**
+ * Generates the instruction to create and push on the stack an array of the given type.
+ *
+ * @param type an array Type.
+ */
+ public void newarray(final Type type) {
+ newarray(mv, type);
+ }
+
+ /**
+ * Generates the instruction to create and push on the stack an array of the given type.
+ *
+ * @param methodVisitor the method visitor to use to generate the instruction.
+ * @param type an array Type.
+ */
+ public static void newarray(final MethodVisitor methodVisitor, final Type type) {
+ int arrayType;
+ switch (type.getSort()) {
+ case Type.BOOLEAN:
+ arrayType = Opcodes.T_BOOLEAN;
+ break;
+ case Type.CHAR:
+ arrayType = Opcodes.T_CHAR;
+ break;
+ case Type.BYTE:
+ arrayType = Opcodes.T_BYTE;
+ break;
+ case Type.SHORT:
+ arrayType = Opcodes.T_SHORT;
+ break;
+ case Type.INT:
+ arrayType = Opcodes.T_INT;
+ break;
+ case Type.FLOAT:
+ arrayType = Opcodes.T_FLOAT;
+ break;
+ case Type.LONG:
+ arrayType = Opcodes.T_LONG;
+ break;
+ case Type.DOUBLE:
+ arrayType = Opcodes.T_DOUBLE;
+ break;
+ default:
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, type.getInternalName());
+ return;
+ }
+ methodVisitor.visitIntInsn(Opcodes.NEWARRAY, arrayType);
+ }
+
+ public void arraylength() {
+ mv.visitInsn(Opcodes.ARRAYLENGTH);
+ }
+
+ public void athrow() {
+ mv.visitInsn(Opcodes.ATHROW);
+ }
+
+ public void checkcast(final Type type) {
+ mv.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName());
+ }
+
+ public void instanceOf(final Type type) {
+ mv.visitTypeInsn(Opcodes.INSTANCEOF, type.getInternalName());
+ }
+
+ public void monitorenter() {
+ mv.visitInsn(Opcodes.MONITORENTER);
+ }
+
+ public void monitorexit() {
+ mv.visitInsn(Opcodes.MONITOREXIT);
+ }
+
+ public void multianewarray(final String descriptor, final int numDimensions) {
+ mv.visitMultiANewArrayInsn(descriptor, numDimensions);
+ }
+
+ public void ifnull(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFNULL, label);
+ }
+
+ public void ifnonnull(final Label label) {
+ mv.visitJumpInsn(Opcodes.IFNONNULL, label);
+ }
+
+ public void mark(final Label label) {
+ mv.visitLabel(label);
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Label.java b/src/jvm/clojure/asm/Label.java
index 86afe087de..07bec62c62 100644
--- a/src/jvm/clojure/asm/Label.java
+++ b/src/jvm/clojure/asm/Label.java
@@ -81,6 +81,9 @@ public class Label {
/** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */
static final int FLAG_SUBROUTINE_END = 64;
+ /** A flag indicating that this label has at least one associated line number. */
+ static final int FLAG_LINE_NUMBER = 128;
+
/**
* The number of elements to add to the {@link #otherLineNumbers} array when it needs to be
* resized to store a new source line number.
@@ -113,6 +116,13 @@ public class Label {
*/
static final int FORWARD_REFERENCE_TYPE_WIDE = 0x20000000;
+ /**
+ * The type of forward references stored in two bytes in the stack map table. This is the
+ * case of the labels of {@link Frame#ITEM_UNINITIALIZED} stack map frame elements, when the NEW
+ * instruction is after the <init> constructor call (in bytecode offset order).
+ */
+ static final int FORWARD_REFERENCE_TYPE_STACK_MAP = 0x30000000;
+
/**
* 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,
@@ -145,9 +155,9 @@ public class Label {
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}.
+ * The source line number corresponding to this label, if {@link #FLAG_LINE_NUMBER} is set. 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;
@@ -178,7 +188,7 @@ public class Label {
* and {@link #FORWARD_REFERENCE_HANDLE_MASK}.
*
*
- * For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is
+ *
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
@@ -227,7 +237,8 @@ public class Label {
/**
* 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.
+ * the basic block corresponding to this label. This maximum is always positive or {@literal
+ * null}.
*/
short outputStackMax;
@@ -264,12 +275,12 @@ public class Label {
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).
+ * The next element in the list of labels to which this label belongs, or {@literal null} if it
+ * does not belong to any list. All lists of labels must end with the {@link #EMPTY_LIST}
+ * sentinel, in order to ensure that this field is null if and only if this label does not belong
+ * to a list of labels. Note that there can be several lists of labels at the same time, but that
+ * a label can belong to at most one list at a time (unless some lists share a common tail, but
+ * this is not used in practice).
*
*
List of labels are used in {@link MethodWriter#computeAllFrames} and {@link
* MethodWriter#computeMaxStackAndLocal} to compute stack map frames and the maximum stack size,
@@ -331,7 +342,8 @@ final Label getCanonicalInstance() {
* @param lineNumber a source line number (which should be strictly positive).
*/
final void addLineNumber(final int lineNumber) {
- if (this.lineNumber == 0) {
+ if ((flags & FLAG_LINE_NUMBER) == 0) {
+ flags |= FLAG_LINE_NUMBER;
this.lineNumber = (short) lineNumber;
} else {
if (otherLineNumbers == null) {
@@ -355,7 +367,7 @@ final void addLineNumber(final int lineNumber) {
*/
final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) {
methodVisitor.visitLabel(this);
- if (visitLineNumbers && lineNumber != 0) {
+ if (visitLineNumbers && (flags & FLAG_LINE_NUMBER) != 0) {
methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this);
if (otherLineNumbers != null) {
for (int i = 1; i <= otherLineNumbers[0]; ++i) {
@@ -381,7 +393,7 @@ final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumb
* @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) {
+ final ByteVector code, final int sourceInsnBytecodeOffset, final boolean wideReference) {
if ((flags & FLAG_RESOLVED) == 0) {
if (wideReference) {
addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_WIDE, code.length);
@@ -399,6 +411,20 @@ final void put(
}
}
+ /**
+ * Puts a reference to this label in the stack map table of a method. If the bytecode
+ * offset of the label is known, it is written directly. Otherwise, a null relative offset is
+ * written and a new forward reference is declared for this label.
+ *
+ * @param stackMapTableEntries the stack map table where the label offset must be added.
+ */
+ final void put(final ByteVector stackMapTableEntries) {
+ if ((flags & FLAG_RESOLVED) == 0) {
+ addForwardReference(0, FORWARD_REFERENCE_TYPE_STACK_MAP, stackMapTableEntries.length);
+ }
+ stackMapTableEntries.putShort(bytecodeOffset);
+ }
+
/**
* 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
@@ -412,7 +438,7 @@ final void put(
* stored.
*/
private void addForwardReference(
- final int sourceInsnBytecodeOffset, final int referenceType, final int referenceHandle) {
+ final int sourceInsnBytecodeOffset, final int referenceType, final int referenceHandle) {
if (forwardReferences == null) {
forwardReferences = new int[FORWARD_REFERENCES_CAPACITY_INCREMENT];
}
@@ -431,17 +457,21 @@ private void addForwardReference(
* 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.
+ * where left in the bytecode (and optionally in the stack map table) by each forward reference
+ * previously added to this label.
*
* @param code the bytecode of the method.
+ * @param stackMapTableEntries the 'entries' array of the StackMapTable code attribute of the
+ * method. Maybe {@literal null}.
* @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
+ * @return {@literal true} if a blank that was left for this label was too small to store the
* offset. In such a case the corresponding jump instruction is replaced with an equivalent
* ASM specific instruction using an unsigned two bytes offset. These ASM specific
* instructions are later replaced with standard bytecode instructions with wider offsets (4
* bytes instead of 2), in ClassReader.
*/
- final boolean resolve(final byte[] code, final int bytecodeOffset) {
+ final boolean resolve(
+ final byte[] code, final ByteVector stackMapTableEntries, final int bytecodeOffset) {
this.flags |= FLAG_RESOLVED;
this.bytecodeOffset = bytecodeOffset;
if (forwardReferences == null) {
@@ -471,11 +501,14 @@ final boolean resolve(final byte[] code, final int bytecodeOffset) {
}
code[handle++] = (byte) (relativeOffset >>> 8);
code[handle] = (byte) relativeOffset;
- } else {
+ } else if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_WIDE) {
code[handle++] = (byte) (relativeOffset >>> 24);
code[handle++] = (byte) (relativeOffset >>> 16);
code[handle++] = (byte) (relativeOffset >>> 8);
code[handle] = (byte) relativeOffset;
+ } else {
+ stackMapTableEntries.data[handle++] = (byte) (bytecodeOffset >>> 8);
+ stackMapTableEntries.data[handle] = (byte) bytecodeOffset;
}
}
return hasAsmInstructions;
@@ -552,15 +585,15 @@ final void addSubroutineRetSuccessors(final Label subroutineCaller) {
// 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.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);
+ 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
@@ -593,7 +626,7 @@ private Label pushSuccessors(final Label listOfLabelsToProcess) {
// 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;
+ (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.
@@ -618,4 +651,4 @@ private Label pushSuccessors(final Label listOfLabelsToProcess) {
public String toString() {
return "L" + System.identityHashCode(this);
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/MethodTooLargeException.java b/src/jvm/clojure/asm/MethodTooLargeException.java
new file mode 100644
index 0000000000..5b790c6140
--- /dev/null
+++ b/src/jvm/clojure/asm/MethodTooLargeException.java
@@ -0,0 +1,99 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+/**
+ * Exception thrown when the Code attribute of a method produced by a {@link ClassWriter} is too
+ * large.
+ *
+ * @author Jason Zaugg
+ */
+public final class MethodTooLargeException extends IndexOutOfBoundsException {
+ private static final long serialVersionUID = 6807380416709738314L;
+
+ private final String className;
+ private final String methodName;
+ private final String descriptor;
+ private final int codeSize;
+
+ /**
+ * Constructs a new {@link MethodTooLargeException}.
+ *
+ * @param className the internal name of the owner class (see {@link Type#getInternalName()}).
+ * @param methodName the name of the method.
+ * @param descriptor the descriptor of the method.
+ * @param codeSize the size of the method's Code attribute, in bytes.
+ */
+ public MethodTooLargeException(
+ final String className,
+ final String methodName,
+ final String descriptor,
+ final int codeSize) {
+ super("Method too large: " + className + "." + methodName + " " + descriptor);
+ this.className = className;
+ this.methodName = methodName;
+ this.descriptor = descriptor;
+ this.codeSize = codeSize;
+ }
+
+ /**
+ * Returns the internal name of the owner class.
+ *
+ * @return the internal name of the owner class (see {@link Type#getInternalName()}).
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ /**
+ * Returns the name of the method.
+ *
+ * @return the name of the method.
+ */
+ public String getMethodName() {
+ return methodName;
+ }
+
+ /**
+ * Returns the descriptor of the method.
+ *
+ * @return the descriptor of the method.
+ */
+ public String getDescriptor() {
+ return descriptor;
+ }
+
+ /**
+ * Returns the size of the method's Code attribute, in bytes.
+ *
+ * @return the size of the method's Code attribute, in bytes.
+ */
+ public int getCodeSize() {
+ return codeSize;
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/MethodVisitor.java b/src/jvm/clojure/asm/MethodVisitor.java
index 4489612134..16cc77f887 100644
--- a/src/jvm/clojure/asm/MethodVisitor.java
+++ b/src/jvm/clojure/asm/MethodVisitor.java
@@ -29,20 +29,21 @@
/**
* 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.
+ * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} |
+ * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} | {@code
+ * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} |
+ * {@code visitXInsn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code
+ * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code
+ * visitLocalVariableAnnotation} | {@code visitLineNumber} | {@code visitAttribute} )* {@code
+ * visitMaxs} ] {@code visitEnd}. In addition, the {@code visitXInsn} and {@code visitLabel}
+ * methods must be called in the sequential order of the bytecode instructions of the visited code,
+ * {@code visitInsnAnnotation} must be called after the annotated instruction, {@code
+ * visitTryCatchBlock} must be called before the labels passed as arguments have been
+ * visited, {@code visitTryCatchBlockAnnotation} must be called after the corresponding try
+ * catch block has been visited, and the {@code visitLocalVariable}, {@code
+ * visitLocalVariableAnnotation} and {@code visitLineNumber} methods must be called after the
+ * labels passed as arguments have been visited. Finally, the {@code visitAttribute} method must be
+ * called before {@code visitCode} for non-code attributes, and after it for code attributes.
*
* @author Eric Bruneton
*/
@@ -51,45 +52,61 @@ public abstract class MethodVisitor {
private static final String REQUIRES_ASM5 = "This feature requires ASM5";
/**
- * The ASM API version implemented by this visitor. The value of this field must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
*/
protected final int api;
- /** The method visitor to which this visitor must delegate method calls. May be null. */
+ /**
+ * The method visitor to which this visitor must delegate method calls. May be {@literal null}.
+ */
protected MethodVisitor mv;
/**
* Constructs a new {@link MethodVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
- * Opcodes#ASM7_EXPERIMENTAL}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public MethodVisitor(final int api) {
+ protected 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 api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @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();
+ protected MethodVisitor(final int api, final MethodVisitor methodVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
this.api = api;
this.mv = methodVisitor;
}
+ /**
+ * The method visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the method visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public MethodVisitor getDelegate() {
+ return mv;
+ }
+
// -----------------------------------------------------------------------------------------------
// Parameters, annotations and non standard attributes
// -----------------------------------------------------------------------------------------------
@@ -97,9 +114,9 @@ public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
/**
* 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}).
+ * @param name parameter name or {@literal null} if none is provided.
+ * @param access the parameter's access flags, only {@code ACC_FINAL}, {@code ACC_SYNTHETIC}
+ * or/and {@code ACC_MANDATED} are allowed (see {@link Opcodes}).
*/
public void visitParameter(final String name, final int access) {
if (api < Opcodes.ASM5) {
@@ -114,9 +131,9 @@ public void visitParameter(final String name, final int 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.
+ * {@literal null} if this visitor is not interested in visiting this default value. The
+ * 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover,
+ * exactly one visit method must be called on this annotation visitor, followed by visitEnd.
*/
public AnnotationVisitor visitAnnotationDefault() {
if (mv != null) {
@@ -129,8 +146,8 @@ public AnnotationVisitor visitAnnotationDefault() {
* 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
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
@@ -149,15 +166,15 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean
* 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
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
* 'typeRef' as a whole.
* @param descriptor the class descriptor of the annotation class.
- * @param visible true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if this visitor is not
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitTypeAnnotation(
- final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (api < Opcodes.ASM5) {
throw new UnsupportedOperationException(REQUIRES_ASM5);
}
@@ -177,8 +194,8 @@ public AnnotationVisitor visitTypeAnnotation(
* 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
+ * @param visible {@literal true} to define the number of method parameters that can have
+ * annotations visible at runtime, {@literal false} to define the number of method parameters
* that can have annotations invisible at runtime.
*/
public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
@@ -197,12 +214,12 @@ public void visitAnnotableParameterCount(final int parameterCount, final boolean
* 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
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitParameterAnnotation(
- final int parameter, final String descriptor, final boolean visible) {
+ final int parameter, final String descriptor, final boolean visible) {
if (mv != null) {
return mv.visitParameterAnnotation(parameter, descriptor, visible);
}
@@ -248,15 +265,15 @@ public void visitCode() {
*
{@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
+ * previous frame and with single value on the stack ( numStack is 1 and
* stack[0] contains value for the type of the stack item).
*
{@link Opcodes#F_APPEND} representing frame with current locals are the same as the
* locals in the previous frame, except that additional locals are defined (
- * nLocal is 1, 2 or 3 and local elements contains values
+ * numLocal is 1, 2 or 3 and local elements contains values
* representing added types).
*
{@link Opcodes#F_CHOP} representing frame with current locals are the same as the
* locals in the previous frame, except that the last 1-3 locals are absent and with
- * the empty stack (nLocals is 1, 2 or 3).
+ * the empty stack (numLocal is 1, 2 or 3).
*
{@link Opcodes#F_FULL} representing complete frame data.
*
*
@@ -269,15 +286,17 @@ public void visitCode() {
* @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 numLocal the number of local variables in the visited frame. Long and double values
+ * count for one variable.
* @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.
+ * Reference types are represented by String objects (representing internal names, see {@link
+ * Type#getInternalName()}), and uninitialized types by Label objects (this label designates
+ * the NEW instruction that created this uninitialized value).
+ * @param numStack the number of operand stack elements in the visited frame. Long and double
+ * values count for one stack element.
* @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
@@ -285,13 +304,13 @@ public void visitCode() {
* is silently ignored).
*/
public void visitFrame(
- final int type,
- final int nLocal,
- final Object[] local,
- final int nStack,
- final Object[] stack) {
+ final int type,
+ final int numLocal,
+ final Object[] local,
+ final int numStack,
+ final Object[] stack) {
if (mv != null) {
- mv.visitFrame(type, nLocal, local, nStack, stack);
+ mv.visitFrame(type, numLocal, local, numStack, stack);
}
}
@@ -345,18 +364,18 @@ public void visitIntInsn(final int opcode, final int operand) {
*
* @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.
+ * @param varIndex 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) {
+ public void visitVarInsn(final int opcode, final int varIndex) {
if (mv != null) {
- mv.visitVarInsn(opcode, var);
+ mv.visitVarInsn(opcode, varIndex);
}
}
/**
* Visits a type instruction. A type instruction is an instruction that takes the internal name of
- * a class as parameter.
+ * a class as parameter (see {@link Type#getInternalName()}).
*
* @param opcode the opcode of the type instruction to be visited. This opcode is either NEW,
* ANEWARRAY, CHECKCAST or INSTANCEOF.
@@ -380,7 +399,7 @@ public void visitTypeInsn(final int opcode, final String type) {
* @param descriptor the field's descriptor (see {@link Type}).
*/
public void visitFieldInsn(
- final int opcode, final String owner, final String name, final String descriptor) {
+ final int opcode, final String owner, final String name, final String descriptor) {
if (mv != null) {
mv.visitFieldInsn(opcode, owner, name, descriptor);
}
@@ -395,19 +414,13 @@ public void visitFieldInsn(
* Type#getInternalName()}).
* @param name the method's name.
* @param descriptor the method's descriptor (see {@link Type}).
- * @deprecated
+ * @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
*/
@Deprecated
public void visitMethodInsn(
- final int opcode, final String owner, final String name, final String descriptor) {
- 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);
- }
+ final int opcode, final String owner, final String name, final String descriptor) {
+ int opcodeAndSource = opcode | (api < Opcodes.ASM5 ? Opcodes.SOURCE_DEPRECATED : 0);
+ visitMethodInsn(opcodeAndSource, owner, name, descriptor, opcode == Opcodes.INVOKEINTERFACE);
}
/**
@@ -422,20 +435,20 @@ public void visitMethodInsn(
* @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) {
+ final int opcode,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ if (api < Opcodes.ASM5 && (opcode & Opcodes.SOURCE_DEPRECATED) == 0) {
if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) {
- throw new IllegalArgumentException("INVOKESPECIAL/STATIC on interfaces requires ASM5");
+ throw new UnsupportedOperationException("INVOKESPECIAL/STATIC on interfaces requires ASM5");
}
visitMethodInsn(opcode, owner, name, descriptor);
return;
}
if (mv != null) {
- mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ mv.visitMethodInsn(opcode & ~Opcodes.SOURCE_MASK, owner, name, descriptor, isInterface);
}
}
@@ -451,10 +464,10 @@ public void visitMethodInsn(
* 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) {
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
if (api < Opcodes.ASM5) {
throw new UnsupportedOperationException(REQUIRES_ASM5);
}
@@ -523,7 +536,7 @@ public void visitLabel(final Label label) {
* }
* } else if (cst instanceof Handle) {
* // ...
- * } else if (cst instanceof Condy) {
+ * } else if (cst instanceof ConstantDynamic) {
* // ...
* } else {
* // throw an exception
@@ -532,18 +545,18 @@ public void visitLabel(final Label label) {
*
* @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
+ * Type} of OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is
* 49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle
* constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant
* dynamic for classes whose version is 55.
*/
public void visitLdcInsn(final Object value) {
if (api < Opcodes.ASM5
- && (value instanceof Handle
+ && (value instanceof Handle
|| (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) {
throw new UnsupportedOperationException(REQUIRES_ASM5);
}
- if (api != Opcodes.ASM7_EXPERIMENTAL && value instanceof ConstantDynamic) {
+ if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) {
throw new UnsupportedOperationException("This feature requires ASM7");
}
if (mv != null) {
@@ -554,12 +567,12 @@ public void visitLdcInsn(final Object value) {
/**
* Visits an IINC instruction.
*
- * @param var index of the local variable to be incremented.
+ * @param varIndex 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) {
+ public void visitIincInsn(final int varIndex, final int increment) {
if (mv != null) {
- mv.visitIincInsn(var, increment);
+ mv.visitIincInsn(varIndex, increment);
}
}
@@ -569,11 +582,11 @@ public void visitIincInsn(final int var, final int increment) {
* @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.
+ * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the
+ * handler block for the {@code min + i} key.
*/
public void visitTableSwitchInsn(
- final int min, final int max, final Label dflt, final Label... labels) {
+ final int min, final int max, final Label dflt, final Label... labels) {
if (mv != null) {
mv.visitTableSwitchInsn(min, max, dflt, labels);
}
@@ -583,9 +596,9 @@ public void visitTableSwitchInsn(
* 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.
+ * @param keys the values of the keys. Keys must be sorted in increasing order.
+ * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the
+ * handler block for the {@code keys[i]} key.
*/
public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
if (mv != null) {
@@ -617,15 +630,15 @@ public void visitMultiANewArrayInsn(final String descriptor, final int numDimens
* 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
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
* 'typeRef' as a whole.
* @param descriptor the class descriptor of the annotation class.
- * @param visible true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if this visitor is not
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitInsnAnnotation(
- final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (api < Opcodes.ASM5) {
throw new UnsupportedOperationException(REQUIRES_ASM5);
}
@@ -645,13 +658,14 @@ public AnnotationVisitor visitInsnAnnotation(
* @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).
+ * @param type the internal name of the type of exceptions handled by the handler (see {@link
+ * Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally"
+ * blocks).
* @throws IllegalArgumentException if one of the labels has already been visited by this visitor
* (by the {@link #visitLabel} method).
*/
public void visitTryCatchBlock(
- final Label start, final Label end, final Label handler, final String type) {
+ final Label start, final Label end, final Label handler, final String type) {
if (mv != null) {
mv.visitTryCatchBlock(start, end, handler, type);
}
@@ -665,15 +679,15 @@ public void visitTryCatchBlock(
* @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
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
* 'typeRef' as a whole.
* @param descriptor the class descriptor of the annotation class.
- * @param visible true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if this visitor is not
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitTryCatchAnnotation(
- final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (api < Opcodes.ASM5) {
throw new UnsupportedOperationException(REQUIRES_ASM5);
}
@@ -688,7 +702,7 @@ public AnnotationVisitor visitTryCatchAnnotation(
*
* @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
+ * @param signature the type signature of this local variable. May be {@literal null} if the local
* variable type does not use generic types.
* @param start the first instruction corresponding to the scope of this local variable
* (inclusive).
@@ -698,12 +712,12 @@ public AnnotationVisitor visitTryCatchAnnotation(
* 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) {
+ 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);
}
@@ -716,7 +730,7 @@ public void visitLocalVariable(
* {@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
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
* 'typeRef' as a whole.
* @param start the fist instructions corresponding to the continuous ranges that make the scope
* of this local variable (inclusive).
@@ -725,24 +739,24 @@ public void visitLocalVariable(
* @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
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
*/
public AnnotationVisitor visitLocalVariableAnnotation(
- final int typeRef,
- final TypePath typePath,
- final Label[] start,
- final Label[] end,
- final int[] index,
- final String descriptor,
- final boolean visible) {
+ 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);
+ typeRef, typePath, start, end, index, descriptor, visible);
}
return null;
}
@@ -753,7 +767,7 @@ public AnnotationVisitor visitLocalVariableAnnotation(
* @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
+ * @throws IllegalArgumentException if {@code start} has not already been visited by this visitor
* (by the {@link #visitLabel} method).
*/
public void visitLineNumber(final int line, final Label start) {
@@ -783,4 +797,4 @@ public void visitEnd() {
mv.visitEnd();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/MethodWriter.java b/src/jvm/clojure/asm/MethodWriter.java
index 2ced84a191..82431710e2 100644
--- a/src/jvm/clojure/asm/MethodWriter.java
+++ b/src/jvm/clojure/asm/MethodWriter.java
@@ -79,208 +79,208 @@ final class MethodWriter extends MethodVisitor {
* @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)
+ 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. */
@@ -299,6 +299,9 @@ final class MethodWriter extends MethodVisitor {
/** The name_index field of the method_info JVMS structure. */
private final int nameIndex;
+ /** The name of this method. */
+ private final String name;
+
/** The descriptor_index field of the method_info JVMS structure. */
private final int descriptorIndex;
@@ -319,35 +322,37 @@ final class MethodWriter extends MethodVisitor {
/**
* 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.
+ * be {@literal null}.
*/
private Handler firstHandler;
/**
* The last element in the exception handler list (used to generate the exception_table of the
* Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May
- * be null.
+ * be {@literal null}.
*/
private Handler lastHandler;
/** The line_number_table_length field of the LineNumberTable code attribute. */
private int lineNumberTableLength;
- /** The line_number_table array of the LineNumberTable code attribute, or null. */
+ /** The line_number_table array of the LineNumberTable code attribute, or {@literal null}. */
private ByteVector lineNumberTable;
/** The local_variable_table_length field of the LocalVariableTable code attribute. */
private int localVariableTableLength;
- /** The local_variable_table array of the LocalVariableTable code attribute, or null. */
+ /**
+ * The local_variable_table array of the LocalVariableTable code attribute, or {@literal null}.
+ */
private ByteVector localVariableTable;
/** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */
private int localVariableTypeTableLength;
/**
- * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or
- * null.
+ * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or {@literal
+ * null}.
*/
private ByteVector localVariableTypeTable;
@@ -359,19 +364,19 @@ final class MethodWriter extends MethodVisitor {
/**
* 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.
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation;
/**
* The last runtime invisible type annotation of the Code attribute. The previous ones can be
- * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation;
/**
* The first non standard attribute of the Code attribute. The next ones can be accessed with the
- * {@link Attribute#nextAttribute} field. May be null.
+ * {@link Attribute#nextAttribute} field. May be {@literal null}.
*
*
WARNING: this list stores the attributes in the reverse order of their visit.
* firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
@@ -385,7 +390,7 @@ final class MethodWriter extends MethodVisitor {
/** The number_of_exceptions field of the Exceptions attribute. */
private final int numberOfExceptions;
- /** The exception_index_table array of the Exceptions attribute, or null. */
+ /** The exception_index_table array of the Exceptions attribute, or {@literal null}. */
private final int[] exceptionIndexTable;
/** The signature_index field of the Signature attribute. */
@@ -393,13 +398,13 @@ final class MethodWriter extends MethodVisitor {
/**
* The last runtime visible annotation of this method. The previous ones can be accessed with the
- * {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleAnnotation;
/**
* The last runtime invisible annotation of this method. The previous ones can be accessed with
- * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleAnnotation;
@@ -408,8 +413,8 @@ final class MethodWriter extends MethodVisitor {
/**
* 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.
+ * annotation of a parameter (which can be {@literal null} - the previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}.
*/
private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations;
@@ -418,35 +423,35 @@ final class MethodWriter extends MethodVisitor {
/**
* 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.
+ * last annotation of a parameter (which can be {@literal null} - the previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}.
*/
private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations;
/**
* The last runtime visible type annotation of this method. The previous ones can be accessed with
- * the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
/**
* The last runtime invisible type annotation of this method. The previous ones can be accessed
- * with the {@link AnnotationWriter#previousAnnotation} field. May be null.
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
- /** The default_value field of the AnnotationDefault attribute, or null. */
+ /** The default_value field of the AnnotationDefault attribute, or {@literal null}. */
private ByteVector defaultValue;
/** The parameters_count field of the MethodParameters attribute. */
private int parametersCount;
- /** The 'parameters' array of the MethodParameters attribute, or null. */
+ /** The 'parameters' array of the MethodParameters attribute, or {@literal null}. */
private ByteVector parameters;
/**
* The first non standard attribute of this method. The next ones can be accessed with the {@link
- * Attribute#nextAttribute} field. May be null.
+ * Attribute#nextAttribute} field. May be {@literal null}.
*
*
WARNING: this list stores the attributes in the reverse order of their visit.
* firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
@@ -461,7 +466,8 @@ final class MethodWriter extends MethodVisitor {
/**
* 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}.
+ * #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
*/
private final int compute;
@@ -480,7 +486,7 @@ final class MethodWriter extends MethodVisitor {
/**
* 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
+ * field is {@literal null} for unreachable code. When {@link #compute} is equal to {@link
* #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays
* unchanged throughout the whole method (i.e. the whole code is seen as a single basic block;
* indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame -
@@ -526,11 +532,11 @@ final class MethodWriter extends MethodVisitor {
* 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.
+ * operand stack elements. In summary frame[0] = offset, frame[1] = numLocal, frame[2] = numStack.
+ * Local variables and operand stack entries contain abstract types, as defined in {@link Frame},
+ * but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND}, {@link
+ * Frame#UNINITIALIZED_KIND} or {@link Frame#FORWARD_UNINITIALIZED_KIND} abstract types. Long and
+ * double types use only one array entry.
*/
private int[] currentFrame;
@@ -576,22 +582,23 @@ final class MethodWriter extends MethodVisitor {
* @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 signature the method's signature. May be {@literal null}.
+ * @param exceptions the internal names of the method's exceptions. May be {@literal null}.
* @param compute indicates what must be computed (see #compute).
*/
MethodWriter(
- final SymbolTable symbolTable,
- final int access,
- final String name,
- final String descriptor,
- final String signature,
- final String[] exceptions,
- final int compute) {
- super(Opcodes.ASM6);
+ final SymbolTable symbolTable,
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions,
+ final int compute) {
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
this.nameIndex = symbolTable.addConstantUtf8(name);
+ this.name = name;
this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
this.descriptor = descriptor;
this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature);
@@ -644,42 +651,31 @@ public void visitParameter(final String name, final int access) {
@Override
public AnnotationVisitor visitAnnotationDefault() {
defaultValue = new ByteVector();
- return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null);
+ 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);
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
} else {
return lastRuntimeInvisibleAnnotation =
- new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
}
}
@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);
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (visible) {
return lastRuntimeVisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
} else {
return lastRuntimeInvisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
}
}
@@ -694,28 +690,25 @@ public void visitAnnotableParameterCount(final int parameterCount, final boolean
@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);
+ final int parameter, final String annotationDescriptor, final boolean visible) {
if (visible) {
if (lastRuntimeVisibleParameterAnnotations == null) {
lastRuntimeVisibleParameterAnnotations =
- new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ new AnnotationWriter[Type.getArgumentCount(descriptor)];
}
return lastRuntimeVisibleParameterAnnotations[parameter] =
- new AnnotationWriter(
- symbolTable, annotation, lastRuntimeVisibleParameterAnnotations[parameter]);
+ AnnotationWriter.create(
+ symbolTable, annotationDescriptor, lastRuntimeVisibleParameterAnnotations[parameter]);
} else {
if (lastRuntimeInvisibleParameterAnnotations == null) {
lastRuntimeInvisibleParameterAnnotations =
- new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ new AnnotationWriter[Type.getArgumentCount(descriptor)];
}
return lastRuntimeInvisibleParameterAnnotations[parameter] =
- new AnnotationWriter(
- symbolTable, annotation, lastRuntimeInvisibleParameterAnnotations[parameter]);
+ AnnotationWriter.create(
+ symbolTable,
+ annotationDescriptor,
+ lastRuntimeInvisibleParameterAnnotations[parameter]);
}
}
@@ -738,11 +731,11 @@ public void visitCode() {
@Override
public void visitFrame(
- final int type,
- final int nLocal,
- final Object[] local,
- final int nStack,
- final Object[] stack) {
+ final int type,
+ final int numLocal,
+ final Object[] local,
+ final int numStack,
+ final Object[] stack) {
if (compute == COMPUTE_ALL_FRAMES) {
return;
}
@@ -754,17 +747,16 @@ public void visitFrame(
// can't be set if EXPAND_ASM_INSNS is not used).
currentBasicBlock.frame = new CurrentFrame(currentBasicBlock);
currentBasicBlock.frame.setInputFrameFromDescriptor(
- symbolTable, accessFlags, descriptor, nLocal);
+ symbolTable, accessFlags, descriptor, numLocal);
currentBasicBlock.frame.accept(this);
} else {
if (type == Opcodes.F_NEW) {
currentBasicBlock.frame.setInputFrameFromApiFormat(
- symbolTable, nLocal, local, nStack, stack);
- } else {
- // 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).
+ symbolTable, numLocal, local, numStack, stack);
}
+ // If type is not F_NEW then it is F_INSERT by hypothesis, and currentBlock.frame contains
+ // the stack map frame at the current instruction, computed from the last F_NEW frame and
+ // the bytecode instructions in between (via calls to CurrentFrame#execute).
currentBasicBlock.frame.accept(this);
}
} else if (type == Opcodes.F_NEW) {
@@ -772,19 +764,22 @@ public void visitFrame(
int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
Frame implicitFirstFrame = new Frame(new Label());
implicitFirstFrame.setInputFrameFromDescriptor(
- symbolTable, accessFlags, descriptor, argumentsSize);
+ symbolTable, accessFlags, descriptor, argumentsSize);
implicitFirstFrame.accept(this);
}
- currentLocals = nLocal;
- int frameIndex = visitFrameStart(code.length, nLocal, nStack);
- for (int i = 0; i < nLocal; ++i) {
+ currentLocals = numLocal;
+ int frameIndex = visitFrameStart(code.length, numLocal, numStack);
+ for (int i = 0; i < numLocal; ++i) {
currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]);
}
- for (int i = 0; i < nStack; ++i) {
+ for (int i = 0; i < numStack; ++i) {
currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]);
}
visitFrameEnd();
} else {
+ if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
+ throw new IllegalArgumentException("Class versions V1_5 or less must use F_NEW frames.");
+ }
int offsetDelta;
if (stackMapTableEntries == null) {
stackMapTableEntries = new ByteVector();
@@ -802,26 +797,26 @@ public void visitFrame(
switch (type) {
case Opcodes.F_FULL:
- currentLocals = nLocal;
- stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal);
- for (int i = 0; i < nLocal; ++i) {
+ currentLocals = numLocal;
+ stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal);
+ for (int i = 0; i < numLocal; ++i) {
putFrameType(local[i]);
}
- stackMapTableEntries.putShort(nStack);
- for (int i = 0; i < nStack; ++i) {
+ stackMapTableEntries.putShort(numStack);
+ for (int i = 0; i < numStack; ++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) {
+ currentLocals += numLocal;
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + numLocal).putShort(offsetDelta);
+ for (int i = 0; i < numLocal; ++i) {
putFrameType(local[i]);
}
break;
case Opcodes.F_CHOP:
- currentLocals -= nLocal;
- stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - nLocal).putShort(offsetDelta);
+ currentLocals -= numLocal;
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - numLocal).putShort(offsetDelta);
break;
case Opcodes.F_SAME:
if (offsetDelta < 64) {
@@ -835,8 +830,8 @@ public void visitFrame(
stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
} else {
stackMapTableEntries
- .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
- .putShort(offsetDelta);
+ .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(offsetDelta);
}
putFrameType(stack[0]);
break;
@@ -849,13 +844,18 @@ public void visitFrame(
}
if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
- relativeStackSize = nStack;
- if (nStack > maxRelativeStackSize) {
+ relativeStackSize = numStack;
+ for (int i = 0; i < numStack; ++i) {
+ if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
+ relativeStackSize++;
+ }
+ }
+ if (relativeStackSize > maxRelativeStackSize) {
maxRelativeStackSize = relativeStackSize;
}
}
- maxStack = Math.max(maxStack, nStack);
+ maxStack = Math.max(maxStack, numStack);
maxLocals = Math.max(maxLocals, currentLocals);
}
@@ -906,26 +906,26 @@ public void visitIntInsn(final int opcode, final int operand) {
}
@Override
- public void visitVarInsn(final int opcode, final int var) {
+ public void visitVarInsn(final int opcode, final int varIndex) {
lastBytecodeOffset = code.length;
// Add the instruction to the bytecode of the method.
- if (var < 4 && opcode != Opcodes.RET) {
+ if (varIndex < 4 && opcode != Opcodes.RET) {
int optimizedOpcode;
if (opcode < Opcodes.ISTORE) {
- optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var;
+ optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex;
} else {
- optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var;
+ optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex;
}
code.putByte(optimizedOpcode);
- } else if (var >= 256) {
- code.putByte(Constants.WIDE).put12(opcode, var);
+ } else if (varIndex >= 256) {
+ code.putByte(Constants.WIDE).put12(opcode, varIndex);
} else {
- code.put11(opcode, var);
+ code.put11(opcode, varIndex);
}
// 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);
+ currentBasicBlock.frame.execute(opcode, varIndex, null, null);
} else {
if (opcode == Opcodes.RET) {
// No stack size delta.
@@ -944,12 +944,12 @@ public void visitVarInsn(final int opcode, final int var) {
if (compute != COMPUTE_NOTHING) {
int currentMaxLocals;
if (opcode == Opcodes.LLOAD
- || opcode == Opcodes.DLOAD
- || opcode == Opcodes.LSTORE
- || opcode == Opcodes.DSTORE) {
- currentMaxLocals = var + 2;
+ || opcode == Opcodes.DLOAD
+ || opcode == Opcodes.LSTORE
+ || opcode == Opcodes.DSTORE) {
+ currentMaxLocals = varIndex + 2;
} else {
- currentMaxLocals = var + 1;
+ currentMaxLocals = varIndex + 1;
}
if (currentMaxLocals > maxLocals) {
maxLocals = currentMaxLocals;
@@ -990,7 +990,7 @@ public void visitTypeInsn(final int opcode, final String type) {
@Override
public void visitFieldInsn(
- final int opcode, final String owner, final String name, final String descriptor) {
+ 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);
@@ -1027,17 +1027,17 @@ public void visitFieldInsn(
@Override
public void visitMethodInsn(
- final int opcode,
- final String owner,
- final String name,
- final String descriptor,
- final boolean isInterface) {
+ 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);
+ .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0);
} else {
code.put12(opcode, methodrefSymbol.index);
}
@@ -1064,15 +1064,15 @@ public void visitMethodInsn(
@Override
public void visitInvokeDynamicInsn(
- final String name,
- final String descriptor,
- final Handle bootstrapMethodHandle,
- final Object... bootstrapMethodArguments) {
+ 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);
+ 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.
@@ -1097,10 +1097,10 @@ public void visitJumpInsn(final int opcode, final Label label) {
// 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;
+ 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) {
+ && 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
@@ -1200,7 +1200,7 @@ public void visitJumpInsn(final int opcode, final Label label) {
@Override
public void visitLabel(final Label label) {
// Resolve the forward references to this label, if any.
- hasAsmInstructions |= label.resolve(code.data, code.length);
+ hasAsmInstructions |= label.resolve(code.data, stackMapTableEntries, 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) {
@@ -1280,9 +1280,13 @@ public void visitLdcInsn(final Object value) {
// Add the instruction to the bytecode of the method.
Symbol constantSymbol = symbolTable.addConstant(value);
int constantIndex = constantSymbol.index;
+ char firstDescriptorChar;
boolean isLongOrDouble =
- constantSymbol.tag == Symbol.CONSTANT_LONG_TAG
- || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG;
+ constantSymbol.tag == Symbol.CONSTANT_LONG_TAG
+ || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG
+ || (constantSymbol.tag == Symbol.CONSTANT_DYNAMIC_TAG
+ && ((firstDescriptorChar = constantSymbol.value.charAt(0)) == 'J'
+ || firstDescriptorChar == 'D'));
if (isLongOrDouble) {
code.put12(Constants.LDC2_W, constantIndex);
} else if (constantIndex >= 256) {
@@ -1305,21 +1309,21 @@ public void visitLdcInsn(final Object value) {
}
@Override
- public void visitIincInsn(final int var, final int increment) {
+ public void visitIincInsn(final int varIndex, 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);
+ if ((varIndex > 255) || (increment > 127) || (increment < -128)) {
+ code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment);
} else {
- code.putByte(Opcodes.IINC).put11(var, increment);
+ code.putByte(Opcodes.IINC).put11(varIndex, 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);
+ && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) {
+ currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null);
}
if (compute != COMPUTE_NOTHING) {
- int currentMaxLocals = var + 1;
+ int currentMaxLocals = varIndex + 1;
if (currentMaxLocals > maxLocals) {
maxLocals = currentMaxLocals;
}
@@ -1328,7 +1332,7 @@ public void visitIincInsn(final int var, final int increment) {
@Override
public void visitTableSwitchInsn(
- final int min, final int max, final Label dflt, final Label... labels) {
+ 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);
@@ -1391,7 +1395,7 @@ public void visitMultiANewArrayInsn(final String descriptor, final int numDimens
if (currentBasicBlock != null) {
if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
currentBasicBlock.frame.execute(
- Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable);
+ Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable);
} else {
// No need to update maxRelativeStackSize (the stack size delta is always negative).
relativeStackSize += 1 - numDimensions;
@@ -1401,30 +1405,32 @@ public void visitMultiANewArrayInsn(final String descriptor, final int numDimens
@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);
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (visible) {
return lastCodeRuntimeVisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable,
+ (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8),
+ typePath,
+ descriptor,
+ lastCodeRuntimeVisibleTypeAnnotation);
} else {
return lastCodeRuntimeInvisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable,
+ (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8),
+ typePath,
+ descriptor,
+ lastCodeRuntimeInvisibleTypeAnnotation);
}
}
@Override
public void visitTryCatchBlock(
- final Label start, final Label end, final Label handler, final String type) {
+ 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);
+ new Handler(
+ start, end, handler, type != null ? symbolTable.addConstantClass(type).index : 0, type);
if (firstHandler == null) {
firstHandler = newHandler;
} else {
@@ -1435,54 +1441,48 @@ public void visitTryCatchBlock(
@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);
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (visible) {
return lastCodeRuntimeVisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeVisibleTypeAnnotation);
} else {
return lastCodeRuntimeInvisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation);
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeInvisibleTypeAnnotation);
}
}
@Override
public void visitLocalVariable(
- final String name,
- final String descriptor,
- final String signature,
- final Label start,
- final Label end,
- final int index) {
+ 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);
+ .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);
+ .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);
@@ -1494,13 +1494,13 @@ public void visitLocalVariable(
@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) {
+ 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();
@@ -1508,19 +1508,27 @@ public AnnotationVisitor visitLocalVariableAnnotation(
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]);
+ .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);
+ new AnnotationWriter(
+ symbolTable,
+ /* useNamedValues= */ true,
+ typeAnnotation,
+ lastCodeRuntimeVisibleTypeAnnotation);
} else {
return lastCodeRuntimeInvisibleTypeAnnotation =
- new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation);
+ new AnnotationWriter(
+ symbolTable,
+ /* useNamedValues= */ true,
+ typeAnnotation,
+ lastCodeRuntimeInvisibleTypeAnnotation);
}
}
@@ -1554,7 +1562,7 @@ private void computeAllFrames() {
Handler handler = firstHandler;
while (handler != null) {
String catchTypeDescriptor =
- handler.catchTypeDescriptor == null ? "java/lang/Throwable" : handler.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();
@@ -1564,7 +1572,7 @@ private void computeAllFrames() {
Label handlerRangeEnd = handler.endPc.getCanonicalInstance();
while (handlerRangeBlock != handlerRangeEnd) {
handlerRangeBlock.outgoingEdges =
- new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges);
+ new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges);
handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
}
handler = handler.nextHandler;
@@ -1601,7 +1609,7 @@ private void computeAllFrames() {
while (outgoingEdge != null) {
Label successorBlock = outgoingEdge.successor.getCanonicalInstance();
boolean successorBlockChanged =
- basicBlock.frame.merge(symbolTable, successorBlock.frame, outgoingEdge.info);
+ 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.
@@ -1618,7 +1626,7 @@ private void computeAllFrames() {
Label basicBlock = firstBasicBlock;
while (basicBlock != null) {
if ((basicBlock.flags & (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE))
- == (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) {
@@ -1634,9 +1642,9 @@ private void computeAllFrames() {
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);
+ int frameIndex = visitFrameStart(startOffset, /* numLocal= */ 0, /* numStack= */ 1);
currentFrame[frameIndex] =
- Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable");
+ Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable");
visitFrameEnd();
// Remove this unreachable basic block from the exception handler ranges.
firstHandler = Handler.removeRange(firstHandler, basicBlock, nextBasicBlock);
@@ -1662,14 +1670,14 @@ private void computeMaxStackAndLocal() {
while (handlerRangeBlock != handlerRangeEnd) {
if ((handlerRangeBlock.flags & Label.FLAG_SUBROUTINE_CALLER) == 0) {
handlerRangeBlock.outgoingEdges =
- new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges);
+ new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges);
} else {
// If handlerRangeBlock is a JSR block, add handlerBlock after the first two outgoing
// edges to preserve the hypothesis about JSR block successors order (see
// {@link #visitJumpInsn}).
handlerRangeBlock.outgoingEdges.nextEdge.nextEdge =
- new Edge(
- Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge);
+ new Edge(
+ Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge);
}
handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
}
@@ -1688,7 +1696,7 @@ private void computeMaxStackAndLocal() {
Label basicBlock = firstBasicBlock;
while (basicBlock != null) {
if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0
- && basicBlock.subroutineId == currentSubroutine) {
+ && 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.
@@ -1747,7 +1755,7 @@ private void computeMaxStackAndLocal() {
Label successorBlock = outgoingEdge.successor;
if (successorBlock.nextListElement == null) {
successorBlock.inputStackSize =
- (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info);
+ (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info);
successorBlock.nextListElement = listOfBlocksToProcess;
listOfBlocksToProcess = successorBlock;
}
@@ -1788,7 +1796,7 @@ private void endCurrentBasicBlockWithNoSuccessor() {
if (compute == COMPUTE_ALL_FRAMES) {
Label nextBasicBlock = new Label();
nextBasicBlock.frame = new Frame(nextBasicBlock);
- nextBasicBlock.resolve(code.data, code.length);
+ nextBasicBlock.resolve(code.data, stackMapTableEntries, code.length);
lastBasicBlock.nextBasicBlock = nextBasicBlock;
lastBasicBlock = nextBasicBlock;
currentBasicBlock = null;
@@ -1806,18 +1814,18 @@ private void endCurrentBasicBlockWithNoSuccessor() {
* 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.
+ * @param numLocal the number of local variables in the frame.
+ * @param numStack the number of stack elements in the frame.
* @return the index of the next element to be written in this frame.
*/
- int visitFrameStart(final int offset, final int nLocal, final int nStack) {
- int frameLength = 3 + nLocal + nStack;
+ int visitFrameStart(final int offset, final int numLocal, final int numStack) {
+ int frameLength = 3 + numLocal + numStack;
if (currentFrame == null || currentFrame.length < frameLength) {
currentFrame = new int[frameLength];
}
currentFrame[0] = offset;
- currentFrame[1] = nLocal;
- currentFrame[2] = nStack;
+ currentFrame[1] = numLocal;
+ currentFrame[2] = numStack;
return 3;
}
@@ -1834,7 +1842,7 @@ void visitAbstractType(final int frameIndex, final int 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.
+ * which is implicit in StackMapTable). Then resets {@link #currentFrame} to {@literal null}.
*/
void visitFrameEnd() {
if (previousFrame != null) {
@@ -1850,25 +1858,25 @@ void visitFrameEnd() {
/** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */
private void putFrame() {
- final int nLocal = currentFrame[1];
- final int nStack = currentFrame[2];
+ final int numLocal = currentFrame[1];
+ final int numStack = currentFrame[2];
if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
// Generate a StackMap attribute entry, which are always uncompressed.
- stackMapTableEntries.putShort(currentFrame[0]).putShort(nLocal);
- putAbstractTypes(3, 3 + nLocal);
- stackMapTableEntries.putShort(nStack);
- putAbstractTypes(3 + nLocal, 3 + nLocal + nStack);
+ stackMapTableEntries.putShort(currentFrame[0]).putShort(numLocal);
+ putAbstractTypes(3, 3 + numLocal);
+ stackMapTableEntries.putShort(numStack);
+ putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
return;
}
final int offsetDelta =
- stackMapTableNumberOfEntries == 0
- ? currentFrame[0]
- : currentFrame[0] - previousFrame[0] - 1;
- final int previousNlocal = previousFrame[1];
- final int nLocalDelta = nLocal - previousNlocal;
+ stackMapTableNumberOfEntries == 0
+ ? currentFrame[0]
+ : currentFrame[0] - previousFrame[0] - 1;
+ final int previousNumlocal = previousFrame[1];
+ final int numLocalDelta = numLocal - previousNumlocal;
int type = Frame.FULL_FRAME;
- if (nStack == 0) {
- switch (nLocalDelta) {
+ if (numStack == 0) {
+ switch (numLocalDelta) {
case -3:
case -2:
case -1:
@@ -1886,16 +1894,16 @@ private void putFrame() {
// Keep the FULL_FRAME type.
break;
}
- } else if (nLocalDelta == 0 && nStack == 1) {
+ } else if (numLocalDelta == 0 && numStack == 1) {
type =
- offsetDelta < 63
- ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME
- : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
+ offsetDelta < 63
+ ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME
+ : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
}
if (type != Frame.FULL_FRAME) {
// Verify if locals are the same as in the previous frame.
int frameIndex = 3;
- for (int i = 0; i < previousNlocal && i < nLocal; i++) {
+ for (int i = 0; i < previousNumlocal && i < numLocal; i++) {
if (currentFrame[frameIndex] != previousFrame[frameIndex]) {
type = Frame.FULL_FRAME;
break;
@@ -1909,30 +1917,35 @@ private void putFrame() {
break;
case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME:
stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
- putAbstractTypes(3 + nLocal, 4 + nLocal);
+ putAbstractTypes(3 + numLocal, 4 + numLocal);
break;
case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
stackMapTableEntries
- .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
- .putShort(offsetDelta);
- putAbstractTypes(3 + nLocal, 4 + nLocal);
+ .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(offsetDelta);
+ putAbstractTypes(3 + numLocal, 4 + numLocal);
break;
case Frame.SAME_FRAME_EXTENDED:
stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
break;
case Frame.CHOP_FRAME:
- stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta);
+ stackMapTableEntries
+ .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
+ .putShort(offsetDelta);
break;
case Frame.APPEND_FRAME:
- stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta);
- putAbstractTypes(3 + previousNlocal, 3 + nLocal);
+ stackMapTableEntries
+ .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
+ .putShort(offsetDelta);
+ putAbstractTypes(3 + previousNumlocal, 3 + numLocal);
break;
case Frame.FULL_FRAME:
default:
- stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal);
- putAbstractTypes(3, 3 + nLocal);
- stackMapTableEntries.putShort(nStack);
- putAbstractTypes(3 + nLocal, 3 + nLocal + nStack);
+ stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal);
+ putAbstractTypes(3, 3 + numLocal);
+ stackMapTableEntries.putShort(numStack);
+ putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
+ break;
}
}
@@ -1964,12 +1977,11 @@ private void putFrameType(final Object type) {
stackMapTableEntries.putByte(((Integer) type).intValue());
} else if (type instanceof String) {
stackMapTableEntries
- .putByte(Frame.ITEM_OBJECT)
- .putShort(symbolTable.addConstantClass((String) type).index);
+ .putByte(Frame.ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass((String) type).index);
} else {
- stackMapTableEntries
- .putByte(Frame.ITEM_UNINITIALIZED)
- .putShort(((Label) type).bytecodeOffset);
+ stackMapTableEntries.putByte(Frame.ITEM_UNINITIALIZED);
+ ((Label) type).put(stackMapTableEntries);
}
}
@@ -1986,14 +1998,12 @@ private void putFrameType(final Object type) {
* 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 descriptorIndex the descriptor_index field of the method_info JVMS structure from which
+ * the attributes of this method might be copied.
* @param signatureIndex the constant pool index contained in the Signature attribute of the
* method_info JVMS structure from which the attributes of this method might be copied, or 0.
* @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info
@@ -2003,20 +2013,25 @@ private void putFrameType(final Object type) {
* + 'methodInfoLength'.
*/
boolean canCopyMethodAttributes(
- final ClassReader source,
- final int methodInfoOffset,
- final int methodInfoLength,
- final boolean hasSyntheticAttribute,
- final boolean hasDeprecatedAttribute,
- final int signatureIndex,
- final int exceptionsOffset) {
+ final ClassReader source,
+ final boolean hasSyntheticAttribute,
+ final boolean hasDeprecatedAttribute,
+ final int descriptorIndex,
+ final int signatureIndex,
+ final int exceptionsOffset) {
+ // If the method descriptor has changed, with more locals than the max_locals field of the
+ // original Code attribute, if any, then the original method attributes can't be copied. A
+ // conservative check on the descriptor changes alone ensures this (being more precise is not
+ // worth the additional complexity, because these cases should be rare -- if a transform changes
+ // a method descriptor, most of the time it needs to change the method's code too).
if (source != symbolTable.getSource()
- || signatureIndex != this.signatureIndex
- || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) {
+ || descriptorIndex != this.descriptorIndex
+ || signatureIndex != this.signatureIndex
+ || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) {
return false;
}
boolean needSyntheticAttribute =
- symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0;
+ symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0;
if (hasSyntheticAttribute != needSyntheticAttribute) {
return false;
}
@@ -2033,12 +2048,23 @@ boolean canCopyMethodAttributes(
currentExceptionOffset += 2;
}
}
+ return true;
+ }
+
+ /**
+ * Sets the source from which the attributes of this method will be copied.
+ *
+ * @param methodInfoOffset the offset in 'symbolTable.getSource()' of the method_info JVMS
+ * structure from which the attributes of this method will be copied.
+ * @param methodInfoLength the length in 'symbolTable.getSource()' of the method_info JVMS
+ * structure from which the attributes of this method will be copied.
+ */
+ void setMethodAttributesSource(final int methodInfoOffset, final int methodInfoLength) {
// Don't copy the attributes yet, instead store their location in the source class reader so
// they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes
// of the method_info JVMS structure.
this.sourceOffset = methodInfoOffset + 6;
this.sourceLength = methodInfoLength - 6;
- return true;
}
/**
@@ -2058,7 +2084,8 @@ int computeMethodInfoSize() {
// 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!");
+ throw new MethodTooLargeException(
+ symbolTable.getClassName(), name, descriptor, code.length);
}
symbolTable.addConstantUtf8(Constants.CODE);
// The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
@@ -2087,74 +2114,48 @@ int computeMethodInfoSize() {
}
if (lastCodeRuntimeVisibleTypeAnnotation != null) {
size +=
- lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
}
if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
size +=
- lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
}
if (firstCodeAttribute != null) {
size +=
- firstCodeAttribute.computeAttributesSize(
- symbolTable, code.data, code.length, maxStack, maxLocals);
+ 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);
- }
+ size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex);
+ size +=
+ AnnotationWriter.computeAnnotationsSize(
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation);
if (lastRuntimeVisibleParameterAnnotations != null) {
size +=
- AnnotationWriter.computeParameterAnnotationsSize(
- Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
- lastRuntimeVisibleParameterAnnotations,
- visibleAnnotableParameterCount == 0
- ? lastRuntimeVisibleParameterAnnotations.length
- : visibleAnnotableParameterCount);
+ 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);
+ AnnotationWriter.computeParameterAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS,
+ lastRuntimeInvisibleParameterAnnotations,
+ invisibleAnnotableParameterCount == 0
+ ? lastRuntimeInvisibleParameterAnnotations.length
+ : invisibleAnnotableParameterCount);
}
if (defaultValue != null) {
symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT);
@@ -2183,7 +2184,7 @@ void putMethodInfo(final ByteVector output) {
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);
+ output.putByteArray(symbolTable.getSource().classFileBuffer, sourceOffset, sourceLength);
return;
}
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
@@ -2259,142 +2260,122 @@ void putMethodInfo(final ByteVector output) {
}
if (lastCodeRuntimeVisibleTypeAnnotation != null) {
size +=
- lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
++codeAttributeCount;
}
if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
size +=
- lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
- Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
++codeAttributeCount;
}
if (firstCodeAttribute != null) {
size +=
- firstCodeAttribute.computeAttributesSize(
- symbolTable, code.data, code.length, maxStack, maxLocals);
+ 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);
+ .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);
+ .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);
+ .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);
+ .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);
+ .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);
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
}
if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations(
- symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
}
if (firstCodeAttribute != null) {
firstCodeAttribute.putAttributes(
- symbolTable, code.data, code.length, maxStack, maxLocals, output);
+ symbolTable, code.data, code.length, maxStack, maxLocals, output);
}
}
if (numberOfExceptions > 0) {
output
- .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS))
- .putInt(2 + 2 * numberOfExceptions)
- .putShort(numberOfExceptions);
+ .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);
- }
+ Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output);
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ output);
if (lastRuntimeVisibleParameterAnnotations != null) {
AnnotationWriter.putParameterAnnotations(
- symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS),
- lastRuntimeVisibleParameterAnnotations,
- visibleAnnotableParameterCount == 0
- ? lastRuntimeVisibleParameterAnnotations.length
- : visibleAnnotableParameterCount,
- output);
+ 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);
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS),
+ lastRuntimeInvisibleParameterAnnotations,
+ invisibleAnnotableParameterCount == 0
+ ? lastRuntimeInvisibleParameterAnnotations.length
+ : invisibleAnnotableParameterCount,
+ output);
}
if (defaultValue != null) {
output
- .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT))
- .putInt(defaultValue.length)
- .putByteArray(defaultValue.data, 0, defaultValue.length);
+ .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);
+ .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);
@@ -2410,4 +2391,4 @@ final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
attributePrototypes.addAttributes(firstAttribute);
attributePrototypes.addAttributes(firstCodeAttribute);
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ModuleVisitor.java b/src/jvm/clojure/asm/ModuleVisitor.java
index 26d2e365cd..20dd86292d 100644
--- a/src/jvm/clojure/asm/ModuleVisitor.java
+++ b/src/jvm/clojure/asm/ModuleVisitor.java
@@ -29,9 +29,8 @@
/**
* 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.
+ * order: ( {@code visitMainClass} | ( {@code visitPackage} | {@code visitRequire} | {@code
+ * visitExport} | {@code visitOpen} | {@code visitUse} | {@code visitProvide} )* ) {@code visitEnd}.
*
* @author Remi Forax
* @author Eric Bruneton
@@ -39,20 +38,22 @@
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}.
+ * Opcodes#ASM6} or {@link Opcodes#ASM7}.
*/
protected final int api;
- /** The module visitor to which this visitor must delegate method calls. May be null. */
+ /**
+ * The module visitor to which this visitor must delegate method calls. May be {@literal null}.
+ */
protected ModuleVisitor mv;
/**
* Constructs a new {@link ModuleVisitor}.
*
* @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
- * or {@link Opcodes#ASM7_EXPERIMENTAL}.
+ * or {@link Opcodes#ASM7}.
*/
- public ModuleVisitor(final int api) {
+ protected ModuleVisitor(final int api) {
this(api, null);
}
@@ -60,22 +61,42 @@ public ModuleVisitor(final int api) {
* 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}.
+ * or {@link Opcodes#ASM7}.
* @param moduleVisitor the module visitor to which this visitor must delegate method calls. May
* be null.
*/
- public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
- if (api != Opcodes.ASM6 && api != Opcodes.ASM7_EXPERIMENTAL) {
- throw new IllegalArgumentException();
+ protected ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
this.api = api;
this.mv = moduleVisitor;
}
/**
- * Visit the main class of the current module.
+ * The module visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the module visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public ModuleVisitor getDelegate() {
+ return mv;
+ }
+
+ /**
+ * Visits the main class of the current module.
*
- * @param mainClass the internal name of the main class of the current module.
+ * @param mainClass the internal name of the main class of the current module (see {@link
+ * Type#getInternalName()}).
*/
public void visitMainClass(final String mainClass) {
if (mv != null) {
@@ -84,9 +105,9 @@ public void visitMainClass(final String mainClass) {
}
/**
- * Visit a package of the current module.
+ * Visits a package of the current module.
*
- * @param packaze the internal name of a package.
+ * @param packaze the internal name of a package (see {@link Type#getInternalName()}).
*/
public void visitPackage(final String packaze) {
if (mv != null) {
@@ -100,7 +121,7 @@ public void visitPackage(final String packaze) {
* @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.
+ * @param version the module version at compile time, or {@literal null}.
*/
public void visitRequire(final String module, final int access, final String version) {
if (mv != null) {
@@ -109,13 +130,13 @@ public void visitRequire(final String module, final int access, final String ver
}
/**
- * Visit an exported package of the current module.
+ * Visits an exported package of the current module.
*
- * @param packaze the internal name of the exported package.
+ * @param packaze the internal name of the exported package (see {@link Type#getInternalName()}).
* @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.
+ * classes of the exported package, or {@literal null}.
*/
public void visitExport(final String packaze, final int access, final String... modules) {
if (mv != null) {
@@ -124,13 +145,13 @@ public void visitExport(final String packaze, final int access, final String...
}
/**
- * Visit an open package of the current module.
+ * Visits an open package of the current module.
*
- * @param packaze the internal name of the opened package.
+ * @param packaze the internal name of the opened package (see {@link Type#getInternalName()}).
* @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.
+ * reflection to the classes of the open package, or {@literal null}.
*/
public void visitOpen(final String packaze, final int access, final String... modules) {
if (mv != null) {
@@ -139,10 +160,10 @@ public void visitOpen(final String packaze, final int access, final String... mo
}
/**
- * Visit a service used by the current module. The name must be the internal name of an interface
+ * Visits 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.
+ * @param service the internal name of the service (see {@link Type#getInternalName()}).
*/
public void visitUse(final String service) {
if (mv != null) {
@@ -151,11 +172,11 @@ public void visitUse(final String service) {
}
/**
- * Visit an implementation of a service.
+ * Visits 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).
+ * @param service the internal name of the service (see {@link Type#getInternalName()}).
+ * @param providers the internal names (see {@link Type#getInternalName()}) of the implementations
+ * of the service (there is at least one provider).
*/
public void visitProvide(final String service, final String... providers) {
if (mv != null) {
@@ -172,4 +193,4 @@ public void visitEnd() {
mv.visitEnd();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/ModuleWriter.java b/src/jvm/clojure/asm/ModuleWriter.java
index 7eb7abda91..7c85c5bc5b 100644
--- a/src/jvm/clojure/asm/ModuleWriter.java
+++ b/src/jvm/clojure/asm/ModuleWriter.java
@@ -94,7 +94,7 @@ final class ModuleWriter extends ModuleVisitor {
private int mainClassIndex;
ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
- super(Opcodes.ASM6);
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.moduleNameIndex = name;
this.moduleFlags = access;
@@ -121,9 +121,9 @@ public void visitPackage(final String packaze) {
@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));
+ .putShort(symbolTable.addConstantModule(module).index)
+ .putShort(access)
+ .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version));
requiresCount++;
}
@@ -196,7 +196,7 @@ 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;
+ 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.
@@ -219,35 +219,35 @@ int computeAttributesSize() {
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;
+ 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);
+ .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);
+ .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);
+ .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS))
+ .putInt(2)
+ .putShort(mainClassIndex);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Opcodes.java b/src/jvm/clojure/asm/Opcodes.java
index 5694eb3aa0..92f3616892 100644
--- a/src/jvm/clojure/asm/Opcodes.java
+++ b/src/jvm/clojure/asm/Opcodes.java
@@ -38,6 +38,7 @@
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
+// DontCheck(InterfaceIsType): can't be fixed (for backward binary compatibility).
public interface Opcodes {
// ASM API versions.
@@ -45,17 +46,224 @@ public interface Opcodes {
int ASM4 = 4 << 16 | 0 << 8;
int ASM5 = 5 << 16 | 0 << 8;
int ASM6 = 6 << 16 | 0 << 8;
+ int ASM7 = 7 << 16 | 0 << 8;
+ int ASM8 = 8 << 16 | 0 << 8;
+ int ASM9 = 9 << 16 | 0 << 8;
/**
- * Experimental, use at your own risk. This field will be renamed when it becomes stable, this
- * will break existing code using it.
+ * Experimental, use at your own risk. This field will be renamed when it becomes stable, this
+ * will break existing code using it. Only code compiled with --enable-preview can use this.
*
* @deprecated This API is experimental.
*/
- @Deprecated int ASM7_EXPERIMENTAL = 1 << 24 | 7 << 16 | 0 << 8;
+ @Deprecated int ASM10_EXPERIMENTAL = 1 << 24 | 10 << 16 | 0 << 8;
- // Java ClassFile versions (the minor version is stored in the 16 most
- // significant bits, and the
+ /*
+ * Internal flags used to redirect calls to deprecated methods. For instance, if a visitOldStuff
+ * method in API_OLD is deprecated and replaced with visitNewStuff in API_NEW, then the
+ * redirection should be done as follows:
+ *
+ *
+ * public class StuffVisitor {
+ * ...
+ *
+ * @Deprecated public void visitOldStuff(int arg, ...) {
+ * // SOURCE_DEPRECATED means "a call from a deprecated method using the old 'api' value".
+ * visitNewStuf(arg | (api < API_NEW ? SOURCE_DEPRECATED : 0), ...);
+ * }
+ *
+ * public void visitNewStuff(int argAndSource, ...) {
+ * if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+ * visitOldStuff(argAndSource, ...);
+ * } else {
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do stuff ]
+ * }
+ * }
+ * }
+ *
+ *
+ *
If 'api' is equal to API_NEW, there are two cases:
+ *
+ *
+ *
call visitNewStuff: the redirection test is skipped and 'do stuff' is executed directly.
+ *
call visitOldSuff: the source is not set to SOURCE_DEPRECATED before calling
+ * visitNewStuff, but the redirection test is skipped anyway in visitNewStuff, which
+ * directly executes 'do stuff'.
+ *
+ *
+ *
If 'api' is equal to API_OLD, there are two cases:
+ *
+ *
+ *
call visitOldSuff: the source is set to SOURCE_DEPRECATED before calling visitNewStuff.
+ * Because of this visitNewStuff does not redirect back to visitOldStuff, and instead
+ * executes 'do stuff'.
+ *
call visitNewStuff: the call is redirected to visitOldStuff because the source is 0.
+ * visitOldStuff now sets the source to SOURCE_DEPRECATED and calls visitNewStuff back. This
+ * time visitNewStuff does not redirect the call, and instead executes 'do stuff'.
+ *
+ *
+ *
User subclasses
+ *
+ *
If a user subclass overrides one of these methods, there are only two cases: either 'api' is
+ * API_OLD and visitOldStuff is overridden (and visitNewStuff is not), or 'api' is API_NEW or
+ * more, and visitNewStuff is overridden (and visitOldStuff is not). Any other case is a user
+ * programming error.
+ *
+ *
If 'api' is equal to API_NEW, the class hierarchy is equivalent to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+ * public void visitNewStuff(int arg, ...) { [ do stuff ] }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int arg, ...) {
+ * super.visitNewStuff(int arg, ...); // optional
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ *
It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff' and 'do
+ * user stuff' will be executed, in this order.
+ *
+ *
If 'api' is equal to API_OLD, the class hierarchy is equivalent to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) {
+ * visitNewStuff(arg | SOURCE_DEPRECATED, ...);
+ * }
+ * public void visitNewStuff(int argAndSource...) {
+ * if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+ * visitOldStuff(argAndSource, ...);
+ * } else {
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do stuff ]
+ * }
+ * }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitOldStuff(int arg, ...) {
+ * super.visitOldStuff(int arg, ...); // optional
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ *
and there are two cases:
+ *
+ *
+ *
call visitOldStuff: in the call to super.visitOldStuff, the source is set to
+ * SOURCE_DEPRECATED and visitNewStuff is called. Here 'do stuff' is run because the source
+ * was previously set to SOURCE_DEPRECATED, and execution eventually returns to
+ * UserStuffVisitor.visitOldStuff, where 'do user stuff' is run.
+ *
call visitNewStuff: the call is redirected to UserStuffVisitor.visitOldStuff because the
+ * source is 0. Execution continues as in the previous case, resulting in 'do stuff' and 'do
+ * user stuff' being executed, in this order.
+ *
+ *
+ *
ASM subclasses
+ *
+ *
In ASM packages, subclasses of StuffVisitor can typically be sub classed again by the user,
+ * and can be used with API_OLD or API_NEW. Because of this, if such a subclass must override
+ * visitNewStuff, it must do so in the following way (and must not override visitOldStuff):
+ *
+ *
+ * public class AsmStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int argAndSource, ...) {
+ * if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+ * super.visitNewStuff(argAndSource, ...);
+ * return;
+ * }
+ * super.visitNewStuff(argAndSource, ...); // optional
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do other stuff ]
+ * }
+ * }
+ *
+ *
+ *
If a user class extends this with 'api' equal to API_NEW, the class hierarchy is equivalent
+ * to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+ * public void visitNewStuff(int arg, ...) { [ do stuff ] }
+ * }
+ * public class AsmStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int arg, ...) {
+ * super.visitNewStuff(arg, ...);
+ * [ do other stuff ]
+ * }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int arg, ...) {
+ * super.visitNewStuff(int arg, ...);
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ *
It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do
+ * other stuff' and 'do user stuff' will be executed, in this order. If, on the other hand, a user
+ * class extends AsmStuffVisitor with 'api' equal to API_OLD, the class hierarchy is equivalent to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) {
+ * visitNewStuf(arg | SOURCE_DEPRECATED, ...);
+ * }
+ * public void visitNewStuff(int argAndSource, ...) {
+ * if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+ * visitOldStuff(argAndSource, ...);
+ * } else {
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do stuff ]
+ * }
+ * }
+ * }
+ * public class AsmStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int argAndSource, ...) {
+ * if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+ * super.visitNewStuff(argAndSource, ...);
+ * return;
+ * }
+ * super.visitNewStuff(argAndSource, ...); // optional
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do other stuff ]
+ * }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitOldStuff(int arg, ...) {
+ * super.visitOldStuff(arg, ...);
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ *
and, here again, whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do other
+ * stuff' and 'do user stuff' will be executed, in this order (exercise left to the reader).
+ *
+ *
Notes
+ *
+ *
+ *
the SOURCE_DEPRECATED flag is set only if 'api' is API_OLD, just before calling
+ * visitNewStuff. By hypothesis, this method is not overridden by the user. Therefore, user
+ * classes can never see this flag. Only ASM subclasses must take care of extracting the
+ * actual argument value by clearing the source flags.
+ *
because the SOURCE_DEPRECATED flag is immediately cleared in the caller, the caller can
+ * call visitOldStuff or visitNewStuff (in 'do stuff' and 'do user stuff') on a delegate
+ * visitor without any risks (breaking the redirection logic, "leaking" the flag, etc).
+ *
all the scenarios discussed above are unit tested in MethodVisitorTest.
+ *
+ */
+
+ int SOURCE_DEPRECATED = 0x100;
+ int SOURCE_MASK = SOURCE_DEPRECATED;
+
+ // Java ClassFile versions (the minor version is stored in the 16 most significant bits, and the
// major version in the 16 least significant bits).
int V1_1 = 3 << 16 | 45;
@@ -69,16 +277,29 @@ public interface Opcodes {
int V9 = 0 << 16 | 53;
int V10 = 0 << 16 | 54;
int V11 = 0 << 16 | 55;
+ int V12 = 0 << 16 | 56;
+ int V13 = 0 << 16 | 57;
+ int V14 = 0 << 16 | 58;
+ int V15 = 0 << 16 | 59;
+ int V16 = 0 << 16 | 60;
+ int V17 = 0 << 16 | 61;
+ int V18 = 0 << 16 | 62;
+ int V19 = 0 << 16 | 63;
+ int V20 = 0 << 16 | 64;
+ int V21 = 0 << 16 | 65;
+ int V22 = 0 << 16 | 66;
+ int V23 = 0 << 16 | 67;
+ int V24 = 0 << 16 | 68;
+ int V25 = 0 << 16 | 69;
+ int V26 = 0 << 16 | 70;
/**
* 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.
+ *
{@code version & V_PREVIEW == V_PREVIEW} tests if a version is flagged with {@code
+ * V_PREVIEW}.
*/
- @Deprecated int V_PREVIEW_EXPERIMENTAL = 0xFFFF0000;
+ int V_PREVIEW = 0xFFFF0000;
// Access flags values, defined in
// - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1
@@ -107,7 +328,7 @@ public interface Opcodes {
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_MANDATED = 0x8000; // field, method, parameter, module, module *
int ACC_MODULE = 0x8000; // class
// ASM specific access flags.
@@ -115,6 +336,7 @@ public interface Opcodes {
// access flags, and also to make sure that these flags are automatically filtered out when
// written in class files (because access flags are stored using 16 bits only).
+ int ACC_RECORD = 0x10000; // class
int ACC_DEPRECATED = 0x20000; // class, field, method
// Possible values for the type operand of the NEWARRAY instruction.
@@ -344,4 +566,4 @@ public interface Opcodes {
int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn
int IFNULL = 198; // visitJumpInsn
int IFNONNULL = 199; // -
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/RecordComponentVisitor.java b/src/jvm/clojure/asm/RecordComponentVisitor.java
new file mode 100644
index 0000000000..4527ec0688
--- /dev/null
+++ b/src/jvm/clojure/asm/RecordComponentVisitor.java
@@ -0,0 +1,153 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+/**
+ * A visitor to visit a record component. The methods of this class must be called in the following
+ * order: ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code
+ * visitEnd}.
+ *
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+public abstract class RecordComponentVisitor {
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM8} or {@link Opcodes#ASM9}.
+ */
+ protected final int api;
+
+ /**
+ * The record visitor to which this visitor must delegate method calls. May be {@literal null}.
+ */
+ protected RecordComponentVisitor delegate;
+
+ /**
+ * Constructs a new {@link RecordComponentVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM8}
+ * or {@link Opcodes#ASM9}.
+ */
+ protected RecordComponentVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link RecordComponentVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be {@link Opcodes#ASM8}.
+ * @param recordComponentVisitor the record component visitor to which this visitor must delegate
+ * method calls. May be null.
+ */
+ protected RecordComponentVisitor(
+ final int api, final RecordComponentVisitor recordComponentVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
+ }
+ this.api = api;
+ this.delegate = recordComponentVisitor;
+ }
+
+ /**
+ * The record visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the record visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public RecordComponentVisitor getDelegate() {
+ return delegate;
+ }
+
+ /**
+ * Visits an annotation of the record component.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (delegate != null) {
+ return delegate.visitAnnotation(descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the record component signature.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link
+ * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (delegate != null) {
+ return delegate.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of the record component.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (delegate != null) {
+ delegate.visitAttribute(attribute);
+ }
+ }
+
+ /**
+ * Visits the end of the record component. This method, which is the last one to be called, is
+ * used to inform the visitor that everything have been visited.
+ */
+ public void visitEnd() {
+ if (delegate != null) {
+ delegate.visitEnd();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/RecordComponentWriter.java b/src/jvm/clojure/asm/RecordComponentWriter.java
new file mode 100644
index 0000000000..ff08e018d6
--- /dev/null
+++ b/src/jvm/clojure/asm/RecordComponentWriter.java
@@ -0,0 +1,225 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package clojure.asm;
+
+final class RecordComponentWriter extends RecordComponentVisitor {
+ /** Where the constants used in this RecordComponentWriter must be stored. */
+ private final SymbolTable symbolTable;
+
+ // Note: fields are ordered as in the record_component_info structure, and those related to
+ // attributes are ordered as in Section 4.7 of the JVMS.
+
+ /** The name_index field of the Record attribute. */
+ private final int nameIndex;
+
+ /** The descriptor_index field of the Record attribute. */
+ private final int descriptorIndex;
+
+ /**
+ * The signature_index field of the Signature attribute of this record component, or 0 if there is
+ * no Signature attribute.
+ */
+ private int signatureIndex;
+
+ /**
+ * The last runtime visible annotation of this record component. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
+
+ /**
+ * The last runtime invisible annotation of this record component. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
+
+ /**
+ * The last runtime visible type annotation of this record component. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of this record component. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /**
+ * The first non standard attribute of this record component. The next ones can be accessed with
+ * the {@link Attribute#nextAttribute} field. May be {@literal null}.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute(Attribute)}.
+ * The {@link #putRecordComponentInfo(ByteVector)} method writes the attributes in the order
+ * defined by this list, i.e. in the reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
+
+ /**
+ * Constructs a new {@link RecordComponentWriter}.
+ *
+ * @param symbolTable where the constants used in this RecordComponentWriter must be stored.
+ * @param name the record component name.
+ * @param descriptor the record component descriptor (see {@link Type}).
+ * @param signature the record component signature. May be {@literal null}.
+ */
+ RecordComponentWriter(
+ final SymbolTable symbolTable,
+ final String name,
+ final String descriptor,
+ final String signature) {
+ super(/* latest api = */ Opcodes.ASM9);
+ this.symbolTable = symbolTable;
+ this.nameIndex = symbolTable.addConstantUtf8(name);
+ this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
+ if (signature != null) {
+ this.signatureIndex = symbolTable.addConstantUtf8(signature);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the FieldVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the size of the record component JVMS structure generated by this
+ * RecordComponentWriter. Also adds the names of the attributes of this record component in the
+ * constant pool.
+ *
+ * @return the size in bytes of the record_component_info of the Record attribute.
+ */
+ int computeRecordComponentInfoSize() {
+ // name_index, descriptor_index and attributes_count fields use 6 bytes.
+ int size = 6;
+ size += Attribute.computeAttributesSize(symbolTable, 0, signatureIndex);
+ size +=
+ AnnotationWriter.computeAnnotationsSize(
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation);
+ if (firstAttribute != null) {
+ size += firstAttribute.computeAttributesSize(symbolTable);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the content of the record component generated by this RecordComponentWriter into the given
+ * ByteVector.
+ *
+ * @param output where the record_component_info structure must be put.
+ */
+ void putRecordComponentInfo(final ByteVector output) {
+ output.putShort(nameIndex).putShort(descriptorIndex);
+ // Compute and put the attributes_count field.
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributesCount = 0;
+ if (signatureIndex != 0) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (firstAttribute != null) {
+ attributesCount += firstAttribute.getAttributeCount();
+ }
+ output.putShort(attributesCount);
+ Attribute.putAttributes(symbolTable, 0, signatureIndex, output);
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ output);
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, output);
+ }
+ }
+
+ /**
+ * Collects the attributes of this record component into the given set of attribute prototypes.
+ *
+ * @param attributePrototypes a set of attribute prototypes.
+ */
+ final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
+ attributePrototypes.addAttributes(firstAttribute);
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Symbol.java b/src/jvm/clojure/asm/Symbol.java
index aa56a4d022..8619a7fdeb 100644
--- a/src/jvm/clojure/asm/Symbol.java
+++ b/src/jvm/clojure/asm/Symbol.java
@@ -103,12 +103,25 @@ abstract class Symbol {
static final int TYPE_TAG = 128;
/**
- * The tag value of an {@link Frame#ITEM_UNINITIALIZED} type entry in the type table of a class.
+ * The tag value of an uninitialized type entry in the type table of a class. This type is used
+ * for the normal case where the NEW instruction is before the <init> constructor call (in
+ * bytecode offset order), i.e. when the label of the NEW instruction is resolved when the
+ * constructor call is visited. If the NEW instruction is after the constructor call, use the
+ * {@link #FORWARD_UNINITIALIZED_TYPE_TAG} tag value instead.
*/
static final int UNINITIALIZED_TYPE_TAG = 129;
+ /**
+ * The tag value of an uninitialized type entry in the type table of a class. This type is used
+ * for the unusual case where the NEW instruction is after the <init> constructor call (in
+ * bytecode offset order), i.e. when the label of the NEW instruction is not resolved when the
+ * constructor call is visited. If the NEW instruction is before the constructor call, use the
+ * {@link #UNINITIALIZED_TYPE_TAG} tag value instead.
+ */
+ static final int FORWARD_UNINITIALIZED_TYPE_TAG = 130;
+
/** The tag value of a merged type entry in the (ASM specific) type table of a class. */
- static final int MERGED_TYPE_TAG = 130;
+ static final int MERGED_TYPE_TAG = 131;
// Instance fields.
@@ -151,9 +164,9 @@ abstract class Symbol {
* #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.
+ *
an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG}, {@link
+ * #UNINITIALIZED_TYPE_TAG} and {@link #FORWARD_UNINITIALIZED_TYPE_TAG} symbols,
+ *
{@literal null} for the other types of symbol.
*
*/
final String value;
@@ -165,13 +178,18 @@ abstract class Symbol {
*
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,
+ * #CONSTANT_METHOD_HANDLE_TAG} symbols (or this value left shifted by 8 bits for
+ * reference_kind values larger than or equal to H_INVOKEVIRTUAL and if the method owner is
+ * an interface),
*
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 index of the {@link Label} (in the {@link SymbolTable#labelTable} table) of the NEW
+ * instruction that created an {@link Frame#ITEM_UNINITIALIZED} type for {@link
+ * #FORWARD_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.
@@ -205,18 +223,19 @@ abstract class Symbol {
* @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 owner The internal name of the symbol's owner class. Maybe {@literal null}.
+ * @param name The name of the symbol's corresponding class field or method. Maybe {@literal
+ * null}.
+ * @param value The string value of this symbol. Maybe {@literal null}.
* @param data The numeric value of this symbol.
*/
Symbol(
- final int index,
- final int tag,
- final String owner,
- final String name,
- final String value,
- final long data) {
+ 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;
@@ -226,6 +245,8 @@ abstract class Symbol {
}
/**
+ * Returns the result {@link Type#getArgumentsAndReturnSizes} on {@link #value}.
+ *
* @return the result {@link Type#getArgumentsAndReturnSizes} on {@link #value} (memoized in
* {@link #info} for efficiency). This should only be used for {@link
* #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link
@@ -237,4 +258,4 @@ int getArgumentsAndReturnSizes() {
}
return info;
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/SymbolTable.java b/src/jvm/clojure/asm/SymbolTable.java
index 30caf77af8..c99e45c6df 100644
--- a/src/jvm/clojure/asm/SymbolTable.java
+++ b/src/jvm/clojure/asm/SymbolTable.java
@@ -31,66 +31,14 @@
* The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type
* table entries of a class.
*
+ * @author Eric Bruneton
* @see JVMS
* 4.4
* @see JVMS
* 4.7.23
- * @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
@@ -99,7 +47,7 @@ private static class Entry extends Symbol {
final ClassWriter classWriter;
/**
- * The ClassReader from which this SymbolTable was constructed, or null if it was
+ * The ClassReader from which this SymbolTable was constructed, or {@literal null} if it was
* constructed from scratch.
*/
private final ClassReader sourceClassReader;
@@ -160,11 +108,35 @@ private static class Entry extends Symbol {
* 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).
+ * Symbol#TYPE_TAG}, {@link Symbol#UNINITIALIZED_TYPE_TAG},{@link
+ * Symbol#FORWARD_UNINITIALIZED_TYPE_TAG} and {@link Symbol#MERGED_TYPE_TAG} entries. The type
+ * symbol at index {@code i} has its {@link Symbol#index} equal to {@code i} (and vice versa).
*/
private Entry[] typeTable;
+ /**
+ * The actual number of {@link LabelEntry} in {@link #labelTable}. These elements are stored from
+ * index 0 to labelCount (excluded). The other array entries are empty. These label entries are
+ * also stored in the {@link #labelEntries} hash set.
+ */
+ private int labelCount;
+
+ /**
+ * The labels corresponding to the "forward uninitialized" types in the ASM specific {@link
+ * typeTable} (see {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}). The label entry at index {@code
+ * i} has its {@link LabelEntry#index} equal to {@code i} (and vice versa).
+ */
+ private LabelEntry[] labelTable;
+
+ /**
+ * A hash set of all the {@link LabelEntry} elements in the {@link #labelTable}. Each {@link
+ * LabelEntry} 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 LabelEntry#next} field. The {@link #getOrAddLabelEntry(Label)} method ensures that this
+ * table does not contain duplicated entries.
+ */
+ private LabelEntry[] labelEntries;
+
/**
* Constructs a new, empty SymbolTable for the given ClassWriter.
*
@@ -191,7 +163,7 @@ private static class Entry extends Symbol {
this.sourceClassReader = classReader;
// Copy the constant pool binary content.
- byte[] inputBytes = classReader.b;
+ byte[] inputBytes = classReader.classFileBuffer;
int constantPoolOffset = classReader.getItem(1) - 1;
int constantPoolLength = classReader.header - constantPoolOffset;
constantPoolCount = classReader.getItemCount();
@@ -203,6 +175,7 @@ private static class Entry extends Symbol {
// method calls below), and to account for bootstrap method entries.
entries = new Entry[constantPoolCount * 2];
char[] charBuffer = new char[classReader.getMaxStringLength()];
+ boolean hasBootstrapMethods = false;
int itemIndex = 1;
while (itemIndex < constantPoolCount) {
int itemOffset = classReader.getItem(itemIndex);
@@ -213,53 +186,56 @@ private static class Entry extends Symbol {
case Symbol.CONSTANT_METHODREF_TAG:
case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
nameAndTypeItemOffset =
- classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
addConstantMemberReference(
- itemIndex,
- itemTag,
- classReader.readClass(itemOffset, charBuffer),
- classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
- classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
+ 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));
+ addConstantIntegerOrFloat(itemIndex, itemTag, classReader.readInt(itemOffset));
break;
case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
addConstantNameAndType(
- itemIndex,
- classReader.readUTF8(itemOffset, charBuffer),
- classReader.readUTF8(itemOffset + 2, charBuffer));
+ 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));
+ addConstantLongOrDouble(itemIndex, itemTag, classReader.readLong(itemOffset));
break;
case Symbol.CONSTANT_UTF8_TAG:
- addConstantUtf8(itemIndex, classReader.readUTF(itemIndex, charBuffer));
+ addConstantUtf8(itemIndex, classReader.readUtf(itemIndex, charBuffer));
break;
case Symbol.CONSTANT_METHOD_HANDLE_TAG:
int memberRefItemOffset =
- classReader.getItem(classReader.readUnsignedShort(itemOffset + 1));
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 1));
nameAndTypeItemOffset =
- classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2));
+ classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2));
addConstantMethodHandle(
- itemIndex,
- classReader.readByte(itemOffset),
- classReader.readClass(memberRefItemOffset, charBuffer),
- classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
- classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
+ itemIndex,
+ classReader.readByte(itemOffset),
+ classReader.readClass(memberRefItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer),
+ classReader.readByte(memberRefItemOffset - 1)
+ == Symbol.CONSTANT_INTERFACE_METHODREF_TAG);
break;
case Symbol.CONSTANT_DYNAMIC_TAG:
case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
+ hasBootstrapMethods = true;
nameAndTypeItemOffset =
- classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
addConstantDynamicOrInvokeDynamicReference(
- itemTag,
- itemIndex,
- classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
- classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer),
- classReader.readUnsignedShort(itemOffset));
+ 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:
@@ -267,16 +243,32 @@ private static class Entry extends Symbol {
case Symbol.CONSTANT_MODULE_TAG:
case Symbol.CONSTANT_PACKAGE_TAG:
addConstantUtf8Reference(
- itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer));
+ itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer));
break;
default:
throw new IllegalArgumentException();
}
itemIndex +=
- (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1;
+ (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1;
+ }
+
+ // Copy the BootstrapMethods, if any.
+ if (hasBootstrapMethods) {
+ copyBootstrapMethods(classReader, charBuffer);
}
+ }
- // Copy the BootstrapMethods 'bootstrap_methods' array binary content, if any.
+ /**
+ * Read the BootstrapMethods 'bootstrap_methods' array binary content and add them as entries of
+ * the SymbolTable.
+ *
+ * @param classReader the ClassReader whose bootstrap methods must be copied to initialize the
+ * SymbolTable.
+ * @param charBuffer a buffer used to read strings in the constant pool.
+ */
+ private void copyBootstrapMethods(final ClassReader classReader, final char[] charBuffer) {
+ // Find attributOffset of the 'bootstrap_methods' array.
+ byte[] inputBytes = classReader.classFileBuffer;
int currentAttributeOffset = classReader.getFirstAttributeOffset();
for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer);
@@ -313,19 +305,29 @@ private static class Entry extends Symbol {
}
/**
- * @return the ClassReader from which this SymbolTable was constructed, or null if it was
- * constructed from scratch.
+ * Returns the ClassReader from which this SymbolTable was constructed.
+ *
+ * @return the ClassReader from which this SymbolTable was constructed, or {@literal null} if it
+ * was constructed from scratch.
*/
ClassReader getSource() {
return sourceClassReader;
}
- /** @return the major version of the class to which this symbol table belongs. */
+ /**
+ * Returns the major version of the class to which this symbol table belongs.
+ *
+ * @return the major version of the class to which this symbol table belongs.
+ */
int getMajorVersion() {
return majorVersion;
}
- /** @return the internal name of the class to which this symbol table belongs. */
+ /**
+ * Returns the internal name of the class to which this symbol table belongs.
+ *
+ * @return the internal name of the class to which this symbol table belongs.
+ */
String getClassName() {
return className;
}
@@ -344,12 +346,20 @@ int setMajorVersionAndClassName(final int majorVersion, final String className)
return addConstantClass(className).index;
}
- /** @return the number of items in this symbol table's constant_pool array (plus 1). */
+ /**
+ * Returns the number of items in this symbol table's constant_pool array (plus 1).
+ *
+ * @return the number of items in this symbol table's constant_pool array (plus 1).
+ */
int getConstantPoolCount() {
return constantPoolCount;
}
- /** @return the length in bytes of this symbol table's constant_pool array. */
+ /**
+ * Returns the length in bytes of this symbol table's constant_pool array.
+ *
+ * @return the length in bytes of this symbol table's constant_pool array.
+ */
int getConstantPoolLength() {
return constantPool.length;
}
@@ -388,10 +398,10 @@ int computeBootstrapMethodsSize() {
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);
+ .putShort(addConstantUtf8(Constants.BOOTSTRAP_METHODS))
+ .putInt(bootstrapMethods.length + 2)
+ .putShort(bootstrapMethodCount)
+ .putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
}
}
@@ -400,6 +410,8 @@ void putBootstrapMethods(final ByteVector output) {
// -----------------------------------------------------------------------------------------------
/**
+ * Returns the list of entries which can potentially have the given hash code.
+ *
* @param hashCode a {@link Entry#hashCode} value.
* @return the list of entries which can potentially have the given hash code. The list is stored
* via the {@link Entry#next} field.
@@ -499,18 +511,18 @@ Symbol addConstant(final Object value) {
} else if (value instanceof Handle) {
Handle handle = (Handle) value;
return addConstantMethodHandle(
- handle.getTag(),
- handle.getOwner(),
- handle.getName(),
- handle.getDesc(),
- handle.isInterface());
+ 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());
+ constantDynamic.getName(),
+ constantDynamic.getDescriptor(),
+ constantDynamic.getBootstrapMethod(),
+ constantDynamic.getBootstrapMethodArgumentsUnsafe());
} else {
throw new IllegalArgumentException("value " + value);
}
@@ -551,7 +563,7 @@ Symbol addConstantFieldref(final String owner, final String name, final String d
* @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) {
+ 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);
}
@@ -569,21 +581,21 @@ Symbol addConstantMethodref(
* @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) {
+ 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)) {
+ && 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));
+ tag, addConstantClass(owner).index, addConstantNameAndType(name, descriptor));
return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, 0, hashCode));
}
@@ -599,11 +611,11 @@ private Entry addConstantMemberReference(
* @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) {
+ 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)));
}
@@ -626,7 +638,7 @@ Symbol addConstantString(final String value) {
* @return a new or already existing Symbol with the given value.
*/
Symbol addConstantInteger(final int value) {
- return addConstantInteger(Symbol.CONSTANT_INTEGER_TAG, value);
+ return addConstantIntegerOrFloat(Symbol.CONSTANT_INTEGER_TAG, value);
}
/**
@@ -637,7 +649,7 @@ Symbol addConstantInteger(final int value) {
* @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));
+ return addConstantIntegerOrFloat(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value));
}
/**
@@ -648,7 +660,7 @@ Symbol addConstantFloat(final float value) {
* @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) {
+ private Symbol addConstantIntegerOrFloat(final int tag, final int value) {
int hashCode = hash(tag, value);
Entry entry = get(hashCode);
while (entry != null) {
@@ -669,7 +681,7 @@ private Symbol addConstantInteger(final int tag, final int value) {
* @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) {
+ private void addConstantIntegerOrFloat(final int index, final int tag, final int value) {
add(new Entry(index, tag, value, hash(tag, value)));
}
@@ -681,7 +693,7 @@ private void addConstantInteger(final int index, final int tag, final int value)
* @return a new or already existing Symbol with the given value.
*/
Symbol addConstantLong(final long value) {
- return addConstantLong(Symbol.CONSTANT_LONG_TAG, value);
+ return addConstantLongOrDouble(Symbol.CONSTANT_LONG_TAG, value);
}
/**
@@ -692,7 +704,7 @@ Symbol addConstantLong(final long value) {
* @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));
+ return addConstantLongOrDouble(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value));
}
/**
@@ -703,7 +715,7 @@ Symbol addConstantDouble(final double value) {
* @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) {
+ private Symbol addConstantLongOrDouble(final int tag, final long value) {
int hashCode = hash(tag, value);
Entry entry = get(hashCode);
while (entry != null) {
@@ -719,13 +731,14 @@ private Symbol addConstantLong(final int tag, final long value) {
}
/**
- * Adds a new CONSTANT_Double_info to the constant pool of this symbol table.
+ * Adds a new CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol
+ * table.
*
* @param index the constant pool index of the new Symbol.
* @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}.
* @param value a long or double.
*/
- private void addConstantLong(final int index, final int tag, final long value) {
+ private void addConstantLongOrDouble(final int index, final int tag, final long value) {
add(new Entry(index, tag, value, hash(tag, value)));
}
@@ -743,9 +756,9 @@ int addConstantNameAndType(final String name, final String descriptor) {
Entry entry = get(hashCode);
while (entry != null) {
if (entry.tag == tag
- && entry.hashCode == hashCode
- && entry.name.equals(name)
- && entry.value.equals(descriptor)) {
+ && entry.hashCode == hashCode
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
return entry.index;
}
entry = entry.next;
@@ -778,8 +791,8 @@ int addConstantUtf8(final String value) {
Entry entry = get(hashCode);
while (entry != null) {
if (entry.tag == Symbol.CONSTANT_UTF8_TAG
- && entry.hashCode == hashCode
- && entry.value.equals(value)) {
+ && entry.hashCode == hashCode
+ && entry.value.equals(value)) {
return entry.index;
}
entry = entry.next;
@@ -813,23 +826,24 @@ private void addConstantUtf8(final int index, final String value) {
* @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 referenceKind,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG;
+ final int data = getConstantMethodHandleSymbolData(referenceKind, isInterface);
// 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);
+ int hashCode = hash(tag, owner, name, descriptor, data);
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)) {
+ && entry.hashCode == hashCode
+ && entry.data == data
+ && entry.owner.equals(owner)
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
return entry;
}
entry = entry.next;
@@ -838,10 +852,9 @@ Symbol addConstantMethodHandle(
constantPool.put112(tag, referenceKind, addConstantFieldref(owner, name, descriptor).index);
} else {
constantPool.put112(
- tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index);
+ tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index);
}
- return put(
- new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode));
+ return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, data, hashCode));
}
/**
@@ -855,16 +868,36 @@ Symbol addConstantMethodHandle(
* @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.
*/
private void addConstantMethodHandle(
- final int index,
- final int referenceKind,
- final String owner,
- final String name,
- final String descriptor) {
+ final int index,
+ final int referenceKind,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
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));
+ final int data = getConstantMethodHandleSymbolData(referenceKind, isInterface);
+ int hashCode = hash(tag, owner, name, descriptor, data);
+ add(new Entry(index, tag, owner, name, descriptor, data, hashCode));
+ }
+
+ /**
+ * Returns the {@link Symbol#data} field for a CONSTANT_MethodHandle_info 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 isInterface whether owner is an interface or not.
+ */
+ private static int getConstantMethodHandleSymbolData(
+ final int referenceKind, final boolean isInterface) {
+ if (referenceKind > Opcodes.H_PUTSTATIC && isInterface) {
+ return referenceKind << 8;
+ }
+ return referenceKind;
}
/**
@@ -890,13 +923,13 @@ Symbol addConstantMethodType(final String methodDescriptor) {
* @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) {
+ 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);
+ Symbol.CONSTANT_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
}
/**
@@ -911,13 +944,13 @@ Symbol addConstantDynamic(
* @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) {
+ 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);
+ Symbol.CONSTANT_INVOKE_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
}
/**
@@ -933,23 +966,23 @@ Symbol addConstantInvokeDynamic(
* @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) {
+ 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)) {
+ && 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));
+ new Entry(
+ constantPoolCount++, tag, null, name, descriptor, bootstrapMethodIndex, hashCode));
}
/**
@@ -965,11 +998,11 @@ private Symbol addConstantDynamicOrInvokeDynamicReference(
* @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) {
+ 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));
}
@@ -1049,7 +1082,7 @@ private void addConstantUtf8Reference(final int index, final int tag, final Stri
* @return a new or already existing Symbol with the given value.
*/
Symbol addBootstrapMethod(
- final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
+ final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
ByteVector bootstrapMethodsAttribute = bootstrapMethods;
if (bootstrapMethodsAttribute == null) {
bootstrapMethodsAttribute = bootstrapMethods = new ByteVector();
@@ -1059,8 +1092,10 @@ Symbol addBootstrapMethod(
// 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);
+ int numBootstrapArguments = bootstrapMethodArguments.length;
+ int[] bootstrapMethodArgumentIndexes = new int[numBootstrapArguments];
+ for (int i = 0; i < numBootstrapArguments; i++) {
+ bootstrapMethodArgumentIndexes[i] = addConstant(bootstrapMethodArguments[i]).index;
}
// Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to
@@ -1068,17 +1103,17 @@ Symbol addBootstrapMethod(
// bootstrap method.
int bootstrapMethodOffset = bootstrapMethodsAttribute.length;
bootstrapMethodsAttribute.putShort(
- addConstantMethodHandle(
- bootstrapMethodHandle.getTag(),
- bootstrapMethodHandle.getOwner(),
- bootstrapMethodHandle.getName(),
- bootstrapMethodHandle.getDesc(),
- bootstrapMethodHandle.isInterface())
- .index);
- int numBootstrapArguments = bootstrapMethodArguments.length;
+ addConstantMethodHandle(
+ bootstrapMethodHandle.getTag(),
+ bootstrapMethodHandle.getOwner(),
+ bootstrapMethodHandle.getName(),
+ bootstrapMethodHandle.getDesc(),
+ bootstrapMethodHandle.isInterface())
+ .index);
+
bootstrapMethodsAttribute.putShort(numBootstrapArguments);
- for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
- bootstrapMethodsAttribute.putShort(addConstant(bootstrapMethodArgument).index);
+ for (int i = 0; i < numBootstrapArguments; i++) {
+ bootstrapMethodsAttribute.putShort(bootstrapMethodArgumentIndexes[i]);
}
// Compute the length and the hash code of the bootstrap method.
@@ -1131,6 +1166,8 @@ private Symbol addBootstrapMethod(final int offset, final int length, final int
// -----------------------------------------------------------------------------------------------
/**
+ * Returns the type table element whose index is given.
+ *
* @param typeIndex a type table index.
* @return the type table element whose index is given.
*/
@@ -1138,6 +1175,18 @@ Symbol getType(final int typeIndex) {
return typeTable[typeIndex];
}
+ /**
+ * Returns the label corresponding to the "forward uninitialized" type table element whose index
+ * is given.
+ *
+ * @param typeIndex the type table index of a "forward uninitialized" type table element.
+ * @return the label corresponding of the NEW instruction which created this "forward
+ * uninitialized" type.
+ */
+ Label getForwardUninitializedLabel(final int typeIndex) {
+ return labelTable[(int) typeTable[typeIndex].data].label;
+ }
+
/**
* Adds a type in the type table of this symbol table. Does nothing if the type table already
* contains a similar type.
@@ -1154,32 +1203,58 @@ int addType(final String value) {
}
entry = entry.next;
}
- return addType(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode));
+ return addTypeInternal(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode));
}
/**
- * Adds an {@link Frame#ITEM_UNINITIALIZED} type in the type table of this symbol table. Does
- * nothing if the type table already contains a similar type.
+ * Adds an 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.
+ * @param bytecodeOffset the bytecode offset of the NEW instruction that created this
+ * uninitialized type value.
+ * @return the index of a new or already existing type #@link 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)) {
+ && 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));
+ return addTypeInternal(
+ new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode));
+ }
+
+ /**
+ * Adds a "forward 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 label the label of the NEW instruction that created this uninitialized type value. If
+ * the label is resolved, use the {@link #addUninitializedType} method instead.
+ * @return the index of a new or already existing type {@link Symbol} with the given value.
+ */
+ int addForwardUninitializedType(final String value, final Label label) {
+ int labelIndex = getOrAddLabelEntry(label).index;
+ int hashCode = hash(Symbol.FORWARD_UNINITIALIZED_TYPE_TAG, value, labelIndex);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.FORWARD_UNINITIALIZED_TYPE_TAG
+ && entry.hashCode == hashCode
+ && entry.data == labelIndex
+ && entry.value.equals(value)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ return addTypeInternal(
+ new Entry(typeCount, Symbol.FORWARD_UNINITIALIZED_TYPE_TAG, value, labelIndex, hashCode));
}
/**
@@ -1194,8 +1269,10 @@ int addUninitializedType(final String value, final int bytecodeOffset) {
* 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);
+ long data =
+ typeTableIndex1 < typeTableIndex2
+ ? typeTableIndex1 | (((long) typeTableIndex2) << 32)
+ : typeTableIndex2 | (((long) typeTableIndex1) << 32);
int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2);
Entry entry = get(hashCode);
while (entry != null) {
@@ -1219,7 +1296,7 @@ int addMergedType(final int typeTableIndex1, final int typeTableIndex2) {
* @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) {
+ private int addTypeInternal(final Entry entry) {
if (typeTable == null) {
typeTable = new Entry[16];
}
@@ -1232,6 +1309,59 @@ private int addType(final Entry entry) {
return put(entry).index;
}
+ /**
+ * Returns the {@link LabelEntry} corresponding to the given label. Creates a new one if there is
+ * no such entry.
+ *
+ * @param label the {@link Label} of a NEW instruction which created an uninitialized type, in the
+ * case where this NEW instruction is after the <init> constructor call (in bytecode
+ * offset order). See {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}.
+ * @return the {@link LabelEntry} corresponding to {@code label}.
+ */
+ private LabelEntry getOrAddLabelEntry(final Label label) {
+ if (labelEntries == null) {
+ labelEntries = new LabelEntry[16];
+ labelTable = new LabelEntry[16];
+ }
+ int hashCode = System.identityHashCode(label);
+ LabelEntry labelEntry = labelEntries[hashCode % labelEntries.length];
+ while (labelEntry != null && labelEntry.label != label) {
+ labelEntry = labelEntry.next;
+ }
+ if (labelEntry != null) {
+ return labelEntry;
+ }
+
+ if (labelCount > (labelEntries.length * 3) / 4) {
+ int currentCapacity = labelEntries.length;
+ int newCapacity = currentCapacity * 2 + 1;
+ LabelEntry[] newLabelEntries = new LabelEntry[newCapacity];
+ for (int i = currentCapacity - 1; i >= 0; --i) {
+ LabelEntry currentEntry = labelEntries[i];
+ while (currentEntry != null) {
+ int newCurrentEntryIndex = System.identityHashCode(currentEntry.label) % newCapacity;
+ LabelEntry nextEntry = currentEntry.next;
+ currentEntry.next = newLabelEntries[newCurrentEntryIndex];
+ newLabelEntries[newCurrentEntryIndex] = currentEntry;
+ currentEntry = nextEntry;
+ }
+ }
+ labelEntries = newLabelEntries;
+ }
+ if (labelCount == labelTable.length) {
+ LabelEntry[] newLabelTable = new LabelEntry[2 * labelTable.length];
+ System.arraycopy(labelTable, 0, newLabelTable, 0, labelTable.length);
+ labelTable = newLabelTable;
+ }
+
+ labelEntry = new LabelEntry(labelCount, label);
+ int index = hashCode % labelEntries.length;
+ labelEntry.next = labelEntries[index];
+ labelEntries[index] = labelEntry;
+ labelTable[labelCount++] = labelEntry;
+ return labelEntry;
+ }
+
// -----------------------------------------------------------------------------------------------
// Static helper methods to compute hash codes.
// -----------------------------------------------------------------------------------------------
@@ -1257,21 +1387,99 @@ private static int hash(final int tag, final String value1, final String value2)
}
private static int hash(
- final int tag, final String value1, final String value2, final int value3) {
+ 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) {
+ 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) {
+ final int tag,
+ final String value1,
+ final String value2,
+ final String value3,
+ final int value4) {
return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4);
}
-}
+
+ /**
+ * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields
+ * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid
+ * duplicate symbols). See {@link #entries}.
+ *
+ * @author Eric Bruneton
+ */
+ private static final 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;
+ }
+ }
+
+ /**
+ * A label corresponding to a "forward uninitialized" type in the ASM specific {@link
+ * SymbolTable#typeTable} (see {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}).
+ *
+ * @author Eric Bruneton
+ */
+ private static final class LabelEntry {
+
+ /** The index of this label entry in the {@link SymbolTable#labelTable} array. */
+ final int index;
+
+ /** The value of this label entry. */
+ final Label label;
+
+ /**
+ * Another entry (and so on recursively) having the same hash code (modulo the size of {@link
+ * SymbolTable#labelEntries}) as this one.
+ */
+ LabelEntry next;
+
+ LabelEntry(final int index, final Label label) {
+ this.index = index;
+ this.label = label;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/Type.java b/src/jvm/clojure/asm/Type.java
index bcd173d097..257a73473c 100644
--- a/src/jvm/clojure/asm/Type.java
+++ b/src/jvm/clojure/asm/Type.java
@@ -37,33 +37,33 @@
* @author Eric Bruneton
* @author Chris Nokleberg
*/
-public class Type {
+public final class Type {
- /** The sort of the void type. See {@link #getSort}. */
+ /** The sort of the {@code void} type. See {@link #getSort}. */
public static final int VOID = 0;
- /** The sort of the boolean type. See {@link #getSort}. */
+ /** The sort of the {@code boolean} type. See {@link #getSort}. */
public static final int BOOLEAN = 1;
- /** The sort of the char type. See {@link #getSort}. */
+ /** The sort of the {@code char} type. See {@link #getSort}. */
public static final int CHAR = 2;
- /** The sort of the byte type. See {@link #getSort}. */
+ /** The sort of the {@code byte} type. See {@link #getSort}. */
public static final int BYTE = 3;
- /** The sort of the short type. See {@link #getSort}. */
+ /** The sort of the {@code short} type. See {@link #getSort}. */
public static final int SHORT = 4;
- /** The sort of the int type. See {@link #getSort}. */
+ /** The sort of the {@code int} type. See {@link #getSort}. */
public static final int INT = 5;
- /** The sort of the float type. See {@link #getSort}. */
+ /** The sort of the {@code float} type. See {@link #getSort}. */
public static final int FLOAT = 6;
- /** The sort of the long type. See {@link #getSort}. */
+ /** The sort of the {@code long} type. See {@link #getSort}. */
public static final int LONG = 7;
- /** The sort of the double type. See {@link #getSort}. */
+ /** The sort of the {@code double} type. See {@link #getSort}. */
public static final int DOUBLE = 8;
/** The sort of array reference types. See {@link #getSort}. */
@@ -81,34 +81,34 @@ public class Type {
/** The descriptors of the primitive types. */
private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD";
- /** The void type. */
+ /** The {@code void} type. */
public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1);
- /** The boolean type. */
+ /** The {@code boolean} type. */
public static final Type BOOLEAN_TYPE =
- new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1);
+ new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1);
- /** The char type. */
+ /** The {@code char} type. */
public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1);
- /** The byte type. */
+ /** The {@code byte} type. */
public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1);
- /** The short type. */
+ /** The {@code short} type. */
public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1);
- /** The int type. */
+ /** The {@code int} type. */
public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1);
- /** The float type. */
+ /** The {@code float} type. */
public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1);
- /** The long type. */
+ /** The {@code long} type. */
public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1);
- /** The double type. */
+ /** The {@code double} type. */
public static final Type DOUBLE_TYPE =
- new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1);
+ new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1);
// -----------------------------------------------------------------------------------------------
// Fields
@@ -146,10 +146,6 @@ public class Type {
*/
private final int valueEnd;
- // -----------------------------------------------------------------------------------------------
- // Constructors
- // -----------------------------------------------------------------------------------------------
-
/**
* Constructs a reference type.
*
@@ -157,7 +153,7 @@ public class Type {
* @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
+ * @param valueEnd the end index, exclusive, of the value of this field or method type in
* valueBuffer.
*/
private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) {
@@ -167,6 +163,10 @@ private Type(final int sort, final String valueBuffer, final int valueBegin, fin
this.valueEnd = valueEnd;
}
+ // -----------------------------------------------------------------------------------------------
+ // Methods to get Type(s) from a descriptor, a reflected Method or Constructor, other types, etc.
+ // -----------------------------------------------------------------------------------------------
+
/**
* Returns the {@link Type} corresponding to the given type descriptor.
*
@@ -174,40 +174,7 @@ private Type(final int sort, final String valueBuffer, final int valueBegin, fin
* @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));
+ return getTypeInternal(typeDescriptor, 0, typeDescriptor.length());
}
/**
@@ -264,6 +231,60 @@ public static Type getType(final Method method) {
return getType(getMethodDescriptor(method));
}
+ /**
+ * Returns the type of the elements of this array type. This method should only be used for an
+ * array type.
+ *
+ * @return Returns the type of the elements of this array type.
+ */
+ public Type getElementType() {
+ final int numDimensions = getDimensions();
+ return getTypeInternal(valueBuffer, valueBegin + numDimensions, valueEnd);
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given internal name.
+ *
+ * @param internalName an internal name (see {@link Type#getInternalName()}).
+ * @return the {@link Type} corresponding to the given internal name.
+ */
+ public static Type getObjectType(final String internalName) {
+ return new Type(
+ internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to
+ * Type.getType(methodDescriptor).
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the {@link Type} corresponding to the given method descriptor.
+ */
+ public static Type getMethodType(final String methodDescriptor) {
+ return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length());
+ }
+
+ /**
+ * Returns the method {@link Type} corresponding to the given argument and return types.
+ *
+ * @param returnType the return type of the method.
+ * @param argumentTypes the argument types of the method.
+ * @return the method {@link Type} corresponding to the given argument and return types.
+ */
+ public static Type getMethodType(final Type returnType, final Type... argumentTypes) {
+ return getType(getMethodDescriptor(returnType, argumentTypes));
+ }
+
+ /**
+ * Returns the argument types of methods of this type. This method should only be used for method
+ * types.
+ *
+ * @return the argument types of methods of this type.
+ */
+ public Type[] getArgumentTypes() {
+ return getArgumentTypes(getDescriptor());
+ }
+
/**
* Returns the {@link Type} values corresponding to the argument types of the given method
* descriptor.
@@ -274,26 +295,12 @@ public static Type getType(final Method method) {
*/
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.
- }
- }
- ++numArgumentTypes;
- }
+ int numArgumentTypes = getArgumentCount(methodDescriptor);
// 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;
+ int currentOffset = 1;
// Parse and create the argument types, one at each loop iteration.
int currentArgumentTypeIndex = 0;
while (methodDescriptor.charAt(currentOffset) != ')') {
@@ -302,12 +309,12 @@ public static Type[] getArgumentTypes(final String methodDescriptor) {
currentOffset++;
}
if (methodDescriptor.charAt(currentOffset++) == 'L') {
- while (methodDescriptor.charAt(currentOffset++) != ';') {
- // Skip the argument descriptor content.
- }
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
}
argumentTypes[currentArgumentTypeIndex++] =
- getType(methodDescriptor, currentArgumentTypeOffset, currentOffset);
+ getTypeInternal(methodDescriptor, currentArgumentTypeOffset, currentOffset);
}
return argumentTypes;
}
@@ -327,6 +334,16 @@ public static Type[] getArgumentTypes(final Method method) {
return types;
}
+ /**
+ * Returns the return type of methods of this type. This method should only be used for method
+ * types.
+ *
+ * @return the return type of methods of this type.
+ */
+ public Type getReturnType() {
+ return getReturnType(getDescriptor());
+ }
+
/**
* Returns the {@link Type} corresponding to the return type of the given method descriptor.
*
@@ -334,20 +351,8 @@ public static Type[] getArgumentTypes(final Method method) {
* @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 getType(methodDescriptor, currentOffset + 1, methodDescriptor.length());
+ return getTypeInternal(
+ methodDescriptor, getReturnTypeOffset(methodDescriptor), methodDescriptor.length());
}
/**
@@ -361,44 +366,26 @@ public static Type getReturnType(final Method method) {
}
/**
- * Computes the size of the arguments and of the return value of a method.
+ * Returns the start index of the return type of the given method descriptor.
*
* @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).
+ * @return the start index of the return type of the given method descriptor.
*/
- public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
- int argumentsSize = 1;
+ static int getReturnTypeOffset(final String methodDescriptor) {
// 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') {
+ // Skip the argument types, one at a each loop iteration.
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
currentOffset++;
- argumentsSize += 2;
- } else {
- while (methodDescriptor.charAt(currentOffset) == '[') {
- currentOffset++;
- }
- if (methodDescriptor.charAt(currentOffset++) == 'L') {
- while (methodDescriptor.charAt(currentOffset++) != ';') {
- // Skip the argument descriptor content.
- }
- }
- 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;
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
+ }
}
+ return currentOffset + 1;
}
/**
@@ -411,8 +398,8 @@ public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
* descriptorBuffer.
* @return the {@link Type} corresponding to the given type descriptor.
*/
- private static Type getType(
- final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) {
+ private static Type getTypeInternal(
+ final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) {
switch (descriptorBuffer.charAt(descriptorBegin)) {
case 'V':
return VOID_TYPE;
@@ -439,50 +426,14 @@ private static Type getType(
case '(':
return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd);
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Invalid descriptor: " + descriptorBuffer);
}
}
// -----------------------------------------------------------------------------------------------
- // Accessors
+ // Methods to get class names, internal names or descriptors.
// -----------------------------------------------------------------------------------------------
- /**
- * 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.
@@ -535,42 +486,16 @@ public String getInternalName() {
}
/**
- * 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.
+ * 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 '/'.
*
- * @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).
+ * @param clazz an object or array class.
+ * @return the internal name of the given class.
*/
- public int getArgumentsAndReturnSizes() {
- return getArgumentsAndReturnSizes(getDescriptor());
+ public static String getInternalName(final Class> clazz) {
+ return clazz.getName().replace('.', '/');
}
- // -----------------------------------------------------------------------------------------------
- // Conversion to type descriptors
- // -----------------------------------------------------------------------------------------------
-
/**
* Returns the descriptor corresponding to this type.
*
@@ -580,67 +505,12 @@ 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();
+ return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';';
} 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.
*
@@ -649,7 +519,7 @@ public static String getInternalName(final Class> clazz) {
*/
public static String getDescriptor(final Class> clazz) {
StringBuilder stringBuilder = new StringBuilder();
- appendDescriptor(stringBuilder, clazz);
+ appendDescriptor(clazz, stringBuilder);
return stringBuilder.toString();
}
@@ -663,12 +533,30 @@ 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]);
+ for (Class> parameter : parameters) {
+ appendDescriptor(parameter, stringBuilder);
}
return stringBuilder.append(")V").toString();
}
+ /**
+ * Returns the descriptor corresponding to the given argument and return types.
+ *
+ * @param returnType the return type of the method.
+ * @param argumentTypes the argument types of the method.
+ * @return the descriptor corresponding to the given argument and return types.
+ */
+ public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('(');
+ for (Type argumentType : argumentTypes) {
+ argumentType.appendDescriptor(stringBuilder);
+ }
+ stringBuilder.append(')');
+ returnType.appendDescriptor(stringBuilder);
+ return stringBuilder.toString();
+ }
+
/**
* Returns the descriptor corresponding to the given method.
*
@@ -679,21 +567,36 @@ 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]);
+ for (Class> parameter : parameters) {
+ appendDescriptor(parameter, stringBuilder);
}
stringBuilder.append(')');
- appendDescriptor(stringBuilder, method.getReturnType());
+ appendDescriptor(method.getReturnType(), stringBuilder);
return stringBuilder.toString();
}
/**
- * Appends the descriptor of the given class to the given string builder.
+ * Appends the descriptor corresponding to this type to the given string buffer.
*
* @param stringBuilder the string builder to which the descriptor must be appended.
+ */
+ private void appendDescriptor(final StringBuilder stringBuilder) {
+ if (sort == OBJECT) {
+ stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1);
+ } else if (sort == INTERNAL) {
+ stringBuilder.append('L').append(valueBuffer, valueBegin, valueEnd).append(';');
+ } else {
+ stringBuilder.append(valueBuffer, valueBegin, valueEnd);
+ }
+ }
+
+ /**
+ * Appends the descriptor of the given class to the given string builder.
+ *
* @param clazz the class whose descriptor must be computed.
+ * @param stringBuilder the string builder to which the descriptor must be appended.
*/
- private static void appendDescriptor(final StringBuilder stringBuilder, final Class> clazz) {
+ private static void appendDescriptor(final Class> clazz, final StringBuilder stringBuilder) {
Class> currentClass = clazz;
while (currentClass.isArray()) {
stringBuilder.append('[');
@@ -724,26 +627,44 @@ private static void appendDescriptor(final StringBuilder stringBuilder, final Cl
}
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(';');
+ stringBuilder.append('L').append(getInternalName(currentClass)).append(';');
}
}
// -----------------------------------------------------------------------------------------------
- // Corresponding size and opcodes
+ // Methods to get the sort, dimension, size, and opcodes corresponding to a Type or descriptor.
// -----------------------------------------------------------------------------------------------
+ /**
+ * Returns the sort of this type.
+ *
+ * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link
+ * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or
+ * {@link #METHOD}.
+ */
+ public int getSort() {
+ return sort == INTERNAL ? OBJECT : sort;
+ }
+
+ /**
+ * Returns the number of dimensions of this array type. This method should only be used for an
+ * array type.
+ *
+ * @return the number of dimensions of this array type.
+ */
+ public int getDimensions() {
+ int numDimensions = 1;
+ while (valueBuffer.charAt(valueBegin + numDimensions) == '[') {
+ numDimensions++;
+ }
+ return numDimensions;
+ }
+
/**
* Returns the size of values of this type. This method must not be used for method types.
*
- * @return the size of values of this type, i.e., 2 for long and double, 0 for
- * void and 1 otherwise.
+ * @return the size of values of this type, i.e., 2 for {@code long} and {@code double}, 0 for
+ * {@code void} and 1 otherwise.
*/
public int getSize() {
switch (sort) {
@@ -767,6 +688,99 @@ public int getSize() {
}
}
+ /**
+ * Returns the number of arguments of this method type. This method should only be used for method
+ * types.
+ *
+ * @return the number of arguments of this method type. Each argument counts for 1, even long and
+ * double ones. The implicit @literal{this} argument is not counted.
+ */
+ public int getArgumentCount() {
+ return getArgumentCount(getDescriptor());
+ }
+
+ /**
+ * Returns the number of arguments in the given method descriptor.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the number of arguments in the given method descriptor. Each argument counts for 1,
+ * even long and double ones. The implicit @literal{this} argument is not counted.
+ */
+ public static int getArgumentCount(final String methodDescriptor) {
+ int argumentCount = 0;
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ // Parse the argument types, one at a each loop iteration.
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
+ }
+ ++argumentCount;
+ }
+ return argumentCount;
+ }
+
+ /**
+ * Returns the size of the arguments and of the return value of methods of this type. This method
+ * should only be used for method types.
+ *
+ * @return the size of the arguments of the method (plus one for the implicit this argument),
+ * argumentsSize, and the size of its return value, returnSize, packed into a single int i =
+ * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
+ * i >> 2}, and returnSize to {@code i & 0x03}). Long and double values have size 2,
+ * the others have size 1.
+ */
+ public int getArgumentsAndReturnSizes() {
+ return getArgumentsAndReturnSizes(getDescriptor());
+ }
+
+ /**
+ * Computes the size of the arguments and of the return value of a method.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the size of the arguments of the method (plus one for the implicit this argument),
+ * argumentsSize, and the size of its return value, returnSize, packed into a single int i =
+ * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
+ * i >> 2}, and returnSize to {@code i & 0x03}). Long and double values have size 2,
+ * the others have size 1.
+ */
+ public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
+ int argumentsSize = 1;
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ int currentChar = methodDescriptor.charAt(currentOffset);
+ // Parse the argument types and compute their size, one at a each loop iteration.
+ while (currentChar != ')') {
+ if (currentChar == 'J' || currentChar == 'D') {
+ currentOffset++;
+ argumentsSize += 2;
+ } else {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
+ }
+ argumentsSize += 1;
+ }
+ currentChar = methodDescriptor.charAt(currentOffset);
+ }
+ currentChar = methodDescriptor.charAt(currentOffset + 1);
+ if (currentChar == 'V') {
+ return argumentsSize << 2;
+ } else {
+ int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1;
+ return argumentsSize << 2 | returnSize;
+ }
+ }
+
/**
* Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for
* method types.
@@ -775,7 +789,7 @@ public int getSize() {
* 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
+ * example, if this type is {@code float} and {@code opcode} is IRETURN, this method returns
* FRETURN.
*/
public int getOpcode(final int opcode) {
@@ -841,14 +855,14 @@ public int getOpcode(final int opcode) {
}
// -----------------------------------------------------------------------------------------------
- // Equals, hashCode and toString
+ // 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.
+ * @return {@literal true} if the given object is equal to this type.
*/
@Override
public boolean equals(final Object object) {
@@ -903,4 +917,4 @@ public int hashCode() {
public String toString() {
return getDescriptor();
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/TypePath.java b/src/jvm/clojure/asm/TypePath.java
index aaffa43b9e..66e5cd6043 100644
--- a/src/jvm/clojure/asm/TypePath.java
+++ b/src/jvm/clojure/asm/TypePath.java
@@ -34,7 +34,7 @@
*
* @author Eric Bruneton
*/
-public class TypePath {
+public final class TypePath {
/** A type path step that steps into the element type of an array type. See {@link #getStep}. */
public static final int ARRAY_ELEMENT = 0;
@@ -113,8 +113,8 @@ public int getStepArgument(final int index) {
* 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.
+ * {@literal null} or empty.
+ * @return the corresponding TypePath object, or {@literal null} if the path is empty.
*/
public static TypePath fromString(final String typePath) {
if (typePath == null || typePath.length() == 0) {
@@ -187,7 +187,7 @@ public String 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 typePath a TypePath instance, or {@literal null} for empty paths.
* @param output where the type path must be put.
*/
static void put(final TypePath typePath, final ByteVector output) {
@@ -198,4 +198,4 @@ static void put(final TypePath typePath, final ByteVector output) {
output.putByteArray(typePath.typePathContainer, typePath.typePathOffset, length);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/TypeReference.java b/src/jvm/clojure/asm/TypeReference.java
index 8de8fb1972..2a2c62dd94 100644
--- a/src/jvm/clojure/asm/TypeReference.java
+++ b/src/jvm/clojure/asm/TypeReference.java
@@ -226,7 +226,7 @@ public static TypeReference newTypeParameterReference(final int sort, final int
* @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) {
+ final int sort, final int paramIndex, final int boundIndex) {
return new TypeReference((sort << 24) | (paramIndex << 16) | (boundIndex << 8));
}
@@ -433,4 +433,4 @@ static void putTarget(final int targetTypeAndInfo, final ByteVector output) {
throw new IllegalArgumentException();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/commons/GeneratorAdapter.java b/src/jvm/clojure/asm/commons/GeneratorAdapter.java
index 7c4b1096f5..04752c931b 100644
--- a/src/jvm/clojure/asm/commons/GeneratorAdapter.java
+++ b/src/jvm/clojure/asm/commons/GeneratorAdapter.java
@@ -30,13 +30,14 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-
import clojure.asm.ClassVisitor;
+import clojure.asm.ConstantDynamic;
import clojure.asm.Handle;
import clojure.asm.Label;
import clojure.asm.MethodVisitor;
import clojure.asm.Opcodes;
import clojure.asm.Type;
+import clojure.asm.InstructionAdapter;
/**
* A {@link MethodVisitor} with convenient methods to generate code. For example, using this
@@ -50,7 +51,7 @@
* }
*
*
- * can be generated as follows:
+ *
can be generated as follows:
*
*
* ClassWriter cw = new ClassWriter(0);
@@ -183,7 +184,7 @@ public class GeneratorAdapter extends LocalVariablesSorter {
private final Type[] argumentTypes;
/** The types of the local variables of the visited method. */
- private final List localTypes = new ArrayList();
+ private final List localTypes = new ArrayList<>();
/**
* Constructs a new {@link GeneratorAdapter}. Subclasses must not use this constructor.
@@ -197,11 +198,11 @@ public class GeneratorAdapter extends LocalVariablesSorter {
* @throws IllegalStateException if a subclass calls this constructor.
*/
public GeneratorAdapter(
- final MethodVisitor methodVisitor,
- final int access,
- final String name,
- final String descriptor) {
- this(Opcodes.ASM6, methodVisitor, access, name, descriptor);
+ final MethodVisitor methodVisitor,
+ final int access,
+ final String name,
+ final String descriptor) {
+ this(/* latest api = */ Opcodes.ASM9, methodVisitor, access, name, descriptor);
if (getClass() != GeneratorAdapter.class) {
throw new IllegalStateException();
}
@@ -210,19 +211,19 @@ public GeneratorAdapter(
/**
* Constructs a new {@link GeneratorAdapter}.
*
- * @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 api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param methodVisitor the method visitor to which this adapter delegates calls.
* @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}).
*/
protected GeneratorAdapter(
- final int api,
- final MethodVisitor methodVisitor,
- final int access,
- final String name,
- final String descriptor) {
+ final int api,
+ final MethodVisitor methodVisitor,
+ final int access,
+ final String name,
+ final String descriptor) {
super(api, access, descriptor, methodVisitor);
this.access = access;
this.name = name;
@@ -240,7 +241,7 @@ protected GeneratorAdapter(
* @param methodVisitor the method visitor to which this adapter delegates calls.
*/
public GeneratorAdapter(
- final int access, final Method method, final MethodVisitor methodVisitor) {
+ final int access, final Method method, final MethodVisitor methodVisitor) {
this(methodVisitor, access, method.getName(), method.getDescriptor());
}
@@ -251,37 +252,34 @@ public GeneratorAdapter(
*
* @param access access flags of the adapted method.
* @param method the adapted method.
- * @param signature the signature of the adapted method (may be null).
- * @param exceptions the exceptions thrown by the adapted method (may be null).
+ * @param signature the signature of the adapted method (may be {@literal null}).
+ * @param exceptions the exceptions thrown by the adapted method (may be {@literal null}).
* @param classVisitor the class visitor to which this adapter delegates calls.
*/
public GeneratorAdapter(
- final int access,
- final Method method,
- final String signature,
- final Type[] exceptions,
- final ClassVisitor classVisitor) {
+ final int access,
+ final Method method,
+ final String signature,
+ final Type[] exceptions,
+ final ClassVisitor classVisitor) {
this(
- access,
- method,
- classVisitor.visitMethod(
access,
- method.getName(),
- method.getDescriptor(),
- signature,
- getInternalNames(exceptions)));
+ method,
+ classVisitor.visitMethod(
+ access,
+ method.getName(),
+ method.getDescriptor(),
+ signature,
+ exceptions == null ? null : getInternalNames(exceptions)));
}
/**
* Returns the internal names of the given types.
*
* @param types a set of types.
- * @return the internal names of the given types.
+ * @return the internal names of the given types (see {@link Type#getInternalName()}).
*/
private static String[] getInternalNames(final Type[] types) {
- if (types == null) {
- return null;
- }
String[] names = new String[types.length];
for (int i = 0; i < names.length; ++i) {
names[i] = types[i].getInternalName();
@@ -379,7 +377,7 @@ public void push(final double value) {
/**
* Generates the instruction to push the given value on the stack.
*
- * @param value the value to be pushed on the stack. May be null.
+ * @param value the value to be pushed on the stack. May be {@literal null}.
*/
public void push(final String value) {
if (value == null) {
@@ -399,6 +397,9 @@ public void push(final Type value) {
mv.visitInsn(Opcodes.ACONST_NULL);
} else {
switch (value.getSort()) {
+ case Type.VOID:
+ mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Void", "TYPE", CLASS_DESCRIPTOR);
+ break;
case Type.BOOLEAN:
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", CLASS_DESCRIPTOR);
break;
@@ -425,6 +426,7 @@ public void push(final Type value) {
break;
default:
mv.visitLdcInsn(value);
+ break;
}
}
}
@@ -442,6 +444,19 @@ public void push(final Handle handle) {
}
}
+ /**
+ * Generates the instruction to push a constant dynamic on the stack.
+ *
+ * @param constantDynamic the constant dynamic to be pushed on the stack.
+ */
+ public void push(final ConstantDynamic constantDynamic) {
+ if (constantDynamic == null) {
+ mv.visitInsn(Opcodes.ACONST_NULL);
+ } else {
+ mv.visitLdcInsn(constantDynamic);
+ }
+ }
+
// -----------------------------------------------------------------------------------------------
// Instructions to load and store method arguments
// -----------------------------------------------------------------------------------------------
@@ -742,53 +757,12 @@ public void iinc(final int local, final int amount) {
public void cast(final Type from, final Type to) {
if (from != to) {
if (from.getSort() < Type.BOOLEAN
- || from.getSort() > Type.DOUBLE
- || to.getSort() < Type.BOOLEAN
- || to.getSort() > Type.DOUBLE) {
- throw new IllegalArgumentException();
- }
- if (from == Type.DOUBLE_TYPE) {
- if (to == Type.FLOAT_TYPE) {
- mv.visitInsn(Opcodes.D2F);
- } else if (to == Type.LONG_TYPE) {
- mv.visitInsn(Opcodes.D2L);
- } else {
- mv.visitInsn(Opcodes.D2I);
- cast(Type.INT_TYPE, to);
- }
- } else if (from == Type.FLOAT_TYPE) {
- if (to == Type.DOUBLE_TYPE) {
- mv.visitInsn(Opcodes.F2D);
- } else if (to == Type.LONG_TYPE) {
- mv.visitInsn(Opcodes.F2L);
- } else {
- mv.visitInsn(Opcodes.F2I);
- cast(Type.INT_TYPE, to);
- }
- } else if (from == Type.LONG_TYPE) {
- if (to == Type.DOUBLE_TYPE) {
- mv.visitInsn(Opcodes.L2D);
- } else if (to == Type.FLOAT_TYPE) {
- mv.visitInsn(Opcodes.L2F);
- } else {
- mv.visitInsn(Opcodes.L2I);
- cast(Type.INT_TYPE, to);
- }
- } else {
- if (to == Type.BYTE_TYPE) {
- mv.visitInsn(Opcodes.I2B);
- } else if (to == Type.CHAR_TYPE) {
- mv.visitInsn(Opcodes.I2C);
- } else if (to == Type.DOUBLE_TYPE) {
- mv.visitInsn(Opcodes.I2D);
- } else if (to == Type.FLOAT_TYPE) {
- mv.visitInsn(Opcodes.I2F);
- } else if (to == Type.LONG_TYPE) {
- mv.visitInsn(Opcodes.I2L);
- } else if (to == Type.SHORT_TYPE) {
- mv.visitInsn(Opcodes.I2S);
- }
+ || from.getSort() > Type.DOUBLE
+ || to.getSort() < Type.BOOLEAN
+ || to.getSort() > Type.DOUBLE) {
+ throw new IllegalArgumentException("Cannot cast from " + from + " to " + to);
}
+ InstructionAdapter.cast(mv, from, to);
}
}
@@ -902,6 +876,7 @@ public void unbox(final Type type) {
break;
default:
unboxMethod = null;
+ break;
}
if (unboxMethod == null) {
checkCast(type);
@@ -950,7 +925,7 @@ public Label mark() {
*
* @param type the type of the top two stack values.
* @param mode how these values must be compared. One of EQ, NE, LT, GE, GT, LE.
- * @param label where to jump if the comparison result is true.
+ * @param label where to jump if the comparison result is {@literal true}.
*/
public void ifCmp(final Type type, final int mode, final Label label) {
switch (type.getSort()) {
@@ -1009,7 +984,7 @@ public void ifCmp(final Type type, final int mode, final Label label) {
* stack values.
*
* @param mode how these values must be compared. One of EQ, NE, LT, GE, GT, LE.
- * @param label where to jump if the comparison result is true.
+ * @param label where to jump if the comparison result is {@literal true}.
*/
public void ifICmp(final int mode, final Label label) {
ifCmp(Type.INT_TYPE, mode, label);
@@ -1020,7 +995,7 @@ public void ifICmp(final int mode, final Label label) {
* value with zero.
*
* @param mode how these values must be compared. One of EQ, NE, LT, GE, GT, LE.
- * @param label where to jump if the comparison result is true.
+ * @param label where to jump if the comparison result is {@literal true}.
*/
public void ifZCmp(final int mode, final Label label) {
mv.visitJumpInsn(mode, label);
@@ -1029,7 +1004,7 @@ public void ifZCmp(final int mode, final Label label) {
/**
* Generates the instruction to jump to the given label if the top stack value is null.
*
- * @param label where to jump if the condition is true.
+ * @param label where to jump if the condition is {@literal true}.
*/
public void ifNull(final Label label) {
mv.visitJumpInsn(Opcodes.IFNULL, label);
@@ -1038,7 +1013,7 @@ public void ifNull(final Label label) {
/**
* Generates the instruction to jump to the given label if the top stack value is not null.
*
- * @param label where to jump if the condition is true.
+ * @param label where to jump if the condition is {@literal true}.
*/
public void ifNonNull(final Label label) {
mv.visitJumpInsn(Opcodes.IFNONNULL, label);
@@ -1047,7 +1022,7 @@ public void ifNonNull(final Label label) {
/**
* Generates the instruction to jump to the given label.
*
- * @param label where to jump if the condition is true.
+ * @param label where to jump if the condition is {@literal true}.
*/
public void goTo(final Label label) {
mv.visitJumpInsn(Opcodes.GOTO, label);
@@ -1084,11 +1059,11 @@ public void tableSwitch(final int[] keys, final TableSwitchGenerator generator)
*
* @param keys the switch case keys.
* @param generator a generator to generate the code for the switch cases.
- * @param useTable true to use a TABLESWITCH instruction, or false to use a
+ * @param useTable {@literal true} to use a TABLESWITCH instruction, or {@literal false} to use a
* LOOKUPSWITCH instruction.
*/
public void tableSwitch(
- final int[] keys, final TableSwitchGenerator generator, final boolean useTable) {
+ final int[] keys, final TableSwitchGenerator generator, final boolean useTable) {
for (int i = 1; i < keys.length; ++i) {
if (keys[i] < keys[i - 1]) {
throw new IllegalArgumentException("keys must be sorted in ascending order");
@@ -1150,7 +1125,7 @@ public void returnValue() {
* @param fieldType the type of the field.
*/
private void fieldInsn(
- final int opcode, final Type ownerType, final String name, final Type fieldType) {
+ final int opcode, final Type ownerType, final String name, final Type fieldType) {
mv.visitFieldInsn(opcode, ownerType.getInternalName(), name, fieldType.getDescriptor());
}
@@ -1211,7 +1186,7 @@ public void putField(final Type owner, final String name, final Type type) {
* @param isInterface whether the 'type' class is an interface or not.
*/
private void invokeInsn(
- final int opcode, final Type type, final Method method, final boolean isInterface) {
+ final int opcode, final Type type, final Method method, final boolean isInterface) {
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor(), isInterface);
}
@@ -1268,10 +1243,10 @@ public void invokeInterface(final Type owner, final Method method) {
* a caller should expect that this array may change.
*/
public void invokeDynamic(
- final String name,
- final String descriptor,
- final Handle bootstrapMethodHandle,
- final Object... bootstrapMethodArguments) {
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
}
@@ -1304,37 +1279,7 @@ public void newInstance(final Type type) {
* @param type the type of the array elements.
*/
public void newArray(final Type type) {
- int arrayType;
- switch (type.getSort()) {
- case Type.BOOLEAN:
- arrayType = Opcodes.T_BOOLEAN;
- break;
- case Type.CHAR:
- arrayType = Opcodes.T_CHAR;
- break;
- case Type.BYTE:
- arrayType = Opcodes.T_BYTE;
- break;
- case Type.SHORT:
- arrayType = Opcodes.T_SHORT;
- break;
- case Type.INT:
- arrayType = Opcodes.T_INT;
- break;
- case Type.FLOAT:
- arrayType = Opcodes.T_FLOAT;
- break;
- case Type.LONG:
- arrayType = Opcodes.T_LONG;
- break;
- case Type.DOUBLE:
- arrayType = Opcodes.T_DOUBLE;
- break;
- default:
- typeInsn(Opcodes.ANEWARRAY, type);
- return;
- }
- mv.visitIntInsn(Opcodes.NEWARRAY, arrayType);
+ InstructionAdapter.newarray(mv, type);
}
// -----------------------------------------------------------------------------------------------
@@ -1413,7 +1358,8 @@ public void endMethod() {
*
* @param start beginning of the exception handler's scope (inclusive).
* @param end end of the exception handler's scope (exclusive).
- * @param exception internal name of the type of exceptions handled by the handler.
+ * @param exception internal name of the type of exceptions handled by the handler (see {@link
+ * Type#getInternalName()}).
*/
public void catchException(final Label start, final Label end, final Type exception) {
Label catchLabel = new Label();
diff --git a/src/jvm/clojure/asm/commons/LocalVariablesSorter.java b/src/jvm/clojure/asm/commons/LocalVariablesSorter.java
index d78e651f4c..abd28b0b0b 100644
--- a/src/jvm/clojure/asm/commons/LocalVariablesSorter.java
+++ b/src/jvm/clojure/asm/commons/LocalVariablesSorter.java
@@ -80,8 +80,8 @@ public class LocalVariablesSorter extends MethodVisitor {
* @throws IllegalStateException if a subclass calls this constructor.
*/
public LocalVariablesSorter(
- final int access, final String descriptor, final MethodVisitor methodVisitor) {
- this(Opcodes.ASM6, access, descriptor, methodVisitor);
+ final int access, final String descriptor, final MethodVisitor methodVisitor) {
+ this(/* latest api = */ Opcodes.ASM9, access, descriptor, methodVisitor);
if (getClass() != LocalVariablesSorter.class) {
throw new IllegalStateException();
}
@@ -90,14 +90,14 @@ public LocalVariablesSorter(
/**
* Constructs a new {@link LocalVariablesSorter}.
*
- * @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 api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param access access flags of the adapted method.
* @param descriptor the method's descriptor (see {@link Type}).
* @param methodVisitor the method visitor to which this adapter delegates calls.
*/
protected LocalVariablesSorter(
- final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) {
+ final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) {
super(api, methodVisitor);
nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (Type argumentType : Type.getArgumentTypes(descriptor)) {
@@ -107,7 +107,7 @@ protected LocalVariablesSorter(
}
@Override
- public void visitVarInsn(final int opcode, final int var) {
+ public void visitVarInsn(final int opcode, final int varIndex) {
Type varType;
switch (opcode) {
case Opcodes.LLOAD:
@@ -134,12 +134,12 @@ public void visitVarInsn(final int opcode, final int var) {
default:
throw new IllegalArgumentException("Invalid opcode " + opcode);
}
- super.visitVarInsn(opcode, remap(var, varType));
+ super.visitVarInsn(opcode, remap(varIndex, varType));
}
@Override
- public void visitIincInsn(final int var, final int increment) {
- super.visitIincInsn(remap(var, Type.INT_TYPE), increment);
+ public void visitIincInsn(final int varIndex, final int increment) {
+ super.visitIincInsn(remap(varIndex, Type.INT_TYPE), increment);
}
@Override
@@ -149,44 +149,44 @@ public void visitMaxs(final int maxStack, final int maxLocals) {
@Override
public void visitLocalVariable(
- final String name,
- final String descriptor,
- final String signature,
- final Label start,
- final Label end,
- final int index) {
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Label start,
+ final Label end,
+ final int index) {
int remappedIndex = remap(index, Type.getType(descriptor));
super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex);
}
@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) {
+ final int typeRef,
+ final TypePath typePath,
+ final Label[] start,
+ final Label[] end,
+ final int[] index,
+ final String descriptor,
+ final boolean visible) {
Type type = Type.getType(descriptor);
int[] remappedIndex = new int[index.length];
for (int i = 0; i < remappedIndex.length; ++i) {
remappedIndex[i] = remap(index[i], type);
}
return super.visitLocalVariableAnnotation(
- typeRef, typePath, start, end, remappedIndex, descriptor, visible);
+ typeRef, typePath, start, end, remappedIndex, descriptor, visible);
}
@Override
public void visitFrame(
- final int type,
- final int nLocal,
- final Object[] local,
- final int nStack,
- final Object[] stack) {
+ final int type,
+ final int numLocal,
+ final Object[] local,
+ final int numStack,
+ final Object[] stack) {
if (type != Opcodes.F_NEW) { // Uncompressed frame.
throw new IllegalArgumentException(
- "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
+ "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
}
// Create a copy of remappedLocals.
@@ -198,7 +198,7 @@ public void visitFrame(
// Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the
// variables added with 'newLocal'.
int oldVar = 0; // Old local variable index.
- for (int i = 0; i < nLocal; ++i) {
+ for (int i = 0; i < numLocal; ++i) {
Object localType = local[i];
if (localType != Opcodes.TOP) {
Type varType = OBJECT_TYPE;
@@ -221,20 +221,20 @@ public void visitFrame(
// Remove TOP after long and double types as well as trailing TOPs.
oldVar = 0;
int newVar = 0;
- int remappedNLocal = 0;
+ int remappedNumLocal = 0;
while (oldVar < remappedLocalTypes.length) {
Object localType = remappedLocalTypes[oldVar];
oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
if (localType != null && localType != Opcodes.TOP) {
remappedLocalTypes[newVar++] = localType;
- remappedNLocal = newVar;
+ remappedNumLocal = newVar;
} else {
remappedLocalTypes[newVar++] = Opcodes.TOP;
}
}
// Visit the remapped frame.
- super.visitFrame(type, remappedNLocal, remappedLocalTypes, nStack, stack);
+ super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack);
// Restore the original value of 'remappedLocals'.
remappedLocalTypes = oldRemappedLocals;
@@ -297,7 +297,7 @@ public int newLocal(final Type type) {
* types for the current stack map frame must be updated in place in this array.
*/
protected void updateNewLocals(final Object[] newLocals) {
- // The default implementation does nothing.
+ // The default implementation does nothing.
}
/**
@@ -321,11 +321,11 @@ private void setFrameLocal(final int local, final Object type) {
remappedLocalTypes[local] = type;
}
- private int remap(final int var, final Type type) {
- if (var + type.getSize() <= firstLocal) {
- return var;
+ private int remap(final int varIndex, final Type type) {
+ if (varIndex + type.getSize() <= firstLocal) {
+ return varIndex;
}
- int key = 2 * var + type.getSize() - 1;
+ int key = 2 * varIndex + type.getSize() - 1;
int size = remappedVariableIndices.length;
if (key >= size) {
int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)];
@@ -348,4 +348,4 @@ protected int newLocalMapping(final Type type) {
nextLocal += type.getSize();
return local;
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/commons/Method.java b/src/jvm/clojure/asm/commons/Method.java
index a3920f7036..c50aa00716 100644
--- a/src/jvm/clojure/asm/commons/Method.java
+++ b/src/jvm/clojure/asm/commons/Method.java
@@ -29,7 +29,6 @@
import java.util.HashMap;
import java.util.Map;
-
import clojure.asm.Type;
/**
@@ -51,7 +50,7 @@ public class Method {
private static final Map PRIMITIVE_TYPE_DESCRIPTORS;
static {
- HashMap descriptors = new HashMap();
+ HashMap descriptors = new HashMap<>();
descriptors.put("void", "V");
descriptors.put("byte", "B");
descriptors.put("char", "C");
@@ -135,14 +134,15 @@ public static Method getMethod(final String method) {
* @throws IllegalArgumentException if method could not get parsed.
*/
public static Method getMethod(final String method, final boolean defaultPackage) {
- int spaceIndex = method.indexOf(' ');
+ final int spaceIndex = method.indexOf(' ');
int currentArgumentStartIndex = method.indexOf('(', spaceIndex) + 1;
- int endIndex = method.indexOf(')', currentArgumentStartIndex);
+ final int endIndex = method.indexOf(')', currentArgumentStartIndex);
if (spaceIndex == -1 || currentArgumentStartIndex == 0 || endIndex == -1) {
throw new IllegalArgumentException();
}
- String returnType = method.substring(0, spaceIndex);
- String methodName = method.substring(spaceIndex + 1, currentArgumentStartIndex - 1).trim();
+ final String returnType = method.substring(0, spaceIndex);
+ final String methodName =
+ method.substring(spaceIndex + 1, currentArgumentStartIndex - 1).trim();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append('(');
int currentArgumentEndIndex;
@@ -151,19 +151,18 @@ public static Method getMethod(final String method, final boolean defaultPackage
currentArgumentEndIndex = method.indexOf(',', currentArgumentStartIndex);
if (currentArgumentEndIndex == -1) {
argumentDescriptor =
- getDescriptor(
- method.substring(currentArgumentStartIndex, endIndex).trim(), defaultPackage);
+ getDescriptorInternal(
+ method.substring(currentArgumentStartIndex, endIndex).trim(), defaultPackage);
} else {
argumentDescriptor =
- getDescriptor(
- method.substring(currentArgumentStartIndex, currentArgumentEndIndex).trim(),
- defaultPackage);
+ getDescriptorInternal(
+ method.substring(currentArgumentStartIndex, currentArgumentEndIndex).trim(),
+ defaultPackage);
currentArgumentStartIndex = currentArgumentEndIndex + 1;
}
stringBuilder.append(argumentDescriptor);
} while (currentArgumentEndIndex != -1);
- stringBuilder.append(')');
- stringBuilder.append(getDescriptor(returnType, defaultPackage));
+ stringBuilder.append(')').append(getDescriptorInternal(returnType, defaultPackage));
return new Method(methodName, stringBuilder.toString());
}
@@ -176,7 +175,7 @@ public static Method getMethod(final String method, final boolean defaultPackage
* option is true, or "java.lang.Object" otherwise.
* @return the descriptor corresponding to the given type name.
*/
- private static String getDescriptor(final String type, final boolean defaultPackage) {
+ private static String getDescriptorInternal(final String type, final boolean defaultPackage) {
if ("".equals(type)) {
return type;
}
@@ -260,4 +259,4 @@ public boolean equals(final Object other) {
public int hashCode() {
return name.hashCode() ^ descriptor.hashCode();
}
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/asm/commons/TableSwitchGenerator.java b/src/jvm/clojure/asm/commons/TableSwitchGenerator.java
index 956440d3d0..5222ab127f 100644
--- a/src/jvm/clojure/asm/commons/TableSwitchGenerator.java
+++ b/src/jvm/clojure/asm/commons/TableSwitchGenerator.java
@@ -48,4 +48,4 @@ public interface TableSwitchGenerator {
/** Generates the code for the default switch case. */
void generateDefault();
-}
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/lang/APersistentSet.java b/src/jvm/clojure/lang/APersistentSet.java
index 2c8caad85b..f565d49e36 100644
--- a/src/jvm/clojure/lang/APersistentSet.java
+++ b/src/jvm/clojure/lang/APersistentSet.java
@@ -50,7 +50,19 @@ public ISeq seq(){
}
public Object invoke(Object arg1) {
- return get(arg1);
+ return impl.valAt(arg1);
+}
+
+public Object invoke(Object arg1, Object arg2) {
+ return impl.valAt(arg1, arg2);
+}
+
+public Object valAt(Object key) {
+ return impl.valAt(key);
+}
+
+public Object valAt(Object key, Object notFound) {
+ return impl.valAt(key, notFound);
}
public boolean equals(Object obj){
diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java
index 07115684aa..20c40918eb 100644
--- a/src/jvm/clojure/lang/APersistentVector.java
+++ b/src/jvm/clojure/lang/APersistentVector.java
@@ -14,6 +14,7 @@
import java.io.Serializable;
import java.util.*;
+import java.util.function.Consumer;
public abstract class APersistentVector extends AFn implements IPersistentVector, Iterable,
List,
@@ -276,6 +277,62 @@ public void remove(){
};
}
+Spliterator rangedSpliterator(final int start, final int end) {
+ return new Spliterator() {
+ int i = start;
+
+ @Override
+ public int characteristics() {
+ return Spliterator.IMMUTABLE | // persistent
+ Spliterator.ORDERED | // know order
+ Spliterator.SIZED | // know size
+ Spliterator.SUBSIZED; // know size after split
+ }
+
+ @Override
+ public long estimateSize() {
+ return end-i;
+ }
+
+ @Override
+ public long getExactSizeIfKnown() {
+ return end-i;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer action) {
+ if(i < end) {
+ action.accept(nth(i++));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Spliterator trySplit() {
+ int lo = i;
+ int mid = (lo + end) >>> 1; // avoid overflow
+ if(lo >= mid) {
+ return null;
+ } else {
+ i = mid;
+ return rangedSpliterator(lo, mid);
+ }
+ }
+
+ @Override
+ public void forEachRemaining(Consumer action) {
+ for(int x=i; x 20") + ") passed to: " + Compiler.demunge(name), cause);
this.actual = actual;
this.name = name;
}
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index 758108e8bc..62ca4adad5 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -15,15 +15,20 @@
//*
import clojure.asm.*;
+import clojure.asm.Type;
import clojure.asm.commons.GeneratorAdapter;
import clojure.asm.commons.Method;
import java.io.*;
+import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
+import java.lang.reflect.Executable;
import java.util.*;
+import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
+import java.util.stream.Collectors;
//*/
/*
@@ -206,13 +211,13 @@ public class Compiler implements Opcodes{
static final public Var CONSTANT_IDS = Var.create().setDynamic();
//vector
-static final public Var KEYWORD_CALLSITES = Var.create().setDynamic();
+static final public Var KEYWORD_CALLSITES = Var.create(null).setDynamic();
//vector
-static final public Var PROTOCOL_CALLSITES = Var.create().setDynamic();
+static final public Var PROTOCOL_CALLSITES = Var.create(null).setDynamic();
//set
-static final public Var VAR_CALLSITES = Var.create().setDynamic();
+//static final public Var VAR_CALLSITES = Var.create(null).setDynamic();
//keyword->constid
static final public Var KEYWORDS = Var.create().setDynamic();
@@ -343,6 +348,8 @@ public enum C{
EVAL
}
+public static final int JVM_BYTECODE_VERSION = V17;
+
private class Recur {};
static final public Class RECUR_CLASS = Recur.class;
@@ -387,7 +394,12 @@ static Symbol resolveSymbol(Symbol sym){
{
Namespace ns = namespaceFor(sym);
if(ns == null || (ns.name.name == null ? sym.ns == null : ns.name.name.equals(sym.ns)))
+ {
+ Class ac = HostExpr.maybeArrayClass(sym);
+ if(ac != null)
+ return Util.arrayTypeToSymbol(ac);
return sym;
+ }
return Symbol.intern(ns.name.name, sym.name);
}
Object o = currentNS().getMapping(sym);
@@ -1004,7 +1016,7 @@ else if(instance != null && instance.hasJavaClass() && instance.getJavaClass() !
if(c != null)
return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args, tailPosition);
else
- return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args, tailPosition);
+ return new InstanceMethodExpr(source, line, column, tag, instance, null, munge(sym.name), args, tailPosition);
}
}
}
@@ -1100,10 +1112,12 @@ static Class tagToClass(Object tag) {
if(tag instanceof Symbol)
{
Symbol sym = (Symbol) tag;
- if(sym.ns == null) //if ns-qualified can't be classname
+ if(sym.ns == null)
{
c = maybeSpecialTag(sym);
}
+ if(c == null)
+ c = HostExpr.maybeArrayClass(sym);
}
if(c == null)
c = maybeClass(tag, true);
@@ -1111,6 +1125,297 @@ static Class tagToClass(Object tag) {
return c;
throw new IllegalArgumentException("Unable to resolve classname: " + tag);
}
+
+ public static boolean looksLikeArrayClass(Symbol sym) {
+ return sym.ns != null && Util.isPosDigit(sym.name);
+ }
+
+ public static String buildArrayClassDescriptor(Symbol sym) {
+ int dim = sym.name.charAt(0) - '0';
+ Symbol componentClassName = Symbol.intern(null, sym.ns);
+ Class componentClass = primClass(componentClassName);
+
+ if(componentClass == null)
+ componentClass = maybeClass(componentClassName, false);
+
+ if(componentClass == null)
+ throw Util.sneakyThrow(new ClassNotFoundException("Unable to resolve component classname: "
+ + componentClassName));
+
+ StringBuilder arrayDescriptor = new StringBuilder();
+
+ for(int i=0; i hintedSig;
+ private final Symbol methodSymbol;
+ private final String methodName;
+ private final MethodKind kind;
+ private final Class tagClass;
+ private final StaticFieldExpr fieldOverload;
+
+ private enum MethodKind {
+ CTOR, INSTANCE, STATIC
+ }
+
+ public QualifiedMethodExpr(Class methodClass, Symbol sym) {
+ this(methodClass, sym, null);
+ }
+
+ public QualifiedMethodExpr(Class methodClass, Symbol sym, StaticFieldExpr fieldOL) {
+ c = methodClass;
+ methodSymbol = sym;
+ tagClass = tagOf(sym) != null ? HostExpr.tagToClass(tagOf(sym)) : AFn.class;
+ hintedSig = tagsToClasses(paramTagsOf(sym));
+ if(sym.name.startsWith(".")) {
+ kind = MethodKind.INSTANCE;
+ methodName = sym.name.substring(1);
+ }
+ else if(sym.name.equals("new")) {
+ kind = MethodKind.CTOR;
+ methodName = sym.name;
+ }
+ else {
+ kind = MethodKind.STATIC;
+ methodName = sym.name;
+ }
+ fieldOverload = fieldOL;
+ }
+
+ private boolean preferOverloadedField() {
+ return fieldOverload != null && paramTagsOf(methodSymbol) == null;
+ }
+
+ // Expr impl - invocation, convert to fn expr
+
+ @Override
+ public Object eval() {
+ if(preferOverloadedField())
+ return fieldOverload.eval();
+ else
+ return buildThunk(C.EVAL, this).eval();
+ }
+
+ @Override
+ public void emit(C context, ObjExpr objx, GeneratorAdapter gen) {
+ if(preferOverloadedField())
+ fieldOverload.emit(context, objx, gen);
+ else
+ buildThunk(context, this).emit(context, objx, gen);
+ }
+
+ // Expr impl - method value, always an AFn
+
+ @Override
+ public boolean hasJavaClass() {
+ return true;
+ }
+
+ @Override
+ public Class getJavaClass() {
+ return tagClass;
+ }
+
+ // TBD: caching/reuse of thunks
+ private static FnExpr buildThunk(C context, QualifiedMethodExpr qmexpr) {
+ // When qualified symbol has param-tags:
+ // (fn invoke__Class_meth ([this? args*] (methodSymbol this? args*)))
+ // When no param-tags:
+ // (fn invoke__Class_meth ([this?] (methodSymbol this?))
+ // ([this? arg1] (methodSymbol this? arg1)) ...)
+ IPersistentCollection form = PersistentVector.EMPTY;
+ Symbol instanceParam = qmexpr.kind == MethodKind.INSTANCE ? THIS : null;
+ String thunkName = "invoke__" + qmexpr.c.getSimpleName() + "_" + qmexpr.methodSymbol.name;
+ Set arities = (qmexpr.hintedSig != null) ? PersistentHashSet.create(qmexpr.hintedSig.size())
+ : aritySet(qmexpr.c, qmexpr.methodName, qmexpr.kind);
+
+ for(Object a : arities) {
+ int arity = (int) a;
+ IPersistentVector params = buildParams(instanceParam, arity);
+ ISeq body = RT.listStar(qmexpr.methodSymbol, params.seq());
+ form = RT.conj(form, RT.list(params, body));
+ }
+
+ ISeq thunkForm = RT.listStar(Symbol.intern("fn"), Symbol.intern(thunkName), RT.seq(form));
+ return (FnExpr) analyzeSeq(context, thunkForm, thunkName);
+ }
+
+ private static IPersistentVector buildParams(Symbol instanceParam, int arity) {
+ IPersistentVector params = PersistentVector.EMPTY;
+ if(instanceParam != null) params = params.cons(instanceParam);
+ for(int i = 0; i();
+ List methods = methodsWithName(c, methodName, kind);
+
+ for(Executable exec : methods)
+ res.add(exec.getParameterCount());
+
+ return res;
+ }
+
+ public static List methodOverloads(Class c, String methodName, MethodKind kind) {
+ final Executable[] methods = c.getMethods();
+ return Arrays.stream(methods)
+ .filter(m -> m.getName().equals(methodName))
+ .filter(m -> {
+ switch(kind) {
+ case STATIC: return isStaticMethod(m);
+ case INSTANCE: return isInstanceMethod(m);
+ default: return false;
+ }
+ })
+ .collect(Collectors.toList());
+ }
+
+ // Returns a list of methods or ctors matching the name and kind given.
+ // Otherwise, will throw if the information provided results in no matches
+ private static List methodsWithName(Class c, String methodName, MethodKind kind) {
+ if (kind == MethodKind.CTOR) {
+ List ctors = Arrays.asList(c.getConstructors());
+ if(ctors.isEmpty())
+ throw noMethodWithNameException(c, methodName, kind);
+ return ctors;
+ }
+
+ List res = methodOverloads(c, methodName, kind);
+
+ if(res.isEmpty())
+ throw noMethodWithNameException(c, methodName, kind);
+ return res;
+ }
+
+ static Executable resolveHintedMethod(Class c, String methodName, MethodKind kind, List hintedSig) {
+ List methods = methodsWithName(c, methodName, kind);
+ final int arity = hintedSig.size();
+ List filteredMethods = methods.stream()
+ .filter(m -> m.getParameterCount() == arity)
+ .filter(m -> !m.isSynthetic()) // remove bridge/lambda methods
+ .filter(m -> signatureMatches(hintedSig, m))
+ .collect(Collectors.toList());
+
+ if(filteredMethods.size() == 1)
+ return filteredMethods.get(0);
+ else
+ throw paramTagsDontResolveException(c, methodName, hintedSig);
+ }
+
+ static IllegalArgumentException noMethodWithNameException(Class c, String methodName, MethodKind kind) {
+ return new IllegalArgumentException("Error - no matches found for "
+ + (kind != MethodKind.CTOR ? kind.toString().toLowerCase() + " " : "")
+ + methodDescription(c, methodName));
+ }
+
+ static IllegalArgumentException paramTagsDontResolveException(Class c, String methodName, List hintedSig) {
+ IPersistentVector paramTags = PersistentVector.create(hintedSig.stream()
+ .map(tag -> tag == null ? PARAM_TAG_ANY : tag)
+ .collect(Collectors.toList()));
+ return new IllegalArgumentException("Error - param-tags " + paramTags
+ + " insufficient to resolve "
+ + methodDescription(c, methodName));
+ }
+
+ public static IllegalArgumentException instanceNoTargetException(QualifiedMethodExpr qmexpr) {
+ return new IllegalArgumentException(
+ "Malformed method expression, expecting (" +
+ qmexpr.c.getName() + "/." + qmexpr.methodName +
+ " target ...)");
+ }
+}
+
+final static Symbol PARAM_TAG_ANY = Symbol.intern(null, "_");
+
+private static IPersistentVector paramTagsOf(Symbol sym){
+ Object paramTags = RT.get(RT.meta(sym), RT.PARAM_TAGS_KEY);
+
+ if(paramTags != null && !(paramTags instanceof IPersistentVector))
+ throw new IllegalArgumentException("param-tags of symbol "
+ + sym + " should be a vector.");
+
+ return (IPersistentVector) paramTags;
+}
+
+// calls tagToClass on every element, unless it encounters _ which becomes null
+private static List tagsToClasses(IPersistentVector paramTags) {
+ if(paramTags == null) return null;
+
+ List sig = new ArrayList<>();
+ for (ISeq s = RT.seq(paramTags); s!=null; s = s.next()) {
+ Object t = s.first();
+ if (t.equals(PARAM_TAG_ANY))
+ sig.add(null);
+ else
+ sig.add(HostExpr.tagToClass(t));
+ }
+ return sig;
+}
+
+private static boolean signatureMatches(List sig, Executable method)
+{
+ Class[] methodSig = method.getParameterTypes();
+ if(methodSig.length != sig.size()) return false;
+
+ for (int i = 0; i < methodSig.length; i++)
+ if (sig.get(i) != null && !sig.get(i).equals(methodSig[i]))
+ return false;
+
+ return true;
+};
+
+static boolean isStaticMethod(Executable method) {
+ return method instanceof java.lang.reflect.Method && Modifier.isStatic(method.getModifiers());
+}
+
+static boolean isInstanceMethod(Executable method) {
+ return method instanceof java.lang.reflect.Method && !Modifier.isStatic(method.getModifiers());
+}
+
+static boolean isConstructor(Executable method) {
+ return method instanceof Constructor;
+}
+
+private static void checkMethodArity(Executable method, int argCount) {
+ if(method.getParameterCount() != argCount)
+ throw new IllegalArgumentException("Invocation of "
+ + methodDescription(method.getDeclaringClass(),
+ (method instanceof Constructor) ? "new" : method.getName())
+ + " expected " + method.getParameterCount() + " arguments, but received " + argCount);
+}
+
+private static String methodDescription(Class c, String methodName) {
+ boolean isCtor = c != null && methodName.equals("new");
+ String type = isCtor ? "constructor" : "method";
+ return type + (isCtor ? "" : " " + methodName) + " in class " + c.getName();
}
static abstract class FieldExpr extends HostExpr{
@@ -1347,6 +1652,161 @@ static Class maybePrimitiveType(Expr e){
return null;
}
+static class FISupport {
+ private static final IPersistentSet AFN_FIS = RT.set(Callable.class, Runnable.class, Comparator.class);
+ private static final IPersistentSet OBJECT_METHODS = RT.set("equals", "toString", "hashCode");
+
+ // Return FI method if:
+ // 1) Target is a functional interface and not already implemented by AFn
+ // 2) Target method matches one of our fn invoker methods (0 <= arity <= 10)
+ static java.lang.reflect.Method maybeFIMethod(Class target) {
+ if (target != null && target.isAnnotationPresent(FunctionalInterface.class)
+ && !AFN_FIS.contains(target)) {
+
+ java.lang.reflect.Method[] methods = target.getMethods();
+ for (java.lang.reflect.Method method : methods) {
+ if (method.getParameterCount() >= 0 && method.getParameterCount() <= 10
+ && Modifier.isAbstract(method.getModifiers())
+ && !OBJECT_METHODS.contains(method.getName()))
+ return method;
+ }
+ }
+ return null;
+ }
+
+ // Invokers support only long, double, Object params; widen numerics
+ private static Class toInvokerParamType(Class c) {
+ if (c.equals(Byte.TYPE) || c.equals(Short.TYPE) || c.equals(Integer.TYPE) || c.equals(Long.TYPE)) {
+ return Long.TYPE;
+ } else if (c.equals(Float.TYPE) || c.equals(Double.TYPE)) {
+ return Double.TYPE;
+ }
+ return Object.class;
+ }
+
+ /**
+ * If targetClass is FI and has an adaptable functional method
+ * Find fn invoker method matching adaptable method of FI
+ * Emit bytecode for (expr is emitted):
+ * if(expr instanceof IFn && !(expr instanceof FI))
+ * invokeDynamic(targetMethod, fnInvokerImplMethod)
+ * Else emit nothing
+ */
+ static boolean maybeEmitFIAdapter(ObjExpr objx, GeneratorAdapter gen, Expr expr, Class targetClass) {
+ // Optimization:
+ // if(expr instanceof QualifiedMethodExpr)
+ // emitInvokeDynamic(targetMethod, QME method) // DON'T emit expr
+
+ java.lang.reflect.Method targetMethod = maybeFIMethod(targetClass);
+ if (targetMethod == null)
+ return false;
+
+ // compute fn invoker method
+ int paramCount = targetMethod.getParameterCount();
+ Class[] invokerParams = new Class[paramCount + 1];
+ invokerParams[0] = IFn.class; // close over Ifn as first arg
+ StringBuilder invokeMethodBuilder = new StringBuilder("invoke");
+ for (int i = 0; i < paramCount; i++) {
+ // FnInvokers only has prims for first 2 args
+ invokerParams[i + 1] = paramCount <= 2 ? toInvokerParamType(targetMethod.getParameterTypes()[i]) : Object.class;
+ invokeMethodBuilder.append(FnInvokers.encodeInvokerType(invokerParams[i + 1]));
+ }
+ // FnInvokers has prim returns for <= 2 params, only Object for higher
+ Class retType = targetMethod.getReturnType();
+ char invokerReturnCode = FnInvokers.encodeInvokerType(paramCount <= 2 ? retType : Object.class);
+ invokeMethodBuilder.append(invokerReturnCode);
+ String invokerMethodName = invokeMethodBuilder.toString();
+
+ // Emit adapter to fn invoker method
+ Type samType = Type.getType(targetClass);
+ Type ifnType = Type.getType(IFn.class);
+ try {
+ java.lang.reflect.Method fnInvokerMethod = FnInvokers.class.getMethod(invokerMethodName, invokerParams);
+
+ // if not (expr instanceof IFn), go to end label
+ expr.emit(C.EXPRESSION, objx, gen);
+ gen.dup();
+ gen.instanceOf(ifnType);
+ Label endLabel = gen.newLabel();
+ gen.ifZCmp(Opcodes.IFEQ, endLabel);
+
+ // if (expr instanceof FI), go to end label
+ gen.dup();
+ gen.instanceOf(samType);
+ gen.ifZCmp(Opcodes.IFNE, endLabel);
+
+ // else adapt fn invoker method as impl for target method
+ emitInvokeDynamicAdapter(gen, targetClass, targetMethod, FnInvokers.class, fnInvokerMethod);
+
+ // end - checkcast that we have the target FI type
+ gen.mark(endLabel);
+ gen.checkCast(samType);
+ return true;
+ } catch (NoSuchMethodException e) {
+ throw Util.sneakyThrow(e); // should never happen
+ }
+
+ }
+
+ // LambdaMetafactory.metafactory() method handle for lambda bootstrap
+ private static final Handle LMF_HANDLE =
+ new Handle(Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+ false);
+
+ /**
+ * Emit an invokedynamic to adapt an implMethod to act as a targetMethod.
+ *
+ * implMethod may be a static method, a constructor, or an instance method. If it is an
+ * instance method, the first argument is the invocation instance.
+ *
+ * The implMethod may close over objects on the stack - these are passed as the initial arguments
+ * to implMethod. The trailing arguments must match the targetMethod arguments.
+ *
+ * See: https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html
+ * @param gen ASM code generator, expects any closed-overs to be on the stack already
+ * @param targetClass The target class
+ * @param targetMethod The target method
+ * @param implClass The impl class
+ * @param implMethod The impl method that will be adapted, takes closed-overs + args of targetMethod
+ */
+ static void emitInvokeDynamicAdapter(GeneratorAdapter gen,
+ Class targetClass, java.lang.reflect.Method targetMethod,
+ Class implClass, Executable implMethod) {
+
+ // Impl method - takes closed overs (on stack now) + args (when called)
+ Class[] implParams = implMethod.getParameterTypes();
+ Class retClass = isConstructor(implMethod) ? implClass
+ : ((java.lang.reflect.Method) implMethod).getReturnType();
+
+ int opCode = isConstructor(implMethod) ? Opcodes.H_INVOKESPECIAL :
+ (isStaticMethod(implMethod) ? Opcodes.H_INVOKESTATIC :
+ Opcodes.H_INVOKEVIRTUAL);
+
+ Handle implHandle = new Handle(opCode,
+ Type.getInternalName(implClass),
+ implMethod.getName(),
+ MethodType.methodType(retClass, implParams).toMethodDescriptorString(),
+ false);
+
+ // Adapter interface lambda-style: (closedOver*) -> targetType
+ int implArgCount = implParams.length;
+ if (isInstanceMethod(implMethod)) // instance is first "arg"
+ implArgCount++;
+ List lambdaParams = Arrays.asList(Arrays.copyOfRange(implParams, 0, implArgCount - targetMethod.getParameterCount()));
+ MethodType lambdaSig = MethodType.methodType(targetClass, lambdaParams);
+
+ Type targetType = Type.getType(targetMethod);
+ gen.visitInvokeDynamicInsn(
+ targetMethod.getName(),
+ lambdaSig.toMethodDescriptorString(), // adapter signature, closedOvers -> target
+ LMF_HANDLE, // bootstrap method handle: LambdaMetaFactory.metafactory()
+ new Object[]{targetType, implHandle, targetType}); // arg types of bootstrap method
+ }
+}
+
static Class maybeJavaClass(Collection exprs){
Class match = null;
try
@@ -1426,7 +1886,7 @@ else if(primc == double.class && parameterTypes[i] == float.class)
pe.emitUnboxed(C.EXPRESSION, objx, gen);
gen.visitInsn(D2F);
}
- else
+ else if(!FISupport.maybeEmitFIAdapter(objx, gen, e, parameterTypes[i]))
{
e.emit(C.EXPRESSION, objx, gen);
HostExpr.emitUnboxArg(objx, gen, parameterTypes[i]);
@@ -1451,14 +1911,34 @@ static class InstanceMethodExpr extends MethodExpr{
public final Symbol tag;
public final boolean tailPosition;
public final java.lang.reflect.Method method;
+ public final Class qualifyingClass;
Class jc;
final static Method invokeInstanceMethodMethod =
Method.getMethod("Object invokeInstanceMethod(Object,String,Object[])");
+ final static Method invokeInstanceMethodOfClassMethod =
+ Method.getMethod("Object invokeInstanceMethodOfClass(Object,String,String,Object[])");
+
+ public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target,
+ Class qualifyingClass, String methodName, java.lang.reflect.Method resolvedMethod,
+ IPersistentVector args, boolean tailPosition)
+ {
+ checkMethodArity(resolvedMethod, RT.count(args));
+ this.source = source;
+ this.line = line;
+ this.column = column;
+ this.args = args;
+ this.methodName = methodName;
+ this.target = target;
+ this.tag = tag;
+ this.tailPosition = tailPosition;
+ this.method = resolvedMethod;
+ this.qualifyingClass = qualifyingClass;
+ }
public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target,
- String methodName, IPersistentVector args, boolean tailPosition)
+ Class qualifyingClass, String methodName, IPersistentVector args, boolean tailPosition)
{
this.source = source;
this.line = line;
@@ -1468,9 +1948,12 @@ public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr
this.target = target;
this.tag = tag;
this.tailPosition = tailPosition;
- if(target.hasJavaClass() && target.getJavaClass() != null)
+ this.qualifyingClass = qualifyingClass;
+ Class contextClass = qualifyingClass != null ? qualifyingClass :
+ (target.hasJavaClass() ? target.getJavaClass() : null);
+ if(contextClass != null)
{
- List methods = Reflector.getMethods(target.getJavaClass(), args.count(), methodName, false);
+ List methods = Reflector.getMethods(contextClass, args.count(), methodName, false);
if(methods.isEmpty())
{
method = null;
@@ -1478,7 +1961,7 @@ public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr
{
RT.errPrintWriter()
.format("Reflection warning, %s:%d:%d - call to method %s on %s can't be resolved (no such method).\n",
- SOURCE_PATH.deref(), line, column, methodName, target.getJavaClass().getName());
+ SOURCE_PATH.deref(), line, column, methodName, contextClass.getName());
}
}
else
@@ -1508,7 +1991,7 @@ public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr
{
RT.errPrintWriter()
.format("Reflection warning, %s:%d:%d - call to method %s on %s can't be resolved (argument types: %s).\n",
- SOURCE_PATH.deref(), line, column, methodName, target.getJavaClass().getName(), getTypeStringForArgs(args));
+ SOURCE_PATH.deref(), line, column, methodName, contextClass.getName(), getTypeStringForArgs(args));
}
}
}
@@ -1537,7 +2020,10 @@ public Object eval() {
ms.add(method);
return Reflector.invokeMatchingMethod(methodName, ms, targetval, argvals);
}
- return Reflector.invokeInstanceMethod(targetval, methodName, argvals);
+ if(qualifyingClass != null)
+ return Reflector.invokeInstanceMethodOfClass(targetval, qualifyingClass, methodName, argvals);
+ else
+ return Reflector.invokeInstanceMethod(targetval, methodName, argvals);
}
catch(Throwable e)
{
@@ -1609,6 +2095,8 @@ else if(retClass != void.class)
else
{
target.emit(C.EXPRESSION, objx, gen);
+ if(qualifyingClass != null)
+ gen.push(qualifyingClass.getName());
gen.push(methodName);
emitArgsAsArray(args, objx, gen);
gen.visitLineNumber(line, gen.mark());
@@ -1617,7 +2105,10 @@ else if(retClass != void.class)
ObjMethod method = (ObjMethod) METHOD.deref();
method.emitClearLocals(gen);
}
- gen.invokeStatic(REFLECTOR_TYPE, invokeInstanceMethodMethod);
+ if(qualifyingClass != null)
+ gen.invokeStatic(REFLECTOR_TYPE, invokeInstanceMethodOfClassMethod);
+ else
+ gen.invokeStatic(REFLECTOR_TYPE, invokeInstanceMethodMethod);
if(context == C.STATEMENT)
gen.pop();
}
@@ -1652,6 +2143,29 @@ static class StaticMethodExpr extends MethodExpr{
final static Keyword warnOnBoxedKeyword = Keyword.intern("warn-on-boxed");
Class jc;
+ public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c,
+ String methodName, java.lang.reflect.Method preferredMethod, IPersistentVector args, boolean tailPosition)
+ {
+ checkMethodArity(preferredMethod, RT.count(args));
+
+ this.c = c;
+ this.methodName = methodName;
+ this.args = args;
+ this.source = source;
+ this.line = line;
+ this.column = column;
+ this.tag = tag;
+ this.tailPosition = tailPosition;
+ this.method = preferredMethod;
+
+ if(method != null && warnOnBoxedKeyword.equals(RT.UNCHECKED_MATH.deref()) && isBoxedMath(method))
+ {
+ RT.errPrintWriter()
+ .format("Boxed math warning, %s:%d:%d - call: %s.\n",
+ SOURCE_PATH.deref(), line, column, method.toString());
+ }
+ }
+
public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c,
String methodName, IPersistentVector args, boolean tailPosition)
{
@@ -2563,6 +3077,13 @@ public static class NewExpr implements Expr{
Method.getMethod("Object invokeConstructor(Class,Object[])");
final static Method forNameMethod = Method.getMethod("Class classForName(String)");
+ public NewExpr(Class c, Constructor preferredConstructor, IPersistentVector args, int line, int column) {
+ checkMethodArity(preferredConstructor, RT.count(args));
+
+ this.args = args;
+ this.c = c;
+ this.ctor = preferredConstructor;
+ }
public NewExpr(Class c, IPersistentVector args, int line, int column) {
this.args = args;
@@ -3634,7 +4155,13 @@ static Object sigTag(int argcount, Var v){
return tagOf(sig);
}
return null;
- }
+ }
+
+ // Callsites are only registered in a function context
+ // In KEYWORD/PROTOCOL_CALLSITES, null indicates "do not register"
+ static boolean shouldRegisterCallsites(Var callSiteVar) {
+ return callSiteVar.deref() != null;
+ }
public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args, boolean tailPosition) {
this.source = source;
@@ -3648,7 +4175,7 @@ public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, I
{
Var fvar = ((VarExpr)fexpr).var;
Var pvar = (Var)RT.get(fvar.meta(), protocolKey);
- if(pvar != null && PROTOCOL_CALLSITES.isBound())
+ if(pvar != null && shouldRegisterCallsites(PROTOCOL_CALLSITES))
{
this.isProtocol = true;
this.siteIndex = registerProtocolCallsite(((VarExpr)fexpr).var);
@@ -3870,24 +4397,78 @@ static public Expr parse(C context, ISeq form) {
}
}
- if(fexpr instanceof KeywordExpr && RT.count(form) == 2 && KEYWORD_CALLSITES.isBound())
+ if(fexpr instanceof KeywordExpr && RT.count(form) == 2 && shouldRegisterCallsites(KEYWORD_CALLSITES))
{
// fexpr = new ConstantExpr(new KeywordCallSite(((KeywordExpr)fexpr).k));
Expr target = analyze(context, RT.second(form));
return new KeywordInvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form),
(KeywordExpr) fexpr, target);
}
+
PersistentVector args = PersistentVector.EMPTY;
for(ISeq s = RT.seq(form.next()); s != null; s = s.next())
{
args = args.cons(analyze(context, s.first()));
}
+
+ // Preserving the existing static field syntax that replaces a reference in parens with
+ // the field itself rather than trying to invoke the value in the field. This is
+ // an exception to the uniform Class/member qualification per CLJ-2806 ticket.
+ if(fexpr instanceof StaticFieldExpr)
+ {
+ if(RT.count(args) == 0)
+ return fexpr;
+ else
+ throw new IllegalArgumentException("No matching method " +
+ ((StaticFieldExpr) fexpr).fieldName +
+ " found taking " + RT.count(args) + " args for " +
+ ((StaticFieldExpr) fexpr).c);
+ }
+
+ if(fexpr instanceof QualifiedMethodExpr)
+ {
+ QualifiedMethodExpr qmexpr = (QualifiedMethodExpr)fexpr;
+ if (qmexpr.kind == QualifiedMethodExpr.MethodKind.INSTANCE && RT.count(args) == 0)
+ throw QualifiedMethodExpr.instanceNoTargetException(qmexpr);
+ return toHostExpr(qmexpr, (String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), tailPosition, args);
+ }
+
// if(args.count() > MAX_POSITIONAL_ARITY)
// throw new IllegalArgumentException(
// String.format("No more than %d args supported", MAX_POSITIONAL_ARITY));
return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args, tailPosition);
}
+
+ private static Expr toHostExpr(QualifiedMethodExpr qmexpr, String source, int line, int column, Symbol tag, boolean tailPosition, IPersistentVector args) {
+ if(qmexpr.hintedSig != null) {
+ Executable method = QualifiedMethodExpr.resolveHintedMethod(qmexpr.c, qmexpr.methodName, qmexpr.kind, qmexpr.hintedSig);
+ switch(qmexpr.kind) {
+ case CTOR:
+ return new NewExpr(qmexpr.c, (Constructor) method, args, line, column);
+ case INSTANCE:
+ return new InstanceMethodExpr(source, line, column, tag, (Expr) RT.first(args),
+ qmexpr.c, munge(qmexpr.methodName), (java.lang.reflect.Method) method,
+ PersistentVector.create(RT.next(args)),
+ tailPosition);
+ default:
+ return new StaticMethodExpr(source, line, column, tag, qmexpr.c,
+ munge(qmexpr.methodName), (java.lang.reflect.Method) method, args, tailPosition);
+ }
+ }
+ else {
+ switch(qmexpr.kind) {
+ case CTOR:
+ return new NewExpr(qmexpr.c, args, line, column);
+ case INSTANCE:
+ return new InstanceMethodExpr(source, line, column, tag, (Expr) RT.first(args), qmexpr.c,
+ munge(qmexpr.methodName), PersistentVector.create(RT.next(args)), tailPosition);
+ default:
+ return new StaticMethodExpr(source, line, column, tag, qmexpr.c,
+ munge(qmexpr.methodName), args, tailPosition);
+ }
+ }
+ }
}
static class SourceDebugExtensionAttribute extends Attribute{
@@ -3999,7 +4580,7 @@ CONSTANT_IDS, new IdentityHashMap(),
VARS, PersistentHashMap.EMPTY,
KEYWORD_CALLSITES, PersistentVector.EMPTY,
PROTOCOL_CALLSITES, PersistentVector.EMPTY,
- VAR_CALLSITES, emptyVarCallSites(),
+ //VAR_CALLSITES, emptyVarCallSites(),
NO_RECUR, null
));
@@ -4079,7 +4660,7 @@ else if(methodArray[f.reqParms.count()] == null)
fn.constants = (PersistentVector) CONSTANTS.deref();
fn.keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();
fn.protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();
- fn.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
+ //fn.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
fn.constantsID = RT.nextID();
// DynamicClassLoader loader = (DynamicClassLoader) LOADER.get();
@@ -4184,7 +4765,7 @@ static public class ObjExpr implements Expr{
IPersistentVector keywordCallsites;
IPersistentVector protocolCallsites;
- IPersistentSet varCallsites;
+ //IPersistentSet varCallsites;
boolean onceOnly = false;
Object src;
@@ -4302,7 +4883,7 @@ void compile(String superName, String[] interfaceNames, boolean oneTimeUse) thro
ClassVisitor cv = cw;
// ClassVisitor cv = new TraceClassVisitor(new CheckClassAdapter(cw), new PrintWriter(System.out));
//ClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
- cv.visit(V1_8, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, internalName, null,superName,interfaceNames);
+ cv.visit(JVM_BYTECODE_VERSION, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, internalName, null,superName,interfaceNames);
// superName != null ? superName :
// (isVariadic() ? "clojure/lang/RestFn" : "clojure/lang/AFunction"), null);
String source = (String) SOURCE.deref();
@@ -5228,9 +5809,9 @@ String cachedVarName(int n){
return "__cached_var__" + n;
}
- String varCallsiteName(int n){
- return "__var__callsite__" + n;
- }
+ //String varCallsiteName(int n){
+ // return "__var__callsite__" + n;
+ //}
String thunkNameStatic(int n){
return thunkName(n) + "__";
@@ -5338,9 +5919,8 @@ static FnMethod parse(ObjExpr objx, ISeq form, Object rettag) {
method.line = lineDeref();
method.column = columnDeref();
//register as the current method and set up a new env frame
- PathNode pnode = (PathNode) CLEAR_PATH.get();
- if(pnode == null)
- pnode = new PathNode(PATHTYPE.PATH,null);
+ PathNode pnode = new PathNode(PATHTYPE.PATH,null);
+ method.clearRoot = pnode;
Var.pushThreadBindings(
RT.mapUniqueKeys(
METHOD, method,
@@ -5784,6 +6364,7 @@ abstract public static class ObjMethod{
boolean usesThis = false;
PersistentHashSet localsUsedInCatchFinally = PersistentHashSet.EMPTY;
protected IPersistentMap methodMeta;
+ PathNode clearRoot;
public final IPersistentMap locals(){
@@ -6038,7 +6619,9 @@ public LocalBindingExpr(LocalBinding b, Symbol tag)
}
}
- if(clearRoot == b.clearPathRoot)
+ ObjMethod method = ((ObjMethod) METHOD.deref());
+ boolean closedOver = method.objx.closes.containsKey(b);
+ if(clearRoot == b.clearPathRoot || (closedOver && clearRoot == method.clearRoot))
{
this.shouldClear = true;
sites = RT.conj(sites,this);
@@ -6381,6 +6964,8 @@ public Expr parse(C context, Object frm) {
Symbol sym = (Symbol) bindings.nth(i);
if(sym.getNamespace() != null)
throw Util.runtimeException("Can't let qualified name: " + sym);
+ if(sym.equals(_AMP_))
+ throw Util.runtimeException("Can't use & as a local binding");
Expr init = analyze(C.EXPRESSION, bindings.nth(i + 1), sym.name);
if(isLoop)
{
@@ -6487,7 +7072,10 @@ public void doEmit(C context, ObjExpr objx, GeneratorAdapter gen, boolean emitUn
}
else
{
- bi.init.emit(C.EXPRESSION, objx, gen);
+ Class bindingClass = HostExpr.maybeClass(bi.binding.tag, true);
+ if(!FISupport.maybeEmitFIAdapter(objx, gen, bi.init, bindingClass))
+ bi.init.emit(C.EXPRESSION, objx, gen);
+
if (!bi.binding.used && bi.binding.canBeCleared)
gen.pop();
else
@@ -6656,6 +7244,11 @@ public Expr parse(C context, Object frm) {
int column = columnDeref();
String source = (String) SOURCE.deref();
+ // In :once fn, recur to head invalidates :once
+ ObjMethod method = (ObjMethod)METHOD.deref();
+ if(method.objx.onceOnly && method.clearRoot == CLEAR_ROOT.deref())
+ method.objx.onceOnly = false;
+
ISeq form = (ISeq) frm;
IPersistentVector loopLocals = (IPersistentVector) LOOP_LOCALS.deref();
if(context != C.RETURN || loopLocals == null)
@@ -6862,8 +7455,10 @@ public IPersistentMap getData(){
private static String verb(Keyword phase) {
if(PHASE_READ.equals(phase)){
return "reading source";
- } else if(PHASE_COMPILE_SYNTAX_CHECK.equals(phase) || PHASE_COMPILATION.equals(phase)){
+ } else if(PHASE_COMPILE_SYNTAX_CHECK.equals(phase) || PHASE_COMPILATION.equals(phase)) {
return "compiling";
+ } else if(PHASE_EXECUTION.equals(phase)) {
+ return "executing";
} else {
return "macroexpanding";
}
@@ -7026,7 +7621,8 @@ public static Object macroexpand1(Object x) {
Symbol sym = (Symbol) op;
String sname = sym.name;
//(.substring s 2 5) => (. s substring 2 5)
- if(sym.name.charAt(0) == '.')
+ // ns == null ensures that Class/.instanceMethod isn't expanded to . form
+ if(sym.name.charAt(0) == '.' && sym.ns == null)
{
if(RT.length(form) < 2)
throw new IllegalArgumentException(
@@ -7039,16 +7635,6 @@ public static Object macroexpand1(Object x) {
}
return preserveTag(form, RT.listStar(DOT, target, meth, form.next().next()));
}
- else if(namesStaticMember(sym))
- {
- Symbol target = Symbol.intern(sym.ns);
- Class c = HostExpr.maybeClass(target, false);
- if(c != null)
- {
- Symbol meth = Symbol.intern(sym.name);
- return preserveTag(form, RT.listStar(DOT, target, meth, form.next()));
- }
- }
else
{
//(s.substring 2 5) => (. s substring 2 5)
@@ -7226,9 +7812,6 @@ private static KeywordExpr registerKeyword(Keyword keyword){
}
private static int registerKeywordCallsite(Keyword keyword){
- if(!KEYWORD_CALLSITES.isBound())
- throw new IllegalAccessError("KEYWORD_CALLSITES is not bound");
-
IPersistentVector keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();
keywordCallsites = keywordCallsites.cons(keyword);
@@ -7237,9 +7820,6 @@ private static int registerKeywordCallsite(Keyword keyword){
}
private static int registerProtocolCallsite(Var v){
- if(!PROTOCOL_CALLSITES.isBound())
- throw new IllegalAccessError("PROTOCOL_CALLSITES is not bound");
-
IPersistentVector protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();
protocolCallsites = protocolCallsites.cons(v);
@@ -7247,16 +7827,16 @@ private static int registerProtocolCallsite(Var v){
return protocolCallsites.count()-1;
}
-private static void registerVarCallsite(Var v){
- if(!VAR_CALLSITES.isBound())
- throw new IllegalAccessError("VAR_CALLSITES is not bound");
-
- IPersistentCollection varCallsites = (IPersistentCollection) VAR_CALLSITES.deref();
-
- varCallsites = varCallsites.cons(v);
- VAR_CALLSITES.set(varCallsites);
-// return varCallsites.count()-1;
-}
+//private static void registerVarCallsite(Var v){
+// if(!VAR_CALLSITES.isBound())
+// throw new IllegalAccessError("VAR_CALLSITES is not bound");
+//
+// IPersistentCollection varCallsites = (IPersistentCollection) VAR_CALLSITES.deref();
+//
+// varCallsites = varCallsites.cons(v);
+// VAR_CALLSITES.set(varCallsites);
+//// return varCallsites.count()-1;
+//}
static ISeq fwdPath(PathNode p1){
ISeq ret = null;
@@ -7300,16 +7880,25 @@ private static Expr analyzeSymbol(Symbol sym) {
}
else
{
- if(namespaceFor(sym) == null)
+ if(namespaceFor(sym) == null && !Util.isPosDigit(sym.name))
{
Symbol nsSym = Symbol.intern(sym.ns);
Class c = HostExpr.maybeClass(nsSym, false);
if(c != null)
{
if(Reflector.getField(c, sym.name, true) != null)
- return new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag);
- throw Util.runtimeException("Unable to find static field: " + sym.name + " in " + c);
+ {
+ List maybeOverloads = QualifiedMethodExpr.methodOverloads(c, sym.name, QualifiedMethodExpr.MethodKind.STATIC);
+
+ if(maybeOverloads.isEmpty())
+ return new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag);
+ else
+ return new QualifiedMethodExpr(c, sym, new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag));
+ }
+ else
+ return new QualifiedMethodExpr(c, sym);
}
+
}
}
//Var v = lookupVar(sym, false);
@@ -7381,7 +7970,12 @@ static public Object resolveIn(Namespace n, Symbol sym, boolean allowPrivate) {
{
Namespace ns = namespaceFor(n, sym);
if(ns == null)
+ {
+ Class ac = HostExpr.maybeArrayClass(sym);
+ if(ac != null)
+ return ac;
throw Util.runtimeException("No such namespace: " + sym.ns);
+ }
Var v = ns.findInternedVar(Symbol.intern(sym.name));
if(v == null)
@@ -7425,7 +8019,7 @@ static public Object maybeResolveIn(Namespace n, Symbol sym) {
{
Namespace ns = namespaceFor(n, sym);
if(ns == null)
- return null;
+ return HostExpr.maybeArrayClass(sym);
Var v = ns.findInternedVar(Symbol.intern(sym.name));
if(v == null)
return null;
@@ -7539,7 +8133,6 @@ static void closeOver(LocalBinding b, ObjMethod method){
}
}
-
static LocalBinding referenceLocal(Symbol sym) {
if(!LOCAL_ENV.isBound())
return null;
@@ -7761,6 +8354,9 @@ public static Object compile(Reader rdr, String sourcePath, String sourceName) t
COLUMN_AFTER, pushbackReader.getColumnNumber(),
CONSTANTS, PersistentVector.EMPTY,
CONSTANT_IDS, new IdentityHashMap(),
+ KEYWORD_CALLSITES, null,
+ PROTOCOL_CALLSITES, null,
+ //VAR_CALLSITES, null,
KEYWORDS, PersistentHashMap.EMPTY,
VARS, PersistentHashMap.EMPTY
,RT.UNCHECKED_MATH, RT.UNCHECKED_MATH.deref()
@@ -7779,7 +8375,7 @@ CONSTANT_IDS, new IdentityHashMap(),
objx.objtype = Type.getObjectType(objx.internalName);
ClassWriter cw = classWriter();
ClassVisitor cv = cw;
- cv.visit(V1_8, ACC_PUBLIC + ACC_SUPER, objx.internalName, null, "java/lang/Object", null);
+ cv.visit(JVM_BYTECODE_VERSION, ACC_PUBLIC + ACC_SUPER, objx.internalName, null, "java/lang/Object", null);
//static load method
GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
@@ -7807,7 +8403,7 @@ CONSTANT_IDS, new IdentityHashMap(),
for(int i = 0; i < objx.constants.count(); i++)
{
if(objx.usedConstants.contains(i))
- cv.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, objx.constantName(i), objx.constantType(i).getDescriptor(),
+ cv.visitField(ACC_PUBLIC + ACC_STATIC, objx.constantName(i), objx.constantType(i).getDescriptor(),
null, null);
}
@@ -7958,9 +8554,11 @@ public Expr parse(C context, Object frm) {
ObjExpr ret = build(interfaces, null, null, classname, Symbol.intern(classname), null, rform, frm, null);
- if(frm instanceof IObj && ((IObj) frm).meta() != null)
- return new MetaExpr(ret, MapExpr
- .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) frm).meta()));
+ IPersistentMap fmeta = RT.meta(frm);
+ if(fmeta != null)
+ fmeta = fmeta.without(RT.LINE_KEY).without(RT.COLUMN_KEY).without(RT.FILE_KEY);
+ if (RT.count(fmeta) > 0)
+ return new MetaExpr(ret, MapExpr.parse(context == C.EVAL ? context : C.EXPRESSION, fmeta));
else
return ret;
}
@@ -8038,7 +8636,7 @@ CONSTANT_IDS, new IdentityHashMap(),
VARS, PersistentHashMap.EMPTY,
KEYWORD_CALLSITES, PersistentVector.EMPTY,
PROTOCOL_CALLSITES, PersistentVector.EMPTY,
- VAR_CALLSITES, emptyVarCallSites(),
+ //VAR_CALLSITES, emptyVarCallSites(),
NO_RECUR, null));
if(ret.isDeftype())
{
@@ -8068,7 +8666,7 @@ VAR_CALLSITES, emptyVarCallSites(),
ret.constantsID = RT.nextID();
ret.keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();
ret.protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();
- ret.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
+ //ret.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
}
finally
{
@@ -8099,7 +8697,7 @@ VAR_CALLSITES, emptyVarCallSites(),
static Class compileStub(String superName, NewInstanceExpr ret, String[] interfaceNames, Object frm){
ClassWriter cw = classWriter();
ClassVisitor cv = cw;
- cv.visit(V1_8, ACC_PUBLIC + ACC_SUPER, COMPILE_STUB_PREFIX + "/" + ret.internalName,
+ cv.visit(JVM_BYTECODE_VERSION, ACC_PUBLIC + ACC_SUPER, COMPILE_STUB_PREFIX + "/" + ret.internalName,
null,superName,interfaceNames);
//instance fields for closed-overs
@@ -8444,7 +9042,8 @@ static NewInstanceMethod parse(ObjExpr objx, ISeq form, Symbol thistag,
method.line = lineDeref();
method.column = columnDeref();
//register as the current method and set up a new env frame
- PathNode pnode = new PathNode(PATHTYPE.PATH, (PathNode) CLEAR_PATH.get());
+ PathNode pnode = new PathNode(PATHTYPE.PATH, null);
+ method.clearRoot = pnode;
Var.pushThreadBindings(
RT.mapUniqueKeys(
METHOD, method,
@@ -8654,7 +9253,7 @@ static Class retType(Class tc, Class ret){
return tc;
}
- static Class primClass(Symbol sym){
+ public static Class primClass(Symbol sym){
if(sym == null)
return null;
Class c = null;
diff --git a/src/jvm/clojure/lang/Cycle.java b/src/jvm/clojure/lang/Cycle.java
index a605e8ca20..293463090c 100644
--- a/src/jvm/clojure/lang/Cycle.java
+++ b/src/jvm/clojure/lang/Cycle.java
@@ -95,4 +95,13 @@ public Object reduce(IFn f, Object start){
s = all;
}
}
+
+public int hashCode(){
+ throw new UnsupportedOperationException();
+}
+
+public int hasheq(){
+ throw new UnsupportedOperationException();
+}
+
}
diff --git a/src/jvm/clojure/lang/Delay.java b/src/jvm/clojure/lang/Delay.java
index ffd418962d..f0aa855d20 100644
--- a/src/jvm/clojure/lang/Delay.java
+++ b/src/jvm/clojure/lang/Delay.java
@@ -12,15 +12,20 @@
package clojure.lang;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
public class Delay implements IDeref, IPending{
-volatile Object val;
-volatile Throwable exception;
-volatile IFn fn;
-
-public Delay(IFn fn){
- this.fn = fn;
- this.val = null;
- this.exception = null;
+Object val;
+Throwable exception;
+IFn fn;
+volatile Lock lock;
+
+public Delay(IFn f){
+ fn = f;
+ val = null;
+ exception = null;
+ lock = new ReentrantLock();
}
static public Object force(Object x) {
@@ -29,32 +34,35 @@ static public Object force(Object x) {
: x;
}
-public Object deref() {
- if(fn != null)
- {
- synchronized(this)
- {
- //double check
- if(fn!=null)
- {
- try
- {
- val = fn.invoke();
- }
- catch(Throwable t)
- {
- exception = t;
- }
- fn = null;
- }
- }
+private void realize() {
+ Lock l = lock;
+ if(l != null) {
+ l.lock();
+ try {
+ if(fn!=null) {
+ try {
+ val = fn.invoke();
+ } catch (Throwable t) {
+ exception = t;
+ }
+ fn = null;
+ lock = null;
+ }
+ } finally {
+ l.unlock();
}
+ }
+}
+
+public Object deref() {
+ if(lock != null)
+ realize();
if(exception != null)
throw Util.sneakyThrow(exception);
return val;
}
public boolean isRealized(){
- return fn == null;
+ return lock == null;
}
}
diff --git a/src/jvm/clojure/lang/ExceptionInfo.java b/src/jvm/clojure/lang/ExceptionInfo.java
index f7b704deab..28f845b7c3 100644
--- a/src/jvm/clojure/lang/ExceptionInfo.java
+++ b/src/jvm/clojure/lang/ExceptionInfo.java
@@ -28,11 +28,7 @@ public ExceptionInfo(String s, IPersistentMap data) {
public ExceptionInfo(String s, IPersistentMap data, Throwable throwable) {
// null cause is equivalent to not passing a cause
super(s, throwable);
- if (data != null) {
- this.data = data;
- } else {
- throw new IllegalArgumentException("Additional data must be non-nil.");
- }
+ this.data = (data == null) ? PersistentArrayMap.EMPTY: data;
}
public IPersistentMap getData() {
diff --git a/src/jvm/clojure/lang/FnInvokers.java b/src/jvm/clojure/lang/FnInvokers.java
new file mode 100644
index 0000000000..34cbaa548b
--- /dev/null
+++ b/src/jvm/clojure/lang/FnInvokers.java
@@ -0,0 +1,782 @@
+/**
+ * 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';
+ }
+ }
+
+ public static long invokeL(IFn f0) {
+ if(f0 instanceof IFn.L) {
+ return ((IFn.L)f0).invokePrim();
+ } else {
+ return RT.longCast(f0.invoke());
+ }
+ }
+
+ public static int invokeI(IFn f0) {
+ if(f0 instanceof IFn.L) {
+ return RT.intCast(((IFn.L)f0).invokePrim());
+ } else {
+ return RT.intCast(f0.invoke());
+ }
+ }
+
+ public static short invokeS(IFn f0) {
+ if(f0 instanceof IFn.L) {
+ return RT.shortCast(((IFn.L)f0).invokePrim());
+ } else {
+ return RT.shortCast(f0.invoke());
+ }
+ }
+
+ public static byte invokeB(IFn f0) {
+ if(f0 instanceof IFn.L) {
+ return RT.byteCast(((IFn.L)f0).invokePrim());
+ } else {
+ return RT.byteCast(f0.invoke());
+ }
+ }
+
+ public static double invokeD(IFn f0) {
+ if(f0 instanceof IFn.D) {
+ return ((IFn.D)f0).invokePrim();
+ } else {
+ return RT.doubleCast(f0.invoke());
+ }
+ }
+
+ public static float invokeF(IFn f0) {
+ if(f0 instanceof IFn.D) {
+ return RT.floatCast(((IFn.D)f0).invokePrim());
+ } else {
+ return RT.floatCast(f0.invoke());
+ }
+ }
+
+ public static Object invokeO(IFn f0) {
+ return f0.invoke();
+ }
+
+ public static long invokeLL(IFn f0, long a) {
+ if(f0 instanceof IFn.LL) {
+ return ((IFn.LL)f0).invokePrim(a);
+ } else {
+ return RT.longCast(f0.invoke(a));
+ }
+ }
+
+ public static long invokeDL(IFn f0, double a) {
+ if(f0 instanceof IFn.DL) {
+ return ((IFn.DL)f0).invokePrim(a);
+ } else {
+ return RT.longCast(f0.invoke(a));
+ }
+ }
+
+ public static long invokeOL(IFn f0, Object a) {
+ if(f0 instanceof IFn.OL) {
+ return ((IFn.OL)f0).invokePrim(a);
+ } else {
+ return RT.longCast(f0.invoke(a));
+ }
+ }
+
+ public static int invokeLI(IFn f0, long a) {
+ if(f0 instanceof IFn.LL) {
+ return RT.intCast(((IFn.LL)f0).invokePrim(a));
+ } else {
+ return RT.intCast(f0.invoke(a));
+ }
+ }
+
+ public static int invokeDI(IFn f0, double a) {
+ if(f0 instanceof IFn.DL) {
+ return RT.intCast(((IFn.DL)f0).invokePrim(a));
+ } else {
+ return RT.intCast(f0.invoke(a));
+ }
+ }
+
+ public static int invokeOI(IFn f0, Object a) {
+ if(f0 instanceof IFn.OL) {
+ return RT.intCast(((IFn.OL)f0).invokePrim(a));
+ } else {
+ return RT.intCast(f0.invoke(a));
+ }
+ }
+
+ public static short invokeLS(IFn f0, long a) {
+ if(f0 instanceof IFn.LL) {
+ return RT.shortCast(((IFn.LL)f0).invokePrim(a));
+ } else {
+ return RT.shortCast(f0.invoke(a));
+ }
+ }
+
+ public static short invokeDS(IFn f0, double a) {
+ if(f0 instanceof IFn.DL) {
+ return RT.shortCast(((IFn.DL)f0).invokePrim(a));
+ } else {
+ return RT.shortCast(f0.invoke(a));
+ }
+ }
+
+ public static short invokeOS(IFn f0, Object a) {
+ if(f0 instanceof IFn.OL) {
+ return RT.shortCast(((IFn.OL)f0).invokePrim(a));
+ } else {
+ return RT.shortCast(f0.invoke(a));
+ }
+ }
+
+ public static byte invokeLB(IFn f0, long a) {
+ if(f0 instanceof IFn.LL) {
+ return RT.byteCast(((IFn.LL)f0).invokePrim(a));
+ } else {
+ return RT.byteCast(f0.invoke(a));
+ }
+ }
+
+ public static byte invokeDB(IFn f0, double a) {
+ if(f0 instanceof IFn.DL) {
+ return RT.byteCast(((IFn.DL)f0).invokePrim(a));
+ } else {
+ return RT.byteCast(f0.invoke(a));
+ }
+ }
+
+ public static byte invokeOB(IFn f0, Object a) {
+ if(f0 instanceof IFn.OL) {
+ return RT.byteCast(((IFn.OL)f0).invokePrim(a));
+ } else {
+ return RT.byteCast(f0.invoke(a));
+ }
+ }
+
+ public static double invokeLD(IFn f0, long a) {
+ if(f0 instanceof IFn.LD) {
+ return ((IFn.LD)f0).invokePrim(a);
+ } else {
+ return RT.doubleCast(f0.invoke(a));
+ }
+ }
+
+ public static double invokeDD(IFn f0, double a) {
+ if(f0 instanceof IFn.DD) {
+ return ((IFn.DD)f0).invokePrim(a);
+ } else {
+ return RT.doubleCast(f0.invoke(a));
+ }
+ }
+
+ public static double invokeOD(IFn f0, Object a) {
+ if(f0 instanceof IFn.OD) {
+ return ((IFn.OD)f0).invokePrim(a);
+ } else {
+ return RT.doubleCast(f0.invoke(a));
+ }
+ }
+
+ public static float invokeLF(IFn f0, long a) {
+ if(f0 instanceof IFn.LD) {
+ return RT.floatCast(((IFn.LD)f0).invokePrim(a));
+ } else {
+ return RT.floatCast(f0.invoke(a));
+ }
+ }
+
+ public static float invokeDF(IFn f0, double a) {
+ if(f0 instanceof IFn.DD) {
+ return RT.floatCast(((IFn.DD)f0).invokePrim(a));
+ } else {
+ return RT.floatCast(f0.invoke(a));
+ }
+ }
+
+ public static float invokeOF(IFn f0, Object a) {
+ if(f0 instanceof IFn.OD) {
+ return RT.floatCast(((IFn.OD)f0).invokePrim(a));
+ } else {
+ return RT.floatCast(f0.invoke(a));
+ }
+ }
+
+ public static Object invokeLO(IFn f0, long a) {
+ if(f0 instanceof IFn.LO) {
+ return ((IFn.LO)f0).invokePrim(a);
+ } else {
+ return f0.invoke(a);
+ }
+ }
+
+ public static Object invokeDO(IFn f0, double a) {
+ if(f0 instanceof IFn.DO) {
+ return ((IFn.DO)f0).invokePrim(a);
+ } else {
+ return f0.invoke(a);
+ }
+ }
+
+ public static Object invokeOO(IFn f0, Object a) {
+ return f0.invoke(a);
+ }
+
+ public static long invokeLLL(IFn f0, long a, long b) {
+ if(f0 instanceof IFn.LLL) {
+ return ((IFn.LLL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeLOL(IFn f0, long a, Object b) {
+ if(f0 instanceof IFn.LOL) {
+ return ((IFn.LOL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeOLL(IFn f0, Object a, long b) {
+ if(f0 instanceof IFn.OLL) {
+ return ((IFn.OLL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeDDL(IFn f0, double a, double b) {
+ if(f0 instanceof IFn.DDL) {
+ return ((IFn.DDL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeLDL(IFn f0, long a, double b) {
+ if(f0 instanceof IFn.LDL) {
+ return ((IFn.LDL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeDLL(IFn f0, double a, long b) {
+ if(f0 instanceof IFn.DLL) {
+ return ((IFn.DLL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeOOL(IFn f0, Object a, Object b) {
+ if(f0 instanceof IFn.OOL) {
+ return ((IFn.OOL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeODL(IFn f0, Object a, double b) {
+ if(f0 instanceof IFn.ODL) {
+ return ((IFn.ODL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static long invokeDOL(IFn f0, double a, Object b) {
+ if(f0 instanceof IFn.DOL) {
+ return ((IFn.DOL)f0).invokePrim(a, b);
+ } else {
+ return RT.longCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeLLI(IFn f0, long a, long b) {
+ if(f0 instanceof IFn.LLL) {
+ return RT.intCast(((IFn.LLL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeLOI(IFn f0, long a, Object b) {
+ if(f0 instanceof IFn.LOL) {
+ return RT.intCast(((IFn.LOL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeOLI(IFn f0, Object a, long b) {
+ if(f0 instanceof IFn.OLL) {
+ return RT.intCast(((IFn.OLL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeDDI(IFn f0, double a, double b) {
+ if(f0 instanceof IFn.DDL) {
+ return RT.intCast(((IFn.DDL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeLDI(IFn f0, long a, double b) {
+ if(f0 instanceof IFn.LDL) {
+ return RT.intCast(((IFn.LDL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeDLI(IFn f0, double a, long b) {
+ if(f0 instanceof IFn.DLL) {
+ return RT.intCast(((IFn.DLL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeOOI(IFn f0, Object a, Object b) {
+ if(f0 instanceof IFn.OOL) {
+ return RT.intCast(((IFn.OOL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeODI(IFn f0, Object a, double b) {
+ if(f0 instanceof IFn.ODL) {
+ return RT.intCast(((IFn.ODL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static int invokeDOI(IFn f0, double a, Object b) {
+ if(f0 instanceof IFn.DOL) {
+ return RT.intCast(((IFn.DOL)f0).invokePrim(a, b));
+ } else {
+ return RT.intCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeLLS(IFn f0, long a, long b) {
+ if(f0 instanceof IFn.LLL) {
+ return RT.shortCast(((IFn.LLL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeLOS(IFn f0, long a, Object b) {
+ if(f0 instanceof IFn.LOL) {
+ return RT.shortCast(((IFn.LOL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeOLS(IFn f0, Object a, long b) {
+ if(f0 instanceof IFn.OLL) {
+ return RT.shortCast(((IFn.OLL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeDDS(IFn f0, double a, double b) {
+ if(f0 instanceof IFn.DDL) {
+ return RT.shortCast(((IFn.DDL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeLDS(IFn f0, long a, double b) {
+ if(f0 instanceof IFn.LDL) {
+ return RT.shortCast(((IFn.LDL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeDLS(IFn f0, double a, long b) {
+ if(f0 instanceof IFn.DLL) {
+ return RT.shortCast(((IFn.DLL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeOOS(IFn f0, Object a, Object b) {
+ if(f0 instanceof IFn.OOL) {
+ return RT.shortCast(((IFn.OOL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeODS(IFn f0, Object a, double b) {
+ if(f0 instanceof IFn.ODL) {
+ return RT.shortCast(((IFn.ODL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static short invokeDOS(IFn f0, double a, Object b) {
+ if(f0 instanceof IFn.DOL) {
+ return RT.shortCast(((IFn.DOL)f0).invokePrim(a, b));
+ } else {
+ return RT.shortCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeLLB(IFn f0, long a, long b) {
+ if(f0 instanceof IFn.LLL) {
+ return RT.byteCast(((IFn.LLL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeLOB(IFn f0, long a, Object b) {
+ if(f0 instanceof IFn.LOL) {
+ return RT.byteCast(((IFn.LOL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeOLB(IFn f0, Object a, long b) {
+ if(f0 instanceof IFn.OLL) {
+ return RT.byteCast(((IFn.OLL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeDDB(IFn f0, double a, double b) {
+ if(f0 instanceof IFn.DDL) {
+ return RT.byteCast(((IFn.DDL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeLDB(IFn f0, long a, double b) {
+ if(f0 instanceof IFn.LDL) {
+ return RT.byteCast(((IFn.LDL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeDLB(IFn f0, double a, long b) {
+ if(f0 instanceof IFn.DLL) {
+ return RT.byteCast(((IFn.DLL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeOOB(IFn f0, Object a, Object b) {
+ if(f0 instanceof IFn.OOL) {
+ return RT.byteCast(((IFn.OOL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeODB(IFn f0, Object a, double b) {
+ if(f0 instanceof IFn.ODL) {
+ return RT.byteCast(((IFn.ODL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static byte invokeDOB(IFn f0, double a, Object b) {
+ if(f0 instanceof IFn.DOL) {
+ return RT.byteCast(((IFn.DOL)f0).invokePrim(a, b));
+ } else {
+ return RT.byteCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeLLD(IFn f0, long a, long b) {
+ if(f0 instanceof IFn.LLD) {
+ return ((IFn.LLD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeLOD(IFn f0, long a, Object b) {
+ if(f0 instanceof IFn.LOD) {
+ return ((IFn.LOD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeOLD(IFn f0, Object a, long b) {
+ if(f0 instanceof IFn.OLD) {
+ return ((IFn.OLD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeDDD(IFn f0, double a, double b) {
+ if(f0 instanceof IFn.DDD) {
+ return ((IFn.DDD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeLDD(IFn f0, long a, double b) {
+ if(f0 instanceof IFn.LDD) {
+ return ((IFn.LDD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeDLD(IFn f0, double a, long b) {
+ if(f0 instanceof IFn.DLD) {
+ return ((IFn.DLD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeOOD(IFn f0, Object a, Object b) {
+ if(f0 instanceof IFn.OOD) {
+ return ((IFn.OOD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeODD(IFn f0, Object a, double b) {
+ if(f0 instanceof IFn.ODD) {
+ return ((IFn.ODD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static double invokeDOD(IFn f0, double a, Object b) {
+ if(f0 instanceof IFn.DOD) {
+ return ((IFn.DOD)f0).invokePrim(a, b);
+ } else {
+ return RT.doubleCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeLLF(IFn f0, long a, long b) {
+ if(f0 instanceof IFn.LLD) {
+ return RT.floatCast(((IFn.LLD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeLOF(IFn f0, long a, Object b) {
+ if(f0 instanceof IFn.LOD) {
+ return RT.floatCast(((IFn.LOD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeOLF(IFn f0, Object a, long b) {
+ if(f0 instanceof IFn.OLD) {
+ return RT.floatCast(((IFn.OLD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeDDF(IFn f0, double a, double b) {
+ if(f0 instanceof IFn.DDD) {
+ return RT.floatCast(((IFn.DDD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeLDF(IFn f0, long a, double b) {
+ if(f0 instanceof IFn.LDD) {
+ return RT.floatCast(((IFn.LDD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeDLF(IFn f0, double a, long b) {
+ if(f0 instanceof IFn.DLD) {
+ return RT.floatCast(((IFn.DLD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeOOF(IFn f0, Object a, Object b) {
+ if(f0 instanceof IFn.OOD) {
+ return RT.floatCast(((IFn.OOD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeODF(IFn f0, Object a, double b) {
+ if(f0 instanceof IFn.ODD) {
+ return RT.floatCast(((IFn.ODD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static float invokeDOF(IFn f0, double a, Object b) {
+ if(f0 instanceof IFn.DOD) {
+ return RT.floatCast(((IFn.DOD)f0).invokePrim(a, b));
+ } else {
+ return RT.floatCast(f0.invoke(a, b));
+ }
+ }
+
+ public static Object invokeLLO(IFn f0, long a, long b) {
+ if(f0 instanceof IFn.LLO) {
+ return ((IFn.LLO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeLOO(IFn f0, long a, Object b) {
+ if(f0 instanceof IFn.LOO) {
+ return ((IFn.LOO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeOLO(IFn f0, Object a, long b) {
+ if(f0 instanceof IFn.OLO) {
+ return ((IFn.OLO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeDDO(IFn f0, double a, double b) {
+ if(f0 instanceof IFn.DDO) {
+ return ((IFn.DDO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeLDO(IFn f0, long a, double b) {
+ if(f0 instanceof IFn.LDO) {
+ return ((IFn.LDO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeDLO(IFn f0, double a, long b) {
+ if(f0 instanceof IFn.DLO) {
+ return ((IFn.DLO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeOOO(IFn f0, Object a, Object b) {
+ return f0.invoke(a, b);
+ }
+
+ public static Object invokeODO(IFn f0, Object a, double b) {
+ if(f0 instanceof IFn.ODO) {
+ return ((IFn.ODO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeDOO(IFn f0, double a, Object b) {
+ if(f0 instanceof IFn.DOO) {
+ return ((IFn.DOO)f0).invokePrim(a, b);
+ } else {
+ return f0.invoke(a, b);
+ }
+ }
+
+ public static Object invokeOOOO(IFn f0, Object a, Object b, Object c) {
+ return f0.invoke(a, b, c);
+ }
+
+ public static Object invokeOOOOO(IFn f0, Object a, Object b, Object c, Object d) {
+ return f0.invoke(a, b, c, d);
+ }
+
+ public static Object invokeOOOOOO(IFn f0, Object a, Object b, Object c, Object d, Object e) {
+ return f0.invoke(a, b, c, d, e);
+ }
+
+ public static Object invokeOOOOOOO(IFn f0, Object a, Object b, Object c, Object d, Object e, Object f) {
+ return f0.invoke(a, b, c, d, e, f);
+ }
+
+ public static Object invokeOOOOOOOO(IFn f0, Object a, Object b, Object c, Object d, Object e, Object f, Object g) {
+ return f0.invoke(a, b, c, d, e, f, g);
+ }
+
+ public static Object invokeOOOOOOOOO(IFn f0, Object a, Object b, Object c, Object d, Object e, Object f, Object g, Object h) {
+ return f0.invoke(a, b, c, d, e, f, g, h);
+ }
+
+ public static Object invokeOOOOOOOOOO(IFn f0, Object a, Object b, Object c, Object d, Object e, Object f, Object g, Object h, Object i) {
+ return f0.invoke(a, b, c, d, e, f, g, h, i);
+ }
+
+ public static Object invokeOOOOOOOOOOO(IFn f0, Object a, Object b, Object c, Object d, Object e, Object f, Object g, Object h, Object i, Object j) {
+ return f0.invoke(a, b, c, d, e, f, g, h, i, j);
+ }
+
+}
\ No newline at end of file
diff --git a/src/jvm/clojure/lang/IDeref.java b/src/jvm/clojure/lang/IDeref.java
index 3a4746157b..0eeaaf329d 100644
--- a/src/jvm/clojure/lang/IDeref.java
+++ b/src/jvm/clojure/lang/IDeref.java
@@ -12,6 +12,38 @@
package clojure.lang;
-public interface IDeref{
+import java.util.function.BooleanSupplier;
+import java.util.function.DoubleSupplier;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
+
+public interface IDeref extends Supplier, BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier {
Object deref() ;
+
+@Override
+default Object get() {
+ return deref();
+}
+
+@Override
+default boolean getAsBoolean() {
+ return RT.booleanCast(deref());
+}
+
+@Override
+default int getAsInt() {
+ return RT.intCast(deref());
+}
+
+@Override
+default long getAsLong() {
+ return RT.longCast(deref());
+}
+
+@Override
+default double getAsDouble() {
+ return RT.doubleCast(deref());
+}
+
}
diff --git a/src/jvm/clojure/lang/IDrop.java b/src/jvm/clojure/lang/IDrop.java
index 336c0a8270..153ab0858f 100644
--- a/src/jvm/clojure/lang/IDrop.java
+++ b/src/jvm/clojure/lang/IDrop.java
@@ -20,11 +20,8 @@ public interface IDrop{
* useful if the returned coll implements IDrop for subsequent use in a
* partition-like scenario.
*
- * If n is <= 0, return this.
- * If n drops to or past the end of the collection, return null.
- *
- * @param n Items to drop
- * @return Collection that is Sequential, ISeq, and IReduceInit
+ * @param n Items to drop, must be > 0
+ * @return Collection that is Sequential, ISeq, and IReduceInit, or null if past the end
*/
Sequential drop(int n);
}
diff --git a/src/jvm/clojure/lang/IPersistentSet.java b/src/jvm/clojure/lang/IPersistentSet.java
index 144d15a300..246c9573fa 100644
--- a/src/jvm/clojure/lang/IPersistentSet.java
+++ b/src/jvm/clojure/lang/IPersistentSet.java
@@ -12,8 +12,20 @@
package clojure.lang;
-public interface IPersistentSet extends IPersistentCollection, Counted{
+public interface IPersistentSet extends IPersistentCollection, Counted, ILookup{
public IPersistentSet disjoin(Object key) ;
public boolean contains(Object key);
public Object get(Object key);
+
+ @Override
+ default Object valAt(Object key) {
+ return get(key);
+ }
+
+ @Override
+ default Object valAt(Object key, Object notFound) {
+ if(contains(key))
+ return get(key);
+ return notFound;
+ }
}
diff --git a/src/jvm/clojure/lang/ITransientSet.java b/src/jvm/clojure/lang/ITransientSet.java
index 7d1ec51721..4c526377bb 100644
--- a/src/jvm/clojure/lang/ITransientSet.java
+++ b/src/jvm/clojure/lang/ITransientSet.java
@@ -12,8 +12,20 @@
package clojure.lang;
-public interface ITransientSet extends ITransientCollection, Counted{
+public interface ITransientSet extends ITransientCollection, Counted, ILookup{
public ITransientSet disjoin(Object key) ;
public boolean contains(Object key);
public Object get(Object key);
+
+ @Override
+ default Object valAt(Object key) {
+ return get(key);
+ }
+
+ @Override
+ default Object valAt(Object key, Object notFound) {
+ if(contains(key))
+ return get(key);
+ return notFound;
+ }
}
diff --git a/src/jvm/clojure/lang/Iterate.java b/src/jvm/clojure/lang/Iterate.java
index 0bbe733d94..a0562bce49 100644
--- a/src/jvm/clojure/lang/Iterate.java
+++ b/src/jvm/clojure/lang/Iterate.java
@@ -12,6 +12,8 @@
/* Alex Miller, Dec 5, 2014 */
+import java.io.IOException;
+
public class Iterate extends ASeq implements IReduce, IPending {
private static final long serialVersionUID = -78221705247226450L;
@@ -86,4 +88,23 @@ public Object reduce(IFn rf, Object start){
v = f.invoke(v);
}
}
+
+public int hashCode(){
+ throw new UnsupportedOperationException();
+}
+
+public int hasheq(){
+ throw new UnsupportedOperationException();
+}
+
+// serialization not supported
+private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ throw new UnsupportedOperationException();
+}
+
+// deserialization not supported
+private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+ throw new UnsupportedOperationException();
+}
+
}
diff --git a/src/jvm/clojure/lang/LazySeq.java b/src/jvm/clojure/lang/LazySeq.java
index f9f9bfb24e..cb8c617bfa 100644
--- a/src/jvm/clojure/lang/LazySeq.java
+++ b/src/jvm/clojure/lang/LazySeq.java
@@ -12,24 +12,30 @@
package clojure.lang;
+import java.io.IOException;
+import java.io.ObjectInputStream;
import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
public final class LazySeq extends Obj implements ISeq, Sequential, List, IPending, IHashEq{
-private static final long serialVersionUID = 7700080124382322592L;
+private static final long serialVersionUID = -7531333024710395876L;
-private IFn fn;
+private transient IFn fn;
private Object sv;
private ISeq s;
+private volatile Lock lock;
-public LazySeq(IFn fn){
- this.fn = fn;
+public LazySeq(IFn f){
+ fn = f;
+ lock = new ReentrantLock();
}
-private LazySeq(IPersistentMap meta, ISeq s){
+private LazySeq(IPersistentMap meta, ISeq seq){
super(meta);
- this.fn = null;
- this.s = s;
+ fn = null;
+ s = seq;
}
public Obj withMeta(IPersistentMap meta){
@@ -38,29 +44,64 @@ public Obj withMeta(IPersistentMap meta){
return new LazySeq(meta, seq());
}
-final synchronized Object sval(){
- if(fn != null)
- {
- sv = fn.invoke();
- fn = null;
+// MUST be locked when called!
+final private void force() {
+ if (fn != null) {
+ sv = fn.invoke();
+ fn = null;
+ }
+}
+
+final private Object sval() {
+ Lock l = lock;
+ if(l != null) {
+ l.lock();
+ try {
+ //must re-examine under lock
+ if(lock != null) { //unrealized
+ force();
+ return sv;
+ }
+ } finally {
+ l.unlock();
+ }
+ }
+ // realized, read of lock above guarantees visibility of s
+ return s;
+}
+
+final private Object unwrap(Object ls){
+ while(ls instanceof LazySeq) {
+ ls = ((LazySeq) ls).sval();
+ }
+ return ls;
+}
+
+final private void realize() {
+ Lock l = lock;
+ if(l != null) {
+ l.lock();
+ try {
+ //must re-examine under lock
+ if(lock != null) {
+ force();
+ Object ls = sv;
+ sv = null;
+ if(ls instanceof LazySeq)
+ ls = unwrap(ls);
+ s = RT.seq(ls);
+ lock = null;
+ }
+ }
+ finally {
+ l.unlock();
}
- if(sv != null)
- return sv;
- return s;
+ }
}
-final synchronized public ISeq seq(){
- sval();
- if(sv != null)
- {
- Object ls = sv;
- sv = null;
- while(ls instanceof LazySeq)
- {
- ls = ((LazySeq)ls).sval();
- }
- s = RT.seq(ls);
- }
+public final ISeq seq(){
+ if(lock != null)
+ realize();
return s;
}
@@ -243,8 +284,18 @@ public boolean addAll(int index, Collection c){
throw new UnsupportedOperationException();
}
+public boolean isRealized(){
+ return lock == null;
+}
-synchronized public boolean isRealized(){
- return fn == null;
+// custom Serializable implementation - ensure seq is fully-realized before writing
+private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ ISeq s = this;
+ while(s != null) {
+ s = s.next();
+ }
+ out.defaultWriteObject();
}
+
}
+
diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java
index 9fcb7b5aa9..8d7079cf5a 100644
--- a/src/jvm/clojure/lang/LispReader.java
+++ b/src/jvm/clojure/lang/LispReader.java
@@ -64,6 +64,7 @@ public class LispReader{
static IFn[] dispatchMacros = new IFn[256];
//static Pattern symbolPat = Pattern.compile("[:]?([\\D&&[^:/]][^:/]*/)?[\\D&&[^:/]][^:/]*");
static Pattern symbolPat = Pattern.compile("[:]?([\\D&&[^/]].*/)?(/|[\\D&&[^/]][^/]*)");
+static Pattern arraySymbolPat = Pattern.compile("([\\D&&[^/:]].*)/([1-9])");
//static Pattern varPat = Pattern.compile("([\\D&&[^:\\.]][^:\\.]*):([\\D&&[^:\\.]][^:\\.]*)");
//static Pattern intPat = Pattern.compile("[-+]?[0-9]+\\.?");
static Pattern intPat =
@@ -71,6 +72,7 @@ public class LispReader{
"([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?");
static Pattern ratioPat = Pattern.compile("([-+]?[0-9]+)/([0-9]+)");
static Pattern floatPat = Pattern.compile("([-+]?[0-9]+(\\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?");
+static Pattern argPat = Pattern.compile("%(?:(&)|([1-9][0-9]*))?");
//static Pattern accessorPat = Pattern.compile("\\.[a-zA-Z_]\\w*");
//static Pattern instanceMemberPat = Pattern.compile("\\.([a-zA-Z_][\\w\\.]*)\\.([a-zA-Z_]\\w*)");
//static Pattern staticMemberPat = Pattern.compile("([a-zA-Z_][\\w\\.]*)\\.([a-zA-Z_]\\w*)");
@@ -460,6 +462,12 @@ private static Object matchSymbol(String s, Resolver resolver){
return Keyword.intern(sym);
return sym;
}
+ else
+ {
+ Matcher am = arraySymbolPat.matcher(s);
+ if(am.matches())
+ return Symbol.intern(am.group(1), am.group(2));
+ }
return null;
}
@@ -920,23 +928,16 @@ static Symbol registerArg(int n){
static class ArgReader extends AFn{
public Object invoke(Object reader, Object pct, Object opts, Object pendingForms) {
PushbackReader r = (PushbackReader) reader;
+ String token = readToken(r, '%');
if(ARG_ENV.deref() == null)
- {
- return interpretToken(readToken(r, '%'), null);
- }
- int ch = read1(r);
- unread(r, ch);
- //% alone is first arg
- if(ch == -1 || isWhitespace(ch) || isTerminatingMacro(ch))
- {
- return registerArg(1);
- }
- Object n = read(r, true, null, true, opts, ensurePending(pendingForms));
- if(n.equals(Compiler._AMP_))
- return registerArg(-1);
- if(!(n instanceof Number))
+ return interpretToken(token, null);
+
+ Matcher m = argPat.matcher(token);
+ if (!m.matches())
throw new IllegalStateException("arg literal must be %, %& or %integer");
- return registerArg(((Number) n).intValue());
+ if (m.group(1) != null) // %&
+ return registerArg(-1);
+ return registerArg(m.group(2) == null ? 1 : Integer.parseInt(m.group(2)));
}
}
@@ -956,8 +957,10 @@ public Object invoke(Object reader, Object caret, Object opts, Object pendingFor
meta = RT.map(RT.TAG_KEY, meta);
else if (meta instanceof Keyword)
meta = RT.map(meta, RT.T);
+ else if (meta instanceof IPersistentVector)
+ meta = RT.map(RT.PARAM_TAGS_KEY, meta);
else if(!(meta instanceof IPersistentMap))
- throw new IllegalArgumentException("Metadata must be Symbol,Keyword,String or Map");
+ throw new IllegalArgumentException("Metadata must be Symbol,Keyword,String,Vector or Map");
Object o = read(r, true, null, true, opts, pendingForms);
if(o instanceof IMeta)
diff --git a/src/jvm/clojure/lang/LockingTransaction.java b/src/jvm/clojure/lang/LockingTransaction.java
index c28d2b2c9d..e76e90418d 100644
--- a/src/jvm/clojure/lang/LockingTransaction.java
+++ b/src/jvm/clojure/lang/LockingTransaction.java
@@ -102,7 +102,7 @@ void stop(int status){
long readPoint;
long startPoint;
long startTime;
-final RetryEx retryex = new RetryEx();
+static final RetryEx retryex = new RetryEx();
final ArrayList actions = new ArrayList();
final HashMap vals = new HashMap();
final HashSet sets = new HashSet();
diff --git a/src/jvm/clojure/lang/LongRange.java b/src/jvm/clojure/lang/LongRange.java
index d1bfba2fe0..10381cc29b 100644
--- a/src/jvm/clojure/lang/LongRange.java
+++ b/src/jvm/clojure/lang/LongRange.java
@@ -116,16 +116,23 @@ public ISeq next() {
}
}
+private static final int CHUNK_SIZE = 32;
+
public IChunk chunkedFirst() {
- return new LongChunk(start, step, count);
+ return new LongChunk(start, step, Math.min(count, CHUNK_SIZE));
}
public ISeq chunkedNext() {
- return null;
+ return chunkedMore().seq();
}
public ISeq chunkedMore() {
- return PersistentList.EMPTY;
+ if(count <= CHUNK_SIZE) {
+ return PersistentList.EMPTY;
+ } else {
+ return LongRange.create(start + (step * CHUNK_SIZE), end, step);
+ }
+
}
public Sequential drop(int n) {
diff --git a/src/jvm/clojure/lang/Numbers.java b/src/jvm/clojure/lang/Numbers.java
index 82d8e6914c..661c051d36 100644
--- a/src/jvm/clojure/lang/Numbers.java
+++ b/src/jvm/clojure/lang/Numbers.java
@@ -530,6 +530,10 @@ static long gcd(long u, long v){
public Number divide(Number x, Number y){
long n = x.longValue();
long val = y.longValue();
+
+ if(n == Long.MIN_VALUE || val == Long.MIN_VALUE)
+ return BIGINT_OPS.divide(x, y);
+
long gcd = gcd(n, val);
if(gcd == 0)
return num(0);
diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java
index 18bbcaa7b3..c3856a932a 100644
--- a/src/jvm/clojure/lang/PersistentArrayMap.java
+++ b/src/jvm/clojure/lang/PersistentArrayMap.java
@@ -33,6 +33,7 @@ public class PersistentArrayMap extends APersistentMap implements IObj, IEditabl
final Object[] array;
static final int HASHTABLE_THRESHOLD = 16;
+static final int KW_HASHTABLE_THRESHOLD = 128;
public static final PersistentArrayMap EMPTY = new PersistentArrayMap();
private final IPersistentMap _meta;
@@ -66,6 +67,19 @@ IPersistentMap createHT(Object[] init){
return PersistentHashMap.create(meta(), init);
}
+static public boolean canBePAM(Object[] init){
+ if(init.length <= HASHTABLE_THRESHOLD)
+ return true;
+ else if(init.length <= KW_HASHTABLE_THRESHOLD) {
+ for(int i = HASHTABLE_THRESHOLD;i < init.length; i+=2){
+ if(!(init[i] instanceof Keyword))
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
static public PersistentArrayMap createWithCheck(Object[] init){
for(int i=0;i< init.length;i += 2)
{
@@ -233,7 +247,9 @@ public IPersistentMap assocEx(Object key, Object val) {
}
else //didn't have key, grow
{
- if(array.length >= HASHTABLE_THRESHOLD)
+ boolean isKW = key instanceof Keyword;
+ if((isKW && array.length >= KW_HASHTABLE_THRESHOLD)
+ || (!isKW && array.length >= HASHTABLE_THRESHOLD))
return createHT(array).assocEx(key, val);
newArray = new Object[array.length + 2];
if(array.length > 0)
@@ -256,7 +272,9 @@ public IPersistentMap assoc(Object key, Object val){
}
else //didn't have key, grow
{
- if(array.length >= HASHTABLE_THRESHOLD)
+ boolean isKW = key instanceof Keyword;
+ if((isKW && array.length >= KW_HASHTABLE_THRESHOLD)
+ || (!isKW && array.length >= HASHTABLE_THRESHOLD))
return createHT(array).assoc(key, val);
newArray = new Object[array.length + 2];
if(array.length > 0)
diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java
index 4b9f3260ad..2ac8b1c142 100644
--- a/src/jvm/clojure/lang/PersistentVector.java
+++ b/src/jvm/clojure/lang/PersistentVector.java
@@ -17,7 +17,9 @@
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Spliterator;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
public class PersistentVector extends APersistentVector implements IObj, IEditableCollection, IReduce, IKVReduce, IDrop{
@@ -318,6 +320,77 @@ public void remove(){
public Iterator iterator(){return rangedIterator(0,count());}
+@Override
+Spliterator rangedSpliterator(final int start, final int end){
+ return new Spliterator(){
+ int i = start;
+ int base = i - (i%32);
+ Object[] array = (start < count())?arrayFor(i):null;
+
+ @Override
+ public int characteristics() {
+ return Spliterator.IMMUTABLE | // persistent
+ Spliterator.ORDERED | // know order
+ Spliterator.SIZED | // know size
+ Spliterator.SUBSIZED; // know size after split
+ }
+
+ @Override
+ public long estimateSize() {
+ return end-i;
+ }
+
+ @Override
+ public long getExactSizeIfKnown() {
+ return end-i;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer action) {
+ if(i < end) {
+ if(i-base == 32){
+ array = arrayFor(i);
+ base += 32;
+ }
+ action.accept(array[i++ & 0x01f]);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Spliterator trySplit() {
+ int lo = i;
+ int mid = (lo + end) >>> 1; // avoid overflow
+ if(lo >= mid) {
+ return null;
+ } else {
+ i = mid;
+ return rangedSpliterator(lo, mid);
+ }
+ }
+
+ @Override
+ public void forEachRemaining(Consumer action) {
+ int x=i;
+ while(x 0)
@@ -366,9 +439,7 @@ public Object kvreduce(IFn f, Object init){
}
public Sequential drop(int n) {
- if(n < 0) {
- return this;
- } else if(n < cnt) {
+ if(n < cnt) {
int offset = n%32;
return new ChunkedSeq(this, this.arrayFor(n), n-offset, offset);
} else {
@@ -500,7 +571,6 @@ public Sequential drop(int n) {
} else {
int i = this.i +o;
if(i < vec.cnt) { // in vec
- Object[] array = vec.arrayFor(i);
int newOffset = i%32;
return new ChunkedSeq(vec, vec.arrayFor(i), i-newOffset, newOffset);
} else {
@@ -618,7 +688,7 @@ public int count(){
ensureEditable();
return cnt;
}
-
+
Node ensureEditable(Node node){
if(node.edit == root.edit)
return node;
diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java
index 5d20ef4964..107b871911 100644
--- a/src/jvm/clojure/lang/RT.java
+++ b/src/jvm/clojure/lang/RT.java
@@ -13,6 +13,8 @@
package clojure.lang;
import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URI;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Callable;
import java.util.*;
@@ -20,10 +22,9 @@
import java.util.regex.Pattern;
import java.io.*;
import java.lang.reflect.Array;
+import java.lang.IllegalArgumentException;
import java.math.BigDecimal;
import java.math.BigInteger;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.net.URL;
import java.net.JarURLConnection;
import java.nio.charset.Charset;
@@ -37,133 +38,111 @@ public class RT{
//simple-symbol->class
final static public IPersistentMap DEFAULT_IMPORTS = map(
-// Symbol.intern("RT"), "clojure.lang.RT",
-// Symbol.intern("Num"), "clojure.lang.Num",
-// Symbol.intern("Symbol"), "clojure.lang.Symbol",
-// Symbol.intern("Keyword"), "clojure.lang.Keyword",
-// Symbol.intern("Var"), "clojure.lang.Var",
-// Symbol.intern("Ref"), "clojure.lang.Ref",
-// Symbol.intern("IFn"), "clojure.lang.IFn",
-// Symbol.intern("IObj"), "clojure.lang.IObj",
-// Symbol.intern("ISeq"), "clojure.lang.ISeq",
-// Symbol.intern("IPersistentCollection"),
-// "clojure.lang.IPersistentCollection",
-// Symbol.intern("IPersistentMap"), "clojure.lang.IPersistentMap",
-// Symbol.intern("IPersistentList"), "clojure.lang.IPersistentList",
-// Symbol.intern("IPersistentVector"), "clojure.lang.IPersistentVector",
+Symbol.intern("AbstractMethodError"), AbstractMethodError.class,
+Symbol.intern("Appendable"), Appendable.class,
+Symbol.intern("ArithmeticException"), ArithmeticException.class,
+Symbol.intern("ArrayIndexOutOfBoundsException"), ArrayIndexOutOfBoundsException.class,
+Symbol.intern("ArrayStoreException"), ArrayStoreException.class,
+Symbol.intern("AssertionError"), AssertionError.class,
+Symbol.intern("AutoCloseable"), AutoCloseable.class,
+Symbol.intern("BigDecimal"), BigDecimal.class,
+Symbol.intern("BigInteger"), BigInteger.class,
Symbol.intern("Boolean"), Boolean.class,
+Symbol.intern("BootstrapMethodError"), BootstrapMethodError.class,
Symbol.intern("Byte"), Byte.class,
+Symbol.intern("Callable"), Callable.class,
+Symbol.intern("CharSequence"), CharSequence.class,
Symbol.intern("Character"), Character.class,
Symbol.intern("Class"), Class.class,
+Symbol.intern("ClassCastException"), ClassCastException.class,
+Symbol.intern("ClassCircularityError"), ClassCircularityError.class,
+Symbol.intern("ClassFormatError"), ClassFormatError.class,
Symbol.intern("ClassLoader"), ClassLoader.class,
+Symbol.intern("ClassNotFoundException"), ClassNotFoundException.class,
+Symbol.intern("ClassValue"), ClassValue.class,
+Symbol.intern("CloneNotSupportedException"), CloneNotSupportedException.class,
+Symbol.intern("Cloneable"), Cloneable.class,
+Symbol.intern("Comparable"), Comparable.class,
Symbol.intern("Compiler"), Compiler.class,
+Symbol.intern("Deprecated"), Deprecated.class,
Symbol.intern("Double"), Double.class,
Symbol.intern("Enum"), Enum.class,
+Symbol.intern("EnumConstantNotPresentException"), EnumConstantNotPresentException.class,
+Symbol.intern("Error"), Error.class,
+Symbol.intern("Exception"), Exception.class,
+Symbol.intern("ExceptionInInitializerError"), ExceptionInInitializerError.class,
Symbol.intern("Float"), Float.class,
+Symbol.intern("IllegalAccessError"), IllegalAccessError.class,
+Symbol.intern("IllegalAccessException"), IllegalAccessException.class,
+Symbol.intern("IllegalArgumentException"), IllegalArgumentException.class,
+Symbol.intern("IllegalCallerException"), IllegalCallerException.class,
+Symbol.intern("IllegalMonitorStateException"), IllegalMonitorStateException.class,
+Symbol.intern("IllegalStateException"), IllegalStateException.class,
+Symbol.intern("IllegalThreadStateException"), IllegalThreadStateException.class,
+Symbol.intern("IncompatibleClassChangeError"), IncompatibleClassChangeError.class,
+Symbol.intern("IndexOutOfBoundsException"), IndexOutOfBoundsException.class,
Symbol.intern("InheritableThreadLocal"), InheritableThreadLocal.class,
+Symbol.intern("InstantiationError"), InstantiationError.class,
+Symbol.intern("InstantiationException"), InstantiationException.class,
Symbol.intern("Integer"), Integer.class,
+Symbol.intern("InternalError"), InternalError.class,
+Symbol.intern("InterruptedException"), InterruptedException.class,
+Symbol.intern("Iterable"), Iterable.class,
+Symbol.intern("LayerInstantiationException"), LayerInstantiationException.class,
+Symbol.intern("LinkageError"), LinkageError.class,
Symbol.intern("Long"), Long.class,
Symbol.intern("Math"), Math.class,
+Symbol.intern("ModuleLayer"), ModuleLayer.class,
+Symbol.intern("NegativeArraySizeException"), NegativeArraySizeException.class,
+Symbol.intern("NoClassDefFoundError"), NoClassDefFoundError.class,
+Symbol.intern("NoSuchFieldError"), NoSuchFieldError.class,
+Symbol.intern("NoSuchFieldException"), NoSuchFieldException.class,
+Symbol.intern("NoSuchMethodError"), NoSuchMethodError.class,
+Symbol.intern("NoSuchMethodException"), NoSuchMethodException.class,
+Symbol.intern("NullPointerException"), NullPointerException.class,
Symbol.intern("Number"), Number.class,
+Symbol.intern("NumberFormatException"), NumberFormatException.class,
Symbol.intern("Object"), Object.class,
+Symbol.intern("OutOfMemoryError"), OutOfMemoryError.class,
+Symbol.intern("Override"), Override.class,
Symbol.intern("Package"), Package.class,
Symbol.intern("Process"), Process.class,
Symbol.intern("ProcessBuilder"), ProcessBuilder.class,
+Symbol.intern("ProcessHandle"), ProcessHandle.class,
+Symbol.intern("Readable"), Readable.class,
+Symbol.intern("ReflectiveOperationException"), ReflectiveOperationException.class,
+Symbol.intern("Runnable"), Runnable.class,
Symbol.intern("Runtime"), Runtime.class,
+Symbol.intern("RuntimeException"), RuntimeException.class,
Symbol.intern("RuntimePermission"), RuntimePermission.class,
+Symbol.intern("SecurityException"), SecurityException.class,
Symbol.intern("SecurityManager"), SecurityManager.class,
Symbol.intern("Short"), Short.class,
+Symbol.intern("StackOverflowError"), StackOverflowError.class,
Symbol.intern("StackTraceElement"), StackTraceElement.class,
+Symbol.intern("StackWalker"), StackWalker.class,
Symbol.intern("StrictMath"), StrictMath.class,
Symbol.intern("String"), String.class,
Symbol.intern("StringBuffer"), StringBuffer.class,
Symbol.intern("StringBuilder"), StringBuilder.class,
+Symbol.intern("StringIndexOutOfBoundsException"), StringIndexOutOfBoundsException.class,
+Symbol.intern("SuppressWarnings"), SuppressWarnings.class,
Symbol.intern("System"), System.class,
Symbol.intern("Thread"), Thread.class,
+Symbol.intern("Thread$State"), Thread.State.class,
+Symbol.intern("Thread$UncaughtExceptionHandler"), Thread.UncaughtExceptionHandler.class,
+Symbol.intern("ThreadDeath"), ThreadDeath.class,
Symbol.intern("ThreadGroup"), ThreadGroup.class,
Symbol.intern("ThreadLocal"), ThreadLocal.class,
Symbol.intern("Throwable"), Throwable.class,
-Symbol.intern("Void"), Void.class,
-Symbol.intern("Appendable"), Appendable.class,
-Symbol.intern("CharSequence"), CharSequence.class,
-Symbol.intern("Cloneable"), Cloneable.class,
-Symbol.intern("Comparable"), Comparable.class,
-Symbol.intern("Iterable"), Iterable.class,
-Symbol.intern("Readable"), Readable.class,
-Symbol.intern("Runnable"), Runnable.class,
-Symbol.intern("Callable"), Callable.class,
-Symbol.intern("BigInteger"), BigInteger.class,
-Symbol.intern("BigDecimal"), BigDecimal.class,
-Symbol.intern("ArithmeticException"), ArithmeticException.class,
-Symbol.intern("ArrayIndexOutOfBoundsException"), ArrayIndexOutOfBoundsException.class,
-Symbol.intern("ArrayStoreException"), ArrayStoreException.class,
-Symbol.intern("ClassCastException"), ClassCastException.class,
-Symbol.intern("ClassNotFoundException"), ClassNotFoundException.class,
-Symbol.intern("CloneNotSupportedException"), CloneNotSupportedException.class,
-Symbol.intern("EnumConstantNotPresentException"), EnumConstantNotPresentException.class,
-Symbol.intern("Exception"), Exception.class,
-Symbol.intern("IllegalAccessException"), IllegalAccessException.class,
-Symbol.intern("IllegalArgumentException"), IllegalArgumentException.class,
-Symbol.intern("IllegalMonitorStateException"), IllegalMonitorStateException.class,
-Symbol.intern("IllegalStateException"), IllegalStateException.class,
-Symbol.intern("IllegalThreadStateException"), IllegalThreadStateException.class,
-Symbol.intern("IndexOutOfBoundsException"), IndexOutOfBoundsException.class,
-Symbol.intern("InstantiationException"), InstantiationException.class,
-Symbol.intern("InterruptedException"), InterruptedException.class,
-Symbol.intern("NegativeArraySizeException"), NegativeArraySizeException.class,
-Symbol.intern("NoSuchFieldException"), NoSuchFieldException.class,
-Symbol.intern("NoSuchMethodException"), NoSuchMethodException.class,
-Symbol.intern("NullPointerException"), NullPointerException.class,
-Symbol.intern("NumberFormatException"), NumberFormatException.class,
-Symbol.intern("RuntimeException"), RuntimeException.class,
-Symbol.intern("SecurityException"), SecurityException.class,
-Symbol.intern("StringIndexOutOfBoundsException"), StringIndexOutOfBoundsException.class,
Symbol.intern("TypeNotPresentException"), TypeNotPresentException.class,
-Symbol.intern("UnsupportedOperationException"), UnsupportedOperationException.class,
-Symbol.intern("AbstractMethodError"), AbstractMethodError.class,
-Symbol.intern("AssertionError"), AssertionError.class,
-Symbol.intern("ClassCircularityError"), ClassCircularityError.class,
-Symbol.intern("ClassFormatError"), ClassFormatError.class,
-Symbol.intern("Error"), Error.class,
-Symbol.intern("ExceptionInInitializerError"), ExceptionInInitializerError.class,
-Symbol.intern("IllegalAccessError"), IllegalAccessError.class,
-Symbol.intern("IncompatibleClassChangeError"), IncompatibleClassChangeError.class,
-Symbol.intern("InstantiationError"), InstantiationError.class,
-Symbol.intern("InternalError"), InternalError.class,
-Symbol.intern("LinkageError"), LinkageError.class,
-Symbol.intern("NoClassDefFoundError"), NoClassDefFoundError.class,
-Symbol.intern("NoSuchFieldError"), NoSuchFieldError.class,
-Symbol.intern("NoSuchMethodError"), NoSuchMethodError.class,
-Symbol.intern("OutOfMemoryError"), OutOfMemoryError.class,
-Symbol.intern("StackOverflowError"), StackOverflowError.class,
-Symbol.intern("ThreadDeath"), ThreadDeath.class,
Symbol.intern("UnknownError"), UnknownError.class,
Symbol.intern("UnsatisfiedLinkError"), UnsatisfiedLinkError.class,
Symbol.intern("UnsupportedClassVersionError"), UnsupportedClassVersionError.class,
+Symbol.intern("UnsupportedOperationException"), UnsupportedOperationException.class,
Symbol.intern("VerifyError"), VerifyError.class,
Symbol.intern("VirtualMachineError"), VirtualMachineError.class,
-Symbol.intern("Thread$UncaughtExceptionHandler"), Thread.UncaughtExceptionHandler.class,
-Symbol.intern("Thread$State"), Thread.State.class,
-Symbol.intern("Deprecated"), Deprecated.class,
-Symbol.intern("Override"), Override.class,
-Symbol.intern("SuppressWarnings"), SuppressWarnings.class
-
-// Symbol.intern("Collection"), "java.util.Collection",
-// Symbol.intern("Comparator"), "java.util.Comparator",
-// Symbol.intern("Enumeration"), "java.util.Enumeration",
-// Symbol.intern("EventListener"), "java.util.EventListener",
-// Symbol.intern("Formattable"), "java.util.Formattable",
-// Symbol.intern("Iterator"), "java.util.Iterator",
-// Symbol.intern("List"), "java.util.List",
-// Symbol.intern("ListIterator"), "java.util.ListIterator",
-// Symbol.intern("Map"), "java.util.Map",
-// Symbol.intern("Map$Entry"), "java.util.Map$Entry",
-// Symbol.intern("Observer"), "java.util.Observer",
-// Symbol.intern("Queue"), "java.util.Queue",
-// Symbol.intern("RandomAccess"), "java.util.RandomAccess",
-// Symbol.intern("Set"), "java.util.Set",
-// Symbol.intern("SortedMap"), "java.util.SortedMap",
-// Symbol.intern("SortedSet"), "java.util.SortedSet"
+Symbol.intern("Void"), Void.class
);
// single instance of UTF-8 Charset, so as to avoid catching UnsupportedCharsetExceptions everywhere
@@ -189,6 +168,7 @@ else if(s.equals("false"))
Var.intern(CLOJURE_NS, Symbol.intern("*err*"),
new PrintWriter(new OutputStreamWriter(System.err), true)).setDynamic();
final static Keyword TAG_KEY = Keyword.intern(null, "tag");
+final static Keyword PARAM_TAGS_KEY = Keyword.intern(null, "param-tags");
final static Keyword CONST_KEY = Keyword.intern(null, "const");
final static public Var AGENT = Var.intern(CLOJURE_NS, Symbol.intern("*agent*"), null).setDynamic();
static Object readeval = readTrueFalseUnknown(System.getProperty("clojure.read.eval","true"));
@@ -292,8 +272,24 @@ private Object readResolve() throws ObjectStreamException {
static AtomicInteger id = new AtomicInteger(1);
+static public URL toUrl(String url) throws MalformedURLException {
+ try {
+ return new URI(url).toURL();
+ } catch (URISyntaxException | IllegalArgumentException e) {
+ // Maintain backward compatibility with error
+ // thrown from now deprecated java.net.URL
+ MalformedURLException ex = new MalformedURLException();
+ ex.initCause(e);
+ throw ex;
+ }
+}
+
+static public URL toUrl(File file) throws MalformedURLException {
+ return file.toURI().toURL();
+}
+
static public void addURL(Object url) throws MalformedURLException{
- URL u = (url instanceof String) ? (new URL((String) url)) : (URL) url;
+ URL u = (url instanceof String) ? toUrl((String) url) : (URL) url;
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if(ccl instanceof DynamicClassLoader)
((DynamicClassLoader)ccl).addURL(u);
@@ -755,6 +751,24 @@ static public Object pop(Object x){
return ((IPersistentStack) x).pop();
}
+private static final Object REQ_NOT_FOUND = new Object();
+
+static public String reqmsg(Object key){
+ String msg = "Missing required key: ";
+ if(key instanceof String)
+ return msg + "\"" + key + "\"";
+ else
+ return msg + key;
+}
+
+static public Object req(Object coll, Object key){
+ Object v = get(coll, key, REQ_NOT_FOUND);
+ if(v == REQ_NOT_FOUND)
+ throw new IllegalArgumentException(reqmsg(key));
+ else
+ return v;
+}
+
static public Object get(Object coll, Object key){
if(coll instanceof ILookup)
return ((ILookup) coll).valAt(key);
@@ -768,20 +782,12 @@ else if(coll instanceof Map) {
Map m = (Map) coll;
return m.get(key);
}
- else if(coll instanceof IPersistentSet) {
- IPersistentSet set = (IPersistentSet) coll;
- return set.get(key);
- }
else if(key instanceof Number && (coll instanceof String || coll.getClass().isArray())) {
int n = ((Number) key).intValue();
if(n >= 0 && n < count(coll))
return nth(coll, n);
return null;
}
- else if(coll instanceof ITransientSet) {
- ITransientSet set = (ITransientSet) coll;
- return set.get(key);
- }
return null;
}
@@ -801,22 +807,10 @@ else if(coll instanceof Map) {
return m.get(key);
return notFound;
}
- else if(coll instanceof IPersistentSet) {
- IPersistentSet set = (IPersistentSet) coll;
- if(set.contains(key))
- return set.get(key);
- return notFound;
- }
else if(key instanceof Number && (coll instanceof String || coll.getClass().isArray())) {
int n = ((Number) key).intValue();
return n >= 0 && n < count(coll) ? nth(coll, n) : notFound;
}
- else if(coll instanceof ITransientSet) {
- ITransientSet set = (ITransientSet) coll;
- if(set.contains(key))
- return set.get(key);
- return notFound;
- }
return notFound;
}
@@ -1588,7 +1582,7 @@ static public double uncheckedDoubleCast(double x){
static public IPersistentMap map(Object... init){
if(init == null || init.length == 0)
return PersistentArrayMap.EMPTY;
- else if(init.length <= PersistentArrayMap.HASHTABLE_THRESHOLD)
+ else if(PersistentArrayMap.canBePAM(init))
return PersistentArrayMap.createWithCheck(init);
return PersistentHashMap.createWithCheck(init);
}
@@ -1596,7 +1590,7 @@ else if(init.length <= PersistentArrayMap.HASHTABLE_THRESHOLD)
static public IPersistentMap mapUniqueKeys(Object... init){
if(init == null)
return PersistentArrayMap.EMPTY;
- else if(init.length <= PersistentArrayMap.HASHTABLE_THRESHOLD)
+ else if(PersistentArrayMap.canBePAM(init))
return new PersistentArrayMap(init);
return PersistentHashMap.create(init);
}
@@ -2158,19 +2152,13 @@ static public Object[] setValues(Object... vals){
}
-static public ClassLoader makeClassLoader(){
- return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction(){
- public Object run(){
- try{
- Var.pushThreadBindings(RT.map(USE_CONTEXT_CLASSLOADER, RT.T));
-// getRootClassLoader();
- return new DynamicClassLoader(baseLoader());
- }
- finally{
- Var.popThreadBindings();
- }
- }
- });
+static public ClassLoader makeClassLoader() {
+ try {
+ Var.pushThreadBindings(RT.map(USE_CONTEXT_CLASSLOADER, RT.T));
+ return new DynamicClassLoader(baseLoader());
+ } finally {
+ Var.popThreadBindings();
+ }
}
static public ClassLoader baseLoader(){
diff --git a/src/jvm/clojure/lang/Range.java b/src/jvm/clojure/lang/Range.java
index 9408421da0..1e4fbf6e01 100644
--- a/src/jvm/clojure/lang/Range.java
+++ b/src/jvm/clojure/lang/Range.java
@@ -101,7 +101,7 @@ public static ISeq create(final Object start, Object end, Object step) {
public Obj withMeta(IPersistentMap meta){
if(meta == _meta)
return this;
- return new Range(meta, end, start, step, boundsCheck, _chunk, _chunkNext);
+ return new Range(meta, start, end, step, boundsCheck, _chunk, _chunkNext);
}
public Object first(){
diff --git a/src/jvm/clojure/lang/Reflector.java b/src/jvm/clojure/lang/Reflector.java
index d5811cc59d..14cef154b9 100644
--- a/src/jvm/clojure/lang/Reflector.java
+++ b/src/jvm/clojure/lang/Reflector.java
@@ -19,6 +19,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;
@@ -94,12 +95,19 @@ private static Method toAccessibleSuperMethod(Method m, Object target) {
}
public static Object invokeInstanceMethod(Object target, String methodName, Object[] args) {
- Class c = target.getClass();
+ return invokeInstanceMethodOfClass(target, target.getClass(), methodName, args);
+}
+
+public static Object invokeInstanceMethodOfClass(Object target, Class c, String methodName, Object[] args) {
List methods = getMethods(c, args.length, methodName, false).stream()
- .map(method -> toAccessibleSuperMethod(method, target))
- .filter(method -> (method != null))
- .collect(Collectors.toList());
- return invokeMatchingMethod(methodName, methods, target, args);
+ .map(method -> toAccessibleSuperMethod(method, target))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ return invokeMatchingMethod(methodName, methods, c, target, args);
+}
+
+public static Object invokeInstanceMethodOfClass(Object target, String className, String methodName, Object[] args) {
+ return invokeInstanceMethodOfClass(target, RT.classForName(className), methodName, args);
}
private static Throwable getCauseOrElse(Exception e) {
@@ -114,57 +122,78 @@ private static RuntimeException throwCauseOrElseException(Exception e) {
throw Util.sneakyThrow(e);
}
-private static String noMethodReport(String methodName, Object target, Object[] args){
+private static String noMethodReport(String methodName, Class contextClass, Object[] args){
return "No matching method " + methodName + " found taking " + args.length + " args"
- + (target==null?"":" for " + target.getClass());
+ + (contextClass != null ? " for " + contextClass : "");
+}
+
+private static Method matchMethod(List methods, Object[] args) {
+ Method foundm = null;
+ for(Iterator i = methods.iterator(); i.hasNext();) {
+ Method m = (Method) i.next();
+ Class[] params = m.getParameterTypes();
+ if(isCongruent(params, args) && (foundm == null || Compiler.subsumes(params, foundm.getParameterTypes())))
+ foundm = m;
+ }
+ return foundm;
+}
+
+private static Object[] widenBoxedArgs(Object[] args) {
+ Object[] widenedArgs = new Object[args.length];
+ for(int i=0; i getMethods(Class c, int arity, String name, boolean g
return methods;
}
+// Return type coercions match coercions in FnInvokers for compiled invokers
+private static Object coerceAdapterReturn(Object ret, Class targetType) {
+ if(targetType.isPrimitive()) {
+ switch (targetType.getName()) {
+ case "boolean": return RT.booleanCast(ret);
+ case "long": return RT.longCast(ret);
+ case "double": return RT.doubleCast(ret);
+ case "int": return RT.intCast(ret);
+ case "short": return RT.shortCast(ret);
+ case "byte": return RT.byteCast(ret);
+ case "float": return RT.floatCast(ret);
+ }
+ }
+ return ret;
+}
static Object boxArg(Class paramType, Object arg){
- if(!paramType.isPrimitive())
+ if(arg instanceof IFn && Compiler.FISupport.maybeFIMethod(paramType) != null && !(paramType.isInstance(arg)))
+ // Adapt IFn obj to targetType using dynamic proxy
+ return Proxy.newProxyInstance(RT.baseLoader(),
+ new Class[]{paramType},
+ (proxy, method, methodArgs) -> {
+ Object ret = ((IFn) arg).applyTo(RT.seq(methodArgs));
+ return coerceAdapterReturn(ret, method.getReturnType());
+ });
+ else if(!paramType.isPrimitive())
return paramType.cast(arg);
else if(paramType == boolean.class)
return Boolean.class.cast(arg);
@@ -592,6 +644,8 @@ static public boolean paramArgTypeMatch(Class paramType, Class argType){
return !paramType.isPrimitive();
if(paramType == argType || paramType.isAssignableFrom(argType))
return true;
+ if(Compiler.FISupport.maybeFIMethod(paramType) != null && IFn.class.isAssignableFrom(argType))
+ return true;
if(paramType == int.class)
return argType == Integer.class
|| argType == long.class
@@ -651,4 +705,5 @@ public static Object prepRet(Class c, Object x){
// return Double.valueOf(((Float) x).doubleValue());
return x;
}
+
}
diff --git a/src/jvm/clojure/lang/Repeat.java b/src/jvm/clojure/lang/Repeat.java
index 934163902e..7e11808385 100644
--- a/src/jvm/clojure/lang/Repeat.java
+++ b/src/jvm/clojure/lang/Repeat.java
@@ -100,13 +100,30 @@ public Object reduce(IFn f, Object start){
}
public Sequential drop(int n) {
- if(count > 1) {
- return new Repeat(count-n, val);
- } else if(count == INFINITE) {
+ if(count == INFINITE) {
return this;
} else {
- return null;
+ long droppedCount = count-n;
+ if(droppedCount > 0) {
+ return new Repeat(droppedCount, val);
+ } else {
+ return null;
+ }
}
}
+public int hashCode(){
+ if(count <= 0)
+ throw new UnsupportedOperationException();
+ else
+ return super.hashCode();
+}
+
+public int hasheq(){
+ if(count <= 0)
+ throw new UnsupportedOperationException();
+ else
+ return super.hasheq();
+}
+
}
diff --git a/src/jvm/clojure/lang/StringSeq.java b/src/jvm/clojure/lang/StringSeq.java
index 3163380898..dbad70810e 100644
--- a/src/jvm/clojure/lang/StringSeq.java
+++ b/src/jvm/clojure/lang/StringSeq.java
@@ -61,7 +61,7 @@ public int count(){
public Sequential drop(int n) {
int ii = i + n;
if (ii < s.length()) {
- return new StringSeq(_meta, s, ii);
+ return new StringSeq(null, s, ii);
} else {
return null;
}
diff --git a/src/jvm/clojure/lang/Util.java b/src/jvm/clojure/lang/Util.java
index ff0e338331..de91dde518 100644
--- a/src/jvm/clojure/lang/Util.java
+++ b/src/jvm/clojure/lang/Util.java
@@ -188,8 +188,8 @@ static public boolean isPrimitive(Class c){
}
static public boolean isInteger(Object x){
- return x instanceof Integer
- || x instanceof Long
+ return x instanceof Long
+ || x instanceof Integer
|| x instanceof BigInt
|| x instanceof BigInteger;
}
@@ -256,5 +256,27 @@ static public Object loadWithClass(String scriptbase, Class> loadFrom) throws
}
}
+static boolean isPosDigit(String s) {
+ if(s.length() != 1)
+ return false;
+ char ch = s.charAt(0);
+ return ch <= '9' && ch >= '1';
+}
+
+public static Symbol arrayTypeToSymbol(Class c) {
+ int dim = 0;
+ Class componentClass = c;
+
+ while(componentClass.isArray()) {
+ if(++dim > 9)
+ break;
+
+ componentClass = componentClass.getComponentType();
+ }
+ if (dim <= 9 && dim >= 1)
+ return Symbol.intern(componentClass.getName(), Integer.toString(dim));
+ else
+ return Symbol.intern(null, c.getName());
+}
}
diff --git a/test/clojure/test_clojure/array_symbols.clj b/test/clojure/test_clojure/array_symbols.clj
new file mode 100644
index 0000000000..7fb98743e3
--- /dev/null
+++ b/test/clojure/test_clojure/array_symbols.clj
@@ -0,0 +1,81 @@
+; 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.test-clojure.array-symbols
+ (:use clojure.test)
+ (:require [clojure.test-helper :as util]))
+
+(set! *warn-on-reflection* true)
+
+(deftest test-array-symbols
+ (is (= 'java.lang.String/1 (read-string "java.lang.String/1")))
+ (is (= 'String/1 (read-string "String/1")))
+ (is (= 'int/2 (read-string "int/2")))
+ (testing "array symbol resolution"
+ (are [str-repr klass] (= (Class/forName str-repr) klass)
+ "[Z" (resolve 'boolean/1)
+ "[B" (resolve 'byte/1)
+ "[C" (resolve 'char/1)
+ "[S" (resolve 'short/1)
+ "[F" (resolve 'float/1)
+ "[D" (resolve 'double/1)
+ "[I" (resolve 'int/1)
+ "[J" (resolve 'long/1)
+ "[[J" (resolve 'long/2)
+ "[Ljava.lang.Object;" (resolve 'Object/1)
+ "[Ljava.lang.String;" (resolve 'String/1)
+ "[[Ljava.lang.String;" (resolve 'String/2))
+ (is (thrown? ClassNotFoundException (resolve 'ThisIsNotAClassThatCouldBeFound138/2)))
+ (is (thrown? ClassNotFoundException (resolve 'foo.bar.ThisIsNotAClassThatCouldBeFound138/2))))
+ (testing "array hints"
+ (util/should-not-reflect
+ (let [^long/1 a (long-array [1 2 3 4 99 100])]
+ (java.util.Arrays/binarySearch a 99))))
+ (testing "syntax quote"
+ (is (= `byte/1 'byte/1))
+ (is (= `byte/9 'byte/9))
+ (is (= `java.util.UUID/1 'java.util.UUID/1))
+ (is (= `String/1 'java.lang.String/1)))
+ (testing "resolution"
+ (is (= (eval 'long/1) (class (make-array Long/TYPE 0))))
+ (is (= (resolve 'long/1) (class (make-array Long/TYPE 0))))
+ (is (= (resolve 'String/1) (class (make-array String 0))))
+ (is (= (resolve 'java.lang.String/1) (class (make-array String 0))))
+ (is (= (resolve 'java.util.UUID/1) (class (make-array java.util.UUID 0))))
+ (is (= (resolve 'String/2)
+ (class (into-array (class (make-array String 0)) [(into-array String ["a" "b"])])))))
+ (testing "value position"
+ (is (= (class (make-array String 0)) String/1))
+ (is (= [(class (make-array String 0))] [String/1])))
+ (testing "printing"
+ (is (= "long/1" (print-str long/1)))
+ (is (= "byte/1" (print-str (class (make-array Byte/TYPE 0)))))
+ (is (= "java.lang.String/2" (print-str String/2)))
+ (is (= "[[java.lang.String/2]]" (print-str [[String/2]])))
+ (is (= "java.util.UUID/4" (print-str java.util.UUID/4)))
+ (is (= "[[[[[[[[[[Ljava.lang.Object;" (print-str (Class/forName "[[[[[[[[[[Ljava.lang.Object;"))))
+ (is (= "java.lang.Object/9" (print-str (Class/forName "[[[[[[[[[Ljava.lang.Object;")))))
+ (testing "error conditions"
+ (is (thrown? Exception (read-string "String/1:")))
+ (is (thrown? Exception (read-string "String/0")))
+ (is (thrown? Exception (read-string "String/42")))
+ (is (thrown? Exception (eval '(deftype Foo/2 [a]))))))
+
+(definterface ArrayClassSymbolFoo (^String/1 bar []))
+(definterface ArrayClassSymbolFooAsHint (^ArrayClassSymbolFoo/1 baz []))
+
+(deftest test-definterface-acs
+ (testing "definterface"
+ (let [obj (reify ArrayClassSymbolFoo (bar [this] (into-array String ["a"])))]
+ (is (= ["a"] (seq (.bar obj)))))
+ (let [obj (reify ArrayClassSymbolFooAsHint
+ (baz [this]
+ (into-array ArrayClassSymbolFoo [(reify ArrayClassSymbolFoo
+ (bar [this] (into-array String ["a"])))])))]
+ (is (= ["a"] (let [^ArrayClassSymbolFoo acsf (first (.baz obj))]
+ (seq (.bar acsf))))))))
diff --git a/test/clojure/test_clojure/atoms.clj b/test/clojure/test_clojure/atoms.clj
index f9ecadcc56..875f26d60f 100644
--- a/test/clojure/test_clojure/atoms.clj
+++ b/test/clojure/test_clojure/atoms.clj
@@ -42,3 +42,22 @@
(deftest reset-on-deref-reset-equality
(let [a (atom :usual-value)]
(is (= :usual-value (reset! a (first (reset-vals! a :almost-never-seen-value)))))))
+
+(deftest atoms-are-suppliers
+ (let [a (atom 10)]
+ (is (instance? java.util.function.Supplier a))
+ (is (= 10 (.get ^java.util.function.Supplier a)))
+ (swap! a inc)
+ (is (= 11 (.get ^java.util.function.Supplier a)))
+
+ (is (instance? java.util.function.IntSupplier a))
+ (is (= 11 (.getAsInt a)))
+
+ (is (instance? java.util.function.LongSupplier a))
+ (is (= 11 (.getAsLong a)))
+
+ (is (instance? java.util.function.BooleanSupplier a))
+ (is (true? (.getAsBoolean a)))
+
+ (is (instance? java.util.function.DoubleSupplier a))
+ (is (= 11.0 (.getAsDouble a)))))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/clearing.clj b/test/clojure/test_clojure/clearing.clj
new file mode 100644
index 0000000000..04e187a74c
--- /dev/null
+++ b/test/clojure/test_clojure/clearing.clj
@@ -0,0 +1,111 @@
+; 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.test-clojure.clearing
+ (:import
+ [java.lang.reflect Field])
+ (:require
+ [clojure.string :as str]
+ [clojure.test :refer :all]))
+
+(set! *warn-on-reflection* true)
+
+(defn fields
+ [o]
+ (.getDeclaredFields (class o)))
+
+(defn primitive?
+ [^Field field]
+ (.isPrimitive (.getType field)))
+
+(defn special-fn-field?
+ [^String field-name]
+ (or (= field-name "__meta")
+ (str/starts-with? field-name "__cached_class__")
+ (str/starts-with? field-name "const__")
+ (str/ends-with? field-name "__")))
+
+(defn clearable-closed-overs
+ [fobj]
+ (->> (fields fobj)
+ (remove primitive?) ;; can't clear primitives
+ (remove #(special-fn-field? (.getName ^Field %)))))
+
+(defn private-field-value [^Object obj ^Field field]
+ (. field (setAccessible true))
+ (. field (get obj)))
+
+;; Check whether all non-primitive closed-overs in a function are nil
+(defn cleared?
+ [fobj]
+ (every? #(nil? (private-field-value fobj %)) (clearable-closed-overs fobj)))
+
+;; ---
+
+;; After invocation, check all closed-over non-primitive fields in a :once fn
+
+(defn check-clear
+ [f]
+ (is (not (cleared? f)))
+ (f)
+ (cleared? f))
+
+(deftest test-clearing
+ (let [x :a]
+ ;; base case
+ (is (check-clear (^{:once true} fn* [] x)))
+
+ ;; conditional above fn
+ (when true
+ (is (check-clear (^{:once true} fn* [] x))))
+ (case x
+ :a (is (check-clear (^{:once true} fn* [] x))))
+
+ ;; loop above fn
+ (loop []
+ (is (check-clear (^{:once true} fn* [] x))))
+
+ ;; conditional below fn
+ (is (check-clear (^{:once true} fn* [] (when true x))))
+
+ ;; loop below fn
+ (is (not (check-clear (^{:once true} fn* [] (loop [] x)))))
+ (is (not (check-clear (^{:once true} fn* [] (loop [] x) nil))))
+
+ ;; recur in :once below fn
+ (is (not (check-clear (^{:once true} fn* [] (if false (recur) x)))))
+ ))
+
+(deftest test-nested
+ (let [x :a]
+ ;; nested fns
+ (let [inner (^{:once true} fn* [] x)
+ outer (fn* [] inner)]
+ (is (not (check-clear outer))) ;; outer not :once
+ (is (check-clear inner)))
+
+ (let [inner (^{:once true} fn* [] x)
+ outer (^{:once true} fn* [] inner)]
+ (is (check-clear outer))
+ (is (check-clear inner)))
+
+ (let [inner (^{:once true} fn* [] x)
+ middle (^{:once true} fn* [] inner)
+ outer (^{:once true} fn* [] middle)]
+ (is (check-clear outer))
+ (is (check-clear middle))
+ (is (check-clear inner)))))
+
+;; Repro from CLJ-2145
+(defn consume [x] (doseq [_ x] _))
+(defn call-and-keep [f] (f) f)
+(defn repro [x]
+ (if true (call-and-keep (^:once fn* [] (consume x)))))
+(deftest CLJ-2145-repro
+ (let [f (repro (range 100))] ;; 1e9 to exhaust
+ (is (cleared? f))))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/clojure_walk.clj b/test/clojure/test_clojure/clojure_walk.clj
index 6c6f866332..fa2f3e6492 100644
--- a/test/clojure/test_clojure/clojure_walk.clj
+++ b/test/clojure/test_clojure/clojure_walk.clj
@@ -63,3 +63,14 @@
(let [coll [:html {:a ["b" 1]} ""]
f (fn [e] (if (and (vector? e) (not (map-entry? e))) (apply list e) e))]
(is (= (list :html {:a (list "b" 1)} "") (w/postwalk f coll)))))
+
+(defrecord RM [a])
+(deftest retain-meta
+ (let [m {:foo true}]
+ (are [o] (= m (meta (w/postwalk identity (with-meta o m))))
+ '(1 2)
+ [1 2]
+ #{1 2}
+ {1 2}
+ (map inc (range 3))
+ (->RM 1))))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj
index 999d33f91b..61c6905622 100644
--- a/test/clojure/test_clojure/compilation.clj
+++ b/test/clojure/test_clojure/compilation.clj
@@ -443,3 +443,11 @@
(testing "CLJ-2580 Correctly calculate exit branches of case"
(is (zero? (let [d (case nil :x nil 0)] d)))
(is (nil? (let [d (case nil :x 0 nil)] d)))))
+
+(deftest CLJ-2945
+ (testing "CLJ-2945 Reify form and obj meta"
+ (are [m x] (= (set (keys m)) x)
+ (meta (reify clojure.lang.ILookup)) #{}
+ (meta ^:foo (reify clojure.lang.ILookup)) #{:foo}
+ (meta (macroexpand-1 '(reify clojure.lang.ILookup))) #{:line :column}
+ (meta (macroexpand-1 '^:foo (reify clojure.lang.ILookup))) #{:line :column :foo})))
diff --git a/test/clojure/test_clojure/data_structures.clj b/test/clojure/test_clojure/data_structures.clj
index 854560466b..c6ff895448 100644
--- a/test/clojure/test_clojure/data_structures.clj
+++ b/test/clojure/test_clojure/data_structures.clj
@@ -710,6 +710,15 @@
(get-in m [] 0) m
(get-in m nil 0) m)))
+(deftest test-req!
+ (let [m {:a 1, :b 2, :f nil, :g false, nil "nil"}]
+ (is (thrown? Exception (req! m :e)))
+ (are [x y] (= x y)
+ (req! m :a) 1
+ (req! m nil) "nil"
+ (req! m :b) 2
+ (req! m :f) nil)))
+
(deftest test-nested-map-destructuring
(let [sample-map {:a 1 :b {:a 2}}
{ao1 :a {ai1 :a} :b} sample-map
@@ -933,7 +942,20 @@
(into-array [1 2]) #{1 2}
"" #{}
- "abc" #{\a \b \c} ))
+ "abc" #{\a \b \c} )
+
+ ; get, get not-found, invoke on sets and transient sets
+ (are [e enf s k] (and
+ (= e (get s k))
+ (= enf (get (transient s) k 1))
+ (= e (s k))
+ (= enf ((transient s) k 1))
+ (= e (k s))
+ (= enf (k s 1)))
+ :a :a #{:a} :a
+ nil 1 #{:a} :b
+ nil 1 #{} :a
+ nil 1 #{nil} :a))
(deftest test-disj
@@ -1361,3 +1383,222 @@
(= m2 (seq-to-map-for-destructuring (list :a 1 :b 2 {:c 3})))
(= m3 (seq-to-map-for-destructuring (list :a 1 :b 2 {:a 0})))
(= a4 nil)))))
+
+(deftest keys-bang
+ (let [sample-map {:a 1 :b 2}]
+ (testing ":keys! happy path, binds and throws when key missing"
+ (is (= 1 (let [{:keys! [a b]} sample-map] a)))
+ (is (thrown? Exception (let [{:keys! [a b]} {:a 1}] a))))
+ (testing ":keys! with & bind and don't bind"
+ (is (= 1 (let [{:keys! [a & :b]} sample-map] a)))
+ (is (thrown? Exception (let [{:keys! [a & :b]} {:a 1}] a))))
+ (testing "nested maps with :keys! &"
+ (let [sample-map {:a 1 :b {:a 2 :b 3 :c 4 :d 42}}
+ {a :a {aa :a :as m :keys! [b c & :d]} :b} sample-map]
+ (is (= m (:b sample-map)))
+ (is (= a 1))
+ (is (= aa 2))
+ (is (= b 3))
+ (is (= c 4))
+ (is (thrown? Exception (let [{a :a {aa :a :as m :keys! [b c & :d :e]} :b} sample-map] a)))
+ (is (thrown? Exception (let [{a :a {aa :a :as m :keys! [b c & :d]} :b} (update sample-map :b dissoc :c)] a)))))
+ (testing "a broad range of qualified names/declarators with :keys! &"
+ (let [sample-map {:foo/a 1 :b 2 :foo/c 3}
+ {:keys! [foo/a & :b]} sample-map
+ {:keys! [b & :foo/c]} sample-map
+ sample-map2 {:foo/aa 1 :bb 2 :foo/cc 3}
+ {:foo/keys! [aa & :foo/cc]} sample-map2]
+ (is (= a 1))
+ (is (= b 2))
+ (is (thrown? Exception (let [{:keys! [b & :foo/c]} (dissoc sample-map :b)] b)))
+ (is (thrown? Exception (let [{:keys! [b & :foo/c]} (dissoc sample-map :foo/c)] b)))
+ (is (= 1 (let [{:foo/keys! [aa & :bb]} sample-map2] aa)))
+ (is (= aa 1))
+ (is (= 1 (let [{:keys! [::a & ::b]} {::a 1 , ::b 2}] a)))))
+ (testing "that right of & is unbound (compile-time errors)"
+ (is (thrown? Exception (eval '(let [{:keys! [a & :b]} sample-map] b))))
+ (is (thrown? Exception (eval '(let [{a :a {aa :a :as m :keys [b c & :e]} :b} sample-map] e))))
+ (is (thrown? Exception (eval '(let [{:keys! [foo/a & :foo/c]} sample-map] c))))
+ (let [sample-map2 {:foo/aa 1 :bb 2 :foo/cc 3}]
+ (is (thrown? Exception (eval '(let [{:foo/keys! [foo/aa & :foo/cc]} sample-map2] cc))))))))
+
+(deftest syms-bang
+ (let [sample-map '{a 1 b 2}]
+ (testing ":syms! happy path, binds and throws when key missing"
+ (is (= 1 (let [{:syms! [a b]} sample-map] a)))
+ (is (thrown? Exception (let [{:syms! [a b]} {:a 1}] a))))
+ (testing ":syms! with & bind and don't bind"
+ (is (= 1 (let [{:syms! [a & 'b]} sample-map] a)))
+ (is (thrown? Exception (let [{:syms! [a & 'b]} {:a 1}] a))))
+ (testing "nested maps with :syms! &"
+ (let [sample-map '{a 1 b {a 2 b 3 c 4 d 42}}
+ {a 'a {aa 'a :as m :syms! [b c & 'd]} 'b} sample-map]
+ (is (= m ('b sample-map)))
+ (is (= a 1))
+ (is (= aa 2))
+ (is (= b 3))
+ (is (= c 4))
+ (is (thrown? Exception (let [{a 'a {aa :a :as m :syms! [b c & 'd 'e]} 'b} sample-map] a)))
+ (is (thrown? Exception (let [{a 'a {aa :a :as m :syms! [b c & 'd]} 'b} (update sample-map 'b dissoc 'c)] a)))))
+ (testing "a broad range of qualified names/declarators with :syms! &"
+ (let [sample-map '{foo/a 1 b 2 foo/c 3}
+ {:syms! [foo/a & 'b]} sample-map
+ {:syms! [b & 'foo/c]} sample-map
+ sample-map2 '{foo/aa 1 bb 2 foo/cc 3}
+ {:foo/syms! [aa & 'foo/cc]} sample-map2]
+ (is (= a 1))
+ (is (= b 2))
+ (is (thrown? Exception (let [{:syms! [b & 'foo/c]} (dissoc sample-map 'b)] b)))
+ (is (thrown? Exception (let [{:syms! [b & 'foo/c]} (dissoc sample-map 'foo/c)] b)))
+ (is (= aa 1))
+ (is (= 1 (let [{:foo/syms! [aa & 'bb]} sample-map2] aa)))))
+ (testing "that right of & is unbound (compile-time errors)"
+ (is (thrown? Exception (eval '(let [{:syms! [a & 'b]} sample-map] b))))
+ (is (thrown? Exception (eval '(let [{a a {aa a :as m :syms [b c & 'e]} :b} sample-map] e))))
+ (is (thrown? Exception (eval '(let [{:syms! [foo/a & 'foo/c]} sample-map] c))))
+ (let [sample-map2 '{foo/aa 1 bb 2 foo/cc 3}]
+ (is (thrown? Exception (eval '(let [{:foo/syms! [foo/aa & 'foo/cc]} sample-map2] cc))))))))
+
+(deftest strs-bang
+ (let [sample-map {"a" 1 "b" 2}]
+ (testing ":strs! happy path, binds and throws when key missing"
+ (is (= 1 (let [{:strs! [a b]} sample-map] a)))
+ (is (thrown? Exception (let [{:strs! [a b]} {:a 1}] a))))
+ (testing ":strs! with & bind and don't bind"
+ (is (= 1 (let [{:strs! [a & "b"]} sample-map] a)))
+ (is (thrown? Exception (let [{:strs! [a & "b"]} {:a 1}] a))))
+ (testing "nested maps with :strs! &"
+ (let [sample-map {"a" 1 "b" {"a" 2 "b" 3 "c" 4 "d" 42}}
+ {a "a" {aa "a" :as m :strs! [b c & "d"]} "b"} sample-map]
+ (is (= m (get sample-map "b")))
+ (is (= a 1))
+ (is (= aa 2))
+ (is (= b 3))
+ (is (= c 4))
+ (is (thrown? Exception (let [{a "a" {aa "a" :as m :strs! [b c & "d" "e"]} "b"} sample-map] a)))
+ (is (thrown? Exception (let [{a "a" {aa "a" :as m :strs! [b c & "d"]} "b"} (update sample-map "b" dissoc "c")] a)))))
+ (testing "that right of & is unbound (compile-time errors)"
+ (is (thrown? Exception (eval '(let [{:strs! [a & "b"]} sample-map] b))))
+ (is (thrown? Exception (eval '(let [{a "a" {aa "a" :as m :keys [b c & "e"]} "b"} sample-map] e)))))))
+
+(deftest select-directive
+ (let [m {:a 1 :b 2 :c 3 :d 4
+ 'sa 10 'sb 20 'sc 30 'sd 40
+ "stra" 100 "strb" 200 "strc" 300 "strd" 400
+ :foo/x 1000 :foo/y 2000 :foo/z 3000
+ ::x 10000 ::y 20000 ::z 30000
+ :nested {:aa 1 'saa 10 "straa" 100}}
+
+ {:keys [a b & :c :z]
+ :keys! [d]
+ :select keys-sel} m
+
+ {:syms [sa sb & 'sc 'sz]
+ :syms! [sd]
+ :select syms-sel} m
+
+ {:strs [stra strb & "strc" "strz"]
+ :strs! [strd]
+ :select strs-sel} m
+
+ {:foo/keys [x & :y :zz]
+ :foo/keys! [z]
+ :select qkeys-sel} m
+
+ {::keys [x & :y :zz]
+ ::keys! [z]
+ :select aqkeys-sel} m
+
+ {{aa :aa saa 'saa
+ :select nest-sel} :nested
+ aqx ::x
+ :select tl-sel} m
+
+ {:keys! [a b & :c]
+ :keys [d & :z]
+ :or {}
+ :select or-sel} m
+
+ {:keys [a b c d]
+ :syms [sa sb sc sd]
+ :strs [stra strb strc strd]
+ :foo/keys! [x y z]
+ ::keys [x y z]
+ nest :nested
+ :as mm
+ :select sel-mm} m]
+ (are [expected result] (= expected result)
+ keys-sel {:a 1 :b 2 :c 3 :d 4}
+ syms-sel '{sa 10 sb 20 sc 30 sd 40}
+ strs-sel {"stra" 100 "strb" 200 "strc" 300 "strd" 400}
+ qkeys-sel {:foo/x 1000 :foo/z 3000}
+ aqkeys-sel {::x 10000 ::z 30000}
+ nest-sel '{:aa 1, saa 10}
+ tl-sel '{:nested {:aa 1, saa 10} ::x 10000}
+ or-sel {:a 1 :b 2 :c 3 :d 4}
+ sel-mm mm))
+ (testing "base cases"
+ (is (nil? (let [{{a :a} :n :select s} nil] s))
+ "if you haven't supplied a map, select won't make one for no reason")
+
+ (testing "you get what you supplied if nothing else"
+ (is (= {} (let [{{a :a} :n :select s} {}] s)))
+ (is (= {:n nil} (let [{{a :a} :n :select s} {:n nil}] s)))
+ (is (= {:n {}} (let [{{a :a} :n :select s} {:n {}}] s))))
+
+ (testing "defaults can turn nothing into something"
+ (is (= {:n {:a 42}} (let [{{a :a :or {a 42}} :n :select s} nil] s)))
+ (is (= {:n {:a 42}} (let [{{a :a :or {a 42}} :n :select s} {:n nil}] s))))))
+
+(deftest select-or-defaults
+ (let [sample-map {:a 1, :b 2, :c {:aa 10 :bb 20},
+ 'd 4 'e 5 'f {'dd 40 'ee 50},
+ "g" 6 "h" 7 "i" {"gg" 60 "hh" 70},}]
+ (testing "happy path"
+ (testing ":defaults"
+ (is (empty? (let [{:defaults d :or {}} {}] d)))
+ (is (thrown? Exception (eval '(let [{:defaults d :or {:a 1}} {}] d))))
+ (is (= {:a 1} (let [{:keys [a] :defaults d :or {:a 1}} {}] d)))
+ (is (= {:a 1} (let [{:keys [a] :defaults d :or {a 1}} {}] d)))
+ (is (= {:a 1, 'b 2, "c" 3} (let [{b 'b, c "c", :keys [a] :defaults d :or {:a 1, 'b 2, "c" 3}} {}] d))))
+
+ (testing ":keys + :select + :or + defaults"
+ (let [{:keys [a b z & :c :d] {:keys! [aa & :bb]} :c
+ :or {:d 42, z :or-z}
+ :select m
+ :defaults dfs} sample-map]
+ (is (= 1 a))
+ (is (= 2 b))
+ (is (= 10 aa))
+ (is (= {:z :or-z, :c {:aa 10, :bb 20}, :b 2, :d 42, :a 1} m))
+ (is (= {:d 42, :z :or-z} dfs))))
+
+ (testing ":syms + :select + :or + defaults"
+ (let [{:syms [d e z & 'd 'f] {:syms! [dd & 'ee]} 'f
+ :or {'d 42, z :or-z}
+ :select m
+ :defaults dfs} sample-map]
+ (is (= 4 d))
+ (is (= 5 e))
+ (is (= 40 dd))
+ (is (= '{f {dd 40, ee 50}, e 5, d 4, z :or-z} m))
+ (is (= '{d 42, z :or-z} dfs))))
+
+ (testing ":strs + :select + :or + defaults"
+ (let [{:strs [g h z & "d" "i"] {:strs! [gg & "hh"]} "i"
+ :or {"d" 42, z :or-z}
+ :select m
+ :defaults dfs} sample-map]
+ (is (= 6 g))
+ (is (= 7 h))
+ (is (= 60 gg))
+ (is (= {"d" 42, "z" :or-z, "i" {"gg" 60, "hh" 70}, "g" 6, "h" 7} m))
+ (is (= {"d" 42, "z" :or-z} dfs))))
+
+ (testing "mixed things after &"
+ (is (= 1 (let [{:keys [a & 'b]} {:a 1}] a)))
+ (is (= 1 (let [{:keys! [a & 'b "c"]} {:a 1, 'b 2, "c" 3}] a))))
+
+ (testing "known compile-time errors"
+ (is (thrown? Exception (eval '(let [{:keys [a] :defaults d :or {:a 1, a 1}} {}] d))))
+ (is (thrown? Exception (eval '(let [{:defaults d} {}] d))))))))
diff --git a/test/clojure/test_clojure/delays.clj b/test/clojure/test_clojure/delays.clj
index 0a2a1c99ff..322feb6e49 100644
--- a/test/clojure/test_clojure/delays.clj
+++ b/test/clojure/test_clojure/delays.clj
@@ -66,3 +66,24 @@
(.await barrier)
(is (instance? Exception (try-call)))
(is (identical? (try-call) (try-call)))))
+
+(deftest delays-are-suppliers
+ (let [dt (delay true)
+ df (delay false)
+ dn (delay nil)
+ di (delay 100)]
+
+ (is (instance? java.util.function.Supplier dt))
+ (is (instance? java.util.function.BooleanSupplier dt))
+ (is (true? (.get dt)))
+ (is (true? (.getAsBoolean dt)))
+ (is (false? (.getAsBoolean df)))
+ (is (false? (.getAsBoolean dn)))
+
+ (is (instance? java.util.function.Supplier di))
+ (is (instance? java.util.function.IntSupplier di))
+ (is (instance? java.util.function.LongSupplier di))
+ (is (= 100 (.get ^java.util.function.Supplier di)))
+ (is (= 100 (.getAsInt ^java.util.function.IntSupplier di)))
+ (is (= 100 (.getAsLong ^java.util.function.LongSupplier di)))
+ (is (= 100.0 (.getAsDouble ^java.util.function.DoubleSupplier di)))))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/errors.clj b/test/clojure/test_clojure/errors.clj
index cebe0392e3..0ee958786d 100644
--- a/test/clojure/test_clojure/errors.clj
+++ b/test/clojure/test_clojure/errors.clj
@@ -107,9 +107,9 @@
(with-out-str (pr t))
(catch Throwable t (is nil)))))))
-(deftest ex-info-disallows-nil-data
- (is (thrown? IllegalArgumentException (ex-info "message" nil)))
- (is (thrown? IllegalArgumentException (ex-info "message" nil (Throwable. "cause")))))
+(deftest ex-info-allows-nil-data
+ (is (= {} (ex-data (ex-info "message" nil))))
+ (is (= {} (ex-data (ex-info "message" nil (Throwable. "cause"))))))
(deftest ex-info-arities-construct-equivalent-exceptions
(let [ex1 (ex-info "message" {:foo "bar"})
diff --git a/test/clojure/test_clojure/genclass.clj b/test/clojure/test_clojure/genclass.clj
index 18e808dc11..3377b36ec2 100644
--- a/test/clojure/test_clojure/genclass.clj
+++ b/test/clojure/test_clojure/genclass.clj
@@ -16,7 +16,8 @@
ExampleAnnotationClass
ProtectedFinalTester
ArrayDefInterface
- ArrayGenInterface]
+ ArrayGenInterface
+ ImportedTypeHintInterface]
[java.lang.annotation ElementType
Retention
@@ -116,7 +117,11 @@
"returnsLongArray" :longs
"returnsFloatArray" :floats
"returnsDoubleArray" :doubles
- "returnsBooleanArray" :booleans))))
+ "returnsBooleanArray" :booleans)))
+ (testing "imported class type hints"
+ (let [method-with-name #(method-with-name % (.getMethods ImportedTypeHintInterface))]
+ (is (= java.util.Map (return-type (method-with-name "returnsImportedType"))))
+ (is (= java.util.Map (parameter-type (method-with-name "takesImportedType")))))))
(testing "gen-interface"
(let [method-with-name #(method-with-name % (.getMethods ArrayGenInterface))]
(testing "sugar primitive array hints"
@@ -149,3 +154,13 @@
(visitSource [source debug] (.append sourceFile source)))]
(.accept classReader sourceVisitor 0)
(is (= "examples.clj" (str sourceFile)))))
+
+(deftest array-descriptors->class
+ (are [descr c] (= (#'clojure.core/the-class descr) c)
+ "[Ljava.util.UUID;" java.util.UUID/1
+ 'String java.lang.String
+ 'String/1 java.lang.String/1
+ 'java.util.UUID java.util.UUID
+ 'java.util.UUID/2 java.util.UUID/2
+ 'int/1 int/1
+ 'boolean/9 boolean/9))
diff --git a/test/clojure/test_clojure/genclass/examples.clj b/test/clojure/test_clojure/genclass/examples.clj
index f30c38a08e..55b6cb97cd 100644
--- a/test/clojure/test_clojure/genclass/examples.clj
+++ b/test/clojure/test_clojure/genclass/examples.clj
@@ -11,6 +11,8 @@
:author "Stuart Halloway, Daniel Solano Gómez"}
clojure.test-clojure.genclass.examples)
+(import java.util.Map)
+
(definterface ExampleInterface
(foo [a])
(foo [a b])
@@ -79,6 +81,10 @@
identity
[^clojure.test-clojure.genclass.examples.ArrayDefInterface a]))
+(definterface ImportedTypeHintInterface
+ (^Map returnsImportedType [])
+ (^void takesImportedType [^Map m]))
+
(gen-interface
:name clojure.test_clojure.genclass.examples.ArrayGenInterface
:methods [; sugar
diff --git a/test/clojure/test_clojure/generated_all_fi_adapters_in_let.clj b/test/clojure/test_clojure/generated_all_fi_adapters_in_let.clj
new file mode 100644
index 0000000000..4bac4914b9
--- /dev/null
+++ b/test/clojure/test_clojure/generated_all_fi_adapters_in_let.clj
@@ -0,0 +1,305 @@
+
+(ns clojure.test-clojure.generated-all-fi-adapters-in-let
+ (:use clojure.test)
+ (:require [clojure.string :as str])
+ (:import (clojure.test AdapterExerciser AdapterExerciser$L
+AdapterExerciser$I
+AdapterExerciser$S
+AdapterExerciser$B
+AdapterExerciser$D
+AdapterExerciser$F
+AdapterExerciser$O
+AdapterExerciser$LL
+AdapterExerciser$DL
+AdapterExerciser$OL
+AdapterExerciser$LI
+AdapterExerciser$DI
+AdapterExerciser$OI
+AdapterExerciser$LS
+AdapterExerciser$DS
+AdapterExerciser$OS
+AdapterExerciser$LB
+AdapterExerciser$DB
+AdapterExerciser$OB
+AdapterExerciser$LD
+AdapterExerciser$DD
+AdapterExerciser$OD
+AdapterExerciser$LF
+AdapterExerciser$DF
+AdapterExerciser$OF
+AdapterExerciser$LO
+AdapterExerciser$DO
+AdapterExerciser$OO
+AdapterExerciser$LLL
+AdapterExerciser$LOL
+AdapterExerciser$OLL
+AdapterExerciser$DDL
+AdapterExerciser$LDL
+AdapterExerciser$DLL
+AdapterExerciser$OOL
+AdapterExerciser$ODL
+AdapterExerciser$DOL
+AdapterExerciser$LLI
+AdapterExerciser$LOI
+AdapterExerciser$OLI
+AdapterExerciser$DDI
+AdapterExerciser$LDI
+AdapterExerciser$DLI
+AdapterExerciser$OOI
+AdapterExerciser$ODI
+AdapterExerciser$DOI
+AdapterExerciser$LLS
+AdapterExerciser$LOS
+AdapterExerciser$OLS
+AdapterExerciser$DDS
+AdapterExerciser$LDS
+AdapterExerciser$DLS
+AdapterExerciser$OOS
+AdapterExerciser$ODS
+AdapterExerciser$DOS
+AdapterExerciser$LLB
+AdapterExerciser$LOB
+AdapterExerciser$OLB
+AdapterExerciser$DDB
+AdapterExerciser$LDB
+AdapterExerciser$DLB
+AdapterExerciser$OOB
+AdapterExerciser$ODB
+AdapterExerciser$DOB
+AdapterExerciser$LLD
+AdapterExerciser$LOD
+AdapterExerciser$OLD
+AdapterExerciser$DDD
+AdapterExerciser$LDD
+AdapterExerciser$DLD
+AdapterExerciser$OOD
+AdapterExerciser$ODD
+AdapterExerciser$DOD
+AdapterExerciser$LLF
+AdapterExerciser$LOF
+AdapterExerciser$OLF
+AdapterExerciser$DDF
+AdapterExerciser$LDF
+AdapterExerciser$DLF
+AdapterExerciser$OOF
+AdapterExerciser$ODF
+AdapterExerciser$DOF
+AdapterExerciser$LLO
+AdapterExerciser$LOO
+AdapterExerciser$OLO
+AdapterExerciser$DDO
+AdapterExerciser$LDO
+AdapterExerciser$DLO
+AdapterExerciser$OOO
+AdapterExerciser$ODO
+AdapterExerciser$DOO
+AdapterExerciser$OOOO
+AdapterExerciser$OOOOO
+AdapterExerciser$OOOOOO
+AdapterExerciser$OOOOOOO
+AdapterExerciser$OOOOOOOO
+AdapterExerciser$OOOOOOOOO
+AdapterExerciser$OOOOOOOOOO
+AdapterExerciser$OOOOOOOOOOO
+)))
+
+ (deftest test-all-fi-adapters-in-let
+ (let [^AdapterExerciser exerciser (AdapterExerciser.)
+ ^AdapterExerciser$L Ladapter (fn [] (long 1))
+ ^AdapterExerciser$I Iadapter (fn [] 1)
+ ^AdapterExerciser$S Sadapter (fn [] (short 1))
+ ^AdapterExerciser$B Badapter (fn [] (byte 1))
+ ^AdapterExerciser$D Dadapter (fn [] (double 1))
+ ^AdapterExerciser$F Fadapter (fn [] (float 1))
+ ^AdapterExerciser$O Oadapter (fn [] exerciser)
+ ^AdapterExerciser$LL LLadapter (fn [^long a] (long 1))
+ ^AdapterExerciser$DL DLadapter (fn [^double a] (long 1))
+ ^AdapterExerciser$OL OLadapter (fn [^AdapterExerciser a] (long 1))
+ ^AdapterExerciser$LI LIadapter (fn [^long a] 1)
+ ^AdapterExerciser$DI DIadapter (fn [^double a] 1)
+ ^AdapterExerciser$OI OIadapter (fn [^AdapterExerciser a] 1)
+ ^AdapterExerciser$LS LSadapter (fn [^long a] (short 1))
+ ^AdapterExerciser$DS DSadapter (fn [^double a] (short 1))
+ ^AdapterExerciser$OS OSadapter (fn [^AdapterExerciser a] (short 1))
+ ^AdapterExerciser$LB LBadapter (fn [^long a] (byte 1))
+ ^AdapterExerciser$DB DBadapter (fn [^double a] (byte 1))
+ ^AdapterExerciser$OB OBadapter (fn [^AdapterExerciser a] (byte 1))
+ ^AdapterExerciser$LD LDadapter (fn [^long a] (double 1))
+ ^AdapterExerciser$DD DDadapter (fn [^double a] (double 1))
+ ^AdapterExerciser$OD ODadapter (fn [^AdapterExerciser a] (double 1))
+ ^AdapterExerciser$LF LFadapter (fn [^long a] (float 1))
+ ^AdapterExerciser$DF DFadapter (fn [^double a] (float 1))
+ ^AdapterExerciser$OF OFadapter (fn [^AdapterExerciser a] (float 1))
+ ^AdapterExerciser$LO LOadapter (fn [^long a] exerciser)
+ ^AdapterExerciser$DO DOadapter (fn [^double a] exerciser)
+ ^AdapterExerciser$OO OOadapter (fn [^AdapterExerciser a] exerciser)
+ ^AdapterExerciser$LLL LLLadapter (fn [^long a ^long b] (long 1))
+ ^AdapterExerciser$LOL LOLadapter (fn [^long a ^AdapterExerciser b] (long 1))
+ ^AdapterExerciser$OLL OLLadapter (fn [^AdapterExerciser a ^long b] (long 1))
+ ^AdapterExerciser$DDL DDLadapter (fn [^double a ^double b] (long 1))
+ ^AdapterExerciser$LDL LDLadapter (fn [^long a ^double b] (long 1))
+ ^AdapterExerciser$DLL DLLadapter (fn [^double a ^long b] (long 1))
+ ^AdapterExerciser$OOL OOLadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (long 1))
+ ^AdapterExerciser$ODL ODLadapter (fn [^AdapterExerciser a ^double b] (long 1))
+ ^AdapterExerciser$DOL DOLadapter (fn [^double a ^AdapterExerciser b] (long 1))
+ ^AdapterExerciser$LLI LLIadapter (fn [^long a ^long b] 1)
+ ^AdapterExerciser$LOI LOIadapter (fn [^long a ^AdapterExerciser b] 1)
+ ^AdapterExerciser$OLI OLIadapter (fn [^AdapterExerciser a ^long b] 1)
+ ^AdapterExerciser$DDI DDIadapter (fn [^double a ^double b] 1)
+ ^AdapterExerciser$LDI LDIadapter (fn [^long a ^double b] 1)
+ ^AdapterExerciser$DLI DLIadapter (fn [^double a ^long b] 1)
+ ^AdapterExerciser$OOI OOIadapter (fn [^AdapterExerciser a ^AdapterExerciser b] 1)
+ ^AdapterExerciser$ODI ODIadapter (fn [^AdapterExerciser a ^double b] 1)
+ ^AdapterExerciser$DOI DOIadapter (fn [^double a ^AdapterExerciser b] 1)
+ ^AdapterExerciser$LLS LLSadapter (fn [^long a ^long b] (short 1))
+ ^AdapterExerciser$LOS LOSadapter (fn [^long a ^AdapterExerciser b] (short 1))
+ ^AdapterExerciser$OLS OLSadapter (fn [^AdapterExerciser a ^long b] (short 1))
+ ^AdapterExerciser$DDS DDSadapter (fn [^double a ^double b] (short 1))
+ ^AdapterExerciser$LDS LDSadapter (fn [^long a ^double b] (short 1))
+ ^AdapterExerciser$DLS DLSadapter (fn [^double a ^long b] (short 1))
+ ^AdapterExerciser$OOS OOSadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (short 1))
+ ^AdapterExerciser$ODS ODSadapter (fn [^AdapterExerciser a ^double b] (short 1))
+ ^AdapterExerciser$DOS DOSadapter (fn [^double a ^AdapterExerciser b] (short 1))
+ ^AdapterExerciser$LLB LLBadapter (fn [^long a ^long b] (byte 1))
+ ^AdapterExerciser$LOB LOBadapter (fn [^long a ^AdapterExerciser b] (byte 1))
+ ^AdapterExerciser$OLB OLBadapter (fn [^AdapterExerciser a ^long b] (byte 1))
+ ^AdapterExerciser$DDB DDBadapter (fn [^double a ^double b] (byte 1))
+ ^AdapterExerciser$LDB LDBadapter (fn [^long a ^double b] (byte 1))
+ ^AdapterExerciser$DLB DLBadapter (fn [^double a ^long b] (byte 1))
+ ^AdapterExerciser$OOB OOBadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (byte 1))
+ ^AdapterExerciser$ODB ODBadapter (fn [^AdapterExerciser a ^double b] (byte 1))
+ ^AdapterExerciser$DOB DOBadapter (fn [^double a ^AdapterExerciser b] (byte 1))
+ ^AdapterExerciser$LLD LLDadapter (fn [^long a ^long b] (double 1))
+ ^AdapterExerciser$LOD LODadapter (fn [^long a ^AdapterExerciser b] (double 1))
+ ^AdapterExerciser$OLD OLDadapter (fn [^AdapterExerciser a ^long b] (double 1))
+ ^AdapterExerciser$DDD DDDadapter (fn [^double a ^double b] (double 1))
+ ^AdapterExerciser$LDD LDDadapter (fn [^long a ^double b] (double 1))
+ ^AdapterExerciser$DLD DLDadapter (fn [^double a ^long b] (double 1))
+ ^AdapterExerciser$OOD OODadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (double 1))
+ ^AdapterExerciser$ODD ODDadapter (fn [^AdapterExerciser a ^double b] (double 1))
+ ^AdapterExerciser$DOD DODadapter (fn [^double a ^AdapterExerciser b] (double 1))
+ ^AdapterExerciser$LLF LLFadapter (fn [^long a ^long b] (float 1))
+ ^AdapterExerciser$LOF LOFadapter (fn [^long a ^AdapterExerciser b] (float 1))
+ ^AdapterExerciser$OLF OLFadapter (fn [^AdapterExerciser a ^long b] (float 1))
+ ^AdapterExerciser$DDF DDFadapter (fn [^double a ^double b] (float 1))
+ ^AdapterExerciser$LDF LDFadapter (fn [^long a ^double b] (float 1))
+ ^AdapterExerciser$DLF DLFadapter (fn [^double a ^long b] (float 1))
+ ^AdapterExerciser$OOF OOFadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (float 1))
+ ^AdapterExerciser$ODF ODFadapter (fn [^AdapterExerciser a ^double b] (float 1))
+ ^AdapterExerciser$DOF DOFadapter (fn [^double a ^AdapterExerciser b] (float 1))
+ ^AdapterExerciser$LLO LLOadapter (fn [^long a ^long b] exerciser)
+ ^AdapterExerciser$LOO LOOadapter (fn [^long a ^AdapterExerciser b] exerciser)
+ ^AdapterExerciser$OLO OLOadapter (fn [^AdapterExerciser a ^long b] exerciser)
+ ^AdapterExerciser$DDO DDOadapter (fn [^double a ^double b] exerciser)
+ ^AdapterExerciser$LDO LDOadapter (fn [^long a ^double b] exerciser)
+ ^AdapterExerciser$DLO DLOadapter (fn [^double a ^long b] exerciser)
+ ^AdapterExerciser$OOO OOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b] exerciser)
+ ^AdapterExerciser$ODO ODOadapter (fn [^AdapterExerciser a ^double b] exerciser)
+ ^AdapterExerciser$DOO DOOadapter (fn [^double a ^AdapterExerciser b] exerciser)
+ ^AdapterExerciser$OOOO OOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c] exerciser)
+ ^AdapterExerciser$OOOOO OOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d] exerciser)
+ ^AdapterExerciser$OOOOOO OOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e] exerciser)
+ ^AdapterExerciser$OOOOOOO OOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f] exerciser)
+ ^AdapterExerciser$OOOOOOOO OOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g] exerciser)
+ ^AdapterExerciser$OOOOOOOOO OOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h] exerciser)
+ ^AdapterExerciser$OOOOOOOOOO OOOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h ^AdapterExerciser i] exerciser)
+ ^AdapterExerciser$OOOOOOOOOOO OOOOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h ^AdapterExerciser i ^AdapterExerciser j] exerciser)]
+ (is (= (.takesRetL Ladapter ) (long 1)))
+ (is (= (.takesRetI Iadapter ) 1))
+ (is (= (.takesRetS Sadapter ) (short 1)))
+ (is (= (.takesRetB Badapter ) (byte 1)))
+ (is (= (.takesRetD Dadapter ) (double 1)))
+ (is (= (.takesRetF Fadapter ) (float 1)))
+ (is (= (.takesRetO Oadapter ) exerciser))
+ (is (= (.takesLRetL LLadapter (long 1)) (long 1)))
+ (is (= (.takesDRetL DLadapter (double 1)) (long 1)))
+ (is (= (.takesORetL OLadapter exerciser) (long 1)))
+ (is (= (.takesLRetI LIadapter (long 1)) 1))
+ (is (= (.takesDRetI DIadapter (double 1)) 1))
+ (is (= (.takesORetI OIadapter exerciser) 1))
+ (is (= (.takesLRetS LSadapter (long 1)) (short 1)))
+ (is (= (.takesDRetS DSadapter (double 1)) (short 1)))
+ (is (= (.takesORetS OSadapter exerciser) (short 1)))
+ (is (= (.takesLRetB LBadapter (long 1)) (byte 1)))
+ (is (= (.takesDRetB DBadapter (double 1)) (byte 1)))
+ (is (= (.takesORetB OBadapter exerciser) (byte 1)))
+ (is (= (.takesLRetD LDadapter (long 1)) (double 1)))
+ (is (= (.takesDRetD DDadapter (double 1)) (double 1)))
+ (is (= (.takesORetD ODadapter exerciser) (double 1)))
+ (is (= (.takesLRetF LFadapter (long 1)) (float 1)))
+ (is (= (.takesDRetF DFadapter (double 1)) (float 1)))
+ (is (= (.takesORetF OFadapter exerciser) (float 1)))
+ (is (= (.takesLRetO LOadapter (long 1)) exerciser))
+ (is (= (.takesDRetO DOadapter (double 1)) exerciser))
+ (is (= (.takesORetO OOadapter exerciser) exerciser))
+ (is (= (.takesLLRetL LLLadapter (long 1) (long 1)) (long 1)))
+ (is (= (.takesLORetL LOLadapter (long 1) exerciser) (long 1)))
+ (is (= (.takesOLRetL OLLadapter exerciser (long 1)) (long 1)))
+ (is (= (.takesDDRetL DDLadapter (double 1) (double 1)) (long 1)))
+ (is (= (.takesLDRetL LDLadapter (long 1) (double 1)) (long 1)))
+ (is (= (.takesDLRetL DLLadapter (double 1) (long 1)) (long 1)))
+ (is (= (.takesOORetL OOLadapter exerciser exerciser) (long 1)))
+ (is (= (.takesODRetL ODLadapter exerciser (double 1)) (long 1)))
+ (is (= (.takesDORetL DOLadapter (double 1) exerciser) (long 1)))
+ (is (= (.takesLLRetI LLIadapter (long 1) (long 1)) 1))
+ (is (= (.takesLORetI LOIadapter (long 1) exerciser) 1))
+ (is (= (.takesOLRetI OLIadapter exerciser (long 1)) 1))
+ (is (= (.takesDDRetI DDIadapter (double 1) (double 1)) 1))
+ (is (= (.takesLDRetI LDIadapter (long 1) (double 1)) 1))
+ (is (= (.takesDLRetI DLIadapter (double 1) (long 1)) 1))
+ (is (= (.takesOORetI OOIadapter exerciser exerciser) 1))
+ (is (= (.takesODRetI ODIadapter exerciser (double 1)) 1))
+ (is (= (.takesDORetI DOIadapter (double 1) exerciser) 1))
+ (is (= (.takesLLRetS LLSadapter (long 1) (long 1)) (short 1)))
+ (is (= (.takesLORetS LOSadapter (long 1) exerciser) (short 1)))
+ (is (= (.takesOLRetS OLSadapter exerciser (long 1)) (short 1)))
+ (is (= (.takesDDRetS DDSadapter (double 1) (double 1)) (short 1)))
+ (is (= (.takesLDRetS LDSadapter (long 1) (double 1)) (short 1)))
+ (is (= (.takesDLRetS DLSadapter (double 1) (long 1)) (short 1)))
+ (is (= (.takesOORetS OOSadapter exerciser exerciser) (short 1)))
+ (is (= (.takesODRetS ODSadapter exerciser (double 1)) (short 1)))
+ (is (= (.takesDORetS DOSadapter (double 1) exerciser) (short 1)))
+ (is (= (.takesLLRetB LLBadapter (long 1) (long 1)) (byte 1)))
+ (is (= (.takesLORetB LOBadapter (long 1) exerciser) (byte 1)))
+ (is (= (.takesOLRetB OLBadapter exerciser (long 1)) (byte 1)))
+ (is (= (.takesDDRetB DDBadapter (double 1) (double 1)) (byte 1)))
+ (is (= (.takesLDRetB LDBadapter (long 1) (double 1)) (byte 1)))
+ (is (= (.takesDLRetB DLBadapter (double 1) (long 1)) (byte 1)))
+ (is (= (.takesOORetB OOBadapter exerciser exerciser) (byte 1)))
+ (is (= (.takesODRetB ODBadapter exerciser (double 1)) (byte 1)))
+ (is (= (.takesDORetB DOBadapter (double 1) exerciser) (byte 1)))
+ (is (= (.takesLLRetD LLDadapter (long 1) (long 1)) (double 1)))
+ (is (= (.takesLORetD LODadapter (long 1) exerciser) (double 1)))
+ (is (= (.takesOLRetD OLDadapter exerciser (long 1)) (double 1)))
+ (is (= (.takesDDRetD DDDadapter (double 1) (double 1)) (double 1)))
+ (is (= (.takesLDRetD LDDadapter (long 1) (double 1)) (double 1)))
+ (is (= (.takesDLRetD DLDadapter (double 1) (long 1)) (double 1)))
+ (is (= (.takesOORetD OODadapter exerciser exerciser) (double 1)))
+ (is (= (.takesODRetD ODDadapter exerciser (double 1)) (double 1)))
+ (is (= (.takesDORetD DODadapter (double 1) exerciser) (double 1)))
+ (is (= (.takesLLRetF LLFadapter (long 1) (long 1)) (float 1)))
+ (is (= (.takesLORetF LOFadapter (long 1) exerciser) (float 1)))
+ (is (= (.takesOLRetF OLFadapter exerciser (long 1)) (float 1)))
+ (is (= (.takesDDRetF DDFadapter (double 1) (double 1)) (float 1)))
+ (is (= (.takesLDRetF LDFadapter (long 1) (double 1)) (float 1)))
+ (is (= (.takesDLRetF DLFadapter (double 1) (long 1)) (float 1)))
+ (is (= (.takesOORetF OOFadapter exerciser exerciser) (float 1)))
+ (is (= (.takesODRetF ODFadapter exerciser (double 1)) (float 1)))
+ (is (= (.takesDORetF DOFadapter (double 1) exerciser) (float 1)))
+ (is (= (.takesLLRetO LLOadapter (long 1) (long 1)) exerciser))
+ (is (= (.takesLORetO LOOadapter (long 1) exerciser) exerciser))
+ (is (= (.takesOLRetO OLOadapter exerciser (long 1)) exerciser))
+ (is (= (.takesDDRetO DDOadapter (double 1) (double 1)) exerciser))
+ (is (= (.takesLDRetO LDOadapter (long 1) (double 1)) exerciser))
+ (is (= (.takesDLRetO DLOadapter (double 1) (long 1)) exerciser))
+ (is (= (.takesOORetO OOOadapter exerciser exerciser) exerciser))
+ (is (= (.takesODRetO ODOadapter exerciser (double 1)) exerciser))
+ (is (= (.takesDORetO DOOadapter (double 1) exerciser) exerciser))
+ (is (= (.takesOOORetO OOOOadapter exerciser exerciser exerciser) exerciser))
+ (is (= (.takesOOOORetO OOOOOadapter exerciser exerciser exerciser exerciser) exerciser))
+ (is (= (.takesOOOOORetO OOOOOOadapter exerciser exerciser exerciser exerciser exerciser) exerciser))
+ (is (= (.takesOOOOOORetO OOOOOOOadapter exerciser exerciser exerciser exerciser exerciser exerciser) exerciser))
+ (is (= (.takesOOOOOOORetO OOOOOOOOadapter exerciser exerciser exerciser exerciser exerciser exerciser exerciser) exerciser))
+ (is (= (.takesOOOOOOOORetO OOOOOOOOOadapter exerciser exerciser exerciser exerciser exerciser exerciser exerciser exerciser) exerciser))
+ (is (= (.takesOOOOOOOOORetO OOOOOOOOOOadapter exerciser exerciser exerciser exerciser exerciser exerciser exerciser exerciser exerciser) exerciser))
+ (is (= (.takesOOOOOOOOOORetO OOOOOOOOOOOadapter exerciser exerciser exerciser exerciser exerciser exerciser exerciser exerciser exerciser exerciser) exerciser))))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/generated_functional_adapters_in_def.clj b/test/clojure/test_clojure/generated_functional_adapters_in_def.clj
new file mode 100644
index 0000000000..ba62daa7c7
--- /dev/null
+++ b/test/clojure/test_clojure/generated_functional_adapters_in_def.clj
@@ -0,0 +1,206 @@
+
+(ns clojure.test-clojure.generated-functional-adapters-in-def
+ (:use clojure.test)
+ (:require [clojure.string :as str])
+ (:import (clojure.test AdapterExerciser)))
+
+ (deftest functional-adapters-in-def
+ (def exerciser (AdapterExerciser.))
+ (def Ladapter (fn [] (long 1)))
+ (is (= (.methodL ^AdapterExerciser exerciser Ladapter) "L"))
+ (def Iadapter (fn [] 1))
+ (is (= (.methodI ^AdapterExerciser exerciser Iadapter) "I"))
+ (def Sadapter (fn [] (short 1)))
+ (is (= (.methodS ^AdapterExerciser exerciser Sadapter) "S"))
+ (def Badapter (fn [] (byte 1)))
+ (is (= (.methodB ^AdapterExerciser exerciser Badapter) "B"))
+ (def Dadapter (fn [] (double 1)))
+ (is (= (.methodD ^AdapterExerciser exerciser Dadapter) "D"))
+ (def Fadapter (fn [] (float 1)))
+ (is (= (.methodF ^AdapterExerciser exerciser Fadapter) "F"))
+ (def Oadapter (fn [] exerciser))
+ (is (= (.methodO ^AdapterExerciser exerciser Oadapter) "O"))
+ (def LLadapter (fn [^long a] (long 1)))
+ (is (= (.methodLL ^AdapterExerciser exerciser LLadapter) "LL"))
+ (def DLadapter (fn [^double a] (long 1)))
+ (is (= (.methodDL ^AdapterExerciser exerciser DLadapter) "DL"))
+ (def OLadapter (fn [^AdapterExerciser a] (long 1)))
+ (is (= (.methodOL ^AdapterExerciser exerciser OLadapter) "OL"))
+ (def LIadapter (fn [^long a] 1))
+ (is (= (.methodLI ^AdapterExerciser exerciser LIadapter) "LI"))
+ (def DIadapter (fn [^double a] 1))
+ (is (= (.methodDI ^AdapterExerciser exerciser DIadapter) "DI"))
+ (def OIadapter (fn [^AdapterExerciser a] 1))
+ (is (= (.methodOI ^AdapterExerciser exerciser OIadapter) "OI"))
+ (def LSadapter (fn [^long a] (short 1)))
+ (is (= (.methodLS ^AdapterExerciser exerciser LSadapter) "LS"))
+ (def DSadapter (fn [^double a] (short 1)))
+ (is (= (.methodDS ^AdapterExerciser exerciser DSadapter) "DS"))
+ (def OSadapter (fn [^AdapterExerciser a] (short 1)))
+ (is (= (.methodOS ^AdapterExerciser exerciser OSadapter) "OS"))
+ (def LBadapter (fn [^long a] (byte 1)))
+ (is (= (.methodLB ^AdapterExerciser exerciser LBadapter) "LB"))
+ (def DBadapter (fn [^double a] (byte 1)))
+ (is (= (.methodDB ^AdapterExerciser exerciser DBadapter) "DB"))
+ (def OBadapter (fn [^AdapterExerciser a] (byte 1)))
+ (is (= (.methodOB ^AdapterExerciser exerciser OBadapter) "OB"))
+ (def LDadapter (fn [^long a] (double 1)))
+ (is (= (.methodLD ^AdapterExerciser exerciser LDadapter) "LD"))
+ (def DDadapter (fn [^double a] (double 1)))
+ (is (= (.methodDD ^AdapterExerciser exerciser DDadapter) "DD"))
+ (def ODadapter (fn [^AdapterExerciser a] (double 1)))
+ (is (= (.methodOD ^AdapterExerciser exerciser ODadapter) "OD"))
+ (def LFadapter (fn [^long a] (float 1)))
+ (is (= (.methodLF ^AdapterExerciser exerciser LFadapter) "LF"))
+ (def DFadapter (fn [^double a] (float 1)))
+ (is (= (.methodDF ^AdapterExerciser exerciser DFadapter) "DF"))
+ (def OFadapter (fn [^AdapterExerciser a] (float 1)))
+ (is (= (.methodOF ^AdapterExerciser exerciser OFadapter) "OF"))
+ (def LOadapter (fn [^long a] exerciser))
+ (is (= (.methodLO ^AdapterExerciser exerciser LOadapter) "LO"))
+ (def DOadapter (fn [^double a] exerciser))
+ (is (= (.methodDO ^AdapterExerciser exerciser DOadapter) "DO"))
+ (def OOadapter (fn [^AdapterExerciser a] exerciser))
+ (is (= (.methodOO ^AdapterExerciser exerciser OOadapter) "OO"))
+ (def LLLadapter (fn [^long a ^long b] (long 1)))
+ (is (= (.methodLLL ^AdapterExerciser exerciser LLLadapter) "LLL"))
+ (def LOLadapter (fn [^long a ^AdapterExerciser b] (long 1)))
+ (is (= (.methodLOL ^AdapterExerciser exerciser LOLadapter) "LOL"))
+ (def OLLadapter (fn [^AdapterExerciser a ^long b] (long 1)))
+ (is (= (.methodOLL ^AdapterExerciser exerciser OLLadapter) "OLL"))
+ (def DDLadapter (fn [^double a ^double b] (long 1)))
+ (is (= (.methodDDL ^AdapterExerciser exerciser DDLadapter) "DDL"))
+ (def LDLadapter (fn [^long a ^double b] (long 1)))
+ (is (= (.methodLDL ^AdapterExerciser exerciser LDLadapter) "LDL"))
+ (def DLLadapter (fn [^double a ^long b] (long 1)))
+ (is (= (.methodDLL ^AdapterExerciser exerciser DLLadapter) "DLL"))
+ (def OOLadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (long 1)))
+ (is (= (.methodOOL ^AdapterExerciser exerciser OOLadapter) "OOL"))
+ (def ODLadapter (fn [^AdapterExerciser a ^double b] (long 1)))
+ (is (= (.methodODL ^AdapterExerciser exerciser ODLadapter) "ODL"))
+ (def DOLadapter (fn [^double a ^AdapterExerciser b] (long 1)))
+ (is (= (.methodDOL ^AdapterExerciser exerciser DOLadapter) "DOL"))
+ (def LLIadapter (fn [^long a ^long b] 1))
+ (is (= (.methodLLI ^AdapterExerciser exerciser LLIadapter) "LLI"))
+ (def LOIadapter (fn [^long a ^AdapterExerciser b] 1))
+ (is (= (.methodLOI ^AdapterExerciser exerciser LOIadapter) "LOI"))
+ (def OLIadapter (fn [^AdapterExerciser a ^long b] 1))
+ (is (= (.methodOLI ^AdapterExerciser exerciser OLIadapter) "OLI"))
+ (def DDIadapter (fn [^double a ^double b] 1))
+ (is (= (.methodDDI ^AdapterExerciser exerciser DDIadapter) "DDI"))
+ (def LDIadapter (fn [^long a ^double b] 1))
+ (is (= (.methodLDI ^AdapterExerciser exerciser LDIadapter) "LDI"))
+ (def DLIadapter (fn [^double a ^long b] 1))
+ (is (= (.methodDLI ^AdapterExerciser exerciser DLIadapter) "DLI"))
+ (def OOIadapter (fn [^AdapterExerciser a ^AdapterExerciser b] 1))
+ (is (= (.methodOOI ^AdapterExerciser exerciser OOIadapter) "OOI"))
+ (def ODIadapter (fn [^AdapterExerciser a ^double b] 1))
+ (is (= (.methodODI ^AdapterExerciser exerciser ODIadapter) "ODI"))
+ (def DOIadapter (fn [^double a ^AdapterExerciser b] 1))
+ (is (= (.methodDOI ^AdapterExerciser exerciser DOIadapter) "DOI"))
+ (def LLSadapter (fn [^long a ^long b] (short 1)))
+ (is (= (.methodLLS ^AdapterExerciser exerciser LLSadapter) "LLS"))
+ (def LOSadapter (fn [^long a ^AdapterExerciser b] (short 1)))
+ (is (= (.methodLOS ^AdapterExerciser exerciser LOSadapter) "LOS"))
+ (def OLSadapter (fn [^AdapterExerciser a ^long b] (short 1)))
+ (is (= (.methodOLS ^AdapterExerciser exerciser OLSadapter) "OLS"))
+ (def DDSadapter (fn [^double a ^double b] (short 1)))
+ (is (= (.methodDDS ^AdapterExerciser exerciser DDSadapter) "DDS"))
+ (def LDSadapter (fn [^long a ^double b] (short 1)))
+ (is (= (.methodLDS ^AdapterExerciser exerciser LDSadapter) "LDS"))
+ (def DLSadapter (fn [^double a ^long b] (short 1)))
+ (is (= (.methodDLS ^AdapterExerciser exerciser DLSadapter) "DLS"))
+ (def OOSadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (short 1)))
+ (is (= (.methodOOS ^AdapterExerciser exerciser OOSadapter) "OOS"))
+ (def ODSadapter (fn [^AdapterExerciser a ^double b] (short 1)))
+ (is (= (.methodODS ^AdapterExerciser exerciser ODSadapter) "ODS"))
+ (def DOSadapter (fn [^double a ^AdapterExerciser b] (short 1)))
+ (is (= (.methodDOS ^AdapterExerciser exerciser DOSadapter) "DOS"))
+ (def LLBadapter (fn [^long a ^long b] (byte 1)))
+ (is (= (.methodLLB ^AdapterExerciser exerciser LLBadapter) "LLB"))
+ (def LOBadapter (fn [^long a ^AdapterExerciser b] (byte 1)))
+ (is (= (.methodLOB ^AdapterExerciser exerciser LOBadapter) "LOB"))
+ (def OLBadapter (fn [^AdapterExerciser a ^long b] (byte 1)))
+ (is (= (.methodOLB ^AdapterExerciser exerciser OLBadapter) "OLB"))
+ (def DDBadapter (fn [^double a ^double b] (byte 1)))
+ (is (= (.methodDDB ^AdapterExerciser exerciser DDBadapter) "DDB"))
+ (def LDBadapter (fn [^long a ^double b] (byte 1)))
+ (is (= (.methodLDB ^AdapterExerciser exerciser LDBadapter) "LDB"))
+ (def DLBadapter (fn [^double a ^long b] (byte 1)))
+ (is (= (.methodDLB ^AdapterExerciser exerciser DLBadapter) "DLB"))
+ (def OOBadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (byte 1)))
+ (is (= (.methodOOB ^AdapterExerciser exerciser OOBadapter) "OOB"))
+ (def ODBadapter (fn [^AdapterExerciser a ^double b] (byte 1)))
+ (is (= (.methodODB ^AdapterExerciser exerciser ODBadapter) "ODB"))
+ (def DOBadapter (fn [^double a ^AdapterExerciser b] (byte 1)))
+ (is (= (.methodDOB ^AdapterExerciser exerciser DOBadapter) "DOB"))
+ (def LLDadapter (fn [^long a ^long b] (double 1)))
+ (is (= (.methodLLD ^AdapterExerciser exerciser LLDadapter) "LLD"))
+ (def LODadapter (fn [^long a ^AdapterExerciser b] (double 1)))
+ (is (= (.methodLOD ^AdapterExerciser exerciser LODadapter) "LOD"))
+ (def OLDadapter (fn [^AdapterExerciser a ^long b] (double 1)))
+ (is (= (.methodOLD ^AdapterExerciser exerciser OLDadapter) "OLD"))
+ (def DDDadapter (fn [^double a ^double b] (double 1)))
+ (is (= (.methodDDD ^AdapterExerciser exerciser DDDadapter) "DDD"))
+ (def LDDadapter (fn [^long a ^double b] (double 1)))
+ (is (= (.methodLDD ^AdapterExerciser exerciser LDDadapter) "LDD"))
+ (def DLDadapter (fn [^double a ^long b] (double 1)))
+ (is (= (.methodDLD ^AdapterExerciser exerciser DLDadapter) "DLD"))
+ (def OODadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (double 1)))
+ (is (= (.methodOOD ^AdapterExerciser exerciser OODadapter) "OOD"))
+ (def ODDadapter (fn [^AdapterExerciser a ^double b] (double 1)))
+ (is (= (.methodODD ^AdapterExerciser exerciser ODDadapter) "ODD"))
+ (def DODadapter (fn [^double a ^AdapterExerciser b] (double 1)))
+ (is (= (.methodDOD ^AdapterExerciser exerciser DODadapter) "DOD"))
+ (def LLFadapter (fn [^long a ^long b] (float 1)))
+ (is (= (.methodLLF ^AdapterExerciser exerciser LLFadapter) "LLF"))
+ (def LOFadapter (fn [^long a ^AdapterExerciser b] (float 1)))
+ (is (= (.methodLOF ^AdapterExerciser exerciser LOFadapter) "LOF"))
+ (def OLFadapter (fn [^AdapterExerciser a ^long b] (float 1)))
+ (is (= (.methodOLF ^AdapterExerciser exerciser OLFadapter) "OLF"))
+ (def DDFadapter (fn [^double a ^double b] (float 1)))
+ (is (= (.methodDDF ^AdapterExerciser exerciser DDFadapter) "DDF"))
+ (def LDFadapter (fn [^long a ^double b] (float 1)))
+ (is (= (.methodLDF ^AdapterExerciser exerciser LDFadapter) "LDF"))
+ (def DLFadapter (fn [^double a ^long b] (float 1)))
+ (is (= (.methodDLF ^AdapterExerciser exerciser DLFadapter) "DLF"))
+ (def OOFadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (float 1)))
+ (is (= (.methodOOF ^AdapterExerciser exerciser OOFadapter) "OOF"))
+ (def ODFadapter (fn [^AdapterExerciser a ^double b] (float 1)))
+ (is (= (.methodODF ^AdapterExerciser exerciser ODFadapter) "ODF"))
+ (def DOFadapter (fn [^double a ^AdapterExerciser b] (float 1)))
+ (is (= (.methodDOF ^AdapterExerciser exerciser DOFadapter) "DOF"))
+ (def LLOadapter (fn [^long a ^long b] exerciser))
+ (is (= (.methodLLO ^AdapterExerciser exerciser LLOadapter) "LLO"))
+ (def LOOadapter (fn [^long a ^AdapterExerciser b] exerciser))
+ (is (= (.methodLOO ^AdapterExerciser exerciser LOOadapter) "LOO"))
+ (def OLOadapter (fn [^AdapterExerciser a ^long b] exerciser))
+ (is (= (.methodOLO ^AdapterExerciser exerciser OLOadapter) "OLO"))
+ (def DDOadapter (fn [^double a ^double b] exerciser))
+ (is (= (.methodDDO ^AdapterExerciser exerciser DDOadapter) "DDO"))
+ (def LDOadapter (fn [^long a ^double b] exerciser))
+ (is (= (.methodLDO ^AdapterExerciser exerciser LDOadapter) "LDO"))
+ (def DLOadapter (fn [^double a ^long b] exerciser))
+ (is (= (.methodDLO ^AdapterExerciser exerciser DLOadapter) "DLO"))
+ (def OOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b] exerciser))
+ (is (= (.methodOOO ^AdapterExerciser exerciser OOOadapter) "OOO"))
+ (def ODOadapter (fn [^AdapterExerciser a ^double b] exerciser))
+ (is (= (.methodODO ^AdapterExerciser exerciser ODOadapter) "ODO"))
+ (def DOOadapter (fn [^double a ^AdapterExerciser b] exerciser))
+ (is (= (.methodDOO ^AdapterExerciser exerciser DOOadapter) "DOO"))
+ (def OOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c] exerciser))
+ (is (= (.methodOOOO ^AdapterExerciser exerciser OOOOadapter) "OOOO"))
+ (def OOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d] exerciser))
+ (is (= (.methodOOOOO ^AdapterExerciser exerciser OOOOOadapter) "OOOOO"))
+ (def OOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e] exerciser))
+ (is (= (.methodOOOOOO ^AdapterExerciser exerciser OOOOOOadapter) "OOOOOO"))
+ (def OOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f] exerciser))
+ (is (= (.methodOOOOOOO ^AdapterExerciser exerciser OOOOOOOadapter) "OOOOOOO"))
+ (def OOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g] exerciser))
+ (is (= (.methodOOOOOOOO ^AdapterExerciser exerciser OOOOOOOOadapter) "OOOOOOOO"))
+ (def OOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h] exerciser))
+ (is (= (.methodOOOOOOOOO ^AdapterExerciser exerciser OOOOOOOOOadapter) "OOOOOOOOO"))
+ (def OOOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h ^AdapterExerciser i] exerciser))
+ (is (= (.methodOOOOOOOOOO ^AdapterExerciser exerciser OOOOOOOOOOadapter) "OOOOOOOOOO"))
+ (def OOOOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h ^AdapterExerciser i ^AdapterExerciser j] exerciser))
+ (is (= (.methodOOOOOOOOOOO ^AdapterExerciser exerciser OOOOOOOOOOOadapter) "OOOOOOOOOOO")))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/generated_functional_adapters_in_def_requiring_reflection.clj b/test/clojure/test_clojure/generated_functional_adapters_in_def_requiring_reflection.clj
new file mode 100644
index 0000000000..7e6212c3ab
--- /dev/null
+++ b/test/clojure/test_clojure/generated_functional_adapters_in_def_requiring_reflection.clj
@@ -0,0 +1,206 @@
+
+(ns clojure.test-clojure.generated-functional-adapters-in-def-requiring-reflection
+ (:use clojure.test)
+ (:require [clojure.string :as str])
+ (:import (clojure.test AdapterExerciser)))
+
+ (deftest functional-adapters-in-def
+ (def exerciser (AdapterExerciser.))
+ (def Ladapter (fn [] (long 1)))
+ (is (= (.methodL exerciser Ladapter) "L"))
+ (def Iadapter (fn [] 1))
+ (is (= (.methodI exerciser Iadapter) "I"))
+ (def Sadapter (fn [] (short 1)))
+ (is (= (.methodS exerciser Sadapter) "S"))
+ (def Badapter (fn [] (byte 1)))
+ (is (= (.methodB exerciser Badapter) "B"))
+ (def Dadapter (fn [] (double 1)))
+ (is (= (.methodD exerciser Dadapter) "D"))
+ (def Fadapter (fn [] (float 1)))
+ (is (= (.methodF exerciser Fadapter) "F"))
+ (def Oadapter (fn [] exerciser))
+ (is (= (.methodO exerciser Oadapter) "O"))
+ (def LLadapter (fn [^long a] (long 1)))
+ (is (= (.methodLL exerciser LLadapter) "LL"))
+ (def DLadapter (fn [^double a] (long 1)))
+ (is (= (.methodDL exerciser DLadapter) "DL"))
+ (def OLadapter (fn [^AdapterExerciser a] (long 1)))
+ (is (= (.methodOL exerciser OLadapter) "OL"))
+ (def LIadapter (fn [^long a] 1))
+ (is (= (.methodLI exerciser LIadapter) "LI"))
+ (def DIadapter (fn [^double a] 1))
+ (is (= (.methodDI exerciser DIadapter) "DI"))
+ (def OIadapter (fn [^AdapterExerciser a] 1))
+ (is (= (.methodOI exerciser OIadapter) "OI"))
+ (def LSadapter (fn [^long a] (short 1)))
+ (is (= (.methodLS exerciser LSadapter) "LS"))
+ (def DSadapter (fn [^double a] (short 1)))
+ (is (= (.methodDS exerciser DSadapter) "DS"))
+ (def OSadapter (fn [^AdapterExerciser a] (short 1)))
+ (is (= (.methodOS exerciser OSadapter) "OS"))
+ (def LBadapter (fn [^long a] (byte 1)))
+ (is (= (.methodLB exerciser LBadapter) "LB"))
+ (def DBadapter (fn [^double a] (byte 1)))
+ (is (= (.methodDB exerciser DBadapter) "DB"))
+ (def OBadapter (fn [^AdapterExerciser a] (byte 1)))
+ (is (= (.methodOB exerciser OBadapter) "OB"))
+ (def LDadapter (fn [^long a] (double 1)))
+ (is (= (.methodLD exerciser LDadapter) "LD"))
+ (def DDadapter (fn [^double a] (double 1)))
+ (is (= (.methodDD exerciser DDadapter) "DD"))
+ (def ODadapter (fn [^AdapterExerciser a] (double 1)))
+ (is (= (.methodOD exerciser ODadapter) "OD"))
+ (def LFadapter (fn [^long a] (float 1)))
+ (is (= (.methodLF exerciser LFadapter) "LF"))
+ (def DFadapter (fn [^double a] (float 1)))
+ (is (= (.methodDF exerciser DFadapter) "DF"))
+ (def OFadapter (fn [^AdapterExerciser a] (float 1)))
+ (is (= (.methodOF exerciser OFadapter) "OF"))
+ (def LOadapter (fn [^long a] exerciser))
+ (is (= (.methodLO exerciser LOadapter) "LO"))
+ (def DOadapter (fn [^double a] exerciser))
+ (is (= (.methodDO exerciser DOadapter) "DO"))
+ (def OOadapter (fn [^AdapterExerciser a] exerciser))
+ (is (= (.methodOO exerciser OOadapter) "OO"))
+ (def LLLadapter (fn [^long a ^long b] (long 1)))
+ (is (= (.methodLLL exerciser LLLadapter) "LLL"))
+ (def LOLadapter (fn [^long a ^AdapterExerciser b] (long 1)))
+ (is (= (.methodLOL exerciser LOLadapter) "LOL"))
+ (def OLLadapter (fn [^AdapterExerciser a ^long b] (long 1)))
+ (is (= (.methodOLL exerciser OLLadapter) "OLL"))
+ (def DDLadapter (fn [^double a ^double b] (long 1)))
+ (is (= (.methodDDL exerciser DDLadapter) "DDL"))
+ (def LDLadapter (fn [^long a ^double b] (long 1)))
+ (is (= (.methodLDL exerciser LDLadapter) "LDL"))
+ (def DLLadapter (fn [^double a ^long b] (long 1)))
+ (is (= (.methodDLL exerciser DLLadapter) "DLL"))
+ (def OOLadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (long 1)))
+ (is (= (.methodOOL exerciser OOLadapter) "OOL"))
+ (def ODLadapter (fn [^AdapterExerciser a ^double b] (long 1)))
+ (is (= (.methodODL exerciser ODLadapter) "ODL"))
+ (def DOLadapter (fn [^double a ^AdapterExerciser b] (long 1)))
+ (is (= (.methodDOL exerciser DOLadapter) "DOL"))
+ (def LLIadapter (fn [^long a ^long b] 1))
+ (is (= (.methodLLI exerciser LLIadapter) "LLI"))
+ (def LOIadapter (fn [^long a ^AdapterExerciser b] 1))
+ (is (= (.methodLOI exerciser LOIadapter) "LOI"))
+ (def OLIadapter (fn [^AdapterExerciser a ^long b] 1))
+ (is (= (.methodOLI exerciser OLIadapter) "OLI"))
+ (def DDIadapter (fn [^double a ^double b] 1))
+ (is (= (.methodDDI exerciser DDIadapter) "DDI"))
+ (def LDIadapter (fn [^long a ^double b] 1))
+ (is (= (.methodLDI exerciser LDIadapter) "LDI"))
+ (def DLIadapter (fn [^double a ^long b] 1))
+ (is (= (.methodDLI exerciser DLIadapter) "DLI"))
+ (def OOIadapter (fn [^AdapterExerciser a ^AdapterExerciser b] 1))
+ (is (= (.methodOOI exerciser OOIadapter) "OOI"))
+ (def ODIadapter (fn [^AdapterExerciser a ^double b] 1))
+ (is (= (.methodODI exerciser ODIadapter) "ODI"))
+ (def DOIadapter (fn [^double a ^AdapterExerciser b] 1))
+ (is (= (.methodDOI exerciser DOIadapter) "DOI"))
+ (def LLSadapter (fn [^long a ^long b] (short 1)))
+ (is (= (.methodLLS exerciser LLSadapter) "LLS"))
+ (def LOSadapter (fn [^long a ^AdapterExerciser b] (short 1)))
+ (is (= (.methodLOS exerciser LOSadapter) "LOS"))
+ (def OLSadapter (fn [^AdapterExerciser a ^long b] (short 1)))
+ (is (= (.methodOLS exerciser OLSadapter) "OLS"))
+ (def DDSadapter (fn [^double a ^double b] (short 1)))
+ (is (= (.methodDDS exerciser DDSadapter) "DDS"))
+ (def LDSadapter (fn [^long a ^double b] (short 1)))
+ (is (= (.methodLDS exerciser LDSadapter) "LDS"))
+ (def DLSadapter (fn [^double a ^long b] (short 1)))
+ (is (= (.methodDLS exerciser DLSadapter) "DLS"))
+ (def OOSadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (short 1)))
+ (is (= (.methodOOS exerciser OOSadapter) "OOS"))
+ (def ODSadapter (fn [^AdapterExerciser a ^double b] (short 1)))
+ (is (= (.methodODS exerciser ODSadapter) "ODS"))
+ (def DOSadapter (fn [^double a ^AdapterExerciser b] (short 1)))
+ (is (= (.methodDOS exerciser DOSadapter) "DOS"))
+ (def LLBadapter (fn [^long a ^long b] (byte 1)))
+ (is (= (.methodLLB exerciser LLBadapter) "LLB"))
+ (def LOBadapter (fn [^long a ^AdapterExerciser b] (byte 1)))
+ (is (= (.methodLOB exerciser LOBadapter) "LOB"))
+ (def OLBadapter (fn [^AdapterExerciser a ^long b] (byte 1)))
+ (is (= (.methodOLB exerciser OLBadapter) "OLB"))
+ (def DDBadapter (fn [^double a ^double b] (byte 1)))
+ (is (= (.methodDDB exerciser DDBadapter) "DDB"))
+ (def LDBadapter (fn [^long a ^double b] (byte 1)))
+ (is (= (.methodLDB exerciser LDBadapter) "LDB"))
+ (def DLBadapter (fn [^double a ^long b] (byte 1)))
+ (is (= (.methodDLB exerciser DLBadapter) "DLB"))
+ (def OOBadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (byte 1)))
+ (is (= (.methodOOB exerciser OOBadapter) "OOB"))
+ (def ODBadapter (fn [^AdapterExerciser a ^double b] (byte 1)))
+ (is (= (.methodODB exerciser ODBadapter) "ODB"))
+ (def DOBadapter (fn [^double a ^AdapterExerciser b] (byte 1)))
+ (is (= (.methodDOB exerciser DOBadapter) "DOB"))
+ (def LLDadapter (fn [^long a ^long b] (double 1)))
+ (is (= (.methodLLD exerciser LLDadapter) "LLD"))
+ (def LODadapter (fn [^long a ^AdapterExerciser b] (double 1)))
+ (is (= (.methodLOD exerciser LODadapter) "LOD"))
+ (def OLDadapter (fn [^AdapterExerciser a ^long b] (double 1)))
+ (is (= (.methodOLD exerciser OLDadapter) "OLD"))
+ (def DDDadapter (fn [^double a ^double b] (double 1)))
+ (is (= (.methodDDD exerciser DDDadapter) "DDD"))
+ (def LDDadapter (fn [^long a ^double b] (double 1)))
+ (is (= (.methodLDD exerciser LDDadapter) "LDD"))
+ (def DLDadapter (fn [^double a ^long b] (double 1)))
+ (is (= (.methodDLD exerciser DLDadapter) "DLD"))
+ (def OODadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (double 1)))
+ (is (= (.methodOOD exerciser OODadapter) "OOD"))
+ (def ODDadapter (fn [^AdapterExerciser a ^double b] (double 1)))
+ (is (= (.methodODD exerciser ODDadapter) "ODD"))
+ (def DODadapter (fn [^double a ^AdapterExerciser b] (double 1)))
+ (is (= (.methodDOD exerciser DODadapter) "DOD"))
+ (def LLFadapter (fn [^long a ^long b] (float 1)))
+ (is (= (.methodLLF exerciser LLFadapter) "LLF"))
+ (def LOFadapter (fn [^long a ^AdapterExerciser b] (float 1)))
+ (is (= (.methodLOF exerciser LOFadapter) "LOF"))
+ (def OLFadapter (fn [^AdapterExerciser a ^long b] (float 1)))
+ (is (= (.methodOLF exerciser OLFadapter) "OLF"))
+ (def DDFadapter (fn [^double a ^double b] (float 1)))
+ (is (= (.methodDDF exerciser DDFadapter) "DDF"))
+ (def LDFadapter (fn [^long a ^double b] (float 1)))
+ (is (= (.methodLDF exerciser LDFadapter) "LDF"))
+ (def DLFadapter (fn [^double a ^long b] (float 1)))
+ (is (= (.methodDLF exerciser DLFadapter) "DLF"))
+ (def OOFadapter (fn [^AdapterExerciser a ^AdapterExerciser b] (float 1)))
+ (is (= (.methodOOF exerciser OOFadapter) "OOF"))
+ (def ODFadapter (fn [^AdapterExerciser a ^double b] (float 1)))
+ (is (= (.methodODF exerciser ODFadapter) "ODF"))
+ (def DOFadapter (fn [^double a ^AdapterExerciser b] (float 1)))
+ (is (= (.methodDOF exerciser DOFadapter) "DOF"))
+ (def LLOadapter (fn [^long a ^long b] exerciser))
+ (is (= (.methodLLO exerciser LLOadapter) "LLO"))
+ (def LOOadapter (fn [^long a ^AdapterExerciser b] exerciser))
+ (is (= (.methodLOO exerciser LOOadapter) "LOO"))
+ (def OLOadapter (fn [^AdapterExerciser a ^long b] exerciser))
+ (is (= (.methodOLO exerciser OLOadapter) "OLO"))
+ (def DDOadapter (fn [^double a ^double b] exerciser))
+ (is (= (.methodDDO exerciser DDOadapter) "DDO"))
+ (def LDOadapter (fn [^long a ^double b] exerciser))
+ (is (= (.methodLDO exerciser LDOadapter) "LDO"))
+ (def DLOadapter (fn [^double a ^long b] exerciser))
+ (is (= (.methodDLO exerciser DLOadapter) "DLO"))
+ (def OOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b] exerciser))
+ (is (= (.methodOOO exerciser OOOadapter) "OOO"))
+ (def ODOadapter (fn [^AdapterExerciser a ^double b] exerciser))
+ (is (= (.methodODO exerciser ODOadapter) "ODO"))
+ (def DOOadapter (fn [^double a ^AdapterExerciser b] exerciser))
+ (is (= (.methodDOO exerciser DOOadapter) "DOO"))
+ (def OOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c] exerciser))
+ (is (= (.methodOOOO exerciser OOOOadapter) "OOOO"))
+ (def OOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d] exerciser))
+ (is (= (.methodOOOOO exerciser OOOOOadapter) "OOOOO"))
+ (def OOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e] exerciser))
+ (is (= (.methodOOOOOO exerciser OOOOOOadapter) "OOOOOO"))
+ (def OOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f] exerciser))
+ (is (= (.methodOOOOOOO exerciser OOOOOOOadapter) "OOOOOOO"))
+ (def OOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g] exerciser))
+ (is (= (.methodOOOOOOOO exerciser OOOOOOOOadapter) "OOOOOOOO"))
+ (def OOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h] exerciser))
+ (is (= (.methodOOOOOOOOO exerciser OOOOOOOOOadapter) "OOOOOOOOO"))
+ (def OOOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h ^AdapterExerciser i] exerciser))
+ (is (= (.methodOOOOOOOOOO exerciser OOOOOOOOOOadapter) "OOOOOOOOOO"))
+ (def OOOOOOOOOOOadapter (fn [^AdapterExerciser a ^AdapterExerciser b ^AdapterExerciser c ^AdapterExerciser d ^AdapterExerciser e ^AdapterExerciser f ^AdapterExerciser g ^AdapterExerciser h ^AdapterExerciser i ^AdapterExerciser j] exerciser))
+ (is (= (.methodOOOOOOOOOOO exerciser OOOOOOOOOOOadapter) "OOOOOOOOOOO")))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/java/io.clj b/test/clojure/test_clojure/java/io.clj
index b83c63d311..78864220d6 100644
--- a/test/clojure/test_clojure/java/io.clj
+++ b/test/clojure/test_clojure/java/io.clj
@@ -13,6 +13,7 @@
FileInputStream InputStreamReader InputStream
FileOutputStream OutputStreamWriter OutputStream
ByteArrayInputStream ByteArrayOutputStream)
+ (clojure.lang RT)
(java.net URL URI Socket ServerSocket)))
(defn temp-file
@@ -48,7 +49,7 @@
(slurp read-from :encoding "UTF-8")))
f f
(.getAbsolutePath f) (.getAbsolutePath f)
- (.toURL f) (.toURL f)
+ (RT/toUrl f) (RT/toUrl f)
(.toURI f) (.toURI f)
(FileOutputStream. f) (FileInputStream. f)
(OutputStreamWriter. (FileOutputStream. f) "UTF-8") (reader f :encoding "UTF-8")
@@ -60,6 +61,9 @@
(finally
(.delete f)))))
+(deftest clj-2783-slurp-maintains-backward-compatibility-errors
+ (is (thrown? java.io.FileNotFoundException (slurp "whitespace = #'\\s+'"))))
+
(deftest test-streams-nil
(is (thrown-with-msg? IllegalArgumentException #"Cannot open.*nil" (reader nil)))
(is (thrown-with-msg? IllegalArgumentException #"Cannot open.*nil" (writer nil)))
diff --git a/test/clojure/test_clojure/java/javadoc.clj b/test/clojure/test_clojure/java/javadoc.clj
index 4ef665121f..664ef22f28 100644
--- a/test/clojure/test_clojure/java/javadoc.clj
+++ b/test/clojure/test_clojure/java/javadoc.clj
@@ -16,12 +16,17 @@
(str (format url-pattern-str module-name) url-suffix))
(deftest javadoc-url-test
+
(testing "for a core api"
- (binding [*feeling-lucky* false]
- (are [x y] (= x (#'j/javadoc-url y))
- nil "foo.Bar"
- (correct-url *core-java-api* "java.base" "java/lang/String.html") "java.lang.String"
- (correct-url *core-java-api* "java.sql" "java/sql/Connection.html") "java.sql.Connection")))
+ (are [x y] (= x (#'j/javadoc-url y))
+ nil "foo.Bar"
+ (correct-url *core-java-api* "java.base" "java/lang/String.html") "java.lang.String"
+ (correct-url *core-java-api* "java.sql" "java/sql/Connection.html") "java.sql.Connection"))
+
(testing "for a remote javadoc"
(binding [*remote-javadocs* (ref (sorted-map "java." "http://example.com/"))]
- (is (= "http://example.com/java/lang/Number.html" (#'j/javadoc-url "java.lang.Number"))))))
+ (is (= "http://example.com/java/lang/Number.html" (#'j/javadoc-url "java.lang.Number")))))
+
+ (testing "javadoc-url strips out inner classes"
+ (is (= (correct-url *core-java-api* "java.base" "java/util/Map.html")
+ (#'j/javadoc-url "java.util.Map$Entry")))))
diff --git a/test/clojure/test_clojure/java/process.clj b/test/clojure/test_clojure/java/process.clj
new file mode 100644
index 0000000000..3e187d58ec
--- /dev/null
+++ b/test/clojure/test_clojure/java/process.clj
@@ -0,0 +1,28 @@
+; 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.test-clojure.java.process
+ (:require
+ [clojure.test :refer :all]
+ [clojure.java.process :as p]
+ [clojure.string :as str]))
+
+(deftest test-stderr-redirect
+ ;; capture to stdout and return string
+ (is (not (str/blank? (p/exec "bash" "-c" "ls"))))
+
+ ;; print to stderr, capture nil
+ (is (str/blank? (p/exec "bash" "-c" "ls >&2")))
+
+ ;; redirect, then capture to string
+ (is (not (str/blank? (p/exec {:err :stdout} "bash" "-c" "ls >&2")))))
+
+(deftest test-process-deref
+ (is (zero? @(p/exit-ref (p/start "sleep" "1"))))
+ (is (zero? (deref (p/exit-ref (p/start "sleep" "1")) 2500 :timeout)))
+ (is (= :timeout (deref (p/exit-ref (p/start "sleep" "1")) 1 :timeout))))
diff --git a/test/clojure/test_clojure/java_interop.clj b/test/clojure/test_clojure/java_interop.clj
index 4925284cc2..3b28cafadc 100644
--- a/test/clojure/test_clojure/java_interop.clj
+++ b/test/clojure/test_clojure/java_interop.clj
@@ -15,9 +15,14 @@
[clojure.inspector]
[clojure.pprint :as pp]
[clojure.set :as set]
- [clojure.test-clojure.proxy.examples :as proxy-examples])
+ [clojure.string :as str]
+ [clojure.test-clojure.proxy.examples :as proxy-examples]
+ [clojure.test-helper :refer [should-not-reflect]])
(:import java.util.Base64
- (java.util.concurrent.atomic AtomicLong AtomicInteger)))
+ (java.io File FileFilter FilenameFilter)
+ (java.util UUID)
+ (java.util.concurrent.atomic AtomicLong AtomicInteger)
+ (clojure.test FIConstructor FIStatic FunctionalTester AdapterExerciser)))
; http://clojure.org/java_interop
; http://clojure.org/compilation
@@ -408,7 +413,7 @@
(to-array [1 2 3]) ))
(defn queue [& contents]
- (apply conj (clojure.lang.PersistentQueue/EMPTY) contents))
+ (apply conj clojure.lang.PersistentQueue/EMPTY contents))
(defn array-typed-equals [expected actual]
(and (= (class expected) (class actual))
@@ -639,3 +644,248 @@
(deftest test-boxing-prevention-when-compiling-statements
(is (= 1 (.get (doto (AtomicInteger. 0) inc-atomic-int))))
(is (= 1 (.get (doto (AtomicLong. 0) inc-atomic-long)))))
+
+(deftest array-type-symbols
+ (is (= long/1 (class (make-array Long/TYPE 0))))
+ (is (= int/1 (class (make-array Integer/TYPE 0))))
+ (is (= double/1 (class (make-array Double/TYPE 0))))
+ (is (= short/1 (class (make-array Short/TYPE 0))))
+ (is (= boolean/1 (class (make-array Boolean/TYPE 0))))
+ (is (= byte/1 (class (make-array Byte/TYPE 0))))
+ (is (= float/1 (class (make-array Float/TYPE 0))))
+ (is (= String/1 (class (make-array String 0))))
+ (is (= java.lang.String/1 (class (make-array String 0))))
+ (is (= java.util.UUID/1 (class (make-array java.util.UUID 0))))
+ (is (= `byte/1 'byte/1))
+ (is (= `byte/3 'byte/3))
+ (is (= `java.util.UUID/1 'java.util.UUID/1))
+ (is (= `String/1 'java.lang.String/1))
+ (is (= `java.lang.String/1 'java.lang.String/1))
+ (is (= ['long/2] `[~'long/2])))
+
+
+(defn make-test-files []
+ (let [id (str (UUID/randomUUID))
+ temp-1 (java.io.File/createTempFile (str "test-1-" id)".edn")
+ temp-2 (java.io.File/createTempFile "test-2"".xml")
+ temp-3 (java.io.File/createTempFile (str "test-3-" id)".edn")
+ dir (File. (.getParent temp-3))]
+ {:dir dir :file-id id}))
+
+(defn return-long ^long []
+ (let [^java.util.function.ToLongFunction f (fn ^long [x] 1)]
+ (Long/highestOneBit (.applyAsLong f :x))))
+
+(deftest clojure-fn-as-java-fn
+ ;; pass Clojure fn as Java Predicate
+ (let [coll (java.util.ArrayList. [1 2 3 4 5])]
+ (is (true? (.removeIf coll even?)))
+ (is (= coll [1 3 5])))
+
+ ;; binding type hint triggers coercion
+ (is (instance? FileFilter
+ (let [^FileFilter ff (fn [f] true)] ff)))
+
+ ;; coercion in let - reflection has types that should work
+ (let [{:keys [dir file-id]} (make-test-files)
+ ^FileFilter ff (fn [^File f]
+ (str/includes? (.getName f) file-id))
+ filtered (.listFiles dir ff)]
+ (is (= 2 (count filtered))))
+
+ ;; coercion in let
+ (let [{:keys [dir file-id]} (make-test-files)
+ ^FileFilter ff (fn [^File f]
+ (str/includes? (.getName f) file-id))
+ filtered (.listFiles ^File dir ff)]
+ (is (= 2 (count filtered))))
+
+ ;;; resolve method ambiguity using member symbol and param-tags
+ (let [{:keys [dir file-id]} (make-test-files)
+ ^FileFilter ff (fn [^File f]
+ (str/includes? (.getName f) file-id))
+ filtered (^[FileFilter] File/.listFiles dir ff)]
+ (is (= 2 (count filtered))))
+
+ (defn files-with-ext [^File dir ext]
+ (vec (.list dir ^FilenameFilter #(str/ends-with? % ext))))
+
+ (let [{:keys [dir file-id]} (make-test-files)
+ ^FilenameFilter ff (fn [dir file-name]
+ (str/includes? file-name file-id))
+ filtered (.list ^File dir ff)]
+ (is (= 2 (count filtered))))
+
+ (let [^java.util.function.DoubleToLongFunction f (fn [d] (int d))]
+ (is (instance? java.util.function.DoubleToLongFunction f))
+ (is (= 10 (.applyAsLong f (double 10.6)))))
+
+ (let [^java.util.function.IntConsumer f (fn [i] nil)]
+ (is (nil? (.accept f 42))))
+
+ (let [^java.util.function.IntPredicate f (fn [i] true)]
+ (is (true? (.test f 42))))
+
+ (let [arr (java.util.ArrayList. [1 2 3 4 5])
+ ^java.util.function.ObjDoubleConsumer f (fn [arr i] nil)]
+ (is (nil? (.accept f arr 42))))
+
+ (let [f (constantly 100)
+ ^Runnable g f]
+ (is (identical? f g) "has been unintentionally adapted"))
+
+ (let [^java.util.function.Predicate pred even?
+ coll1 (java.util.ArrayList. [1 2 3 4 5])
+ coll2 (java.util.ArrayList. [6 7 8 9 10])]
+ (is (instance? java.util.function.Predicate pred))
+ (is (true? (.removeIf coll1 pred)))
+ (is (= coll1 [1 3 5]))
+ (is (true? (.removeIf coll2 pred)))
+ (is (= coll2 [7 9])))
+
+ (let [^java.util.function.Predicate pred even?
+ coll1 (java.util.ArrayList. [1 2 3 4 5])
+ cup-fn (java.util.ArrayList. [1 2 3 4 5])]
+ (is (instance? java.util.function.Predicate pred))
+ (is (true? (.removeIf coll1 pred)))
+ (is (= coll1 [1 3 5]))
+ (is (true? (.removeIf cup-fn pred)))
+ (is (= cup-fn [1 3 5])))
+
+ (should-not-reflect #(clojure.test-clojure.java-interop/return-long))
+
+ ;; FI in class constructor
+ (let [^java.util.function.Predicate hinted-pred (fn [i] (> i 0))
+ clj-pred (fn [i] (> i 0))
+ fi-constructor-1 (FIConstructor. hinted-pred)
+ fi-constructor-2 (FIConstructor. clj-pred)
+ fi-constructor-3 (FIConstructor. (fn [i] (> i 0)))]
+ (is (= [1 2] (.numbers fi-constructor-1)))
+ (is (= [1 2] (.numbers fi-constructor-2)))
+ (is (= [1 2] (.numbers fi-constructor-3))))
+
+ ;; FI as arg to static
+ (let [^java.util.function.Predicate hinted-pred (fn [i] (> i 0))
+ res (FIStatic/numbers hinted-pred)]
+ (is (= [1 2] res))))
+
+(deftest eval-in-place-supplier-instance
+ (def stream (java.util.stream.Stream/generate ^java.util.function.Supplier (atom 42)))
+ (is (instance? java.util.stream.Stream stream)))
+
+(deftest eval-in-place-as-java-fn
+ (def filtered-list (.removeIf (java.util.ArrayList. [1 2 3 4 5]) even?))
+ (is (true? filtered-list))
+
+ (def fi-constructor-numbers (.numbers (FIConstructor. (fn [i] (> i 0)))))
+ (is (= [1 2] fi-constructor-numbers))
+
+ (def fi-static (FIStatic/numbers (fn [i] (< i 0))))
+ (is (= [-2 -1] fi-static)))
+
+;; newDirectoryStream is overloaded, takes ^[Path String] or ^[Path DirectoryStream$Filter]
+;; so this method will reflect
+(defn get-dir-stream [^java.nio.file.Path dir-path glob-pattern]
+ (let [path (.toPath (java.io.File. dir-path))]
+ (java.nio.file.Files/newDirectoryStream path glob-pattern)))
+
+(deftest test-reflection-to-overloaded-method-taking-FI
+ ;; all of these should resolve at runtime in reflection
+ (is (not (nil? (get-dir-stream "." "*"))))
+ (is (not (nil? (get-dir-stream "." (reify java.nio.file.DirectoryStream$Filter (accept [_ path] (.isDirectory (.toFile path))))))))
+ ;; this one gets FI converted from IFn to DirectoryStream$Filter
+ (is (not (nil? (get-dir-stream "." (fn [^java.nio.file.Path path] (.isDirectory (.toFile path))))))))
+
+;; we only support FI invoke coercion up to 10 args, this has 11
+(definterface ^{java.lang.FunctionalInterface true} FIWontWork
+ (invoke [a b c d e f g h i j k]))
+
+(definterface ReceivesFI
+ (call [^clojure.test_clojure.java_interop.FIWontWork fi]))
+
+(deftest test-reify-to-FI-allowed
+ ;; throws because there is no 11-arity invoker method and thus it is not possible to coerce
+ (is (thrown? ClassCastException
+ (eval '(let [^clojure.test_clojure.java_interop.FIWontWork f
+ (fn [p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11] p11)]
+ (.invoke f 1 2 3 4 5 6 7 8 9 10 11)))))
+
+ (let [r (reify clojure.test_clojure.java_interop.ReceivesFI
+ (call [_ fi] (.invoke fi 0 0 0 0 0 0 0 0 0 0 1)))]
+
+ ;; doesn't throw at compilation time, but throws at runtime
+ ;; because IFn cannot be implicitly converted
+ (is (thrown? ClassCastException
+ (.call r (fn [a b c d e f g h i j k] k))))
+
+ ;; works because the reify implements the FI, no conversion necessary
+ (is (= 1 (.call r (reify clojure.test_clojure.java_interop.FIWontWork (invoke [_ a b c d e f g h i j k] k)))))))
+
+(definterface ^{java.lang.FunctionalInterface true} FIPrims
+ (^long invoke [^long a ^long b ^long c ^long d]))
+
+(definterface ReceivesFIPrims
+ (call [^clojure.test_clojure.java_interop.FIPrims fi]))
+
+(deftest test-match-prim-args-only-to-2
+ (let [r (reify clojure.test_clojure.java_interop.ReceivesFIPrims
+ (call [_ fi] (.invoke fi 1 2 3 4)))]
+ (is (= 4 (.call r (fn [^long a ^long b ^long c ^long d] d))))))
+
+(deftest test-invoke-fiprim-rets
+ (let [^clojure.test_clojure.java_interop.FIPrims f (fn [a b c d] a)]
+ (is (instance? clojure.test_clojure.java_interop.FIPrims f))
+ (is (= 1 (.invoke f 1 2 3 4))))
+
+ (is (= "LLL" (AdapterExerciser/.methodLLL (AdapterExerciser.) (fn ^long [^long a ^long b]))))
+ (is (= "OOOO" (AdapterExerciser/.methodOOOO (AdapterExerciser.) (fn ^long [^long a ^long b ^long c]))))
+ )
+
+(deftest test-null-reify
+ (is (= "null" ((fn [x] (FIStatic/allowsNullFI x)) nil))))
+
+(deftest test-FI-subtype
+ (is (= [1 2 3 4 5] (->> (java.util.stream.Stream/iterate 1 inc) stream-seq! (take 5)))))
+
+(deftest class-methods-with-fi-args
+ (testing "Constructor accepting FI arg, provided overloaded static class FI"
+ (let [fi (FunctionalTester. "Constructor" 0 FunctionalTester/getChar)]
+ (is (= \C (.testVar fi)))))
+
+ (testing "Instance method accepting FI arg, provided overloaded static class FI"
+ (let [fi (FunctionalTester. "asf" 0 FunctionalTester/getChar)]
+ (.instanceMethodWithFIArg fi "Instance" 0 FunctionalTester/getChar)
+ (is (= \I (.testVar fi)))))
+
+ (testing "Static method accepting FI arg, provided overloaded static class FI"
+ (is (= \S (FunctionalTester/staticMethodWithFIArg "Static" 0 FunctionalTester/getChar)))))
+
+;; call is reflective and one overload takes an FI (Supplier)
+(definterface TakesFIOverloaded
+ (call [^java.util.function.Supplier s])
+ (call [^String s]))
+
+(deftest CLJ-2898-reified-objs-both-IFn-and-FI
+ ;; f is both IFn and FI (Supplier)
+ (let [f (reify
+ java.util.function.Supplier
+ (get [_] 100)
+
+ clojure.lang.IFn
+ (applyTo [_ _] 201)
+ (invoke [_] 200))]
+
+ ;; should not be adapted. use Supplier.get() impl on tl
+ (is (= 100 (.get (ThreadLocal/withInitial f))))
+
+ (let [tfio (reify TakesFIOverloaded
+ (call [_ ^java.util.function.Supplier o] (.get o))
+ (call [_ ^String s] "string"))]
+ ;; reflective call to TakesFIOverloaded.call()
+ ;; as above, should not be adapted and use Supplier.get()
+ (is (= 100 (.call tfio (identity f)))))))
+
+(deftest CLJ-2914-Qualified-Method-Expr-NPE
+ (is (fails-with-cause? IllegalArgumentException
+ #"Malformed method expression.*String/\.length"
+ (eval '(fn [] (java.lang.String/.length))))))
diff --git a/test/clojure/test_clojure/keywords.clj b/test/clojure/test_clojure/keywords.clj
index 614fbc14e2..b486a067ab 100644
--- a/test/clojure/test_clojure/keywords.clj
+++ b/test/clojure/test_clojure/keywords.clj
@@ -27,5 +27,5 @@
(deftest arity-exceptions
(is (thrown-with-msg? IllegalArgumentException #"Wrong number of args \(0\) passed to: :kw" (:kw)))
(is (thrown-with-msg? IllegalArgumentException #"Wrong number of args \(20\) passed to: :foo/bar" (apply :foo/bar (range 20))))
- (is (thrown-with-msg? IllegalArgumentException #"Wrong number of args \(21\) passed to: :foo/bar" (apply :foo/bar (range 21))))
- (is (thrown-with-msg? IllegalArgumentException #"Wrong number of args \(22\) passed to: :foo/bar" (apply :foo/bar (range 22)))))
+ (is (thrown-with-msg? IllegalArgumentException #"Wrong number of args \(> 20\) passed to: :foo/bar" (apply :foo/bar (range 21))))
+ (is (thrown-with-msg? IllegalArgumentException #"Wrong number of args \(> 20\) passed to: :foo/bar" (apply :foo/bar (range 22)))))
diff --git a/test/clojure/test_clojure/method_thunks.clj b/test/clojure/test_clojure/method_thunks.clj
new file mode 100644
index 0000000000..b999e539a4
--- /dev/null
+++ b/test/clojure/test_clojure/method_thunks.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.
+; Authors: Fogus
+
+(ns clojure.test-clojure.method-thunks
+ (:use clojure.test)
+ (:require [clojure.java.io :as jio])
+ (:import (clojure.lang Compiler Tuple)
+ (java.util Arrays UUID Locale)
+ (java.io File FileFilter)
+ clojure.lang.IFn$LL))
+
+(set! *warn-on-reflection* true)
+
+(deftest method-arity-selection
+ (is (= '([] [] [])
+ (take 3 (repeatedly ^[] Tuple/create))))
+ (is (= '([1] [2] [3])
+ (map ^[_] Tuple/create [1 2 3])))
+ (is (= '([1 4] [2 5] [3 6])
+ (map ^[_ _] Tuple/create [1 2 3] [4 5 6]))))
+
+(deftest method-signature-selection
+ (is (= [1.23 3.14]
+ (map ^[double] Math/abs [1.23 -3.14])))
+ (is (= [(float 1.23) (float 3.14)]
+ (map ^[float] Math/abs [1.23 -3.14])))
+ (is (= [1 2 3]
+ (map ^[long] Math/abs [1 2 -3])))
+ (is (= [#uuid "00000000-0000-0001-0000-000000000002"]
+ (map ^[long long] UUID/new [1] [2])))
+ (is (= '("a" "12")
+ (map ^[Object] String/valueOf ["a" 12])))
+ (is (= ["A" "B" "C"]
+ (map ^[java.util.Locale] String/.toUpperCase ["a" "b" "c"] (repeat java.util.Locale/ENGLISH))))
+ (is (thrown? ClassCastException
+ (doall (map ^[long] String/valueOf [12 "a"]))))
+ (testing "bad method names"
+ (is (thrown-with-msg? Exception #"static method" (eval 'java.lang.String/foo)))
+ (is (thrown-with-msg? Exception #"instance method" (eval 'java.lang.String/.foo)))
+ (is (thrown-with-msg? Exception #"constructor" (eval 'Math/new)))))
+
+(def mt ^[_] Tuple/create)
+(def mts {:fromString ^[_] UUID/fromString})
+(def gbs ^[] String/.getBytes)
+
+(deftest method-thunks-in-structs
+ (is (= #uuid "00000000-0000-0001-0000-000000000002"
+ ((:fromString mts) "00000000-0000-0001-0000-000000000002")))
+ (is (= [1] (mt 1)))
+ (is (= 97 (first (seq (gbs "a"))))))
+
+(deftest hinted-method-values
+ (is (thrown? Exception (eval '(.listFiles (jio/file ".") #(File/.isDirectory %)))))
+ (is (seq (.listFiles (jio/file ".") ^FileFilter File/.isDirectory)))
+ (is (seq (File/.listFiles (jio/file ".") ^FileFilter File/.isDirectory)))
+ (is (seq (^[FileFilter] File/.listFiles (jio/file ".") ^FileFilter File/.isDirectory))))
diff --git a/test/clojure/test_clojure/numbers.clj b/test/clojure/test_clojure/numbers.clj
index bf822cc947..be4f7a0a60 100644
--- a/test/clojure/test_clojure/numbers.clj
+++ b/test/clojure/test_clojure/numbers.clj
@@ -598,7 +598,17 @@ Math/pow overflows to Infinity."
(is (== (denominator 1/2) 2))
(is (== (numerator 1/2) 1))
(is (= (bigint (/ 100000000000000000000 3)) 33333333333333333333))
- (is (= (long 10000000000000000000/3) 3333333333333333333)))
+ (is (= (long 10000000000000000000/3) 3333333333333333333))
+
+ ;; special cases around Long/MIN_VALUE
+ (is (= (/ 1 Long/MIN_VALUE) -1/9223372036854775808))
+ (is (true? (< (/ 1 Long/MIN_VALUE) 0)))
+ (is (true? (< (* 1 (/ 1 Long/MIN_VALUE)) 0)))
+ (is (= (abs (/ 1 Long/MIN_VALUE)) 1/9223372036854775808))
+ (is (false? (< (abs (/ 1 Long/MIN_VALUE)) 0)))
+ (is (false? (< (* 1 (abs (/ 1 Long/MIN_VALUE))) 0)))
+ (is (= (/ Long/MIN_VALUE -3) 9223372036854775808/3))
+ (is (false? (< (/ Long/MIN_VALUE -3) 0))))
(deftest test-arbitrary-precision-subtract
(are [x y] (= x y)
diff --git a/test/clojure/test_clojure/other_functions.clj b/test/clojure/test_clojure/other_functions.clj
index 517f633fcc..b7e3637925 100644
--- a/test/clojure/test_clojure/other_functions.clj
+++ b/test/clojure/test_clojure/other_functions.clj
@@ -102,6 +102,10 @@
(deftest test-constantly
(let [c0 (constantly 10)]
(are [x] (= 10 (c0 x))
+ nil
+ 42
+ "foo")
+ (are [x] (= 10 (c0 x :a :b :c))
nil
42
"foo")))
diff --git a/test/clojure/test_clojure/param_tags.clj b/test/clojure/test_clojure/param_tags.clj
new file mode 100644
index 0000000000..bccd3ef127
--- /dev/null
+++ b/test/clojure/test_clojure/param_tags.clj
@@ -0,0 +1,220 @@
+; 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.
+; Authors: Fogus
+
+(ns clojure.test-clojure.param-tags
+ (:use clojure.test)
+ (:require
+ [clojure.string :as str]
+ [clojure.reflect :as r]
+ [clojure.test-helper :refer [should-not-reflect]])
+ (:import
+ (clojure.test SwissArmy ConcreteClass)
+ (clojure.lang Tuple Compiler Compiler$CompilerException)
+ (java.util Arrays UUID Locale)))
+
+(set! *warn-on-reflection* true)
+
+(deftest no-hints-with-param-tags
+ (should-not-reflect
+ (defn touc-no-reflect [s]
+ (^[] String/.toUpperCase s)))
+ (should-not-reflect
+ (defn touc-no-reflectq [s]
+ (^[] java.lang.String/.toUpperCase s)))
+ (should-not-reflect
+ (defn touc-no-reflect-arg-tags [s]
+ (^[java.util.Locale] String/.toUpperCase s java.util.Locale/ENGLISH)))
+ (should-not-reflect
+ (defn no-overloads-no-reflect [v]
+ (java.time.OffsetDateTime/.getYear v))))
+
+(deftest no-param-tags-use-qualifier
+ ;; both Date and OffsetDateTime have .getYear - want to show here the qualifier is used
+ (let [f (fn [^java.util.Date d] (java.time.OffsetDateTime/.getYear d))
+ date (java.util.Date. 1714495523100)]
+ ;; works when passed OffsetDateTime
+ (is (= 2024 (f (-> date .toInstant (.atOffset java.time.ZoneOffset/UTC)))))
+
+ ;; fails when passed Date, expects OffsetDateTime
+ (is (thrown? ClassCastException
+ (f date)))))
+
+(deftest param-tags-in-invocation-positions
+ (testing "qualified static method invocation"
+ (is (= 3 (^[long] Math/abs -3)))
+ (is (= [1 2] (^[_ _] Tuple/create 1 2)))
+ (is (= "42" (Long/toString 42))))
+ (testing "qualified ctor invocation"
+ (is (= (^[long long] UUID/new 1 2) #uuid "00000000-0000-0001-0000-000000000002"))
+ (is (= (^[long long] java.util.UUID/new 1 2) #uuid "00000000-0000-0001-0000-000000000002"))
+ (is (= "a" (^[String] String/new "a"))))
+ (testing "qualified instance method invocation"
+ (is (= \A (String/.charAt "A" 0)))
+ (is (= "A" (^[java.util.Locale] String/.toUpperCase "a" java.util.Locale/ENGLISH)))
+ (is (= "A" (^[Locale] String/.toUpperCase "a" java.util.Locale/ENGLISH)))
+ (is (= 65 (aget (^[String] String/.getBytes "A" "US-ASCII") 0)))
+ (is (= "42" (^[] Long/.toString 42))))
+ (testing "string repr array type resolutions"
+ (let [lary (long-array [1 2 3 4 99 100])
+ oary (into-array [1 2 3 4 99 100])
+ sary (into-array String ["a" "b" "c"])]
+ (is (= 4 (^[longs long] Arrays/binarySearch lary (long 99))))
+ (is (= 4 (^[objects _] Arrays/binarySearch oary 99)))
+ (is (= 4 (^["[Ljava.lang.Object;" _] Arrays/binarySearch oary 99)))
+ (is (= 1 (^["[Ljava.lang.Object;" _] Arrays/binarySearch sary "b")))))
+ (testing "bad method names"
+ (is (thrown? Exception (eval '(^[] java.lang.String/foo "a"))))
+ (is (thrown? Exception (eval '(^[] java.lang.String/.foo "a"))))
+ (is (thrown? Exception (eval '(^[] Math/new "a"))))))
+
+
+;; Mapping of symbols returned from reflect call to :parameter-type used as arguments to .getDeclaredMethod,
+;; :arg-type used as arguments to the methods and constructors being tested, :arg-tag used as arg-tags
+;; to the methods and constructors being tested.
+(def reflected-parameter-types {'int {:parameter-type Integer/TYPE
+ :arg-type "(int 42)"
+ :arg-tag "int"}
+ 'boolean {:parameter-type Boolean/TYPE
+ :arg-type "true"
+ :arg-tag "boolean"}
+ 'long {:parameter-type Long/TYPE
+ :arg-type "42"
+ :arg-tag "long"}
+ 'long<> {:parameter-type (Class/forName "[J")
+ :arg-type "(long-array [1 2])"
+ :arg-tag "long*"}
+ 'int<><> {:parameter-type (Class/forName "[[I")
+ :arg-type "(make-array Integer/TYPE 1 2)"
+ :arg-tag "int**"}
+ 'java.lang.Object<> {:parameter-type (Class/forName "[Ljava.lang.Object;")
+ :arg-type "(into-array [1 2])"
+ :arg-tag "\"[Ljava.lang.Object;\""}
+ 'java.lang.String<> {:parameter-type (Class/forName "[Ljava.lang.String;")
+ :arg-type "(into-array [\"a\" \"b\"])"
+ :arg-tag "\"[Ljava.lang.String;\""}})
+
+(defn is-static-method? [class method-name params]
+ (let [method (.getDeclaredMethod ^Class class ^String (name method-name) ^"[Ljava.lang.Object;" params)]
+ (java.lang.reflect.Modifier/isStatic (.getModifiers method))))
+
+(defn get-methods
+ "Reflect the class located at `path`, filter out the public members, add a :type
+ of :constructor, :static, or :instance to each."
+ [path]
+ (let [reflected-class (r/reflect (resolve path))
+ public (filter #(contains? (:flags %) :public) (:members reflected-class))]
+ (reduce (fn [res m]
+ (let [class (-> m :declaring-class resolve)
+ params (into-array Class (map #(-> % reflected-parameter-types :parameter-type) (:parameter-types m)))]
+ (cond
+ (not (contains? m :return-type)) (conj res (assoc m :type :constructor))
+ (is-static-method? class (:name m) params) (conj res (assoc m :type :static))
+ :else (conj res (assoc m :type :instance)))))
+ [] public)))
+
+(defn exercise-constructor
+ "Provided a map of data returned from a call to reflect representing a constructor.
+ Construct a new instance of the class providing the appropriate arg-tags and return
+ a map containing the new instance and expected target class"
+ [{:keys [declaring-class parameter-types] :as m}]
+ (let [target-class (-> declaring-class str Class/forName)
+ args (str/join " " (map #(-> % reflected-parameter-types :arg-type) parameter-types))
+ arg-tags (str/join " " (map #(-> % reflected-parameter-types :arg-tag) parameter-types))
+ fun-call-str (read-string (str "(^[" arg-tags "] " declaring-class ". " args ")"))
+ _ (should-not-reflect #(eval 'fun-call-str))
+ new-instance (eval fun-call-str)]
+ {:expected target-class :actual new-instance}))
+
+(defn exercise-static-method
+ "Provided a map of data returned from a call to reflect representing a static class method.
+ Call the static method providing the appropriate arg-tags and return a map containing
+ the actual and expected response."
+ [{:keys [name declaring-class parameter-types]}]
+ (let [class (str declaring-class)
+ method (str name)
+ args (str/join " " (map #(-> % reflected-parameter-types :arg-type) parameter-types))
+ arg-tags (str/join " " (map #(-> % reflected-parameter-types :arg-tag) parameter-types))
+ expected-response (str/join "-" parameter-types)
+ fun-call-str (read-string (str "(^[" arg-tags "] " class "/" method " " args ")"))
+ _ (should-not-reflect #(eval 'fun-call-str))
+ response (eval fun-call-str)]
+ {:expected expected-response :actual response}))
+
+(defn exercise-instance-method
+ "Provided a map of data returned from a call to reflect representing a class instance method.
+ Call the method providing the appropriate arg-tags and return a map containing
+ the actual and expected response."
+ [{:keys [name declaring-class parameter-types]}]
+ (let [method (str "." name)
+ args (str/join " " (map #(-> % reflected-parameter-types :arg-type) parameter-types))
+ arg-tags (str/join " " (map #(-> % reflected-parameter-types :arg-tag) parameter-types))
+ expected-response (str/join "-" parameter-types)
+ fun-call-str (read-string (str "(^[" arg-tags "] " declaring-class "/" method " " "(" declaring-class ".)" " " args ")"))
+ _ (should-not-reflect #(eval 'fun-call-str))
+ response (eval fun-call-str)]
+ {:expected expected-response :actual response}))
+
+(deftest arg-tags-in-constructors-and-static-and-instance-methods
+ (doseq [m (get-methods 'clojure.test.SwissArmy)]
+ (case (:type m)
+ :constructor (let [{:keys [expected actual]} (exercise-constructor m)]
+ (is (instance? expected actual)))
+ :static (let [{:keys [expected actual]} (exercise-static-method m)]
+ (is (= expected actual)))
+ :instance (let [{:keys [expected actual]} (exercise-instance-method m)]
+ (is (= expected actual))))))
+
+(deftest field-overloads-method-CLJ-2899-regression
+ (testing "overloaded in value position"
+ (is (= "static-field" clojure.test.SwissArmy/doppelganger)))
+
+ (testing "overloaded in value position, w/paramtags"
+ (is (= "" (apply ^[] clojure.test.SwissArmy/doppelganger []))))
+
+ (testing "overloaded, invoke no args"
+ (is (= "" (clojure.test.SwissArmy/doppelganger))))
+
+ (testing "overloaded, invoke w/args"
+ (is (= "int-int-long" (clojure.test.SwissArmy/doppelganger (int 1) (int 2) (long 42)))))
+
+ (testing "non-overloaded, field holds IFn, invoke w/args fails"
+ (is (thrown? Exception (eval '(clojure.test.SwissArmy/idFn 42))))
+ (is (= #'clojure.core/identity clojure.test.SwissArmy/idFn)))
+
+ (testing "non-overloaded, field holds IFn, invoke no args"
+ (is (= #'clojure.core/identity (clojure.test.SwissArmy/idFn))))
+
+ (testing "instance method overloads"
+ (is (= "int-int" (clojure.test.SwissArmy/.doppelganger (clojure.test.SwissArmy/new) (int 1) (int 2))))
+ (is (= "int-int" (apply clojure.test.SwissArmy/.doppelganger (clojure.test.SwissArmy/new) (int 1) (int 2) [])))))
+
+(defmacro arg-tags-called-in-macro
+ [a-type b-type a b]
+ `(^[~a-type ~b-type] SwissArmy/staticArityOverloadMethod ~a ~b))
+
+(deftest arg-tags-in-macro
+ (is (= "int-int" (arg-tags-called-in-macro int int 1 2))))
+
+(deftest bridge-methods
+ (testing "Allows correct intended usage."
+ (let [concrete (ConcreteClass.)]
+ (is (= 42 (^[Integer] ConcreteClass/.stampWidgets concrete (int 99))))))
+ (testing "Will not call bridge method."
+ (is (thrown? Compiler$CompilerException
+ (eval '(let [concrete (clojure.test.ConcreteClass.)]
+ (^[Object] ConcreteClass/.stampWidgets concrete (int 99))))))))
+
+
+(deftest incorrect-arity-invocation-error-messages
+
+ (testing "Invocation with param-tags having incorrect number of args"
+ (let [e (try
+ (eval '(^[long] Math/abs -1 -2 -3))
+ (catch Compiler$CompilerException e (str "-> " (.getMessage (.getCause e)))))]
+ (is (not (nil? (re-find #"expected 1.*received 3" e))) "Error message was expected to indicate 1 argument was expected but 2 were provided"))))
diff --git a/test/clojure/test_clojure/pprint/test_pretty.clj b/test/clojure/test_clojure/pprint/test_pretty.clj
index 4d31a21396..4f4ad4969d 100644
--- a/test/clojure/test_clojure/pprint/test_pretty.clj
+++ b/test/clojure/test_clojure/pprint/test_pretty.clj
@@ -235,15 +235,15 @@ It is implemented with a number of custom enlive templates.\"
(defrecord pprint-test-rec [a b c])
(simple-tests pprint-datastructures-tests
- (tst-pprint 20 future-filled) #"#"
- (tst-pprint 20 future-unfilled) #"#"
- (tst-pprint 20 promise-filled) #"#"
+ (tst-pprint 20 future-filled) #"#object\[clojure\.core\$future_call\$reify__\w+ 0x[0-9a-f]+ \{:status\s*:ready,\s*:val\s+100\}\]"
+ (tst-pprint 20 future-unfilled) #"(?s)#object\[clojure\.core\$future_call\$reify__\w+ 0x[0-9a-f]+.*:status\s*:pending.*:val\s*nil.*\]"
+ (tst-pprint 20 promise-filled) #"(?s)#object\[clojure\.core\$promise\$reify__\w+ 0x[0-9a-f]+.*:status\s*:ready.*:val\s*\(first\s*second\s*third\).*\]"
;; This hangs currently, cause we can't figure out whether a promise is filled
;;(tst-pprint 20 promise-unfilled) #"#"
- (tst-pprint 20 basic-agent) #"#"
- (tst-pprint 20 basic-atom) #"#"
- (tst-pprint 20 basic-ref) #"#"
- (tst-pprint 20 delay-forced) #"#"
+ (tst-pprint 20 basic-agent) #"(?s)#object\[clojure\.lang\.Agent 0x[0-9a-f]+.*:status\s*:ready.*:val\s*\(first\s*second\s*third\).*\]"
+ (tst-pprint 20 basic-atom) #"(?s)#object\[clojure\.lang\.Atom 0x[0-9a-f]+.*:status\s*:ready.*:val\s*\(first\s*second\s*third\).*\]"
+ (tst-pprint 20 basic-ref) #"(?s)#object\[clojure\.lang\.Ref 0x[0-9a-f]+.*:status\s*:ready.*:val\s*\(first\s*second\s*third\).*\]"
+ (tst-pprint 20 delay-forced) #"(?s)#object\[clojure\.lang\.Delay 0x[0-9a-f]+.*:status\s*:ready.*:val\s*\(first\s*second\s*third\).*\]"
;; Currently no way not to force the delay
;;(tst-pprint 20 delay-unforced) #"#"
(tst-pprint 20 (pprint-test-rec. 'first 'second 'third)) "{:a first,\n :b second,\n :c third}"
@@ -412,4 +412,9 @@ It is implemented with a number of custom enlive templates.\"
^{:a 1} (with-meta (range 4) {:a 1})
"^{:a 1} #{1 4 3 2}\n"
- ^{:a 1} #{1 2 3 4})))
\ No newline at end of file
+ ^{:a 1} #{1 2 3 4})))
+
+(deftest CLJ-2801-tagged-literal-print-dup-test
+ (is (= "#foo 123"
+ (binding [*default-data-reader-fn* tagged-literal]
+ (with-out-str (eval (read-string "(pr #foo 123)")))))))
diff --git a/test/clojure/test_clojure/printer.clj b/test/clojure/test_clojure/printer.clj
index ffb3007138..0216f4f48f 100644
--- a/test/clojure/test_clojure/printer.clj
+++ b/test/clojure/test_clojure/printer.clj
@@ -123,8 +123,9 @@
(deftest print-throwable
(binding [*data-readers* {'error identity}]
- (are [e] (= (-> e Throwable->map)
- (-> e pr-str read-string))
+ (are [e] (let [e' e] ;; use same templated exception object in both cases
+ (= (-> e' Throwable->map)
+ (-> e' pr-str read-string)))
(Exception. "heyo")
(Throwable. "I can a throwable"
(Exception. "chain 1"
diff --git a/test/clojure/test_clojure/protocols.clj b/test/clojure/test_clojure/protocols.clj
index 4e951449bb..4cd87a42b5 100644
--- a/test/clojure/test_clojure/protocols.clj
+++ b/test/clojure/test_clojure/protocols.clj
@@ -705,3 +705,17 @@
(f [s] (seq s)))
(deftest test-resolve-type-hints-in-protocol-methods
(is (= 4 (clojure.test-clojure.protocols/f "test"))))
+
+;; CLJ-2698 - Ignore primitive hints in defprotocol return
+
+(defprotocol PrimHinted
+ (^double f1 [p a])
+ (^void f2 [p]))
+
+(deftest test-prim-ret-hints-ignored
+ (is (thrown-with-msg?
+ Exception
+ #"Receiver class"
+ (eval '(f1 (reify PrimHinted) :foo))))
+ (is (= :foo (f1 (reify PrimHinted (f1 [_ x] x)) :foo)))
+ (is (= "foo" (f2 (reify PrimHinted (f2 [_] "foo"))))))
diff --git a/test/clojure/test_clojure/reader.cljc b/test/clojure/test_clojure/reader.cljc
index 522a27928a..a0e1f1f13e 100644
--- a/test/clojure/test_clojure/reader.cljc
+++ b/test/clojure/test_clojure/reader.cljc
@@ -428,8 +428,9 @@
(doseq [form top-levels]
(clojure.walk/postwalk
#(when (list? %)
- (is (= (expected-metadata (first %))
- (meta %)))
+ (when (contains? expected-metadata (first %))
+ (is (= (expected-metadata (first %))
+ (meta %))))
(is (->> (meta %)
vals
(filter number?)
@@ -453,7 +454,26 @@
;; Anonymous function literal (#())
-(deftest t-Anonymouns-function-literal)
+(deftest t-Anonymous-function-literal
+ ;; #(vector %) => #(fn* [gen__#] (vector gen__#))
+ ;; [\S]+ matches the anon arg, then \1 is backref matching first group
+ (is (= "(fn* [] (vector))" (pr-str (read-string "#(vector)"))))
+ (is (not (nil? (re-matches #"\(fn\* \[([\S]+)] \(vector \1\)\)" (pr-str (read-string "#(vector %)"))))))
+ (is (not (nil? (re-matches #"\(fn\* \[([\S]+)] \(vector \1 \1\)\)" (pr-str (read-string "#(vector % %)"))))))
+ (is (not (nil? (re-matches #"\(fn\* \[([\S]+)] \(vector \1 \1\)\)" (pr-str (read-string "#(vector % %1)"))))))
+ (is (not (nil? (re-matches #"\(fn\* \[([\S]+) ([\S]+)] \(vector \1 \2\)\)" (pr-str (read-string "#(vector %1 %2)"))))))
+ (is (not (nil? (re-matches #"\(fn\* \[([\S]+) ([\S]+) & ([\S]+)] \(vector \2 \3\)\)" (pr-str (read-string "#(vector %2 %&)"))))))
+
+ ;; invalid formats
+ (is (thrown? RuntimeException (read-string "#(vector %%)")))
+ (is (thrown? RuntimeException (read-string "#(vector %1/2)")))
+ (is (thrown? RuntimeException (read-string "#(vector %1.5)")))
+ (is (thrown? RuntimeException (read-string "#(vector %-0.2)")))
+ (is (thrown? RuntimeException (read-string "#(vector %3M)")))
+
+ ;; check sneaking in discarded content
+ (is (thrown? RuntimeException (read-string "#(list %#_(first arg)1.00000001 %#_(secong arg)2r10 %#_(rest arg)-1.5)")))
+ )
;; Syntax-quote (`, note, the "backquote" character), Unquote (~) and
;; Unquote-splicing (~@)
diff --git a/test/clojure/test_clojure/reflect.clj b/test/clojure/test_clojure/reflect.clj
index 36ea68172e..2cb92a5a8f 100644
--- a/test/clojure/test_clojure/reflect.clj
+++ b/test/clojure/test_clojure/reflect.clj
@@ -51,4 +51,13 @@
(checkCLJ2066 (javax.xml.stream.XMLInputFactory/newInstance))
;; CLJ-2414 - find default method on interface of inaccessible class
- (checkCLJ2414 (java.nio.file.Paths/get "src" (into-array String []))))
\ No newline at end of file
+ (checkCLJ2414 (java.nio.file.Paths/get "src" (into-array String []))))
+
+(defn sleep [ms]
+ ;; in Java <19, does not reflect. Java >= 19, does reflect
+ (Thread/sleep ms))
+
+
+(deftest check-CLJ-2843
+ (sleep 1)
+ (sleep (Integer/valueOf 1)))
diff --git a/test/clojure/test_clojure/repl/deps.clj b/test/clojure/test_clojure/repl/deps.clj
new file mode 100644
index 0000000000..a2a69078d1
--- /dev/null
+++ b/test/clojure/test_clojure/repl/deps.clj
@@ -0,0 +1,30 @@
+(ns clojure.test-clojure.repl.deps
+ (:use clojure.test)
+ (:require [clojure.string :as str]
+ [clojure.repl.deps :as deps]
+ [clojure.main :as main]))
+
+(defmacro with-dynamic-loader
+ "Ensure or install a DynamicClassLoader as the current thread's
+ context classloader and execute the body."
+ [& body]
+ `(let [t# (Thread/currentThread)
+ cl# (.getContextClassLoader t#)]
+ (if (instance? ~'clojure.lang.DynamicClassLoader cl#)
+ (do ~@body)
+ (try
+ (.setContextClassLoader t# (clojure.lang.DynamicClassLoader. cl#))
+ ~@body
+ (finally
+ (.setContextClassLoader t# cl#))))))
+
+;(deftest test-no-add-libs-outside-repl
+; (try
+; (deps/add-lib 'org.clojure/data.json {:mvn/version "2.4.0"})
+; (is false "add-libs outside repl should throw")
+; (catch Throwable t (str/includes? (ex-message t) "add-libs")))
+;
+; (with-dynamic-loader
+; (binding [*repl* true]
+; (is (some #{'org.clojure/data.json} (deps/add-lib 'org.clojure/data.json {:mvn/version "2.4.0"})))))
+; )
diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj
index 7565c6fb8a..f8f3f5910d 100644
--- a/test/clojure/test_clojure/sequences.clj
+++ b/test/clojure/test_clojure/sequences.clj
@@ -413,6 +413,49 @@
(next (to-array [(into-array []) nil])) '(nil)
(next (to-array [(into-array []) 2 nil])) '(2 nil) ))
+(deftest test-nthnext+rest-on-0
+ (are [coll]
+ (and (= (seq coll) (nthnext coll 0))
+ (= coll (nthrest coll 0)))
+ nil
+ ""
+ ()
+ '(0)
+ []
+ [0]
+ #{}
+ {}
+ {:a 1}
+ (range 5)))
+
+(deftest test-nthnext+rest-on-pos
+ (are [coll n nthnext-expected nthrest-expected]
+ (and (= nthnext-expected (nthnext coll n))
+ (= nthrest-expected (nthrest coll n)))
+
+ ;coll n nthnext nthrest
+ nil 1 nil ()
+ "abc" 1 '(\b \c) '(\b \c)
+ "abc" 3 nil ()
+ "abc" 4 nil ()
+ () 1 nil ()
+ '(1) 1 nil ()
+ '(1) 2 nil ()
+ '(()) 1 nil ()
+ #{} 1 nil ()
+ {:a 1} 1 nil ()
+ [] 1 nil ()
+ [0] 1 nil ()
+ [0] 2 nil ()
+ [[] 2 nil] 1 '(2 nil) '(2 nil)
+ [[] 2 nil] 2 '(nil) '(nil)
+ [[] 2 nil] 3 nil ()
+ (sorted-set 1 2 3) 2 '(3) '(3)
+ (sorted-map :a 1 :b 2) 1 '([:b 2]) '([:b 2])
+ (into-array []) 1 nil ()
+ (into-array [1]) 1 nil ()
+ (range 5) 3 '(3 4) '(3 4)
+ (range 5) 5 nil ()))
(deftest test-last
(are [x y] (= x y)
@@ -781,6 +824,8 @@
(partition 5 [1 2 3]) ()
+ (partition 4 4 [0 0 0] (range 10)) '((0 1 2 3) (4 5 6 7) (8 9 0 0))
+
; (partition 0 [1 2 3]) (repeat nil) ; infinite sequence of nil
(partition -1 [1 2 3]) ()
(partition -2 [1 2 3]) () )
@@ -809,6 +854,8 @@
(partitionv 1 []) ()
(partitionv 1 [1 2 3]) '((1) (2) (3))
+ (partitionv 4 4 [0 0 0] (range 10)) '([0 1 2 3] [4 5 6 7] [8 9 0 0])
+
(partitionv 5 [1 2 3]) ()
(partitionv -1 [1 2 3]) ()
@@ -851,7 +898,9 @@
(take 0 [1 2 3 4 5]) ()
(take -1 [1 2 3 4 5]) ()
- (take -2 [1 2 3 4 5]) () ))
+ (take -2 [1 2 3 4 5]) ()
+
+ (take 1/4 [1 2 3 4 5]) '(1) ))
(deftest test-drop
@@ -863,8 +912,52 @@
(drop 0 [1 2 3 4 5]) '(1 2 3 4 5)
(drop -1 [1 2 3 4 5]) '(1 2 3 4 5)
- (drop -2 [1 2 3 4 5]) '(1 2 3 4 5) ))
+ (drop -2 [1 2 3 4 5]) '(1 2 3 4 5)
+
+ (drop 1/4 [1 2 3 4 5]) '(2 3 4 5) )
+ (are [coll] (= (drop 4 coll) (drop -2 (drop 4 coll)))
+ [0 1 2 3 4 5]
+ (seq [0 1 2 3 4 5])
+ (range 6)
+ (repeat 6 :x))
+ )
+
+(deftest test-nthrest
+ (are [x y] (= x y)
+ (nthrest [1 2 3 4 5] 1) '(2 3 4 5)
+ (nthrest [1 2 3 4 5] 3) '(4 5)
+ (nthrest [1 2 3 4 5] 5) ()
+ (nthrest [1 2 3 4 5] 9) ()
+
+ (nthrest [1 2 3 4 5] 0) '(1 2 3 4 5)
+ (nthrest [1 2 3 4 5] -1) '(1 2 3 4 5)
+ (nthrest [1 2 3 4 5] -2) '(1 2 3 4 5)
+
+ (nthrest [1 2 3 4 5] 1/4) '(2 3 4 5)
+ (nthrest [1 2 3 4 5] 1.2) '(3 4 5) )
+
+ ;; (nthrest coll 0) should return coll
+ (are [coll] (let [r (nthrest coll 0)] (and (= coll r) (= (class coll) (class r))))
+ [1 2 3]
+ (seq [1 2 3])
+ (range 10)
+ (repeat 10 :x)
+ (seq "abc") ))
+
+(deftest test-nthnext
+ (are [x y] (= x y)
+ (nthnext [1 2 3 4 5] 1) '(2 3 4 5)
+ (nthnext [1 2 3 4 5] 3) '(4 5)
+ (nthnext [1 2 3 4 5] 5) nil
+ (nthnext [1 2 3 4 5] 9) nil
+
+ (nthnext [1 2 3 4 5] 0) '(1 2 3 4 5)
+ (nthnext [1 2 3 4 5] -1) '(1 2 3 4 5)
+ (nthnext [1 2 3 4 5] -2) '(1 2 3 4 5)
+
+ (nthnext [1 2 3 4 5] 1/4) '(2 3 4 5)
+ (nthnext [1 2 3 4 5] 1.2) '(3 4 5) ))
(deftest test-take-nth
(are [x y] (= x y)
@@ -1003,7 +1096,12 @@
() '(1 2)
[] [1 2]
{} {:a 1 :b 2}
- #{} #{1 2} ))
+ #{} #{1 2})
+
+ ; CLJ-2718
+ (is (= '(:a) (drop 1 (repeat 2 :a))))
+ (is (= () (drop 2 (repeat 2 :a))))
+ (is (= () (drop 3 (repeat 2 :a)))))
(defspec longrange-equals-range 1000
(prop/for-all [start gen/int
@@ -1075,6 +1173,15 @@
(reduce + (iterator-seq (.iterator (range 100)))) 4950
(reduce + (iterator-seq (.iterator (range 0.0 100.0 1.0)))) 4950.0 ))
+(deftest range-meta
+ (are [r] (= r (with-meta r {:a 1}))
+ (range 10)
+ (range 5 10)
+ (range 5 10 1)
+ (range 10.0)
+ (range 5.0 10.0)
+ (range 5.0 10.0 1.0)))
+
(deftest range-test
(let [threads 10
n 1000
@@ -1523,6 +1630,16 @@
(range 1 2) 1 101
(range 1 3) 3 103))
+(deftest infinite-seq-hash
+ (are [e] (thrown? Exception (.hashCode ^Object e))
+ (iterate identity nil)
+ (cycle [1])
+ (repeat 1))
+ (are [e] (thrown? Exception (.hasheq ^clojure.lang.IHashEq e))
+ (iterate identity nil)
+ (cycle [1])
+ (repeat 1)))
+
(defspec iteration-seq-equals-reduce 1000
(prop/for-all [initk gen/int
seed gen/int]
diff --git a/test/clojure/test_clojure/serialization.clj b/test/clojure/test_clojure/serialization.clj
index 51a0d6a263..bce7013675 100644
--- a/test/clojure/test_clojure/serialization.clj
+++ b/test/clojure/test_clojure/serialization.clj
@@ -92,6 +92,7 @@
; lazy seqs
(lazy-seq nil)
(lazy-seq (list* (range 50)))
+ (iterator-seq (.iterator (range 50)))
; transient / persistent! round-trip
(build-via-transient [])
@@ -182,5 +183,11 @@
(agent nil)
;; stateful seqs
- (enumeration-seq (java.util.Collections/enumeration (range 50)))
- (iterator-seq (.iterator (range 50)))))
\ No newline at end of file
+ (enumeration-seq (java.util.Collections/enumeration (range 50)))))
+
+;; necessary for CVE-2024-22871
+(deftest CLJ-2839
+ (are [e] (thrown? Exception (.hashCode ^Object (-> e serialize deserialize)))
+ (repeat 1)
+ (iterate identity nil)
+ (cycle [1])))
\ No newline at end of file
diff --git a/test/clojure/test_clojure/server.clj b/test/clojure/test_clojure/server.clj
index 1d24d0b2dd..4d4f20a480 100644
--- a/test/clojure/test_clojure/server.clj
+++ b/test/clojure/test_clojure/server.clj
@@ -9,6 +9,7 @@
; Author: Alex Miller
(ns clojure.test-clojure.server
+ (:import java.util.Random)
(:require [clojure.test :refer :all])
(:require [clojure.core.server :as s]))
@@ -21,9 +22,26 @@
(is (= (ex-data e) opts))
(is (= msg (.getMessage e))))))
+(defn create-random-thread
+ []
+ (Thread.
+ (fn []
+ (let [random (new Random)]
+ (while (not (.isInterrupted (Thread/currentThread)))
+ (System/setProperty (Integer/toString (.nextInt random)) (Integer/toString (.nextInt random))))))))
+
(deftest test-validate-opts
(check-invalid-opts {} "Missing required socket server property :name")
(check-invalid-opts {:name "a" :accept 'clojure.core/+} "Missing required socket server property :port")
(doseq [port [-1 "5" 999999]]
(check-invalid-opts {:name "a" :port port :accept 'clojure.core/+} (str "Invalid socket server port: " port)))
(check-invalid-opts {:name "a" :port 5555} "Missing required socket server property :accept"))
+
+(deftest test-parse-props
+ (let [thread (create-random-thread)]
+ (.start thread)
+ (Thread/sleep 1000)
+ (try
+ (is (>= (count
+ (#'s/parse-props (System/getProperties))) 0))
+ (finally (.interrupt thread)))))
diff --git a/test/clojure/test_clojure/special.clj b/test/clojure/test_clojure/special.clj
index c4a04cffb2..78c8d13f40 100644
--- a/test/clojure/test_clojure/special.clj
+++ b/test/clojure/test_clojure/special.clj
@@ -66,6 +66,16 @@
(let [{:a/syms [b c d] :or {d 3}} {'a/b 1 'a/c 2}]
(is (= [1 2 3] [b c d]))))
+;; CLJ-2968 - only simple symbols allowed in namespaced destructuring directives
+(deftest binding-types-not-allowed
+ (doseq [directive [:a1/keys :a1/syms :a1/keys! :a1/syms!]
+ binding ['b/c :c :b1/c]]
+ (let [val {(if (= (name directive) "keys")
+ (keyword (namespace directive) "c")
+ (symbol (namespace directive) "c")) 1}]
+ (is (thrown-with-cause-msg? Exception #"did not conform to spec"
+ (eval `(let [{~directive ~(vector binding)} ~val] ~'c)))))))
+
(deftest keywords-not-allowed-in-let-bindings
(is (thrown-with-cause-msg? Exception #"did not conform to spec"
(eval '(let [:a 1] a))))
@@ -86,6 +96,11 @@
(is (thrown-with-cause-msg? Exception #"Unable to resolve symbol: b"
(eval '(let [{:keys [a] :or {b 2}} {:a 1}] [a b])))))
+(deftest amp-not-allowed-as-let-or-loop*-binding-name
+ (is (thrown? Exception (eval '(let [& 42] &))))
+ (is (thrown? Exception (eval '(let* [& 42] &))))
+ (is (thrown? Exception (eval '(loop* [& 42] &)))))
+
(require '[clojure.string :as s])
(deftest resolve-keyword-ns-alias-in-destructuring
(let [{:keys [::s/x ::s/y ::s/z] :or {z 3}} {:clojure.string/x 1 :clojure.string/y 2}]
diff --git a/test/clojure/test_clojure/streams.clj b/test/clojure/test_clojure/streams.clj
new file mode 100644
index 0000000000..c5462a1aa0
--- /dev/null
+++ b/test/clojure/test_clojure/streams.clj
@@ -0,0 +1,103 @@
+; 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.test-clojure.streams
+ (:use clojure.test)
+ (:import [java.util.stream Stream LongStream]
+ [java.util.function Consumer Predicate Supplier]))
+
+(deftest test-stream-reduce!
+ (is (= :only-val (stream-reduce! + (.stream [:only-val]))))
+ (is (= 0 (stream-reduce! + (.stream []))))
+ (is (= 5 (stream-reduce! + (.stream [1 4]))))
+ (is (= 6 (stream-reduce! + (.stream [1 2 3]))))
+ (is (= [2 3 4]
+ (stream-reduce! (fn [v i] (conj v (inc i)))
+ []
+ (.stream [1 2 3]))))
+
+ (is (= 15 (stream-reduce! + (LongStream/rangeClosed 1 5))))
+
+ (is (= 9 (stream-reduce! + (-> (Stream/of (to-array [1 2 3 4 5]))
+ (.filter (reify Predicate (test [_ v] (odd? v))))))))
+ (is (= {:a 1}
+ (meta
+ (stream-reduce! (fn [v i] (conj v (inc i)))
+ (with-meta [] {:a 1})
+ (.stream [1 2 3]))))))
+
+(deftest test-stream-reduced!
+ (is (= 5 (stream-reduce!
+ (fn [x y] (reduced (+ x y)))
+ (.stream [1 4]))))
+
+ (is (= 45 (stream-reduce!
+ (fn [acc v]
+ (if (= v 10)
+ (reduced acc)
+ (+ acc v)))
+ (LongStream/rangeClosed 1 10)))))
+
+(deftest stream-seq!-test
+ (let [none (.stream [])
+ one (Stream/of "a")
+ n (.stream ["a" "b" "c"])
+ inf (Stream/generate (reify Supplier (get [_] 42)))
+ st (stream-seq! one)
+ l100 (LongStream/range 0 100)]
+ (is (empty? (map identity (stream-seq! none))))
+ (is (seq? st))
+ (is (= ["a"] (map identity st)))
+ (is (= ["a" "b" "c"] (map identity (stream-seq! n))))
+ (is (= [42 42 42 42 42] (take 5 (stream-seq! inf))))
+ (is (= 4950 (reduce + (stream-seq! l100)))) 4950))
+
+(deftest stream-transduce!-test
+ (let [xf (comp (filter odd?) (take 5))]
+ (let [st (Stream/of (to-array [1 2 3 4 5 6 7 8 9]))]
+ (is (= [1 3 5 7 9] (stream-transduce! xf conj st))))
+
+ (let [inf (Stream/generate (reify Supplier (get [_] 0)))]
+ (is (empty? (stream-transduce! xf conj (.limit inf 50)))))
+
+ (let [inf (Stream/generate (reify Supplier (get [_] 43)))]
+ (is (= [43 43 43 43 43] (stream-transduce! xf conj (.limit inf 50)))))
+
+ (let [inf (Stream/generate (reify Supplier (get [_] 43)))]
+ (is (= 215 (stream-transduce! xf + (.limit inf 50)))))
+
+ (let [inf (Stream/generate (reify Supplier (get [_] 43)))]
+ (is (= 315 (stream-transduce! xf + 100 (.limit inf 50)))))
+
+ (let [inf (Stream/generate (reify Supplier (get [_] 43)))]
+ (is (= "4343434343" (stream-transduce! xf str (.limit inf 50)))))))
+
+(deftest stream-into!-test
+ (let [none (.stream [])
+ one (Stream/of "a")
+ n (.stream ["a" "b" "c"])
+ inf (Stream/generate (reify Supplier (get [_] 42)))
+ par (-> (LongStream/rangeClosed 1 10) .boxed .parallel)
+ xf (comp (map #(+ 2 %)) (filter odd?))
+ par2 (-> (LongStream/rangeClosed 1 10) .boxed .parallel)]
+ (is (empty? (stream-into! [] none)))
+ (is (= ["a"] (stream-into! [] one)))
+ (is (= ["a" "b" "c"] (stream-into! [] n)))
+ (is (= [42 42 42 42 42]
+ (stream-into! [] (.limit inf 5))))
+ (is (= [1 2 3 4 5 6 7 8 9 10]
+ (stream-into! [] par)))
+ (is (= {:a 1}
+ (meta
+ (stream-into! (with-meta [] {:a 1})
+ (.stream [1 2 3])))))
+ (is (= {:a 1}
+ (meta
+ (stream-into! (with-meta clojure.lang.PersistentQueue/EMPTY {:a 1})
+ (.stream [1 2 3])))))
+ (is (= [-1 -2 3 5 7 9 11] (stream-into! [-1 -2] xf par2)))))
diff --git a/test/clojure/test_clojure/vectors.clj b/test/clojure/test_clojure/vectors.clj
index ea2b8e77d5..97cc0c5add 100644
--- a/test/clojure/test_clojure/vectors.clj
+++ b/test/clojure/test_clojure/vectors.clj
@@ -9,7 +9,11 @@
; Author: Stuart Halloway, Daniel Solano Gómez
(ns clojure.test-clojure.vectors
- (:use clojure.test))
+ (:use clojure.test)
+ (:import
+ [java.util Collection Spliterator]
+ [java.util.function Consumer]
+ [java.util.stream Collectors]))
(deftest test-reversed-vec
(let [r (range 6)
@@ -428,3 +432,61 @@
(is (= [0 1 2] (new java.util.ArrayList [0 1 2])))
(is (not= [1 2] (take 1 (cycle [1 2]))))
(is (= [1 2 3 nil 4 5 6 nil] (eduction cat [[1 2 3 nil] [4 5 6 nil]]))))
+
+(set! *warn-on-reflection* true)
+
+;; remember returns a consumer that adds to an-atom of coll
+(defn remember
+ ^Consumer [an-atom]
+ (reify Consumer (accept [_ v] (swap! an-atom conj v))))
+
+(deftest test-empty-vector-spliterator
+ (let [v []
+ s (.spliterator ^Collection v)
+ seen (atom [])]
+ (is (= 0 (.estimateSize s) (.getExactSizeIfKnown s)))
+ (is (nil? (.trySplit s)))
+ (is (false? (.tryAdvance s (remember seen))))
+ (is (= @seen []))))
+
+;; tryAdvance then forEachRemaining walks vector spliterator
+(deftest test-spliterator-tryadvance-then-forEach
+ (let [n 66
+ source-vec (vec (range n))]
+ (for [v (into [(vec (range n))] (map #(subvec source-vec % (+ % 33)) (range 33)))]
+ (dotimes [up-to n]
+ (let [s (.spliterator ^Collection v)
+ seen (atom [])
+ consumer (remember seen)]
+ (loop [i 0]
+ (if (< i up-to)
+ (do (is (true? (.tryAdvance s consumer))) (recur (inc i)))
+ (.forEachRemaining s consumer)))
+ (is (= v @seen))
+ (is (false? (.tryAdvance s consumer))))))))
+
+;; recursively split vector spliterators, walk all of the splits
+(deftest test-spliterator-trySplit
+ (dotimes [n 257]
+ (let [v (vec (range n))
+ seen (atom #{})
+ consumer (remember seen)
+ splits (loop [ss [(.spliterator ^Collection v)]]
+ (let [ss' (map #(.trySplit ^Spliterator %) ss)]
+ (if (every? nil? ss')
+ ss
+ (recur (into ss (remove nil? ss'))))))]
+ (loop [[spl & sr] splits]
+ (when spl
+ (.forEachRemaining ^Spliterator spl consumer)
+ (recur sr)))
+ (is (= v (sort @seen))))))
+
+(deftest test-vector-parallel-stream
+ (dotimes [n 1024]
+ (let [v (vec (range n))]
+ (is (= n
+ (-> ^Collection v .stream (.collect (Collectors/counting)))
+ (-> ^Collection v .parallelStream (.collect (Collectors/counting)))
+ (-> v ^Collection (subvec 0 n) .stream (.collect (Collectors/counting)))
+ (-> v ^Collection (subvec 0 n) .parallelStream (.collect (Collectors/counting))))))))
\ No newline at end of file
diff --git a/test/java/clojure/test/AdapterExerciser.java b/test/java/clojure/test/AdapterExerciser.java
new file mode 100644
index 0000000000..5e2758b1d7
--- /dev/null
+++ b/test/java/clojure/test/AdapterExerciser.java
@@ -0,0 +1,499 @@
+
+package clojure.test;
+
+public class AdapterExerciser {
+ @FunctionalInterface
+ public interface L {
+ public long takesRetL();
+ }
+ @FunctionalInterface
+ public interface I {
+ public int takesRetI();
+ }
+ @FunctionalInterface
+ public interface S {
+ public short takesRetS();
+ }
+ @FunctionalInterface
+ public interface B {
+ public byte takesRetB();
+ }
+ @FunctionalInterface
+ public interface D {
+ public double takesRetD();
+ }
+ @FunctionalInterface
+ public interface F {
+ public float takesRetF();
+ }
+ @FunctionalInterface
+ public interface O {
+ public AdapterExerciser takesRetO();
+ }
+ @FunctionalInterface
+ public interface LL {
+ public long takesLRetL(long a);
+ }
+ @FunctionalInterface
+ public interface DL {
+ public long takesDRetL(double a);
+ }
+ @FunctionalInterface
+ public interface OL {
+ public long takesORetL(AdapterExerciser a);
+ }
+ @FunctionalInterface
+ public interface LI {
+ public int takesLRetI(long a);
+ }
+ @FunctionalInterface
+ public interface DI {
+ public int takesDRetI(double a);
+ }
+ @FunctionalInterface
+ public interface OI {
+ public int takesORetI(AdapterExerciser a);
+ }
+ @FunctionalInterface
+ public interface LS {
+ public short takesLRetS(long a);
+ }
+ @FunctionalInterface
+ public interface DS {
+ public short takesDRetS(double a);
+ }
+ @FunctionalInterface
+ public interface OS {
+ public short takesORetS(AdapterExerciser a);
+ }
+ @FunctionalInterface
+ public interface LB {
+ public byte takesLRetB(long a);
+ }
+ @FunctionalInterface
+ public interface DB {
+ public byte takesDRetB(double a);
+ }
+ @FunctionalInterface
+ public interface OB {
+ public byte takesORetB(AdapterExerciser a);
+ }
+ @FunctionalInterface
+ public interface LD {
+ public double takesLRetD(long a);
+ }
+ @FunctionalInterface
+ public interface DD {
+ public double takesDRetD(double a);
+ }
+ @FunctionalInterface
+ public interface OD {
+ public double takesORetD(AdapterExerciser a);
+ }
+ @FunctionalInterface
+ public interface LF {
+ public float takesLRetF(long a);
+ }
+ @FunctionalInterface
+ public interface DF {
+ public float takesDRetF(double a);
+ }
+ @FunctionalInterface
+ public interface OF {
+ public float takesORetF(AdapterExerciser a);
+ }
+ @FunctionalInterface
+ public interface LO {
+ public AdapterExerciser takesLRetO(long a);
+ }
+ @FunctionalInterface
+ public interface DO {
+ public AdapterExerciser takesDRetO(double a);
+ }
+ @FunctionalInterface
+ public interface OO {
+ public AdapterExerciser takesORetO(AdapterExerciser a);
+ }
+ @FunctionalInterface
+ public interface LLL {
+ public long takesLLRetL(long a, long b);
+ }
+ @FunctionalInterface
+ public interface LOL {
+ public long takesLORetL(long a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OLL {
+ public long takesOLRetL(AdapterExerciser a, long b);
+ }
+ @FunctionalInterface
+ public interface DDL {
+ public long takesDDRetL(double a, double b);
+ }
+ @FunctionalInterface
+ public interface LDL {
+ public long takesLDRetL(long a, double b);
+ }
+ @FunctionalInterface
+ public interface DLL {
+ public long takesDLRetL(double a, long b);
+ }
+ @FunctionalInterface
+ public interface OOL {
+ public long takesOORetL(AdapterExerciser a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface ODL {
+ public long takesODRetL(AdapterExerciser a, double b);
+ }
+ @FunctionalInterface
+ public interface DOL {
+ public long takesDORetL(double a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface LLI {
+ public int takesLLRetI(long a, long b);
+ }
+ @FunctionalInterface
+ public interface LOI {
+ public int takesLORetI(long a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OLI {
+ public int takesOLRetI(AdapterExerciser a, long b);
+ }
+ @FunctionalInterface
+ public interface DDI {
+ public int takesDDRetI(double a, double b);
+ }
+ @FunctionalInterface
+ public interface LDI {
+ public int takesLDRetI(long a, double b);
+ }
+ @FunctionalInterface
+ public interface DLI {
+ public int takesDLRetI(double a, long b);
+ }
+ @FunctionalInterface
+ public interface OOI {
+ public int takesOORetI(AdapterExerciser a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface ODI {
+ public int takesODRetI(AdapterExerciser a, double b);
+ }
+ @FunctionalInterface
+ public interface DOI {
+ public int takesDORetI(double a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface LLS {
+ public short takesLLRetS(long a, long b);
+ }
+ @FunctionalInterface
+ public interface LOS {
+ public short takesLORetS(long a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OLS {
+ public short takesOLRetS(AdapterExerciser a, long b);
+ }
+ @FunctionalInterface
+ public interface DDS {
+ public short takesDDRetS(double a, double b);
+ }
+ @FunctionalInterface
+ public interface LDS {
+ public short takesLDRetS(long a, double b);
+ }
+ @FunctionalInterface
+ public interface DLS {
+ public short takesDLRetS(double a, long b);
+ }
+ @FunctionalInterface
+ public interface OOS {
+ public short takesOORetS(AdapterExerciser a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface ODS {
+ public short takesODRetS(AdapterExerciser a, double b);
+ }
+ @FunctionalInterface
+ public interface DOS {
+ public short takesDORetS(double a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface LLB {
+ public byte takesLLRetB(long a, long b);
+ }
+ @FunctionalInterface
+ public interface LOB {
+ public byte takesLORetB(long a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OLB {
+ public byte takesOLRetB(AdapterExerciser a, long b);
+ }
+ @FunctionalInterface
+ public interface DDB {
+ public byte takesDDRetB(double a, double b);
+ }
+ @FunctionalInterface
+ public interface LDB {
+ public byte takesLDRetB(long a, double b);
+ }
+ @FunctionalInterface
+ public interface DLB {
+ public byte takesDLRetB(double a, long b);
+ }
+ @FunctionalInterface
+ public interface OOB {
+ public byte takesOORetB(AdapterExerciser a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface ODB {
+ public byte takesODRetB(AdapterExerciser a, double b);
+ }
+ @FunctionalInterface
+ public interface DOB {
+ public byte takesDORetB(double a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface LLD {
+ public double takesLLRetD(long a, long b);
+ }
+ @FunctionalInterface
+ public interface LOD {
+ public double takesLORetD(long a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OLD {
+ public double takesOLRetD(AdapterExerciser a, long b);
+ }
+ @FunctionalInterface
+ public interface DDD {
+ public double takesDDRetD(double a, double b);
+ }
+ @FunctionalInterface
+ public interface LDD {
+ public double takesLDRetD(long a, double b);
+ }
+ @FunctionalInterface
+ public interface DLD {
+ public double takesDLRetD(double a, long b);
+ }
+ @FunctionalInterface
+ public interface OOD {
+ public double takesOORetD(AdapterExerciser a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface ODD {
+ public double takesODRetD(AdapterExerciser a, double b);
+ }
+ @FunctionalInterface
+ public interface DOD {
+ public double takesDORetD(double a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface LLF {
+ public float takesLLRetF(long a, long b);
+ }
+ @FunctionalInterface
+ public interface LOF {
+ public float takesLORetF(long a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OLF {
+ public float takesOLRetF(AdapterExerciser a, long b);
+ }
+ @FunctionalInterface
+ public interface DDF {
+ public float takesDDRetF(double a, double b);
+ }
+ @FunctionalInterface
+ public interface LDF {
+ public float takesLDRetF(long a, double b);
+ }
+ @FunctionalInterface
+ public interface DLF {
+ public float takesDLRetF(double a, long b);
+ }
+ @FunctionalInterface
+ public interface OOF {
+ public float takesOORetF(AdapterExerciser a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface ODF {
+ public float takesODRetF(AdapterExerciser a, double b);
+ }
+ @FunctionalInterface
+ public interface DOF {
+ public float takesDORetF(double a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface LLO {
+ public AdapterExerciser takesLLRetO(long a, long b);
+ }
+ @FunctionalInterface
+ public interface LOO {
+ public AdapterExerciser takesLORetO(long a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OLO {
+ public AdapterExerciser takesOLRetO(AdapterExerciser a, long b);
+ }
+ @FunctionalInterface
+ public interface DDO {
+ public AdapterExerciser takesDDRetO(double a, double b);
+ }
+ @FunctionalInterface
+ public interface LDO {
+ public AdapterExerciser takesLDRetO(long a, double b);
+ }
+ @FunctionalInterface
+ public interface DLO {
+ public AdapterExerciser takesDLRetO(double a, long b);
+ }
+ @FunctionalInterface
+ public interface OOO {
+ public AdapterExerciser takesOORetO(AdapterExerciser a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface ODO {
+ public AdapterExerciser takesODRetO(AdapterExerciser a, double b);
+ }
+ @FunctionalInterface
+ public interface DOO {
+ public AdapterExerciser takesDORetO(double a, AdapterExerciser b);
+ }
+ @FunctionalInterface
+ public interface OOOO {
+ public AdapterExerciser takesOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c);
+ }
+ @FunctionalInterface
+ public interface OOOOO {
+ public AdapterExerciser takesOOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c, AdapterExerciser d);
+ }
+ @FunctionalInterface
+ public interface OOOOOO {
+ public AdapterExerciser takesOOOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c, AdapterExerciser d, AdapterExerciser e);
+ }
+ @FunctionalInterface
+ public interface OOOOOOO {
+ public AdapterExerciser takesOOOOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c, AdapterExerciser d, AdapterExerciser e, AdapterExerciser f);
+ }
+ @FunctionalInterface
+ public interface OOOOOOOO {
+ public AdapterExerciser takesOOOOOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c, AdapterExerciser d, AdapterExerciser e, AdapterExerciser f, AdapterExerciser g);
+ }
+ @FunctionalInterface
+ public interface OOOOOOOOO {
+ public AdapterExerciser takesOOOOOOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c, AdapterExerciser d, AdapterExerciser e, AdapterExerciser f, AdapterExerciser g, AdapterExerciser h);
+ }
+ @FunctionalInterface
+ public interface OOOOOOOOOO {
+ public AdapterExerciser takesOOOOOOOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c, AdapterExerciser d, AdapterExerciser e, AdapterExerciser f, AdapterExerciser g, AdapterExerciser h, AdapterExerciser i);
+ }
+ @FunctionalInterface
+ public interface OOOOOOOOOOO {
+ public AdapterExerciser takesOOOOOOOOOORetO(AdapterExerciser a, AdapterExerciser b, AdapterExerciser c, AdapterExerciser d, AdapterExerciser e, AdapterExerciser f, AdapterExerciser g, AdapterExerciser h, AdapterExerciser i, AdapterExerciser j);
+ }
+ public String methodL(L a) { return "L"; }
+ public String methodI(I a) { return "I"; }
+ public String methodS(S a) { return "S"; }
+ public String methodB(B a) { return "B"; }
+ public String methodD(D a) { return "D"; }
+ public String methodF(F a) { return "F"; }
+ public String methodO(O a) { return "O"; }
+ public String methodLL(LL a) { return "LL"; }
+ public String methodDL(DL a) { return "DL"; }
+ public String methodOL(OL a) { return "OL"; }
+ public String methodLI(LI a) { return "LI"; }
+ public String methodDI(DI a) { return "DI"; }
+ public String methodOI(OI a) { return "OI"; }
+ public String methodLS(LS a) { return "LS"; }
+ public String methodDS(DS a) { return "DS"; }
+ public String methodOS(OS a) { return "OS"; }
+ public String methodLB(LB a) { return "LB"; }
+ public String methodDB(DB a) { return "DB"; }
+ public String methodOB(OB a) { return "OB"; }
+ public String methodLD(LD a) { return "LD"; }
+ public String methodDD(DD a) { return "DD"; }
+ public String methodOD(OD a) { return "OD"; }
+ public String methodLF(LF a) { return "LF"; }
+ public String methodDF(DF a) { return "DF"; }
+ public String methodOF(OF a) { return "OF"; }
+ public String methodLO(LO a) { return "LO"; }
+ public String methodDO(DO a) { return "DO"; }
+ public String methodOO(OO a) { return "OO"; }
+ public String methodLLL(LLL a) { return "LLL"; }
+ public String methodLOL(LOL a) { return "LOL"; }
+ public String methodOLL(OLL a) { return "OLL"; }
+ public String methodDDL(DDL a) { return "DDL"; }
+ public String methodLDL(LDL a) { return "LDL"; }
+ public String methodDLL(DLL a) { return "DLL"; }
+ public String methodOOL(OOL a) { return "OOL"; }
+ public String methodODL(ODL a) { return "ODL"; }
+ public String methodDOL(DOL a) { return "DOL"; }
+ public String methodLLI(LLI a) { return "LLI"; }
+ public String methodLOI(LOI a) { return "LOI"; }
+ public String methodOLI(OLI a) { return "OLI"; }
+ public String methodDDI(DDI a) { return "DDI"; }
+ public String methodLDI(LDI a) { return "LDI"; }
+ public String methodDLI(DLI a) { return "DLI"; }
+ public String methodOOI(OOI a) { return "OOI"; }
+ public String methodODI(ODI a) { return "ODI"; }
+ public String methodDOI(DOI a) { return "DOI"; }
+ public String methodLLS(LLS a) { return "LLS"; }
+ public String methodLOS(LOS a) { return "LOS"; }
+ public String methodOLS(OLS a) { return "OLS"; }
+ public String methodDDS(DDS a) { return "DDS"; }
+ public String methodLDS(LDS a) { return "LDS"; }
+ public String methodDLS(DLS a) { return "DLS"; }
+ public String methodOOS(OOS a) { return "OOS"; }
+ public String methodODS(ODS a) { return "ODS"; }
+ public String methodDOS(DOS a) { return "DOS"; }
+ public String methodLLB(LLB a) { return "LLB"; }
+ public String methodLOB(LOB a) { return "LOB"; }
+ public String methodOLB(OLB a) { return "OLB"; }
+ public String methodDDB(DDB a) { return "DDB"; }
+ public String methodLDB(LDB a) { return "LDB"; }
+ public String methodDLB(DLB a) { return "DLB"; }
+ public String methodOOB(OOB a) { return "OOB"; }
+ public String methodODB(ODB a) { return "ODB"; }
+ public String methodDOB(DOB a) { return "DOB"; }
+ public String methodLLD(LLD a) { return "LLD"; }
+ public String methodLOD(LOD a) { return "LOD"; }
+ public String methodOLD(OLD a) { return "OLD"; }
+ public String methodDDD(DDD a) { return "DDD"; }
+ public String methodLDD(LDD a) { return "LDD"; }
+ public String methodDLD(DLD a) { return "DLD"; }
+ public String methodOOD(OOD a) { return "OOD"; }
+ public String methodODD(ODD a) { return "ODD"; }
+ public String methodDOD(DOD a) { return "DOD"; }
+ public String methodLLF(LLF a) { return "LLF"; }
+ public String methodLOF(LOF a) { return "LOF"; }
+ public String methodOLF(OLF a) { return "OLF"; }
+ public String methodDDF(DDF a) { return "DDF"; }
+ public String methodLDF(LDF a) { return "LDF"; }
+ public String methodDLF(DLF a) { return "DLF"; }
+ public String methodOOF(OOF a) { return "OOF"; }
+ public String methodODF(ODF a) { return "ODF"; }
+ public String methodDOF(DOF a) { return "DOF"; }
+ public String methodLLO(LLO a) { return "LLO"; }
+ public String methodLOO(LOO a) { return "LOO"; }
+ public String methodOLO(OLO a) { return "OLO"; }
+ public String methodDDO(DDO a) { return "DDO"; }
+ public String methodLDO(LDO a) { return "LDO"; }
+ public String methodDLO(DLO a) { return "DLO"; }
+ public String methodOOO(OOO a) { return "OOO"; }
+ public String methodODO(ODO a) { return "ODO"; }
+ public String methodDOO(DOO a) { return "DOO"; }
+ public String methodOOOO(OOOO a) { return "OOOO"; }
+ public String methodOOOOO(OOOOO a) { return "OOOOO"; }
+ public String methodOOOOOO(OOOOOO a) { return "OOOOOO"; }
+ public String methodOOOOOOO(OOOOOOO a) { return "OOOOOOO"; }
+ public String methodOOOOOOOO(OOOOOOOO a) { return "OOOOOOOO"; }
+ public String methodOOOOOOOOO(OOOOOOOOO a) { return "OOOOOOOOO"; }
+ public String methodOOOOOOOOOO(OOOOOOOOOO a) { return "OOOOOOOOOO"; }
+ public String methodOOOOOOOOOOO(OOOOOOOOOOO a) { return "OOOOOOOOOOO"; }}
\ No newline at end of file
diff --git a/test/java/clojure/test/ConcreteClass.java b/test/java/clojure/test/ConcreteClass.java
new file mode 100644
index 0000000000..69dde53538
--- /dev/null
+++ b/test/java/clojure/test/ConcreteClass.java
@@ -0,0 +1,8 @@
+package clojure.test;
+
+public class ConcreteClass implements GenericInterface {
+ @Override
+ public Integer stampWidgets(Integer val) {
+ return 42;
+ }
+}
\ No newline at end of file
diff --git a/test/java/clojure/test/FIConstructor.java b/test/java/clojure/test/FIConstructor.java
new file mode 100644
index 0000000000..42a4e6caa3
--- /dev/null
+++ b/test/java/clojure/test/FIConstructor.java
@@ -0,0 +1,17 @@
+package clojure.test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+public class FIConstructor {
+
+ public List