blob: fffc509c6d0f52bedd51bba779ae14f1a89099dc [file] [log] [blame] [raw]
/*******************************************************************************
* 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, valueOf(ar.what));
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 ? TRUE : FALSE);
}
if (what.indexOf('n') >= 0) {
if (ar.name != null)
info.set(NAME, valueOf(ar.name));
info.set(NAMEWHAT, valueOf(ar.namewhat));
}
if (what.indexOf('t') >= 0) {
info.set(ISTAILCALL, FALSE);
}
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 = "[Java]";
}
}
}
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;
}
}