Add SampleSandboxed.java sample code to illustrate sandboxing.
This commit is contained in:
@@ -980,7 +980,7 @@ Files are no longer hosted at LuaForge.
|
|||||||
<li>Improve garbage collection of orphaned coroutines when yielding from debug hook functions (fixes issue #32).</li>
|
<li>Improve garbage collection of orphaned coroutines when yielding from debug hook functions (fixes issue #32).</li>
|
||||||
<li>LuaScriptEngineFactory.getScriptEngine() now returns new instance of LuaScriptEngine for each call.</li>
|
<li>LuaScriptEngineFactory.getScriptEngine() now returns new instance of LuaScriptEngine for each call.</li>
|
||||||
<li>Fix os.date("*t") to return hour in 24 hour format (fixes issue #45)</li>
|
<li>Fix os.date("*t") to return hour in 24 hour format (fixes issue #45)</li>
|
||||||
<li>Add ReadOnlyTable and ReadWriteShadowTable utility classes to simplify sandboxing.</li>
|
<li>Add SampleSandboxed.java sample code to illustrate sandboxing techniques.</li>
|
||||||
<li>Make string metatable a proper metatable, and make it read-only by default.</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 sample code that illustrates techniques in creating sandboxed environments.</li>
|
||||||
<li>Add convenience methods to Global to load string scripts with custom environment.</li>
|
<li>Add convenience methods to Global to load string scripts with custom environment.</li>
|
||||||
|
|||||||
@@ -6,72 +6,57 @@ import org.luaj.vm2.lib.jse.*;
|
|||||||
/** Simple program that illustrates basic sand-boxing of client scripts
|
/** Simple program that illustrates basic sand-boxing of client scripts
|
||||||
* in a server environment.
|
* in a server environment.
|
||||||
*
|
*
|
||||||
* <p>Although this sandboxing is done primarily in Java here, most of the
|
* <p>Although this sandboxing is done primarily in Java here, these
|
||||||
* same techniques can be done directly from lua using metatables.
|
* same techniques should all be possible directly from lua using metatables.
|
||||||
*
|
*
|
||||||
* <p> This class makes particular use of two utility classes,
|
* <p> The main goals of this sandbox are:
|
||||||
* {@link ReadOnlyTable} which is used to wrap shared global metatables
|
* <ul>
|
||||||
* such as the string metatable or the number metatable, and
|
* <li>Lightweight sandbox without using custom class loaders</li>
|
||||||
* {@link ReadWriteShadowTable} which can provide a lightweight user
|
* <li>use globals per-script and leave out dangerous libraries</li>
|
||||||
* environment around an arbitrarily deep shared globals instance while
|
* <li>use hook functions with Errors to limit lua scripts</li>
|
||||||
* limiting the resources at startup for small scripts that use few globals.
|
* <li>use read-only tables to protect shared metatables</li>
|
||||||
*
|
*
|
||||||
* @see Globals
|
* @see Globals
|
||||||
* @see LuaValue
|
* @see LuaValue
|
||||||
* @see ReadOnlyTable
|
|
||||||
* @see ReadWriteShadowTable
|
|
||||||
*/
|
*/
|
||||||
public class SampleSandboxed {
|
public class SampleSandboxed {
|
||||||
|
// These globals are used by the server to compile scripts.
|
||||||
// Globals use by the server itself, say to compile scripts that are loaded.
|
static Globals server_globals;
|
||||||
// In a real server there should be one of these per server thread.
|
|
||||||
// See SampleMultiThreaded.java for an example of multi-threaded setup.
|
|
||||||
static final Globals server_globals = JsePlatform.debugGlobals();
|
|
||||||
|
|
||||||
// A set of global functions and packages that are shared across users.
|
|
||||||
// These are exposed in a read-only fashion through user environment
|
|
||||||
// shadow tables.
|
|
||||||
static final Globals shared_globals = new Globals();
|
|
||||||
static {
|
|
||||||
// Load only packages known to be safe for multiple users.
|
|
||||||
shared_globals.load(new JseBaseLib());
|
|
||||||
shared_globals.load(new PackageLib());
|
|
||||||
shared_globals.load(new Bit32Lib());
|
|
||||||
shared_globals.load(new TableLib());
|
|
||||||
shared_globals.load(new StringLib());
|
|
||||||
shared_globals.load(new JseMathLib());
|
|
||||||
LoadState.install(shared_globals);
|
|
||||||
LuaC.install(shared_globals);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new user environment which refers to, but does not modify the
|
|
||||||
// shared globals. Each top-level user script should get their own copy
|
|
||||||
// of these which are a lightweight shadow of the shared globals.
|
|
||||||
// Writes to these shadow tables do not affect the original table.
|
|
||||||
static LuaTable create_user_environment() {
|
|
||||||
LuaTable user_environment = new ReadWriteShadowTable(shared_globals);
|
|
||||||
user_environment.set("_G", user_environment);
|
|
||||||
return user_environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
// Create server globals with just enough library support to compile user scripts.
|
||||||
|
server_globals = new Globals();
|
||||||
|
server_globals.load(new JseBaseLib());
|
||||||
|
server_globals.load(new PackageLib());
|
||||||
|
server_globals.load(new StringLib());
|
||||||
|
|
||||||
// Should be able to see and use globals as if they are owned by this environment.
|
// To load scripts, we occasionally need a math library in addition to compiler support.
|
||||||
expectSuccess("print('_G', _G)");
|
// To limit scripts using the debug library, they must be closures, so we only install LuaC.
|
||||||
expectSuccess("print('math.pi', math.pi)");;
|
server_globals.load(new JseMathLib());
|
||||||
expectSuccess("x = 'abc'; print('_G.x', _G.x); assert(_G.x == 'abc')");
|
LoadState.install(server_globals);
|
||||||
expectSuccess("print('_G.x', _G.x); assert(x == nil)");
|
LuaC.install(server_globals);
|
||||||
|
|
||||||
// Should not be able to write to global shared metatables.
|
// Set up the LuaString metatable to be read-only since it is shared across all scripts.
|
||||||
expectSuccess("print('string meta', getmetatable('abc'))");
|
LuaString.s_metatable = new ReadOnlyLuaTable(LuaString.s_metatable);
|
||||||
expectException("print('string meta.x=foo'); getmetatable('abc')['x']='foo'");
|
|
||||||
expectException("print('setmetatable(abc)', setmetatable('abc', {}))");
|
|
||||||
expectException("print('setmetatable(true)', setmetatable(true, {}))");
|
|
||||||
|
|
||||||
// Should be able to provide useful global server metatable behavior
|
// Example normal scripts that behave as expected.
|
||||||
// Example use of shared global metatable.
|
runScriptInSandbox( "return 'foo'" );
|
||||||
// Allows bools to be added to numbers.
|
runScriptInSandbox( "return ('abc'):len()" );
|
||||||
LuaBoolean.s_metatable = new ReadOnlyTable(new LuaValue[] {
|
runScriptInSandbox( "return getmetatable('abc')" );
|
||||||
|
runScriptInSandbox( "return getmetatable('abc').len" );
|
||||||
|
runScriptInSandbox( "return getmetatable('abc').__index" );
|
||||||
|
|
||||||
|
// Example user scripts that attempt rogue operations, and will fail.
|
||||||
|
runScriptInSandbox( "return setmetatable('abc', {})" );
|
||||||
|
runScriptInSandbox( "getmetatable('abc').len = function() end" );
|
||||||
|
runScriptInSandbox( "getmetatable('abc').__index = {}" );
|
||||||
|
runScriptInSandbox( "getmetatable('abc').__index.x = 1" );
|
||||||
|
runScriptInSandbox( "while true do print('loop') end" );
|
||||||
|
|
||||||
|
// Example use of other shared metatables, which should also be made read-only.
|
||||||
|
// This toy example allows booleans to be added to numbers.
|
||||||
|
runScriptInSandbox( "return 5 + 6, 5 + true, false + 6" );
|
||||||
|
LuaBoolean.s_metatable = new ReadOnlyLuaTable(LuaValue.tableOf(new LuaValue[] {
|
||||||
LuaValue.ADD, new TwoArgFunction() {
|
LuaValue.ADD, new TwoArgFunction() {
|
||||||
public LuaValue call(LuaValue x, LuaValue y) {
|
public LuaValue call(LuaValue x, LuaValue y) {
|
||||||
return LuaValue.valueOf(
|
return LuaValue.valueOf(
|
||||||
@@ -79,47 +64,93 @@ public class SampleSandboxed {
|
|||||||
(y == TRUE ? 1.0 : y.todouble()) );
|
(y == TRUE ? 1.0 : y.todouble()) );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
expectSuccess("print('pi + true', math.pi + true)");
|
runScriptInSandbox( "return 5 + 6, 5 + true, false + 6" );
|
||||||
|
|
||||||
// Should be able to use the metatable for our own globals.
|
|
||||||
expectSuccess("setmetatable(_G, {__index={foo='bar'}}); print('foo', foo); assert(foo == 'bar')");
|
|
||||||
|
|
||||||
// Subtables of globals can be modified but are shadow tables and don't affect
|
|
||||||
// shared globals or environment of other scripts.
|
|
||||||
expectSuccess("print('setmetatable(math)', setmetatable(math, {foo='bar'}))");
|
|
||||||
expectSuccess("print('getmetatable(math)', getmetatable(math)); assert(getmetatable(math) == nil)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run a script in a lua thread and limit it to a certain number
|
||||||
|
// of instructions by setting a hook function.
|
||||||
|
// Give each script its own copy of globals, but leave out libraries
|
||||||
|
// that contain functions that can be abused.
|
||||||
|
static void runScriptInSandbox(String script) {
|
||||||
|
|
||||||
// Run a script and return the results.
|
// Each script will have it's own set of globals, which should
|
||||||
static Varargs runScript(String script) {
|
// prevent leakage between scripts running on the same server.
|
||||||
LuaTable user_environment = create_user_environment();
|
Globals user_globals = new Globals();
|
||||||
LuaValue chunk = server_globals.load(script, "main", user_environment);
|
user_globals.load(new JseBaseLib());
|
||||||
return chunk.invoke();
|
user_globals.load(new PackageLib());
|
||||||
|
user_globals.load(new Bit32Lib());
|
||||||
|
user_globals.load(new TableLib());
|
||||||
|
user_globals.load(new StringLib());
|
||||||
|
user_globals.load(new JseMathLib());
|
||||||
|
|
||||||
|
// This library is dangerous as it gives unfettered access to the
|
||||||
|
// entire Java VM, so it's not suitable within this lightweight sandbox.
|
||||||
|
// user_globals.load(new LuajavaLib());
|
||||||
|
|
||||||
|
// Starting coroutines in scripts will result in threads that are
|
||||||
|
// not under the server control, so this libary should probably remain out.
|
||||||
|
// user_globals.load(new CoroutineLib());
|
||||||
|
|
||||||
|
// These are probably unwise and unnecessary for scripts on servers,
|
||||||
|
// although some date and time functions may be useful.
|
||||||
|
// user_globals.load(new JseIoLib());
|
||||||
|
// user_globals.load(new JseOsLib());
|
||||||
|
|
||||||
|
// Loading and compiling scripts from within scripts may also be
|
||||||
|
// prohibited, though in theory it should be fairly safe.
|
||||||
|
// LoadState.install(user_globals);
|
||||||
|
// LuaC.install(user_globals);
|
||||||
|
|
||||||
|
// The debug library must be loaded for hook functions to work, which
|
||||||
|
// allow us to limit scripts to run a certain number of instructions at a time.
|
||||||
|
// However we don't wish to expose the library in the user globals,
|
||||||
|
// so it is immediately removed from the user globals once created.
|
||||||
|
user_globals.load(new DebugLib());
|
||||||
|
LuaValue sethook = user_globals.get("debug").get("sethook");
|
||||||
|
user_globals.set("debug", LuaValue.NIL);
|
||||||
|
|
||||||
|
// Set up the script to run in its own lua thread, which allows us
|
||||||
|
// to set a hook function that limits the script to a specific number of cycles.
|
||||||
|
// Note that the environment is set to the user globals, even though the
|
||||||
|
// compiling is done with the server globals.
|
||||||
|
LuaValue chunk = server_globals.load(script, "main", user_globals);
|
||||||
|
LuaThread thread = new LuaThread(user_globals, chunk);
|
||||||
|
|
||||||
|
// Set the hook function to immediately throw an Error, which will not be
|
||||||
|
// handled by any Lua code other than the coroutine.
|
||||||
|
LuaValue hookfunc = new ZeroArgFunction() {
|
||||||
|
public LuaValue call() {
|
||||||
|
// A simple lua error may be caught by the script, but a
|
||||||
|
// Java Error will pass through to top and stop the script.
|
||||||
|
throw new Error("Script overran resource limits.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final int instruction_count = 20;
|
||||||
|
sethook.invoke(LuaValue.varargsOf(new LuaValue[] { thread, hookfunc,
|
||||||
|
LuaValue.EMPTYSTRING, LuaValue.valueOf(instruction_count) }));
|
||||||
|
|
||||||
|
// When we resume the thread, it will run up to 'instruction_count' instructions
|
||||||
|
// then call the hook function which will error out and stop the script.
|
||||||
|
Varargs result = thread.resume(LuaValue.NIL);
|
||||||
|
System.out.println("[["+script+"]] -> "+result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run a script, expecting it to succeed, to illustrate various uses
|
// Simple read-only table whose contents are initialized from another table.
|
||||||
// that should succeed without affecting shared resources.
|
static class ReadOnlyLuaTable extends LuaTable {
|
||||||
static Varargs expectSuccess(String script) {
|
public ReadOnlyLuaTable(LuaValue table) {
|
||||||
try {
|
presize(table.length(), 0);
|
||||||
return runScript(script);
|
for (Varargs n = table.next(LuaValue.NIL); !n.arg1().isnil(); n = table
|
||||||
} catch (Throwable t) {
|
.next(n.arg1())) {
|
||||||
System.out.println("script failed: "+t);
|
LuaValue key = n.arg1();
|
||||||
return LuaValue.NONE;
|
LuaValue value = n.arg(2);
|
||||||
|
super.rawset(key, value.istable() ? new ReadOnlyLuaTable(value) : value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public LuaValue setmetatable(LuaValue metatable) { return error("table is read-only"); }
|
||||||
// Run a script, expecting it to fail, to illustrate various expected ways
|
public void set(int key, LuaValue value) { error("table is read-only"); }
|
||||||
// rogue attempts will fail to abuse resources.
|
public void rawset(int key, LuaValue value) { error("table is read-only"); }
|
||||||
static Varargs expectException(String script) {
|
public void rawset(LuaValue key, LuaValue value) { error("table is read-only"); }
|
||||||
try {
|
public LuaValue remove(int pos) { return error("table is read-only"); }
|
||||||
Varargs result = runScript(script);
|
|
||||||
System.out.println("failure: script returned "+ result);
|
|
||||||
return result;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
System.out.println("success: "+t.getMessage());
|
|
||||||
return LuaValue.NONE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/** Utility class to construct a read-only LuaTable whose initial contents are
|
|
||||||
* initialized to some set of values and whose contents cannot be changed after
|
|
||||||
* construction, allowing the table to be shared in a multi-threaded context.
|
|
||||||
*/
|
|
||||||
public final class ReadOnlyTable extends LuaTable {
|
|
||||||
|
|
||||||
/** Empty read-only table with no metatable. */
|
|
||||||
public static final ReadOnlyTable empty_read_only_table =
|
|
||||||
new ReadOnlyTable();
|
|
||||||
|
|
||||||
/** Construct a ReadOnlyTable with a set of named key-value pairs.
|
|
||||||
* 'named' is a list of LuaValues, with the first being a key, the
|
|
||||||
* second being the corresponding value, and so on. All key-value
|
|
||||||
* pairs are copied into the table as part of construction,
|
|
||||||
* however only a shallow copy is done so values which are tables
|
|
||||||
* remain read-write even if accessed from this read-only table.
|
|
||||||
* @param named array of values in key,value,key,value order
|
|
||||||
* which are the key-value pairs that will appear in this read-only table.
|
|
||||||
*/
|
|
||||||
public ReadOnlyTable(LuaValue[] named) {
|
|
||||||
presize(named.length/2, 0);
|
|
||||||
for ( int i=0; i<named.length; i+=2 )
|
|
||||||
if (!named[i+1].isnil())
|
|
||||||
super.rawset(named[i], named[i+1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a ReadOnlyTable with initial values contained in 'table'
|
|
||||||
* All key-value pairs in 'table' are copied, however only a shallow
|
|
||||||
* copy is done so values which are tables remain read-write even
|
|
||||||
* if they are returned by accessing this read-only table.
|
|
||||||
* @param table LuaTable containing values to copy to this read-only table.
|
|
||||||
*/
|
|
||||||
public ReadOnlyTable(LuaTable table) {
|
|
||||||
this(table, null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a ReadOnlyTable with initial values contained in 'table'
|
|
||||||
* and a read-only metatable 'metatable'.
|
|
||||||
* All key-value pairs in 'table' are copied, however only a shallow
|
|
||||||
* copy is done so values which are tables remain read-write even
|
|
||||||
* if accessed from this read-only table.
|
|
||||||
* @param table LuaTable containing values to copy in initially.
|
|
||||||
* @param metatable ReadOnlyTable to be used as a metatable.
|
|
||||||
*/
|
|
||||||
public ReadOnlyTable(LuaTable table, ReadOnlyTable metatable) {
|
|
||||||
this(table, metatable, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a ReadOnlyTable with initial values contained in 'table'
|
|
||||||
* and a read-only metatable 'metatable', and optionally converting all
|
|
||||||
* contained keys and values that are tables into read-only tables.
|
|
||||||
* All key-value pairs in 'table' are copied, and when deepcopy is true,
|
|
||||||
* tables are converted to ReadOnlyTable recursively.
|
|
||||||
* @param table LuaTable containing values to copy in initially.
|
|
||||||
* @param metatable ReadOnlyTable to be used as a metatable.
|
|
||||||
* @param deepcopy when true, also converts table keys and values to read-only tables.
|
|
||||||
*/
|
|
||||||
public ReadOnlyTable(LuaTable table, ReadOnlyTable metatable, boolean deepcopy) {
|
|
||||||
presize(table.length(), 0);
|
|
||||||
for (Varargs n = table.next(LuaValue.NIL); !n.arg1().isnil();
|
|
||||||
n = table.next(n.arg1())) {
|
|
||||||
LuaValue key = n.arg1();
|
|
||||||
LuaValue value = n.arg(2);
|
|
||||||
super.rawset(deepcopy && key.istable()? new ReadOnlyTable(key.checktable()): key,
|
|
||||||
deepcopy && value.istable()? new ReadOnlyTable(value.checktable()): value);
|
|
||||||
}
|
|
||||||
this.m_metatable = metatable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Constructor for default instance which is an empty read-only table. */
|
|
||||||
private ReadOnlyTable() {}
|
|
||||||
|
|
||||||
/** Throw error indicating this is a read-only table. */
|
|
||||||
public LuaValue setmetatable(LuaValue metatable) {
|
|
||||||
return error("table is read-only");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Throw error indicating this is a read-only table. */
|
|
||||||
public void set(int key, LuaValue value) {
|
|
||||||
error("table is read-only");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Throw error indicating this is a read-only table. */
|
|
||||||
public void set(LuaValue key, LuaValue value) {
|
|
||||||
error("table is read-only");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Throw error indicating this is a read-only table. */
|
|
||||||
public void rawset(int key, LuaValue value) {
|
|
||||||
error("table is read-only");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Throw error indicating this is a read-only table. */
|
|
||||||
public void rawset(LuaValue key, LuaValue value) {
|
|
||||||
error("table is read-only");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Throw error indicating this is a read-only table. */
|
|
||||||
public LuaValue remove(int pos) {
|
|
||||||
return error("table is read-only");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaTable;
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
|
|
||||||
/** LuaTable that has all read/write properties of a normal table,
|
|
||||||
* but falls back to values in an underlying delegate table on reads,
|
|
||||||
* including wrapping any table result in another shadow table.
|
|
||||||
*
|
|
||||||
* <p>This simplifies creation of a safe unique environment for user scripts
|
|
||||||
* which falls back to a delegate shared globals table.
|
|
||||||
*/
|
|
||||||
public class ReadWriteShadowTable extends LuaTable {
|
|
||||||
/** The underlying table from which values are read when
|
|
||||||
* the table does not contain a key. The values in delegate
|
|
||||||
* should not be modified as a result of being a delegate for
|
|
||||||
* this table, however if the values are userdata that expose
|
|
||||||
* mutators, those values may undergo mutations once exposed.
|
|
||||||
*/
|
|
||||||
public final LuaValue delegate;
|
|
||||||
|
|
||||||
/** Construct a read-write shadow table around 'delegate' without
|
|
||||||
* copying elements, but retaining a reference to delegate.
|
|
||||||
* @param fallback The table containing values we would like to
|
|
||||||
* reference without affecting the contents of that table.
|
|
||||||
*/
|
|
||||||
public ReadWriteShadowTable(LuaValue delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Normal table get, but return delegate value if not found.
|
|
||||||
* If the delegate returns a table, wraps that in a read-write shadow
|
|
||||||
* and does a local rawset on that value before returning it.
|
|
||||||
* @param key LuaValue to look up.
|
|
||||||
*/
|
|
||||||
public LuaValue get(LuaValue key) {
|
|
||||||
LuaValue value = super.get(key);
|
|
||||||
if (value.isnil()) {
|
|
||||||
value = delegate.get(key);
|
|
||||||
if (value.istable())
|
|
||||||
value = new ReadWriteShadowTable(value);
|
|
||||||
rawset(key, value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user