Having Java as a scripting language?

Okay, I’ve got to the point where I want to implement a scripting language. I want a very powerful scripting language, but it obviously has to be fast, so I thought to myself: “What’s wrong with Java?”. I mean, I can compile the scripts at runtime to bytecode, load it and run it at the same speed as normal code. It should beat any scripting language implementation when it comes to performance, plus be easier to implement.

I realize that I will need some good security for this. I need to disallow networking, reflection, file access, e.t.c, or people could create viruses in my game scripts! :o I’m also thinking of restricting class access to a white list. I’ve heard that these 2 things can be implemented with a SecurityManager and a custom ClassLoader, respectively.

Now, I have no experience what so ever with SecurityManagers and ClassLoaders and such stuff, so I don’t really know how to use them. I’ve managed to compile some test Java code with JavaCompiler, but I don’t really understand how to load the class after that. I’ve tried Google, but I couldn’t find anything like this…

You should go with “scripting” feature of Java with a langage like Groovy that can be easily integrated inside a java program.
you can read this : http://groovy.codehaus.org/Embedding+Groovy

and do code like it in your java program

ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass(new File("src/test/groovy/script/HelloWorld.groovy"));

// let's call some method on an instance
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
Object[] args = {};
groovyObject.invokeMethod("run", args);

Interesting! It even seems to be able to do object orientation too, which I will need! I’ll look into it, but it seems a little complicated…

You could have a look at Janino http://docs.codehaus.org/display/JANINO/Home

As well as being an embedded compiler, which has the advantage that it will work with the JRE, it can now also wrap javac. It manages the ClassLoader stuff for you, and it is possible to set a ProtectionDomain on the loaded classes. It also provides the means to compile individual methods or expressions.

I use Janino as the basis for the live compilation in Praxis and it works really well. I haven’t yet looked at the security manager aspect of it - would be nice but not essential for my use case - if the user wants to destroy their home directory, I’m not going to stop them! :smiley:

This looks just perfect. Nyehehehe… (<— my experimenting laugh) Groovy looks interesting too, but I’ll start with Janino for now.

EDIT: This code freaks me out.

package dam.test;

import org.codehaus.janino.ExpressionEvaluator;

public class JaninoTest {
    public static void main(String[] args) throws Exception{
        ExpressionEvaluator exp = new ExpressionEvaluator("10 + 5*3 + dam.test.JaninoTest.getValue(\"stat\")", int.class, new String[0], new Class[0]);
        System.out.println(exp.evaluate(new Object[0]));
    }
    
    public static int getValue(String name){
        return 5;
    }
}

Why? Is that not exactly what you’d expect?

Well, yeah, but the power is overwhelming. xD Hahaha…

Okay, so I’ve got my little compile test running, and I’ve also made a custom SecurityManager that denies everything (I’ll make it toggle-able eventually). Is there any way to restrict access to a whitelist of classes? I’d like to restrict access from everything that isn’t needed. This is to serve as an extra security layer (paranoia xD), but also as a hint to scripters to keep the scripts simple. Basically to send the message that if you can’t do it with the available classes, you’re either not supposed to be doing that, or you’re Doing It Wrong. I think I heard someone mentioning that this could be done with a custom ClassLoader, but I don’t see how to do that at all. How do I even create a ClassLoader? T___T

hmmm … power! … :persecutioncomplex:

It might be worth looking at this bug report http://jira.codehaus.org/browse/JANINO-66 The Sandbox attachment might be useful in terms of setting up an unprivileged context. Along with that, the mentioned auxillary classes are set in SimpleCompiler.setParentClassLoader(…) which is a superclass of ExpressionEvaluator (and the other evaluators). You should be able to restrict access to just JVM classes using the BOOT_CLASS_LOADER field in SimpleCompiler. It may even be possible to pass a ClassLoader that doesn’t load anything, and pass all the classes you want people to be able to load in the auxillary classes array.

As I said before, I haven’t tried any of these things out myself yet. Good luck! :wink:

I’m using JavaSourceClassLoader to load .java files, and it actually had a ClassLoader constructor argument. I did this:

    private class RestrictedClassLoader extends ClassLoader {
        
        public RestrictedClassLoader(ClassLoader parent){
            super(parent);
        }
        
        @Override
        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            System.out.println("Loading class " + name);
            if(
                    !name.startsWith("java.lang.") //for all basic classes
                    && !name.equals("java.io.Serializable") //String extends this I think
                    && !name.startsWith("compiling.") //The package of the script(s)
                    && !name.startsWith("dam.test.") //The script object implements an interface from this package
            ){
                throw new SecurityException("Bad class!");
            }
            return super.loadClass(name, resolve);
        }
    }

It seems to be working. I also added the SecurityManager thingy, and it all seems to work fine. I’ll have to make a “Hack my script engine” contest before I release the game if I ever get that far. xD

Thanks for your help Nsigma, and thanks to Divxdede too!

You forgot to exclude reflection :-*

Sorry to hijack the thread a bit… I’ve been using Janino for a while, and ran into an odd bug - it seems to not be able to handle break/continue inside a loop. It will compile that code fine, but then dies on the next compilation unit with this stack trace:


 org.codehaus.janino.JaninoRuntimeException: Cannot "set()" Offset more than once
	at org.codehaus.janino.CodeContext$Offset.set(CodeContext.java:1178)
	at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:1009)
	at org.codehaus.janino.UnitCompiler.access$1000(UnitCompiler.java:104)
	at org.codehaus.janino.UnitCompiler$5.visitForStatement(UnitCompiler.java:870)
	at org.codehaus.janino.Java$ForStatement.accept(Java.java:1537)
	at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:888)
	at org.codehaus.janino.UnitCompiler.compileStatements(UnitCompiler.java:914)
	at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:1999)
	at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:789)
	at org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:770)
	at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:464)
	at org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:357)
	at org.codehaus.janino.UnitCompiler$3.visitPackageMemberClassDeclaration(UnitCompiler.java:312)
	at org.codehaus.janino.Java$PackageMemberClassDeclaration.accept(Java.java:770)
	at org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:319)
	at org.codehaus.janino.UnitCompiler.compileUnit(UnitCompiler.java:288)
	at org.codehaus.janino.JavaSourceClassLoader.generateBytecodes(JavaSourceClassLoader.java:203)
	at org.codehaus.janino.JavaSourceClassLoader.findClass(JavaSourceClassLoader.java:157)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	at com.fs.starfarer.loading.scripts.ScriptStore$1.run(ScriptStore.java:85)

Has anyone had much experience with it and managed to work around the problem (or not run into it at all)?

Excluded with the SecurityManager, but I’ll be using a whitelist later.

Uwaaaa… Sounds horrible! Keep me updated!!!

You can use reflection with any security manager. The security manager is only notified when you do something ‘forbidden’ like accessing a private field.

What about javascript? You can implement it almost exactly like java, and it will take parameters. Does not need compilimg.

+1 Janino. -1 Groovy (pile of junk).

I’ll use a whitelist in the end anyway, so it should be okay. That code was just for my test.

It needs to be fast, so not having compilation needing compilation is a bad thing in this case.

@Avm1979
I managed to reproduce the error. Goddamn it! Is there a bug report for this problem already, or should we hurry up and make one?
EDIT: Yay! http://jira.codehaus.org/browse/JANINO-147

@Riven. Can you clarify what the issue with reflection would be? How could you access anything that you couldn’t with normal code anyway, as the SecurityManager will (should) stop you accessing other classloaders / private fields? This is a sincere question, btw - it’s not an area of Java I feel I know a lot about.

Dang, was just going to reply and point you to that! Seems fixed in 2.6.2, but that’s not released yet. Incidentally, could you post an example that will reproduce this. Have tried in Praxis but can’t seem to force this error - incidentally, that’s with the last release of the 2.5 branch, and using ClassBodyEvaluator. I wonder if downgrading to 2.5 is a short term fix?

Incidentally, I personally like using the ClassBodyEvaluator for what you’re doing because you don’t have to name classes, and more importantly you can force a particular superclass / interface.

Best wishes, Neil

Sorry, my code is currently more similar to scrambled eggs than to actual code. I basically hackingly merged two different test programs. Go figure… T_T

It’s easy to reproduce though. Just create two java files, with the first one containing a method with a while(true)-loop with a break; to exit it. Then load both of them with a JavaSourceClassLoader (the while-break one first) and bam! JaninoRuntimeException! :’(

Thanks for verifying, and tracking that down! Thing is, it’s been over a year since 2.6.1 - wonder if 2.6.2 is going to get released at all. Ah, well, keeping my fingers crossed.

Yep, that’s all it takes. Kind of surprised it took this long for someone to run into that, tbh.

I started out using Groovy for scripting in Starfarer and had all sorts of trouble, from compilation speed to code compiling and then dying at runtime. It was all resolvable, more or less, but added up to being a huge pain. Janino, on the other hand, worked very smoothly.

As an added bonus, you can use your Java IDE of choice for the scripts. I’ve set up my projects like this: a core project, an api project, and a scripts project. Both core and scripts depend on api, but core does not depend on scripts. That way, you get all the compile-as-you-type support from the IDE - but at runtime, since core doesn’t depend on the scripts project, it doesn’t see the IDE-compiled class files and can use Janino to compile them on startup. Works like a charm, and forces a clean separation of what’s accessible to scripts vs not.

Damn, the last new version was indeed released in June 2010… >_< Stupid world!

I read on the front page that you can make it use JavaCompiler instead.

[quote]JANINO can be configured to use the javax.tools.JavaCompiler API (available since JDK 1.6), which removes the Java 5-related limitations.
[/quote]
Nothing anywhere on HOW to actually do that though. -_- I suppose it would require a JDK on the computer, but it would at least be a workaround until the next version is released, whenever that happens.