├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build-helper-class.rs ├── javalib ├── agent-tests │ └── src │ │ └── test │ │ └── java │ │ └── stackparam │ │ ├── StackParamNativeTest.java │ │ └── ThrowableTest.java ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── native │ └── src │ │ └── main │ │ └── java │ │ └── stackparam │ │ └── StackParamNative.java └── settings.gradle ├── src ├── bytecode │ ├── README.md │ ├── classfile.rs │ ├── io │ │ ├── mod.rs │ │ ├── reader.rs │ │ └── writer.rs │ └── mod.rs ├── jvmti_sys │ └── mod.rs ├── lib.rs ├── manip.rs ├── native.rs └── util.rs └── tests ├── bytecode_tests.rs └── external_java_tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | /thoughts.txt 3 | /.idea 4 | /target 5 | /Cargo.lock 6 | /javalib/.idea 7 | /javalib/build 8 | /javalib/.gradle 9 | /javalib/agent-tests/build 10 | /javalib/ow2-manip/build 11 | /javalib/native/build 12 | /stackparam.iml 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stackparam" 3 | version = "0.1.0" 4 | authors = ["Chad Retz "] 5 | build = "build-helper-class.rs" 6 | 7 | [lib] 8 | crate-type = ["dylib", "rlib"] 9 | 10 | [dependencies] 11 | jni-sys = "0.2" 12 | log = "0.3" 13 | env_logger = "0.3" 14 | 15 | [dev-dependencies] 16 | zip = { version = "0.2", default-features = false } 17 | 18 | [build-dependencies] 19 | log = "0.3" 20 | env_logger = "0.3" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chad Retz 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StackParam 2 | 3 | StackParam is a utility that gives method parameters to Java 8 stack traces. It is written in Rust and built to be 4 | fairly unobtrusive. 5 | 6 | It adds the parameter information to stack trace outputs and can be used to programmatically obtain method parameters 7 | (including "this" for non-static methods) up the stack. 8 | 9 | ## Quick Start 10 | 11 | StackParam is a shared library loaded as a [JVMTI](http://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html) 12 | agent (see [Installation](#installation) to get it). For example, say the following Java class is at 13 | `HelloFailure.java`: 14 | 15 | ```java 16 | public class HelloFailure { 17 | public static void main(String[] args) { 18 | throwException(42); 19 | } 20 | 21 | public static void throwException(int foo) { 22 | throw new RuntimeException("Hello, Failure!"); 23 | } 24 | } 25 | ``` 26 | 27 | Compile with `javac ./HelloFailure.java` which will create the `HelloFailure.class` file. Now run via Java while 28 | specifying the shared library path: 29 | 30 | java -agentpath:path/to/shared.ext HelloFailure --some-arg --another-arg 10 31 | 32 | The file name `shared.ext` might be `stackparam.dll` on Windows, `libstackparam.so` on Linux, etc. The output is: 33 | 34 | Exception in thread "main" java.lang.RuntimeException: Hello, Failure! 35 | at HelloFailure.throwException(HelloFailure.java:0) [arg0=42] 36 | at HelloFailure.main(HelloFailure.java:0) [arg0=[--some-arg, --another-arg, 10]] 37 | 38 | Compiling with the `-g` option passed to `javac` gives debug information to StackParam so the parameter names are 39 | accurate. The output for the same code as above compiled with debug info is: 40 | 41 | Exception in thread "main" java.lang.RuntimeException: Hello, Failure! 42 | at HelloFailure.throwException(HelloFailure.java:0) [foo=42] 43 | at HelloFailure.main(HelloFailure.java:0) [args=[--some-arg, --another-arg, 10]] 44 | 45 | Note that StackParam works with all exception stack trace uses and even provides a mechanism to programmatically obtain 46 | method parameters up the call stack. 47 | 48 | ## Installation 49 | 50 | if you are using 64-bit Windows or Linux, the easiest way is to download the latest stackparam.dll or libstackparam.so 51 | from the releases area. For Mac or other architectures, I don't have a precompiled shared lib but it should be easy to 52 | build. See [Manually Building](#manually-building). 53 | 54 | ### Java Versions 55 | 56 | This should work with OpenJDK/Oracle 8. It might also work on OpenJDK/Oracle 7 if manually compiled as the injection 57 | points are similar, but this is untested. This will not yet work with OpenJDK/Oracle <= 6 or OpenJDK/Oracle 9. It will 58 | also not work on other JREs whose runtimes are not based on the OpenJDK stdlib. 59 | 60 | It simply didn't suit my needs to support other JVMs (yet), but it would be fairly trivial to implement if enough people 61 | want it. 62 | 63 | I doubt Android would be as straightforward. [This](https://code.google.com/p/android/issues/detail?id=60961) seems to 64 | imply there is no JVMTI interface. If that was in place, the 65 | [`Throwable`](https://android.googlesource.com/platform/libcore/+/6975f84c2ed72e1e26d20190b6f318718c849008/ojluni/src/main/java/java/lang/Throwable.java) 66 | class there would need the specifically targeted injections to support our needs which is no big deal. 67 | 68 | ### Manually Building 69 | 70 | This library is written in Rust and compiles a Java class at build time. Therefore the prerequisites are a recent 71 | installation of [Rust](https://www.rust-lang.org/) and a JDK 8 installation with `javac` on the `PATH`. 72 | 73 | Once the prerequisites are installed, the tests can be run via `cargo`: 74 | 75 | cargo test 76 | 77 | This does several things internally including running Gradle (automatically downloaded if not present) to build a Java 78 | class and running Gradle again to do some Java-side tests. 79 | 80 | If the tests succeed, build can be done via `cargo` as well: 81 | 82 | cargo build --release 83 | 84 | Once built, the shared library is in `target/release/shared.ext` where `shared.ext` might be `stackparam.dll` on 85 | Windows, `libstackparam.so` on Linux, etc. 86 | 87 | ## Usage 88 | 89 | Once the agent is loaded, it automatically injects strings into stack traces. 90 | 91 | ### Loading the Agent 92 | 93 | While [JVMTI](http://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#deployingAgents) has a few approaches to 94 | deploying an agent, this agent needs to be deployed via command line because it needs to hook into the very earliest 95 | part of the JVM load. This is easily done via the `-agentpath` path parameter of the `java` command, e.g.: 96 | 97 | java -agentpath:path/to/shared.ext HelloWorld 98 | 99 | Instead of `-agentpath` for the exact path, `-agentlib` could be used to just give the library name. Assuming the 100 | `stackparam.dll` is on the `PATH` in Windows or `libstackparam.so` is in the shared library location (e.g. an 101 | overridden `LD_LIBRARY_PATH`) in Linux, you can run: 102 | 103 | java -agentlib:stackparam HelloWorld 104 | 105 | Note, although untested, this library can likely be placed in the JRE's `lib/amd64` folder to get the same effect. 106 | 107 | ### Logging 108 | 109 | This library uses Rust's [env_logger](https://doc.rust-lang.org/log/env_logger/) which lets the logging be controlled 110 | by the `RUST_LOG` environment variable. The binary name is `stackparam`, so setting `RUST_LOG` to `stackparam=info` 111 | shows info logs, `stackparam=debug` shows debug logs, and just `stackparam` shows all logs. The logs are emitted to the 112 | standard error stream. 113 | 114 | ### Programmatic Value Access 115 | 116 | In addition to just showing parameters, the 117 | [`stackparam.StackParamNative`](javalib/native/src/main/java/stackparam/StackParamNative.java) class is automatically injected. 118 | This class provides the following `loadStackParams` method: 119 | 120 | ```java 121 | /** 122 | * Returns the stack params of the given thread for the given depth. It is 123 | * returned with closest depth first. 124 | * 125 | * Each returned sub array (representing a single depth) has params 126 | * including "this" as the first param for non-static methods. Each param 127 | * takes 3 values in the array: the string name, the string JVM type 128 | * signature, and the actual object. All primitives are boxed. 129 | * 130 | * In cases where the param cannot be obtained (i.e. non-"this" for native 131 | * methods), the string "" becomes the value regardless of the 132 | * type's signature. 133 | * 134 | * @param thread The thread to get params for 135 | * @param maxDepth The maximum depth to go to 136 | * @return Array where each value represents params for a frame. Each param 137 | * takes 3 spots in the sub-array for name, type, and value. 138 | * @throws NullPointerException If thread is null 139 | * @throws IllegalArgumentException If maxDepth is negative 140 | * @throws RuntimeException Any internal error we were not prepared for 141 | */ 142 | public static native Object[][] loadStackParams(Thread thread, int maxDepth); 143 | ``` 144 | 145 | Since this is automatically injected, it can easily be accessed via reflection. The problem with accessing via 146 | reflection is there are a few stack frames between the caller and the reflected method that `invoke` is called on which 147 | makes the params less predictably navigable. 148 | 149 | Instead, you can either add the above contents in a `stackparam.StackParamNative` class in your source code or you can 150 | add a dependency to the `native` library JAR to your build file via 151 | [`JitPack`](https://jitpack.io/#com.github.cretz.stackparam/native/0.1.0). Even though it is included, the file/JAR 152 | will never be directly used because the actual class is injected early at VM startup. 153 | 154 | Once in place, you can do neat things like grab the caller, e.g.: 155 | 156 | ```java 157 | public class CallerTest { 158 | /** Check that this is only called via instance method of foo.Bar */ 159 | public boolean isFooBarInstanceMethod() { 160 | // We have to give a max depth of 3 and access the third one because on 161 | // the stack, "loadStackParams" is at 0, this method ("checkCaller") is 162 | // at 1, and then the caller is at 2. 163 | Object[] callerParams = stackparam.StackParamNative.loadStackParams(Thread.currentThread(), 3)[2]; 164 | return callerParams.length != 0 && 165 | "this".equals(callerParams[0]) && 166 | callerParams[2] instanceof foo.Bar; 167 | } 168 | } 169 | ``` 170 | 171 | While this kind of programming/validation in general is very bad and not portable, it demonstrates usage of the library. 172 | Also, it is very high performing. 173 | 174 | There is also a `public static String appendParamsToFrameString(String frameString, Object[] params)` method on the 175 | class which takes the given set of `params` triplets and appends it (after a space) to the given `frameString` and 176 | returns it. It is mostly a helper for the library, but can be used by others. 177 | 178 | ### Production Usage? 179 | 180 | I wouldn't, but I took care to silently fail and fall back to original JVM functionality in most cases. There are a few 181 | possible burdensome performance issues: 182 | 183 | * Stack walking at exception creation (CPU) - This is not extremely heavy and note that the JVM does its own native 184 | stack walking at this time to build the stack trace in the first place. Usually this is not an issue except for those 185 | using exceptions for control flow, and in those cases the developers should be overriding `fillInStackTrace()` to do 186 | nothing which will also make this library do nothing. 187 | * Holding object references (memory) - For every exception stack trace created, a multi-dimensional array is created to 188 | hold references to all local params, their names, and their signatures. Usually once an exception is thrown, many of 189 | the local variables on the stack go out of scope and are eligible for garbage collection. With StackParam, the 190 | references (and the strings) live at least as long as the exception does. Besides just the multi-dimensional array on 191 | `Throwable`, a reference is also placed on `StackTraceElement` to the individual array item. 192 | * According to 193 | [this StackOverflow question](http://stackoverflow.com/questions/24108591/overhead-of-enabling-jvmti-capability-to-query-local-variables), 194 | since we tell the JVM we want local variables, a couple of optimizations might be disabled. I have not independently 195 | verified this. 196 | 197 | There are also a couple of security concerns: 198 | 199 | * Low-level library - Care should always be taken when adding native code behind the JVM in production. There are no 200 | guarantees of the safety of the software. Granted, the danger surface area is not much higher than untrusted Java 201 | code. 202 | * Sensitive data - StackParam does not know what is considered confidential data and what is not. Data/method filtering 203 | is not yet implemented. So your parameters to `BCrypt.checkpw` for example would be visible to all if an exception 204 | occurred inside it. 205 | 206 | ## How Does it Work? 207 | 208 | StackParam takes a series of steps to do what it does. In no particular order, they are: 209 | 210 | * During Rust build time, compile `stackparam.StackParamNative` to a class file and include the bytes into the shared 211 | library. 212 | * On agent start, ask the JVM to access local vars and class file load events. Also register callbacks for VM init and 213 | class file load hook. 214 | * On VM init, take the `stackparam.StackParamNative` bytes and inject the class via 215 | [`DefineClass`](http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#DefineClass). 216 | * Just before `Throwable` class load, transform the class bytes to: 217 | * Add a `private transient Object[][] stackParams` field to the class. 218 | * Add a `private native Throwable stackParamFillInStackTrace(Thread)` method to the class. 219 | * Change the existing `fillInStackTrace` method to find the internal native `fillStackTrace` overload call. Then 220 | inject instructions to call our `stackParamFillInStackTrace(Thread)` method afterwards. 221 | * Rename the existing `getOurStackTrace` method to `$$stack_param$$getOurStackTrace`. 222 | * Add a `private synchronized native StackTraceElement[] getOurStackTrace` method to the class. 223 | * Just before `StackTraceElement` class load, transform the class bytes to: 224 | * Add a `transient Object[] paramInfo` field to the class. 225 | * Rename the existing `toString` method to `$$stack_param$$toString`. 226 | * Add a `public native String toString` method to the class. 227 | * On native invoke of `stackparam.StackParamNative.loadStackParams`, walk up the stack grabbing params and return them. 228 | * On the native invoke of `Throwable.stackParamFillInStackTrace`: 229 | * Call `getStackTraceDepth` to fetch the depth of the current stack trace. 230 | * Walk up the stack grabbing params for only the stack trace depth (plus a little for ourselves). 231 | * Take the last depth amount of params, and store in `Throwable.stackParams`. 232 | * On the native invoke of `Throwable.getOurStackTrace`: 233 | * Record state of `Throwable.stackTrace`. 234 | * Call `Throwable.$$stack_param$$getOurStackTrace` for the array of `StackTraceElement` values. 235 | * Bail if the result isn't different than the recorded field value. 236 | * Take the resulting array of `StackTraceElement` values and set each one's `paramInfo` field to its set of params 237 | from the `Throwable.stackParams` field. 238 | * On the native invoke of `StackTraceElement.toString`: 239 | * Call `StackTraceElement.$$stack_param$$toString`. 240 | * Run the result through `stackparam.StackParamNative.appendParamsToFrameString` method along with the 241 | `StackTraceElement.paramInfo` value to return a string with parameters. 242 | 243 | While it looks like a good bit of reverse engineering and a bit brittle, it's not that bad. Failures are handled 244 | gracefully for the most part. And it is not that difficult to add conditionals and do different bytecode manipulation 245 | based on what is seen (e.g. for different Java versions or different JVMs). 246 | 247 | ## Why? 248 | 249 | Primary goals: 250 | * Learn Rust (only kinda, I live in unsafe code which isn't cool) 251 | * Learn Rust + JNI/JVMTI 252 | * Learn intricacies of JVMTI and more about JVM internals 253 | * Provide nice example project for others wanting to hook into the JVM 254 | 255 | Secondary goals: 256 | * Actually get method params on the stack trace 257 | 258 | ## TODO 259 | 260 | * Other JVM versions 261 | * Especially Java 9, where we can just have params on 262 | [StackFrame](http://download.java.net/java/jdk9/docs/api/java/lang/StackWalker.StackFrame.html) and the callers can 263 | choose how they walk it. 264 | * Proper ignoring of certain OOM exceptions, see 265 | [this](http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/memory/universe.cpp#l557) for some 266 | special exceptions that don't get traces. 267 | * Options such as filtering, disabling, etc. 268 | * Stop checking for JNI errors on every invocation, but only on error situations like null responses. 269 | 270 | ## Acknowledgements 271 | 272 | The entire [bytecode manipulation](src/bytecode) library was taken from https://github.com/xea/rust-jvmti with many 273 | thanks. I also looked at that repo quite a bit when I was getting started and took the initial JVMTI definitions from 274 | there. 275 | 276 | Also, this project uses the JNI definitions from https://github.com/sfackler/rust-jni-sys. -------------------------------------------------------------------------------- /build-helper-class.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::process::Command; 3 | 4 | fn main() { 5 | let javalib_path = std::env::current_dir().expect("No current dir").join("javalib"); 6 | let mut gradle_path = javalib_path.join("gradlew"); 7 | if cfg!(target_os = "windows") { 8 | gradle_path.set_extension("bat"); 9 | } 10 | 11 | println!("Starting Gradle at {}", gradle_path.to_string_lossy()); 12 | 13 | let output = Command::new(gradle_path) 14 | .current_dir(javalib_path) 15 | .arg("--no-daemon") 16 | .arg(":native:classes") 17 | .output() 18 | .expect("Couldn't start gradle"); 19 | 20 | println!("Gradle stdout: {}", String::from_utf8_lossy(&output.stdout)); 21 | println!("Gradle stderr: {}", String::from_utf8_lossy(&output.stderr)); 22 | assert!(output.status.success()); 23 | } 24 | -------------------------------------------------------------------------------- /javalib/agent-tests/src/test/java/stackparam/StackParamNativeTest.java: -------------------------------------------------------------------------------- 1 | package stackparam; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertArrayEquals; 6 | 7 | public class StackParamNativeTest { 8 | 9 | private static final Object CONST_OBJ = new Object(); 10 | 11 | @Test 12 | public void testLoadStackParamsSimple() { 13 | Object[][] stackParams = instanceWithStringArg("Some string"); 14 | 15 | Object[] expectedOtherMethodArgs = { 16 | "boolArg", "Z", true, 17 | "byteArg", "B", (byte) 100, 18 | "charArg", "C", (char) 101, 19 | "shortArg", "S", (short) 102, 20 | "intArg", "I", 103, 21 | "longArg", "J", 104L, 22 | "floatArg", "F", 105.6f, 23 | "doubleArg", "D", 106.7, 24 | "nullArg", "Ljava/lang/Object;", null, 25 | "objectExactArg", "Ljava/lang/Object;", CONST_OBJ, 26 | "stringVarArgs", "[Ljava/lang/String;", new String[] { "foo", "bar", "baz" } 27 | }; 28 | assertArrayEquals(expectedOtherMethodArgs, stackParams[1]); 29 | 30 | Object[] expectedStringMethodArgs = { 31 | "this", 32 | "L" + getClass().getName().replace('.', '/') + ";", 33 | this, 34 | "stringArg", 35 | "Ljava/lang/String;", 36 | "Some string" 37 | }; 38 | assertArrayEquals(expectedStringMethodArgs, stackParams[2]); 39 | } 40 | 41 | private Object[][] instanceWithStringArg(String stringArg) { 42 | return withOtherArgs(true, (byte) 100, (char) 101, 43 | (short) 102, 103, 104L, 44 | 105.6f, 106.7, null, 45 | CONST_OBJ, "foo", "bar", "baz"); 46 | } 47 | 48 | private static Object[][] withOtherArgs(boolean boolArg, byte byteArg, char charArg, 49 | short shortArg, int intArg, long longArg, 50 | float floatArg, double doubleArg, Object nullArg, 51 | Object objectExactArg, String... stringVarArgs) { 52 | return StackParamNative.loadStackParams(Thread.currentThread(), 3); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /javalib/agent-tests/src/test/java/stackparam/ThrowableTest.java: -------------------------------------------------------------------------------- 1 | package stackparam; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | 6 | public class ThrowableTest { 7 | 8 | private static final Object CONST_OBJ = new Object(); 9 | 10 | @Test 11 | public void testStackTraceElementToString() throws Exception { 12 | String expected = "[this=" + this + ", boolArg=true, byteArg=100, " + 13 | "charArg=e, shortArg=102, intArg=103, longArg=104, " + 14 | "floatArg=105.6, doubleArg=106.7, nullArg=null, " + 15 | "objectExactArg=" + CONST_OBJ + ", stringVarArgs=[foo, bar, baz]]"; 16 | StackTraceElement elem = getTestElement(); 17 | String traceStr = elem.toString(); 18 | traceStr = traceStr.substring(traceStr.indexOf('[')); 19 | assertEquals(expected, traceStr); 20 | } 21 | 22 | private StackTraceElement getTestElement() { 23 | try { 24 | methodThatWillThrow(true, (byte) 100, (char) 101, 25 | (short) 102, 103, 104L, 26 | 105.6f, 106.7, null, 27 | CONST_OBJ, "foo", "bar", "baz"); 28 | fail(); 29 | return null; 30 | } catch (RuntimeException e) { 31 | return e.getStackTrace()[0]; 32 | } 33 | } 34 | 35 | private void methodThatWillThrow(boolean boolArg, byte byteArg, char charArg, 36 | short shortArg, int intArg, long longArg, 37 | float floatArg, double doubleArg, Object nullArg, 38 | Object objectExactArg, String... stringVarArgs) { 39 | throw new RuntimeException("OH!"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /javalib/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | group 'stackparam' 3 | version '1.0-SNAPSHOT' 4 | 5 | apply plugin: 'java' 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | testCompile 'junit:junit:4.12' 13 | } 14 | } 15 | 16 | project(':native') { 17 | // With enough requests, I'll enable this to go backwards for older JVMs 18 | // sourceCompatibility = 1.6 19 | // targetCompatibility = 1.6 20 | 21 | compileJava { 22 | options.fork = true 23 | options.forkOptions.executable = 'javac' 24 | } 25 | compileTestJava { 26 | options.fork = true 27 | options.forkOptions.executable = 'javac' 28 | } 29 | } 30 | 31 | project(':agent-tests') { 32 | 33 | dependencies { 34 | compile project(':native') 35 | } 36 | 37 | test { 38 | testLogging.showStandardStreams = true 39 | testLogging.showExceptions = true 40 | testLogging.exceptionFormat = 'full' 41 | jvmArgs += '-agentpath:../../target/debug/' + System.mapLibraryName('stackparam') 42 | // jvmArgs += '-XX:+TraceClassLoading' 43 | // jvmArgs += '-XX:+TraceClassUnloading' 44 | // jvmArgs += ['-XX:+AggressiveOpts', '-XX:+UnlockDiagnosticVMOptions', '-XX:+UnlockExperimentalVMOptions'] 45 | // jvmArgs += '-XX:+PrintFlagsFinal' 46 | // jvmArgs += '-Xprof' 47 | // jvmArgs += '-XX:TraceJVMTI=all' 48 | // jvmArgs += '-Xcheck:jni' 49 | // jvmArgs += '-verbose:jni' 50 | // jvmArgs += ['-XX:+LogVMOutput', '-XX:+LogEvents', '-XX:LogEventsBufferEntries=50', '-XX:+LogCommercialFeatures'] 51 | // jvmArgs += '-XX:+CreateMinidumpOnCrash' 52 | } 53 | } -------------------------------------------------------------------------------- /javalib/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cretz/stackparam/d42fd0a15fdc97ed02f5fc2c998143a2e4402a28/javalib/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /javalib/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Mar 11 12:26:55 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip 7 | -------------------------------------------------------------------------------- /javalib/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /javalib/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /javalib/native/src/main/java/stackparam/StackParamNative.java: -------------------------------------------------------------------------------- 1 | package stackparam; 2 | 3 | import java.util.Arrays; 4 | 5 | public class StackParamNative { 6 | 7 | /** 8 | * Maximum number of chars a single param can take before it is chopped 9 | * and has an ellipsis appended. 10 | */ 11 | public static int MAX_PARAM_STR_LEN = 50; 12 | 13 | /** 14 | * Returns the stack params of the given thread for the given depth. It is 15 | * returned with closest depth first. 16 | * 17 | * Each returned sub array (representing a single depth) has params 18 | * including "this" as the first param for non-static methods. Each param 19 | * takes 3 values in the array: the string name, the string JVM type 20 | * signature, and the actual object. All primitives are boxed. 21 | * 22 | * In cases where the param cannot be obtained (i.e. non-"this" for native 23 | * methods), the string "" becomes the value regardless of the 24 | * type's signature. 25 | * 26 | * @param thread The thread to get params for 27 | * @param maxDepth The maximum depth to go to 28 | * @return Array where each value represents params for a frame. Each param 29 | * takes 3 spots in the sub-array for name, type, and value. 30 | * @throws NullPointerException If thread is null 31 | * @throws IllegalArgumentException If maxDepth is negative 32 | * @throws RuntimeException Any internal error we were not prepared for 33 | */ 34 | public static native Object[][] loadStackParams(Thread thread, int maxDepth); 35 | 36 | /** 37 | * Appends params string, e.g. "[foo=bar, baz=null]" to the given frame 38 | * string. Any exceptions during string building are trapped. 39 | * 40 | * @param frameString The string to append to 41 | * @param params The array for params. Must be multiple of 3 as returned by 42 | * loadStackParams. 43 | * @return The resulting string 44 | */ 45 | public static String appendParamsToFrameString(String frameString, Object[] params) { 46 | try { 47 | if (params == null) return frameString; 48 | StringBuilder ret = new StringBuilder(frameString); 49 | ret.append(" ["); 50 | for (int i = 0; i < params.length / 3; i++) { 51 | if (i > 0) ret.append(", "); 52 | ret.append((String) params[i * 3]).append("="); 53 | String param; 54 | try { 55 | param = paramValToString(params[(i * 3) + 2]); 56 | } catch (Exception e) { 57 | ret.append("toString err: ").append(e.toString()); 58 | continue; 59 | } 60 | if (param.length() <= MAX_PARAM_STR_LEN) ret.append(param); 61 | else ret.append(param, 0, MAX_PARAM_STR_LEN).append("..."); 62 | } 63 | return ret.append("]").toString(); 64 | } catch (Exception e) { 65 | return frameString + "[failed getting params: " + e + "]"; 66 | } 67 | } 68 | 69 | private static String paramValToString(Object paramVal) { 70 | if (paramVal != null && paramVal.getClass().isArray()) { 71 | if (paramVal instanceof boolean[]) return Arrays.toString((boolean[]) paramVal); 72 | else if (paramVal instanceof byte[]) return Arrays.toString((byte[]) paramVal); 73 | else if (paramVal instanceof char[]) return Arrays.toString((char[]) paramVal); 74 | else if (paramVal instanceof short[]) return Arrays.toString((short[]) paramVal); 75 | else if (paramVal instanceof int[]) return Arrays.toString((int[]) paramVal); 76 | else if (paramVal instanceof long[]) return Arrays.toString((long[]) paramVal); 77 | else if (paramVal instanceof float[]) return Arrays.toString((float[]) paramVal); 78 | else if (paramVal instanceof double[]) return Arrays.toString((double[]) paramVal); 79 | else return Arrays.toString((Object[]) paramVal); 80 | } 81 | return String.valueOf(paramVal); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /javalib/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'stackparam' 2 | 3 | include 'native' 4 | include 'agent-tests' -------------------------------------------------------------------------------- /src/bytecode/README.md: -------------------------------------------------------------------------------- 1 | Taken from https://github.com/xea/rust-jvmti with many thanks. -------------------------------------------------------------------------------- /src/bytecode/classfile.rs: -------------------------------------------------------------------------------- 1 | 2 | /// 3 | /// A `Classfile` represents a definition of a single JVM class or interface. Unlike the bytecode 4 | /// itself, it doesn't represent every byte in the class definition, though, many information are 5 | /// encoded in the type system instead. This approach may seem restrictive but it helps achieving 6 | /// bytecode safety. 7 | #[derive(Debug)] 8 | pub struct Classfile { 9 | pub version: ClassfileVersion, 10 | pub constant_pool: ConstantPool, 11 | pub access_flags: AccessFlags, 12 | pub this_class: ConstantPoolIndex, 13 | pub super_class: ConstantPoolIndex, 14 | pub interfaces: Vec, 15 | pub fields: Vec, 16 | pub methods: Vec, 17 | pub attributes: Vec 18 | } 19 | 20 | impl Classfile { 21 | /// Create a new classfile, initialised with sensible default values 22 | pub fn new() -> Classfile { 23 | Classfile::default() 24 | } 25 | } 26 | 27 | impl Default for Classfile { 28 | fn default() -> Self { 29 | Classfile { 30 | version: ClassfileVersion::default(), 31 | constant_pool: ConstantPool::default(), 32 | access_flags: AccessFlags::default(), 33 | this_class: ConstantPoolIndex::default(), 34 | super_class: ConstantPoolIndex::default(), 35 | interfaces: vec![], 36 | fields: vec![], 37 | methods: vec![], 38 | attributes: vec![] 39 | } 40 | } 41 | } 42 | 43 | /// 44 | /// Describe a classfile version number. 45 | #[derive(Ord, PartialOrd, Eq, PartialEq, Debug)] 46 | pub struct ClassfileVersion { 47 | pub minor_version: u16, 48 | pub major_version: u16 49 | } 50 | 51 | impl ClassfileVersion { 52 | pub fn new(major_version: u16, minor_version: u16) -> ClassfileVersion { 53 | ClassfileVersion { major_version: major_version, minor_version: minor_version } 54 | } 55 | } 56 | 57 | impl Default for ClassfileVersion { 58 | fn default() -> Self { 59 | const DEFAULT_MAJOR_VERSION: u16 = 52; 60 | const DEFAULT_MINOR_VERSION: u16 = 0; 61 | 62 | ClassfileVersion { major_version: DEFAULT_MAJOR_VERSION, minor_version: DEFAULT_MINOR_VERSION } 63 | } 64 | } 65 | 66 | /// 67 | /// A `ConstantPool` is a table of various string and number literal constants that are referred 68 | /// within the substructures of the `Classfile`. 69 | #[derive(Debug)] 70 | pub struct ConstantPool { 71 | pub constants: Vec 72 | } 73 | 74 | impl ConstantPool { 75 | pub fn new(constants: Vec) -> ConstantPool { 76 | ConstantPool { 77 | constants: constants 78 | } 79 | } 80 | 81 | pub fn get_utf8(&self, idx: u16) -> Option<&Vec> { 82 | match self.constants.get(idx as usize) { 83 | Some(constant) => match constant { 84 | &Constant::Utf8(ref bytes) => Some(bytes), 85 | _ => None 86 | }, 87 | _ => None 88 | } 89 | } 90 | 91 | pub fn get_utf8_string(&self, idx: u16) -> Option { 92 | match self.get_utf8(idx) { 93 | Some(bytes) => match String::from_utf8(bytes.clone()) { 94 | Ok(string) => Some(string), 95 | _ => None 96 | }, 97 | _ => None 98 | } 99 | } 100 | 101 | pub fn find_ut8_index(&self, utf8: &'static str) -> Option { 102 | for i in 0..self.constants.len() { 103 | match self.constants[i] { 104 | Constant::Utf8(ref bytes) => { 105 | if bytes.as_slice() == utf8.as_bytes() { 106 | return Some(i); 107 | } 108 | }, 109 | _ => () 110 | } 111 | } 112 | None 113 | } 114 | 115 | pub fn get_utf8_index(&self, utf8: &'static str) -> usize { 116 | self.find_ut8_index(utf8).unwrap_or(0) 117 | } 118 | 119 | pub fn resolve_index(&self, idx: &ConstantPoolIndex) -> Option<&Constant> { 120 | self.constants.get(idx.idx) 121 | } 122 | 123 | pub fn cp_len(&self) -> usize { 124 | //self.constants.iter().fold(0, |acc, x| acc + x.cp_size()) 125 | self.constants.len() 126 | } 127 | } 128 | 129 | impl Default for ConstantPool { 130 | fn default() -> Self { 131 | ConstantPool { 132 | constants: vec![] 133 | } 134 | } 135 | } 136 | 137 | #[derive(Default, Debug)] 138 | pub struct ConstantPoolIndex { 139 | pub idx: usize 140 | } 141 | 142 | impl ConstantPoolIndex { 143 | pub fn new(idx: usize) -> Self { 144 | ConstantPoolIndex { idx: idx } 145 | } 146 | } 147 | 148 | #[derive(Debug)] 149 | pub enum Constant { 150 | Utf8(Vec), 151 | Integer(u32), 152 | Float(u32), 153 | Long(u64), 154 | Double(u64), 155 | Class(ConstantPoolIndex), 156 | FieldRef { class_index: ConstantPoolIndex, name_and_type_index: ConstantPoolIndex }, 157 | MethodRef { class_index: ConstantPoolIndex, name_and_type_index: ConstantPoolIndex }, 158 | InterfaceMethodRef { class_index: ConstantPoolIndex, name_and_type_index: ConstantPoolIndex }, 159 | String(ConstantPoolIndex), 160 | NameAndType { name_index: ConstantPoolIndex, descriptor_index: ConstantPoolIndex }, 161 | MethodHandle { reference_kind: ReferenceKind, reference_index: ConstantPoolIndex }, 162 | MethodType(ConstantPoolIndex), 163 | InvokeDynamic { bootstrap_method_attr_index: ConstantPoolIndex, name_and_type_index: ConstantPoolIndex }, 164 | Unknown(u8), 165 | Placeholder 166 | } 167 | 168 | impl Constant { 169 | pub fn cp_size(&self) -> usize { 170 | match self { 171 | &Constant::Long(_) => 2, 172 | &Constant::Double(_) => 2, 173 | &Constant::Placeholder => 0, 174 | _ => 1 175 | } 176 | } 177 | } 178 | 179 | #[derive(Debug)] 180 | pub enum ReferenceKind { 181 | GetField = 1, 182 | GetStatic = 2, 183 | PutField = 3, 184 | PutStatic = 4, 185 | InvokeVirtual = 5, 186 | InvokeStatic = 6, 187 | InvokeSpecial = 7, 188 | NewInvokeSpecial = 8, 189 | InvokeInterface = 9, 190 | Unknown = 255 191 | } 192 | 193 | impl ReferenceKind { 194 | pub fn from_u8(value: u8) -> ReferenceKind { 195 | match value { 196 | 1 => ReferenceKind::GetField, 197 | 2 => ReferenceKind::GetStatic, 198 | 3 => ReferenceKind::PutField, 199 | 4 => ReferenceKind::PutStatic, 200 | 5 => ReferenceKind::InvokeVirtual, 201 | 6 => ReferenceKind::InvokeStatic, 202 | 7 => ReferenceKind::InvokeSpecial, 203 | 8 => ReferenceKind::NewInvokeSpecial, 204 | 9 => ReferenceKind::InvokeInterface, 205 | _ => ReferenceKind::Unknown 206 | } 207 | } 208 | 209 | pub fn to_u8(&self) -> u8 { 210 | match *self { 211 | ReferenceKind::GetField => 1, 212 | ReferenceKind::GetStatic => 2, 213 | ReferenceKind::PutField => 3, 214 | ReferenceKind::PutStatic => 4, 215 | ReferenceKind::InvokeVirtual => 5, 216 | ReferenceKind::InvokeStatic => 6, 217 | ReferenceKind::InvokeSpecial => 7, 218 | ReferenceKind::NewInvokeSpecial => 8, 219 | ReferenceKind::InvokeInterface => 9, 220 | ReferenceKind::Unknown => 255 221 | } 222 | } 223 | } 224 | 225 | #[derive(Default, Debug)] 226 | pub struct AccessFlags { 227 | pub flags: u16 228 | } 229 | 230 | impl AccessFlags { 231 | pub fn new() -> AccessFlags { 232 | AccessFlags::of(0) 233 | } 234 | 235 | pub fn of(val: u16) -> AccessFlags { 236 | AccessFlags { flags: val } 237 | } 238 | 239 | pub fn has_flag(&self, flag: u16) -> bool { 240 | self.flags & flag > 0 241 | } 242 | 243 | pub fn set_flag(&mut self, flag: u16) { 244 | self.flags |= flag; 245 | } 246 | 247 | pub fn clear_flag(&mut self, flag: u16) { 248 | self.flags &= flag ^ 0xFFFF; 249 | } 250 | } 251 | 252 | pub enum ClassAccessFlags { 253 | Public = 0x0001, // Declared public; may be accessed from outside its package. 254 | Final = 0x0010, // Declared final; no subclasses allowed. 255 | Super = 0x0020, // Treat superclass methods specially when invoked by the invokespecial instruction. 256 | Interface = 0x0200, // Is an interface, not a class. 257 | Abstract = 0x0400, // Declared abstract; must not be instantiated. 258 | Synthetic = 0x1000, // Declared synthetic; not present in the source code. 259 | Annotation = 0x2000, // Declared as an annotation type. 260 | Enum = 0x4000 // Declared as an enum type. 261 | } 262 | 263 | pub enum FieldAccessFlags { 264 | Public = 0x0001, // Declared public; may be accessed from outside its package. 265 | Private = 0x0002, // Declared private; usable only within the defining class. 266 | Protected = 0x0004, // Declared protected; may be accessed within subclasses. 267 | Static = 0x0008, // Declared static. 268 | Final = 0x0010, // Declared final; never directly assigned to after object construction (JLS §17.5). 269 | Volatile = 0x0040, // Declared volatile; cannot be cached. 270 | Transient = 0x0080, // Declared transient; not written or read by a persistent object manager. 271 | Synthetic = 0x1000, // Declared synthetic; not present in the source code. 272 | Enum = 0x4000 // Declared as an element of an enum. 273 | } 274 | 275 | pub enum MethodAccessFlags { 276 | Public = 0x0001, // Declared public; may be accessed from outside its package. 277 | Private = 0x0002, // Declared private; accessible only within the defining class. 278 | Protected = 0x0004, // Declared protected; may be accessed within subclasses. 279 | Static = 0x0008, // Declared static. 280 | Final = 0x0010, // Declared final; must not be overridden (§5.4.5). 281 | Synchronized = 0x0020, // Declared synchronized; invocation is wrapped by a monitor use. 282 | Bridge = 0x0040, // A bridge method, generated by the compiler. 283 | Varargs = 0x0080, // Declared with variable number of arguments. 284 | Native = 0x0100, // Declared native; implemented in a language other than Java. 285 | Abstract = 0x0400, // Declared abstract; no implementation is provided. 286 | Strict = 0x0800, // Declared strictfp; floating-point mode is FP-strict. 287 | Synthetic = 0x1000 // Declared synthetic; not present in the source code. 288 | } 289 | 290 | pub enum InnerClassAccessFlags { 291 | Public = 0x0001, // Marked or implicitly public in source. 292 | Private = 0x0002, // Marked private in source. 293 | Protected = 0x0004, // Marked protected in source. 294 | Static = 0x0008, // Marked or implicitly static in source. 295 | Final = 0x0010, // Marked final in source. 296 | Interface = 0x0200, // Was an interface in source. 297 | Abstract = 0x0400, // Marked or implicitly abstract in source. 298 | Synthetic = 0x1000, // Declared synthetic; not present in the source code. 299 | Annotation = 0x2000, // Declared as an annotation type. 300 | Enum = 0x4000, // Declared as an enum type. 301 | } 302 | 303 | pub enum ParameterAccessFlags { 304 | Final = 0x0010, 305 | Synthetic = 0x1000, 306 | Mandated = 0x8000 307 | } 308 | 309 | #[derive(Default, Debug)] 310 | pub struct Field { 311 | pub access_flags: AccessFlags, 312 | pub name_index: ConstantPoolIndex, 313 | pub descriptor_index: ConstantPoolIndex, 314 | pub attributes: Vec 315 | } 316 | 317 | #[derive(Default, Debug)] 318 | pub struct Method { 319 | pub access_flags: AccessFlags, 320 | pub name_index: ConstantPoolIndex, 321 | pub descriptor_index: ConstantPoolIndex, 322 | pub attributes: Vec 323 | } 324 | 325 | #[derive(Debug)] 326 | pub enum Attribute { 327 | ConstantValue(ConstantPoolIndex), 328 | Code { max_stack: u16, max_locals: u16, code: Vec, exception_table: Vec, attributes: Vec }, 329 | StackMapTable(Vec), 330 | Exceptions(Vec), 331 | InnerClasses(Vec), 332 | EnclosingMethod { class_index: ConstantPoolIndex, method_index: ConstantPoolIndex }, 333 | Synthetic, 334 | Signature(ConstantPoolIndex), 335 | SourceFile(ConstantPoolIndex), 336 | SourceDebugExtension(Vec), 337 | LineNumberTable(Vec), 338 | LocalVariableTable(Vec), 339 | LocalVariableTypeTable(Vec), 340 | Deprecated, 341 | RuntimeVisibleAnnotations(Vec), 342 | RuntimeInvisibleAnnotations(Vec), 343 | RuntimeVisibleParameterAnnotations(Vec>), 344 | RuntimeInvisibleParameterAnnotations(Vec>), 345 | RuntimeVisibleTypeAnnotations(Vec), 346 | RuntimeInvisibleTypeAnnotations(Vec), 347 | AnnotationDefault(ElementValue), 348 | BootstrapMethods(Vec), 349 | MethodParameters(Vec), 350 | RawAttribute { name_index: ConstantPoolIndex, info: Vec } 351 | } 352 | 353 | #[derive(Debug)] 354 | pub enum StackMapFrame { 355 | SameFrame { tag: u8 }, 356 | SameLocals1StackItemFrame { tag: u8, stack: VerificationType }, 357 | SameLocals1StackItemFrameExtended { offset_delta: u16, stack: VerificationType }, 358 | ChopFrame { tag: u8, offset_delta: u16 }, 359 | SameFrameExtended { offset_delta: u16 }, 360 | AppendFrame { tag: u8, offset_delta: u16, locals: Vec }, 361 | FullFrame { offset_delta: u16, locals: Vec, stack: Vec }, 362 | FutureUse { tag: u8 } 363 | } 364 | 365 | impl StackMapFrame { 366 | pub fn len(&self) -> usize { 367 | match self { 368 | &StackMapFrame::SameFrame { tag: _ } => 1, 369 | &StackMapFrame::SameLocals1StackItemFrame{ tag: _, ref stack } => 1 + stack.len(), 370 | &StackMapFrame::SameLocals1StackItemFrameExtended { offset_delta: _, ref stack } => 3 + stack.len(), 371 | &StackMapFrame::ChopFrame { tag: _, offset_delta: _ } => 3, 372 | &StackMapFrame::SameFrameExtended { offset_delta: _ } => 3, 373 | &StackMapFrame::AppendFrame { tag: _, offset_delta: _, ref locals } => 3 + locals.iter().fold(0, |acc, x| acc + x.len()), 374 | &StackMapFrame::FullFrame { offset_delta: _, ref locals, ref stack } => 7 + locals.iter().fold(0, |acc, x| acc + x.len()) + stack.iter().fold(0, |acc, x| acc + x.len()), 375 | &StackMapFrame::FutureUse { tag: _ } => 0 376 | } 377 | } 378 | } 379 | 380 | #[derive(Debug)] 381 | pub enum VerificationType { 382 | Top, 383 | Integer, 384 | Float, 385 | Long, 386 | Double, 387 | Null, 388 | UninitializedThis, 389 | Object { cpool_index: ConstantPoolIndex }, 390 | Uninitialized { offset: u16 } 391 | } 392 | 393 | impl VerificationType { 394 | pub fn len(&self) -> usize { 395 | match self { 396 | &VerificationType::Object { cpool_index: _ } => 3, 397 | &VerificationType::Uninitialized { offset: _ } => 3, 398 | _ => 1 399 | } 400 | } 401 | } 402 | 403 | #[derive(Debug)] 404 | pub struct ExceptionHandler { 405 | pub start_pc: u16, 406 | pub end_pc: u16, 407 | pub handler_pc: u16, 408 | pub catch_type: ConstantPoolIndex 409 | } 410 | 411 | #[derive(Debug)] 412 | pub struct InnerClass { 413 | pub inner_class_info_index: ConstantPoolIndex, 414 | pub outer_class_info_index: ConstantPoolIndex, 415 | pub inner_name_index: ConstantPoolIndex, 416 | pub access_flags: AccessFlags 417 | } 418 | 419 | #[derive(Debug)] 420 | pub struct LineNumberTable { 421 | pub start_pc: u16, 422 | pub line_number: u16 423 | } 424 | 425 | #[derive(Debug)] 426 | pub struct LocalVariableTable { 427 | pub start_pc: u16, 428 | pub length: u16, 429 | pub name_index: ConstantPoolIndex, 430 | pub descriptor_index: ConstantPoolIndex, 431 | pub index: u16 432 | } 433 | 434 | #[derive(Debug)] 435 | pub struct LocalVariableTypeTable { 436 | pub start_pc: u16, 437 | pub length: u16, 438 | pub name_index: ConstantPoolIndex, 439 | pub signature_index: ConstantPoolIndex, 440 | pub index: u16 441 | } 442 | 443 | #[derive(Debug)] 444 | pub struct Annotation { 445 | pub type_index: ConstantPoolIndex, 446 | pub element_value_pairs: Vec 447 | } 448 | 449 | impl Annotation { 450 | pub fn len(&self) -> usize { 451 | 4 + self.element_value_pairs.iter().fold(0, |acc, x| acc + x.len()) 452 | //4 + self.element_value_pairs.len() * 2 + self.element_value_pairs.iter().fold(0, |acc, x| acc + x.len()) 453 | } 454 | } 455 | 456 | #[derive(Debug)] 457 | pub struct ElementValuePair { 458 | pub element_name_index: ConstantPoolIndex, 459 | pub value: ElementValue 460 | } 461 | 462 | impl ElementValuePair { 463 | pub fn len(&self) -> usize { 464 | 2 + self.value.len() 465 | } 466 | } 467 | 468 | #[derive(Debug)] 469 | pub enum ElementValue { 470 | ConstantValue(u8, ConstantPoolIndex), 471 | Enum { type_name_index: ConstantPoolIndex, const_name_index: ConstantPoolIndex }, 472 | ClassInfo(ConstantPoolIndex), 473 | Annotation(Annotation), 474 | Array(Vec) 475 | } 476 | 477 | impl ElementValue { 478 | pub fn len(&self) -> usize { 479 | match self { 480 | &ElementValue::ConstantValue(_, _) => 3, 481 | &ElementValue::Enum { type_name_index: _, const_name_index: _ } => 5, 482 | &ElementValue::ClassInfo(_) => 3, 483 | &ElementValue::Annotation(ref annotation) => 1 + annotation.len(), 484 | &ElementValue::Array(ref table) => table.iter().fold(3, |acc, x| acc + x.len()) 485 | } 486 | } 487 | } 488 | 489 | #[derive(Debug)] 490 | pub struct TypeAnnotation { 491 | pub target_info: TargetInfo, 492 | pub target_path: TypePath, 493 | pub type_index: ConstantPoolIndex, 494 | pub element_value_pairs: Vec 495 | } 496 | 497 | impl TypeAnnotation { 498 | pub fn len(&self) -> usize { 499 | 5 + self.target_info.len() + self.target_path.len() + self.element_value_pairs.iter().fold(0, |acc, x| acc + x.len()) 500 | } 501 | } 502 | 503 | #[derive(Debug)] 504 | pub enum TargetInfo { 505 | TypeParameter { subtype: u8, idx: u8 }, 506 | SuperType { idx: u16 }, 507 | TypeParameterBound { subtype: u8, param_idx: u8, bound_index: u8 }, 508 | Empty { subtype: u8 }, 509 | MethodFormalParameter { idx: u8 }, 510 | Throws { idx: u16 }, 511 | LocalVar { subtype: u8, target: Vec<(u16, u16, u16)> }, 512 | Catch { idx: u16 }, 513 | Offset { subtype: u8, idx: u16 }, 514 | TypeArgument { subtype: u8, offset: u16, type_arg_idx: u8 } 515 | } 516 | 517 | impl TargetInfo { 518 | pub fn len(&self) -> usize { 519 | match self { 520 | &TargetInfo::TypeParameter { subtype: _, idx: _ } => 1, 521 | &TargetInfo::SuperType { idx: _ } => 2, 522 | &TargetInfo::TypeParameterBound { subtype: _, param_idx: _, bound_index: _ } => 2, 523 | &TargetInfo::Empty { subtype: _ } => 0, 524 | &TargetInfo::MethodFormalParameter { idx: _ } => 1, 525 | &TargetInfo::Throws { idx: _ } => 2, 526 | &TargetInfo::LocalVar { subtype: _, ref target } => { 2 + target.len() * 6 }, 527 | &TargetInfo::Catch { idx: _ } => 2, 528 | &TargetInfo::Offset { subtype: _, idx: _ } => 2, 529 | &TargetInfo::TypeArgument { subtype: _, offset: _, type_arg_idx: _ } => 3 530 | } 531 | } 532 | 533 | pub fn subtype(&self) -> u8 { 534 | match self { 535 | &TargetInfo::TypeParameter { subtype, idx: _ } => subtype, 536 | &TargetInfo::SuperType { idx: _ } => 0x10, 537 | &TargetInfo::TypeParameterBound { subtype, param_idx: _, bound_index: _ } => subtype, 538 | &TargetInfo::Empty { subtype } => subtype, 539 | &TargetInfo::MethodFormalParameter { idx: _ } => 0x16, 540 | &TargetInfo::Throws { idx: _ } => 0x17, 541 | &TargetInfo::LocalVar { subtype, target: _ } => subtype, 542 | &TargetInfo::Catch { idx: _ } => 0x42, 543 | &TargetInfo::Offset { subtype, idx: _ } => subtype, 544 | &TargetInfo::TypeArgument { subtype, offset: _, type_arg_idx: _ } => subtype 545 | } 546 | } 547 | } 548 | 549 | #[derive(Debug)] 550 | pub struct TypePath { 551 | pub path: Vec<(TypePathKind, u8)> 552 | } 553 | 554 | impl TypePath { 555 | pub fn len(&self) -> usize { 556 | 1 + self.path.len() * 2 557 | } 558 | } 559 | 560 | #[derive(Debug)] 561 | pub enum TypePathKind { 562 | Array, // Annotation is deeper in an array type 563 | Nested, // Annotation is deeper in a nested type 564 | Wildcard, // Annotation is on the bound of a wildcard type argument of a parameterized type 565 | TypeArgument // Annotation is on a type argument of a parameterized type 566 | } 567 | 568 | impl TypePathKind { 569 | pub fn value(&self) -> u8 { 570 | match self { 571 | &TypePathKind::Array => 0, 572 | &TypePathKind::Nested => 1, 573 | &TypePathKind::Wildcard => 2, 574 | &TypePathKind::TypeArgument => 3, 575 | } 576 | } 577 | } 578 | 579 | #[derive(Debug)] 580 | pub struct BootstrapMethod { 581 | pub bootstrap_method_ref: ConstantPoolIndex, 582 | pub bootstrap_arguments: Vec 583 | } 584 | 585 | impl BootstrapMethod { 586 | } 587 | 588 | #[derive(Debug)] 589 | pub struct MethodParameter { 590 | pub name_index: ConstantPoolIndex, 591 | pub access_flags: AccessFlags 592 | } 593 | 594 | impl MethodParameter { 595 | pub fn len(&self) -> usize { 4 } 596 | } 597 | 598 | #[allow(non_camel_case_types)] 599 | #[derive(Debug)] 600 | pub enum Instruction { 601 | AALOAD, 602 | AASTORE, 603 | ACONST_NULL, 604 | ALOAD(u8), 605 | ALOAD_0, 606 | ALOAD_1, 607 | ALOAD_2, 608 | ALOAD_3, 609 | ANEWARRAY(u16), 610 | ARETURN, 611 | ARRAYLENGTH, 612 | ASTORE(u8), 613 | ASTORE_0, 614 | ASTORE_1, 615 | ASTORE_2, 616 | ASTORE_3, 617 | ATHROW, 618 | BALOAD, 619 | BASTORE, 620 | BIPUSH(u8), 621 | CALOAD, 622 | CASTORE, 623 | CHECKCAST(u16), 624 | D2F, 625 | D2I, 626 | D2L, 627 | DADD, 628 | DALOAD, 629 | DASTORE, 630 | DCMPL, 631 | DCMPG, 632 | DCONST_0, 633 | DCONST_1, 634 | DDIV, 635 | DLOAD(u8), 636 | DLOAD_0, 637 | DLOAD_1, 638 | DLOAD_2, 639 | DLOAD_3, 640 | DMUL, 641 | DNEG, 642 | DREM, 643 | DRETURN, 644 | DSTORE(u8), 645 | DSTORE_0, 646 | DSTORE_1, 647 | DSTORE_2, 648 | DSTORE_3, 649 | DSUB, 650 | DUP, 651 | DUP_X1, 652 | DUP_X2, 653 | DUP2, 654 | DUP2_X1, 655 | DUP2_X2, 656 | F2D, 657 | F2I, 658 | F2L, 659 | FADD, 660 | FALOAD, 661 | FASTORE, 662 | FCMPL, 663 | FCMPG, 664 | FCONST_0, 665 | FCONST_1, 666 | FCONST_2, 667 | FDIV, 668 | FLOAD(u8), 669 | FLOAD_0, 670 | FLOAD_1, 671 | FLOAD_2, 672 | FLOAD_3, 673 | FMUL, 674 | FNEG, 675 | FREM, 676 | FRETURN, 677 | FSTORE(u8), 678 | FSTORE_0, 679 | FSTORE_1, 680 | FSTORE_2, 681 | FSTORE_3, 682 | FSUB, 683 | GETFIELD(u16), 684 | GETSTATIC(u16), 685 | GOTO(i16), 686 | GOTO_W(i32), 687 | I2B, 688 | I2C, 689 | I2D, 690 | I2F, 691 | I2L, 692 | I2S, 693 | IADD, 694 | IALOAD, 695 | IAND, 696 | IASTORE, 697 | ICONST_M1, 698 | ICONST_0, 699 | ICONST_1, 700 | ICONST_2, 701 | ICONST_3, 702 | ICONST_4, 703 | ICONST_5, 704 | IDIV, 705 | IF_ACMPEQ(i16), 706 | IF_ACMPNE(i16), 707 | IF_ICMPEQ(i16), 708 | IF_ICMPNE(i16), 709 | IF_ICMPLT(i16), 710 | IF_ICMPGE(i16), 711 | IF_ICMPGT(i16), 712 | IF_ICMPLE(i16), 713 | IFEQ(i16), 714 | IFNE(i16), 715 | IFLT(i16), 716 | IFGE(i16), 717 | IFGT(i16), 718 | IFLE(i16), 719 | IFNONNULL(i16), 720 | IFNULL(i16), 721 | IINC(u8, i8), 722 | ILOAD(u8), 723 | ILOAD_0, 724 | ILOAD_1, 725 | ILOAD_2, 726 | ILOAD_3, 727 | IMUL, 728 | INEG, 729 | INSTANCEOF(u16), 730 | INVOKEDYNAMIC(u16), 731 | INVOKEINTERFACE(u16, u8), 732 | INVOKESPECIAL(u16), 733 | INVOKESTATIC(u16), 734 | INVOKEVIRTUAL(u16), 735 | IOR, 736 | IREM, 737 | IRETURN, 738 | ISHL, 739 | ISHR, 740 | ISTORE(u8), 741 | ISTORE_0, 742 | ISTORE_1, 743 | ISTORE_2, 744 | ISTORE_3, 745 | ISUB, 746 | IUSHR, 747 | IXOR, 748 | JSR(i16), 749 | JSR_W(i32), 750 | L2D, 751 | L2F, 752 | L2I, 753 | LADD, 754 | LALOAD, 755 | LAND, 756 | LASTORE, 757 | LCMP, 758 | LCONST_0, 759 | LCONST_1, 760 | LDC(u8), 761 | LDC_W(u16), 762 | LDC2_W(u16), 763 | LDIV, 764 | LLOAD(u8), 765 | LLOAD_0, 766 | LLOAD_1, 767 | LLOAD_2, 768 | LLOAD_3, 769 | LMUL, 770 | LNEG, 771 | LOOKUPSWITCH(i32, Vec<(i32, i32)>), 772 | LOR, 773 | LREM, 774 | LRETURN, 775 | LSHL, 776 | LSHR, 777 | LSTORE(u8), 778 | LSTORE_0, 779 | LSTORE_1, 780 | LSTORE_2, 781 | LSTORE_3, 782 | LSUB, 783 | LUSHR, 784 | LXOR, 785 | MONITORENTER, 786 | MONITOREXIT, 787 | MULTIANEWARRAY(u16, u8), 788 | NEW(u16), 789 | NEWARRAY(u8), 790 | NOP, 791 | POP, 792 | POP2, 793 | PUTFIELD(u16), 794 | PUTSTATIC(u16), 795 | RET(u8), 796 | RETURN, 797 | SALOAD, 798 | SASTORE, 799 | SIPUSH(u16), 800 | SWAP, 801 | TABLESWITCH(i32, i32, i32, Vec), 802 | IINC_W(u16, i16), 803 | ILOAD_W(u16), 804 | FLOAD_W(u16), 805 | ALOAD_W(u16), 806 | LLOAD_W(u16), 807 | DLOAD_W(u16), 808 | ISTORE_W(u16), 809 | FSTORE_W(u16), 810 | ASTORE_W(u16), 811 | LSTORE_W(u16), 812 | DSTORE_W(u16), 813 | RET_W(u16), 814 | PADDED_INSTRUCTION(usize), 815 | WTF(u32), 816 | } 817 | 818 | impl Instruction { 819 | pub fn len(&self) -> usize { 820 | match self { 821 | &Instruction::ALOAD(_) => 2, 822 | &Instruction::ANEWARRAY(_) => 3, 823 | &Instruction::ASTORE(_) => 2, 824 | &Instruction::CHECKCAST(_) => 3, 825 | &Instruction::DLOAD(_) => 2, 826 | &Instruction::DSTORE(_) => 2, 827 | &Instruction::FLOAD(_) => 2, 828 | &Instruction::FSTORE(_) => 2, 829 | &Instruction::GETFIELD(_) => 3, 830 | &Instruction::GETSTATIC(_) => 3, 831 | &Instruction::GOTO(_) => 3, 832 | &Instruction::GOTO_W(_) => 5, 833 | &Instruction::IF_ACMPEQ(_) => 3, 834 | &Instruction::IF_ACMPNE(_) => 3, 835 | &Instruction::IF_ICMPEQ(_) => 3, 836 | &Instruction::IF_ICMPNE(_) => 3, 837 | &Instruction::IF_ICMPLT(_) => 3, 838 | &Instruction::IF_ICMPGE(_) => 3, 839 | &Instruction::IF_ICMPGT(_) => 3, 840 | &Instruction::IF_ICMPLE(_) => 3, 841 | &Instruction::IFEQ(_) => 3, 842 | &Instruction::IFNE(_) => 3, 843 | &Instruction::IFLT(_) => 3, 844 | &Instruction::IFGE(_) => 3, 845 | &Instruction::IFGT(_) => 3, 846 | &Instruction::IFLE(_) => 3, 847 | &Instruction::IFNONNULL(_) => 3, 848 | &Instruction::IFNULL(_) => 3, 849 | &Instruction::IINC(_, _) => 3, 850 | &Instruction::ILOAD(_) => 2, 851 | &Instruction::INSTANCEOF(_) => 3, 852 | &Instruction::INVOKEDYNAMIC(_) => 3, 853 | &Instruction::INVOKEINTERFACE(_, _) => 4, 854 | &Instruction::INVOKESPECIAL(_) => 3, 855 | &Instruction::INVOKESTATIC(_) => 3, 856 | &Instruction::INVOKEVIRTUAL(_) => 3, 857 | &Instruction::ISTORE(_) => 2, 858 | &Instruction::JSR(_) => 3, 859 | &Instruction::JSR_W(_) => 5, 860 | &Instruction::LDC(_) => 2, 861 | &Instruction::LDC_W(_) => 3, 862 | &Instruction::LDC2_W(_) => 3, 863 | &Instruction::LLOAD(_) => 2, 864 | &Instruction::LOOKUPSWITCH(_, ref pairs) => { 5 + pairs.len() * 4 }, 865 | &Instruction::LSTORE(_) => 2, 866 | &Instruction::MULTIANEWARRAY(_, _) => 4, 867 | &Instruction::NEW(_) => 3, 868 | &Instruction::NEWARRAY(_) => 2, 869 | &Instruction::PUTFIELD(_) => 3, 870 | &Instruction::PUTSTATIC(_) => 3, 871 | &Instruction::RET(_) => 2, 872 | &Instruction::SIPUSH(_) => 3, 873 | &Instruction::TABLESWITCH(_, _, _, ref indices) => { 13 + (indices.len() * 4) }, 874 | &Instruction::IINC_W(_, _) => 9, 875 | &Instruction::ILOAD_W(_) => 5, 876 | &Instruction::FLOAD_W(_) => 5, 877 | &Instruction::ALOAD_W(_) => 5, 878 | &Instruction::LLOAD_W(_) => 5, 879 | &Instruction::DLOAD_W(_) => 5, 880 | &Instruction::ISTORE_W(_) => 5, 881 | &Instruction::FSTORE_W(_) => 5, 882 | &Instruction::ASTORE_W(_) => 5, 883 | &Instruction::LSTORE_W(_) => 5, 884 | &Instruction::DSTORE_W(_) => 5, 885 | &Instruction::RET_W(_) => 5, 886 | &Instruction::PADDED_INSTRUCTION(padding) => padding, 887 | _ => 1 888 | } 889 | } 890 | } 891 | -------------------------------------------------------------------------------- /src/bytecode/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::reader::*; 2 | pub use self::writer::*; 3 | 4 | pub mod reader; 5 | pub mod writer; 6 | -------------------------------------------------------------------------------- /src/bytecode/io/writer.rs: -------------------------------------------------------------------------------- 1 | use std::io::{ Write, Error, ErrorKind }; 2 | use super::super::classfile::*; 3 | 4 | pub struct ClassWriter<'a> { 5 | target: &'a mut Write 6 | } 7 | 8 | impl<'a> ClassWriter<'a> { 9 | pub fn new(target: &'a mut T) -> ClassWriter where T: Write { 10 | ClassWriter { target: target } 11 | } 12 | 13 | pub fn write_class(&mut self, classfile: &Classfile) -> Result { 14 | self.write_magic_bytes() 15 | .and(self.write_classfile_version(&classfile.version)) 16 | .and(self.write_constant_pool(&classfile.constant_pool)) 17 | .and(self.write_access_flags(&classfile.access_flags)) 18 | .and(self.write_constant_pool_index(&classfile.this_class)) 19 | .and(self.write_constant_pool_index(&classfile.super_class)) 20 | .and(self.write_interfaces(&classfile.interfaces)) 21 | .and(self.write_fields(&classfile.fields, &classfile.constant_pool)) 22 | .and(self.write_methods(&classfile.methods, &classfile.constant_pool)) 23 | .and(self.write_attributes(&classfile.attributes, &classfile.constant_pool)) 24 | } 25 | 26 | pub fn write_magic_bytes(&mut self) -> Result { 27 | self.write_u32(0xCAFEBABE) 28 | } 29 | 30 | pub fn write_classfile_version(&mut self, version: &ClassfileVersion) -> Result { 31 | self.write_u16(version.minor_version) 32 | .and(self.write_u16(version.major_version)) 33 | } 34 | 35 | pub fn write_constant_pool(&mut self, cp: &ConstantPool) -> Result { 36 | cp.constants.iter().fold(self.write_u16(cp.cp_len() as u16), |acc, x| { 37 | match acc { 38 | Ok(ctr) => self.write_constant(x).map(|c| c + ctr), 39 | err@_ => err 40 | } 41 | }) 42 | } 43 | 44 | fn write_constant(&mut self, constant: &Constant) -> Result { 45 | match constant { 46 | &Constant::Utf8(ref bytes) => self.write_u8(1).and(self.write_u16(bytes.len() as u16)).and(self.write_n(bytes)), 47 | &Constant::Integer(ref value) => self.write_u8(3).and(self.write_u32(*value)), 48 | &Constant::Float(ref value) => self.write_u8(4).and(self.write_u32(*value)), 49 | &Constant::Long(ref value) => self.write_u8(5).and(self.write_u64(*value)), 50 | &Constant::Double(ref value) => self.write_u8(6).and(self.write_u64(*value)), 51 | &Constant::Class(ref idx) => self.write_u8(7).and(self.write_u16(idx.idx as u16)), 52 | &Constant::String(ref idx) => self.write_u8(8).and(self.write_u16(idx.idx as u16)), 53 | &Constant::MethodType(ref idx) => self.write_u8(16).and(self.write_u16(idx.idx as u16)), 54 | &Constant::FieldRef { class_index: ref c_idx, name_and_type_index: ref n_idx } => self.write_u8(9).and(self.write_u16(c_idx.idx as u16)).and(self.write_u16(n_idx.idx as u16)), 55 | &Constant::MethodRef { class_index: ref c_idx, name_and_type_index: ref n_idx } => self.write_u8(10).and(self.write_u16(c_idx.idx as u16)).and(self.write_u16(n_idx.idx as u16)), 56 | &Constant::InterfaceMethodRef { class_index: ref c_idx, name_and_type_index: ref n_idx } => self.write_u8(11).and(self.write_u16(c_idx.idx as u16)).and(self.write_u16(n_idx.idx as u16)), 57 | &Constant::NameAndType { name_index: ref n_idx, descriptor_index: ref d_idx } => self.write_u8(12).and(self.write_u16(n_idx.idx as u16)).and(self.write_u16(d_idx.idx as u16)), 58 | &Constant::MethodHandle { reference_kind: ref kind, reference_index: ref r_idx } => self.write_u8(15).and(self.write_u8(kind.to_u8())).and(self.write_u16(r_idx.idx as u16)), 59 | &Constant::InvokeDynamic { bootstrap_method_attr_index: ref m_idx, name_and_type_index: ref n_idx } => self.write_u8(18).and(self.write_u16(m_idx.idx as u16)).and(self.write_u16(n_idx.idx as u16)), 60 | &Constant::Placeholder => Ok(0), 61 | _ => Err(Error::new(ErrorKind::InvalidData, "Unknown constant detected")) 62 | } 63 | } 64 | 65 | fn write_access_flags(&mut self, flags: &AccessFlags) -> Result { 66 | self.write_u16(flags.flags) 67 | } 68 | 69 | fn write_constant_pool_index(&mut self, class_index: &ConstantPoolIndex) -> Result { 70 | self.write_u16(class_index.idx as u16) 71 | } 72 | 73 | fn write_interfaces(&mut self, ifs: &Vec) -> Result { 74 | ifs.iter().fold(self.write_u16(ifs.len() as u16), |acc, x| { 75 | match acc { 76 | Ok(ctr) => self.write_u16(x.idx as u16).map(|c| c + ctr), 77 | err@_ => err 78 | } 79 | }) 80 | } 81 | 82 | fn write_fields(&mut self, fields: &Vec, cp: &ConstantPool) -> Result { 83 | fields.iter().fold(self.write_u16(fields.len() as u16), |acc, x| { 84 | match acc { 85 | Ok(ctr) => self.write_field(x, cp).map(|c| c + ctr), 86 | err@_ => err 87 | } 88 | }) 89 | } 90 | 91 | fn write_field(&mut self, field: &Field, cp: &ConstantPool) -> Result { 92 | self.write_access_flags(&field.access_flags) 93 | .and(self.write_constant_pool_index(&field.name_index)) 94 | .and(self.write_constant_pool_index(&field.descriptor_index)) 95 | .and(self.write_attributes(&field.attributes, cp)) 96 | } 97 | 98 | fn write_methods(&mut self, methods: &Vec, cp: &ConstantPool) -> Result { 99 | methods.iter().fold(self.write_u16(methods.len() as u16), |acc, x| { 100 | match acc { 101 | Ok(ctr) => self.write_method(x, cp).map(|c| c + ctr), 102 | err@_ => err 103 | } 104 | }) 105 | } 106 | 107 | fn write_method(&mut self, method: &Method, cp: &ConstantPool) -> Result { 108 | self.write_access_flags(&method.access_flags) 109 | .and(self.write_constant_pool_index(&method.name_index)) 110 | .and(self.write_constant_pool_index(&method.descriptor_index)) 111 | .and(self.write_attributes(&method.attributes, cp)) 112 | } 113 | 114 | fn write_attributes(&mut self, attributes: &Vec, cp: &ConstantPool) -> Result { 115 | attributes.iter().fold(self.write_u16(attributes.len() as u16), |acc, x| { 116 | match acc { 117 | Ok(ctr) => self.write_attribute(x, cp).map(|c| c + ctr), 118 | err@_ => err 119 | } 120 | }) 121 | } 122 | 123 | fn write_attribute(&mut self, attribute: &Attribute, cp: &ConstantPool) -> Result { 124 | match attribute { 125 | &Attribute::RawAttribute { name_index: ref n_idx, info: ref bytes } => self.write_u16(n_idx.idx as u16).and(self.write_u32(bytes.len() as u32)).and(self.write_n(bytes)), 126 | &Attribute::ConstantValue(ref idx) => self.write_u16(cp.get_utf8_index("ConstantValue") as u16).and(self.write_u32(2)).and(self.write_u16(idx.idx as u16)), 127 | &Attribute::Code { max_stack, max_locals, ref code, ref exception_table, ref attributes } => { 128 | let mut target: Vec = vec![]; 129 | 130 | { 131 | let mut code_writer = ClassWriter::new(&mut target); 132 | 133 | let _ = code_writer.write_u16(max_stack) 134 | .and(code_writer.write_u16(max_locals)) 135 | .and(code_writer.write_instructions(code)) 136 | .and(code_writer.write_exception_handlers(exception_table)) 137 | .and(code_writer.write_attributes(attributes, cp)); 138 | } 139 | 140 | self.write_u16(cp.get_utf8_index("Code") as u16) 141 | .and(self.write_u32(target.len() as u32)) 142 | .and(self.write_n(&target)) 143 | }, 144 | &Attribute::StackMapTable(ref table) => self.write_stack_map_table(table, cp), 145 | &Attribute::Exceptions(ref table) => self.write_u16(cp.get_utf8_index("Exceptions") as u16).and(self.write_u32(2 + (table.len() as u32) * 2)).and(self.write_u16(table.len() as u16)).and(table.iter().fold(Ok(0), |_, x| self.write_u16(x.idx as u16))), 146 | &Attribute::InnerClasses(ref table) => self.write_u16(cp.get_utf8_index("InnerClasses") as u16).and(self.write_u32(2 + (table.len() as u32) * 8)).and(self.write_u16(table.len() as u16)).and(table.iter().fold(Ok(0), |_, x| { 147 | self.write_u16(x.inner_class_info_index.idx as u16) 148 | .and(self.write_u16(x.outer_class_info_index.idx as u16)) 149 | .and(self.write_u16(x.inner_name_index.idx as u16)) 150 | .and(self.write_u16(x.access_flags.flags)) 151 | })), 152 | &Attribute::EnclosingMethod { ref class_index, ref method_index } => self.write_u16(cp.get_utf8_index("EnclosingMethod") as u16).and(self.write_u32(4)).and(self.write_u16(class_index.idx as u16)).and(self.write_u16(method_index.idx as u16)), 153 | &Attribute::Synthetic => self.write_u16(cp.get_utf8_index("Synthetic") as u16).and(self.write_u32(0)), 154 | &Attribute::Signature(ref idx) => self.write_u16(cp.get_utf8_index("Signature") as u16).and(self.write_u32(2)).and(self.write_u16(idx.idx as u16)), 155 | &Attribute::SourceFile(ref idx) => self.write_u16(cp.get_utf8_index("SourceFile") as u16).and(self.write_u32(2)).and(self.write_u16(idx.idx as u16)), 156 | &Attribute::SourceDebugExtension(ref vec) => self.write_u16(cp.get_utf8_index("SourceDebugExtension") as u16).and(self.write_u32(vec.len() as u32)).and(self.write_n(vec)), 157 | &Attribute::LineNumberTable(ref table) => self.write_u16(cp.get_utf8_index("LineNumberTable") as u16).and(self.write_u32(2 + table.len() as u32 * 4)).and(self.write_u16(table.len() as u16)).and(table.iter().fold(Ok(0), |_, x| { 158 | self.write_u16(x.start_pc).and(self.write_u16(x.line_number)) 159 | })), 160 | &Attribute::LocalVariableTable(ref table) => self.write_u16(cp.get_utf8_index("LocalVariableTable") as u16).and(self.write_u32(2 + table.len() as u32 * 10)).and(self.write_u16(table.len() as u16)).and(table.iter().fold(Ok(0), |_, x| { 161 | self.write_u16(x.start_pc) 162 | .and(self.write_u16(x.length)) 163 | .and(self.write_u16(x.name_index.idx as u16)) 164 | .and(self.write_u16(x.descriptor_index.idx as u16)) 165 | .and(self.write_u16(x.index)) 166 | })), 167 | &Attribute::LocalVariableTypeTable(ref table) => self.write_u16(cp.get_utf8_index("LocalVariableTypeTable") as u16).and(self.write_u32(2 + table.len() as u32 * 10)).and(self.write_u16(table.len() as u16)).and(table.iter().fold(Ok(0), |_, x| { 168 | self.write_u16(x.start_pc) 169 | .and(self.write_u16(x.length)) 170 | .and(self.write_u16(x.name_index.idx as u16)) 171 | .and(self.write_u16(x.signature_index.idx as u16)) 172 | .and(self.write_u16(x.index)) 173 | })), 174 | &Attribute::Deprecated => self.write_u16(cp.get_utf8_index("Deprecated") as u16).and(self.write_u32(0)), 175 | &Attribute::RuntimeVisibleAnnotations(ref table) => { 176 | self.write_u16(cp.get_utf8_index("RuntimeVisibleAnnotations") as u16) 177 | // attribute_length 178 | .and(self.write_u32(table.iter().fold(2, |acc, x| acc + x.len() as u32))) 179 | // num_annotations 180 | .and(self.write_u16(table.len() as u16)) 181 | // annotations 182 | .and(table.iter().fold(Ok(0), |_, x| self.write_annotation(x, cp))) 183 | }, 184 | &Attribute::RuntimeInvisibleAnnotations(ref table) => { 185 | self.write_u16(cp.get_utf8_index("RuntimeInvisibleAnnotations") as u16) 186 | // attribute_length 187 | .and(self.write_u32(table.iter().fold(2, |acc, x| acc + x.len() as u32))) 188 | // num_annotations 189 | .and(self.write_u16(table.len() as u16)) 190 | // annotations 191 | .and(table.iter().fold(Ok(0), |_, x| self.write_annotation(x, cp))) 192 | }, 193 | &Attribute::RuntimeVisibleParameterAnnotations(ref table) => { 194 | self.write_u16(cp.get_utf8_index("RuntimeVisibleParameterAnnotations") as u16) 195 | // attribute_length 196 | .and(self.write_u32(table.iter().fold(1, |acc, x| acc + x.iter().fold(2, |acc2, x2| acc2 + x2.len()) as u32))) 197 | // num_parameters 198 | .and(self.write_u8(table.len() as u8)) 199 | // parameter_annotations 200 | .and(table.iter().fold(Ok(0), |_, ann_table| self.write_u16(ann_table.len() as u16).and(ann_table.iter().fold(Ok(0), |_, ann| self.write_annotation(ann, cp))))) 201 | }, 202 | &Attribute::RuntimeInvisibleParameterAnnotations(ref table) => { 203 | self.write_u16(cp.get_utf8_index("RuntimeInvisibleParameterAnnotations") as u16) 204 | // attribute_length 205 | .and(self.write_u32(table.iter().fold(1, |acc, x| acc + x.iter().fold(2, |acc2, x2| acc2 + x2.len()) as u32))) 206 | // num_parameters 207 | .and(self.write_u8(table.len() as u8)) 208 | // parameter_annotations 209 | .and(table.iter().fold(Ok(0), |_, ann_table| self.write_u16(ann_table.len() as u16).and(ann_table.iter().fold(Ok(0), |_, ann| self.write_annotation(ann, cp))))) 210 | }, 211 | &Attribute::RuntimeVisibleTypeAnnotations(ref table) => { 212 | self.write_u16(cp.get_utf8_index("RuntimeVisibleTypeAnnotations") as u16) 213 | // attribute_length 214 | .and(self.write_u32(table.iter().fold(2, |acc, x| acc + x.len() as u32))) 215 | // num_annotations 216 | .and(self.write_u16(table.len() as u16)) 217 | // annotations 218 | .and(table.iter().fold(Ok(0), |_, x| self.write_type_annotation(x, cp))) 219 | }, 220 | &Attribute::RuntimeInvisibleTypeAnnotations(ref table) => { 221 | self.write_u16(cp.get_utf8_index("RuntimeInvisibleTypeAnnotations") as u16) 222 | // attribute_length 223 | .and(self.write_u32(table.iter().fold(2, |acc, x| acc + x.len() as u32))) 224 | // num_annotations 225 | .and(self.write_u16(table.len() as u16)) 226 | // annotations 227 | .and(table.iter().fold(Ok(0), |_, x| self.write_type_annotation(x, cp))) 228 | }, 229 | &Attribute::AnnotationDefault(ref value) => { 230 | self.write_u16(cp.get_utf8_index("AnnotationDefault") as u16) 231 | .and(self.write_u32(value.len() as u32)) 232 | .and(self.write_element_value(value, cp)) 233 | }, 234 | &Attribute::BootstrapMethods(ref table) => { 235 | self.write_u16(cp.get_utf8_index("BootstrapMethods") as u16) 236 | // attribute_length 237 | .and(self.write_u32(table.iter().fold(2, |acc, method| acc + 4 + method.bootstrap_arguments.len() as u32 * 2))) 238 | // num_bootstrap_methods 239 | .and(self.write_u16(table.len() as u16)) 240 | // bootstrap_methods 241 | .and(table.iter().fold(Ok(0), |_, method| { 242 | // bootstrap_method_ref 243 | self.write_u16(method.bootstrap_method_ref.idx as u16) 244 | // num_bootstrap_arguments 245 | .and(self.write_u16(method.bootstrap_arguments.len() as u16)) 246 | // bootstrap_arguments 247 | .and(method.bootstrap_arguments.iter().fold(Ok(0), |_, arg| self.write_u16(arg.idx as u16))) 248 | })) 249 | }, 250 | &Attribute::MethodParameters(ref table) => { 251 | self.write_u16(cp.get_utf8_index("MethodParameters") as u16) 252 | .and(self.write_u32(1 + table.len() as u32 * 4)) 253 | .and(table.iter().fold(Ok(0), |_, p| self.write_u16(p.name_index.idx as u16).and(self.write_u16(p.access_flags.flags as u16)))) 254 | } 255 | } 256 | } 257 | 258 | fn write_stack_map_table(&mut self, table: &Vec, cp: &ConstantPool) -> Result { 259 | // attribute_name_index 260 | self.write_u16(cp.get_utf8_index("StackMapTable") as u16) 261 | // attribute_length = number_of_entries length (2) + sum of entries' length 262 | .and(self.write_u32(2 + table.iter().map(|st| st.len()).fold(0, |acc, x| acc + x) as u32)) 263 | // number_of_entries 264 | .and(self.write_u16(table.len() as u16)) 265 | // entries 266 | .and(table.iter().fold(Ok(0), |_, x| { 267 | match x { 268 | &StackMapFrame::SameFrame { tag } => self.write_u8(tag), 269 | &StackMapFrame::SameLocals1StackItemFrame{ tag, ref stack } => self.write_u8(tag).and(self.write_verification_type(stack)), 270 | &StackMapFrame::SameLocals1StackItemFrameExtended { offset_delta, ref stack } => self.write_u8(247).and(self.write_u16(offset_delta)).and(self.write_verification_type(stack)), 271 | &StackMapFrame::ChopFrame { tag, offset_delta } => self.write_u8(tag).and(self.write_u16(offset_delta)), 272 | &StackMapFrame::SameFrameExtended { offset_delta } => self.write_u8(251).and(self.write_u16(offset_delta)), 273 | &StackMapFrame::AppendFrame { tag, offset_delta, ref locals } => self.write_u8(tag).and(self.write_u16(offset_delta)).and(locals.iter().fold(Ok(0), |_, x| self.write_verification_type(x))), 274 | &StackMapFrame::FullFrame { offset_delta, ref locals, ref stack } => { 275 | // full frame tag 276 | self.write_u8(255) 277 | // offset_delta 278 | .and(self.write_u16(offset_delta)) 279 | // number_of_locals 280 | .and(self.write_u16(locals.len() as u16)) 281 | // locals 282 | .and(locals.iter().fold(Ok(0), |_, x| self.write_verification_type(x))) 283 | // number_of_stack_items 284 | .and(self.write_u16(stack.len() as u16)) 285 | // stack 286 | .and(stack.iter().fold(Ok(0), |_, x| self.write_verification_type(x))) 287 | }, 288 | &StackMapFrame::FutureUse { tag } => self.write_u8(tag) 289 | } 290 | })) 291 | } 292 | 293 | fn write_verification_type(&mut self, info: &VerificationType) -> Result { 294 | match info { 295 | &VerificationType::Top => self.write_u8(0), 296 | &VerificationType::Integer => self.write_u8(1), 297 | &VerificationType::Float => self.write_u8(2), 298 | &VerificationType::Long => self.write_u8(4), 299 | &VerificationType::Double => self.write_u8(3), 300 | &VerificationType::Null => self.write_u8(5), 301 | &VerificationType::UninitializedThis => self.write_u8(6), 302 | &VerificationType::Object { ref cpool_index } => self.write_u8(7).and(self.write_u16(cpool_index.idx as u16)), 303 | &VerificationType::Uninitialized { offset } => self.write_u8(8).and(self.write_u16(offset)) 304 | } 305 | } 306 | 307 | fn write_element_value(&mut self, element_value: &ElementValue, cp: &ConstantPool) -> Result { 308 | match element_value { 309 | &ElementValue::ConstantValue(tag, ref idx) => self.write_u8(tag).and(self.write_u16(idx.idx as u16)), 310 | &ElementValue::Enum { ref type_name_index, ref const_name_index } => self.write_u8(101).and(self.write_u16(type_name_index.idx as u16)).and(self.write_u16(const_name_index.idx as u16)), 311 | &ElementValue::ClassInfo(ref idx) => self.write_u8(99).and(self.write_u16(idx.idx as u16)), 312 | &ElementValue::Annotation(ref annotation) => self.write_u8(64).and(self.write_annotation(annotation, cp)), 313 | &ElementValue::Array(ref table) => self.write_u8(91).and(self.write_u16(table.len() as u16)).and(table.iter().fold(Ok(0), |_, x| { self.write_element_value(x, cp) })) 314 | } 315 | } 316 | 317 | fn write_element_value_pair(&mut self, pair: &ElementValuePair, cp: &ConstantPool) -> Result { 318 | self.write_u16(pair.element_name_index.idx as u16).and(self.write_element_value(&pair.value, cp)) 319 | } 320 | 321 | fn write_annotation(&mut self, annotation: &Annotation, cp: &ConstantPool) -> Result { 322 | // type_index 323 | self.write_u16(annotation.type_index.idx as u16) 324 | // num_element_value_pairs 325 | .and(self.write_u16(annotation.element_value_pairs.len() as u16)) 326 | // element_value_pairs 327 | .and(annotation.element_value_pairs.iter().fold(Ok(0), |_, x| self.write_element_value_pair(x, cp))) 328 | } 329 | 330 | fn write_type_annotation(&mut self, annotation: &TypeAnnotation, cp: &ConstantPool) -> Result { 331 | // target_type 332 | self.write_u8(annotation.target_info.subtype()) 333 | // target_info 334 | .and({ 335 | match &annotation.target_info { 336 | &TargetInfo::TypeParameter { subtype: _, idx } => self.write_u8(idx), 337 | &TargetInfo::SuperType { idx } => self.write_u16(idx), 338 | &TargetInfo::TypeParameterBound { subtype: _, param_idx, bound_index } => self.write_u8(param_idx).and(self.write_u8(bound_index)), 339 | &TargetInfo::Empty { subtype: _ } => Ok(0), 340 | &TargetInfo::MethodFormalParameter { idx } => self.write_u8(idx), 341 | &TargetInfo::Throws { idx } => self.write_u16(idx), 342 | &TargetInfo::LocalVar { subtype: _, ref target } => self.write_u16(target.len() as u16).and(target.iter().fold(Ok(0), |_, x| self.write_u16(x.0).and(self.write_u16(x.1)).and(self.write_u16(x.2)))), 343 | &TargetInfo::Catch { idx } => self.write_u16(idx), 344 | &TargetInfo::Offset { subtype: _, idx } => self.write_u16(idx), 345 | &TargetInfo::TypeArgument { subtype: _, offset, type_arg_idx } => self.write_u16(offset).and(self.write_u8(type_arg_idx)) 346 | } 347 | }) 348 | .and({ 349 | // path_length 350 | self.write_u8(annotation.target_path.path.len() as u8) 351 | // path 352 | .and(annotation.target_path.path.iter().fold(Ok(0), |_, x| self.write_u8(x.0.value()).and(self.write_u8(x.1)))) 353 | }) 354 | .and(self.write_u16(annotation.type_index.idx as u16)) 355 | .and(self.write_u16(annotation.element_value_pairs.len() as u16)) 356 | .and(annotation.element_value_pairs.iter().fold(Ok(0), |_, x| self.write_element_value_pair(x, cp))) 357 | } 358 | 359 | fn write_instructions(&mut self, instructions: &Vec) -> Result { 360 | let mut target: Vec = vec![]; 361 | let _ /*written_bytes*/ = { 362 | let mut instr_writer = ClassWriter::new(&mut target); 363 | 364 | instructions.iter().fold(0 as usize, |counter, instr| { 365 | counter + instr_writer.render_instruction(instr, counter) 366 | }) 367 | }; 368 | 369 | self.write_u32(target.len() as u32).and_then(|x| self.write_n(&target).map(|y| x + y)) 370 | } 371 | 372 | /// Renders a single instruction into the output stream 373 | fn render_instruction(&mut self, instruction: &Instruction, offset: usize) -> usize { 374 | match instruction { 375 | &Instruction::AALOAD => self.write_u8(0x32), 376 | &Instruction::AASTORE => self.write_u8(0x53), 377 | &Instruction::ACONST_NULL => self.write_u8(0x01), 378 | &Instruction::ALOAD(value) => self.write_u8(0x19).and(self.write_u8(value)).and(Ok(2)), 379 | &Instruction::ALOAD_0 => self.write_u8(0x2a), 380 | &Instruction::ALOAD_1 => self.write_u8(0x2b), 381 | &Instruction::ALOAD_2 => self.write_u8(0x2c), 382 | &Instruction::ALOAD_3 => self.write_u8(0x2d), 383 | &Instruction::ANEWARRAY(b) => self.write_u8(0xbd).and(self.write_u16(b)).and(Ok(3)), 384 | &Instruction::ARETURN => self.write_u8(0xb0), 385 | &Instruction::ARRAYLENGTH => self.write_u8(0xbe), 386 | &Instruction::ASTORE(value) => self.write_u8(0x3a).and(self.write_u8(value)).and(Ok(2)), 387 | &Instruction::ASTORE_0 => self.write_u8(0x4b), 388 | &Instruction::ASTORE_1 => self.write_u8(0x4c), 389 | &Instruction::ASTORE_2 => self.write_u8(0x4d), 390 | &Instruction::ASTORE_3 => self.write_u8(0x4e), 391 | &Instruction::ATHROW => self.write_u8(0xbf), 392 | &Instruction::BALOAD => self.write_u8(0x33), 393 | &Instruction::BASTORE => self.write_u8(0x54), 394 | &Instruction::BIPUSH(value) => self.write_u8(0x10).and(self.write_u8(value)).and(Ok(2)), 395 | &Instruction::CALOAD => self.write_u8(0x34), 396 | &Instruction::CASTORE => self.write_u8(0x55), 397 | &Instruction::CHECKCAST(value) => self.write_u8(0xc0).and(self.write_u16(value)).and(Ok(3)), 398 | &Instruction::D2F => self.write_u8(0x90), 399 | &Instruction::D2I => self.write_u8(0x8e), 400 | &Instruction::D2L => self.write_u8(0x8f), 401 | &Instruction::DADD => self.write_u8(0x63), 402 | &Instruction::DALOAD => self.write_u8(0x31), 403 | &Instruction::DASTORE => self.write_u8(0x52), 404 | &Instruction::DCMPL => self.write_u8(0x97), 405 | &Instruction::DCMPG => self.write_u8(0x98), 406 | &Instruction::DCONST_0 => self.write_u8(0x0e), 407 | &Instruction::DCONST_1 => self.write_u8(0x0f), 408 | &Instruction::DDIV => self.write_u8(0x6f), 409 | &Instruction::DLOAD(value) => self.write_u8(0x18).and(self.write_u8(value)).and(Ok(2)), 410 | &Instruction::DLOAD_0 => self.write_u8(0x26), 411 | &Instruction::DLOAD_1 => self.write_u8(0x27), 412 | &Instruction::DLOAD_2 => self.write_u8(0x28), 413 | &Instruction::DLOAD_3 => self.write_u8(0x29), 414 | &Instruction::DMUL => self.write_u8(0x6b), 415 | &Instruction::DNEG => self.write_u8(0x77), 416 | &Instruction::DREM => self.write_u8(0x73), 417 | &Instruction::DRETURN => self.write_u8(0xaf), 418 | &Instruction::DSTORE(value) => self.write_u8(0x39).and(self.write_u8(value)).and(Ok(2)), 419 | &Instruction::DSTORE_0 => self.write_u8(0x47), 420 | &Instruction::DSTORE_1 => self.write_u8(0x48), 421 | &Instruction::DSTORE_2 => self.write_u8(0x49), 422 | &Instruction::DSTORE_3 => self.write_u8(0x4a), 423 | &Instruction::DSUB => self.write_u8(0x67), 424 | &Instruction::DUP => self.write_u8(0x59), 425 | &Instruction::DUP_X1 => self.write_u8(0x5a), 426 | &Instruction::DUP_X2 => self.write_u8(0x5b), 427 | &Instruction::DUP2 => self.write_u8(0x5c), 428 | &Instruction::DUP2_X1 => self.write_u8(0x5d), 429 | &Instruction::DUP2_X2 => self.write_u8(0x5e), 430 | &Instruction::F2D => self.write_u8(0x8d), 431 | &Instruction::F2I => self.write_u8(0x8b), 432 | &Instruction::F2L => self.write_u8(0x8c), 433 | &Instruction::FADD => self.write_u8(0x62), 434 | &Instruction::FALOAD => self.write_u8(0x30), 435 | &Instruction::FASTORE => self.write_u8(0x51), 436 | &Instruction::FCMPL => self.write_u8(0x95), 437 | &Instruction::FCMPG => self.write_u8(0x96), 438 | &Instruction::FCONST_0 => self.write_u8(0x0b), 439 | &Instruction::FCONST_1 => self.write_u8(0x0c), 440 | &Instruction::FCONST_2 => self.write_u8(0x0d), 441 | &Instruction::FDIV => self.write_u8(0x6e), 442 | &Instruction::FLOAD(value) => self.write_u8(0x17).and(self.write_u8(value)).and(Ok(2)), 443 | &Instruction::FLOAD_0 => self.write_u8(0x22), 444 | &Instruction::FLOAD_1 => self.write_u8(0x23), 445 | &Instruction::FLOAD_2 => self.write_u8(0x24), 446 | &Instruction::FLOAD_3 => self.write_u8(0x25), 447 | &Instruction::FMUL => self.write_u8(0x6a), 448 | &Instruction::FNEG => self.write_u8(0x76), 449 | &Instruction::FREM => self.write_u8(0x72), 450 | &Instruction::FRETURN => self.write_u8(0xae), 451 | &Instruction::FSTORE(value) => self.write_u8(0x38).and(self.write_u8(value)).and(Ok(2)), 452 | &Instruction::FSTORE_0 => self.write_u8(0x43), 453 | &Instruction::FSTORE_1 => self.write_u8(0x44), 454 | &Instruction::FSTORE_2 => self.write_u8(0x45), 455 | &Instruction::FSTORE_3 => self.write_u8(0x46), 456 | &Instruction::FSUB => self.write_u8(0x66), 457 | &Instruction::GETFIELD(value) => self.write_u8(0xb4).and(self.write_u16(value)).and(Ok(3)), 458 | &Instruction::GETSTATIC(value) => self.write_u8(0xb2).and(self.write_u16(value)).and(Ok(3)), 459 | &Instruction::GOTO(value) => self.write_u8(0xa7).and(self.write_u16(value as u16)).and(Ok(3)), 460 | &Instruction::GOTO_W(value) => self.write_u8(0xc8).and(self.write_u32(value as u32)).and(Ok(5)), 461 | &Instruction::I2B => self.write_u8(0x91), 462 | &Instruction::I2C => self.write_u8(0x92), 463 | &Instruction::I2D => self.write_u8(0x87), 464 | &Instruction::I2F => self.write_u8(0x86), 465 | &Instruction::I2L => self.write_u8(0x85), 466 | &Instruction::I2S => self.write_u8(0x93), 467 | &Instruction::IADD => self.write_u8(0x60), 468 | &Instruction::IALOAD => self.write_u8(0x2e), 469 | &Instruction::IAND => self.write_u8(0x7e), 470 | &Instruction::IASTORE => self.write_u8(0x4f), 471 | &Instruction::ICONST_M1 => self.write_u8(0x02), 472 | &Instruction::ICONST_0 => self.write_u8(0x03), 473 | &Instruction::ICONST_1 => self.write_u8(0x04), 474 | &Instruction::ICONST_2 => self.write_u8(0x05), 475 | &Instruction::ICONST_3 => self.write_u8(0x06), 476 | &Instruction::ICONST_4 => self.write_u8(0x07), 477 | &Instruction::ICONST_5 => self.write_u8(0x08), 478 | &Instruction::IDIV => self.write_u8(0x6c), 479 | &Instruction::IF_ACMPEQ(value) => self.write_u8(0xa5).and(self.write_u16(value as u16)).and(Ok(3)), 480 | &Instruction::IF_ACMPNE(value) => self.write_u8(0xa6).and(self.write_u16(value as u16)).and(Ok(3)), 481 | &Instruction::IF_ICMPEQ(value) => self.write_u8(0x9f).and(self.write_u16(value as u16)).and(Ok(3)), 482 | &Instruction::IF_ICMPNE(value) => self.write_u8(0xa0).and(self.write_u16(value as u16)).and(Ok(3)), 483 | &Instruction::IF_ICMPLT(value) => self.write_u8(0xa1).and(self.write_u16(value as u16)).and(Ok(3)), 484 | &Instruction::IF_ICMPGE(value) => self.write_u8(0xa2).and(self.write_u16(value as u16)).and(Ok(3)), 485 | &Instruction::IF_ICMPGT(value) => self.write_u8(0xa3).and(self.write_u16(value as u16)).and(Ok(3)), 486 | &Instruction::IF_ICMPLE(value) => self.write_u8(0xa4).and(self.write_u16(value as u16)).and(Ok(3)), 487 | &Instruction::IFEQ(value) => self.write_u8(0x99).and(self.write_u16(value as u16)).and(Ok(3)), 488 | &Instruction::IFNE(value) => self.write_u8(0x9a).and(self.write_u16(value as u16)).and(Ok(3)), 489 | &Instruction::IFLT(value) => self.write_u8(0x9b).and(self.write_u16(value as u16)).and(Ok(3)), 490 | &Instruction::IFGE(value) => self.write_u8(0x9c).and(self.write_u16(value as u16)).and(Ok(3)), 491 | &Instruction::IFGT(value) => self.write_u8(0x9d).and(self.write_u16(value as u16)).and(Ok(3)), 492 | &Instruction::IFLE(value) => self.write_u8(0x9e).and(self.write_u16(value as u16)).and(Ok(3)), 493 | &Instruction::IFNONNULL(value) => self.write_u8(0xc7).and(self.write_u16(value as u16)).and(Ok(3)), 494 | &Instruction::IFNULL(value) => self.write_u8(0xc6).and(self.write_u16(value as u16)).and(Ok(3)), 495 | &Instruction::IINC(a, b) => self.write_u8(0x84).and(self.write_u8(a)).and(self.write_u8(b as u8)).and(Ok(3)), 496 | &Instruction::ILOAD(value) => self.write_u8(0x15).and(self.write_u8(value)).and(Ok(2)), 497 | &Instruction::ILOAD_0 => self.write_u8(0x1a), 498 | &Instruction::ILOAD_1 => self.write_u8(0x1b), 499 | &Instruction::ILOAD_2 => self.write_u8(0x1c), 500 | &Instruction::ILOAD_3 => self.write_u8(0x1d), 501 | &Instruction::IMUL => self.write_u8(0x68), 502 | &Instruction::INEG => self.write_u8(0x74), 503 | &Instruction::INSTANCEOF(value) => self.write_u8(0xc1).and(self.write_u16(value)).and(Ok(3)), 504 | &Instruction::INVOKEDYNAMIC(value) => self.write_u8(0xba).and(self.write_u16(value)).and(self.write_u16(0)).and(Ok(5)), 505 | &Instruction::INVOKEINTERFACE(a, b) => self.write_u8(0xb9).and(self.write_u16(a)).and(self.write_u8(b)).and(self.write_u8(0)).and(Ok(5)), 506 | &Instruction::INVOKESPECIAL(value) => self.write_u8(0xb7).and(self.write_u16(value)).and(Ok(3)), 507 | &Instruction::INVOKESTATIC(value) => self.write_u8(0xb8).and(self.write_u16(value)).and(Ok(3)), 508 | &Instruction::INVOKEVIRTUAL(value) => self.write_u8(0xb6).and(self.write_u16(value)).and(Ok(3)), 509 | &Instruction::IOR => self.write_u8(0x80), 510 | &Instruction::IREM => self.write_u8(0x70), 511 | &Instruction::IRETURN => self.write_u8(0xac), 512 | &Instruction::ISHL => self.write_u8(0x78), 513 | &Instruction::ISHR => self.write_u8(0x7a), 514 | &Instruction::ISTORE(value) => self.write_u8(0x36).and(self.write_u8(value)).and(Ok(2)), 515 | &Instruction::ISTORE_0 => self.write_u8(0x3b), 516 | &Instruction::ISTORE_1 => self.write_u8(0x3c), 517 | &Instruction::ISTORE_2 => self.write_u8(0x3d), 518 | &Instruction::ISTORE_3 => self.write_u8(0x3e), 519 | &Instruction::ISUB => self.write_u8(0x64), 520 | &Instruction::IUSHR => self.write_u8(0x7c), 521 | &Instruction::IXOR => self.write_u8(0x82), 522 | &Instruction::JSR(value) => self.write_u8(0xa8).and(self.write_u16(value as u16)).and(Ok(3)), 523 | &Instruction::JSR_W(value) => self.write_u8(0xc9).and(self.write_u32(value as u32)).and(Ok(5)), 524 | &Instruction::L2D => self.write_u8(0x8a), 525 | &Instruction::L2F => self.write_u8(0x89), 526 | &Instruction::L2I => self.write_u8(0x88), 527 | &Instruction::LADD => self.write_u8(0x61), 528 | &Instruction::LALOAD => self.write_u8(0x2f), 529 | &Instruction::LAND => self.write_u8(0x7f), 530 | &Instruction::LASTORE => self.write_u8(0x50), 531 | &Instruction::LCMP => self.write_u8(0x94), 532 | &Instruction::LCONST_0 => self.write_u8(0x09), 533 | &Instruction::LCONST_1 => self.write_u8(0x0a), 534 | &Instruction::LDC(value) => self.write_u8(0x12).and(self.write_u8(value)).and(Ok(2)), 535 | &Instruction::LDC_W(value) => self.write_u8(0x13).and(self.write_u16(value)).and(Ok(3)), 536 | &Instruction::LDC2_W(value) => self.write_u8(0x14).and(self.write_u16(value)).and(Ok(3)), 537 | &Instruction::LDIV => self.write_u8(0x6d), 538 | &Instruction::LLOAD(value) => self.write_u8(0x16).and(self.write_u8(value)).and(Ok(2)), 539 | &Instruction::LLOAD_0 => self.write_u8(0x1e), 540 | &Instruction::LLOAD_1 => self.write_u8(0x1f), 541 | &Instruction::LLOAD_2 => self.write_u8(0x20), 542 | &Instruction::LLOAD_3 => self.write_u8(0x21), 543 | &Instruction::LMUL => self.write_u8(0x69), 544 | &Instruction::LNEG => self.write_u8(0x75), 545 | &Instruction::LOOKUPSWITCH(a, ref l) => { 546 | let _ = self.write_u8(0xab); 547 | 548 | let padding = (4 - ((offset + 1) % 4)) % 4; 549 | 550 | 551 | for _ in 0..padding { 552 | let _ = self.write_u8(0); 553 | } 554 | 555 | let _ = self.write_u32(a as u32); 556 | let _ = self.write_u32(l.len() as u32); 557 | 558 | for &(p1, p2) in l { 559 | let _ = self.write_u32(p1 as u32); 560 | let _ = self.write_u32(p2 as u32); 561 | } 562 | 563 | let len = 9 + padding + l.len() * 8; 564 | 565 | Ok(len) 566 | }, 567 | &Instruction::LOR => self.write_u8(0x81), 568 | &Instruction::LREM => self.write_u8(0x71), 569 | &Instruction::LRETURN => self.write_u8(0xad), 570 | &Instruction::LSHL => self.write_u8(0x79), 571 | &Instruction::LSHR => self.write_u8(0x7b), 572 | &Instruction::LSTORE(value) => self.write_u8(0x37).and(self.write_u8(value)).and(Ok(2)), 573 | &Instruction::LSTORE_0 => self.write_u8(0x3f), 574 | &Instruction::LSTORE_1 => self.write_u8(0x40), 575 | &Instruction::LSTORE_2 => self.write_u8(0x41), 576 | &Instruction::LSTORE_3 => self.write_u8(0x42), 577 | &Instruction::LSUB => self.write_u8(0x65), 578 | &Instruction::LUSHR => self.write_u8(0x7d), 579 | &Instruction::LXOR => self.write_u8(0x83), 580 | &Instruction::MONITORENTER => self.write_u8(0xc2), 581 | &Instruction::MONITOREXIT => self.write_u8(0xc3), 582 | &Instruction::MULTIANEWARRAY(a, b) => self.write_u8(0xc5).and(self.write_u16(a)).and(self.write_u8(b)).and(Ok(4)), 583 | &Instruction::NEW(value) => self.write_u8(0xbb).and(self.write_u16(value)).and(Ok(3)), 584 | &Instruction::NEWARRAY(value) => self.write_u8(0xbc).and(self.write_u8(value)).and(Ok(2)), 585 | &Instruction::NOP => self.write_u8(0x00), 586 | &Instruction::POP => self.write_u8(0x57), 587 | &Instruction::POP2 => self.write_u8(0x58), 588 | &Instruction::PUTFIELD(value) => self.write_u8(0xb5).and(self.write_u16(value)).and(Ok(3)), 589 | &Instruction::PUTSTATIC(value) => self.write_u8(0xb3).and(self.write_u16(value)).and(Ok(3)), 590 | &Instruction::RET(value) => self.write_u8(0xa9).and(self.write_u8(value)).and(Ok(2)), 591 | &Instruction::RETURN => self.write_u8(0xb1), 592 | &Instruction::SALOAD => self.write_u8(0x35), 593 | &Instruction::SASTORE => self.write_u8(0x56), 594 | &Instruction::SIPUSH(value) => self.write_u8(0x11).and(self.write_u16(value)).and(Ok(3)), 595 | &Instruction::SWAP => self.write_u8(0x5f), 596 | //TABLESWITCH(i32, i32, i32, Vec), 597 | &Instruction::TABLESWITCH(a, b, c, ref d) => { 598 | let _ = self.write_u8(0xaa); 599 | 600 | let padding = (4 - ((offset + 1) % 4)) % 4; 601 | 602 | for _ in 0..padding { 603 | let _ = self.write_u8(0); 604 | } 605 | 606 | let _ = self.write_u32(a as u32); 607 | let _ = self.write_u32(b as u32); 608 | let _ = self.write_u32(c as u32); 609 | 610 | for &v in d { 611 | let _ = self.write_u32(v as u32); 612 | } 613 | 614 | Ok(13 + padding + d.len() * 4) 615 | }, 616 | &Instruction::ILOAD_W(value) => self.write_u16(0xc415).and(self.write_u16(value)).and(Ok(4)), 617 | &Instruction::FLOAD_W(value) => self.write_u16(0xc417).and(self.write_u16(value)).and(Ok(4)), 618 | &Instruction::ALOAD_W(value) => self.write_u16(0xc419).and(self.write_u16(value)).and(Ok(4)), 619 | &Instruction::LLOAD_W(value) => self.write_u16(0xc416).and(self.write_u16(value)).and(Ok(4)), 620 | &Instruction::DLOAD_W(value) => self.write_u16(0xc418).and(self.write_u16(value)).and(Ok(4)), 621 | &Instruction::ISTORE_W(value) => self.write_u16(0xc436).and(self.write_u16(value)).and(Ok(4)), 622 | &Instruction::FSTORE_W(value) => self.write_u16(0xc438).and(self.write_u16(value)).and(Ok(4)), 623 | &Instruction::ASTORE_W(value) => self.write_u16(0xc43a).and(self.write_u16(value)).and(Ok(4)), 624 | &Instruction::LSTORE_W(value) => self.write_u16(0xc437).and(self.write_u16(value)).and(Ok(4)), 625 | &Instruction::DSTORE_W(value) => self.write_u16(0xc439).and(self.write_u16(value)).and(Ok(4)), 626 | &Instruction::RET_W(value) => self.write_u16(0xc4a9).and(self.write_u16(value)).and(Ok(4)), 627 | &Instruction::IINC_W(a, b) => self.write_u16(0xc484).and(self.write_u16(a)).and(self.write_u16(b as u16)).and(Ok(6)), 628 | _ => self.write_u8(0xFF) 629 | }.ok().unwrap_or(0) 630 | } 631 | 632 | fn write_exception_handlers(&mut self, exception_table: &Vec) -> Result { 633 | self.write_u16(exception_table.len() as u16) 634 | .and(exception_table.iter().fold(Ok(0), |_, x| { 635 | self.write_u16(x.start_pc) 636 | .and(self.write_u16(x.end_pc)) 637 | .and(self.write_u16(x.handler_pc)) 638 | .and(self.write_u16(x.catch_type.idx as u16)) 639 | })) 640 | } 641 | 642 | pub fn write_n(&mut self, bytes: &Vec) -> Result { 643 | bytes.iter().fold(Ok(0), |acc, x| match acc { 644 | Ok(ctr) => self.write_u8(*x).map(|c| c + ctr), 645 | err@_ => err 646 | }) 647 | } 648 | 649 | pub fn write_u64(&mut self, value: u64) -> Result { 650 | let buf: [u8; 8] = [ 651 | ((value & 0xFF << 56) >> 56) as u8, 652 | ((value & 0xFF << 48) >> 48) as u8, 653 | ((value & 0xFF << 40) >> 40) as u8, 654 | ((value & 0xFF << 32) >> 32) as u8, 655 | ((value & 0xFF << 24) >> 24) as u8, 656 | ((value & 0xFF << 16) >> 16) as u8, 657 | ((value & 0xFF << 8) >> 8) as u8, 658 | (value & 0xFF) as u8 659 | ]; 660 | 661 | self.target.write(&buf) 662 | } 663 | 664 | pub fn write_u32(&mut self, value: u32) -> Result { 665 | let buf: [u8; 4] = [ 666 | ((value & 0xFF << 24) >> 24) as u8, 667 | ((value & 0xFF << 16) >> 16) as u8, 668 | ((value & 0xFF << 8) >> 8) as u8, 669 | (value & 0xFF) as u8 670 | ]; 671 | 672 | self.target.write(&buf) 673 | } 674 | 675 | pub fn write_u16(&mut self, value: u16) -> Result { 676 | let buf: [u8; 2] = [((value & 0xFF00) >> 8) as u8, (value & 0xFF) as u8]; 677 | 678 | self.target.write(&buf) 679 | } 680 | 681 | pub fn write_u8(&mut self, value: u8) -> Result { 682 | self.target.write(&[value]) 683 | } 684 | } 685 | -------------------------------------------------------------------------------- /src/bytecode/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::classfile::*; 2 | pub use self::io::*; 3 | 4 | pub mod classfile; 5 | pub mod io; 6 | 7 | /* 8 | 9 | // -- 10 | 11 | pub enum BytecodeError { 12 | NotImplemented 13 | } 14 | 15 | pub type BytecodeResult = Result; 16 | 17 | // -- Stream 18 | 19 | pub struct BytecodeReader<'a> { 20 | source: &'a Read, 21 | } 22 | 23 | impl <'a> BytecodeReader<'a> { 24 | pub fn new(source: &'a Read) -> BytecodeReader { 25 | BytecodeReader { source: source } 26 | } 27 | 28 | pub fn read_u16(&self) -> Option { 29 | None 30 | } 31 | } 32 | 33 | pub struct BytecodeWriter<'a> { 34 | target: &'a mut Write 35 | } 36 | 37 | impl<'a> BytecodeWriter<'a> { 38 | /// Create a new bytecode writer that outputs the generated bytecode to the specified target 39 | pub fn new(target: &'a mut Write) -> BytecodeWriter { 40 | BytecodeWriter { target: target } 41 | } 42 | 43 | pub fn write_bytecode(&mut self, bytecode: &T) -> Result where T: Bytecode { 44 | bytecode.write_bytecode(self) 45 | } 46 | 47 | pub fn write_u64(&mut self, value: u64) -> Result { 48 | self.target.write(&*vec![ 49 | ((value & 0xFF << 56) >> 56) as u8, 50 | ((value & 0xFF << 48) >> 48) as u8, 51 | ((value & 0xFF << 40) >> 40) as u8, 52 | ((value & 0xFF << 32) >> 32) as u8, 53 | ((value & 0xFF << 24) >> 24) as u8, 54 | ((value & 0xFF << 16) >> 16) as u8, 55 | ((value & 0xFF << 8) >> 8) as u8, 56 | (value & 0xFF) as u8 57 | ]) 58 | } 59 | 60 | pub fn write_u32(&mut self, value: u32) -> Result { 61 | self.target.write(&*vec![ 62 | ((value & 0xFF << 24) >> 24) as u8, 63 | ((value & 0xFF << 16) >> 16) as u8, 64 | ((value & 0xFF << 8) >> 8) as u8, 65 | (value & 0xFF) as u8 66 | ]) 67 | } 68 | 69 | pub fn write_u16(&mut self, value: u16) -> Result { 70 | self.target.write(&*vec![ ((value & 0xFF00) >> 8) as u8, (value & 0xFF) as u8 ]) 71 | } 72 | 73 | pub fn write_u8(&mut self, value: u8) -> Result { 74 | self.target.write(&*vec![value]) 75 | } 76 | 77 | pub fn write_n(&mut self, value: Vec) -> Result { 78 | value.iter().map(|v| self.write_u8(*v)).fold(Ok(0), |acc, x| { 79 | match (acc, x) { 80 | (Ok(i), Ok(s)) => Ok(i + s), 81 | (e@Err(_), _) => e, 82 | (_, Err(err)) => Err(err) 83 | } 84 | }) 85 | } 86 | } 87 | 88 | pub trait Bytecode: Sized { 89 | fn read_bytecode(reader: &BytecodeReader) -> Result; 90 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result; 91 | } 92 | 93 | // -- Constants -- 94 | 95 | pub struct ConstantPool { 96 | } 97 | 98 | impl ConstantPool { 99 | } 100 | 101 | pub struct ConstantPoolIndex { 102 | pub index: u16 103 | } 104 | 105 | impl Bytecode for ConstantPoolIndex { 106 | 107 | fn read_bytecode(reader: &BytecodeReader) -> Result { 108 | match reader.read_u16() { 109 | Some(index) => Ok(ConstantPoolIndex { index: index }), 110 | None => Err(BytecodeError::NotImplemented) 111 | } 112 | } 113 | 114 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 115 | writer.write_u16(self.index) 116 | } 117 | } 118 | 119 | // -- Attributes -- 120 | 121 | pub enum Attribute { 122 | ConstantValue(ConstantValue), 123 | Code(Code) 124 | } 125 | 126 | impl Bytecode for Attribute { 127 | 128 | fn read_bytecode(reader: &BytecodeReader) -> Result { 129 | Err(BytecodeError::NotImplemented) 130 | } 131 | 132 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 133 | match self { 134 | &Attribute::ConstantValue(ref val) => val.write_bytecode(writer), 135 | &Attribute::Code(ref val) => val.write_bytecode(writer) 136 | } 137 | } 138 | } 139 | 140 | pub struct ConstantValue { 141 | pub index: ConstantPoolIndex 142 | } 143 | 144 | impl Bytecode for ConstantValue { 145 | fn read_bytecode(bytes: &BytecodeReader) -> Result { 146 | Err(BytecodeError::NotImplemented) 147 | } 148 | 149 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 150 | Ok(0) 151 | } 152 | } 153 | 154 | pub struct Code { 155 | pub max_stack: u16, 156 | pub max_locals: u16, 157 | pub code: Vec, 158 | pub exception_table: Vec, 159 | pub attributes: Vec 160 | } 161 | 162 | impl Bytecode for Code { 163 | fn read_bytecode(bytes: &BytecodeReader) -> Result { 164 | Err(BytecodeError::NotImplemented) 165 | } 166 | 167 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 168 | Ok(0) 169 | } 170 | } 171 | 172 | pub struct ExceptionHandler { 173 | pub start_pc: u16, 174 | pub end_pc: u16, 175 | pub handler_pc: u16, 176 | pub catch_type: u16 177 | } 178 | 179 | pub struct StackMapTable { 180 | pub entries: Vec 181 | } 182 | 183 | pub enum StackMapFrame { 184 | // TODO incomplete 185 | } 186 | 187 | pub struct Exceptions { 188 | pub exception_index_table: Vec 189 | } 190 | 191 | */ 192 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate jni_sys; 2 | #[macro_use] 3 | extern crate log; 4 | extern crate env_logger; 5 | 6 | mod jvmti_sys; 7 | mod manip; 8 | mod util; 9 | pub mod bytecode; 10 | pub mod native; 11 | 12 | use jni_sys::{JavaVM, jint, jclass, jobject, JNIEnv, JNI_OK}; 13 | use jvmti_sys::{jvmtiEnv, JVMTI_VERSION, jvmtiEventCallbacks, jvmtiCapabilities, jvmtiEventMode, jvmtiEvent, jthread}; 14 | use std::os::raw::{c_char, c_void, c_uchar}; 15 | use std::ffi::CStr; 16 | use std::mem::size_of; 17 | use std::ptr; 18 | 19 | #[no_mangle] 20 | #[allow(non_snake_case)] 21 | pub unsafe extern "C" fn Agent_OnLoad(vm: *mut JavaVM, 22 | options: *mut c_char, 23 | _reserved: *mut c_void) 24 | -> jint { 25 | debug!("Agent loading"); 26 | match run(vm, options) { 27 | Ok(()) => debug!("Agent loaded"), 28 | Err(errStr) => info!("Agent unable to load: {}", errStr), 29 | } 30 | return 0; 31 | } 32 | 33 | #[no_mangle] 34 | #[allow(non_snake_case)] 35 | pub extern "C" fn Agent_OnUnload(_vm: *mut JavaVM) { 36 | debug!("Agent unloaded"); 37 | } 38 | 39 | unsafe fn run(vm: *mut JavaVM, options: *mut c_char) -> Result<(), String> { 40 | // Init things like logging 41 | init(options); 42 | 43 | // Get the environment 44 | let jvmti_env = get_env(vm)?; 45 | 46 | // Add needed capabilities 47 | add_capabilities(jvmti_env)?; 48 | 49 | // Set the callbacks 50 | set_event_callbacks(jvmti_env)?; 51 | 52 | // Enable the notifications 53 | return enable_notifications(jvmti_env); 54 | } 55 | 56 | fn init(_options: *mut c_char) { 57 | let _ = env_logger::init(); 58 | } 59 | 60 | unsafe fn get_env(vm: *mut JavaVM) -> Result<*mut jvmtiEnv, String> { 61 | let mut ptr: *mut c_void = ptr::null_mut() as *mut c_void; 62 | let env_res = (**vm).GetEnv.unwrap()(vm, &mut ptr, JVMTI_VERSION); 63 | if env_res != JNI_OK { 64 | return Result::Err(format!("No environment, err: {}", env_res)); 65 | } 66 | return Result::Ok(ptr as *mut jvmtiEnv); 67 | } 68 | 69 | unsafe fn add_capabilities(jvmti_env: *mut jvmtiEnv) -> Result<(), String> { 70 | let caps = jvmtiCapabilities { 71 | // can_access_local_variables | can_generate_all_class_hook_events 72 | _bindgen_bitfield_1_: 0x00004000 | 0x04000000, 73 | ..Default::default() 74 | }; 75 | return util::unit_or_jvmti_err((**jvmti_env).AddCapabilities.unwrap()(jvmti_env, &caps)); 76 | } 77 | 78 | unsafe fn set_event_callbacks(jvmti_env: *mut jvmtiEnv) -> Result<(), String> { 79 | // We only need init and load hook 80 | let cb = jvmtiEventCallbacks { 81 | ClassFileLoadHook: Some(class_file_load_hook), 82 | VMInit: Some(vm_init), 83 | ..Default::default() 84 | }; 85 | let cb_res = (**jvmti_env).SetEventCallbacks.unwrap()(jvmti_env, 86 | &cb, 87 | size_of::() as i32); 88 | return util::unit_or_jvmti_err(cb_res); 89 | } 90 | 91 | unsafe fn enable_notifications(jvmti_env: *mut jvmtiEnv) -> Result<(), String> { 92 | enable_notification(jvmti_env, jvmtiEvent::JVMTI_EVENT_VM_INIT)?; 93 | return enable_notification(jvmti_env, jvmtiEvent::JVMTI_EVENT_CLASS_FILE_LOAD_HOOK); 94 | } 95 | 96 | unsafe fn enable_notification(jvmti_env: *mut jvmtiEnv, event: jvmtiEvent) -> Result<(), String> { 97 | let mode_res = (**jvmti_env).SetEventNotificationMode.unwrap()(jvmti_env, 98 | jvmtiEventMode::JVMTI_ENABLE, 99 | event, 100 | ptr::null_mut()); 101 | return util::unit_or_jvmti_err(mode_res); 102 | } 103 | 104 | unsafe fn transform_class_file(jvmti_env: *mut jvmtiEnv, 105 | jni_env: *mut JNIEnv, 106 | class_being_redefined: jclass, 107 | name: *const c_char, 108 | class_data_len: jint, 109 | class_data: *const c_uchar, 110 | new_class_data_len: *mut jint, 111 | new_class_data: *mut *mut c_uchar) 112 | -> Result<(), String> { 113 | // Must have name and must be being first class definition 114 | if name.is_null() || !class_being_redefined.is_null() { 115 | return Result::Ok(()); 116 | } 117 | return match CStr::from_ptr(name).to_str() { 118 | Ok("java/lang/Throwable") => 119 | manip::manip_throwable_class(jvmti_env, jni_env, class_data_len, class_data, new_class_data_len, new_class_data), 120 | Ok("java/lang/StackTraceElement") => 121 | manip::manip_element_class(jvmti_env, jni_env, class_data_len, class_data, new_class_data_len, new_class_data), 122 | _ => 123 | Result::Ok(()) 124 | } 125 | } 126 | 127 | unsafe extern "C" fn class_file_load_hook(jvmti_env: *mut jvmtiEnv, 128 | jni_env: *mut JNIEnv, 129 | class_being_redefined: jclass, 130 | _loader: jobject, 131 | name: *const c_char, 132 | _protection_domain: jobject, 133 | class_data_len: jint, 134 | class_data: *const c_uchar, 135 | new_class_data_len: *mut jint, 136 | new_class_data: *mut *mut c_uchar) 137 | -> () { 138 | match transform_class_file(jvmti_env, 139 | jni_env, 140 | class_being_redefined, 141 | name, 142 | class_data_len, 143 | class_data, 144 | new_class_data_len, 145 | new_class_data) { 146 | Ok(()) => (), 147 | Err(err_str) => info!("Failed to hook class: {}", err_str) 148 | } 149 | } 150 | 151 | unsafe extern "C" fn vm_init(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, _thread: jthread) -> () { 152 | info!("Agent initializing"); 153 | // Set the global jvmti env for later jni use 154 | native::init(jvmti_env); 155 | match manip::define_manip_class(jni_env) { 156 | Ok(()) => info!("Agent initialized"), 157 | Err(err_str) => info!("Unable to initialize agent: {}", err_str), 158 | } 159 | } -------------------------------------------------------------------------------- /src/manip.rs: -------------------------------------------------------------------------------- 1 | extern crate jni_sys; 2 | extern crate env_logger; 3 | 4 | use util; 5 | use jni_sys::{JNIEnv, jbyte, jclass, jint, jlong}; 6 | use jvmti_sys::jvmtiEnv; 7 | use std::ffi::CString; 8 | use std::ptr; 9 | use std::os::raw::c_uchar; 10 | use std::io::{Cursor, Error}; 11 | use std::slice; 12 | use bytecode::classfile::{AccessFlags, Attribute, Classfile, Constant, ConstantPoolIndex, Field, FieldAccessFlags, Instruction, Method, MethodAccessFlags}; 13 | use bytecode::io::reader::ClassReader; 14 | use bytecode::io::writer::ClassWriter; 15 | 16 | pub unsafe fn define_manip_class(jni_env: *mut JNIEnv) -> Result<(), String> { 17 | debug!("Defining class"); 18 | // Get the bytes from file 19 | let class_bytes = include_bytes!("../javalib/native/build/classes/main/stackparam/StackParamNative.class"); 20 | // Define the class 21 | let class_name = CString::new("stackparam/StackParamNative").unwrap(); 22 | // We don't want the defined class, because it is not "prepared", we want to make them ask for it again 23 | let _ = (**jni_env).DefineClass.unwrap()(jni_env, 24 | class_name.as_ref().as_ptr(), 25 | ptr::null_mut(), 26 | class_bytes.as_ptr() as *const jbyte, 27 | class_bytes.len() as i32); 28 | // Confirm no exception 29 | util::result_or_jni_ex((), jni_env)?; 30 | return Result::Ok(()); 31 | } 32 | 33 | pub unsafe fn manip_throwable_class(jvmti_env: *mut jvmtiEnv, 34 | _jni_env: *mut JNIEnv, 35 | class_data_len: jint, 36 | class_data: *const c_uchar, 37 | new_class_data_len: *mut jint, 38 | new_class_data: *mut *mut c_uchar) 39 | -> Result<(), String> { 40 | // Read the class 41 | let mut class_file = read_class(class_data_len, class_data)?; 42 | 43 | // Do transforms 44 | add_stack_params_field(&mut class_file); 45 | add_native_stack_params_method(&mut class_file); 46 | update_fill_method(&mut class_file)?; 47 | replace_our_trace_method(&mut class_file)?; 48 | 49 | // Write the class 50 | return write_class(jvmti_env, &class_file, new_class_data_len, new_class_data); 51 | } 52 | 53 | pub unsafe fn manip_element_class(jvmti_env: *mut jvmtiEnv, 54 | _jni_env: *mut JNIEnv, 55 | class_data_len: jint, 56 | class_data: *const c_uchar, 57 | new_class_data_len: *mut jint, 58 | new_class_data: *mut *mut c_uchar) 59 | -> Result<(), String> { 60 | // Read the class 61 | let mut class_file = read_class(class_data_len, class_data)?; 62 | 63 | // Do transforms 64 | add_param_info_field(&mut class_file); 65 | replace_elem_to_string(&mut class_file)?; 66 | 67 | // Write the class 68 | return write_class(jvmti_env, &class_file, new_class_data_len, new_class_data); 69 | } 70 | 71 | unsafe fn read_class(class_data_len: jint, class_data: *const c_uchar) -> Result { 72 | let class_data_bytes = slice::from_raw_parts(class_data, class_data_len as usize); 73 | let mut rdr = Cursor::new(class_data_bytes); 74 | return str_err(ClassReader::read_class(&mut rdr)); 75 | } 76 | 77 | unsafe fn write_class(jvmti_env: *mut jvmtiEnv, 78 | class_file: &Classfile, 79 | new_class_data_len: *mut jint, 80 | new_class_data: *mut *mut c_uchar) -> Result<(), String> { 81 | let mut new_class_curs = Cursor::new(Vec::new()); 82 | str_err(ClassWriter::new(&mut new_class_curs).write_class(&class_file))?; 83 | let new_class_vec: &Vec = new_class_curs.get_ref(); 84 | ptr::write(new_class_data_len, new_class_vec.len() as jint); 85 | let alloc_res = (**jvmti_env).Allocate.unwrap()(jvmti_env, new_class_vec.len() as jlong, new_class_data); 86 | util::unit_or_jvmti_err(alloc_res)?; 87 | ptr::copy_nonoverlapping(new_class_vec.as_ptr(), *new_class_data, new_class_vec.len()); 88 | return Result::Ok(()); 89 | } 90 | 91 | unsafe fn add_param_info_field(class_file: &mut Classfile) { 92 | let field_name_idx = ConstantPoolIndex { idx: utf8_const(class_file, "paramInfo") }; 93 | let field_desc_idx = ConstantPoolIndex { idx: utf8_const(class_file, "[Ljava/lang/Object;") }; 94 | class_file.fields.push(Field { 95 | access_flags: AccessFlags { flags: FieldAccessFlags::Transient as u16 }, 96 | name_index: field_name_idx, 97 | descriptor_index: field_desc_idx, 98 | attributes: Vec::new(), 99 | }); 100 | 101 | // Note, even if we had code to manip to set our field as null here, it doesn't 102 | // help as who knows how the StackTraceElement is inited. 103 | } 104 | 105 | unsafe fn replace_elem_to_string(class_file: &mut Classfile) -> Result<(), String> { 106 | // Change current toString to $$stack_param$$toString and make a new native one 107 | 108 | // Rename 109 | let mut found = false; 110 | let meth_to_str_name_idx = utf8_const(class_file, "toString"); 111 | let meth_ret_str_desc_idx = utf8_const(class_file, "()Ljava/lang/String;"); 112 | let new_meth_to_str_name_idx = utf8_const(class_file, "$$stack_param$$toString"); 113 | for method in class_file.methods.iter_mut() { 114 | if method.name_index.idx == meth_to_str_name_idx && method.descriptor_index.idx == meth_ret_str_desc_idx { 115 | found = true; 116 | method.name_index = ConstantPoolIndex { idx: new_meth_to_str_name_idx }; 117 | } 118 | } 119 | if !found { return Result::Err("Unable to find toString".to_string()); } 120 | 121 | // Make new native method 122 | class_file.methods.push(Method { 123 | access_flags: AccessFlags { flags: MethodAccessFlags::Public as u16 + MethodAccessFlags::Native as u16 }, 124 | name_index: ConstantPoolIndex { idx: meth_to_str_name_idx }, 125 | descriptor_index: ConstantPoolIndex { idx: meth_ret_str_desc_idx }, 126 | attributes: Vec::new() 127 | }); 128 | return Result::Ok(()); 129 | } 130 | 131 | unsafe fn add_stack_params_field(class_file: &mut Classfile) { 132 | // Add "private transient Object[][] stackParams" field 133 | let field_name_idx = ConstantPoolIndex { idx: utf8_const(class_file, "stackParams") }; 134 | let field_desc_idx = ConstantPoolIndex { idx: utf8_const(class_file, "[[Ljava/lang/Object;") }; 135 | class_file.fields.push(Field { 136 | access_flags: AccessFlags { flags: FieldAccessFlags::Private as u16 + FieldAccessFlags::Transient as u16 }, 137 | name_index: field_name_idx, 138 | descriptor_index: field_desc_idx, 139 | attributes: Vec::new(), 140 | }); 141 | 142 | // Note, we choose not to explicitly set the stackParams field to null in Throwable 143 | // constructors because we do it in fillInStackTrace one way or another 144 | } 145 | 146 | unsafe fn add_native_stack_params_method(class_file: &mut Classfile) { 147 | // Create native stackParamFillInStackTrace(Thread) 148 | let sp_fill_meth_name_idx = utf8_const(class_file, "stackParamFillInStackTrace"); 149 | let meth_thread_ret_throwable_idx = utf8_const(class_file, "(Ljava/lang/Thread;)Ljava/lang/Throwable;"); 150 | class_file.methods.push(Method { 151 | access_flags: AccessFlags { flags: MethodAccessFlags::Private as u16 + MethodAccessFlags::Native as u16 }, 152 | name_index: ConstantPoolIndex { idx: sp_fill_meth_name_idx }, 153 | descriptor_index: ConstantPoolIndex { idx: meth_thread_ret_throwable_idx }, 154 | attributes: Vec::new() 155 | }); 156 | } 157 | 158 | unsafe fn update_fill_method(class_file: &mut Classfile) -> Result<(), String> { 159 | // Change existing fillInStackTrace to call stackParamFillInStackTrace(Thread) right after fillInStackTrace(0) 160 | let fill_meth_name_idx = utf8_const(class_file, "fillInStackTrace"); 161 | // Get the code 162 | let curr_thread_ref_idx = method_ref_const(class_file, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;"); 163 | let meth_ret_throwable_idx = utf8_const(class_file, "()Ljava/lang/Throwable;"); 164 | let native_fill_meth_ref_idx = method_ref_const(class_file, "java/lang/Throwable", "fillInStackTrace", "(I)Ljava/lang/Throwable;"); 165 | let new_native_fill_meth_ref_idx = method_ref_const(class_file, 166 | "java/lang/Throwable", 167 | "stackParamFillInStackTrace", 168 | "(Ljava/lang/Thread;)Ljava/lang/Throwable;"); 169 | let mut fill_meth = class_file.methods.iter_mut().find(|m| { 170 | m.name_index.idx == fill_meth_name_idx && m.descriptor_index.idx == meth_ret_throwable_idx 171 | }).ok_or("Cannot find fill method".to_string())?; 172 | let mut fill_meth_code = get_method_code_mut(&mut fill_meth)?; 173 | // Find the index of the invoke special 174 | let fill_invoke_idx = fill_meth_code.iter().position(|i| { 175 | match i { 176 | &Instruction::INVOKESPECIAL(ref idx) if *idx == native_fill_meth_ref_idx as u16 => true, 177 | _ => false 178 | } 179 | }).ok_or("Cannot find invoke of native fill".to_string())?; 180 | // Call mine afterwards. "this" is currently on the stack already. It takes the current thread, 181 | // so we grab that statically before calling so it is on the stack (current max stack of >= 2 is 182 | // still ok for us). Result is a throwable so the stack is left how we got it. 183 | fill_meth_code.insert(fill_invoke_idx + 1, Instruction::INVOKESTATIC(curr_thread_ref_idx as u16)); 184 | fill_meth_code.insert(fill_invoke_idx + 2, Instruction::INVOKESPECIAL(new_native_fill_meth_ref_idx as u16)); 185 | return Result::Ok(()); 186 | } 187 | 188 | unsafe fn replace_our_trace_method(class_file: &mut Classfile) -> Result<(), String> { 189 | // Rename getOurStackTrace to $$stack_param$$getOurStackTrace, then create a new 190 | // (synchronized) version that is our native one. 191 | 192 | // Rename 193 | let mut found = false; 194 | let meth_get_our_name_idx = utf8_const(class_file, "getOurStackTrace"); 195 | let meth_ret_elems_desc_idx = utf8_const(class_file, "()[Ljava/lang/StackTraceElement;"); 196 | let new_meth_get_our_name_idx = utf8_const(class_file, "$$stack_param$$getOurStackTrace"); 197 | for method in class_file.methods.iter_mut() { 198 | if method.name_index.idx == meth_get_our_name_idx && method.descriptor_index.idx == meth_ret_elems_desc_idx { 199 | found = true; 200 | method.name_index = ConstantPoolIndex { idx: new_meth_get_our_name_idx }; 201 | } 202 | } 203 | if !found { return Result::Err("Unable to find getOurStackTrace".to_string()); } 204 | 205 | // Make new native method 206 | class_file.methods.push(Method { 207 | access_flags: AccessFlags { flags: MethodAccessFlags::Private as u16 + MethodAccessFlags::Synchronized as u16 + MethodAccessFlags::Native as u16 }, 208 | name_index: ConstantPoolIndex { idx: meth_get_our_name_idx }, 209 | descriptor_index: ConstantPoolIndex { idx: meth_ret_elems_desc_idx }, 210 | attributes: Vec::new() 211 | }); 212 | return Result::Ok(()); 213 | } 214 | 215 | unsafe fn get_method_code_mut(method: &mut Method) -> Result<&mut Vec, String> { 216 | for attr in method.attributes.iter_mut() { 217 | match attr { 218 | &mut Attribute::Code { ref mut code, .. } => return Result::Ok(code), 219 | _ => () 220 | } 221 | } 222 | return Result::Err("Unable to find code for method".to_string()); 223 | } 224 | 225 | #[allow(dead_code)] 226 | unsafe fn get_manip_class(jni_env: *mut JNIEnv) -> Result { 227 | let class_name = CString::new("stackparam/StackParamNative").unwrap(); 228 | let class = (**jni_env).FindClass.unwrap()(jni_env, class_name.as_ref().as_ptr()); 229 | return util::result_or_jni_ex(class, jni_env); 230 | } 231 | 232 | fn str_err(res: Result) -> Result { 233 | return res.map_err(|err| format!("{}", err)) 234 | } 235 | 236 | fn utf8_const(class_file: &mut Classfile, str: &str) -> usize { 237 | for i in 0..class_file.constant_pool.constants.len() { 238 | match class_file.constant_pool.constants[i] { 239 | Constant::Utf8(ref bytes) => { 240 | if bytes.as_slice() == str.as_bytes() { 241 | return i; 242 | } 243 | }, 244 | _ => () 245 | } 246 | } 247 | let ret = class_file.constant_pool.constants.len(); 248 | class_file.constant_pool.constants.push(Constant::Utf8(str.as_bytes().to_vec())); 249 | return ret; 250 | } 251 | 252 | #[allow(dead_code)] 253 | fn str_const(class_file: &mut Classfile, str: &str) -> usize { 254 | let utf8_idx = utf8_const(class_file, str); 255 | for i in 0..class_file.constant_pool.constants.len() { 256 | match class_file.constant_pool.constants[i] { 257 | Constant::String(ref idx) => { 258 | if idx.idx == utf8_idx { 259 | return i; 260 | } 261 | }, 262 | _ => () 263 | } 264 | } 265 | let ret = class_file.constant_pool.constants.len(); 266 | class_file.constant_pool.constants.push(Constant::String(ConstantPoolIndex { idx: utf8_idx })); 267 | return ret; 268 | } 269 | 270 | fn class_const(class_file: &mut Classfile, class_name: &str) -> usize { 271 | let utf8_idx = utf8_const(class_file, class_name); 272 | for i in 0..class_file.constant_pool.constants.len() { 273 | match class_file.constant_pool.constants[i] { 274 | Constant::Class(ref idx) => { 275 | if idx.idx == utf8_idx { 276 | return i; 277 | } 278 | }, 279 | _ => () 280 | } 281 | } 282 | let ret = class_file.constant_pool.constants.len(); 283 | class_file.constant_pool.constants.push(Constant::Class(ConstantPoolIndex { idx: utf8_idx })); 284 | return ret; 285 | } 286 | 287 | #[allow(dead_code)] 288 | fn name_and_type_const(class_file: &mut Classfile, name: &str, desc: &str) -> usize { 289 | let name_idx = utf8_const(class_file, name); 290 | let desc_idx = utf8_const(class_file, desc); 291 | for i in 0..class_file.constant_pool.constants.len() { 292 | match class_file.constant_pool.constants[i] { 293 | Constant::NameAndType { ref name_index, ref descriptor_index } => { 294 | if name_index.idx == name_idx && descriptor_index.idx == desc_idx { 295 | return i; 296 | } 297 | }, 298 | _ => () 299 | } 300 | } 301 | let ret = class_file.constant_pool.constants.len(); 302 | class_file.constant_pool.constants.push(Constant::NameAndType { 303 | name_index: ConstantPoolIndex { idx: name_idx }, 304 | descriptor_index: ConstantPoolIndex { idx: desc_idx }, 305 | }); 306 | return ret; 307 | } 308 | 309 | #[allow(dead_code)] 310 | fn field_ref_const(class_file: &mut Classfile, class_name: &str, field_name: &str, desc: &str) -> usize { 311 | let class_idx = class_const(class_file, class_name); 312 | let name_and_type_idx = name_and_type_const(class_file, field_name, desc); 313 | for i in 0..class_file.constant_pool.constants.len() { 314 | match class_file.constant_pool.constants[i] { 315 | Constant::FieldRef { ref class_index, ref name_and_type_index } => { 316 | if class_index.idx == class_idx && name_and_type_index.idx == name_and_type_idx { 317 | return i; 318 | } 319 | }, 320 | _ => () 321 | } 322 | } 323 | let ret = class_file.constant_pool.constants.len(); 324 | class_file.constant_pool.constants.push(Constant::FieldRef { 325 | class_index: ConstantPoolIndex { idx: class_idx }, 326 | name_and_type_index: ConstantPoolIndex { idx: name_and_type_idx }, 327 | }); 328 | return ret; 329 | } 330 | 331 | fn method_ref_const(class_file: &mut Classfile, class_name: &str, method_name: &str, desc: &str) -> usize { 332 | let class_idx = class_const(class_file, class_name); 333 | let name_and_type_idx = name_and_type_const(class_file, method_name, desc); 334 | for i in 0..class_file.constant_pool.constants.len() { 335 | match class_file.constant_pool.constants[i] { 336 | Constant::MethodRef { ref class_index, ref name_and_type_index } => { 337 | if class_index.idx == class_idx && name_and_type_index.idx == name_and_type_idx { 338 | return i; 339 | } 340 | }, 341 | _ => () 342 | } 343 | } 344 | let ret = class_file.constant_pool.constants.len(); 345 | class_file.constant_pool.constants.push(Constant::MethodRef { 346 | class_index: ConstantPoolIndex { idx: class_idx }, 347 | name_and_type_index: ConstantPoolIndex { idx: name_and_type_idx }, 348 | }); 349 | return ret; 350 | } 351 | -------------------------------------------------------------------------------- /src/native.rs: -------------------------------------------------------------------------------- 1 | extern crate jni_sys; 2 | 3 | use log::LogLevel::{Debug, Trace}; 4 | use jni_sys::{JNIEnv, jclass, jint, jlong, jfloat, jdouble, jobject, jmethodID, jfieldID, jstring, jobjectArray, jsize}; 5 | use jvmti_sys::{jvmtiEnv, jthread, jvmtiFrameInfo, jvmtiLocalVariableEntry, jvmtiError}; 6 | use std::ptr; 7 | use util; 8 | use std::os::raw::{c_char, c_uchar, c_uint, c_int, c_double}; 9 | use std::slice; 10 | use std::ffi::{CStr, CString}; 11 | use std::sync::{Once, ONCE_INIT}; 12 | use std::mem; 13 | 14 | const DEFAULT_MAX_STACK_DEPTH: jint = 3000; 15 | 16 | // Not set until after init on purpose 17 | static mut JVMTI_ENV: *mut jvmtiEnv = 0 as *mut jvmtiEnv; 18 | 19 | pub unsafe fn init(jvmti_env: *mut jvmtiEnv) { 20 | JVMTI_ENV = jvmti_env; 21 | } 22 | 23 | #[no_mangle] 24 | #[allow(non_snake_case)] 25 | pub unsafe extern "C" fn Java_java_lang_Throwable_getOurStackTrace(jni_env: *mut JNIEnv, 26 | this: jobject) -> jobject { 27 | if log_enabled!(Debug) { 28 | debug!("Asking for trace from {}", class_sig_from_obj(jni_env, this).unwrap_or("".to_string())); 29 | } 30 | return match populate_trace_elements(jni_env, this) { 31 | Result::Err(err_str) => { 32 | debug!("Stack elem populate err: {}", err_str); 33 | ptr::null_mut() 34 | }, 35 | Result::Ok(ret) => ret 36 | }; 37 | } 38 | 39 | #[no_mangle] 40 | #[allow(non_snake_case)] 41 | pub unsafe extern "C" fn Java_java_lang_StackTraceElement_toString(jni_env: *mut JNIEnv, this: jobject) -> jobject { 42 | return match append_param_to_string(jni_env, this) { 43 | Result::Err(err_str) => { 44 | debug!("Stack elem toString err: {}", err_str); 45 | ptr::null_mut() 46 | }, 47 | Result::Ok(ret) => ret 48 | }; 49 | } 50 | 51 | #[no_mangle] 52 | #[allow(non_snake_case)] 53 | pub unsafe extern "C" fn Java_java_lang_Throwable_stackParamFillInStackTrace(jni_env: *mut JNIEnv, 54 | this: jobject, 55 | thread: jthread) -> jobject { 56 | // This is needed in case of failure, we need to at least set the field to something 57 | unsafe fn set_param_to_null_ignore_err(jni_env: *mut JNIEnv, this: jobject) { 58 | let field = get_stack_params_field(jni_env).unwrap_or(ptr::null_mut()); 59 | if !field.is_null() { 60 | (**jni_env).SetObjectField.unwrap()(jni_env, this, field, ptr::null_mut()); 61 | let _ = util::result_or_jni_ex((), jni_env); 62 | } 63 | } 64 | 65 | // Do nothing before vm init (i.e. before our static is set) 66 | if JVMTI_ENV.is_null() { 67 | set_param_to_null_ignore_err(jni_env, this); 68 | return this; 69 | } 70 | 71 | // TODO: there are a ton of exception fills happening on startup that are slowing things down and 72 | // are not relayed to the user. We should either skip filling those, or find a way to make the fill 73 | // cheaper (bunch of string allocs) 74 | if log_enabled!(Debug) { 75 | let class_name = class_sig_from_obj(jni_env, this).unwrap_or("".to_string()); 76 | debug!("Asking to fill for {}", class_name); 77 | } 78 | 79 | // Populate the field, swallow the err 80 | match populate_stack_params(jni_env, this, thread) { 81 | Result::Err(err_str) => { 82 | debug!("Stack param fill err: {}", err_str); 83 | set_param_to_null_ignore_err(jni_env, this); 84 | }, 85 | Result::Ok(()) => () 86 | }; 87 | return this; 88 | } 89 | 90 | unsafe fn append_param_to_string(jni_env: *mut JNIEnv, this: jobject) -> Result { 91 | // First call the original one, then take the result and append our stuff via static call 92 | let str = get_elem_str_orig(jni_env, this)?; 93 | // Only if JVMTI is inited (because we need our manip class loaded) 94 | if JVMTI_ENV.is_null() { 95 | return Result::Ok(str); 96 | } 97 | // Get the param info 98 | let param_info_field = get_elem_param_info_field(jni_env)?; 99 | let param_info = util::result_or_jni_ex((**jni_env).GetObjectField.unwrap()(jni_env, this, param_info_field), jni_env)?; 100 | if param_info.is_null() { 101 | return Result::Ok(str); 102 | } 103 | return append_param_info(jni_env, str, param_info); 104 | } 105 | 106 | unsafe fn get_elem_str_orig(jni_env: *mut JNIEnv, this: jobject) -> Result { 107 | static mut STR_ORIG_METH: jmethodID = 0 as jmethodID; 108 | static ONCE: Once = ONCE_INIT; 109 | ONCE.call_once(|| { 110 | let elem_class = get_elem_class(jni_env).unwrap_or(ptr::null_mut()); 111 | if !elem_class.is_null() { 112 | // We swallow exceptions in here on purpose 113 | let meth_name_str = CString::new("$$stack_param$$toString").unwrap(); 114 | let meth_sig_str = CString::new("()Ljava/lang/String;").unwrap(); 115 | STR_ORIG_METH = (**jni_env).GetMethodID.unwrap()(jni_env, 116 | elem_class, 117 | meth_name_str.as_ptr(), 118 | meth_sig_str.as_ptr()); 119 | let _ = util::result_or_jni_ex((), jni_env); 120 | } 121 | }); 122 | if STR_ORIG_METH.is_null() { return Result::Err("No $$stack_param$$toString method".to_string()); } 123 | return util::result_or_jni_ex((**jni_env).CallObjectMethod.unwrap()(jni_env, this, STR_ORIG_METH), jni_env); 124 | } 125 | 126 | unsafe fn append_param_info(jni_env: *mut JNIEnv, str: jobject, param_info: jobject) -> Result { 127 | let manip_class = get_manip_class(jni_env)?; 128 | static mut APPEND_METH: jmethodID = 0 as jmethodID; 129 | static ONCE: Once = ONCE_INIT; 130 | ONCE.call_once(|| { 131 | // We swallow exceptions in here on purpose 132 | let meth_name_str = CString::new("appendParamsToFrameString").unwrap(); 133 | let meth_sig_str = CString::new("(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;").unwrap(); 134 | APPEND_METH = (**jni_env).GetStaticMethodID.unwrap()(jni_env, 135 | manip_class, 136 | meth_name_str.as_ptr(), 137 | meth_sig_str.as_ptr()); 138 | let _ = util::result_or_jni_ex((), jni_env); 139 | }); 140 | if APPEND_METH.is_null() { return Result::Err("No append method".to_string()); } 141 | let ret = util::result_or_jni_ex((**jni_env).CallStaticObjectMethod.unwrap()(jni_env, 142 | manip_class, 143 | APPEND_METH, 144 | str, 145 | param_info), jni_env); 146 | return ret; 147 | } 148 | 149 | unsafe fn get_manip_class(jni_env: *mut JNIEnv) -> Result { 150 | let class_name_str = CString::new("stackparam/StackParamNative").unwrap(); 151 | return util::result_or_jni_ex((**jni_env).FindClass.unwrap()(jni_env, class_name_str.as_ptr()), jni_env); 152 | } 153 | 154 | unsafe fn get_elem_class(jni_env: *mut JNIEnv) -> Result { 155 | let class_name_str = CString::new("java/lang/StackTraceElement").unwrap(); 156 | return util::result_or_jni_ex((**jni_env).FindClass.unwrap()(jni_env, class_name_str.as_ptr()), jni_env); 157 | } 158 | 159 | unsafe fn populate_trace_elements(jni_env: *mut JNIEnv, this: jobject) -> Result { 160 | // We will fill the stack trace field if it has changed and it's 161 | // a non-null array with length greater than 0. 162 | 163 | // Grab the field value 164 | let field_val = util::result_or_jni_ex((**jni_env).GetObjectField.unwrap()(jni_env, 165 | this, 166 | get_stack_trace_field(jni_env)?), jni_env)?; 167 | // Defer to original method 168 | let ret = get_our_trace_orig(jni_env, this)?; 169 | 170 | // Is the field value the same or did we get null back? 171 | if ret.is_null() || ret == field_val { 172 | return Result::Ok(ret); 173 | } 174 | 175 | // Is the result array size over 0? 176 | let ret_len = util::result_or_jni_ex((**jni_env).GetArrayLength.unwrap()(jni_env, ret), jni_env)?; 177 | if ret_len == 0 { 178 | return Result::Ok(ret); 179 | } 180 | 181 | add_element_params(jni_env, this, ret, ret_len)?; 182 | return Result::Ok(ret); 183 | } 184 | 185 | unsafe fn add_element_params(jni_env: *mut JNIEnv, this: jobject, elems: jobjectArray, elems_len: jsize) -> Result<(), String> { 186 | let params = util::result_or_jni_ex((**jni_env).GetObjectField.unwrap()(jni_env, 187 | this, 188 | get_stack_params_field(jni_env)?), jni_env)?; 189 | // If it's null we just treat it as empty 190 | let params_len = if params.is_null() { 191 | 0 192 | } else { 193 | util::result_or_jni_ex((**jni_env).GetArrayLength.unwrap()(jni_env, params), jni_env)? 194 | }; 195 | 196 | for index in 0..elems_len { 197 | let elem = util::result_or_jni_ex((**jni_env).GetObjectArrayElement.unwrap()(jni_env, elems, index), jni_env)?; 198 | if !elem.is_null() { 199 | let param = if index >= params_len { 200 | ptr::null_mut() 201 | } else { 202 | util::result_or_jni_ex((**jni_env).GetObjectArrayElement.unwrap()(jni_env, params, index), jni_env)? 203 | }; 204 | set_elem_param_info(jni_env, param, elem)?; 205 | } 206 | } 207 | return Result::Ok(()); 208 | } 209 | 210 | unsafe fn get_elem_param_info_field(jni_env: *mut JNIEnv) -> Result { 211 | static mut PARAM_INFO_FIELD: jfieldID = 0 as jfieldID; 212 | static ONCE: Once = ONCE_INIT; 213 | ONCE.call_once(|| { 214 | let elem_class = get_elem_class(jni_env).unwrap_or(ptr::null_mut()); 215 | if !elem_class.is_null() { 216 | // We swallow exceptions in here on purpose 217 | let field_name_str = CString::new("paramInfo").unwrap(); 218 | let field_sig_str = CString::new("[Ljava/lang/Object;").unwrap(); 219 | PARAM_INFO_FIELD = (**jni_env).GetFieldID.unwrap()(jni_env, 220 | elem_class, 221 | field_name_str.as_ptr(), 222 | field_sig_str.as_ptr()); 223 | let _ = util::result_or_jni_ex((), jni_env); 224 | } 225 | }); 226 | if PARAM_INFO_FIELD.is_null() { return Result::Err("No paramInfo field".to_string()); } 227 | return Result::Ok(PARAM_INFO_FIELD); 228 | } 229 | 230 | unsafe fn set_elem_param_info(jni_env: *mut JNIEnv, param_info: jobject, on: jobject) -> Result<(), String> { 231 | let param_info_field = get_elem_param_info_field(jni_env)?; 232 | (**jni_env).SetObjectField.unwrap()(jni_env, on, param_info_field, param_info); 233 | return util::result_or_jni_ex((), jni_env); 234 | } 235 | 236 | unsafe fn get_our_trace_orig(jni_env: *mut JNIEnv, this: jobject) -> Result { 237 | static mut TRACE_ORIG_METH: jmethodID = 0 as jmethodID; 238 | static ONCE: Once = ONCE_INIT; 239 | ONCE.call_once(|| { 240 | let throwable_class = get_throwable_class(jni_env).unwrap_or(ptr::null_mut()); 241 | if !throwable_class.is_null() { 242 | // We swallow exceptions in here on purpose 243 | let meth_name_str = CString::new("$$stack_param$$getOurStackTrace").unwrap(); 244 | let meth_sig_str = CString::new("()[Ljava/lang/StackTraceElement;").unwrap(); 245 | TRACE_ORIG_METH = (**jni_env).GetMethodID.unwrap()(jni_env, 246 | throwable_class, 247 | meth_name_str.as_ptr(), 248 | meth_sig_str.as_ptr()); 249 | let _ = util::result_or_jni_ex((), jni_env); 250 | } 251 | }); 252 | if TRACE_ORIG_METH.is_null() { return Result::Err("No $$stack_param$$getOurStackTrace method".to_string()); } 253 | return util::result_or_jni_ex((**jni_env).CallObjectMethod.unwrap()(jni_env, this, TRACE_ORIG_METH), jni_env); 254 | } 255 | 256 | unsafe fn get_stack_trace_field(jni_env: *mut JNIEnv) -> Result { 257 | static mut STACK_TRACE_FIELD: jfieldID = 0 as jfieldID; 258 | static ONCE: Once = ONCE_INIT; 259 | ONCE.call_once(|| { 260 | let throwable_class = get_throwable_class(jni_env).unwrap_or(ptr::null_mut()); 261 | if !throwable_class.is_null() { 262 | // We swallow exceptions in here on purpose 263 | let field_name_str = CString::new("stackTrace").unwrap(); 264 | let field_sig_str = CString::new("[Ljava/lang/StackTraceElement;").unwrap(); 265 | STACK_TRACE_FIELD = (**jni_env).GetFieldID.unwrap()(jni_env, 266 | throwable_class, 267 | field_name_str.as_ptr(), 268 | field_sig_str.as_ptr()); 269 | let _ = util::result_or_jni_ex((), jni_env); 270 | } 271 | }); 272 | if STACK_TRACE_FIELD.is_null() { return Result::Err("No stackTrace field".to_string()); } 273 | return Result::Ok(STACK_TRACE_FIELD); 274 | } 275 | 276 | unsafe fn populate_stack_params(jni_env: *mut JNIEnv, this: jobject, thread: jthread) -> Result<(), String> { 277 | // Grab the depth we want 278 | let mut depth = get_stack_trace_depth(jni_env, this)?; 279 | if depth == 0 { 280 | debug!("Unable to get stack trace depth, using {}", DEFAULT_MAX_STACK_DEPTH); 281 | depth = DEFAULT_MAX_STACK_DEPTH; 282 | } 283 | 284 | // Load the stack params, skipping the first 2 by default which we know are not the caller 285 | let mut params = get_params(jni_env, thread, depth + 10, 2)?; 286 | // Only take the last so many to match the existing frame 287 | if (depth as usize) < params.len() { 288 | let to_remove_from_head = params.len() - (depth as usize); 289 | params.drain(0..to_remove_from_head); 290 | } 291 | 292 | // Convert to object array 293 | let params_arr = params_to_object_array(jni_env, params)?; 294 | // Store in local field... 295 | (**jni_env).SetObjectField.unwrap()(jni_env, this, get_stack_params_field(jni_env)?, params_arr); 296 | return util::result_or_jni_ex((), jni_env); 297 | } 298 | 299 | unsafe fn get_throwable_class(jni_env: *mut JNIEnv) -> Result { 300 | let class_name_str = CString::new("java/lang/Throwable").unwrap(); 301 | return util::result_or_jni_ex((**jni_env).FindClass.unwrap()(jni_env, class_name_str.as_ptr()), jni_env); 302 | } 303 | 304 | unsafe fn get_stack_params_field(jni_env: *mut JNIEnv) -> Result { 305 | static mut STACK_PARAMS_FIELD: jfieldID = 0 as jfieldID; 306 | static ONCE: Once = ONCE_INIT; 307 | ONCE.call_once(|| { 308 | let throwable_class = get_throwable_class(jni_env).unwrap_or(ptr::null_mut()); 309 | if !throwable_class.is_null() { 310 | // We swallow exceptions in here on purpose 311 | let field_name_str = CString::new("stackParams").unwrap(); 312 | let field_sig_str = CString::new("[[Ljava/lang/Object;").unwrap(); 313 | STACK_PARAMS_FIELD = (**jni_env).GetFieldID.unwrap()(jni_env, 314 | throwable_class, 315 | field_name_str.as_ptr(), 316 | field_sig_str.as_ptr()); 317 | let _ = util::result_or_jni_ex((), jni_env); 318 | } 319 | }); 320 | if STACK_PARAMS_FIELD.is_null() { return Result::Err("No stackParams field".to_string()); } 321 | return Result::Ok(STACK_PARAMS_FIELD); 322 | } 323 | 324 | unsafe fn get_stack_trace_depth(jni_env: *mut JNIEnv, this: jobject) -> Result { 325 | static mut STACK_DEPTH_METH: jmethodID = 0 as jmethodID; 326 | static ONCE: Once = ONCE_INIT; 327 | ONCE.call_once(|| { 328 | let throwable_class = get_throwable_class(jni_env).unwrap_or(ptr::null_mut()); 329 | if !throwable_class.is_null() { 330 | // We swallow exceptions in here on purpose 331 | let meth_name_str = CString::new("getStackTraceDepth").unwrap(); 332 | let meth_sig_str = CString::new("()I").unwrap(); 333 | STACK_DEPTH_METH = (**jni_env).GetMethodID.unwrap()(jni_env, 334 | throwable_class, 335 | meth_name_str.as_ptr(), 336 | meth_sig_str.as_ptr()); 337 | let _ = util::result_or_jni_ex((), jni_env); 338 | } 339 | }); 340 | if STACK_DEPTH_METH.is_null() { return Result::Err("No getStackTraceDepth method".to_string()); } 341 | return util::result_or_jni_ex((**jni_env).CallIntMethod.unwrap()(jni_env, this, STACK_DEPTH_METH), jni_env) 342 | } 343 | 344 | #[no_mangle] 345 | #[allow(non_snake_case)] 346 | pub unsafe extern "C" fn Java_stackparam_StackParamNative_loadStackParams(jni_env: *mut JNIEnv, 347 | _cls: jclass, 348 | thread: jthread, 349 | max_depth: jint) -> jobject { 350 | if thread.is_null() { 351 | let _ = throw_ex_with_msg(jni_env, "java/lang/NullPointerException", "Thread is null"); 352 | return ptr::null_mut(); 353 | } 354 | if max_depth < 0 { 355 | let _ = throw_ex_with_msg(jni_env, "java/lang/IllegalArgumentException", "Max depth < 0"); 356 | return ptr::null_mut(); 357 | } 358 | // TODO 359 | return match get_params_as_object_array(jni_env, thread, max_depth, 0) { 360 | Result::Err(err_str) => { 361 | debug!("Stack param err: {}", err_str); 362 | let _ = throw_ex_with_msg(jni_env, 363 | "java/lang/RuntimeException", 364 | format!("Unexpected stack param err: {}", err_str).as_ref()); 365 | ptr::null_mut() 366 | }, 367 | Result::Ok(methods) => methods 368 | }; 369 | } 370 | 371 | unsafe fn get_params_as_object_array(jni_env: *mut JNIEnv, 372 | thread: jthread, 373 | max_depth: jint, 374 | index_until_start: usize) -> Result { 375 | return params_to_object_array(jni_env, get_params(jni_env, thread, max_depth, index_until_start)?); 376 | } 377 | 378 | unsafe fn params_to_object_array(jni_env: *mut JNIEnv, methods: Vec) -> Result { 379 | let obj_str = CString::new("java/lang/Object").unwrap(); 380 | let obj_class = util::result_or_jni_ex((**jni_env).FindClass.unwrap()(jni_env, obj_str.as_ptr()), jni_env)?; 381 | let obj_arr_str = CString::new("[Ljava/lang/Object;").unwrap(); 382 | let obj_arr_class = util::result_or_jni_ex((**jni_env).FindClass.unwrap()(jni_env, obj_arr_str.as_ptr()), jni_env)?; 383 | let ret = util::result_or_jni_ex((**jni_env).NewObjectArray.unwrap()(jni_env, 384 | methods.len() as jsize, 385 | obj_arr_class, 386 | ptr::null_mut()), jni_env)?; 387 | let mut unknown_param: jstring = ptr::null_mut(); 388 | for (method_index, method) in methods.iter().enumerate() { 389 | let param_arr = util::result_or_jni_ex((**jni_env).NewObjectArray.unwrap()(jni_env, 390 | (method.params.len() * 3) as jsize, 391 | obj_class, 392 | ptr::null_mut()), jni_env)?; 393 | for (param_index, param) in method.params.iter().enumerate() { 394 | // Goes: param name, param sig, val 395 | (**jni_env).SetObjectArrayElement.unwrap()(jni_env, 396 | param_arr, 397 | (param_index * 3) as jsize, 398 | new_string(jni_env, param.name.as_ref())?); 399 | util::result_or_jni_ex((), jni_env)?; 400 | (**jni_env).SetObjectArrayElement.unwrap()(jni_env, 401 | param_arr, 402 | ((param_index * 3) + 1) as jsize, 403 | new_string(jni_env, param.typ.as_ref())?); 404 | util::result_or_jni_ex((), jni_env)?; 405 | let val = match param.val { 406 | Some(val) => val, 407 | None => { 408 | if unknown_param.is_null() { 409 | unknown_param = new_string(jni_env, "")?; 410 | } 411 | unknown_param 412 | } 413 | }; 414 | (**jni_env).SetObjectArrayElement.unwrap()(jni_env, param_arr, ((param_index * 3) + 2) as jsize, val); 415 | util::result_or_jni_ex((), jni_env)?; 416 | } 417 | (**jni_env).SetObjectArrayElement.unwrap()(jni_env, ret, method_index as jsize, param_arr); 418 | util::result_or_jni_ex((), jni_env)?; 419 | } 420 | return Result::Ok(ret); 421 | } 422 | 423 | unsafe fn throw_ex_with_msg(jni_env: *mut JNIEnv, ex_class: &str, ex_msg: &str) -> Result<(), String> { 424 | let ex_class_str = CString::new(ex_class).unwrap(); 425 | let class = util::result_or_jni_ex((**jni_env).FindClass.unwrap()(jni_env, 426 | ex_class_str.as_ptr()), jni_env)?; 427 | let ex_msg_str = CString::new(ex_msg).unwrap(); 428 | if (**jni_env).ThrowNew.unwrap()(jni_env, class, ex_msg_str.as_ptr()) < 0 { 429 | return util::result_or_jni_ex((), jni_env); 430 | } 431 | return Result::Ok(()); 432 | } 433 | 434 | unsafe fn get_params(jni_env: *mut JNIEnv, 435 | thread: jthread, 436 | max_depth: jint, 437 | index_until_start: usize) -> Result, String> { 438 | // Grab the trace 439 | let trace = get_stack_trace(thread, max_depth)?; 440 | // Go over every frame getting the info 441 | let mut ret: Vec = Vec::new(); 442 | for (index, frame) in trace.iter().enumerate() { 443 | if index >= index_until_start { 444 | ret.push(get_frame_params(jni_env, thread, frame, index as jint)?); 445 | } 446 | } 447 | return Result::Ok(ret); 448 | } 449 | 450 | unsafe fn class_sig(class: jclass) -> Result { 451 | let mut sig: *mut c_char = 0 as *mut c_char; 452 | let sig_res = (**JVMTI_ENV).GetClassSignature.unwrap()(JVMTI_ENV, class, &mut sig, ptr::null_mut()); 453 | util::unit_or_jvmti_err(sig_res)?; 454 | let sig_str = CStr::from_ptr(sig).to_string_lossy().clone().into_owned(); 455 | dealloc(sig)?; 456 | return Result::Ok(sig_str); 457 | } 458 | 459 | unsafe fn class_sig_from_obj(jni_env: *mut JNIEnv, obj: jobject) -> Result { 460 | let class = util::result_or_jni_ex((**jni_env).GetObjectClass.unwrap()(jni_env, obj), jni_env)?; 461 | return class_sig(class); 462 | } 463 | 464 | unsafe fn method_name(method: jmethodID) -> Result { 465 | let mut name: *mut c_char = 0 as *mut c_char; 466 | let name_res = (**JVMTI_ENV).GetMethodName.unwrap()(JVMTI_ENV, method, &mut name, ptr::null_mut(), ptr::null_mut()); 467 | util::unit_or_jvmti_err(name_res)?; 468 | let name_str = CStr::from_ptr(name).to_string_lossy().clone().into_owned(); 469 | dealloc(name)?; 470 | return Result::Ok(name_str); 471 | } 472 | 473 | unsafe fn get_stack_trace(thread: jthread, max_depth: jint) -> Result, String> { 474 | let mut frames: Vec = Vec::with_capacity(max_depth as usize); 475 | let mut frame_count: jint = 0; 476 | let trace_res = (**JVMTI_ENV).GetStackTrace.unwrap()(JVMTI_ENV, 477 | thread, 478 | 0, 479 | max_depth, 480 | frames.as_mut_ptr(), &mut frame_count); 481 | util::unit_or_jvmti_err(trace_res)?; 482 | frames.set_len(frame_count as usize); 483 | frames.shrink_to_fit(); 484 | return Result::Ok(frames); 485 | } 486 | 487 | unsafe fn get_frame_params(jni_env: *mut JNIEnv, thread: jthread, frame: &jvmtiFrameInfo, depth: jint) -> Result { 488 | if log_enabled!(Trace) { trace!("Getting info for {}", method_name(frame.method)?); } 489 | let mut method = get_method_param_info(frame.method)?; 490 | let is_native = method.mods & 0x00000100 != 0; 491 | if is_native { 492 | trace!("Native method, not applying local table or getting values"); 493 | } else { 494 | trace!("Applying local table"); 495 | apply_local_var_table(frame.method, &mut method)?; 496 | } 497 | // Apply the param values if we can get them 498 | for param in method.params.iter_mut() { 499 | trace!("Var named {} at slot {} has type {}", param.name, param.slot, param.typ); 500 | // Now get the local var if we can 501 | if param.slot == 0 && param.name == "this" { 502 | param.val = Some(get_this(thread, depth)?); 503 | } else if !is_native { 504 | param.val = Some(get_local_var(jni_env, thread, depth, param.slot, param.typ.as_ref())?); 505 | } 506 | } 507 | return Result::Ok(method); 508 | } 509 | 510 | unsafe fn new_string(jni_env: *mut JNIEnv, str: &str) -> Result { 511 | let cstr = CString::new(str).unwrap(); 512 | return util::result_or_jni_ex((**jni_env).NewStringUTF.unwrap()(jni_env, cstr.as_ptr()), jni_env); 513 | } 514 | 515 | unsafe fn get_this(thread: jthread, depth: jint) -> Result { 516 | let mut obj: jobject = ptr::null_mut(); 517 | let inst_res = (**JVMTI_ENV).GetLocalInstance.unwrap()(JVMTI_ENV, thread, depth, &mut obj); 518 | return util::result_or_jvmti_err(obj, inst_res); 519 | } 520 | 521 | unsafe fn get_local_var(jni_env: *mut JNIEnv, thread: jthread, depth: jint, slot: jint, typ: &str) -> Result { 522 | return match typ { 523 | "Z" => { 524 | let val = get_local_int(thread, depth, slot)?; 525 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.boolean; 526 | util::result_or_jni_ex( 527 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val as c_uint), jni_env) 528 | }, 529 | "B" => { 530 | let val = get_local_int(thread, depth, slot)?; 531 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.byte; 532 | util::result_or_jni_ex( 533 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val as c_int), jni_env) 534 | }, 535 | "C" => { 536 | let val = get_local_int(thread, depth, slot)?; 537 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.char; 538 | util::result_or_jni_ex( 539 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val as c_uint), jni_env) 540 | }, 541 | "S" => { 542 | let val = get_local_int(thread, depth, slot)?; 543 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.short; 544 | util::result_or_jni_ex( 545 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val as c_int), jni_env) 546 | }, 547 | "I" => { 548 | let val = get_local_int(thread, depth, slot)?; 549 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.int; 550 | util::result_or_jni_ex( 551 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val), jni_env) 552 | }, 553 | "J" => { 554 | let mut val: jlong = 0; 555 | util::unit_or_jvmti_err((**JVMTI_ENV).GetLocalLong.unwrap()(JVMTI_ENV, thread, depth, slot, &mut val))?; 556 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.long; 557 | util::result_or_jni_ex( 558 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val), jni_env) 559 | }, 560 | "F" => { 561 | let mut val: jfloat = 0.0; 562 | util::unit_or_jvmti_err((**JVMTI_ENV).GetLocalFloat.unwrap()(JVMTI_ENV, thread, depth, slot, &mut val))?; 563 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.float; 564 | util::result_or_jni_ex( 565 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val as c_double), jni_env) 566 | }, 567 | "D" => { 568 | let mut val: jdouble = 0.0; 569 | util::unit_or_jvmti_err((**JVMTI_ENV).GetLocalDouble.unwrap()(JVMTI_ENV, thread, depth, slot, &mut val))?; 570 | let (box_class, box_meth) = primitive_box_methods(jni_env)?.double; 571 | util::result_or_jni_ex( 572 | (**jni_env).CallStaticObjectMethod.unwrap()(jni_env, box_class, box_meth, val as c_double), jni_env) 573 | }, 574 | typ if typ.starts_with("[") || typ.starts_with("L") => { 575 | let mut val: jobject = ptr::null_mut(); 576 | let local_res = (**JVMTI_ENV).GetLocalObject.unwrap()(JVMTI_ENV, thread, depth, slot, &mut val); 577 | util::result_or_jvmti_err(val, local_res) 578 | }, 579 | _ => Result::Err(format!("Unrecognized type: {}", typ)) 580 | } 581 | } 582 | 583 | unsafe fn get_local_int(thread: jthread, depth: jint, slot: jint) -> Result { 584 | let mut val: jint = 0; 585 | let local_res = (**JVMTI_ENV).GetLocalInt.unwrap()(JVMTI_ENV, thread, depth, slot, &mut val); 586 | return util::result_or_jvmti_err(val, local_res); 587 | } 588 | 589 | struct MethodInfo { 590 | mods: jint, 591 | params: Vec, 592 | } 593 | 594 | struct Param { 595 | name: String, 596 | typ: String, 597 | slot: jint, 598 | val: Option, 599 | } 600 | 601 | unsafe fn get_method_param_info(method: jmethodID) -> Result { 602 | let mut ret = MethodInfo { 603 | mods: get_method_modifiers(method)?, 604 | params: Vec::new(), 605 | }; 606 | let is_static = ret.mods & 0x00000008 != 0; 607 | let mut sig: *mut c_char = 0 as *mut c_char; 608 | let name_res = (**JVMTI_ENV).GetMethodName.unwrap()(JVMTI_ENV, method, ptr::null_mut(), &mut sig, ptr::null_mut()); 609 | util::unit_or_jvmti_err(name_res)?; 610 | // Parse the sig 611 | let sig_str = CStr::from_ptr(sig).to_str().map_err(|_| "Error parsing method sig")?; 612 | let mut sig_chars = sig_str.chars(); 613 | if sig_chars.next() != Some('(') { return Result::Err(format!("Str {} missing opening param", sig_str)); } 614 | let mut working_str = "".to_string(); 615 | let mut in_obj = false; 616 | let mut slot_counter = 0; 617 | if !is_static { 618 | ret.params.push(Param { 619 | name: "this".to_string(), 620 | typ: get_class_signature(get_method_declaring_class(method)?)?, 621 | slot: slot_counter, 622 | val: None, 623 | }); 624 | slot_counter += 1; 625 | } 626 | let mut param_counter = 0; 627 | loop { 628 | match sig_chars.next() { 629 | None => { 630 | let _ = dealloc(sig as *mut c_char); 631 | return Result::Err("Unexpected end of desc".to_string()); 632 | }, 633 | Some(c) => match c { 634 | ')' => { 635 | dealloc(sig)?; 636 | return Result::Ok(ret); 637 | }, 638 | ';' if in_obj => { 639 | working_str.push(';'); 640 | ret.params.push(Param { 641 | name: format!("arg{}", param_counter), 642 | typ: working_str.clone(), 643 | slot: slot_counter, 644 | val: None, 645 | }); 646 | param_counter += 1; 647 | slot_counter += 1; 648 | in_obj = false; 649 | working_str.clear(); 650 | }, 651 | _ if in_obj => working_str.push(c), 652 | 'L' => { 653 | in_obj = true; 654 | working_str.push('L'); 655 | } 656 | '[' => working_str.push('['), 657 | 'B' | 'C' | 'D' | 'F' | 'I' | 'J' | 'S' | 'Z' => { 658 | working_str.push(c); 659 | ret.params.push(Param { 660 | name: format!("arg{}", param_counter), 661 | typ: working_str.clone(), 662 | slot: slot_counter, 663 | val: None, 664 | }); 665 | param_counter += 1; 666 | if c == 'J' || c == 'D' { 667 | slot_counter += 2; 668 | } else { 669 | slot_counter += 1; 670 | } 671 | working_str.clear(); 672 | }, 673 | _ => { 674 | // Ignore dealloc err 675 | let _ = dealloc(sig as *mut c_char); 676 | return Result::Err(format!("Unrecognized char: {}", c)); 677 | }, 678 | }, 679 | } 680 | } 681 | } 682 | 683 | unsafe fn dealloc(v: *mut T) -> Result<(), String> { 684 | let de_res = (**JVMTI_ENV).Deallocate.unwrap()(JVMTI_ENV, v as *mut c_uchar); 685 | return util::unit_or_jvmti_err(de_res); 686 | } 687 | 688 | unsafe fn get_class_signature(class: jclass) -> Result { 689 | let mut ret: *mut c_char = ptr::null_mut(); 690 | let sig_res = (**JVMTI_ENV).GetClassSignature.unwrap()(JVMTI_ENV, class, &mut ret, ptr::null_mut()); 691 | util::unit_or_jvmti_err(sig_res)?; 692 | let sig_str = CStr::from_ptr(ret).to_string_lossy().clone().into_owned(); 693 | dealloc(ret)?; 694 | return Result::Ok(sig_str); 695 | } 696 | 697 | unsafe fn get_method_declaring_class(method: jmethodID) -> Result { 698 | let mut ret: jclass = ptr::null_mut(); 699 | let cls_res = (**JVMTI_ENV).GetMethodDeclaringClass.unwrap()(JVMTI_ENV, method, &mut ret); 700 | return util::result_or_jvmti_err(ret, cls_res); 701 | } 702 | 703 | unsafe fn get_method_modifiers(method: jmethodID) -> Result { 704 | let mut mods: jint = 0; 705 | let mod_res = (**JVMTI_ENV).GetMethodModifiers.unwrap()(JVMTI_ENV, method, &mut mods); 706 | return util::result_or_jvmti_err(mods, mod_res); 707 | } 708 | 709 | unsafe fn apply_local_var_table(method: jmethodID, info: &mut MethodInfo) -> Result<(), String> { 710 | let mut entries: *mut jvmtiLocalVariableEntry = ptr::null_mut(); 711 | let mut entry_count: jint = 0; 712 | let table_res = (**JVMTI_ENV).GetLocalVariableTable.unwrap()(JVMTI_ENV, method, &mut entry_count, &mut entries); 713 | if table_res as u32 == jvmtiError::JVMTI_ERROR_ABSENT_INFORMATION as u32 { 714 | // When information is absent, we don't care 715 | return Result::Ok(()); 716 | } 717 | util::unit_or_jvmti_err(table_res)?; 718 | let entry_slice = slice::from_raw_parts(entries, entry_count as usize); 719 | if log_enabled!(Trace) { 720 | for entry in entry_slice { 721 | trace!("Var table entry named {} at slot {} has type {}", 722 | CStr::from_ptr(entry.name).to_string_lossy(), 723 | entry.slot, CStr::from_ptr(entry.signature).to_string_lossy()); 724 | } 725 | } 726 | let mut err: Option = None; 727 | 'param_loop: for param in info.params.iter_mut() { 728 | // Find the entry at the expected slot and start location 0, but break 729 | // if there is something else at that slot but not at location 0 730 | let mut maybe_entry: Option<&jvmtiLocalVariableEntry> = None; 731 | for entry in entry_slice { 732 | if entry.slot == param.slot { 733 | if entry.start_location != 0 { 734 | err = Some(format!("Var at slot {} should be location 0, but is {}", entry.slot, entry.start_location)); 735 | break 'param_loop; 736 | } 737 | maybe_entry = Some(entry); 738 | } 739 | } 740 | let entry = match maybe_entry { 741 | Some(entry) => entry, 742 | None => { 743 | err = Some(format!("Can't find var entry for slot {} and location 0", param.slot)); 744 | break; 745 | }, 746 | }; 747 | param.name = CStr::from_ptr(entry.name).to_string_lossy().clone().into_owned(); 748 | // Don't need to own this 749 | let type_str = CStr::from_ptr(entry.signature).to_string_lossy(); 750 | if type_str != param.typ { 751 | err = Some(format!("Var {} expected type {}, got {}", param.name, param.typ, type_str.clone())); 752 | break; 753 | } 754 | } 755 | // Dealloc everything, ignoring errors 756 | for entry in entry_slice { 757 | let _ = dealloc(entry.name); 758 | let _ = dealloc(entry.signature); 759 | if !entry.generic_signature.is_null() { 760 | let _ = dealloc(entry.generic_signature); 761 | } 762 | } 763 | let _ = dealloc(entries); 764 | return match err { 765 | Some(err_str) => Result::Err(err_str), 766 | None => Result::Ok(()) 767 | }; 768 | } 769 | 770 | type MethodRef = (jclass, jmethodID); 771 | 772 | struct PrimitiveBoxMethods { 773 | boolean: MethodRef, 774 | byte: MethodRef, 775 | char: MethodRef, 776 | short: MethodRef, 777 | int: MethodRef, 778 | long: MethodRef, 779 | float: MethodRef, 780 | double: MethodRef 781 | } 782 | 783 | unsafe fn primitive_box_methods(jni_env: *mut JNIEnv) -> Result { 784 | static mut PRIM_BOX_METHS: *const Result = 0 as *const Result; 785 | static ONCE: Once = ONCE_INIT; 786 | ONCE.call_once(|| { 787 | unsafe fn method_ref(jni_env: *mut JNIEnv, class_name: &str, method_desc: &str) -> Result { 788 | let class_name_str = CString::new(class_name).unwrap(); 789 | let class = util::result_or_jni_ex((**jni_env).FindClass.unwrap()(jni_env, 790 | class_name_str.as_ptr()), jni_env)?; 791 | let meth_name_str = CString::new("valueOf").unwrap(); 792 | let desc_str = CString::new(method_desc).unwrap(); 793 | let method = util::result_or_jni_ex((**jni_env).GetStaticMethodID.unwrap()(jni_env, 794 | class, 795 | meth_name_str.as_ptr(), 796 | desc_str.as_ptr()), jni_env)?; 797 | return Result::Ok((class, method)); 798 | } 799 | unsafe fn prim_box_meths(jni_env: *mut JNIEnv) -> Result { 800 | return Result::Ok(PrimitiveBoxMethods { 801 | boolean: method_ref(jni_env, "java/lang/Boolean", "(Z)Ljava/lang/Boolean;")?, 802 | byte: method_ref(jni_env, "java/lang/Byte", "(B)Ljava/lang/Byte;")?, 803 | char: method_ref(jni_env, "java/lang/Character", "(C)Ljava/lang/Character;")?, 804 | short: method_ref(jni_env, "java/lang/Short", "(S)Ljava/lang/Short;")?, 805 | int: method_ref(jni_env, "java/lang/Integer", "(I)Ljava/lang/Integer;")?, 806 | long: method_ref(jni_env, "java/lang/Long", "(J)Ljava/lang/Long;")?, 807 | float: method_ref(jni_env, "java/lang/Float", "(F)Ljava/lang/Float;")?, 808 | double: method_ref(jni_env, "java/lang/Double", "(D)Ljava/lang/Double;")?, 809 | }); 810 | } 811 | PRIM_BOX_METHS = mem::transmute(Box::new(prim_box_meths(jni_env))); 812 | }); 813 | return ptr::read(PRIM_BOX_METHS); 814 | } -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | extern crate jni_sys; 2 | extern crate env_logger; 3 | 4 | use jni_sys::{JNIEnv, jclass, jmethodID, jint}; 5 | use jvmti_sys::{jvmtiEnv, jvmtiError}; 6 | use std::ffi::CStr; 7 | use std::ptr; 8 | use std::slice; 9 | use std::os::raw::c_char; 10 | 11 | pub unsafe fn result_or_jni_ex(res: T, jni_env: *mut JNIEnv) -> Result { 12 | if (**jni_env).ExceptionCheck.unwrap()(jni_env) == 1 { 13 | // TODO: extract the exception info instead of dumping to stderr 14 | (**jni_env).ExceptionDescribe.unwrap()(jni_env); 15 | (**jni_env).ExceptionClear.unwrap()(jni_env); 16 | return Result::Err("Unexpected exception, logged to stderr".to_string()); 17 | } 18 | return Result::Ok(res); 19 | } 20 | 21 | pub unsafe fn result_or_jvmti_err(res: T, err_maybe: jvmtiError) -> Result { 22 | if err_maybe as i32 != 0 { 23 | return Result::Err(format!("Unexpected jvmti error: {:?}", err_maybe)); 24 | } 25 | return Result::Ok(res); 26 | } 27 | 28 | pub unsafe fn unit_or_jvmti_err(res: jvmtiError) -> Result<(), String> { 29 | if res as i32 != 0 { 30 | return Result::Err(format!("Unexpected jvmti error: {:?}", res)); 31 | } 32 | return Result::Ok(()); 33 | } 34 | 35 | #[allow(dead_code)] 36 | pub unsafe fn find_method(jvmti_env: *mut jvmtiEnv, 37 | class: jclass, 38 | name: &str) 39 | -> Result { 40 | // TODO: sad we can't use GetMethodID 41 | // ref: http://stackoverflow.com/questions/42746496/call-class-method-from-manually-defined-class-in-jvmti 42 | let mut method_count: jint = 0; 43 | let mut methods: *mut jmethodID = ptr::null_mut(); 44 | let meth_ret = 45 | (**jvmti_env).GetClassMethods.unwrap()(jvmti_env, class, &mut method_count, &mut methods); 46 | try!(unit_or_jvmti_err(meth_ret)); 47 | let method_slice: &[jmethodID] = slice::from_raw_parts_mut(methods, method_count as usize); 48 | let ret = method_slice.into_iter().find(|&&m| { 49 | let mut method_name: *mut c_char = ptr::null_mut(); 50 | let name_ret = (**jvmti_env).GetMethodName.unwrap()(jvmti_env, 51 | m, 52 | &mut method_name, 53 | ptr::null_mut(), 54 | ptr::null_mut()) as 55 | i32; 56 | name_ret == 0 && CStr::from_ptr(method_name).to_str().unwrap() == name 57 | }); 58 | return match ret { 59 | Some(&method) => Result::Ok(method), 60 | None => Result::Err("Method not found".to_string()), 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /tests/bytecode_tests.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate env_logger; 4 | extern crate zip; 5 | extern crate stackparam; 6 | 7 | use std::env; 8 | use std::path::PathBuf; 9 | use std::fs::File; 10 | use zip::ZipArchive; 11 | use std::io::{Cursor, Read}; 12 | use stackparam::bytecode::io::reader::ClassReader; 13 | use stackparam::bytecode::io::writer::ClassWriter; 14 | 15 | #[test] 16 | #[ignore] 17 | fn bytecode_tests() { 18 | let _ = env_logger::init(); 19 | 20 | // Find rt.jar 21 | let java_home = env::var("JAVA_HOME").expect("Unable to find JAVA_HOME"); 22 | let java_home_path = PathBuf::from(java_home); 23 | let rt_jar_path: PathBuf = { 24 | // Try JDK first 25 | let mut rt_maybe = java_home_path.join("jre/lib/rt.jar"); 26 | if !rt_maybe.is_file() { 27 | rt_maybe = java_home_path.join("lib/rt.jar"); 28 | assert!(rt_maybe.is_file(), "Unable to find rt.jar on JAVA_HOME path: {}", java_home_path.display()); 29 | } 30 | rt_maybe.to_owned() 31 | }; 32 | 33 | // Check each class 34 | let file = File::open(rt_jar_path).unwrap(); 35 | let mut rt_jar = ZipArchive::new(file).unwrap(); 36 | for i in 0..rt_jar.len() { 37 | let mut file = rt_jar.by_index(i).unwrap(); 38 | if file.name().ends_with(".class") { 39 | // Read the class and just write it back and confirm same bytes 40 | let mut in_bytes: Vec = Vec::new(); 41 | file.read_to_end(&mut in_bytes).expect(&format!("Cannot read {}", file.name())); 42 | let mut in_curs = Cursor::new(in_bytes); 43 | let class_file = ClassReader::read_class(&mut in_curs).expect(&format!("Failed parsing {}", file.name())); 44 | let mut out_curs = Cursor::new(Vec::new()); 45 | ClassWriter::new(&mut out_curs).write_class(&class_file).expect(&format!("Failed writing {}", file.name())); 46 | 47 | in_bytes = in_curs.into_inner(); 48 | let out_bytes = out_curs.into_inner(); 49 | debug!("For {} - {} and {}", file.name(), in_bytes.len(), out_bytes.len()); 50 | assert_eq!(in_bytes.as_slice(), out_bytes.as_slice(), "Not same for {}", file.name()); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /tests/external_java_tests.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate env_logger; 4 | 5 | use std::process::Command; 6 | 7 | #[test] 8 | fn java_tests() { 9 | let _ = env_logger::init(); 10 | 11 | // Run the gradle test that references this agent 12 | let javalib_path = std::env::current_dir().expect("No current dir").join("javalib"); 13 | let mut gradle_path = javalib_path.join("gradlew"); 14 | if cfg!(target_os = "windows") { 15 | gradle_path.set_extension("bat"); 16 | } 17 | info!("Starting Gradle at {}", gradle_path.to_string_lossy()); 18 | 19 | let output = Command::new(gradle_path) 20 | .current_dir(javalib_path) 21 | .arg("--no-daemon") 22 | .arg(":agent-tests:cleanTest") 23 | .arg(":agent-tests:test") 24 | .output() 25 | .expect("Couldn't start gradle"); 26 | 27 | let stdout = String::from_utf8_lossy(&output.stdout); 28 | info!("stdout: {}", stdout); 29 | info!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 30 | 31 | assert!(output.status.success()); 32 | } 33 | --------------------------------------------------------------------------------