An implementation of property probes.
Quick overview of features (5 minutes): https://www.youtube.com/watch?v=d-KvFy5h9W0
Installation & getting started: https://www.youtube.com/watch?v=1beyfNhUQEg
- Download codeprober.jar from the latest release.
- Start like this:
java -jar codeprober.jar your-analyzer-or-compiler.jar [args-to-forward-to-compiler-on-each-request]
For example, if you have codeprober.jar in your downloads directory, and your tool is called compiler.jar
and is located in your home directory, then run:
java -jar ~/Downloads/codeprober.jar ~/compiler.jar
Once started, you should open http://localhost:8000 in your browser.
When the page is loaded, you'll find a Help
button on the right side which can help you further.
To use CodeProber, you must have a compiler or analyzer that follows certain conventions. Any tool built using JastAdd follow these conventions automatically. Support for non-JastAdd tools (and formalizing the conventions) is planned future work (also, see 'I didn't use JastAdd' below).
Even if you built your tool with JastAdd, you must add some way for CodeProber to transform a source file into an AST. There are two options.
The first option is preferered, and it is to add a method CodeProber_parse
method in your main class.
It can look like this:
public static Object CodeProber_parse(String[] args) throws Throwable {
// 'args' has at least one entry.
// First are all optional args (see "args-to-forward-to-compiler-on-each-request" above).
// The last entry in the array is path to a source file containing the CodeProber editor text.
String sourceFile = args[args.length - 1];
// "parse" is expected to take the path to a source file and transform it to the root of an AST
return parse(sourceFile);
}
CodeProber will invoke this and use the return value as the entry point into your AST.
The second option is for CodeProber to use your normal main method as an entry point. Since main cannot return anything, the resulting AST must instead be assigned to a static field within your main class. In total, there are therefore two changes that are required: In your main java file, add the following declaration:
public static Object CodeProber_root_node;
Then, immediately after parsing the input file(s), assign the root of your AST to this variable. E.g:
Program myAst = parse(...);
CodeProber_root_node = myAst;
The CodeProber_root_node
variable will be used by CodeProber as an entry point.
Since many tools perform semantic analysis and call System.exit in main if an error is encountered, CodeProber attempts to install a System.exit interceptor when using the main method.
This has issues on newer versions of java (see "System.exit/SecurityManager problem" below).
If you have problems with this, consider using CodeProber_parse
instead, which doesn't rely on intercepting System.exit.
If you define both CodeProber_parse
and CodeProber_root_node
, then CodeProber_parse
takes precedence.
If you previously used DrAST
(https://bitbucket.org/jastadd/drast/src/master/), then you likely have DrAST_root_node
declared and assigned.
CodeProber will use this as a fallback if neither CodeProber_parse
nor CodeProber_root_node
is not defined, so you don't have to do any changes.
However, the help/warning messages inside CodeProber
that reference the root node will reference CodeProber_root_node
, even if you don't have it.
So for a more consistent experience, consider adding a specific declaration for CodeProber (or even better, use CodeProber_parse
).
There are a few optional environment variables that can be set.
Key | Default value | Description |
---|---|---|
PORT | 8000 | The port to serve HTML/JS/websocket request on. This is the port you visit in your browser, e.g the '8000' in 'http://localhost:8000' |
WEB_SERVER_PORT | null | The port for for HTML/JS (non-websocket) requests. If not set, PORT will be used. |
WEBSOCKET_SERVER_PORT | null | The port for websocket requests. This isn't visible to the user, and normally doesn't need to be changed. If not set, PORT will be used. This can be set to http to delegate all websocket traffic to normal HTTP requests instead. This is worse for performance, but can be necessary if hosting CodeProber in a place where websocket doesn't work as expected. |
PERMIT_REMOTE_CONNECTIONS | false | Whether or not to permit unauthenticated remote (non-local) connections to the server. Most compilers/analyzers read/write files on your computer based on user input. By allowing remote connections, you open up a potential vulnerability. Only set to true when on a trusted network, or if running inside a sandboxed environment. |
WEB_RESOURCES_OVERRIDE | null | A file path that should be used to serve web resources (e.g HTML/JS/etc). If null, then resources are read from the classpath. Setting this can be benificial during development of the code prober tool itself (set it to client/public/ ), but there is very little point in setting it for normal tool users. |
CODESPACES_COMPATIBILITY_HACK | true | When running CodeProber inside the Github Codespaces web editor, CodeProber will need to modify the protocol and url used to connect to the websocket server. Normally, CodeProber connects to ws://host:8080 (or whichever port is used). In Codespaces, CodeProber needs to connect to wss://{host.replace(8000, 8080)} , i.e wss instead of ws , and the port(s) are part of the host rather than the normal : port part of the URL. If the compatibility hack doesn't work for you, you can disable it by setting CODESPACES_COMPATIBILITY_HACK=false . If you get it to work without the hack; please open an issue on the CodeProber repository and tell us how you did it! Otherwise, try setting WEBSOCKET_SERVER_PORT to http instead. |
Example invocation where some of these are set:
PORT=8005 WEB_RESOURCES_OVERRIDE=client/public/ java -jar codeprober.jar /path/to/your/compiler/or/analyzer.jar
CodeProber should run on any OS on Java 8 and above. However, sometimes things don't work as they should. This section has some known issues and their workarounds.
By default, CodeProber only accepts requests from localhost. When you run CodeProber inside a container (for example WSL or Docker) then requests from your host machine can appear as remote, not local. To solve this you have two options:
- Use the URL printed to the terminal when you start CodeProber. It contains an authorization key that enables non-local access. If connecting to a non-localhost url, please make sure the "?auth=some_key_here" part of the URL printed to the terminal is included.
- Add the
PERMIT_REMOTE_CONNECTIONS
environment variable mentioned above.
If you run Java version 17+ then you may run into error messages that mention "Failed installing System.exit interceptor". For many language tools, the main function behaves like this:
- Parse the incoming document
- Perform semantic analysis, print results
- If any errors were detected, call System.exit(1);
To avoid the System.exit call killing the CodeProber process, CodeProber uses System.setSecurityManager(..)
to intercept all calls to System.exit.
As of Java 17, this feature is disabled by default. You can re-enable it by adding the system property 'java.security.manager=allow'. I.e run CodeProber with:
java -Djava.security.manager=allow -jar codeprober.jar path/to/your/analyzer-or-compiler.jar [args-to-forward-to-compiler-on-each-request]
Alterntiavely, add a CodeProber_parse
method as mentioned above in the Compatibility
section.
Here, CodeProber does not use a System.exit interceptor, so this issue will not appear.
For more information about this issue, see https://openjdk.org/jeps/411 and https://bugs.openjdk.org/browse/JDK-8199704.
Check the terminal where you started codeprober.jar If no message there helps you, please open an issue in this repository!
The client is built with TypeScript. Do the following to generate the JavaScript files:
cd client/ts
npm install
npm run bw
Where 'bw' stands for 'build --watch'.
All HTML, JavaScript and CSS files should now be available in the client/public
directory.
The server is written in Java and has primarily been developed in Eclipse, and there are .project
& .classpath
files in the repository that should let you import the project into your own Eclipse instance.
That said, release builds should be performed on the command line. To build, do the following:
cd server
./build.sh
This will generate codeprober.jar
.
Release builds are performed via releases in Github.
Create a new release and publish it.
Within a few minutes, a Github action runner (.github/workflows/release-build.yml
) will push a freshly built and tested codeprober.jar
to your release.
If no jar file is pushed, then the release build failed.
Please look at the action runner log to debug why.
Release builds can be retried by editing the release in any way.
It is possible that your AST will just "magically" work with CodeProber. Add CodeProber_parse
or CodeProber_root_node
as described above and just try running with it.
CodeProber has a few different styles of ASTs it tries to detect and interact with.
If that doesn't work, the quickest way to get started is to use the minimal-prober-wrapper example implementation.
If you have a little more time and want to create a richer CodeProber experience then the best thing would be to adapt to one of the AST structures CodeProber expects. AstNodeApiStyle.java is an enum representing the different options currently supported. CodeProber tries to detect which style is present by experimentally invoking some methods within each style. For some more examples, you can also look in TestData.java->Node, which shows the non-JastAdd node implementation used for test cases.
CodeProber needs to know a shared supertype of all AST nodes. Any subtype of this supertype is expected to implement the "AST API style" mentioned above. CodeProber tries to automatically detect the common supertype with the following pseudocode:
def find_super(type)
if (type.supertype.package == type.package)
return find_super(type.supertype)
else
return type
find_super
is called with your AST root (returned from CodeProber_parse
or CodeProber_root_node
). In other words, it finds the top supertype that belongs to the same package as the AST root.
If your native AST structure uses a hierarchy of packages rather than a single flat package, then this will likely cause problems so you should probably rely on a wrapper type instead.
In the CodeProber settings panel there is a checkbox with the label Capture traces
.
This allows you to capture information about indirect dependencies of properties.
For this to work, the AST root must have a function cpr_setTraceReceiver
that looks like this:
public void cpr_setTraceReceiver(java.util.function.Consumer<Object[]> recv) {
// 'recv' records trace information.
// You should assign it to a field for later use.
this.theTraceReceiver = recv;
}
// Later, when something should be added to a trace, call it
// It will be non-null if `Capture traces` is checked.
if (this.theTraceReceiver != null) {
this.theTraceReceiver.accept(new Object[]{ ... })
}
If recv
is called while CodeProber is evaluating a property, then the information there will be visible in the CodeProber UI, if the user has checked Capture traces
.
CodeProber will toString
the first element in the object array to identify the kinds of trace event.
Currently, two types of events are supported, and they match tracing events produced by JastAdd:
Expected structure:
["COMPUTE_BEGIN", ASTNode node, String attribute, Object params, Object value]
Example invocation to recv
in cpr_setTraceReceiver
:
recv.accept(new Object[]{ "COMPUTE_BEGIN", someAstNode, "foo()", null, null })
Expected structure:
["COMPUTE_END", ASTNode node, String attribute, Object params, Object value]
Example invocation to recv
in cpr_setTraceReceiver
:
recv.accept(new Object[]{ "COMPUTE_END", someAstNode, "foo()", null, "ResultValue" })
Tracing can be tricky to get right. You may get errors in the terminal where you started codeprober.jar
stating something like:
Failed creating locator for AstNode< [..]
This happens if one of the AST nodes passed to a trace events aren't attached to the AST anymore. This can happen for example if you mutate the tree through rewrites.
You can try toggling the flush tree first
checkbox under Capture traces
on and off. You can also try changing the cache strategy
values back and forth. Some combination of the two might work.
If changing the settings doesn't work, then you must change which events are reported to CodeProber. Try to avoid setting the ASTNode
arguments to nodes that get removed from the tree.
If you want to try CodeProber, but don't have an analysis tool of your own, you can try out the playground at https://github.com/Kevlanche/codeprober-playground/.
You can also download the artifact to our Property probe paper, found here: https://doi.org/10.5281/zenodo.7185242.
Both options let you use CodeProber with a Java compiler/analyzer called IntraJ (https://github.com/lu-cs-sde/IntraJ).