| /******************************************************************************* |
| * 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.vm3; |
| |
| import java.io.DataInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| |
| import org.luaj.vm3.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; |
| } |
| } |