//////////////////////////////////////////////////////////////////// // // This file contains the code to compare the contents of tables. // The top level functions in this file are: // // World::diff_lua_tables // World::diff_tangible_databases // // This file also contains all the support code needed to implement // this stuff. // //////////////////////////////////////////////////////////////////// #include "luastack.hpp" #include "streambuffer.hpp" #include "world.hpp" // Given a table and an tnmap, return the table number of the table. // Returns zero if the table doesn't have a table number. // static int get_table_number(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap) { lua_State *L = MLS.state(); lua_pushvalue(L, mval.index()); lua_rawget(L, mtnmap.index()); int result = 0; if (lua_type(L, -1) == LUA_TNUMBER) { result = lua_tointeger(L, -1); } lua_pop(L, 1); return result; } static bool equivalent_values(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, LuaStack &SLS, LuaSlot sval, LuaSlot stnmap) { switch (MLS.xtype(mval)) { case LUA_TBOOLEAN: { if (SLS.type(sval) != LUA_TBOOLEAN) return false; return MLS.ckboolean(mval) == SLS.ckboolean(sval); } case LUA_TNUMBER: { if (SLS.type(sval) != LUA_TNUMBER) return false; return MLS.cknumber(mval) == SLS.cknumber(sval); } case LUA_TSTRING: { // This could be faster if I used lua_tolstring directly. if (SLS.type(sval) != LUA_TSTRING) return false; return MLS.ckstring(mval) == SLS.ckstring(sval); } case LUA_TFUNCTION: { // Cannot really compare. Just return true if the types match. return SLS.type(sval) == MLS.type(mval); } case LUA_TT_GENERAL: { if (SLS.xtype(sval) != LUA_TT_GENERAL) return false; int midx = get_table_number(MLS, mval, mtnmap); if (midx == 0) return false; int sidx = get_table_number(SLS, sval, stnmap); if (sidx == 0) return false; return midx == sidx; } case LUA_TT_CLASS: { if (SLS.xtype(sval) != LUA_TT_CLASS) return false; return MLS.classname(mval) == SLS.classname(sval); } case LUA_TT_TANGIBLE: { if (SLS.xtype(sval) != LUA_TT_TANGIBLE) return false; return World::tangible_id(MLS, mval) == World::tangible_id(SLS, sval); } case LUA_TT_GLOBALENV: { return (SLS.xtype(sval) == LUA_TT_GLOBALENV); } default: // We're forcing anything else to go to NIL, // and once it's NIL, we consider it 'equal'. return SLS.type(sval) == LUA_TNIL; } } static void transmit_value(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, StreamBuffer *sb) { switch (MLS.xtype(mval)) { case LUA_TBOOLEAN: { sb->write_uint8(LUA_TBOOLEAN); sb->write_bool(MLS.ckboolean(mval)); return; } case LUA_TNUMBER: { sb->write_uint8(LUA_TNUMBER); sb->write_double(MLS.cknumber(mval)); return; } case LUA_TSTRING: { sb->write_uint8(LUA_TSTRING); sb->write_string(MLS.ckstring(mval)); return; } case LUA_TT_GENERAL: { sb->write_uint8(LUA_TT_GENERAL); sb->write_uint32(get_table_number(MLS, mval, mtnmap)); return; } case LUA_TT_CLASS: { sb->write_uint8(LUA_TT_CLASS); sb->write_string(MLS.classname(mval)); return; } case LUA_TT_TANGIBLE: { sb->write_uint8(LUA_TT_TANGIBLE); sb->write_int64(World::tangible_id(MLS, mval)); return; } case LUA_TT_GLOBALENV: { sb->write_uint8(LUA_TT_GLOBALENV); return; } default: sb->write_uint8(LUA_TNIL); return; } } static void transmit_value_debug_string(StreamBuffer *sb, std::ostringstream &oss) { int kind = sb->read_uint8(); switch (kind) { case LUA_TBOOLEAN: { bool b = sb->read_bool(); oss << (b ? "true":"false"); return; } case LUA_TNUMBER: { oss << sb->read_double(); return; } case LUA_TSTRING: { oss << sb->read_string(); return; } case LUA_TT_GENERAL: { oss << "table " << sb->read_int32(); return; } case LUA_TT_CLASS: { oss << "class " << sb->read_string(); return; } case LUA_TT_TANGIBLE: { oss << "tan " << sb->read_int64(); return; } case LUA_TT_GLOBALENV: { oss << "globals"; return; } case LUA_TNIL: { oss << "nil"; return; } default: assert(false); // Should not get here. } } static bool compare_tables(lua_State *synch, lua_State *master, bool cmeta, StreamBuffer *sb) { LuaArg mtnmap, mtab, stnmap, stab; LuaVar skey, mkey, sval, mval, mnil; LuaStack SLS(synch, stnmap, stab, skey, sval); LuaStack MLS(master, mtnmap, mtab, mkey, mval, mnil); MLS.set(mnil, LuaNil); int nupdates = 0; sb->write_int32(0); int wc = sb->total_writes(); MLS.set(mkey, LuaNil); while (MLS.next(mtab, mkey, mval)) { if (!MLS.issortablekey(mkey)) continue; MLS.movesortablekey(mkey, SLS, skey); SLS.rawget(sval, stab, skey); if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { transmit_value(MLS, mkey, mtnmap, sb); transmit_value(MLS, mval, mtnmap, sb); nupdates += 1; } } SLS.set(skey, LuaNil); while (SLS.next(stab, skey, sval)) { if (!SLS.issortablekey(skey)) continue; SLS.movesortablekey(skey, MLS, mkey); MLS.rawget(mval, mtab, mkey); if (MLS.isnil(mval)) { transmit_value(MLS, mkey, mtnmap, sb); transmit_value(MLS, mval, mtnmap, sb); nupdates += 1; } } if (cmeta) { SLS.getmetatable(sval, stab); MLS.getmetatable(mval, mtab); if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { transmit_value(MLS, mnil, mtnmap, sb); transmit_value(MLS, mval, mtnmap, sb); nupdates += 1; } } sb->overwrite_int32(wc, nupdates); SLS.result(); MLS.result(); return (nupdates > 0); } static std::string compare_tables_debug_string(StreamBuffer *sb) { std::vector sorted; std::ostringstream oss; int ndiffs = sb->read_int32(); for (int i = 0; i < ndiffs; i++) { transmit_value_debug_string(sb, oss); oss << "="; transmit_value_debug_string(sb, oss); sorted.push_back(oss.str()); oss.str(""); } std::sort(sorted.begin(), sorted.end()); for (const std::string &s : sorted) { oss << s << ";"; } return oss.str(); } void World::diff_lua_tables(lua_State *master, StreamBuffer *sb) { lua_State *synch = state(); LuaVar sntmap, mntmap, stnmap, mtnmap; LuaStack SLS(synch, sntmap, stnmap); LuaStack MLS(master, mntmap, mtnmap); SLS.rawget(sntmap, LuaRegistry, "ntmap"); MLS.rawget(mntmap, LuaRegistry, "ntmap"); SLS.rawget(stnmap, LuaRegistry, "tnmap"); MLS.rawget(mtnmap, LuaRegistry, "tnmap"); int m_ntables = MLS.rawlen(mntmap); int s_ntables = SLS.rawlen(sntmap); assert(m_ntables == s_ntables); sb->write_int32(0); int write_count_after = sb->total_writes(); int nmodified = 0; int s_top = lua_gettop(synch); int m_top = lua_gettop(master); for (int id = 1; id <= m_ntables; id++) { lua_pushvalue(master, mtnmap.index()); lua_rawgeti(master, mtnmap.index(), id); if (lua_type(master, -1) == LUA_TTABLE) { lua_pushvalue(synch, stnmap.index()); lua_rawgeti(synch, sntmap.index(), id); int tw = sb->total_writes(); sb->write_int32(id); nmodified += 1; if (!compare_tables(synch, master, true, sb)) { sb->unwrite_to(tw); nmodified -= 1; } assert(lua_gettop(synch) == s_top); assert(lua_gettop(master) == m_top); } else { lua_pop(master, 2); } } sb->overwrite_int32(write_count_after, nmodified); } void World::diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb) { lua_State *synch = state(); LuaVar stnmap, mtnmap, stangibles, mtangibles; LuaStack SLS(synch, stnmap, stangibles); LuaStack MLS(master, mtnmap, mtangibles); SLS.rawget(stnmap, LuaRegistry, "tnmap"); MLS.rawget(mtnmap, LuaRegistry, "tnmap"); SLS.rawget(stangibles, LuaRegistry, "tangibles"); MLS.rawget(mtangibles, LuaRegistry, "tangibles"); sb->write_int32(0); int write_count_after = sb->total_writes(); int nmodified = 0; int s_top = lua_gettop(synch); int m_top = lua_gettop(master); for (int64_t id : basis) { lua_pushvalue(master, mtnmap.index()); lua_rawgeti(master, mtangibles.index(), id); lua_pushvalue(synch, stnmap.index()); lua_rawgeti(synch, stangibles.index(), id); int tw = sb->total_writes(); sb->write_int64(id); nmodified += 1; if (!compare_tables(synch, master, false, sb)) { sb->unwrite_to(tw); nmodified -= 1; } assert(lua_gettop(synch) == s_top); assert(lua_gettop(master) == m_top); } sb->overwrite_int32(write_count_after, nmodified); } LuaDefine(table_diffcompare, "c") { LuaArg mtnmap, mtab, stnmap, stab; LuaRet dbgstring; LuaVar tthread; LuaStack LS(L, mtnmap, mtab, stnmap, stab, dbgstring, tthread); // Create a temporary thread to be the 'synch model'. We'll use the // existing thread as the 'master model'. lua_State *synch = lua_newthread(L); lua_replace(L, tthread.index()); // Call tablecmp_diff. StreamBuffer sb; lua_pushvalue(L, stnmap.index()); lua_pushvalue(L, stab.index()); lua_xmove(L, synch, 2); lua_pushvalue(L, mtnmap.index()); lua_pushvalue(L, mtab.index()); compare_tables(synch, L, true, &sb); // Convert the output to a debug string. LS.set(dbgstring, compare_tables_debug_string(&sb)); return LS.result(); }