Files
luaj/src/main/java/lua/debug/DebugStackState.java

522 lines
18 KiB
Java
Raw Normal View History

/*******************************************************************************
* Copyright (c) 2007 LuaJ. 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 lua.debug;
2007-10-17 00:56:08 +00:00
import java.io.IOException;
import java.util.Hashtable;
2007-10-17 00:56:08 +00:00
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import lua.CallInfo;
import lua.Lua;
import lua.StackState;
import lua.addon.compile.LexState;
import lua.debug.event.DebugEvent;
import lua.debug.event.DebugEventBreakpoint;
import lua.debug.event.DebugEventError;
2007-10-17 00:56:08 +00:00
import lua.debug.event.DebugEventType;
import lua.debug.request.DebugRequest;
import lua.debug.request.DebugRequestLineBreakpointToggle;
import lua.debug.request.DebugRequestListener;
import lua.debug.request.DebugRequestStack;
import lua.debug.request.DebugRequestType;
import lua.debug.response.DebugResponse;
import lua.debug.response.DebugResponseCallgraph;
import lua.debug.response.DebugResponseSimple;
import lua.debug.response.DebugResponseStack;
import lua.io.LocVars;
import lua.io.Proto;
import lua.value.LTable;
import lua.value.LValue;
public class DebugStackState extends StackState implements DebugRequestListener {
// stepping constants and stepping state
protected static final int STEP_NONE = 0;
protected static final int STEP_OVER = 1;
protected static final int STEP_INTO = 2;
protected static final int STEP_RETURN = 3;
protected int stepping = STEP_NONE;
protected boolean shouldPauseForStepping = false;
protected int steppingFrame = -1;
protected Hashtable breakpoints = new Hashtable();
protected boolean exiting = false;
protected boolean suspended = false;
protected int lastline = -1;
protected String lastSource;
protected DebugSupport debugSupport;
protected VMException lastException;
2007-10-17 00:56:08 +00:00
public DebugStackState() {}
2007-10-17 00:56:08 +00:00
public void setDebugSupport(DebugSupport debugSupport) throws IOException {
if (debugSupport == null) {
throw new IllegalArgumentException("DebugSupport cannot be null");
}
this.debugSupport = debugSupport;
debugSupport.setDebugStackState(this);
debugSupport.start();
}
protected void debugAssert(boolean b) {
if ( ! b )
error( "assert failure" );
}
private String getFileLine(int cindex) {
String func = "?";
String line = "?";
String source = "?";
if ( cindex >= 0 ) {
CallInfo call = this.calls[cindex];
Proto p = call.closure.p;
if ( p != null && p.source != null )
source = p.source.toJavaString();
if ( p.lineinfo != null && p.lineinfo.length > call.pc )
line = String.valueOf( p.lineinfo[call.pc] );
// TODO: reverse lookup on function name ????
func = call.closure.toJavaString();
}
return source+":"+line+"("+func+")";
}
// override and fill in line number info
public void error(String message, int level) {
super.error( level<=0? message: getFileLine(cc+1-level)+": "+message );
}
// use line numbers by default
public void error(String message) {
error(message, 1);
}
// intercept exceptions and fill in line numbers
public void exec() {
try {
super.exec();
} catch ( AbortException e ) {
// ignored. Client aborts the debugging session.
} catch ( VMException e ) {
// let VM exceptions be processed
// the same as the base class to minimize differences
// between the debug and non-debug behavior
throw e;
} catch ( Exception e ) {
lastException = new VMException(e);
debugSupport.notifyDebugEvent(new DebugEventError(e.getMessage()));
suspend();
}
}
// debug hooks
public void debugHooks( int pc ) {
2007-10-12 23:39:27 +00:00
if ( exiting ) {
throw new AbortException("aborted by debug client");
}
if(DebugUtils.IS_DEBUG)
DebugUtils.println("entered debugHook on pc=" + pc + "...");
synchronized ( this ) {
CallInfo currentCallInfo = calls[cc];
Proto currentProto = currentCallInfo.closure.p;
// if we are not stepping, we keep going if the line doesn't change
int line = getLineNumber(currentCallInfo);
String source = DebugUtils.getSourceFileName(currentProto.source);
if ( !isStepping() && lastline == line && source.equals(lastSource) ) {
2007-08-11 05:29:56 +00:00
return;
}
if(DebugUtils.IS_DEBUG)
DebugUtils.println("debugHook - executing line: " + line);
int i = currentProto.code[pc];
int opCode = StackState.GET_OPCODE(i);
if (isStepping() &&
opCode == StackState.OP_RETURN && cc == 0) {
cancelStepping();
} else if (shouldPauseForStepping) {
shouldPauseForStepping = false;
suspendOnStepping();
} else if ( stepping == STEP_INTO ) {
if (lastline != line){
suspendOnStepping();
} else if (opCode == StackState.OP_CALL) {
shouldPauseForStepping = true;
}
} else if (stepping == STEP_OVER) {
if ((lastline != line && steppingFrame == cc) || (steppingFrame > cc)) {
suspendOnStepping();
}
} else if (stepping == STEP_RETURN) {
if ((opCode == StackState.OP_RETURN && cc == this.steppingFrame) ||
(opCode == StackState.OP_TAILCALL && cc == this.steppingFrame)){
shouldPauseForStepping = true;
}
}
// check for a break point if we aren't suspended already
if ( !suspended && lastline != line) {
if (DebugUtils.IS_DEBUG)
DebugUtils.println("Source: " + currentProto.source);
String breakpointKey = constructBreakpointKey(source, line);
if (breakpoints.containsKey(breakpointKey)){
if(DebugUtils.IS_DEBUG)
DebugUtils.println("hitting breakpoint " + constructBreakpointKey(source, line));
debugSupport.notifyDebugEvent(new DebugEventBreakpoint(source, line));
suspended = true;
}
2007-08-11 05:29:56 +00:00
}
// save line in case next op is a step
lastline = line;
lastSource = source;
// wait for a state change
while (suspended && !exiting ) {
try {
this.wait();
if(DebugUtils.IS_DEBUG)
DebugUtils.println("resuming execution...");
if (lastException != null) {
throw lastException;
}
} catch ( InterruptedException ie ) {
ie.printStackTrace();
}
}
}
}
private boolean isStepping() {
return stepping != STEP_NONE;
}
private void cancelStepping() {
debugSupport.notifyDebugEvent(new DebugEvent(DebugEventType.resumedOnSteppingEnd));
stepping = STEP_NONE;
steppingFrame = -1;
shouldPauseForStepping = false;
}
private void suspendOnStepping() {
debugSupport.notifyDebugEvent(new DebugEvent(DebugEventType.suspendedOnStepping));
suspended = true;
steppingFrame = -1;
}
/**
* Get the current line number
* @param pc program counter
* @return the line number corresponding to the pc
*/
private int getLineNumber(CallInfo ci) {
int[] lineNumbers = ci.closure.p.lineinfo;
int pc = ci.pc;
int line = (lineNumbers != null && lineNumbers.length > pc ? lineNumbers[pc] : -1);
return line;
}
// ------------------ commands coming from the debugger -------------------
public DebugResponse handleRequest(DebugRequest request) {
2007-10-17 00:56:08 +00:00
if (this.debugSupport == null) {
throw new IllegalStateException("DebugStackState is not equiped with DebugSupport.");
}
if (DebugUtils.IS_DEBUG)
DebugUtils.println("DebugStackState is handling request: " + request.toString());
2007-10-17 00:56:08 +00:00
DebugRequestType requestType = request.getType();
if (DebugRequestType.start == requestType) {
DebugEvent event = new DebugEvent(DebugEventType.started);
debugSupport.notifyDebugEvent(event);
return DebugResponseSimple.SUCCESS;
2007-10-17 00:56:08 +00:00
} else if (DebugRequestType.exit == requestType) {
stop();
return DebugResponseSimple.SUCCESS;
} else if (DebugRequestType.suspend == requestType) {
suspend();
DebugEvent event = new DebugEvent(DebugEventType.suspendedByClient);
debugSupport.notifyDebugEvent(event);
return DebugResponseSimple.SUCCESS;
2007-10-17 00:56:08 +00:00
} else if (DebugRequestType.resume == requestType) {
resume();
DebugEvent event = new DebugEvent(DebugEventType.resumedByClient);
debugSupport.notifyDebugEvent(event);
return DebugResponseSimple.SUCCESS;
} else if (DebugRequestType.lineBreakpointSet == requestType) {
DebugRequestLineBreakpointToggle setBreakpointRequest
= (DebugRequestLineBreakpointToggle)request;
setBreakpoint(setBreakpointRequest.getSource(), setBreakpointRequest.getLineNumber());
return DebugResponseSimple.SUCCESS;
} else if (DebugRequestType.lineBreakpointClear == requestType) {
DebugRequestLineBreakpointToggle clearBreakpointRequest
= (DebugRequestLineBreakpointToggle)request;
clearBreakpoint(clearBreakpointRequest.getSource(), clearBreakpointRequest.getLineNumber());
return DebugResponseSimple.SUCCESS;
} else if (DebugRequestType.callgraph == requestType) {
return new DebugResponseCallgraph(getCallgraph());
} else if (DebugRequestType.stack == requestType) {
DebugRequestStack stackRequest = (DebugRequestStack) request;
int index = stackRequest.getIndex();
return new DebugResponseStack(getStack(index));
} else if (DebugRequestType.stepInto == requestType) {
DebugEvent event = new DebugEvent(DebugEventType.resumedOnSteppingInto);
debugSupport.notifyDebugEvent(event);
stepInto();
return DebugResponseSimple.SUCCESS;
} else if (DebugRequestType.stepOver == requestType) {
DebugEvent event = new DebugEvent(DebugEventType.resumedOnSteppingOver);
debugSupport.notifyDebugEvent(event);
stepOver();
return DebugResponseSimple.SUCCESS;
} else if (DebugRequestType.stepReturn == requestType) {
DebugEvent event = new DebugEvent(DebugEventType.resumedOnSteppingReturn);
debugSupport.notifyDebugEvent(event);
stepReturn();
return DebugResponseSimple.SUCCESS;
}
throw new java.lang.IllegalArgumentException( "unkown request type: "+ request.getType());
}
/**
* suspend the execution
*/
public void suspend() {
synchronized ( this ) {
suspended = true;
stepping = STEP_NONE;
lastline = -1;
this.notify();
}
}
/**
* resume the execution
*/
public void resume() {
synchronized ( this ) {
suspended = false;
stepping = STEP_NONE;
this.notify();
}
}
2007-10-17 00:56:08 +00:00
public void stop() {
if (this.debugSupport == null) {
throw new IllegalStateException("DebugStackState is not equiped with DebugSupport.");
}
DebugEvent event = new DebugEvent(DebugEventType.terminated);
debugSupport.notifyDebugEvent(event);
new Timer().schedule(new TimerTask() {
public void run () {
debugSupport.stop();
debugSupport = null;
}
}, 500);
2007-10-17 00:56:08 +00:00
exit();
}
/**
* terminate the execution
*/
public void exit() {
synchronized ( this ) {
exiting = true;
this.notify();
}
}
/**
* set breakpoint at line N
* @param N the line to set the breakpoint at
*/
public void setBreakpoint(String source, int lineNumber) {
if (DebugUtils.IS_DEBUG) {
DebugUtils.print("source: " + source + " line:" + lineNumber);
DebugUtils.println("adding breakpoint " + constructBreakpointKey(source, lineNumber));
}
synchronized ( this ) {
String normalizedFileName = DebugUtils.getSourceFileName(source);
breakpoints.put(constructBreakpointKey(normalizedFileName, lineNumber), Boolean.TRUE );
}
}
protected String constructBreakpointKey(String source, int lineNumber) {
return source + ":" + lineNumber;
}
/**
* clear breakpoint at line lineNumber of source source
*/
public void clearBreakpoint(String source, int lineNumber) {
if(DebugUtils.IS_DEBUG)
DebugUtils.println("removing breakpoint " + constructBreakpointKey(source, lineNumber));
synchronized ( this ) {
String normalizedFileName = DebugUtils.getSourceFileName(source);
breakpoints.remove(constructBreakpointKey(normalizedFileName, lineNumber));
}
}
/**
* return the current call graph (i.e. stack frames from
* old to new, include information about file, method, etc.)
*/
public StackFrame[] getCallgraph() {
int n = cc;
if ( n < 0 || n >= calls.length )
return new StackFrame[0];
StackFrame[] frames = new StackFrame[n+1];
for ( int i = 0; i <= n; i++ ) {
CallInfo ci = calls[i];
frames[i] = new StackFrame(ci, getLineNumber(ci));
}
return frames;
}
public Variable[] getStack(int index) {
if (index < 0 || index >= calls.length) {
//TODO: this is an error, handle it differently
return new Variable[0];
}
CallInfo callInfo = calls[index];
if(DebugUtils.IS_DEBUG)
DebugUtils.println("Stack Frame: " + index + "[" + callInfo.base + "," + callInfo.top + "]");
int top = callInfo.top < callInfo.base ? callInfo.base : callInfo.top;
Proto prototype = callInfo.closure.p;
LocVars[] localVariables = prototype.locvars;
Vector variables = new Vector();
int localVariableCount = 0;
Hashtable variablesSeen = new Hashtable();
for (int i = 0; localVariables != null && i < localVariables.length && i <= top; i++) {
String varName = localVariables[i].varname.toString();
if(DebugUtils.IS_DEBUG) {
DebugUtils.print("\tVariable: " + varName);
DebugUtils.print("\tValue: " + stack[callInfo.base + i]);
}
if (!variablesSeen.contains(varName) &&
!LexState.isReservedKeyword(varName)) {
variablesSeen.put(varName, varName);
LValue value = stack[callInfo.base + i];
if (value != null) {
int type = value.luaGetType();
if (DebugUtils.IS_DEBUG)
DebugUtils.print("\tType: " + Lua.TYPE_NAMES[type]);
if (type == Lua.LUA_TTABLE) {
if (DebugUtils.IS_DEBUG)
DebugUtils.println(" (selected)");
variables.addElement(
new TableVariable(localVariableCount++,
varName,
type,
(LTable) value));
} else if (type != Lua.LUA_TFUNCTION &&
type != LUA_TTHREAD) {
if (DebugUtils.IS_DEBUG)
DebugUtils.println(" (selected)");
variables.addElement(
new Variable(localVariableCount++,
varName,
type,
value.toString()));
} else {
if (DebugUtils.IS_DEBUG)
DebugUtils.println("");
}
} else {
if (DebugUtils.IS_DEBUG)
DebugUtils.println("");
}
} else {
if (DebugUtils.IS_DEBUG)
DebugUtils.println("");
}
}
Variable[] result = new Variable[variables.size()];
for (int i = 0; i < variables.size(); i++) {
result[i] = (Variable) variables.elementAt(i);
}
return result;
}
/**
* step over to next line
*/
public void stepOver() {
synchronized ( this ) {
if (DebugUtils.IS_DEBUG)
DebugUtils.println("stepOver on cc=" + cc + "...");
suspended = false;
stepping = STEP_OVER;
steppingFrame = cc;
this.notify();
}
}
/**
* step a single statement
*/
public void stepInto() {
synchronized ( this ) {
suspended = false;
stepping = STEP_INTO;
this.notify();
}
}
/**
* return from the method call
*/
public void stepReturn() {
synchronized ( this ) {
if (DebugUtils.IS_DEBUG)
DebugUtils.println("stepReturn on cc=" + cc + "...");
suspended = false;
stepping = STEP_RETURN;
steppingFrame = cc;
this.notify();
}
}
}