Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ venv/
__pycache__/
_old/
fuzzing*/
java/
jasmin/
work/
evaluation/test/
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,31 @@ pip install -e .
- Edit the template `run_template.sh' to point to the relevant filepaths on your machine
- Run the shell script



# UROP
Things I have worked on:
- Switching paths after hotspot JIT compiled with one path to trigger deoptimization
- Created a fuzzer that produces Java code where each function represents nodes, to prevent everything being jammed in one method like in Java bytecode
- Tested the fuzzer on OpenJDK and GraalVM
- Running multiple paths concurrently to test the Thread-Safeness of the JIT compiler
- Created a hotspot log analyzer that summarizes compilation patterns, inlines, number of invocations etc.

Potential areas to look into:
- GraalVM - Less mature JVM compared to OpenJDK so higher chance of finding bugs. Uncommon traps are not being set unlike OpenJDK probably because they use different optimizations so creating a fuzzer to target GraalVM optimizations could be interesting.
- Javabc - the compilation fails completely because of an irreducible loop when the graph size is too big. It does compile on small graphs and setting the MaxDuplicationFactor can increase that bounds.
- Java - all the inlining is failing for small and large graphs; it could be causing an error and is getting aborted(?)
- Generating semi-complex graphs - javabc is producing an irreducible loop that is not optimizable and in the Java fuzzer, each function is too simple to cause any bugs. Creating a fuzzer that does something in between the two might exercise the JIT compiler more.
- Concurrency - I've only tested running multiple paths concurrently. If the deoptimization is dealing with values in the registers real-time during deoptimization instead of only falling back to lower tiered code, it may be complex enough to be a source of bugs
- Dynamic dispatch - having an interface and calling different child class methods should trigger optimization/deoptimization which isn't tested in the current fuzzer.


Notes:
- Running the fuzzer with switching paths can be specified with ACTION='fuzz_with_changing_paths' in the argument
- I've renewed the wrapper to accept single or multiple paths. The normal one and multithread one are both in the root directory so copy-pasting it to the wrapper folder should switch the wrapper.
- JIT-related flags that I've used are in the java/javabc runner which is commented/uncommented to toggle
- Analyzer
- The logs can be specified in the setup_logs function under analyze.py
- It parses the xml and looks for tags task_queued, nmethod and task with method names that start with "TestCase"
- The tags are linked by compile_id to gather the statistics
- Occasionally, the hotspot log emits data in the <![CDATA[ ]]> under fragment tag and if important information is emitted there, the parser fails to find it and crashes sometimes. The information inside these tags is sometimes incomplete as well so I didn't know how to handle it so I just reran the fuzzer in case that happened.
267 changes: 267 additions & 0 deletions WrapperMultiThread.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import java.io.File;
import java.util.Scanner;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.lang.Math;
import java.lang.Thread;
import java.lang.reflect.*;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

class Path {
String inputFilename;
int dir[];
int actualOutput[];
int expectedOutput[];
int outputSize;

Path(String inputFilename) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ParseException {
this.inputFilename = inputFilename;

// get direction size and expected output size from file
JSONParser parser = new JSONParser();
JSONObject jsonObject = (JSONObject) parser.parse(new FileReader(inputFilename));

JSONArray jsonDir = (JSONArray) jsonObject.get("dirs");
JSONArray jsonOutput = (JSONArray) jsonObject.get("expected_output");

outputSize = jsonOutput.size();
int dirSize = jsonDir.size();

// create arrays
dir = new int[dirSize];
actualOutput = new int[2*outputSize];
expectedOutput = new int[outputSize];

// fill arrays
int counter = 0;
for (Object o : jsonDir) {
dir[counter] = (int) (long) o;
counter++;
}
counter = 0;
for (Object o : jsonOutput) {
expectedOutput[counter] = (int) (long) o;
counter++;
}
for (int i = outputSize; i < 2*outputSize; i++) {
actualOutput[i] = -1;
}
}

boolean compare() {
// check actual and expected path are the same
for(int i = 0; i < outputSize; i++){
if(expectedOutput[i] != actualOutput[i]){
return false;
}
}

// check no overflow occurred
for(int i = outputSize; i < 2*outputSize; i++){
if(actualOutput[i] != -1){
return false;
}
}

return true;
}

void recordOutput(String fileName, boolean result) {
if (fileName == null) {
System.out.print("Expected and actual output are");

if(result) {
System.out.print("");
}
else {
System.out.print(" not");
}

System.out.print(" the same\n");

System.out.print("Expected output:");

for(int i = 0; i < outputSize; i++){
System.out.print(" " + expectedOutput[i]);
}

System.out.print("\nActual output: ");

for(int i = 0; i < 2*outputSize; i++){
System.out.print(" " + actualOutput[i]);
}

System.out.print("\n");
return;
}

try{
FileWriter fw = new FileWriter(fileName, true);

fw.write("Test name: " + inputFilename + "\n");

fw.write("Expected and actual output are");

if(result) {
fw.write("");
}
else {
fw.write(" not");
}

fw.write(" the same\n");

fw.write("Expected output:");

for(int i = 0; i < outputSize; i++){
fw.write(" " + expectedOutput[i]);
}

fw.write("\n");


fw.write("Actual output: ");

for(int i = 0; i < 2*outputSize; i++){
fw.write(" " + actualOutput[i]);
}

fw.write("\n");

fw.close();

}catch(IOException e){
System.out.println("Error writing results to file");
}
}

void resetActualOutput() {
for (int i = 0; i < actualOutput.length; ++i) {
actualOutput[i] = -1;
}
}
}

class Wrapper{
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ParseException, InterruptedException {
// parse args
String inputFilenames[] = args[0].split(":");
String outputFilename = args[1];
String badOutputFilename = args[2];
int nFunctionRepeats = Integer.parseInt(args[3]);

Path paths[] = new Path[inputFilenames.length];

for (int i = 0; i < inputFilenames.length; i++) {
paths[i] = new Path(inputFilenames[i]);
}

// To keep track if any of them failed
AtomicBoolean success = new AtomicBoolean(true);

Thread threads[] = new Thread[paths.length];

// Initializing threads
for (int i = 0; i < threads.length; ++i) {
final Path path = paths[i];
final String tempBadOutputFilename = badOutputFilename + i;

threads[i] = new Thread(() -> {
boolean threadSuccess = true;
boolean result = true;
TestCase test = new TestCase();

for(int j = 0; j < nFunctionRepeats; j++){
path.resetActualOutput();
test.testCase(path.dir, path.actualOutput);

// compare each expected and actual and write out if any are inconsistent
result = path.compare();
threadSuccess = threadSuccess && result;

// record bad output in separate file
if (!result){
path.recordOutput(tempBadOutputFilename, result);
}

}

// Update success if any of paths failed in this thread
success.set(success.get() && threadSuccess);

// Print to console
path.recordOutput(null, result);
});

// Catch exceptions occured in threads
threads[i].setUncaughtExceptionHandler((thread, exception) -> {
boolean result = path.compare();
path.recordOutput(outputFilename, result);
path.recordOutput(badOutputFilename, result);
path.recordOutput(null, result);

exception.printStackTrace();
System.exit(1);
});
}

// create class
TestCase test = new TestCase();

// Running one path to let it JIT compile
for(int i = 0; i < nFunctionRepeats; i++){
try {
paths[0].resetActualOutput();
test.testCase(paths[0].dir, paths[0].actualOutput);
} catch (Exception e) {
boolean result = paths[0].compare();
paths[0].recordOutput(outputFilename, result);
paths[0].recordOutput(badOutputFilename, result);
paths[0].recordOutput(null, result);

e.printStackTrace();
System.exit(1);
}

// compare each expected and actual and write out if any are inconsistent
boolean result = paths[0].compare();
success.set(success.get() && result);

// record bad output in separate file
if (!result){
paths[0].recordOutput(badOutputFilename, result);
}

}

// Launching all threads
for (Thread thread : threads) {
thread.start();
}

for (Thread thread : threads) {
thread.join();
}

for (Path path : paths) {
boolean result = path.compare();
path.recordOutput(outputFilename, result);
if (!result) paths[0].recordOutput(badOutputFilename, result);
}


if(success.get()) {
System.exit(0);
}

System.exit(1);

}
}
Loading