Java Language Hacking: throw “error”

Welcome to a new category in my blog, called Java Language Hacking. I recently gained interest in extending/building programming languages. To learn more about the subject, I decided to hack a bit on the Java compiler.

Of course this is for learning/experimentation/fun purposes only, I understand that the resulting code will be of no use, because the resulting language will be incompatible with Java. I know that it is a bad thing to add arbitrary patches to the java compiler. And I will do it anyway, because usually I learn a lot when attempting crazy things. I’ll blog about this because writing helps me focusing and I thought it might be remotely interesting. Don’t try this at home or at work.

I’ll use the Eclipse JDT Java compiler for this because of the very liberal EPL license it’s licensed under.

Todays special: throw String

I decided to start with something very small, a feature I learned to value in the Ruby language:

raise "error"

This is equivalent with

throw new RuntimeException("error");

in Java. Why is it useful? There are errors which will never be specifically catched, so we don’t need to declare an exception class (for example: assertions and situations that can only occur because of programming errors).

So let’s see how this could be added to the java compiler, making

throw "error"

a shortcut to

throw new RuntimeException("error")

Lets find the place in the Eclipse JDT Java compiler that’s responsible for the compiler error we get when the desired syntax is used:

No exception of type String can be thrown; an exception type must be a subclass of Throwable

Says who? My intuition says me org.eclipse.jdt.core might have to do with it, so lets get the sources and do a quick grep:

$ git clone git://dev.eclipse.org/org.eclipse.jdt/org.eclipse.jdt.core.git
$ grep 'No exception of type' . -R
./compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties:320 =
    No exception of type {0} can be thrown; an exception type must be a subclass of Throwable
$ grep '320' . -Rw
./compiler/org/eclipse/jdt/core/compiler/IProblem.java:	int CannotThrowType = TypeRelated + 320;

Aha! Let’s import the project into Eclipse and use Find References. This leads to ProblemReporter#cannotThrowType, leading to org.eclipse.jdt.internal.compiler.ast.ThrowStatement.

Seems like ThrowStatement might be extended to support the given syntax. It represents the throw statement in the abstract syntax tree and handles validation and outputting java bytecode. Luckily the ‘throw’ statement allows any expression as far as the parser is concerned, the error is thrown when ThrowStatement tries to resolve the exception class:

public class ThrowStatement extends Statement {

    public Expression exception;
    public TypeBinding exceptionType;
        
    public void resolve(BlockScope scope) {
        this.exceptionType = this.exception.resolveType(scope);
        if (this.exceptionType != null && this.exceptionType.isValidBinding()) {
            // ...
             if (this.exceptionType.findSuperTypeOriginatingFrom(TypeIds.T_JavaLangThrowable, true) == null) {
                scope.problemReporter().cannotThrowType(this.exception, this.exceptionType);
            }
            // ...
        }
    }
}

Let’s try to patch the extension in the statement class. Lets see if we have a String expression:

if (this.exceptionType.id == TypeIds.T_JavaLangString) {
    // what now?
}

But how do we sneak in the implicit new RuntimeException(<<expression>>)? There seems to be two ways, by extending the bytecode generation in #generateCode, or by wrapping the expression in a ‘synthetic’ new ...() statement (which is represented by AllocationExpression in the AST). Looking at the code in AllocationExpression, I’m pretty sure that I don’t want to care about all the corner-cases of the new statement (like resolving the correct constructor method), I want to reuse this. Unfortunately, I couldn’t find another case where such an approach is used, the AST classes are tightly coupled to the source text and this might get in the way when reusing them synthetically (if you know if there is a better way to do this, I’d be very curious to hear about it). Lets see if I can get through with this.

An AllocationExpression gets a TypeReference type, resolves it to a scoped TypeBinding resolvedType and generates the bytecode for the allocation. It seems to be not intended to be re-used in that way, especially you can’t create a TypeReference without a reference to some piece of source code it comes from. To make it work for my little hack, I just pretended it would come from the source:

if (this.exceptionType.id == TypeIds.T_JavaLangString) {
    AllocationExpression newExceptionExpression = new AllocationExpression();
    // TODO: Is there a way to reuse AllocationExpression without referring to source positions?
    newExceptionExpression.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_RUNTIMEEXCEPTION,
            new long[] { 0, 0, 0 });
    newExceptionExpression.arguments = new Expression[] { this.exception };
    this.exceptionType = newExceptionExpression.resolveType(scope);
    this.exception = newExceptionExpression;
}

I was very amazed to find this working out of the box without any other trickery at all:

If you’re interested, the code/patch is available on github.

PS, Upcoming

I found it a funny little first adventure into the world of the Java compiler. Despite being late, I proposed this to Project Coin. Although given the latest plans I guess I might be too impatient to wait for this.

Of course even such a simple feature needs proper tests, I’ll explore the test infrastructure of JDT in one of the next posts.

Also, it might be fun to do a little language that is Java + X (yes, I know about Groovy ;). This might be cool if JDT could be reused completely, adding only extensions to it. Which might not be the most far-fetched thing, maybe I’ll explore this in one of the following posts. Some kind of extensions seem to be possible using the agents feature, as Lombok proves.

Ultimately, I’m very interested in ‘modular programming languages’ which would allow to import and extend even language syntax. Maybe I’ll take a look into languages that already have such capabilities - so far my work unfortunately never offered a chance to do so.

Ralf Ebert

Ralf Ebert is an independent software developer and trainer. He makes apps for Mac OS X and iOS and offers iOS and Git training courses for software developers.