/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /** * This code is heavily based on the Pluto code base. Copyright below */ /* Tamed Pluto - Heavy-duty persistence for Lua * Copyright (C) 2004 by Ben Sunshine-Hill, and released into the public * domain. People making use of this software as part of an application * are politely requested to email the author at sneftel@gmail.com * with a brief description of the application, primarily to satisfy his * curiosity. * * 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. * * Instrumented by Stefan Reich (info@luaos.net) * for Mobile Lua (http://luaos.net/pages/mobile-lua.php) */ #include "lua_persistence.h" #include "double_serialization.h" #include "lua_persistence_util.h" #include "common/stream.h" #include "lobject.h" #include "lstate.h" #include "lgc.h" #include "lopcodes.h" namespace Lua { struct UnSerializationInfo { lua_State *luaState; Common::ReadStream *readStream; }; static void unpersist(UnSerializationInfo *info); static void unpersistBoolean(UnSerializationInfo *info); static void unpersistNumber(UnSerializationInfo *info); static void unpersistString(UnSerializationInfo *info); static void unpersistTable(UnSerializationInfo *info, int index); static void unpersistFunction(UnSerializationInfo *info, int index); static void unpersistThread(UnSerializationInfo *info, int index); static void unpersistProto(UnSerializationInfo *info, int index); static void unpersistUpValue(UnSerializationInfo *info, int index); static void unpersistUserData(UnSerializationInfo *info, int index); static void unpersistPermanent(UnSerializationInfo *info, int index); void unpersistLua(lua_State *luaState, Common::ReadStream *readStream) { UnSerializationInfo info; info.luaState = luaState; info.readStream = readStream; // The process starts with the lua stack as follows: // >>>>> permTbl // That's the table of permanents // Make sure there is enough room on the stack lua_checkstack(luaState, 3); // Create a table to hold indexes of everything thats already been read lua_newtable(luaState); // >>>>> permTbl indexTbl // Prevent garbage collection while we unserialize lua_gc(luaState, LUA_GCSTOP, 0); // Unserialize the root object unpersist(&info); // >>>>> permTbl indexTbl rootObj // Re-start garbage collection lua_gc(luaState, LUA_GCRESTART, 0); // Remove the indexTbl lua_replace(luaState, 2); // >>>>> permTbl rootObj } /* The object is left on the stack. This is primarily used by unserialize, but * may be used by GCed objects that may incur cycles in order to preregister * the object. */ static void registerObjectInIndexTable(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... obj // Make sure there is enough room on the stack lua_checkstack(info->luaState, 1); lua_pushvalue(info->luaState, -1); // >>>>> permTbl indexTbl ...... obj obj // Push the k/v pair into the indexTbl lua_rawseti(info->luaState, 2, index); // >>>>> permTbl indexTbl ...... obj } static void unpersist(UnSerializationInfo *info) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 2); byte isARealValue = info->readStream->readByte(); if (isARealValue) { int index = info->readStream->readSint32LE(); int type = info->readStream->readSint32LE(); switch (type) { case LUA_TBOOLEAN: unpersistBoolean(info); break; case LUA_TLIGHTUSERDATA: // You can't serialize a pointer // It would be meaningless on the next run assert(0); break; case LUA_TNUMBER: unpersistNumber(info); break; case LUA_TSTRING: unpersistString(info); break; case LUA_TTABLE: unpersistTable(info, index); break; case LUA_TFUNCTION: unpersistFunction(info, index); break; case LUA_TTHREAD: unpersistThread(info, index); break; case LUA_TPROTO: unpersistProto(info, index); break; case LUA_TUPVAL: unpersistUpValue(info, index); break; case LUA_TUSERDATA: unpersistUserData(info, index); break; case PERMANENT_TYPE: unpersistPermanent(info, index); break; default: assert(0); } // >>>>> permTbl indexTbl ...... obj assert(lua_type(info->luaState, -1) == type || type == PERMANENT_TYPE || // Remember, upvalues get a special dispensation, as described in boxUpValue (lua_type(info->luaState, -1) == LUA_TFUNCTION && type == LUA_TUPVAL)); registerObjectInIndexTable(info, index); // >>>>> permTbl indexTbl ...... obj } else { int index = info->readStream->readSint32LE(); if (index == 0) { lua_pushnil(info->luaState); // >>>>> permTbl indexTbl ...... nil } else { // Fetch the object from the indexTbl lua_rawgeti(info->luaState, 2, index); // >>>>> permTbl indexTbl ...... ?obj? assert(!lua_isnil(info->luaState, -1)); } // >>>>> permTbl indexTbl ...... obj/nil } // >>>>> permTbl indexTbl ...... obj/nil } static void unpersistBoolean(UnSerializationInfo *info) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 1); int value = info->readStream->readSint32LE(); lua_pushboolean(info->luaState, value); // >>>>> permTbl indexTbl ...... bool } static void unpersistNumber(UnSerializationInfo *info) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 1); // Read the serialized double Util::SerializedDouble serializedValue; serializedValue.significandOne = info->readStream->readUint32LE(); serializedValue.signAndSignificandTwo = info->readStream->readUint32LE(); serializedValue.exponent = info->readStream->readSint16LE(); lua_Number value = Util::decodeDouble(serializedValue); lua_pushnumber(info->luaState, value); // >>>>> permTbl indexTbl ...... num } static void unpersistString(UnSerializationInfo *info) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 1); uint32 length = info->readStream->readUint32LE(); char *string = new char[length]; info->readStream->read(string, length); lua_pushlstring(info->luaState, string, length); // >>>>> permTbl indexTbl ...... string delete[] string; } static void unserializeSpecialTable(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 1); unpersist(info); // >>>>> permTbl indexTbl ...... spfunc lua_call(info->luaState, 0, 1); // >>>>> permTbl indexTbl ...... tbl } static void unserializeLiteralTable(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 3); // Preregister table for handling of cycles lua_newtable(info->luaState); // >>>>> permTbl indexTbl ...... tbl registerObjectInIndexTable(info, index); // >>>>> permTbl indexTbl ...... tbl // Unserialize metatable unpersist(info); // >>>>> permTbl indexTbl ...... tbl ?metaTbl/nil? if (lua_istable(info->luaState, -1)) { // >>>>> permTbl indexTbl ...... tbl metaTbl lua_setmetatable(info->luaState, -2); // >>>>> permTbl indexTbl ...... tbl } else { // >>>>> permTbl indexTbl ...... tbl nil lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... tbl } // >>>>> permTbl indexTbl ...... tbl while (1) { // >>>>> permTbl indexTbl ...... tbl unpersist(info); // >>>>> permTbl indexTbl ...... tbl key/nil // The table serialization is nil terminated if (lua_isnil(info->luaState, -1)) { // >>>>> permTbl indexTbl ...... tbl nil lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... tbl break; } // >>>>> permTbl indexTbl ...... tbl key unpersist(info); // >>>>> permTbl indexTbl ...... tbl value lua_rawset(info->luaState, -3); // >>>>> permTbl indexTbl ...... tbl } } void unpersistTable(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 1); int isSpecial = info->readStream->readSint32LE(); if (isSpecial) { unserializeSpecialTable(info, index); // >>>>> permTbl indexTbl ...... tbl } else { unserializeLiteralTable(info, index); // >>>>> permTbl indexTbl ...... tbl } } void unpersistFunction(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 2); byte numUpValues = info->readStream->readByte(); LClosure *lclosure = (LClosure *)lua_newLclosure(info->luaState, numUpValues, hvalue(&info->luaState->l_gt)); pushClosure(info->luaState, (Closure *)lclosure); // >>>>> permTbl indexTbl ...... func // Put *some* proto in the closure, before the GC can find it lclosure->p = makeFakeProto(info->luaState, numUpValues); //Also, we need to temporarily fill the upvalues lua_pushnil(info->luaState); // >>>>> permTbl indexTbl ...... func nil for (byte i = 0; i < numUpValues; ++i) { lclosure->upvals[i] = createUpValue(info->luaState, -1); } lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... func // I can't see offhand how a function would ever get to be self- // referential, but just in case let's register it early registerObjectInIndexTable(info, index); // Now that it's safe, we can get the real proto unpersist(info); // >>>>> permTbl indexTbl ...... func proto lclosure->p = gco2p(getObject(info->luaState, -1)->value.gc); lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... func for (byte i = 0; i < numUpValues; ++i) { // >>>>> permTbl indexTbl ...... func unpersist(info); // >>>>> permTbl indexTbl ...... func func2 unboxUpValue(info->luaState); // >>>>> permTbl indexTbl ...... func upValue lclosure->upvals[i] = gco2uv(getObject(info->luaState, -1)->value.gc); lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... func } // Finally, the fenv unpersist(info); // >>>>> permTbl indexTbl ...... func ?fenv/nil? if (!lua_isnil(info->luaState, -1)) { // >>>>> permTbl indexTbl ...... func fenv lua_setfenv(info->luaState, -2); // >>>>> permTbl indexTbl ...... func } else { // >>>>> permTbl indexTbl ...... func nil lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... func } // >>>>> permTbl indexTbl ...... func } void unpersistThread(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... lua_State *L2; uint32 stacklimit = 0; L2 = lua_newthread(info->luaState); lua_checkstack(info->luaState, 3); // L1: permTbl indexTbl ...... thread // L2: (empty) registerObjectInIndexTable(info, index); // First, deserialize the object stack uint32 stackSize = info->readStream->readUint32LE(); lua_checkstack(info->luaState, (int)stackSize); // Make sure that the first stack element (a nil, representing // the imaginary top-level C function) is written to the very, // very bottom of the stack L2->top--; for (uint32 i = 0; i < stackSize; ++i) { unpersist(info); // L1: permTbl indexTbl ...... thread obj* } lua_xmove(info->luaState, L2, stackSize); // L1: permTbl indexTbl ...... thread // L2: obj* // Hereafter, stacks refer to L1 // Now, deserialize the CallInfo stack uint32 numFrames = info->readStream->readUint32LE(); lua_reallocCallInfo(L2, numFrames * 2); for (uint32 i = 0; i < numFrames; ++i) { CallInfo *ci = L2->base_ci + i; uint32 stackbase = info->readStream->readUint32LE(); uint32 stackfunc = info->readStream->readUint32LE(); uint32 stacktop = info->readStream->readUint32LE(); ci->nresults = info->readStream->readSint32LE(); uint32 savedpc = info->readStream->readUint32LE(); if (stacklimit < stacktop) { stacklimit = stacktop; } ci->base = L2->stack + stackbase; ci->func = L2->stack + stackfunc; ci->top = L2->stack + stacktop; ci->savedpc = (ci != L2->base_ci) ? ci_func(ci)->l.p->code + savedpc : 0; ci->tailcalls = 0; // Update the pointer each time, to keep the GC happy L2->ci = ci; } // >>>>> permTbl indexTbl ...... thread // Deserialize the state's other parameters, with the exception of upval stuff L2->savedpc = L2->ci->savedpc; L2->status = info->readStream->readByte(); uint32 stackbase = info->readStream->readUint32LE(); uint32 stacktop = info->readStream->readUint32LE(); L2->errfunc = info->readStream->readUint32LE(); L2->base = L2->stack + stackbase; L2->top = L2->stack + stacktop; // Finally, "reopen" upvalues. See serializeUpVal() for why we do this UpVal *uv; GCObject **nextslot = &L2->openupval; global_State *g = G(L2); while (true) { unpersist(info); // >>>>> permTbl indexTbl ...... thread upVal/nil // The list is terminated by a nil if (lua_isnil(info->luaState, -1)) { // >>>>> permTbl indexTbl ...... thread nil lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... thread break; } // >>>>> permTbl indexTbl ...... thread boxedUpVal unboxUpValue(info->luaState); // >>>>> permTbl indexTbl ...... thread boxedUpVal uv = &(getObject(info->luaState, -1)->value.gc->uv); lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... thread uint32 stackpos = info->readStream->readUint32LE(); uv->v = L2->stack + stackpos; GCUnlink(info->luaState, (GCObject *)uv); uv->marked = luaC_white(g); *nextslot = (GCObject *)uv; nextslot = &uv->next; uv->u.l.prev = &G(L2)->uvhead; uv->u.l.next = G(L2)->uvhead.u.l.next; uv->u.l.next->u.l.prev = uv; G(L2)->uvhead.u.l.next = uv; lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); } *nextslot = NULL; // The stack must be valid at least to the highest value among the CallInfos // 'top' and the values up to there must be filled with 'nil' lua_checkstack(L2, (int)stacklimit); for (StkId o = L2->top; o <= L2->top + stacklimit; ++o) { setnilvalue(o); } } void unpersistProto(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... // We have to be careful. The GC expects a lot out of protos. In particular, we need // to give the function a valid string for its source, and valid code, even before we // actually read in the real code. TString *source = lua_newlstr(info->luaState, "", 0); Proto *p = lua_newproto(info->luaState); p->source = source; p->sizecode = 1; p->code = (Instruction *)lua_reallocv(info->luaState, NULL, 0, 1, sizeof(Instruction)); p->code[0] = CREATE_ABC(OP_RETURN, 0, 1, 0); p->maxstacksize = 2; p->sizek = 0; p->sizep = 0; lua_checkstack(info->luaState, 2); pushProto(info->luaState, p); // >>>>> permTbl indexTbl ...... proto // We don't need to register early, since protos can never ever be // involved in cyclic references // Read in constant references int sizek = info->readStream->readSint32LE(); lua_reallocvector(info->luaState, p->k, 0, sizek, TValue); for (int i = 0; i < sizek; ++i) { // >>>>> permTbl indexTbl ...... proto unpersist(info); // >>>>> permTbl indexTbl ...... proto k setobj2s(info->luaState, &p->k[i], getObject(info->luaState, -1)); p->sizek++; lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... proto } // >>>>> permTbl indexTbl ...... proto // Read in sub-proto references int sizep = info->readStream->readSint32LE(); lua_reallocvector(info->luaState, p->p, 0, sizep, Proto *); for (int i = 0; i < sizep; ++i) { // >>>>> permTbl indexTbl ...... proto unpersist(info); // >>>>> permTbl indexTbl ...... proto subproto p->p[i] = (Proto *)getObject(info->luaState, -1)->value.gc; p->sizep++; lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... proto } // >>>>> permTbl indexTbl ...... proto // Read in code p->sizecode = info->readStream->readSint32LE(); lua_reallocvector(info->luaState, p->code, 1, p->sizecode, Instruction); info->readStream->read(p->code, sizeof(Instruction) * p->sizecode); /* Read in upvalue names */ p->sizeupvalues = info->readStream->readSint32LE(); if (p->sizeupvalues) { lua_reallocvector(info->luaState, p->upvalues, 0, p->sizeupvalues, TString *); for (int i = 0; i < p->sizeupvalues; ++i) { // >>>>> permTbl indexTbl ...... proto unpersist(info); // >>>>> permTbl indexTbl ...... proto str p->upvalues[i] = lua_newlstr(info->luaState, lua_tostring(info->luaState, -1), strlen(lua_tostring(info->luaState, -1))); lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... proto } } // >>>>> permTbl indexTbl ...... proto // Read in local variable infos p->sizelocvars = info->readStream->readSint32LE(); if (p->sizelocvars) { lua_reallocvector(info->luaState, p->locvars, 0, p->sizelocvars, LocVar); for (int i = 0; i < p->sizelocvars; ++i) { // >>>>> permTbl indexTbl ...... proto unpersist(info); // >>>>> permTbl indexTbl ...... proto str p->locvars[i].varname = lua_newlstr(info->luaState, lua_tostring(info->luaState, -1), strlen(lua_tostring(info->luaState, -1))); lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... proto p->locvars[i].startpc = info->readStream->readSint32LE(); p->locvars[i].endpc = info->readStream->readSint32LE(); } } // >>>>> permTbl indexTbl ...... proto // Read in source string unpersist(info); // >>>>> permTbl indexTbl ...... proto sourceStr p->source = lua_newlstr(info->luaState, lua_tostring(info->luaState, -1), strlen(lua_tostring(info->luaState, -1))); lua_pop(info->luaState, 1); // >>>>> permTbl indexTbl ...... proto // Read in line numbers p->sizelineinfo = info->readStream->readSint32LE(); if (p->sizelineinfo) { lua_reallocvector(info->luaState, p->lineinfo, 0, p->sizelineinfo, int); info->readStream->read(p->lineinfo, sizeof(int) * p->sizelineinfo); } /* Read in linedefined and lastlinedefined */ p->linedefined = info->readStream->readSint32LE(); p->lastlinedefined = info->readStream->readSint32LE(); // Read in misc values p->nups = info->readStream->readByte(); p->numparams = info->readStream->readByte(); p->is_vararg = info->readStream->readByte(); p->maxstacksize = info->readStream->readByte(); } void unpersistUpValue(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... lua_checkstack(info->luaState, 2); boxUpValue_start(info->luaState); // >>>>> permTbl indexTbl ...... func registerObjectInIndexTable(info, index); unpersist(info); // >>>>> permTbl indexTbl ...... func obj boxUpValue_finish(info->luaState); // >>>>> permTbl indexTbl ...... func } void unpersistUserData(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 2); int isspecial = info->readStream->readSint32LE(); if (isspecial) { unpersist(info); // >>>>> permTbl indexTbl ...... specialFunc lua_call(info->luaState, 0, 1); // >>>>> permTbl indexTbl ...... udata } else { uint32 length = info->readStream->readUint32LE(); lua_newuserdata(info->luaState, length); // >>>>> permTbl indexTbl ...... udata registerObjectInIndexTable(info, index); info->readStream->read(lua_touserdata(info->luaState, -1), length); unpersist(info); // >>>>> permTbl indexTbl ...... udata metaTable/nil lua_setmetatable(info->luaState, -2); // >>>>> permTbl indexTbl ...... udata } // >>>>> permTbl indexTbl ...... udata } void unpersistPermanent(UnSerializationInfo *info, int index) { // >>>>> permTbl indexTbl ...... // Make sure there is enough room on the stack lua_checkstack(info->luaState, 2); unpersist(info); // >>>>> permTbl indexTbl ...... permKey lua_gettable(info->luaState, 1); // >>>>> permTbl indexTbl ...... perm } } // End of namespace Lua