Add utilities and sample code to load luaj in custom class loader for strong sandboxing, and use of orphaned threads.
This commit is contained in:
30
README.html
30
README.html
@@ -464,6 +464,28 @@ multiple threads see <a href="examples/jse/SampleMultiThreaded.java">examples/js
|
||||
As an alternative, the JSR-223 scripting interface can be used, and should always provide a separate Globals instance
|
||||
per script engine instance by using a ThreadLocal internally.
|
||||
|
||||
<h2>Sandboxing</h2>
|
||||
Lua and luaj are allow for easy sandboxing of scripts in a server environment.
|
||||
<P>
|
||||
Considerations include
|
||||
<ul>
|
||||
<li>The <em>debug</em> and <em>luajava</em> library give unfettered access to the luaj vm and java vm
|
||||
<li>Portions of the <em>os</em>, <em>io</em>, and <em>coroutine</em> libraries are prone to abuse
|
||||
<li>Rogue scripts may need to be throttled or killed
|
||||
<li>Shared metatables (string, booleans, etc.) need to be made read-only or isolated via class loaders
|
||||
such as <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/server/LuajClassLoader.html">LuajClassLoader</a>
|
||||
</ul>
|
||||
|
||||
Luaj provides sample code covering various approaches:
|
||||
<ul>
|
||||
<li><a href="examples/jse/SampleSandboxed.java">examples/jse/SampleSandboxed.java</a>
|
||||
A java sandbox that limits libraries, limits bytecodes per script, and makes shared tables read-only
|
||||
<li><a href="examples/lua/samplesandboxed.lua">examples/jse/samplesandboxed.lua</a>
|
||||
A lua sandbox that limits librares,limits bytecodes per script, and makes shared tables read-only
|
||||
<li><a href="examples/jse/SampleUsingClassLoader.java">examples/jse/SampleUsingClassLoader.java</a>
|
||||
A heavier but strong sandbox where each script gets its own class loader and a full private luaj implementation
|
||||
</ul>
|
||||
|
||||
<h1>4 - <a name="4">Libraries</a></h1>
|
||||
|
||||
<h2>Standard Libraries</h2>
|
||||
@@ -535,7 +557,8 @@ Luaj uses WeakReferences and the OrphanedThread error to ensure that coroutines
|
||||
are properly garbage collected. For thread safety, OrphanedThread should not be caught by Java code.
|
||||
See <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/LuaThread.html">LuaThread</a>
|
||||
and <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/OrphanedThread.html">OrphanedThread</a>
|
||||
javadoc for details.
|
||||
javadoc for details. The sample code in <a href="examples/jse/CollectingOrphanedCoroutines.java">examples/jse/CollectingOrphanedCoroutines.java</a>
|
||||
provides working examples.
|
||||
|
||||
<h3>Debug Library</h3>
|
||||
The <em>debug</em> library is not included by default by
|
||||
@@ -982,6 +1005,9 @@ Files are no longer hosted at LuaForge.
|
||||
<li>Fix os.date("*t") to return hour in 24 hour format (fixes issue #45)</li>
|
||||
<li>Add SampleSandboxed.java example code to illustrate sandboxing techniques in Java.</li>
|
||||
<li>Add samplesandboxed.lua example code to illustrate sandboxing techniques in lua.</li>
|
||||
<li>Add CollectingOrphanedCoroutines.java example code to show how to deal with orphaned lua threads.</li>
|
||||
<li>Add LuajClassLoader.java and Launcher.java to simplify loading via custom class loader.</li>
|
||||
<li>Add SampleUsingClassLoader.java example code to demonstrate loading using custom class loader.</li>
|
||||
<li>Make string metatable a proper metatable, and make it read-only by default.</li>
|
||||
<li>Add sample code that illustrates techniques in creating sandboxed environments.</li>
|
||||
<li>Add convenience methods to Global to load string scripts with custom environment.</li>
|
||||
@@ -1001,6 +1027,8 @@ Files are no longer hosted at LuaForge.
|
||||
<li>negative zero is treated as identical to integer value zero throughout luaj
|
||||
<li>lua compiled into java bytecode using luajc cannot use string.dump() or xpcall()
|
||||
<li>number formatting with string.format() is not supported
|
||||
<li>shared metatables for string, bool, etc are shared across Globals instances in the same class loader
|
||||
<li>orphaned threads will not be collected unless garbage collection is run and sufficient time elapses
|
||||
</ul>
|
||||
<h3>File Character Encoding</h3>
|
||||
Source files can be considered encoded in UTF-8 or ISO-8859-1 and results should be as expected,
|
||||
|
||||
@@ -86,11 +86,11 @@
|
||||
<javac destdir="build/jse/classes" encoding="utf-8" source="1.3" target="1.3"
|
||||
classpath="lib/bcel-5.2.jar"
|
||||
srcdir="build/jse/src"
|
||||
excludes="**/script/*,**/Lua2Java*,lua*"/>
|
||||
excludes="**/script/*,**/Lua2Java*,**/server/*,lua*"/>
|
||||
<javac destdir="build/jse/classes" encoding="utf-8" source="1.5" target="1.5"
|
||||
classpath="build/jse/classes"
|
||||
srcdir="build/jse/src"
|
||||
includes="**/script/*,**/Lua2Java*"/>
|
||||
includes="**/script/*,**/Lua2Java*,**/server/*"/>
|
||||
<javac destdir="build/jse/classes" encoding="utf-8" source="1.3" target="1.3"
|
||||
classpath="build/jse/classes"
|
||||
srcdir="build/jse/src"
|
||||
@@ -127,7 +127,7 @@
|
||||
use="true"
|
||||
windowtitle="Luaj API">
|
||||
<fileset dir="src/core" defaultexcludes="yes" includes="org/luaj/vm2/*.java,org/luaj/vm2/compiler/LuaC.java,org/luaj/vm2/lib/*.java"/>
|
||||
<fileset dir="src/jse" defaultexcludes="yes" includes="org/luaj/vm2/lib/jse/*.java,org/luaj/vm2/luajc/LuaJC.java"/>
|
||||
<fileset dir="src/jse" defaultexcludes="yes" includes="org/luaj/vm2/lib/jse/*.java,org/luaj/vm2/luajc/LuaJC.java,org/luaj/vm2/server/*.java"/>
|
||||
<fileset dir="src/jme" defaultexcludes="yes" includes="org/luaj/vm2/lib/jme/*.java"/>
|
||||
<doctitle><![CDATA[<h1>Luaj API</h1>]]></doctitle>
|
||||
<bottom><![CDATA[<i>Copyright © 2007-2008 Luaj.org. All Rights Reserved.</i>]]></bottom>
|
||||
|
||||
53
examples/jse/CollectingOrphanedCoroutines.java
Normal file
53
examples/jse/CollectingOrphanedCoroutines.java
Normal file
@@ -0,0 +1,53 @@
|
||||
import org.luaj.vm2.Globals;
|
||||
import org.luaj.vm2.LuaThread;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.jse.JsePlatform;
|
||||
|
||||
/** Example that continually launches coroutines, and illustrates how to make
|
||||
* sure the orphaned coroutines are cleaned up properly.
|
||||
*
|
||||
* Main points:
|
||||
* <ul><li>Each coroutine consumes one Java Thread while active or reference anywhere</li>
|
||||
* <li>All references to a coroutine must be dropped for the coroutine to be collected</li>
|
||||
* <li>Garbage collection must be run regularly to remove weak references to lua threads</li>
|
||||
* <li>LuaThread.thread_orphan_check_interval must be short enough to find orphaned references quickly</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CollectingOrphanedCoroutines {
|
||||
|
||||
// Script that launches coroutines over and over in a loop.
|
||||
// Garbage collection is done periodically to find and remove orphaned threads.
|
||||
// Coroutines yield out when they are done.
|
||||
static String script =
|
||||
"i,n = 0,0\n print(i)\n"
|
||||
+ "f = function() n=n+1; coroutine.yield(false) end\n"
|
||||
+ "while true do\n"
|
||||
+ " local cor = coroutine.wrap(f)\n"
|
||||
+ " cor()\n"
|
||||
+ " i = i + 1\n"
|
||||
+ " if i % 1000 == 0 then\n"
|
||||
+ " collectgarbage()\n"
|
||||
+ " print('threads:', i, 'executions:', n, collectgarbage('count'))\n"
|
||||
+ " end\n"
|
||||
+ "end\n";
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
// This timer controls how often each Java thread wakes up and checks if
|
||||
// it has been orhaned or not. A large number here will produce a long
|
||||
// delay between orphaning and colleciton, and a small number here will
|
||||
// consumer resources polling for orphaned status if there are many threads.
|
||||
LuaThread.thread_orphan_check_interval = 500;
|
||||
|
||||
// Should work with standard or debug globals.
|
||||
Globals globals = JsePlatform.standardGlobals();
|
||||
// Globals globals = JsePlatform.debugGlobals();
|
||||
|
||||
// Should work with plain compiler or lua-to-Java compiler.
|
||||
// org.luaj.vm2.luajc.LuaJC.install(globals);;
|
||||
|
||||
// Load and run the script, which launches coroutines over and over forever.
|
||||
LuaValue chunk = globals.load(script, "main");
|
||||
chunk.call();
|
||||
}
|
||||
|
||||
}
|
||||
95
examples/jse/SampleUsingClassLoader.java
Normal file
95
examples/jse/SampleUsingClassLoader.java
Normal file
@@ -0,0 +1,95 @@
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.luaj.vm2.Globals;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.jse.JsePlatform;
|
||||
import org.luaj.vm2.server.Launcher;
|
||||
import org.luaj.vm2.server.LuajClassLoader;
|
||||
|
||||
/** Example of using {@link LuajClassLoader} to launch scripts that are blocked from
|
||||
* interfering with globals from other scripts including shared static metatables.
|
||||
* <P>
|
||||
* This technique is useful in a server environment to expose the full set of
|
||||
* lua features to each script, while preventing scripts from interfering with
|
||||
* each other.
|
||||
* <P>
|
||||
* Because each Launch gets its own {@link LuajClassLoader}, it should be possible
|
||||
* to include the debug library, or let scripts manipulate shared metatables,
|
||||
* or luajava, which otherwise present challenges in a server environment.
|
||||
* <P>
|
||||
*/
|
||||
public class SampleUsingClassLoader {
|
||||
|
||||
/** Script that manipulates the shared string metatable.
|
||||
* When loaded by the {@link LuajClassLoader} via a {@link Launcher}
|
||||
* created by that class, each instance of {@link LuajClassLoader} will
|
||||
* have a completely separate version of all static variables.
|
||||
*/
|
||||
static String script =
|
||||
"print('args:', ...)\n" +
|
||||
"print('abc.foo', ('abc').foo)\n" +
|
||||
"getmetatable('abc').__index.foo = function() return 'bar' end\n" +
|
||||
"print('abc.foo', ('abc').foo)\n" +
|
||||
"print('abc:foo()', ('abc'):foo())\n" +
|
||||
"return math.pi\n";
|
||||
|
||||
public static void main(String[] s) throws Exception {
|
||||
// The default launcher used standard globals.
|
||||
RunUsingDefaultLauncher();
|
||||
RunUsingDefaultLauncher();
|
||||
// Example using custom launcher class that instantiates debug globals.
|
||||
RunUsingCustomLauncherClass();
|
||||
RunUsingCustomLauncherClass();
|
||||
}
|
||||
|
||||
static void RunUsingDefaultLauncher() throws Exception {
|
||||
Launcher launcher = LuajClassLoader.NewLauncher();
|
||||
// starts with pristine Globals including all luaj static variables.
|
||||
print(launcher.launch(script, new Object[] { "--------" }));
|
||||
// reuses Globals and static variables from previous step.
|
||||
print(launcher.launch(script, new Object[] {}));
|
||||
}
|
||||
|
||||
static void RunUsingCustomLauncherClass() throws Exception {
|
||||
Launcher launcher = LuajClassLoader.NewLauncher(MyLauncher.class);
|
||||
// starts with pristine Globals including all luaj static variables.
|
||||
print(launcher.launch(script, new Object[] { "=========" }));
|
||||
// reuses Globals and static variables from previous step.
|
||||
print(launcher.launch(script, new Object[] { "" }));
|
||||
}
|
||||
|
||||
/** Example of Launcher implementation performing specialized launching.
|
||||
* When loaded by the {@link LuajClassLoader} all luaj classes will be loaded
|
||||
* for each instance of the {@link Launcher} and not interfere with other
|
||||
* classes loaded by other instances.
|
||||
*/
|
||||
public static class MyLauncher implements Launcher {
|
||||
Globals g;
|
||||
public MyLauncher() {
|
||||
g = JsePlatform.debugGlobals();
|
||||
// ... plus any other customization of the user environment
|
||||
}
|
||||
|
||||
public Object[] launch(String script, Object[] arg) {
|
||||
LuaValue chunk = g.load(script, "main");
|
||||
return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
|
||||
}
|
||||
|
||||
public Object[] launch(InputStream script, Object[] arg) {
|
||||
LuaValue chunk = g.load(script, "main", "bt", g);
|
||||
return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
|
||||
}
|
||||
|
||||
public Object[] launch(Reader script, Object[] arg) {
|
||||
LuaValue chunk = g.load(script, "main");
|
||||
return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
|
||||
}
|
||||
}
|
||||
|
||||
/** Print the return values as strings. */
|
||||
private static void print(Object[] return_values) {
|
||||
for (int i =0; i<return_values.length; ++i)
|
||||
System.out.println("Return value " + return_values[i]);
|
||||
}
|
||||
}
|
||||
105
src/jse/org/luaj/vm2/server/DefaultLauncher.java
Normal file
105
src/jse/org/luaj/vm2/server/DefaultLauncher.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Luaj.org. 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.server;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.luaj.vm2.Globals;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.Varargs;
|
||||
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
|
||||
import org.luaj.vm2.lib.jse.JsePlatform;
|
||||
|
||||
/**
|
||||
* Default {@link Launcher} instance that creates standard globals
|
||||
* and runs the supplied scripts with chunk name 'main'.
|
||||
* <P>
|
||||
* Arguments are coerced into lua using {@link CoerceJavaToLua#coerce(Object)}.
|
||||
* <P>
|
||||
* Return values with simple types are coerced into Java simple types.
|
||||
* Tables, threads, and functions are returned as lua objects.
|
||||
*
|
||||
* @see Launcher
|
||||
* @see LuajClassLoader
|
||||
* @see LuajClassLoader#NewLauncher()
|
||||
* @see LuajClassLoader#NewLauncher(Class)
|
||||
* @since luaj 3.0.1
|
||||
*/
|
||||
public class DefaultLauncher implements Launcher {
|
||||
protected Globals g;
|
||||
|
||||
public DefaultLauncher() {
|
||||
g = JsePlatform.standardGlobals();
|
||||
}
|
||||
|
||||
/** Launches the script with chunk name 'main' */
|
||||
public Object[] launch(String script, Object[] arg) {
|
||||
return launchChunk(g.load(script, "main"), arg);
|
||||
}
|
||||
|
||||
/** Launches the script with chunk name 'main' and loading using modes 'bt' */
|
||||
public Object[] launch(InputStream script, Object[] arg) {
|
||||
return launchChunk(g.load(script, "main", "bt", g), arg);
|
||||
}
|
||||
|
||||
/** Launches the script with chunk name 'main' */
|
||||
public Object[] launch(Reader script, Object[] arg) {
|
||||
return launchChunk(g.load(script, "main"), arg);
|
||||
}
|
||||
|
||||
private Object[] launchChunk(LuaValue chunk, Object[] arg) {
|
||||
LuaValue args[] = new LuaValue[arg.length];
|
||||
for (int i = 0; i < args.length; ++i)
|
||||
args[i] = CoerceJavaToLua.coerce(arg[i]);
|
||||
Varargs results = chunk.invoke(LuaValue.varargsOf(args));
|
||||
|
||||
final int n = results.narg();
|
||||
Object return_values[] = new Object[n];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
LuaValue r = results.arg(i+1);
|
||||
switch (r.type()) {
|
||||
case LuaValue.TBOOLEAN:
|
||||
return_values[i] = r.toboolean();
|
||||
break;
|
||||
case LuaValue.TNUMBER:
|
||||
return_values[i] = r.todouble();
|
||||
break;
|
||||
case LuaValue.TINT:
|
||||
return_values[i] = r.toint();
|
||||
break;
|
||||
case LuaValue.TNIL:
|
||||
return_values[i] = null;
|
||||
break;
|
||||
case LuaValue.TSTRING:
|
||||
return_values[i] = r.tojstring();
|
||||
break;
|
||||
case LuaValue.TUSERDATA:
|
||||
return_values[i] = r.touserdata();
|
||||
break;
|
||||
default:
|
||||
return_values[i] = r;
|
||||
}
|
||||
}
|
||||
return return_values;
|
||||
}
|
||||
}
|
||||
70
src/jse/org/luaj/vm2/server/Launcher.java
Normal file
70
src/jse/org/luaj/vm2/server/Launcher.java
Normal file
@@ -0,0 +1,70 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Luaj.org. 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.server;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
/** Interface to launch lua scripts using the {@link LuajClassLoader}.
|
||||
* <P>
|
||||
* <em>Note: This class is experimental and subject to change in future versions.</em>
|
||||
* <P>
|
||||
* This interface is purposely genericized to defer class loading so that
|
||||
* luaj classes can come from the class loader.
|
||||
* <P>
|
||||
* The implementation should be acquired using {@link LuajClassLoader#NewLauncher()}
|
||||
* or {@link LuajClassLoader#NewLauncher(Class)} which ensure that the classes are
|
||||
* loaded to give each Launcher instance a pristine set of Globals, including
|
||||
* the shared metatables.
|
||||
*
|
||||
* @see LuajClassLoader
|
||||
* @see LuajClassLoader#NewLauncher()
|
||||
* @see LuajClassLoader#NewLauncher(Class)
|
||||
* @see DefaultLauncher
|
||||
* @since luaj 3.0.1
|
||||
*/
|
||||
public interface Launcher {
|
||||
|
||||
/** Launch a script contained in a String.
|
||||
*
|
||||
* @param script The script contents.
|
||||
* @param arg Optional arguments supplied to the script.
|
||||
* @return return values from the script.
|
||||
*/
|
||||
public Object[] launch(String script, Object[] arg);
|
||||
|
||||
/** Launch a script from an InputStream.
|
||||
*
|
||||
* @param script The script as an InputStream.
|
||||
* @param arg Optional arguments supplied to the script.
|
||||
* @return return values from the script.
|
||||
*/
|
||||
public Object[] launch(InputStream script, Object[] arg);
|
||||
|
||||
/** Launch a script from a Reader.
|
||||
*
|
||||
* @param script The script as a Reader.
|
||||
* @param arg Optional arguments supplied to the script.
|
||||
* @return return values from the script.
|
||||
*/
|
||||
public Object[] launch(Reader script, Object[] arg);
|
||||
}
|
||||
157
src/jse/org/luaj/vm2/server/LuajClassLoader.java
Normal file
157
src/jse/org/luaj/vm2/server/LuajClassLoader.java
Normal file
@@ -0,0 +1,157 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Luaj.org. 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.server;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class loader that can be used to launch a lua script in a Java VM that has a
|
||||
* unique set of classes for org.luaj classes.
|
||||
* <P>
|
||||
* <em>Note: This class is experimental and subject to change in future versions.</em>
|
||||
* <P>
|
||||
* By using a custom class loader per script, it allows the script to have
|
||||
* its own set of globals, including static values such as shared metatables
|
||||
* that cannot access lua values from other scripts because their classes are
|
||||
* loaded from different class loaders. Thus normally unsafe libraries such
|
||||
* as luajava can be exposed to scripts in a server environment using these
|
||||
* techniques.
|
||||
* <P>
|
||||
* All classes in the package "org.luaj.vm2." are considered user classes, and
|
||||
* loaded into this class loader from their bytes in the class path. Other
|
||||
* classes are considered systemc classes and loaded via the system loader. This
|
||||
* class set can be extended by overriding {@link #isUserClass(String)}.
|
||||
* <P>
|
||||
* The {@link Launcher} interface is loaded as a system class by exception so
|
||||
* that the caller may use it to launch lua scripts.
|
||||
* <P>
|
||||
* By default {@link #NewLauncher()} creates a subclass of {@link Launcher} of
|
||||
* type {@link DefaultLauncher} which creates debug globals, runs the script,
|
||||
* and prints the return values. This behavior can be changed by supplying a
|
||||
* different implementation class to {@link #NewLauncher(Class)} which must
|
||||
* extend {@link Launcher}.
|
||||
*
|
||||
* @see Launcher
|
||||
* @see #NewLauncher()
|
||||
* @see #NewLauncher(Class)
|
||||
* @see DefaultLauncher
|
||||
* @since luaj 3.0.1
|
||||
*/
|
||||
public class LuajClassLoader extends ClassLoader {
|
||||
|
||||
/** String describing the luaj packages to consider part of the user classes */
|
||||
static final String luajPackageRoot = "org.luaj.vm2.";
|
||||
|
||||
/** String describing the Launcher interface to be considered a system class */
|
||||
static final String launcherInterfaceRoot = Launcher.class.getName();
|
||||
|
||||
/** Local cache of classes loaded by this loader. */
|
||||
Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
|
||||
|
||||
/**
|
||||
* Construct a default {@link Launcher} instance that will load classes in
|
||||
* its own {@link LuajClassLoader} using the default implementation class
|
||||
* {@link DefaultLauncher}.
|
||||
* <P>
|
||||
* The {@link Launcher} that is returned will be a pristine luaj vm
|
||||
* whose classes are loaded into this loader including static variables
|
||||
* such as shared metatables, and should not be able to directly access
|
||||
* variables from other Launcher instances.
|
||||
*
|
||||
* @return {@link Launcher} instance that can be used to launch scripts.
|
||||
* @throws InstantiationException
|
||||
* @throws IllegalAccessException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public static Launcher NewLauncher() throws InstantiationException,
|
||||
IllegalAccessException, ClassNotFoundException {
|
||||
return NewLauncher(DefaultLauncher.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link Launcher} instance that will load classes in
|
||||
* its own {@link LuajClassLoader} using a user-supplied implementation class
|
||||
* that implements {@link Launcher}.
|
||||
* <P>
|
||||
* The {@link Launcher} that is returned will be a pristine luaj vm
|
||||
* whose classes are loaded into this loader including static variables
|
||||
* such as shared metatables, and should not be able to directly access
|
||||
* variables from other Launcher instances.
|
||||
*
|
||||
* @return instance of type 'launcher_class' that can be used to launch scripts.
|
||||
* @throws InstantiationException
|
||||
* @throws IllegalAccessException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public static Launcher NewLauncher(Class<? extends Launcher> launcher_class)
|
||||
throws InstantiationException, IllegalAccessException,
|
||||
ClassNotFoundException {
|
||||
final LuajClassLoader loader = new LuajClassLoader();
|
||||
final Object instance = loader.loadAsUserClass(launcher_class.getName())
|
||||
.newInstance();
|
||||
return (Launcher) instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a class name should be considered a user class and loaded
|
||||
* by this loader, or a system class and loaded by the system loader.
|
||||
* @param classname Class name to test.
|
||||
* @return true if this should be loaded into this class loader.
|
||||
*/
|
||||
public static boolean isUserClass(String classname) {
|
||||
return classname.startsWith(luajPackageRoot)
|
||||
&& !classname.startsWith(launcherInterfaceRoot);
|
||||
}
|
||||
|
||||
public Class<?> loadClass(String classname) throws ClassNotFoundException {
|
||||
if (classes.containsKey(classname))
|
||||
return classes.get(classname);
|
||||
if (!isUserClass(classname))
|
||||
return super.findSystemClass(classname);
|
||||
return loadAsUserClass(classname);
|
||||
}
|
||||
|
||||
private Class<?> loadAsUserClass(String classname) throws ClassNotFoundException {
|
||||
final String path = classname.replace('.', '/').concat(".class");
|
||||
InputStream is = getResourceAsStream(path);
|
||||
if (is != null) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
byte[] b = new byte[1024];
|
||||
for (int n = 0; (n = is.read(b)) >= 0;)
|
||||
baos.write(b, 0, n);
|
||||
byte[] bytes = baos.toByteArray();
|
||||
Class<?> result = super.defineClass(classname, bytes, 0,
|
||||
bytes.length);
|
||||
classes.put(classname, result);
|
||||
return result;
|
||||
} catch (java.io.IOException e) {
|
||||
throw new ClassNotFoundException("Read failed: " + classname
|
||||
+ ": " + e);
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("Not found: " + classname);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user