| /******************************************************************************* |
| * Copyright (c) 2009-2011 Luaj.org. All rights reserved. |
| * |
| * 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: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * 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.vm3.lib; |
| |
| import org.luaj.vm3.Globals; |
| import org.luaj.vm3.Lua; |
| import org.luaj.vm3.LuaBoolean; |
| import org.luaj.vm3.LuaClosure; |
| import org.luaj.vm3.LuaError; |
| import org.luaj.vm3.LuaFunction; |
| import org.luaj.vm3.LuaNil; |
| import org.luaj.vm3.LuaNumber; |
| import org.luaj.vm3.LuaString; |
| import org.luaj.vm3.LuaTable; |
| import org.luaj.vm3.LuaThread; |
| import org.luaj.vm3.LuaUserdata; |
| import org.luaj.vm3.LuaValue; |
| import org.luaj.vm3.Print; |
| import org.luaj.vm3.Prototype; |
| import org.luaj.vm3.Varargs; |
| |
| /** |
| * 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()); |
| env.set("debug", debug); |
| env.get("package").get("loaded").set("debug", debug); |
| return debug; |
| } |
| |
| // 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; |
| } |
| } |
| |
| public void onCall(LuaFunction f) { |
| LuaThread t = globals.running; |
| if (t.inhook) return; |
| callstack().onCall(f); |
| if (t.hookcall && t.hookfunc != null) |
| callHook(CALL, NIL); |
| } |
| |
| public void onCall(LuaClosure c, Varargs varargs, LuaValue[] stack) { |
| LuaThread t = globals.running; |
| if (t.inhook) return; |
| callstack().onCall(c, varargs, stack); |
| if (t.hookcall && t.hookfunc != null) |
| callHook(CALL, NIL); |
| } |
| |
| public void onInstruction(int pc, Varargs v, int top) { |
| LuaThread t = globals.running; |
| if (t.inhook) return; |
| callstack().onInstruction(pc, v, top); |
| 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; |
| if (t.inhook) return; |
| callstack().onReturn(); |
| 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) |
| 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; |
| } |
| } |