diff --git a/README.html b/README.html index 1cba2d7b..7ec0d26d 100644 --- a/README.html +++ b/README.html @@ -842,6 +842,7 @@ Files are no longer hosted at LuaForge.
  • Fix bug 3565008 so that short substrings are backed by short arrays.
  • Fix bug 3495802 to return correct offset of substrings from string.find().
  • Add artifacts to Maven central repository.
  • +
  • Limit pluggable scripting to use compatible bindings and contexts, implement redirection.
  • diff --git a/examples/jse/ScriptEngineSample.java b/examples/jse/ScriptEngineSample.java index 1583f79e..ec9b638b 100644 --- a/examples/jse/ScriptEngineSample.java +++ b/examples/jse/ScriptEngineSample.java @@ -1,5 +1,9 @@ +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.Reader; + import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; @@ -11,6 +15,8 @@ import javax.script.SimpleBindings; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.script.LuaScriptEngine; +import org.luaj.vm2.script.LuajBindings; public class ScriptEngineSample { @@ -22,6 +28,7 @@ public class ScriptEngineSample { ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine e = sem.getEngineByExtension(".lua"); + e = new LuaScriptEngine(); ScriptEngineFactory f = e.getFactory(); // uncomment to enable the lua-to-java bytecode compiler @@ -66,8 +73,8 @@ public class ScriptEngineSample { System.out.println( "y="+b1.get("y") ); System.out.println( "y="+b2.get("y") ); - // Bindings that use a client-specified Bindings type. - Bindings sb = new SimpleBindings(); + // In Luaj 3.0, client bindings must derive from LuajBindings. + Bindings sb = new LuajBindings(); sb.put("x", 2); System.out.println( "eval: "+cs.eval(sb) ); @@ -81,6 +88,36 @@ public class ScriptEngineSample { testEngineBindings(e); testClientBindings(e); testUserClasses(e); + + // Test redirection of input, output, and standard error. + Reader input = new CharArrayReader("abcdefg\nhijk".toCharArray()); + CharArrayWriter output = new CharArrayWriter(); + CharArrayWriter errors = new CharArrayWriter(); + String script = + "print(\"string written using 'print'\")\n" + + "io.write(\"string written using 'io.write()'\\n\")\n" + + "io.stdout:write(\"string written using 'io.stdout:write()'\\n\")\n" + + "io.stderr:write(\"string written using 'io.stderr:write()'\\n\")\n" + + "io.write([[string read using 'io.stdin:read(\"*l\")':]]..io.stdin:read(\"*l\")..\"\\n\")\n"; + + System.out.println("Evaluating script with redirection set."); + e.getContext().setReader(input); + e.getContext().setWriter(output); + e.getContext().setErrorWriter(errors); + e.eval(script); + System.out.println("output::>"+output+"<::output"); + System.out.println("errors::>"+errors+"<::errors"); + + System.out.println("Evaluating script with redirection reset."); + output.reset(); + errors.reset(); + e.getContext().setReader(null); + e.getContext().setWriter(null); + e.getContext().setErrorWriter(null); + e.eval(script); + System.out.println("output::>"+output+"<::output"); + System.out.println("errors::>"+errors+"<::errors"); + } catch (ScriptException ex) { ex.printStackTrace(); @@ -97,7 +134,7 @@ public class ScriptEngineSample { testBindings(e, e.createBindings()); } public static void testClientBindings(ScriptEngine e) throws ScriptException { - testBindings(e, new SimpleBindings()); + testBindings(e, new LuajBindings()); } public static void testBindings(ScriptEngine e, Bindings b) throws ScriptException { CompiledScript cs = ((Compilable)e).compile( diff --git a/src/jse/org/luaj/vm2/script/LuaScriptEngine.java b/src/jse/org/luaj/vm2/script/LuaScriptEngine.java index d48d360f..400d570d 100644 --- a/src/jse/org/luaj/vm2/script/LuaScriptEngine.java +++ b/src/jse/org/luaj/vm2/script/LuaScriptEngine.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2008 LuaJ. All rights reserved. +* Copyright (c) 2008-2013 LuaJ. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,38 +21,21 @@ ******************************************************************************/ package org.luaj.vm2.script; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.io.StringReader; -import java.util.Map.Entry; +import java.io.*; +import javax.script.*; -import javax.script.Bindings; -import javax.script.Compilable; -import javax.script.CompiledScript; -import javax.script.ScriptContext; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptException; -import javax.script.SimpleBindings; -import javax.script.SimpleScriptContext; - -import org.luaj.vm2.Globals; -import org.luaj.vm2.LoadState; -import org.luaj.vm2.Lua; -import org.luaj.vm2.LuaClosure; -import org.luaj.vm2.LuaError; -import org.luaj.vm2.LuaFunction; -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; -import org.luaj.vm2.Prototype; -import org.luaj.vm2.Varargs; -import org.luaj.vm2.lib.jse.CoerceJavaToLua; -import org.luaj.vm2.lib.jse.JsePlatform; +import org.luaj.vm2.*; /** + * Implementation of the ScriptEngine interface which can compile and execute + * scripts using luaj. * - * @author jim_roseborough + *

    + * This engine requires the types of the Bindings and ScriptContext to be + * compatible with the engine. For creating new client context use + * ScriptEngine.createContext() which will return {@link LuajContext}, + * and for client bindings use the default engine scoped bindings or + * construct a {@link LuajBindings} directly. */ public class LuaScriptEngine implements ScriptEngine, Compilable { @@ -66,21 +49,13 @@ public class LuaScriptEngine implements ScriptEngine, Compilable { private static final ScriptEngineFactory myFactory = new LuaScriptEngineFactory(); - private ScriptContext defaultContext; - - private final Globals _G; + private LuajContext context; public LuaScriptEngine() { - - // create globals - _G = "true".equals(System.getProperty("luaj.debug"))? - JsePlatform.debugGlobals(): - JsePlatform.standardGlobals(); - // set up context - ScriptContext ctx = new SimpleScriptContext(); - ctx.setBindings(createBindings(), ScriptContext.ENGINE_SCOPE); - setContext(ctx); + context = new LuajContext(); + context.setBindings(createBindings(), ScriptContext.ENGINE_SCOPE); + setContext(context); // set special values put(LANGUAGE_VERSION, __LANGUAGE_VERSION__); @@ -91,9 +66,6 @@ public class LuaScriptEngine implements ScriptEngine, Compilable { put(FILENAME, __FILENAME__); put(NAME, __SHORT_NAME__); put("THREADING", null); - - // Let globals act as an index metatable - _G.set(LuaValue.INDEX, _G); } public Object eval(String script) throws ScriptException { @@ -109,7 +81,7 @@ public class LuaScriptEngine implements ScriptEngine, Compilable { } public Object eval(Reader reader) throws ScriptException { - return eval(reader, getContext()); + return compile(reader).eval(); } public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException { @@ -117,12 +89,7 @@ public class LuaScriptEngine implements ScriptEngine, Compilable { } public Object eval(Reader reader, Bindings bindings) throws ScriptException { - ScriptContext c = getContext(); - Bindings current = c.getBindings(ScriptContext.ENGINE_SCOPE); - c.setBindings(bindings, ScriptContext.ENGINE_SCOPE); - Object result = eval(reader); - c.setBindings(current, ScriptContext.ENGINE_SCOPE); - return result; + return compile(reader).eval(bindings); } public void put(String key, Object value) { @@ -144,15 +111,17 @@ public class LuaScriptEngine implements ScriptEngine, Compilable { } public Bindings createBindings() { - return new SimpleBindings(); + return new LuajBindings(); } public ScriptContext getContext() { - return defaultContext; + return context; } public void setContext(ScriptContext context) { - defaultContext = context; + if (!(context instanceof LuajContext)) + throw new IllegalArgumentException("LuaScriptEngine can only be used with LuajScriptContext"); + this.context = (LuajContext) context; } public ScriptEngineFactory getFactory() { @@ -165,88 +134,65 @@ public class LuaScriptEngine implements ScriptEngine, Compilable { public CompiledScript compile(Reader reader) throws ScriptException { try { - InputStream ris = new Utf8Encoder(reader); + InputStream is = new Utf8Encoder(reader); try { - final LuaFunction f = LoadState.load(ris, "script", "bt", _G); - if ( f.isclosure() ) { - // most compiled functions are closures with prototypes - final Prototype p = f.checkclosure().p; - return new CompiledScriptImpl() { - protected LuaFunction newFunctionInstance(LuaTable env) { - return new LuaClosure( p, env ); - } - }; - } else { - // when luajc is used, functions are java class instances - final Class c = f.getClass(); - return new CompiledScriptImpl() { - protected LuaFunction newFunctionInstance(LuaTable env) throws ScriptException { - try { - LuaFunction f = (LuaFunction) c.newInstance(); - f.initupvalue1( env ); - return f; - } catch (Exception e) { - throw new ScriptException("instantiation failed: "+e.toString()); - } - } - }; - } + final Globals g = context.globals; + final LuaFunction f = LoadState.load(is, "script", "bt", g); + return new LuajCompiledScript(f, g); } catch ( LuaError lee ) { throw new ScriptException(lee.getMessage() ); } finally { - ris.close(); + is.close(); } } catch ( Exception e ) { throw new ScriptException("eval threw "+e.toString()); } } - - abstract protected class CompiledScriptImpl extends CompiledScript { - abstract protected LuaFunction newFunctionInstance(LuaTable env) throws ScriptException; + + class LuajCompiledScript extends CompiledScript { + final LuaFunction function; + final Globals compiling_globals; + LuajCompiledScript(LuaFunction function, Globals compiling_globals) { + this.function = function; + this.compiling_globals = compiling_globals; + } + public ScriptEngine getEngine() { return LuaScriptEngine.this; } - public Object eval(ScriptContext context) throws ScriptException { - Bindings b = context.getBindings(ScriptContext.ENGINE_SCOPE); - BindingsGlobals env = new BindingsGlobals(b); - LuaFunction f = newFunctionInstance(env); + + public Object eval() throws ScriptException { + return eval(getContext()); + } + + public Object eval(Bindings bindings) throws ScriptException { + ScriptContext c = getContext(); + Bindings current = c.getBindings(ScriptContext.ENGINE_SCOPE); try { - return f.invoke(LuaValue.NONE); + c.setBindings(bindings, ScriptContext.ENGINE_SCOPE); + Object result = eval(c); + return result; } finally { - env.copyout(); + c.setBindings(current, ScriptContext.ENGINE_SCOPE); } - } - } + } - class BindingsGlobals extends Globals { - final Bindings b; - BindingsGlobals(Bindings b) { - this.b = b; - this.setmetatable(_G); - this.debuglib = _G.debuglib; - for (Entry e : b.entrySet()) - rawset(toLua(e.getKey()), toLua(e.getValue())); - } - void copyout() { - b.clear(); - for (Varargs v = next(LuaValue.NIL); !v.arg1().isnil(); v = next(v.arg1())) - b.put(toJava(v.arg1()).toString(), toJava(v.arg(2))); - } - } - - LuaValue toLua(Object javaValue) { - return javaValue == null? LuaValue.NIL: - javaValue instanceof LuaValue? (LuaValue) javaValue: - CoerceJavaToLua.coerce(javaValue); - } - - Object toJava(LuaValue v) { - switch ( v.type() ) { - case LuaValue.TNIL: return null; - case LuaValue.TSTRING: return v.tojstring(); - case LuaValue.TUSERDATA: return v.checkuserdata(Object.class); - case LuaValue.TNUMBER: return v.isinttype()? (Object) new Integer(v.toint()): (Object) new Double(v.todouble()); - default: return v; + public Object eval(ScriptContext context) throws ScriptException { + Globals g = ((LuajContext) context).globals; + LuaFunction f = function; + if (g != compiling_globals) { + if (f.isclosure()) + f = new LuaClosure(f.checkclosure().p, g); + else { + try { + f = f.getClass().newInstance(); + } catch (Exception e) { + throw new ScriptException(e); + } + f.initupvalue1(g); + } + } + return f.invoke(LuaValue.NONE); } } diff --git a/src/jse/org/luaj/vm2/script/LuajBindings.java b/src/jse/org/luaj/vm2/script/LuajBindings.java new file mode 100644 index 00000000..fe262c5b --- /dev/null +++ b/src/jse/org/luaj/vm2/script/LuajBindings.java @@ -0,0 +1,211 @@ +/******************************************************************************* +* Copyright (c) 2013 LuaJ. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +package org.luaj.vm2.script; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.script.Bindings; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.ThreeArgFunction; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; + +/** + * Implementation of Bindings that may be used with Luaj ScriptEngine implementaiton. + * + *

    + * Normally this will not be created directly, but instead will be created fro the script + * engine using something like: + *

    + *       ScriptEngineManager manager = new ScriptEngineManager();
    + *       ScriptEngine engine = manager.getEngineByExtension(".lua");
    + *       Bindings luaj_bindings = engine.createBindings();
    + * 
    + * + *

    + * Each instance of LuajBindings holds its own global state. + * Calls to get(), put(), and so on will coerce the lua values + * into Java values on-the-fly, so that any performance or + * memory occur at that point. + * + *

    + * Only Java String keys may be used for get(), put(), and similar + * operations, or ClassCastException will be thrown. + * + *

    + * When iterating over the globals, only values which have string keys + * will be returned. + */ +public class LuajBindings implements Bindings { + + /** Values that are in these bindings. This table is linked to the metatable + * of the context when it is executed via the metatable. + */ + public final LuaTable env; + + /** Metatable to be used on bindings that will forards gets and sets into our table of bindings. + */ + final LuaTable metatable; + + /** Construct an empty LuajBindings. + *

    + * Each LuajBindings has its own environment table, which will + * delegate to the context on global reads during execution. + */ + public LuajBindings() { + env = new LuaTable(); + metatable = new LuaTable(); + metatable.set(LuaValue.INDEX, env); + metatable.set(LuaValue.NEWINDEX, new ThreeArgFunction() { + public LuaValue call(LuaValue table, LuaValue key, LuaValue value) { + env.rawset(key, value); + return LuaValue.NONE; + } + }); + } + + /** Coerce a value from luaj types to Java types. + * @param luajValue any value that derives from LuaValue. + * @return If luajValue is: + * {@link #NIL}, null; + * {@link #LuaString}: String; + * {@link #LuaUserdata}: Object; + * {@link #LuaNumber}: Integer or Double; + * otherwise: the raw {@link #LuaValue}. + */ + public Object toJava(LuaValue luajValue) { + switch ( luajValue.type() ) { + case LuaValue.TNIL: return null; + case LuaValue.TSTRING: return luajValue.tojstring(); + case LuaValue.TUSERDATA: return luajValue.checkuserdata(Object.class); + case LuaValue.TNUMBER: return luajValue.isinttype()? + (Object) new Integer(luajValue.toint()): + (Object) new Double(luajValue.todouble()); + default: return luajValue; + } + } + + /** Coerce a value from Java types to luaj types. + * @param javaValue Any Java value, including possibly null. + * @return LuaValue for this Java Value. If javaValue is + * null: {@link #NIL}; + * {@link #LuaValue}: the value; + * String: {@link #LuaString}; + * Integer: {@link #LuaInteger}; + * Double: {@link #LuaDouble}; + * array: {@link #LuaTable} containing list of values; + * otherwise {@link #LuaUserdata} from behavior of {@link CoerceJavaToLua.coerce(Object)} + * @see CoerceJavaToLua + */ + public LuaValue toLua(Object javaValue) { + return javaValue == null? LuaValue.NIL: + javaValue instanceof LuaValue? (LuaValue) javaValue: + CoerceJavaToLua.coerce(javaValue); + } + + @Override + public void clear() { + for (LuaValue key : env.keys()) + env.rawset(key, LuaValue.NIL); + } + + @Override + public boolean containsValue(Object string_key) { + return !env.rawget((String)string_key).isnil(); + } + + @Override + public Set> entrySet() { + HashMap map = new HashMap(); + for (LuaValue key : env.keys()) + if (key.type() == LuaValue.TSTRING) + map.put(key.tojstring(), toJava(env.rawget(key))); + return map.entrySet(); + } + + @Override + public boolean isEmpty() { + return keySet().isEmpty(); + } + + @Override + public Set keySet() { + Set set = new HashSet(); + for (LuaValue key : env.keys()) + if (key.type() == LuaValue.TSTRING) + set.add(key.tojstring()); + return set; + } + + @Override + public int size() { + return keySet().size(); + } + + @Override + public Collection values() { + List values = new ArrayList(); + for (LuaValue key : env.keys()) + if (key.type() == LuaValue.TSTRING) + values.add(toJava(env.rawget(key))); + return values; + } + + @Override + public boolean containsKey(Object key) { + return keySet().contains(key); + } + + @Override + public Object get(Object string_key) { + return toJava(env.rawget((String)string_key)); + } + + @Override + public Object put(String string_key, Object java_value) { + Object previous = get(string_key); + env.rawset((String)string_key, toLua(java_value)); + return previous; + } + + @Override + public void putAll(Map values) { + for (java.util.Map.Entry e : values.entrySet()) + env.rawset(e.getKey(), toLua(e.getValue())); + } + + @Override + public Object remove(Object string_key) { + Object previous = get(string_key); + env.rawset((String)string_key, LuaValue.NIL); + return previous; + } + +} diff --git a/src/jse/org/luaj/vm2/script/LuajContext.java b/src/jse/org/luaj/vm2/script/LuajContext.java new file mode 100644 index 00000000..ab7d1bdf --- /dev/null +++ b/src/jse/org/luaj/vm2/script/LuajContext.java @@ -0,0 +1,143 @@ +/******************************************************************************* +* Copyright (c) 2013 LuaJ. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ +package org.luaj.vm2.script; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Reader; +import java.io.Writer; + +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.SimpleScriptContext; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.lib.jse.JsePlatform; + +/** + * Context for LuaScriptEngine execution which maintains its own Globals, + * and manages the input and output redirection. + */ +public class LuajContext extends SimpleScriptContext implements ScriptContext { + + /** Globals for this context instance. */ + public final Globals globals; + + /** The initial value of globals.STDIN */ + private final InputStream stdin; + /** The initial value of globals.STDOUT */ + private final PrintStream stdout; + /** The initial value of globals.STDERR */ + private final PrintStream stderr; + + /** Construct a LuajContext with its own globals which may + * be debug globals depending on the value of the system + * property 'org.luaj.debug' + *

    + * If the system property 'org.luaj.debug' is set, the globals + * created will be a debug globals that includes the debug + * library. This may provide better stack traces, but may + * have negative impact on performance. + */ + public LuajContext() { + this("true".equals(System.getProperty("org.luaj.debug"))); + } + + /** Construct a LuajContext with its own globals, which + * which optionally are debug globals. + *

    + * If createDebugGlobals is set, the globals + * created will be a debug globals that includes the debug + * library. This may provide better stack traces, but may + * have negative impact on performance. + * @param createDebugGlobals true to create debug globals, + * false for standard globals. + */ + public LuajContext(boolean createDebugGlobals) { + globals = createDebugGlobals? + JsePlatform.debugGlobals(): + JsePlatform.standardGlobals(); + stdin = globals.STDIN; + stdout = globals.STDOUT; + stderr = globals.STDERR; + } + + public Bindings getBindings(int scope) { + if (scope != ScriptContext.ENGINE_SCOPE) + throw new IllegalArgumentException("LuajScriptContext only supports ENGINE_SCOPE"); + return super.getBindings(ENGINE_SCOPE); + } + + public void setBindings(Bindings bindings, int scope) { + if (!(bindings instanceof LuajBindings)) + throw new IllegalArgumentException("LuajScriptContext can only be used with LuajBindings"); + if (scope != ScriptContext.ENGINE_SCOPE) + throw new IllegalArgumentException("LuajScriptContext only supports ENGINE_SCOPE"); + LuajBindings luaj_bindings = (LuajBindings) bindings; + globals.setmetatable(luaj_bindings.metatable); + super.setBindings(bindings, scope); + } + + @Override + public void setErrorWriter(Writer writer) { + globals.STDERR = writer != null? + new PrintStream(new WriterOutputStream(writer)): + stderr; + } + + @Override + public void setReader(Reader reader) { + globals.STDIN = reader != null? + new ReaderInputStream(reader): + stdin; + } + + @Override + public void setWriter(Writer writer) { + globals.STDOUT = writer != null? + new PrintStream(new WriterOutputStream(writer)): + stdout; + } + + static final class WriterOutputStream extends OutputStream { + final Writer w; + WriterOutputStream(Writer w) { + this.w = w; + } + public void write(int b) throws IOException { + w.write(b); + } + } + + static final class ReaderInputStream extends InputStream { + final Reader r; + ReaderInputStream(Reader r) { + this.r = r; + } + public int read() throws IOException { + return r.read(); + } + } + +}