/******************************************************************************* * Copyright (c) 2009 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 java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import org.luaj.vm2.compiler.DumpState; /** * Class to undump compiled lua bytecode into a {@link Prototype} instances. * <p> * The {@link LoadState} class implements {@link Globals#Undumper} * which is used to undump a string of bytes that represent a lua binary file * using either the C-based lua compiler, or luaj's {@link DumpState#dump} function. * <p> * The canonical method to load and execute code is done * indirectly using the Globals: * <pre> {@code * Globals globals = JsePlatform.standardGlobals(); * LuaValue chunk = globasl.load("print('hello, world')", "main.lua"); * chunk.call(); * } </pre> * This should work regardless of which {@link Globals.Compiler} * has been installed. * <p> * By default, when using {@link JsePlatform} or {@JmePlatform} * to construct globals, the {@link LoadState} is installed * as the default {@link Globals#undumper}. * <p> * * A lua binary file is created via {@link DumpState#dump}: * <pre> {@code * Globals globals = JsePlatform.standardGlobals(); * Prototype p = globals.compilePrototype(new StringReader("print('hello, world')"), "main.lua"); * ByteArrayOutputStream o = new ByteArrayOutputStream(); * DumpState.dump(p, o, false); * byte[] lua_binary_file_bytes = o.toByteArray(); * } </pre> * * The {@link LoadState} may be used directly to undump these bytes: * <pre> {@code * Prototypep = LoadState.instance.undump(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua"); * LuaClosure c = new LuaClosure(p, globals); * c.call(); * } </pre> * * * More commonly, the {@link Globals#undumper} may be used to undump them: * <pre> {@code * Prototype p = globals.loadPrototype(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua", "b"); * LuaClosure c = new LuaClosure(p, globals); * c.call(); * } </pre> * * @see LuaCompiler * @see LuaClosure * @see LuaFunction * @see LoadState#compiler * @see LoadState#load(InputStream, String, LuaValue) * @see LuaC * @see LuaJC */ public class LoadState implements Globals.Undumper { /** Shared instance of Globals.Undumper to use loading prototypes from binary lua files */ public static final Globals.Undumper instance = new LoadState(); /** format corresponding to non-number-patched lua, all numbers are floats or doubles */ public static final int NUMBER_FORMAT_FLOATS_OR_DOUBLES = 0; /** format corresponding to non-number-patched lua, all numbers are ints */ public static final int NUMBER_FORMAT_INTS_ONLY = 1; /** format corresponding to number-patched lua, all numbers are 32-bit (4 byte) ints */ public static final int NUMBER_FORMAT_NUM_PATCH_INT32 = 4; // type constants public static final int LUA_TINT = (-2); public static final int LUA_TNONE = (-1); public static final int LUA_TNIL = 0; public static final int LUA_TBOOLEAN = 1; public static final int LUA_TLIGHTUSERDATA = 2; public static final int LUA_TNUMBER = 3; public static final int LUA_TSTRING = 4; public static final int LUA_TTABLE = 5; public static final int LUA_TFUNCTION = 6; public static final int LUA_TUSERDATA = 7; public static final int LUA_TTHREAD = 8; public static final int LUA_TVALUE = 9; /** The character encoding to use for file encoding. Null means the default encoding */ public static String encoding = null; /** Signature byte indicating the file is a compiled binary chunk */ public static final byte[] LUA_SIGNATURE = { '\033', 'L', 'u', 'a' }; /** Data to catch conversion errors */ public static final byte[] LUAC_TAIL = { (byte) 0x19, (byte) 0x93, '\r', '\n', (byte) 0x1a, '\n', }; /** Name for compiled chunks */ public static final String SOURCE_BINARY_STRING = "binary string"; /** for header of binary files -- this is Lua 5.2 */ public static final int LUAC_VERSION = 0x52; /** for header of binary files -- this is the official format */ public static final int LUAC_FORMAT = 0; /** size of header of binary files */ public static final int LUAC_HEADERSIZE = 12; // values read from the header private int luacVersion; private int luacFormat; private boolean luacLittleEndian; private int luacSizeofInt; private int luacSizeofSizeT; private int luacSizeofInstruction; private int luacSizeofLuaNumber; private int luacNumberFormat; /** input stream from which we are loading */ public final DataInputStream is; /** Name of what is being loaded? */ String name; private static final LuaValue[] NOVALUES = {}; private static final Prototype[] NOPROTOS = {}; private static final LocVars[] NOLOCVARS = {}; private static final LuaString[] NOSTRVALUES = {}; private static final Upvaldesc[] NOUPVALDESCS = {}; private static final int[] NOINTS = {}; /** Read buffer */ private byte[] buf = new byte[512]; /** Install this class as the standard Globals.Undumper for the supplied Globals */ public static void install(Globals globals) { globals.undumper = instance; } /** Load a 4-byte int value from the input stream * @return the int value laoded. **/ int loadInt() throws IOException { is.readFully(buf,0,4); return luacLittleEndian? (buf[3] << 24) | ((0xff & buf[2]) << 16) | ((0xff & buf[1]) << 8) | (0xff & buf[0]): (buf[0] << 24) | ((0xff & buf[1]) << 16) | ((0xff & buf[2]) << 8) | (0xff & buf[3]); } /** Load an array of int values from the input stream * @return the array of int values laoded. **/ int[] loadIntArray() throws IOException { int n = loadInt(); if ( n == 0 ) return NOINTS; // read all data at once int m = n << 2; if ( buf.length < m ) buf = new byte[m]; is.readFully(buf,0,m); int[] array = new int[n]; for ( int i=0, j=0; i<n; ++i, j+=4 ) array[i] = luacLittleEndian? (buf[j+3] << 24) | ((0xff & buf[j+2]) << 16) | ((0xff & buf[j+1]) << 8) | (0xff & buf[j+0]): (buf[j+0] << 24) | ((0xff & buf[j+1]) << 16) | ((0xff & buf[j+2]) << 8) | (0xff & buf[j+3]); return array; } /** Load a long value from the input stream * @return the long value laoded. **/ long loadInt64() throws IOException { int a,b; if ( this.luacLittleEndian ) { a = loadInt(); b = loadInt(); } else { b = loadInt(); a = loadInt(); } return (((long)b)<<32) | (((long)a)&0xffffffffL); } /** Load a lua strin gvalue from the input stream * @return the {@link LuaString} value laoded. **/ LuaString loadString() throws IOException { int size = this.luacSizeofSizeT == 8? (int) loadInt64(): loadInt(); if ( size == 0 ) return null; byte[] bytes = new byte[size]; is.readFully( bytes, 0, size ); return LuaString.valueOf( bytes, 0, bytes.length - 1 ); } /** * Convert bits in a long value to a {@link LuaValue}. * @param bits long value containing the bits * @return {@link LuaInteger} or {@link LuaDouble} whose value corresponds to the bits provided. */ public static LuaValue longBitsToLuaNumber( long bits ) { if ( ( bits & ( ( 1L << 63 ) - 1 ) ) == 0L ) { return LuaValue.ZERO; } int e = (int)((bits >> 52) & 0x7ffL) - 1023; if ( e >= 0 && e < 31 ) { long f = bits & 0xFFFFFFFFFFFFFL; int shift = 52 - e; long intPrecMask = ( 1L << shift ) - 1; if ( ( f & intPrecMask ) == 0 ) { int intValue = (int)( f >> shift ) | ( 1 << e ); return LuaInteger.valueOf( ( ( bits >> 63 ) != 0 ) ? -intValue : intValue ); } } return LuaValue.valueOf( Double.longBitsToDouble(bits) ); } /** * Load a number from a binary chunk * @return the {@link LuaValue} loaded * @throws IOException if an i/o exception occurs */ LuaValue loadNumber() throws IOException { if ( luacNumberFormat == NUMBER_FORMAT_INTS_ONLY ) { return LuaInteger.valueOf( loadInt() ); } else { return longBitsToLuaNumber( loadInt64() ); } } /** * Load a list of constants from a binary chunk * @param f the function prototype * @throws IOException if an i/o exception occurs */ void loadConstants(Prototype f) throws IOException { int n = loadInt(); LuaValue[] values = n>0? new LuaValue[n]: NOVALUES; for ( int i=0; i<n; i++ ) { switch ( is.readByte() ) { case LUA_TNIL: values[i] = LuaValue.NIL; break; case LUA_TBOOLEAN: values[i] = (0 != is.readUnsignedByte()? LuaValue.TRUE: LuaValue.FALSE); break; case LUA_TINT: values[i] = LuaInteger.valueOf( loadInt() ); break; case LUA_TNUMBER: values[i] = loadNumber(); break; case LUA_TSTRING: values[i] = loadString(); break; default: throw new IllegalStateException("bad constant"); } } f.k = values; n = loadInt(); Prototype[] protos = n>0? new Prototype[n]: NOPROTOS; for ( int i=0; i<n; i++ ) protos[i] = loadFunction(f.source); f.p = protos; } void loadUpvalues(Prototype f) throws IOException { int n = loadInt(); f.upvalues = n>0? new Upvaldesc[n]: NOUPVALDESCS; for (int i=0; i<n; i++) { boolean instack = is.readByte() != 0; int idx = ((int) is.readByte()) & 0xff; f.upvalues[i] = new Upvaldesc(null, instack, idx); } } /** * Load the debug info for a function prototype * @param f the function Prototype * @throws IOException if there is an i/o exception */ void loadDebug( Prototype f ) throws IOException { f.source = loadString(); f.lineinfo = loadIntArray(); int n = loadInt(); f.locvars = n>0? new LocVars[n]: NOLOCVARS; for ( int i=0; i<n; i++ ) { LuaString varname = loadString(); int startpc = loadInt(); int endpc = loadInt(); f.locvars[i] = new LocVars(varname, startpc, endpc); } n = loadInt(); for ( int i=0; i<n; i++ ) f.upvalues[i].name = loadString(); } /** * Load a function prototype from the input stream * @param p name of the source * @return {@link Prototype} instance that was loaded * @throws IOException */ public Prototype loadFunction(LuaString p) throws IOException { Prototype f = new Prototype(); //// this.L.push(f); // f.source = loadString(); // if ( f.source == null ) // f.source = p; f.linedefined = loadInt(); f.lastlinedefined = loadInt(); f.numparams = is.readUnsignedByte(); f.is_vararg = is.readUnsignedByte(); f.maxstacksize = is.readUnsignedByte(); f.code = loadIntArray(); loadConstants(f); loadUpvalues(f); loadDebug(f); // TODO: add check here, for debugging purposes, I believe // see ldebug.c // IF (!luaG_checkcode(f), "bad code"); // this.L.pop(); return f; } /** * Load the lua chunk header values. * @throws IOException if an i/o exception occurs. */ public void loadHeader() throws IOException { luacVersion = is.readByte(); luacFormat = is.readByte(); luacLittleEndian = (0 != is.readByte()); luacSizeofInt = is.readByte(); luacSizeofSizeT = is.readByte(); luacSizeofInstruction = is.readByte(); luacSizeofLuaNumber = is.readByte(); luacNumberFormat = is.readByte(); for (int i=0; i < LUAC_TAIL.length; ++i) if (is.readByte() != LUAC_TAIL[i]) throw new LuaError("Unexpeted byte in luac tail of header, index="+i); } /** * Load input stream as a lua binary chunk if the first 4 bytes are the lua binary signature. * @param stream InputStream to read, after having read the first byte already * @param name Name to apply to the loaded chunk * @return {@link Prototype} that was loaded, or null if the first 4 bytes were not the lua signature. * @throws IOException if an IOException occurs */ public Prototype undump(InputStream stream, String chunkname) throws IOException { // check rest of signature if ( stream.read() != LUA_SIGNATURE[0] || stream.read() != LUA_SIGNATURE[1] || stream.read() != LUA_SIGNATURE[2] || stream.read() != LUA_SIGNATURE[3] ) return null; // load file as a compiled chunk String sname = getSourceName(chunkname); LoadState s = new LoadState( stream, sname ); s.loadHeader(); // check format switch ( s.luacNumberFormat ) { case NUMBER_FORMAT_FLOATS_OR_DOUBLES: case NUMBER_FORMAT_INTS_ONLY: case NUMBER_FORMAT_NUM_PATCH_INT32: break; default: throw new LuaError("unsupported int size"); } return s.loadFunction( LuaString.valueOf(sname) ); } /** * Construct a source name from a supplied chunk name * @param name String name that appears in the chunk * @return source file name */ public static String getSourceName(String name) { String sname = name; if ( name.startsWith("@") || name.startsWith("=") ) sname = name.substring(1); else if ( name.startsWith("\033") ) sname = SOURCE_BINARY_STRING; return sname; } /** Private constructor for create a load state */ private LoadState( InputStream stream, String name ) { this.name = name; this.is = new DataInputStream( stream ); } private LoadState() { this.name = ""; this.is = null; } }