Add ReadOnlyTable and ReadWriteShadowTable, add sandboxing example code, make string metatable a real metatable.

This commit is contained in:
James Roseborough
2015-03-22 00:36:44 +00:00
parent 85381770a7
commit ba43e15e72
7 changed files with 586 additions and 222 deletions

View File

@@ -980,6 +980,10 @@ 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>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>Add ReadOnlyTable and ReadWriteShadowTable utility classes to simplify sandboxing.</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>
</ul></td></tr>
</table></td></tr></table>

View File

@@ -0,0 +1,125 @@
import org.luaj.vm2.*;
import org.luaj.vm2.compiler.LuaC;
import org.luaj.vm2.lib.*;
import org.luaj.vm2.lib.jse.*;
/** Simple program that illustrates basic sand-boxing of client scripts
* in a server environment.
*
* <p>Although this sandboxing is done primarily in Java here, most of the
* same techniques can be done directly from lua using metatables.
*
* <p> This class makes particular use of two utility classes,
* {@link ReadOnlyTable} which is used to wrap shared global metatables
* such as the string metatable or the number metatable, and
* {@link ReadWriteShadowTable} which can provide a lightweight user
* environment around an arbitrarily deep shared globals instance while
* limiting the resources at startup for small scripts that use few globals.
*
* @see Globals
* @see LuaValue
* @see ReadOnlyTable
* @see ReadWriteShadowTable
*/
public class SampleSandboxed {
// Globals use by the server itself, say to compile scripts that are loaded.
// 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) {
// Should be able to see and use globals as if they are owned by this environment.
expectSuccess("print('_G', _G)");
expectSuccess("print('math.pi', math.pi)");;
expectSuccess("x = 'abc'; print('_G.x', _G.x); assert(_G.x == 'abc')");
expectSuccess("print('_G.x', _G.x); assert(x == nil)");
// Should not be able to write to global shared metatables.
expectSuccess("print('string meta', getmetatable('abc'))");
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 use of shared global metatable.
// Allows bools to be added to numbers.
LuaBoolean.s_metatable = new ReadOnlyTable(new LuaValue[] {
LuaValue.ADD, new TwoArgFunction() {
public LuaValue call(LuaValue x, LuaValue y) {
return LuaValue.valueOf(
(x == TRUE ? 1.0 : x.todouble()) +
(y == TRUE ? 1.0 : y.todouble()) );
}
},
});
expectSuccess("print('pi + true', math.pi + true)");
// 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 and return the results.
static Varargs runScript(String script) {
LuaTable user_environment = create_user_environment();
LuaValue chunk = server_globals.load(script, "main", user_environment);
return chunk.invoke();
}
// Run a script, expecting it to succeed, to illustrate various uses
// that should succeed without affecting shared resources.
static Varargs expectSuccess(String script) {
try {
return runScript(script);
} catch (Throwable t) {
System.out.println("script failed: "+t);
return LuaValue.NONE;
}
}
// Run a script, expecting it to fail, to illustrate various expected ways
// rogue attempts will fail to abuse resources.
static Varargs expectException(String script) {
try {
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;
}
}
}

View File

@@ -203,18 +203,54 @@ public class Globals extends LuaTable {
return load(new StrReader(script), script);
}
/** Convenience function to load a string value as a script with a custom environment.
* Must be lua source.
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @param environment LuaTable to be used as the environment for the loaded function.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @throws LuaError if the script could not be compiled.
*/
public LuaValue load(String script, String chunkname, LuaTable environment) {
return load(new StrReader(script), chunkname, environment);
}
/** Load the content form a reader as a text file. Must be lua source.
* The source is converted to UTF-8, so any characters appearing in quoted literals
* above the range 128 will be converted into multiple bytes. */
* above the range 128 will be converted into multiple bytes.
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @throws LuaError if the script could not be compiled.
*/
public LuaValue load(Reader reader, String chunkname) {
return load(new UTF8Stream(reader), chunkname, "t", this);
}
/** Load the content form an input stream as a binary chunk or text file. */
public LuaValue load(InputStream is, String chunkname, String mode, LuaValue env) {
/** Load the content form a reader as a text file, supplying a custom environment.
* Must be lua source. The source is converted to UTF-8, so any characters
* appearing in quoted literals above the range 128 will be converted into
* multiple bytes.
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @param environment LuaTable to be used as the environment for the loaded function.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @throws LuaError if the script could not be compiled.
*/
public LuaValue load(Reader reader, String chunkname, LuaTable environment) {
return load(new UTF8Stream(reader), chunkname, "t", environment);
}
/** Load the content form an input stream as a binary chunk or text file.
* @param is Input stream containing a lua script or compiled lua"
* @param chunkname Name that will be used within the chunk as the source.
* @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
* @param environment LuaTable to be used as the environment for the loaded function.
* */
public LuaValue load(InputStream is, String chunkname, String mode, LuaValue environment) {
try {
Prototype p = loadPrototype(is, chunkname, mode);
return loader.load(p, chunkname, env);
return loader.load(p, chunkname, environment);
} catch (LuaError l) {
throw l;
} catch (Exception e) {
@@ -225,6 +261,9 @@ public class Globals extends LuaTable {
/** Load lua source or lua binary from an input stream into a Prototype.
* The InputStream is either a binary lua chunk starting with the lua binary chunk signature,
* or a text input file. If it is a text input file, it is interpreted as a UTF-8 byte sequence.
* @param is Input stream containing a lua script or compiled lua"
* @param chunkname Name that will be used within the chunk as the source.
* @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
*/
public Prototype loadPrototype(InputStream is, String chunkname, String mode) throws IOException {
if (mode.indexOf('b') >= 0) {

View File

@@ -62,7 +62,7 @@ import org.luaj.vm2.lib.StringLib;
*/
public class LuaString extends LuaValue {
/** The singleton instance representing lua {@code true} */
/** The singleton instance for string metatables that forwards to the string functions */
public static LuaValue s_metatable;
/** The bytes for the string. These <em><b>must not be mutated directly</b></em> because
@@ -254,11 +254,6 @@ public class LuaString extends LuaValue {
return decodeAsUtf8(m_bytes, m_offset, m_length);
}
// get is delegated to the string library
public LuaValue get(LuaValue key) {
return s_metatable!=null? gettable(this,key): StringLib.instance.get(key);
}
// unary operators
public LuaValue neg() { double d = scannumber(); return Double.isNaN(d)? super.neg(): valueOf(-d); }

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* 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");
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* 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;
}
}

View File

@@ -29,6 +29,7 @@ import org.luaj.vm2.Buffer;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.ReadOnlyTable;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.compiler.DumpState;
@@ -61,55 +62,35 @@ import org.luaj.vm2.compiler.DumpState;
*/
public class StringLib extends TwoArgFunction {
public static LuaTable instance;
public static final ReadOnlyTable lib_functions;
static {
LuaTable t = new LuaTable();
t.set("byte", new byte_());
t.set("char", new char_());
t.set("dump", new dump());
t.set("find", new find());
t.set("format", new format());
t.set("gmatch", new gmatch());
t.set("gsub", new gsub());
t.set("len", new len());
t.set("lower", new lower());
t.set("match", new match());
t.set("rep", new rep());
t.set("reverse", new reverse());
t.set("sub", new sub());
t.set("upper", new upper());
lib_functions = new ReadOnlyTable(t);
LuaString.s_metatable = new ReadOnlyTable(
new LuaValue[] { INDEX, lib_functions });
}
public StringLib() {
}
public LuaValue call(LuaValue modname, LuaValue env) {
LuaTable t = new LuaTable();
bind(t, StringLib1.class, new String[] {
"dump", "len", "lower", "reverse", "upper", } );
bind(t, StringLibV.class, new String[] {
"byte", "char", "find", "format",
"gmatch", "gsub", "match", "rep",
"sub"} );
env.set("string", t);
instance = t;
if ( LuaString.s_metatable == null )
LuaString.s_metatable = tableOf( new LuaValue[] { INDEX, t } );
env.get("package").get("loaded").set("string", t);
return t;
}
static final class StringLib1 extends OneArgFunction {
public LuaValue call(LuaValue arg) {
switch ( opcode ) {
case 0: return dump(arg); // dump (function)
case 1: return StringLib.len(arg); // len (function)
case 2: return lower(arg); // lower (function)
case 3: return reverse(arg); // reverse (function)
case 4: return upper(arg); // upper (function)
}
return NIL;
}
}
static final class StringLibV extends VarArgFunction {
public Varargs invoke(Varargs args) {
switch ( opcode ) {
case 0: return StringLib.byte_( args );
case 1: return StringLib.char_( args );
case 2: return StringLib.find( args );
case 3: return StringLib.format( args );
case 4: return StringLib.gmatch( args );
case 5: return StringLib.gsub( args );
case 6: return StringLib.match( args );
case 7: return StringLib.rep( args );
case 8: return StringLib.sub( args );
}
return NONE;
}
env.set("string", lib_functions);
env.get("package").get("loaded").set("string", lib_functions);
return lib_functions;
}
/**
@@ -123,7 +104,8 @@ public class StringLib extends TwoArgFunction {
*
* @param args the calling args
*/
static Varargs byte_( Varargs args ) {
static final class byte_ extends VarArgFunction {
public Varargs invoke(Varargs args) {
LuaString s = args.checkstring(1);
int l = s.m_length;
int posi = posrelat( args.optint(2,1), l );
@@ -140,6 +122,7 @@ public class StringLib extends TwoArgFunction {
v[i] = valueOf(s.luaByte(posi+i-1));
return varargsOf(v);
}
}
/**
* string.char (...)
@@ -152,7 +135,8 @@ public class StringLib extends TwoArgFunction {
*
* @param args the calling VM
*/
public static Varargs char_( Varargs args) {
static final class char_ extends VarArgFunction {
public Varargs invoke(Varargs args) {
int n = args.narg();
byte[] bytes = new byte[n];
for ( int i=0, a=1; i<n; i++, a++ ) {
@@ -162,6 +146,7 @@ public class StringLib extends TwoArgFunction {
}
return LuaString.valueUsing( bytes );
}
}
/**
* string.dump (function)
@@ -172,7 +157,8 @@ public class StringLib extends TwoArgFunction {
*
* TODO: port dumping code as optional add-on
*/
static LuaValue dump( LuaValue arg ) {
static final class dump extends OneArgFunction {
public LuaValue call(LuaValue arg) {
LuaValue f = arg.checkfunction();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
@@ -182,6 +168,7 @@ public class StringLib extends TwoArgFunction {
return error( e.getMessage() );
}
}
}
/**
* string.find (s, pattern [, init [, plain]])
@@ -199,9 +186,11 @@ public class StringLib extends TwoArgFunction {
* If the pattern has captures, then in a successful match the captured values
* are also returned, after the two indices.
*/
static Varargs find( Varargs args ) {
static final class find extends VarArgFunction {
public Varargs invoke(Varargs args) {
return str_find_aux( args, true );
}
}
/**
* string.format (formatstring, ...)
@@ -226,7 +215,8 @@ public class StringLib extends TwoArgFunction {
* This function does not accept string values containing embedded zeros,
* except as arguments to the q option.
*/
static Varargs format( Varargs args ) {
static final class format extends VarArgFunction {
public Varargs invoke(Varargs args) {
LuaString fmt = args.checkstring( 1 );
final int n = fmt.length();
Buffer result = new Buffer(n);
@@ -293,6 +283,7 @@ public class StringLib extends TwoArgFunction {
return result.tostring();
}
}
private static void addquoted(Buffer buf, LuaString s) {
int c;
@@ -503,11 +494,13 @@ public class StringLib extends TwoArgFunction {
* For this function, a '^' at the start of a pattern does not work as an anchor,
* as this would prevent the iteration.
*/
static Varargs gmatch( Varargs args ) {
static final class gmatch extends VarArgFunction {
public Varargs invoke(Varargs args) {
LuaString src = args.checkstring( 1 );
LuaString pat = args.checkstring( 2 );
return new GMatchAux(args, src, pat);
}
}
static class GMatchAux extends VarArgFunction {
private final int srclen;
@@ -578,7 +571,8 @@ public class StringLib extends TwoArgFunction {
* x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
* --> x="lua-5.1.tar.gz"
*/
static Varargs gsub( Varargs args ) {
static final class gsub extends VarArgFunction {
public Varargs invoke(Varargs args) {
LuaString src = args.checkstring( 1 );
final int srclen = src.length();
LuaString p = args.checkstring( 2 );
@@ -610,6 +604,7 @@ public class StringLib extends TwoArgFunction {
lbuf.append( src.substring( soffset, srclen ) );
return varargsOf(lbuf.tostring(), valueOf(n));
}
}
/**
* string.len (s)
@@ -617,9 +612,11 @@ public class StringLib extends TwoArgFunction {
* Receives a string and returns its length. The empty string "" has length 0.
* Embedded zeros are counted, so "a\000bc\000" has length 5.
*/
static LuaValue len( LuaValue arg ) {
static final class len extends OneArgFunction {
public LuaValue call(LuaValue arg) {
return arg.checkstring().len();
}
}
/**
* string.lower (s)
@@ -628,9 +625,11 @@ public class StringLib extends TwoArgFunction {
* changed to lowercase. All other characters are left unchanged.
* The definition of what an uppercase letter is depends on the current locale.
*/
static LuaValue lower( LuaValue arg ) {
static final class lower extends OneArgFunction {
public LuaValue call(LuaValue arg) {
return valueOf( arg.checkjstring().toLowerCase() );
}
}
/**
* string.match (s, pattern [, init])
@@ -641,16 +640,19 @@ public class StringLib extends TwoArgFunction {
* A third, optional numerical argument init specifies where to start the
* search; its default value is 1 and may be negative.
*/
static Varargs match( Varargs args ) {
static final class match extends VarArgFunction {
public Varargs invoke(Varargs args) {
return str_find_aux( args, false );
}
}
/**
* string.rep (s, n)
*
* Returns a string that is the concatenation of n copies of the string s.
*/
static Varargs rep( Varargs args ) {
static final class rep extends VarArgFunction {
public Varargs invoke(Varargs args) {
LuaString s = args.checkstring( 1 );
int n = args.checkint( 2 );
final byte[] bytes = new byte[ s.length() * n ];
@@ -660,13 +662,15 @@ public class StringLib extends TwoArgFunction {
}
return LuaString.valueUsing( bytes );
}
}
/**
* string.reverse (s)
*
* Returns a string that is the string s reversed.
*/
static LuaValue reverse( LuaValue arg ) {
static final class reverse extends OneArgFunction {
public LuaValue call(LuaValue arg) {
LuaString s = arg.checkstring();
int n = s.length();
byte[] b = new byte[n];
@@ -674,6 +678,7 @@ public class StringLib extends TwoArgFunction {
b[j] = (byte) s.luaByte(i);
return LuaString.valueUsing( b );
}
}
/**
* string.sub (s, i [, j])
@@ -686,7 +691,8 @@ public class StringLib extends TwoArgFunction {
* string.sub(s, -i)
* returns a suffix of s with length i.
*/
static Varargs sub( Varargs args ) {
static final class sub extends VarArgFunction {
public Varargs invoke(Varargs args) {
final LuaString s = args.checkstring( 1 );
final int l = s.length();
@@ -704,6 +710,7 @@ public class StringLib extends TwoArgFunction {
return EMPTYSTRING;
}
}
}
/**
* string.upper (s)
@@ -712,9 +719,11 @@ public class StringLib extends TwoArgFunction {
* changed to uppercase. All other characters are left unchanged.
* The definition of what a lowercase letter is depends on the current locale.
*/
static LuaValue upper( LuaValue arg ) {
static final class upper extends OneArgFunction {
public LuaValue call(LuaValue arg) {
return valueOf(arg.checkjstring().toUpperCase());
}
}
/**
* This utility method implements both string.find and string.match.