/*******************************************************************************
* Copyright (c) 2009 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.log;
import com.taobao.luaview.util.LogUtil;
import org.luaj.vm2.Lua;
import org.luaj.vm2.LuaClosure;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Prototype;
import org.luaj.vm2.Upvaldesc;
import org.luaj.vm2.Varargs;
/**
* Debug helper class to pretty-print lua bytecodes.
*
* @see Prototype
* @see LuaClosure
*/
public class LuaPrint extends Lua {
/**
* opcode names
*/
private static final String STRING_FOR_NULL = "null";
public StringBuffer ps = new StringBuffer();
public LuaPrint() {
}
/**
* String names for each lua opcode value.
*/
public static final String[] OPNAMES = {
"MOVE",
"LOADK",
"LOADKX",
"LOADBOOL",
"LOADNIL",
"GETUPVAL",
"GETTABUP",
"GETTABLE",
"SETTABUP",
"SETUPVAL",
"SETTABLE",
"NEWTABLE",
"SELF",
"ADD",
"SUB",
"MUL",
"DIV",
"MOD",
"POW",
"UNM",
"NOT",
"LEN",
"CONCAT",
"JMP",
"EQ",
"LT",
"LE",
"TEST",
"TESTSET",
"CALL",
"TAILCALL",
"RETURN",
"FORLOOP",
"FORPREP",
"TFORCALL",
"TFORLOOP",
"SETLIST",
"CLOSURE",
"VARARG",
"EXTRAARG",
null,
};
void buildString(StringBuffer ps, final LuaString s) {
ps.append('"');
for (int i = 0, n = s.m_length; i < n; i++) {
int c = s.m_bytes[s.m_offset + i];
if (c >= ' ' && c <= '~' && c != '\"' && c != '\\')
ps.append((char) c);
else {
switch (c) {
case '"':
ps.append("\\\"");
break;
case '\\':
ps.append("\\\\");
break;
case 0x0007: /* bell */
ps.append("\\a");
break;
case '\b': /* backspace */
ps.append("\\b");
break;
case '\f': /* form feed */
ps.append("\\f");
break;
case '\t': /* tab */
ps.append("\\t");
break;
case '\r': /* carriage return */
ps.append("\\r");
break;
case '\n': /* newline */
ps.append("\\n");
break;
case 0x000B: /* vertical tab */
ps.append("\\v");
break;
default:
ps.append('\\');
ps.append(Integer.toString(1000 + 0xff & c).substring(1));
break;
}
}
}
ps.append('"');
}
void buildValue(StringBuffer ps, LuaValue v) {
switch (v.type()) {
case LuaValue.TSTRING:
buildString(ps, (LuaString) v);
break;
default:
ps.append(v.tojstring());
}
}
void buildConstant(StringBuffer ps, Prototype f, int i) {
buildValue(ps, f.k[i]);
}
void buildUpvalue(StringBuffer ps, Upvaldesc u) {
ps.append(u.idx + " ");
buildValue(ps, u.name);
}
/**
* Print the code in a prototype
*
* @param f the {@link Prototype}
*/
void buildCode(Prototype f) {
int[] code = f.code;
int pc, n = code.length;
for (pc = 0; pc < n; pc++) {
buildOpCode(f, pc);
ps.append("\n");
}
}
/**
* Print an opcode in a prototype
*
* @param f the {@link Prototype}
* @param pc the program counter to look up and print
*/
public LuaPrint buildOpCode(Prototype f, int pc) {
buildOpCode(ps, f, pc);
return this;
}
/**
* Print an opcode in a prototype
*
* @param f the {@link Prototype}
* @param pc the program counter to look up and print
*/
public LuaPrint buildOpCode(StringBuffer ps, Prototype f, int pc) {
int[] code = f.code;
int i = code[pc];
int o = GET_OPCODE(i);
int a = GETARG_A(i);
int b = GETARG_B(i);
int c = GETARG_C(i);
int bx = GETARG_Bx(i);
int sbx = GETARG_sBx(i);
int line = getline(f, pc);
ps.append(" " + (pc + 1) + " ");
if (line > 0)
ps.append("[" + line + "] ");
else
ps.append("[-] ");
ps.append(OPNAMES[o] + " ");
switch (getOpMode(o)) {
case iABC:
ps.append(a);
if (getBMode(o) != OpArgN)
ps.append(" " + (ISK(b) ? (-1 - INDEXK(b)) : b));
if (getCMode(o) != OpArgN)
ps.append(" " + (ISK(c) ? (-1 - INDEXK(c)) : c));
break;
case iABx:
if (getBMode(o) == OpArgK) {
ps.append(a + " " + (-1 - bx));
} else {
ps.append(a + " " + (bx));
}
break;
case iAsBx:
if (o == OP_JMP)
ps.append(sbx);
else
ps.append(a + " " + sbx);
break;
}
switch (o) {
case OP_LOADK:
ps.append(" ; ");
buildConstant(ps, f, bx);
break;
case OP_GETUPVAL:
case OP_SETUPVAL:
ps.append(" ; ");
buildUpvalue(ps, f.upvalues[b]);
break;
case OP_GETTABUP:
ps.append(" ; ");
buildUpvalue(ps, f.upvalues[b]);
ps.append(" ");
if (ISK(c))
buildConstant(ps, f, INDEXK(c));
else
ps.append("-");
break;
case OP_SETTABUP:
ps.append(" ; ");
buildUpvalue(ps, f.upvalues[a]);
ps.append(" ");
if (ISK(b))
buildConstant(ps, f, INDEXK(b));
else
ps.append("-");
ps.append(" ");
if (ISK(c))
buildConstant(ps, f, INDEXK(c));
else
ps.append("-");
break;
case OP_GETTABLE:
case OP_SELF:
if (ISK(c)) {
ps.append(" ; ");
buildConstant(ps, f, INDEXK(c));
}
break;
case OP_SETTABLE:
case OP_ADD:
case OP_SUB:
case OP_MUL:
case OP_DIV:
case OP_POW:
case OP_EQ:
case OP_LT:
case OP_LE:
if (ISK(b) || ISK(c)) {
ps.append(" ; ");
if (ISK(b))
buildConstant(ps, f, INDEXK(b));
else
ps.append("-");
ps.append(" ");
if (ISK(c))
buildConstant(ps, f, INDEXK(c));
else
ps.append("-");
}
break;
case OP_JMP:
case OP_FORLOOP:
case OP_FORPREP:
ps.append(" ; to " + (sbx + pc + 2));
break;
case OP_CLOSURE:
ps.append(" ; " + f.p[bx].getClass().getName());
break;
case OP_SETLIST:
if (c == 0)
ps.append(" ; " + ((int) code[++pc]));
else
ps.append(" ; " + ((int) c));
break;
case OP_VARARG:
ps.append(" ; is_vararg=" + f.is_vararg);
break;
default:
break;
}
return this;
}
int getline(Prototype f, int pc) {
return pc > 0 && f.lineinfo != null && pc < f.lineinfo.length ? f.lineinfo[pc] : -1;
}
void buildHeader(Prototype f) {
String s = String.valueOf(f.source);
if (s.startsWith("@") || s.startsWith("="))
s = s.substring(1);
else if ("\033Lua".equals(s))
s = "(bstring)";
else
s = "(string)";
String a = (f.linedefined == 0) ? "main" : "function";
ps.append("\n%" + a + " <" + s + ":" + f.linedefined + ","
+ f.lastlinedefined + "> (" + f.code.length + " instructions, "
+ f.code.length * 4 + " bytes at " + id(f) + ")\n");
ps.append(f.numparams + " param, " + f.maxstacksize + " slot, "
+ f.upvalues.length + " upvalue, ");
ps.append(f.locvars.length + " local, " + f.k.length
+ " constant, " + f.p.length + " function\n");
}
void buildConstants(Prototype f) {
int i, n = f.k.length;
ps.append("constants (" + n + ") for " + id(f) + ":\n");
for (i = 0; i < n; i++) {
ps.append(" " + (i + 1) + " ");
buildValue(ps, f.k[i]);
ps.append("\n");
}
}
void buildLocals(Prototype f) {
int i, n = f.locvars.length;
ps.append("locals (" + n + ") for " + id(f) + ":\n");
for (i = 0; i < n; i++) {
ps.append(" " + i + " " + f.locvars[i].varname + " " + (f.locvars[i].startpc + 1) + " " + (f.locvars[i].endpc + 1));
}
}
void buildUpValues(Prototype f) {
int i, n = f.upvalues.length;
ps.append("upvalues (" + n + ") for " + id(f) + ":\n");
for (i = 0; i < n; i++) {
ps.append(" " + i + " " + f.upvalues[i] + "\n");
}
}
/**
* Pretty-prints contents of a Prototype.
*
* @param prototype Prototype to print.
*/
void print(Prototype prototype) {
buildFunction(prototype, true);
}
/**
* Pretty-prints contents of a Prototype in short or long form.
*
* @param prototype Prototype to print.
* @param full true to print all fields, false to print short form.
*/
void buildFunction(Prototype prototype, boolean full) {
int i, n = prototype.p.length;
buildHeader(prototype);
buildCode(prototype);
if (full) {
buildConstants(prototype);
buildLocals(prototype);
buildUpValues(prototype);
}
for (i = 0; i < n; i++)
buildFunction(prototype.p[i], full);
}
void format(String s, int maxcols) {
int n = s.length();
if (n > maxcols)
ps.append(s.substring(0, maxcols));
else {
ps.append(s);
for (int i = maxcols - n; --i >= 0; )
ps.append(' ');
}
}
String id(Prototype f) {
return "Proto";
}
void _assert(boolean b) {
if (!b)
throw new NullPointerException("_assert failed");
}
/**
* Print the state of a {@link LuaClosure} that is being executed
*
* @param cl the {@link LuaClosure}
* @param pc the program counter
* @param stack the stack of {@link LuaValue}
* @param top the top of the stack
* @param varargs any {@link Varargs} value that may apply
*/
public LuaPrint buildState(LuaClosure cl, int pc, LuaValue[] stack, int top, Varargs varargs) {
// print opcode into buffer
StringBuffer previous = ps;
ps = new StringBuffer();
buildOpCode(cl.p, pc);
LogUtil.i(ps);
ps = previous;
format(ps.toString(), 50);
// print stack
ps.append('[');
for (int i = 0; i < stack.length; i++) {
LuaValue v = stack[i];
if (v == null)
ps.append(STRING_FOR_NULL);
else switch (v.type()) {
case LuaValue.TSTRING:
LuaString s = v.checkstring();
ps.append(s.length() < 48 ?
s.tojstring() :
s.substring(0, 32).tojstring() + "...+" + (s.length() - 32) + "b");
break;
case LuaValue.TFUNCTION:
ps.append(v.tojstring());
break;
case LuaValue.TUSERDATA:
Object o = v.touserdata();
if (o != null) {
String n = o.getClass().getName();
n = n.substring(n.lastIndexOf('.') + 1);
ps.append(n + ": " + Integer.toHexString(o.hashCode()));
} else {
ps.append(v.toString());
}
break;
default:
ps.append(v.tojstring());
}
if (i + 1 == top)
ps.append(']');
ps.append(" | ");
}
ps.append(varargs);
ps.append("\n");
return this;
}
public void print() {
LogUtil.i(ps);
}
@Override
public String toString() {
return ps != null ? ps.toString() : super.toString();
}
}