├── .classpath
├── .gitignore
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── .travis.yml
├── LICENSE
├── MANIFEST.MF
├── README.md
├── build.pro
├── pro_wrapper.java
├── pro_wrapper_settings.txt
└── src
├── main
└── java
│ ├── fr.umlv.mjolnir.agent
│ ├── fr
│ │ └── umlv
│ │ │ └── mjolnir
│ │ │ └── agent
│ │ │ ├── Agent.java
│ │ │ ├── AgentFacadeImpl.java
│ │ │ └── Main.java
│ └── module-info.java
│ ├── fr.umlv.mjolnir.amber
│ ├── fr
│ │ └── umlv
│ │ │ └── mjolnir
│ │ │ └── amber
│ │ │ ├── Deconstruct.java
│ │ │ ├── Pattern.java
│ │ │ ├── PatternMatchingMetaFactory.java
│ │ │ ├── TupleGenerator.java
│ │ │ └── TupleHandle.java
│ └── module-info.java
│ └── fr.umlv.mjolnir
│ ├── fr
│ └── umlv
│ │ └── mjolnir
│ │ ├── AgentFacade.java
│ │ ├── Mjolnir.java
│ │ ├── OverrideEntryPoint.java
│ │ ├── bytecode
│ │ ├── AnnotationOracle.java
│ │ └── Rewriter.java
│ │ ├── log
│ │ ├── Log.java
│ │ └── OldLog.java
│ │ └── util
│ │ └── stream
│ │ ├── LoopBuilder.java
│ │ └── Stream.java
│ └── module-info.java
└── test
└── java
├── fr.umlv.mjolnir.amber
├── fr
│ └── umlv
│ │ └── mjolnir
│ │ └── amber
│ │ ├── PatternMatchingTests.java
│ │ └── TupleHandleTests.java
└── module-info.java
└── fr.umlv.mjolnir
├── fr
└── umlv
│ └── mjolnir
│ ├── LogTests.java
│ └── MjolnirTests.java
└── module-info.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.ear
17 | *.zip
18 | *.tar.gz
19 | *.rar
20 |
21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
22 | hs_err_pid*
23 | /target/
24 | /pro/
25 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | mjolnir
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=13
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=13
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
12 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
13 | org.eclipse.jdt.core.compiler.release=enabled
14 | org.eclipse.jdt.core.compiler.source=13
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk: openjdk11
3 |
4 | install:
5 | - java pro_wrapper.java version
6 |
7 | script:
8 | - ./pro/bin/pro
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Rémi Forax
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Can-Retransform-Classes: true
2 | Premain-Class: fr.umlv.mjolnir.agent.Agent
3 | Launcher-Agent-Class: fr.umlv.mjolnir.agent.Agent
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mjolnir
2 | Thor Hammer and secondarily a way to express invokedynamic in Java
3 |
4 | [](https://travis-ci.org/forax/mjolnir)
5 |
6 | ## Goal
7 | Mjolnir is a Java class allowing to initialize a stable value by calling a bootstrap method once.
8 |
9 | The implementation is optimized so the stable value is very cheap to get.
10 | A bytecode rewriter is provided to replace the access to the stable value by an invokedynamic making the call even cheaper (mostly free).
11 |
12 | Mjolnir has the following properties:
13 | - Mjolnir.get().invokeExact() is semantically equivalent to an invokedynamic
14 | - initialize the constant with a bootstrap method
15 | no bootstrap call in the fast path
16 | - no boxing of arguments
17 | - no static analysis requires for the bytecode rewriter
18 | - crawling the bytecode is enough
19 | - should work without the bytecode rewriter (for testing)
20 |
21 | ## Video
22 |
23 | [](https://www.youtube.com/embed/Rco7hcOM7Ig?list=PLX8CzqL3ArzXJ2EGftrmz4SzS6NRr6p2n "Me presenting Mjolnir at JVM Summit 2017")
24 |
25 | ## How to build it
26 |
27 | Mjolnir is built using [pro](https://github.com/forax/pro) which is my own build tool, you can download it from github (amazon S3) like this
28 | ```
29 | sh get_pro.sh
30 | ```
31 | (if you are not on linux, you have to build pro by yourself, sorry)
32 |
33 | and build Mjolnir just by running pro.
34 | ```
35 | pro/bin/pro
36 | ```
37 |
38 | ## Examples
39 |
40 | The following example implements the equivalent of the macro__LINE__ i.e. it returns the current line number like in C
41 | ```java
42 | private static int boostrap(Lookup lookup) {
43 | String className = lookup.lookupClass().getName();
44 | int lineNumber = StackWalker.getInstance()
45 | .walk(s -> s.skip(1).filter(f -> f.getClassName().equals(className)).findFirst())
46 | .get()
47 | .getLineNumber();
48 | return lineNumber;
49 | }
50 |
51 | public static void main(String[] args) {
52 | int __LINE__ = Mjolnir.get(lookup -> boostrap(lookup));
53 | }
54 | ```
55 |
56 | This mechanism can be used to express an invokedynamic in Java, the bootstrap method can return a MethodHandle
57 | that will be called with invokeExact.
58 |
59 | ```java
60 | private static String hello(String name) {
61 | return "Hello " + name;
62 | }
63 |
64 | private static MethodHandle initHello(Lookup lookup) throws NoSuchMethodException, IllegalAccessException {
65 | Class> declaringClass = lookup.lookupClass();
66 | return lookup.findStatic(declaringClass, "hello", methodType(String.class, String.class));
67 | }
68 |
69 | public static void main(String[] args) hello() throws Throwable {
70 | String result = (String)Mjolnir.get(lookup -> initHello(lookup)).invokeExact("Mjolnir");
71 | System.out.println(result); // Hello Mjolnir
72 | }
73 | ```
74 |
--------------------------------------------------------------------------------
/build.pro:
--------------------------------------------------------------------------------
1 | import static com.github.forax.pro.Pro.*
2 | import static com.github.forax.pro.builder.Builders.*
3 |
4 | pro.loglevel("verbose")
5 | pro.exitOnError(true)
6 |
7 | resolver.
8 | checkForUpdate(true).
9 | dependencies(
10 | // ASM 9
11 | "org.objectweb.asm=org.ow2.asm:asm:9.0",
12 | "org.objectweb.asm.util=org.ow2.asm:asm-util:9.0",
13 | "org.objectweb.asm.tree=org.ow2.asm:asm-tree:9.0",
14 | "org.objectweb.asm.tree.analysis=org.ow2.asm:asm-analysis:9.0",
15 |
16 | // JUnit 5
17 | "org.junit.jupiter.api=org.junit.jupiter:junit-jupiter-api:5.7.0",
18 | "org.junit.platform.commons=org.junit.platform:junit-platform-commons:1.7.0",
19 | "org.apiguardian.api=org.apiguardian:apiguardian-api:1.1.0",
20 | "org.opentest4j=org.opentest4j:opentest4j:1.2.0"
21 | )
22 |
23 | compiler.lint("all,-varargs,-overloads")
24 |
25 | packager.moduleMetadata(
26 | "fr.umlv.mjolnir@1.0/fr.umlv.mjolnir.bytecode.Rewriter",
27 | "fr.umlv.mjolnir.agent@1.0/fr.umlv.mjolnir.agent.Main",
28 | "fr.umlv.mjolnir.amber@1.0/fr.umlv.mjolnir.amber.Main"
29 | )
30 | packager.rawArguments(
31 | "--manifest=MANIFEST.MF"
32 | )
33 |
34 | // the runner will rewrite the bytecode when called
35 | runner.module("fr.umlv.mjolnir")
36 |
37 | tester.timeout(99)
38 |
39 | // run the test once without bytecode modification and again after bytecode rewriting
40 | run(resolver, modulefixer, compiler, packager, tester, runner, tester)
41 |
42 |
43 | // test agent
44 | runner.module("fr.umlv.mjolnir.agent")
45 | runner.rawArguments(
46 | "-javaagent:target/main/artifact/fr.umlv.mjolnir.agent-1.0.jar"
47 | )
48 | run(runner)
49 |
50 | /exit
--------------------------------------------------------------------------------
/pro_wrapper.java:
--------------------------------------------------------------------------------
1 | import static java.lang.System.exit;
2 | import static java.lang.System.getProperty;
3 | import static java.nio.file.Files.createDirectories;
4 | import static java.nio.file.Files.delete;
5 | import static java.nio.file.Files.exists;
6 | import static java.nio.file.Files.getLastModifiedTime;
7 | import static java.nio.file.Files.getPosixFilePermissions;
8 | import static java.nio.file.Files.list;
9 | import static java.nio.file.Files.newBufferedReader;
10 | import static java.nio.file.Files.newOutputStream;
11 | import static java.nio.file.Files.readAllLines;
12 | import static java.nio.file.Files.setPosixFilePermissions;
13 | import static java.nio.file.Files.walk;
14 | import static java.nio.file.Files.walkFileTree;
15 | import static java.nio.file.Files.write;
16 | import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE;
17 | import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE;
18 | import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
19 | import static java.util.Collections.reverseOrder;
20 | import static java.util.Comparator.comparing;
21 | import static java.util.function.Predicate.not;
22 |
23 | import java.io.BufferedReader;
24 | import java.io.IOException;
25 | import java.io.InputStreamReader;
26 | import java.io.UncheckedIOException;
27 | import java.net.InetAddress;
28 | import java.net.URL;
29 | import java.net.UnknownHostException;
30 | import java.nio.charset.StandardCharsets;
31 | import java.nio.file.FileVisitResult;
32 | import java.nio.file.Files;
33 | import java.nio.file.Path;
34 | import java.nio.file.SimpleFileVisitor;
35 | import java.nio.file.attribute.BasicFileAttributes;
36 | import java.nio.file.attribute.FileTime;
37 | import java.util.Arrays;
38 | import java.util.Collections;
39 | import java.util.List;
40 | import java.util.Optional;
41 | import java.util.Properties;
42 | import java.util.regex.Matcher;
43 | import java.util.regex.Pattern;
44 | import java.util.stream.Stream;
45 | import java.util.zip.ZipFile;
46 |
47 | class pro_wrapper {
48 | private static final String GITHUB_API_RELEASES = "https://github.com/forax/pro/releases";
49 | private static final String GITHUB_DOWNLOAD = "https://github.com/forax/pro/releases/download";
50 | private static final Pattern PATTERN = Pattern.compile("tag/([^\"]+)\"");
51 |
52 | private static String platform() {
53 | var osName = getProperty("os.name").toLowerCase();
54 | if (osName.indexOf("win") != -1) {
55 | return "windows";
56 | }
57 | if (osName.indexOf("mac") != -1) {
58 | return "macos";
59 | }
60 | return "linux";
61 | }
62 |
63 | private static String shell() {
64 | var osName = getProperty("os.name").toLowerCase();
65 | if (osName.indexOf("win") != -1) {
66 | return "cmd.exe";
67 | }
68 | return Optional.ofNullable(System.getenv("SHELL")).filter(not(String::isEmpty)).orElse("/bin/sh");
69 | }
70 |
71 | private static Optional specialBuild() throws IOException {
72 | var specialBuild = System.getenv("PRO_SPECIAL_BUILD");
73 | var path = Path.of("pro_wrapper_settings.txt");
74 | var proSettings = new Properties();
75 | if (exists(path)) {
76 | try(var reader = newBufferedReader(path)) {
77 | proSettings.load(reader);
78 | }
79 | }
80 | return Optional.ofNullable(specialBuild).filter(not(String::isEmpty)).or(
81 | () -> Optional.ofNullable(proSettings.getProperty("PRO_SPECIAL_BUILD")));
82 | }
83 |
84 | private static String userHome() {
85 | return System.getProperty("user.home");
86 | }
87 |
88 | private static Optional lastestReleaseVersionFromGitHub() throws IOException {
89 | System.out.println("try to find latest release on Github ...");
90 | var url = new URL(GITHUB_API_RELEASES);
91 | try {
92 | InetAddress.getByName(url.getHost()); // is internet accessible ?
93 | } catch(@SuppressWarnings("unused") UnknownHostException e) {
94 | return Optional.empty();
95 | }
96 | try(var input = url.openStream();
97 | var reader = new InputStreamReader(input, StandardCharsets.UTF_8);
98 | var buffered = new BufferedReader(reader, 8192)) {
99 | return buffered.lines()
100 | .flatMap(line -> Optional.of(PATTERN.matcher(line)).filter(Matcher::find).map(matcher -> matcher.group(1)).stream())
101 | .findFirst();
102 | }
103 | }
104 |
105 | private static FileTime lastModified(Path path) {
106 | try {
107 | return getLastModifiedTime(path);
108 | } catch (IOException e) {
109 | throw new UncheckedIOException(e);
110 | }
111 | }
112 |
113 | private static Optional latestReleaseVersionFromCache(Path cache, String filename) {
114 | System.out.println("try to find latest release in the cache ...");
115 | try {
116 | return walk(cache)
117 | .filter(p -> p.getFileName().toString().equals(filename))
118 | .sorted(reverseOrder(comparing(p -> lastModified(p))))
119 | .findFirst().map(p -> p.getParent().getFileName().toString());
120 | } catch (@SuppressWarnings("unused") IOException | UncheckedIOException e) {
121 | return Optional.empty();
122 | }
123 | }
124 |
125 | private static void download(String release, String filename, Path path) throws IOException {
126 | var url = new URL(GITHUB_DOWNLOAD + "/"+ release + "/" + filename);
127 | System.out.println("download " + url + " to " + path);
128 |
129 | createDirectories(path.getParent());
130 | try(var input = url.openStream();
131 | var output = newOutputStream(path)) {
132 | int read;
133 | var sum = 0;
134 | var buffer = new byte[8192];
135 | while((read = input.read(buffer)) != -1) {
136 | output.write(buffer, 0, read);
137 |
138 | sum += read;
139 | if (sum >= 1_000_000) {
140 | System.out.print(".");
141 | sum = 0;
142 | }
143 | }
144 | }
145 | System.out.println("");
146 | }
147 |
148 | private static void setExecutionPermissions(Path path) throws IOException {
149 | var permissions = getPosixFilePermissions(path);
150 | Collections.addAll(permissions, OWNER_EXECUTE, GROUP_EXECUTE, OTHERS_EXECUTE);
151 | setPosixFilePermissions(path, permissions);
152 | }
153 |
154 | private static void unpack(Path localPath, Path folder) throws IOException {
155 | System.out.println("unpack pro to " + folder);
156 | createDirectories(folder);
157 | try(var zip = new ZipFile(localPath.toFile())) {
158 | for(var entry: Collections.list(zip.entries())) {
159 | var path = folder.resolve(entry.getName());
160 | if (entry.isDirectory()) {
161 | createDirectories(path);
162 | continue;
163 | }
164 | try(var input = zip.getInputStream(entry);
165 | var output = newOutputStream(path)) {
166 | input.transferTo(output);
167 | }
168 | }
169 | }
170 |
171 | // make the commands in pro/bin, and pro/lib executable
172 | for(var cmd: (Iterable)list(folder.resolve("pro").resolve("bin"))::iterator) {
173 | setExecutionPermissions(cmd);
174 | }
175 | for(var path: (Iterable)list(folder.resolve("pro").resolve("lib"))::iterator) {
176 | var fileName = path.getFileName().toString();
177 | if (fileName.equals("jexec") || fileName.equals("jspawnhelper")) {
178 | setExecutionPermissions(path);
179 | }
180 | }
181 | }
182 |
183 | private static void deleteAllFiles(Path directory) throws IOException {
184 | if (!exists(directory)) {
185 | return;
186 | }
187 |
188 | walkFileTree(directory, new SimpleFileVisitor<>() {
189 | @Override
190 | public FileVisitResult postVisitDirectory(Path path, IOException e) throws IOException {
191 | delete(path);
192 | return super.postVisitDirectory(path, e);
193 | }
194 | @Override
195 | public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
196 | delete(path);
197 | return FileVisitResult.CONTINUE;
198 | }
199 | });
200 | }
201 |
202 | private static String firstLine(Path path) throws IOException {
203 | return Optional.of(readAllLines(path)).filter(lines -> !lines.isEmpty()).map(lines -> lines.get(0)).orElse("");
204 | }
205 |
206 | private static int exec(String command, String[] args) throws IOException {
207 | var builder = new ProcessBuilder(Stream.of(Stream.of(shell()), Stream.of(command), Arrays.stream(args)).flatMap(x -> x).toArray(String[]::new));
208 | var process = builder.inheritIO().start();
209 | try {
210 | return process.waitFor();
211 | } catch (InterruptedException e) {
212 | throw new IOException(e);
213 | }
214 | }
215 |
216 | public interface PathConsumer {
217 | void accept(Path path) throws IOException;
218 | }
219 |
220 | private static void retry(Path resource, int times, PathConsumer consumer) throws IOException {
221 | if (times <= 0) {
222 | throw new IllegalArgumentException("times <= 0");
223 | }
224 | IOException exception = null;
225 | var count = times;
226 | do {
227 | try {
228 | consumer.accept(resource);
229 | return;
230 | } catch(IOException e) {
231 | if (exception == null) {
232 | exception = new IOException();
233 | }
234 | exception.addSuppressed(e);
235 |
236 | // cleanup
237 | Files.deleteIfExists(resource);
238 |
239 | System.out.println("download fails ... retry !");
240 | }
241 | } while(--count != 0);
242 | throw exception;
243 | }
244 |
245 | private static int installAndRun(String[] args) throws IOException {
246 | var specialBuild = specialBuild().map(build -> '-' + build).orElse("");
247 | var filename = "pro-" + platform() + specialBuild + ".zip";
248 |
249 | var cache = Path.of(userHome(), ".pro", "cache");
250 | var release = lastestReleaseVersionFromGitHub()
251 | .or(() -> latestReleaseVersionFromCache(cache, filename))
252 | .orElseThrow(() -> new IOException("no release found !"));
253 |
254 |
255 | System.out.println("require " + filename + " release " + release + " ...");
256 |
257 | var cachePath = cache.resolve(Path.of(release, filename));
258 | if (!exists(cachePath)) {
259 | retry(cachePath, 3, _cachedPath -> download(release, filename, _cachedPath));
260 | }
261 |
262 | var releaseTxt = Path.of("pro", "pro-release.txt");
263 | if (!exists(releaseTxt) || !firstLine(releaseTxt).equals(filename)) {
264 | deleteAllFiles(releaseTxt.getParent());
265 |
266 | unpack(cachePath, Path.of("."));
267 | write(releaseTxt, List.of(filename));
268 | }
269 |
270 | return exec("pro/bin/pro", args);
271 | }
272 |
273 | public static void main(String[] args) {
274 | try {
275 | exit(installAndRun(args));
276 | } catch(IOException e) {
277 | System.err.println("i/o error " + e.getMessage() +
278 | Optional.ofNullable(e.getStackTrace()).filter(stack -> stack.length > 0).map(stack -> " at " + stack[0]).orElse(""));
279 | exit(1);
280 | }
281 | }
282 | }
--------------------------------------------------------------------------------
/pro_wrapper_settings.txt:
--------------------------------------------------------------------------------
1 | PRO_SPECIAL_BUILD=early-access
2 |
--------------------------------------------------------------------------------
/src/main/java/fr.umlv.mjolnir.agent/fr/umlv/mjolnir/agent/Agent.java:
--------------------------------------------------------------------------------
1 | package fr.umlv.mjolnir.agent;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.lang.instrument.ClassFileTransformer;
7 | import java.lang.instrument.IllegalClassFormatException;
8 | import java.lang.instrument.Instrumentation;
9 | import java.security.ProtectionDomain;
10 | import java.util.Optional;
11 | import java.util.function.Function;
12 |
13 | import fr.umlv.mjolnir.bytecode.Rewriter;
14 |
15 | //import fr.umlv.mjolnir.bytecode.Rewriter;
16 |
17 | class Agent {
18 | static Instrumentation instrumentation;
19 |
20 | public static void premain(String agentArgs, Instrumentation instrumentation) {
21 |
22 | //public static void agentmain(String agentArgs, Instrumentation instrumentation) {
23 | //System.out.println("agent started");
24 |
25 |
26 | Agent.instrumentation = instrumentation;
27 |
28 | instrumentation.addTransformer(new ClassFileTransformer() {
29 | @Override
30 | public byte[] transform(Module module, ClassLoader loader, String className, Class> classBeingRedefined,
31 | ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
32 |
33 | //System.out.println("transform " + className + " " + classBeingRedefined);
34 |
35 | if (classBeingRedefined == null) { // do not rewrite too early
36 | return null;
37 | }
38 |
39 | Function> classFileFinder = internalName -> Optional.ofNullable(loader.getResourceAsStream(internalName + ".class"));
40 | try {
41 | return Rewriter.rewrite(new ByteArrayInputStream(classfileBuffer), classFileFinder);
42 | } catch (IOException e) {
43 | throw (IllegalClassFormatException)new IllegalClassFormatException().initCause(e);
44 | }
45 | }
46 | }, true);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/fr.umlv.mjolnir.agent/fr/umlv/mjolnir/agent/AgentFacadeImpl.java:
--------------------------------------------------------------------------------
1 | package fr.umlv.mjolnir.agent;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.lang.instrument.ClassDefinition;
6 | import java.lang.instrument.Instrumentation;
7 | import java.lang.instrument.UnmodifiableClassException;
8 | import java.util.Map;
9 | import java.util.Optional;
10 | import java.util.Set;
11 | import java.util.function.Function;
12 |
13 | import fr.umlv.mjolnir.AgentFacade;
14 | import fr.umlv.mjolnir.bytecode.Rewriter;
15 |
16 | public class AgentFacadeImpl implements AgentFacade {
17 | private Instrumentation checkInstrumentation() {
18 | Instrumentation instrumentation = Agent.instrumentation;
19 | if (instrumentation == null) {
20 | throw new IllegalStateException("no instrumentation");
21 | }
22 | return instrumentation;
23 | }
24 |
25 | @Override
26 | public void rewriteIfPossible(Class> declaringClass) throws IllegalStateException {
27 | Instrumentation instrumentation = checkInstrumentation();
28 |
29 | /*
30 | byte[] bytecode;
31 | ClassLoader loader = declaringClass.getClassLoader();
32 | try(InputStream input = loader.getResourceAsStream(declaringClass.getName().replace('.', '/') + ".class")) {
33 | if (input == null) {
34 | throw new IllegalStateException("no input");
35 | }
36 |
37 | Function> classFileFinder = internalName -> Optional.ofNullable(loader.getResourceAsStream(internalName + ".class"));
38 | bytecode = Rewriter.rewrite(input, classFileFinder);
39 | } catch (IOException e) {
40 | throw new IllegalStateException(e);
41 | }
42 |
43 | try {
44 | instrumentation.redefineClasses(new ClassDefinition(declaringClass, bytecode));
45 | } catch (ClassNotFoundException | UnmodifiableClassException e) {
46 | throw new IllegalStateException(e);
47 | }*/
48 |
49 | try {
50 | instrumentation.retransformClasses(declaringClass);
51 | } catch (UnmodifiableClassException e) {
52 | throw new IllegalStateException(e);
53 | }
54 | }
55 |
56 | @Override
57 | public void addReads(Module source, Module destination) throws IllegalStateException {
58 | Instrumentation instrumentation = checkInstrumentation();
59 | instrumentation.redefineModule(source, Set.of(destination), Map.of(), Map.of(), Set.of(), Map.of());
60 | }
61 |
62 | @Override
63 | public void addOpens(Module source, String packaze, Module destination) {
64 | Instrumentation instrumentation = checkInstrumentation();
65 | instrumentation.redefineModule(source, Set.of(), Map.of(), Map.of(packaze, Set.of(destination)), Set.of(), Map.of());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/fr.umlv.mjolnir.agent/fr/umlv/mjolnir/agent/Main.java:
--------------------------------------------------------------------------------
1 | package fr.umlv.mjolnir.agent;
2 |
3 | import java.util.function.IntUnaryOperator;
4 |
5 | import fr.umlv.mjolnir.Mjolnir;
6 | import fr.umlv.mjolnir.Mjolnir.Bootstrap;
7 |
8 | public class Main {
9 | private static int incr(int i) {
10 | return Mjolnir.get((Bootstrap)__ -> v -> v + 1).applyAsInt(i);
11 | }
12 |
13 | private static void loop() {
14 | int i = 0;
15 | while (i < 10_000_000) {
16 | i = incr(i);
17 | }
18 | }
19 |
20 | public static void main(String[] args) {
21 | String message = Mjolnir.get(lookup -> "Hello Mjolnir");
22 | System.out.println(message);
23 |
24 | loop();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/fr.umlv.mjolnir.agent/module-info.java:
--------------------------------------------------------------------------------
1 | open module fr.umlv.mjolnir.agent {
2 | //requires jdk.attach;
3 | requires java.instrument;
4 | requires transitive fr.umlv.mjolnir;
5 |
6 | exports fr.umlv.mjolnir.agent;
7 |
8 | provides fr.umlv.mjolnir.AgentFacade with fr.umlv.mjolnir.agent.AgentFacadeImpl;
9 | }
--------------------------------------------------------------------------------
/src/main/java/fr.umlv.mjolnir.amber/fr/umlv/mjolnir/amber/Deconstruct.java:
--------------------------------------------------------------------------------
1 | package fr.umlv.mjolnir.amber;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface Deconstruct {
11 | Class>[] value();
12 | }
--------------------------------------------------------------------------------
/src/main/java/fr.umlv.mjolnir.amber/fr/umlv/mjolnir/amber/Pattern.java:
--------------------------------------------------------------------------------
1 | package fr.umlv.mjolnir.amber;
2 |
3 | import static java.lang.invoke.MethodHandles.constant;
4 | import static java.lang.invoke.MethodHandles.dropArguments;
5 | import static java.lang.invoke.MethodHandles.guardWithTest;
6 | import static java.lang.invoke.MethodHandles.identity;
7 | import static java.lang.invoke.MethodHandles.insertArguments;
8 | import static java.lang.invoke.MethodType.methodType;
9 |
10 | import java.lang.invoke.MethodHandle;
11 | import java.lang.invoke.MethodHandles;
12 | import java.lang.invoke.MethodHandles.Lookup;
13 | import java.lang.invoke.MethodType;
14 | import java.lang.reflect.Method;
15 | import java.util.Arrays;
16 | import java.util.function.Function;
17 |
18 | import fr.umlv.mjolnir.amber.TupleHandle.Form;
19 |
20 | public class Pattern {
21 | private final Form form;
22 | private final Function