#include "serializelua.hpp" enum PackCodes { LUA_PK_TRUE = LUA_TT_SENTINEL, LUA_PK_FALSE, LUA_PK_REFERENCE, LUA_PK_ENDTABLE }; class DeserializeError { }; class Deserializer { LuaVar id_to_value_; LuaOldStack LS_; eng::string &error_; StreamBuffer *sb_; int next_id_; void deserialize_table_r(LuaSlot target) { LuaVar key, val; LuaOldStack LS(LS_.state(), key, val); LS.newtable(target); LS.rawset(id_to_value_, next_id_++, target); bool hasmeta = sb_->read_bool(); if (hasmeta) { uint8_t vtype = sb_->read_uint8(); deserialize_r(vtype, val); if (!LS.istable(val)) { error_ = "serialized data contains invalid metatable"; throw DeserializeError(); } LS.setmetatable(target, val); } while (true) { uint8_t ktype = sb_->read_uint8(); if (ktype == LUA_PK_ENDTABLE) { break; } deserialize_r(ktype, key); uint8_t vtype = sb_->read_uint8(); deserialize_r(vtype, val); LS.rawset(target, key, val); } } void deserialize_r(uint8_t kind, LuaSlot target) { switch (kind) { case LUA_TNIL: { LS_.set(target, LuaNil); return; } case LUA_PK_TRUE: { LS_.set(target, true); return; } case LUA_PK_FALSE: { LS_.set(target, false); return; } case LUA_TLIGHTUSERDATA: { LS_.set(target, LuaToken(sb_->read_uint64())); return; } case LUA_TNUMBER: { LS_.set(target, sb_->read_double()); return; } case LUA_TSTRING: { LS_.set(target, sb_->read_string()); LS_.rawset(id_to_value_, next_id_++, target); return; } case LUA_TT_GENERAL: { deserialize_table_r(target); return; } case LUA_TT_GLOBALENV: { LS_.getglobaltable(target); return; } case LUA_TT_TANGIBLE: { LS_.maketan(target, sb_->read_int64()); return; } case LUA_TT_CLASS: { LS_.makeclass(target, sb_->read_string()); return; } case LUA_PK_REFERENCE: { int32_t key = sb_->read_int32(); LS_.rawget(target, id_to_value_, key); if (LS_.isnil(target)) { error_ = "invalid backward reference in serialized data"; throw DeserializeError(); } return; } default: { error_ = util::ss("unrecognized type token in serialized data: ", kind); throw DeserializeError(); } } } public: Deserializer(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb, eng::string &error) : LS_(LS0.state(), id_to_value_), error_(error), sb_(sb), next_id_(1) { LS_.newtable(id_to_value_); int top = lua_gettop(LS_.state()); try { uint16_t v = sb_->read_uint16(); if (v != 0xD096) { error_ = "This is not a serialized data structure"; return; } uint8_t b = sb_->read_uint8(); deserialize_r(b, val); } catch (const StreamEof &e) { error_ = "EOF reached while deserializing data"; lua_settop(LS_.state(), top); LS_.set(val, LuaNil); } catch (DeserializeError e) { lua_settop(LS_.state(), top); LS_.set(val, LuaNil); } LS_.result(); } }; // If the serialize routine encounters an error, it must set the // error message string. In that case, it is assumed that the contents // of the serialization buffer is no longer valid and there is no // requirement to try to keep it valid. class Serializer { LuaVar lookup_; LuaVar value_to_id_; LuaOldStack LS_; eng::string &error_; StreamBuffer *sb_; int next_id_; void serialize_table_r(LuaSlot tab) { LuaVar key, val; LuaOldStack SLS(LS_.state(), key, val); sb_->write_uint8(LUA_TT_GENERAL); SLS.getmetatable(val, tab); if (SLS.isnil(val)) { sb_->write_bool(false); } else { sb_->write_bool(true); serialize_r(val); } SLS.set(key, LuaNil); while (SLS.next(tab, key, val)) { serialize_r(key); if (!error_.empty()) { SLS.result(); return; } serialize_r(val); if (!error_.empty()) { SLS.result(); return; } } sb_->write_uint8(LUA_PK_ENDTABLE); SLS.result(); } void serialize_r(LuaSlot val) { int tt = LS_.xtype(val); switch (tt) { case LUA_TNIL: { sb_->write_uint8(LUA_TNIL); return; } case LUA_TBOOLEAN: { if (LS_.ckboolean(val)) { sb_->write_uint8(LUA_PK_TRUE); } else { sb_->write_uint8(LUA_PK_FALSE); } return; } case LUA_TLIGHTUSERDATA: { sb_->write_uint8(LUA_TLIGHTUSERDATA); sb_->write_uint64(LS_.cktoken(val).value); return; } case LUA_TNUMBER: { sb_->write_uint8(LUA_TNUMBER); sb_->write_double(LS_.cknumber(val)); return; } case LUA_TSTRING: { LS_.rawget(lookup_, value_to_id_, val); if (!LS_.isnil(lookup_)) { sb_->write_uint8(LUA_PK_REFERENCE); sb_->write_int32(LS_.ckint(lookup_)); } else { LS_.rawset(value_to_id_, val, next_id_++); sb_->write_uint8(LUA_TSTRING); sb_->write_string(LS_.ckstring(val)); } return; } case LUA_TTABLE: { // LS.xtype should never return LUA_TTABLE. error_ = "Bad xtype in serialization"; return; } case LUA_TFUNCTION: { error_ = "Cannot serialize closures"; return; } case LUA_TUSERDATA: { error_ = "Cannot serialize userdata"; return; } case LUA_TTHREAD: { error_ = "Cannot serialize coroutines"; return; } case LUA_TT_GENERAL: { LS_.rawget(lookup_, value_to_id_, val); if (!LS_.isnil(lookup_)) { sb_->write_uint8(LUA_PK_REFERENCE); sb_->write_int32(LS_.ckint(lookup_)); } else { LS_.rawset(value_to_id_, val, next_id_++); serialize_table_r(val); } return; } case LUA_TT_REGISTRY: { error_ = "Pointer to registry found in serialization, shouldn't happen"; return; } case LUA_TT_GLOBALENV: { sb_->write_uint8(LUA_TT_GLOBALENV); return; } case LUA_TT_TANGIBLE: { sb_->write_uint8(LUA_TT_TANGIBLE); sb_->write_int64(LS_.tanid(val)); return; } case LUA_TT_TANGIBLEMETA: { error_ = "Pointer to a tangible metatable found in serialization, shouldn't happen"; return; } case LUA_TT_CLASS: { sb_->write_uint8(LUA_TT_CLASS); sb_->write_string(LS_.classname(val)); return; } default: { error_ = "Unrecognized xtype in serialization"; return; } } } public: Serializer(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb, eng::string &error) : LS_(LS0.state(), lookup_, value_to_id_), error_(error), sb_(sb), next_id_(1) { LS_.newtable(value_to_id_); sb_->write_uint16(0xD096); serialize_r(val); LS_.result(); } }; eng::string serialize_lua(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb) { eng::string error; Serializer sz(LS0, val, sb, error); return error; }; eng::string deserialize_lua(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb) { eng::string error; Deserializer dsz(LS0, val, sb, error); if (!error.empty()) { LS0.set(val, LuaNil); } return error; } LuaDefine(table_serialize, "value", "|Serialize any lua value, returning a string" "|" "|Converts lua values into a binary string. The string" "|can then be deserialized to produce a copy of the value." "|" "|Supports these atomic types:" "|" "| nil, string, number, boolean, token" "|" "|Does not support these types at all:" "|" "| function, thread" "|" "|Supports simple tables. When it finds a simple table, it recurses" "|into the table making a deep copy. Metatables are also supported." "|" "|Cycles are supported: if you have two tables which point to each other," "|they can be serialized, and the deserialized copy will contain two" "|tables that point to each other." "|" "|Duplicate tables and duplicate strings are handled efficiently." "|For example, if the data contains two copies of a 10KB string, the" "|serialized data will only contain one copy of the string." "|" "|Tangibles are tables, but they are handled specially. When the" "|serialize routine finds a tangible, it just stores the tangible's ID." "|The deserialized copy will then contain the same tangible by ID." "|" "|Classes are also tables, but they too are handled specially. When" "|the serialize routine finds a class, it stores the class name, and" "|the deserialized copy will then contain the same class." "|" "|The global environment table is also treated specially: if you" "|serialize it, it will not recurse into it, instead, the deserialized" "|copy will just contain a pointer to the global environment." "|") { LuaArg value; LuaRet str; LuaOldStack LS(L, value, str); StreamBuffer sb; eng::string error = serialize_lua(LS, value, &sb); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); return LS.result(); } LS.set(str, sb.view()); return LS.result(); } LuaDefine(table_deserialize, "binary", "|Deserialize a serialized block, returning a value" "|" "|See doc(table.serialize) for more information about what" "|kind of data can be serialized and how it is deserialized." "|") { LuaArg str; LuaRet value; LuaOldStack LS(L, value, str); std::string_view s = LS.ckstringview(str); StreamBuffer sb(s); eng::string error = deserialize_lua(LS, value, &sb); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); return LS.result(); } return LS.result(); }