/******************************************************************************* * Copyright (c) 2009-2011 Luaj.org. All rights reserved. * <p/> * 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: * <p/> * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * <p/> * 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.vm2.lib; //TODO 与3.0不一样 import org.luaj.vm2.Globals; import org.luaj.vm2.Lua; import org.luaj.vm2.LuaBoolean; import org.luaj.vm2.LuaClosure; import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaFunction; import org.luaj.vm2.LuaNil; import org.luaj.vm2.LuaNumber; import org.luaj.vm2.LuaString; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaThread; import org.luaj.vm2.LuaUserdata; import org.luaj.vm2.LuaValue; import org.luaj.vm2.Prototype; import org.luaj.vm2.Varargs; import org.luaj.vm2.log.LuaPrint; /** * Subclass of {@link LibFunction} which implements the lua standard {@code debug} * library. * <p/> * The debug library in luaj tries to emulate the behavior of the corresponding C-based lua library. * To do this, it must maintain a separate stack of calls to {@link LuaClosure} and {@link LibFunction} * instances. * Especially when lua-to-java bytecode compiling is being used * via a {@link LuaCompiler} such as {@link LuaJC}, * this cannot be done in all cases. * <p/> * Typically, this library is included as part of a call to either * {@link JsePlatform#debugGlobals()} or {@link JmePlatform#debugGlobals()} * <pre> {@code * Globals globals = JsePlatform.debugGlobals(); * System.out.println( globals.get("debug").get("traceback").call() ); * } </pre> * <p/> * To instantiate and use it directly, * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: * <pre> {@code * Globals globals = new Globals(); * globals.load(new JseBaseLib()); * globals.load(new PackageLib()); * globals.load(new DebugLib()); * System.out.println( globals.get("debug").get("traceback").call() ); * } </pre> * <p/> * * @see LibFunction * @see JsePlatform * @see JmePlatform * @see <a href="http://www.lua.org/manual/5.2/manual.html#6.10">Lua 5.2 Debug Lib Reference</a> */ public class DebugLib extends TwoArgFunction { public static boolean CALLS; public static boolean TRACE; static { try { CALLS = (null != System.getProperty("CALLS")); } catch (Exception e) { } try { TRACE = (null != System.getProperty("TRACE")); } catch (Exception e) { } } private static final LuaString LUA = valueOf("Lua"); private static final LuaString QMARK = valueOf("?"); private static final LuaString CALL = valueOf("call"); private static final LuaString LINE = valueOf("line"); private static final LuaString COUNT = valueOf("count"); private static final LuaString RETURN = valueOf("return"); private static final LuaString FUNC = valueOf("func"); private static final LuaString ISTAILCALL = valueOf("istailcall"); private static final LuaString ISVARARG = valueOf("isvararg"); private static final LuaString NUPS = valueOf("nups"); private static final LuaString NPARAMS = valueOf("nparams"); private static final LuaString NAME = valueOf("name"); private static final LuaString NAMEWHAT = valueOf("namewhat"); private static final LuaString WHAT = valueOf("what"); private static final LuaString SOURCE = valueOf("source"); private static final LuaString SHORT_SRC = valueOf("short_src"); private static final LuaString LINEDEFINED = valueOf("linedefined"); private static final LuaString LASTLINEDEFINED = valueOf("lastlinedefined"); private static final LuaString CURRENTLINE = valueOf("currentline"); private static final LuaString ACTIVELINES = valueOf("activelines"); Globals globals; public LuaValue call(LuaValue modname, LuaValue env) { globals = env.checkglobals(); globals.debuglib = this; LuaTable debug = new LuaTable(); debug.set("debug", new debug()); debug.set("gethook", new gethook()); debug.set("getinfo", new getinfo()); debug.set("getlocal", new getlocal()); debug.set("getmetatable", new getmetatable()); debug.set("getregistry", new getregistry()); debug.set("getupvalue", new getupvalue()); debug.set("getuservalue", new getuservalue()); debug.set("sethook", new sethook()); debug.set("setlocal", new setlocal()); debug.set("setmetatable", new setmetatable()); debug.set("setupvalue", new setupvalue()); debug.set("setuservalue", new setuservalue()); debug.set("traceback", new traceback()); debug.set("upvalueid", new upvalueid()); debug.set("upvaluejoin", new upvaluejoin()); debug.set("traceback_count", new tracebackCount()); //extend for luaview new com.taobao.luaview.vm.extend.DebugLib(this, globals).extend(debug); env.set("debug", debug); env.get("package").get("loaded").set("debug", debug); return debug; } //---------------------------------------------------------------------------------------------- final class readCmd extends ZeroArgFunction { @Override public LuaValue call() { if (globals.debugConnection != null) { String cmd = globals.debugConnection.reciveCMD(); return cmd != null ? valueOf(cmd) : NIL; } else { return NIL; } } } final class sleep extends OneArgFunction { @Override public LuaValue call(LuaValue arg) { try { Thread.sleep(arg.checklong()); } catch (InterruptedException e) { e.printStackTrace(); } return NIL; } } final class printToServer extends OneArgFunction { @Override public LuaValue call(LuaValue arg) { if (globals.debugConnection != null) { globals.debugConnection.sendingEnabled = arg.optboolean(false); } return NIL; } } final class runningLine extends TwoArgFunction { @Override public LuaValue call(LuaValue arg1, LuaValue arg2) { String fileName = arg1.checkjstring(1); String lineNumber = String.valueOf(arg2.checkint()); if (globals.debugConnection != null) { globals.debugConnection.sendCmd("running", fileName, lineNumber.getBytes()); } return NIL; } } final class get_file_line extends ZeroArgFunction { @Override public LuaValue call() { return NIL; } } //---------------------------------------------------------------------------------------------- // debug.debug() static final class debug extends ZeroArgFunction { public LuaValue call() { return NONE; } } // debug.gethook ([thread]) final class gethook extends VarArgFunction { public Varargs invoke(Varargs args) { LuaThread t = args.narg() > 0 ? args.checkthread(1) : globals.running; return varargsOf( t.hookfunc != null ? t.hookfunc : NIL, valueOf((t.hookcall ? "c" : "") + (t.hookline ? "l" : "") + (t.hookrtrn ? "r" : "")), valueOf(t.hookcount)); } } // debug.getinfo ([thread,] f [, what]) final class getinfo extends VarArgFunction { public Varargs invoke(Varargs args) { int a = 1; LuaThread thread = args.isthread(a) ? args.checkthread(a++) : globals.running; LuaValue func = args.arg(a++); String what = args.optjstring(a++, "flnStu"); DebugLib.CallStack callstack = callstack(thread); // find the stack info DebugLib.CallFrame frame; if (func.isnumber()) { frame = callstack.getCallFrame(func.toint()); if (frame == null) return NONE; func = frame.f; } else if (func.isfunction()) { frame = callstack.findCallFrame(func); } else { return argerror(a - 2, "function or level"); } // start a table DebugInfo ar = callstack.auxgetinfo(what, (LuaFunction) func, frame); LuaTable info = new LuaTable(); if (what.indexOf('S') >= 0) { info.set(WHAT, LUA); info.set(SOURCE, valueOf(ar.source)); info.set(SHORT_SRC, valueOf(ar.short_src)); info.set(LINEDEFINED, valueOf(ar.linedefined)); info.set(LASTLINEDEFINED, valueOf(ar.lastlinedefined)); } if (what.indexOf('l') >= 0) { info.set(CURRENTLINE, valueOf(ar.currentline)); } if (what.indexOf('u') >= 0) { info.set(NUPS, valueOf(ar.nups)); info.set(NPARAMS, valueOf(ar.nparams)); info.set(ISVARARG, ar.isvararg ? ONE : ZERO); } if (what.indexOf('n') >= 0) { info.set(NAME, LuaValue.valueOf(ar.name != null ? ar.name : "?")); info.set(NAMEWHAT, LuaValue.valueOf(ar.namewhat)); } if (what.indexOf('t') >= 0) { info.set(ISTAILCALL, ZERO); } if (what.indexOf('L') >= 0) { LuaTable lines = new LuaTable(); info.set(ACTIVELINES, lines); DebugLib.CallFrame cf; for (int l = 1; (cf = callstack.getCallFrame(l)) != null; ++l) if (cf.f == func) lines.insert(-1, valueOf(cf.currentline())); } if (what.indexOf('f') >= 0) { if (func != null) info.set(FUNC, func); } return info; } } // debug.getlocal ([thread,] f, local) final class getlocal extends VarArgFunction { public Varargs invoke(Varargs args) { int a = 1; LuaThread thread = args.isthread(a) ? args.checkthread(a++) : globals.running; int level = args.checkint(a++); int local = args.checkint(a++); CallFrame f = callstack(thread).getCallFrame(level); return f != null ? f.getLocal(local) : NONE; } } // debug.getmetatable (value) final class getmetatable extends LibFunction { public LuaValue call(LuaValue v) { LuaValue mt = v.getmetatable(); return mt != null ? mt : NIL; } } // debug.getregistry () final class getregistry extends ZeroArgFunction { public LuaValue call() { return globals; } } // debug.getupvalue (f, up) static final class getupvalue extends VarArgFunction { public Varargs invoke(Varargs args) { LuaValue func = args.checkfunction(1); int up = args.checkint(2); if (func instanceof LuaClosure) { LuaClosure c = (LuaClosure) func; LuaString name = findupvalue(c, up); if (name != null) { return varargsOf(name, c.upValues[up - 1].getValue()); } } return NIL; } } // debug.getuservalue (u) static final class getuservalue extends LibFunction { public LuaValue call(LuaValue u) { return u.isuserdata() ? u : NIL; } } // debug.sethook ([thread,] hook, mask [, count]) final class sethook extends VarArgFunction { public Varargs invoke(Varargs args) { int a = 1; LuaThread t = args.isthread(a) ? args.checkthread(a++) : globals.running; LuaValue func = args.optfunction(a++, null); String str = args.optjstring(a++, ""); int count = args.optint(a++, 0); boolean call = false, line = false, rtrn = false; for (int i = 0; i < str.length(); i++) switch (str.charAt(i)) { case 'c': call = true; break; case 'l': line = true; break; case 'r': rtrn = true; break; } t.hookfunc = func; t.hookcall = call; t.hookline = line; t.hookcount = count; t.hookrtrn = rtrn; return NONE; } } // debug.setlocal ([thread,] level, local, value) final class setlocal extends VarArgFunction { public Varargs invoke(Varargs args) { int a = 1; LuaThread thread = args.isthread(a) ? args.checkthread(a++) : globals.running; int level = args.checkint(a++); int local = args.checkint(a++); LuaValue value = args.arg(a++); CallFrame f = callstack(thread).getCallFrame(level); return f != null ? f.setLocal(local, value) : NONE; } } // debug.setmetatable (value, table) final class setmetatable extends TwoArgFunction { public LuaValue call(LuaValue value, LuaValue table) { LuaValue mt = table.opttable(null); switch (value.type()) { case TNIL: LuaNil.s_metatable = mt; break; case TNUMBER: LuaNumber.s_metatable = mt; break; case TBOOLEAN: LuaBoolean.s_metatable = mt; break; case TSTRING: LuaString.s_metatable = mt; break; case TFUNCTION: LuaFunction.s_metatable = mt; break; case TTHREAD: LuaThread.s_metatable = mt; break; default: value.setmetatable(mt); } return value; } } // debug.setupvalue (f, up, value) final class setupvalue extends VarArgFunction { public Varargs invoke(Varargs args) { LuaValue func = args.checkfunction(1); int up = args.checkint(2); LuaValue value = args.arg(3); if (func instanceof LuaClosure) { LuaClosure c = (LuaClosure) func; LuaString name = findupvalue(c, up); if (name != null) { c.upValues[up - 1].setValue(value); return name; } } return NIL; } } // debug.setuservalue (udata, value) final class setuservalue extends VarArgFunction { public Varargs invoke(Varargs args) { Object o = args.checkuserdata(1); LuaValue v = args.checkvalue(2); LuaUserdata u = (LuaUserdata) args.arg1(); u.m_instance = v.checkuserdata(); u.m_metatable = v.getmetatable(); return NONE; } } // debug.traceback ([thread,] [message [, level]]) final class traceback extends VarArgFunction { public Varargs invoke(Varargs args) { int a = 1; LuaThread thread = args.isthread(a) ? args.checkthread(a++) : globals.running; String message = args.optjstring(a++, null); int level = args.optint(a++, 1); String tb = callstack(thread).traceback(level); return valueOf(message != null ? message + "\n" + tb : tb); } } // debug.upvalueid (f, n) final class upvalueid extends VarArgFunction { public Varargs invoke(Varargs args) { LuaValue func = args.checkfunction(1); int up = args.checkint(2); if (func instanceof LuaClosure) { LuaClosure c = (LuaClosure) func; if (c.upValues != null && up > 0 && up <= c.upValues.length) { return valueOf(c.upValues[up - 1].hashCode()); } } return NIL; } } // debug.upvaluejoin (f1, n1, f2, n2) final class upvaluejoin extends VarArgFunction { public Varargs invoke(Varargs args) { LuaClosure f1 = args.checkclosure(1); int n1 = args.checkint(2); LuaClosure f2 = args.checkclosure(3); int n2 = args.checkint(4); if (n1 < 1 || n1 > f1.upValues.length) argerror("index out of range"); if (n2 < 1 || n2 > f2.upValues.length) argerror("index out of range"); f1.upValues[n1 - 1] = f2.upValues[n2 - 1]; return NONE; } } final class tracebackCount extends ZeroArgFunction { public LuaValue call() { int calls = globals.debuglib.callstack().calls; return valueOf(calls > 0 ? (calls - 1) : 0); } } public void onCall(LuaFunction f) { LuaThread t = globals.running; callstack().onCall(f); if (t.inhook) return; if (t.hookcall && t.hookfunc != null) callHook(CALL, NIL); } public void onCall(LuaClosure c, Varargs varargs, LuaValue[] stack) { LuaThread t = globals.running; callstack().onCall(c, varargs, stack); if (t.inhook) return; if (t.hookcall && t.hookfunc != null) callHook(CALL, NIL); } public void onInstruction(int pc, Varargs v, int top) { LuaThread t = globals.running; callstack().onInstruction(pc, v, top); if (t.inhook) return; if (t.hookfunc == null) return; if (t.hookcount > 0) if (++t.bytecodes % t.hookcount == 0) callHook(COUNT, NIL); if (t.hookline) { int newline = callstack().currentline(); if (newline != t.lastline) { t.lastline = newline; callHook(LINE, LuaValue.valueOf(newline)); } } } public void onReturn() { LuaThread t = globals.running; callstack().onReturn(); if (t.inhook) return; if (t.hookcall && t.hookfunc != null) callHook(RETURN, NIL); } public String traceback(int level) { return callstack().traceback(level); } void callHook(LuaValue type, LuaValue arg) { LuaThread t = globals.running; t.inhook = true; try { t.hookfunc.call(type, arg); } catch (LuaError e) { throw e; } catch (RuntimeException e) { throw new LuaError(e); } finally { t.inhook = false; } } CallStack callstack() { return callstack(globals.running); } CallStack callstack(LuaThread t) { if (t.callstack == null) t.callstack = new CallStack(); return (CallStack) t.callstack; } static class DebugInfo { String name; /* (n) */ String namewhat; /* (n) 'global', 'local', 'field', 'method' */ String what; /* (S) 'Lua', 'C', 'main', 'tail' */ String source; /* (S) */ int currentline; /* (l) */ int linedefined; /* (S) */ int lastlinedefined; /* (S) */ short nups; /* (u) number of upvalues */ short nparams;/* (u) number of parameters */ boolean isvararg; /* (u) */ boolean istailcall; /* (t) */ String short_src; /* (S) */ CallFrame cf; /* active function */ public void funcinfo(LuaFunction f) { if (f.isclosure()) { Prototype p = f.checkclosure().p; this.source = p.source != null ? p.source.tojstring() : "=?"; this.linedefined = p.linedefined; this.lastlinedefined = p.lastlinedefined; this.what = (this.linedefined == 0) ? "main" : "Lua"; this.short_src = p.shortsource(); } else { this.source = "=[Java]"; this.linedefined = -1; this.lastlinedefined = -1; this.what = "Java"; this.short_src = f.name(); } } } public static class CallStack { final static CallFrame[] EMPTY = {}; CallFrame[] frame = EMPTY; int calls = 0; CallStack() { } int currentline() { return calls > 0 ? frame[calls - 1].currentline() : -1; } private CallFrame pushcall() { if (calls >= frame.length) { int n = Math.max(4, frame.length * 3 / 2); CallFrame[] f = new CallFrame[n]; System.arraycopy(frame, 0, f, 0, frame.length); for (int i = frame.length; i < n; ++i) f[i] = new CallFrame(); frame = f; for (int i = 1; i < n; ++i) f[i].previous = f[i - 1]; } return frame[calls++]; } final void onCall(LuaFunction function) { pushcall().set(function); } final void onCall(LuaClosure function, Varargs varargs, LuaValue[] stack) { pushcall().set(function, varargs, stack); } final void onReturn() { if (calls > 0) frame[--calls].reset(); } final void onInstruction(int pc, Varargs v, int top) { frame[calls - 1].instr(pc, v, top); } /** * Get the traceback starting at a specific level. * * @param level * @return String containing the traceback. */ String traceback(int level) { StringBuffer sb = new StringBuffer(); sb.append("stack traceback:"); for (DebugLib.CallFrame c; (c = getCallFrame(level++)) != null; ) { sb.append("\n\t"); sb.append(c.shortsource()); sb.append(':'); if (c.currentline() > 0) sb.append(c.currentline() + ":"); sb.append(" in "); DebugInfo ar = auxgetinfo("n", c.f, c); if (c.linedefined() == 0) sb.append("main chunk"); else if (ar.name != null) { sb.append("function '"); sb.append(ar.name); sb.append('\''); } else { sb.append("function <" + c.shortsource() + ":" + c.linedefined() + ">"); } } sb.append("\n\t[Java]: in ?"); return sb.toString(); } DebugLib.CallFrame getCallFrame(int level) { if (level < 1 || level > calls) return null; return frame[calls - level]; } DebugLib.CallFrame findCallFrame(LuaValue func) { for (int i = 1; i <= calls; ++i) if (frame[calls - i].f == func) return frame[i]; return null; } DebugInfo auxgetinfo(String what, LuaFunction f, CallFrame ci) { DebugInfo ar = new DebugInfo(); for (int i = 0, n = what.length(); i < n; ++i) { switch (what.charAt(i)) { case 'S': ar.funcinfo(f); break; case 'l': ar.currentline = ci != null && ci.f.isclosure() ? ci.currentline() : -1; break; case 'u': if (f != null && f.isclosure()) { Prototype p = f.checkclosure().p; ar.nups = (short) p.upvalues.length; ar.nparams = (short) p.numparams; ar.isvararg = p.is_vararg != 0; } else { ar.nups = 0; ar.isvararg = true; ar.nparams = 0; } break; case 't': ar.istailcall = false; break; case 'n': { /* calling function is a known Lua function? */ if (ci != null && ci.previous != null) { if (ci.previous.f.isclosure()) { NameWhat nw = getfuncname(ci.previous); if (nw != null) { ar.name = nw.name; ar.namewhat = nw.namewhat; } } } if (ar.namewhat == null) { ar.namewhat = ""; /* not found */ ar.name = null; } break; } case 'L': case 'f': break; default: // TODO: return bad status. break; } } return ar; } } static class CallFrame { LuaFunction f; int pc; int top; Varargs v; LuaValue[] stack; CallFrame previous; void set(LuaClosure function, Varargs varargs, LuaValue[] stack) { this.f = function; this.v = varargs; this.stack = stack; } public String shortsource() { return f.isclosure() ? f.checkclosure().p.shortsource() : "[Java]"; } void set(LuaFunction function) { this.f = function; } void reset() { this.f = null; this.v = null; this.stack = null; } void instr(int pc, Varargs v, int top) { this.pc = pc; this.v = v; this.top = top; if (TRACE) { new LuaPrint().buildState(f.checkclosure(), pc, stack, top, v).print(); //Print.printState(f.checkclosure(), pc, stack, top, v); } } Varargs getLocal(int i) { LuaString name = getlocalname(i); if (name != null) return varargsOf(name, stack[i - 1]); else return NIL; } Varargs setLocal(int i, LuaValue value) { LuaString name = getlocalname(i); if (name != null) { stack[i - 1] = value; return name; } else { return NIL; } } int currentline() { if (!f.isclosure()) return -1; int[] li = f.checkclosure().p.lineinfo; return li == null || pc < 0 || pc >= li.length ? -1 : li[pc]; } String sourceline() { if (!f.isclosure()) return f.tojstring(); return f.checkclosure().p.shortsource() + ":" + currentline(); } private int linedefined() { return f.isclosure() ? f.checkclosure().p.linedefined : -1; } LuaString getlocalname(int index) { if (!f.isclosure()) return null; return f.checkclosure().p.getlocalname(index, pc); } } static LuaString findupvalue(LuaClosure c, int up) { if (c.upValues != null && up > 0 && up <= c.upValues.length) { if (c.p.upvalues != null && up <= c.p.upvalues.length) return c.p.upvalues[up - 1].name; else return LuaString.valueOf("." + up); } return null; } static void lua_assert(boolean x) { if (!x) throw new RuntimeException("lua_assert failed"); } static class NameWhat { final String name; final String namewhat; NameWhat(String name, String namewhat) { this.name = name; this.namewhat = namewhat; } } // Return the name info if found, or null if no useful information could be found. static NameWhat getfuncname(DebugLib.CallFrame frame) { if (!frame.f.isclosure()) return new NameWhat(frame.f.classnamestub(), "Java"); Prototype p = frame.f.checkclosure().p; int pc = frame.pc; int i = p.code[pc]; /* calling instruction */ LuaString tm; switch (Lua.GET_OPCODE(i)) { case Lua.OP_CALL: case Lua.OP_TAILCALL: /* get function name */ return getobjname(p, pc, Lua.GETARG_A(i)); case Lua.OP_TFORCALL: /* for iterator */ return new NameWhat("(for iterator)", "(for iterator"); /* all other instructions can call only through metamethods */ case Lua.OP_SELF: case Lua.OP_GETTABUP: case Lua.OP_GETTABLE: tm = LuaValue.INDEX; break; case Lua.OP_SETTABUP: case Lua.OP_SETTABLE: tm = LuaValue.NEWINDEX; break; case Lua.OP_EQ: tm = LuaValue.EQ; break; case Lua.OP_ADD: tm = LuaValue.ADD; break; case Lua.OP_SUB: tm = LuaValue.SUB; break; case Lua.OP_MUL: tm = LuaValue.MUL; break; case Lua.OP_DIV: tm = LuaValue.DIV; break; case Lua.OP_MOD: tm = LuaValue.MOD; break; case Lua.OP_POW: tm = LuaValue.POW; break; case Lua.OP_UNM: tm = LuaValue.UNM; break; case Lua.OP_LEN: tm = LuaValue.LEN; break; case Lua.OP_LT: tm = LuaValue.LT; break; case Lua.OP_LE: tm = LuaValue.LE; break; case Lua.OP_CONCAT: tm = LuaValue.CONCAT; break; default: return null; /* else no useful name can be found */ } return new NameWhat(tm.tojstring(), "metamethod"); } // return NameWhat if found, null if not public static NameWhat getobjname(Prototype p, int lastpc, int reg) { int pc = lastpc; // currentpc(L, ci); LuaString name = p.getlocalname(reg + 1, pc); if (name != null) /* is a local? */ return new NameWhat(name.tojstring(), "local"); /* else try symbolic execution */ pc = findsetreg(p, lastpc, reg); if (pc != -1) { /* could find instruction? */ int i = p.code[pc]; switch (Lua.GET_OPCODE(i)) { case Lua.OP_MOVE: { int a = Lua.GETARG_A(i); int b = Lua.GETARG_B(i); /* move from `b' to `a' */ if (b < a) return getobjname(p, pc, b); /* get name for `b' */ break; } case Lua.OP_GETTABUP: case Lua.OP_GETTABLE: { int k = Lua.GETARG_C(i); /* key index */ int t = Lua.GETARG_B(i); /* table index */ LuaString vn = (Lua.GET_OPCODE(i) == Lua.OP_GETTABLE) /* name of indexed variable */ ? p.getlocalname(t + 1, pc) : (t < p.upvalues.length ? p.upvalues[t].name : QMARK); name = kname(p, k); return new NameWhat(name.tojstring(), vn != null && vn.eq_b(ENV) ? "global" : "field"); } case Lua.OP_GETUPVAL: { int u = Lua.GETARG_B(i); /* upvalue index */ name = u < p.upvalues.length ? p.upvalues[u].name : QMARK; return new NameWhat(name.tojstring(), "upvalue"); } case Lua.OP_LOADK: case Lua.OP_LOADKX: { int b = (Lua.GET_OPCODE(i) == Lua.OP_LOADK) ? Lua.GETARG_Bx(i) : Lua.GETARG_Ax(p.code[pc + 1]); if (p.k[b].isstring()) { name = p.k[b].strvalue(); return new NameWhat(name.tojstring(), "constant"); } break; } case Lua.OP_SELF: { int k = Lua.GETARG_C(i); /* key index */ name = kname(p, k); return new NameWhat(name.tojstring(), "method"); } default: break; } } return null; /* no useful name found */ } static LuaString kname(Prototype p, int c) { if (Lua.ISK(c) && p.k[Lua.INDEXK(c)].isstring()) return p.k[Lua.INDEXK(c)].strvalue(); else return QMARK; } /* ** try to find last instruction before 'lastpc' that modified register 'reg' */ static int findsetreg(Prototype p, int lastpc, int reg) { int pc; int setreg = -1; /* keep last instruction that changed 'reg' */ for (pc = 0; pc < lastpc; pc++) { int i = p.code[pc]; int op = Lua.GET_OPCODE(i); int a = Lua.GETARG_A(i); switch (op) { case Lua.OP_LOADNIL: { int b = Lua.GETARG_B(i); if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */ setreg = pc; break; } case Lua.OP_TFORCALL: { if (reg >= a + 2) setreg = pc; /* affect all regs above its base */ break; } case Lua.OP_CALL: case Lua.OP_TAILCALL: { if (reg >= a) setreg = pc; /* affect all registers above base */ break; } case Lua.OP_JMP: { int b = Lua.GETARG_sBx(i); int dest = pc + 1 + b; /* jump is forward and do not skip `lastpc'? */ if (pc < dest && dest <= lastpc) pc += b; /* do the jump */ break; } case Lua.OP_TEST: { if (reg == a) setreg = pc; /* jumped code can change 'a' */ break; } default: if (Lua.testAMode(op) && reg == a) /* any instruction that set A */ setreg = pc; break; } } return setreg; } }