fengari
Version:
A Lua VM written in JS ES6 targeting the browser
293 lines (249 loc) • 8.35 kB
JavaScript
"use strict";
const {
LUA_SIGNATURE,
constant_types: {
LUA_TBOOLEAN,
LUA_TLNGSTR,
LUA_TNIL,
LUA_TNUMFLT,
LUA_TNUMINT,
LUA_TSHRSTR
},
thread_status: { LUA_ERRSYNTAX },
is_luastring,
luastring_eq,
to_luastring
} = require('./defs.js');
const ldo = require('./ldo.js');
const lfunc = require('./lfunc.js');
const lobject = require('./lobject.js');
const {
MAXARG_sBx,
POS_A,
POS_Ax,
POS_B,
POS_Bx,
POS_C,
POS_OP,
SIZE_A,
SIZE_Ax,
SIZE_B,
SIZE_Bx,
SIZE_C,
SIZE_OP
} = require('./lopcodes.js');
const { lua_assert } = require("./llimits.js");
const { luaS_bless } = require('./lstring.js');
const {
luaZ_read,
ZIO
} = require('./lzio.js');
let LUAC_DATA = [0x19, 0x93, 13, 10, 0x1a, 10];
class BytecodeParser {
constructor(L, Z, name) {
this.intSize = 4;
this.size_tSize = 4;
this.instructionSize = 4;
this.integerSize = 4;
this.numberSize = 8;
lua_assert(Z instanceof ZIO, "BytecodeParser only operates on a ZIO");
lua_assert(is_luastring(name));
if (name[0] === 64 /* ('@').charCodeAt(0) */ || name[0] === 61 /* ('=').charCodeAt(0) */)
this.name = name.subarray(1);
else if (name[0] == LUA_SIGNATURE[0])
this.name = to_luastring("binary string", true);
else
this.name = name;
this.L = L;
this.Z = Z;
// Used to do buffer to number conversions
this.arraybuffer = new ArrayBuffer(
Math.max(this.intSize, this.size_tSize, this.instructionSize, this.integerSize, this.numberSize)
);
this.dv = new DataView(this.arraybuffer);
this.u8 = new Uint8Array(this.arraybuffer);
}
read(size) {
let u8 = new Uint8Array(size);
if(luaZ_read(this.Z, u8, 0, size) !== 0)
this.error("truncated");
return u8;
}
LoadByte() {
if (luaZ_read(this.Z, this.u8, 0, 1) !== 0)
this.error("truncated");
return this.u8[0];
}
LoadInt() {
if (luaZ_read(this.Z, this.u8, 0, this.intSize) !== 0)
this.error("truncated");
return this.dv.getInt32(0, true);
}
LoadNumber() {
if (luaZ_read(this.Z, this.u8, 0, this.numberSize) !== 0)
this.error("truncated");
return this.dv.getFloat64(0, true);
}
LoadInteger() {
if (luaZ_read(this.Z, this.u8, 0, this.integerSize) !== 0)
this.error("truncated");
return this.dv.getInt32(0, true);
}
LoadSize_t() {
return this.LoadInteger();
}
LoadString() {
let size = this.LoadByte();
if (size === 0xFF)
size = this.LoadSize_t();
if (size === 0)
return null;
return luaS_bless(this.L, this.read(size-1));
}
/* creates a mask with 'n' 1 bits at position 'p' */
static MASK1(n, p) {
return ((~((~0)<<(n)))<<(p));
}
LoadCode(f) {
let n = this.LoadInt();
let p = BytecodeParser;
for (let i = 0; i < n; i++) {
if (luaZ_read(this.Z, this.u8, 0, this.instructionSize) !== 0)
this.error("truncated");
let ins = this.dv.getUint32(0, true);
f.code[i] = {
code: ins,
opcode: (ins >> POS_OP) & p.MASK1(SIZE_OP, 0),
A: (ins >> POS_A) & p.MASK1(SIZE_A, 0),
B: (ins >> POS_B) & p.MASK1(SIZE_B, 0),
C: (ins >> POS_C) & p.MASK1(SIZE_C, 0),
Bx: (ins >> POS_Bx) & p.MASK1(SIZE_Bx, 0),
Ax: (ins >> POS_Ax) & p.MASK1(SIZE_Ax, 0),
sBx: ((ins >> POS_Bx) & p.MASK1(SIZE_Bx, 0)) - MAXARG_sBx
};
}
}
LoadConstants(f) {
let n = this.LoadInt();
for (let i = 0; i < n; i++) {
let t = this.LoadByte();
switch (t) {
case LUA_TNIL:
f.k.push(new lobject.TValue(LUA_TNIL, null));
break;
case LUA_TBOOLEAN:
f.k.push(new lobject.TValue(LUA_TBOOLEAN, this.LoadByte() !== 0));
break;
case LUA_TNUMFLT:
f.k.push(new lobject.TValue(LUA_TNUMFLT, this.LoadNumber()));
break;
case LUA_TNUMINT:
f.k.push(new lobject.TValue(LUA_TNUMINT, this.LoadInteger()));
break;
case LUA_TSHRSTR:
case LUA_TLNGSTR:
f.k.push(new lobject.TValue(LUA_TLNGSTR, this.LoadString()));
break;
default:
this.error(`unrecognized constant '${t}'`);
}
}
}
LoadProtos(f) {
let n = this.LoadInt();
for (let i = 0; i < n; i++) {
f.p[i] = new lfunc.Proto(this.L);
this.LoadFunction(f.p[i], f.source);
}
}
LoadUpvalues(f) {
let n = this.LoadInt();
for (let i = 0; i < n; i++) {
f.upvalues[i] = {
name: null,
instack: this.LoadByte(),
idx: this.LoadByte()
};
}
}
LoadDebug(f) {
let n = this.LoadInt();
for (let i = 0; i < n; i++)
f.lineinfo[i] = this.LoadInt();
n = this.LoadInt();
for (let i = 0; i < n; i++) {
f.locvars[i] = {
varname: this.LoadString(),
startpc: this.LoadInt(),
endpc: this.LoadInt()
};
}
n = this.LoadInt();
for (let i = 0; i < n; i++) {
f.upvalues[i].name = this.LoadString();
}
}
LoadFunction(f, psource) {
f.source = this.LoadString();
if (f.source === null) /* no source in dump? */
f.source = psource; /* reuse parent's source */
f.linedefined = this.LoadInt();
f.lastlinedefined = this.LoadInt();
f.numparams = this.LoadByte();
f.is_vararg = this.LoadByte() !== 0;
f.maxstacksize = this.LoadByte();
this.LoadCode(f);
this.LoadConstants(f);
this.LoadUpvalues(f);
this.LoadProtos(f);
this.LoadDebug(f);
}
checkliteral(s, msg) {
let buff = this.read(s.length);
if (!luastring_eq(buff, s))
this.error(msg);
}
checkHeader() {
this.checkliteral(LUA_SIGNATURE.subarray(1), "not a"); /* 1st char already checked */
if (this.LoadByte() !== 0x53)
this.error("version mismatch in");
if (this.LoadByte() !== 0)
this.error("format mismatch in");
this.checkliteral(LUAC_DATA, "corrupted");
this.intSize = this.LoadByte();
this.size_tSize = this.LoadByte();
this.instructionSize = this.LoadByte();
this.integerSize = this.LoadByte();
this.numberSize = this.LoadByte();
this.checksize(this.intSize, 4, "int");
this.checksize(this.size_tSize, 4, "size_t");
this.checksize(this.instructionSize, 4, "instruction");
this.checksize(this.integerSize, 4, "integer");
this.checksize(this.numberSize, 8, "number");
if (this.LoadInteger() !== 0x5678)
this.error("endianness mismatch in");
if (this.LoadNumber() !== 370.5)
this.error("float format mismatch in");
}
error(why) {
lobject.luaO_pushfstring(this.L, to_luastring("%s: %s precompiled chunk"), this.name, to_luastring(why));
ldo.luaD_throw(this.L, LUA_ERRSYNTAX);
}
checksize(byte, size, tname) {
if (byte !== size)
this.error(`${tname} size mismatch in`);
}
}
const luaU_undump = function(L, Z, name) {
let S = new BytecodeParser(L, Z, name);
S.checkHeader();
let cl = lfunc.luaF_newLclosure(L, S.LoadByte());
ldo.luaD_inctop(L);
L.stack[L.top-1].setclLvalue(cl);
cl.p = new lfunc.Proto(L);
S.LoadFunction(cl.p, null);
lua_assert(cl.nupvalues === cl.p.upvalues.length);
/* luai_verifycode */
return cl;
};
module.exports.luaU_undump = luaU_undump;