From 32e1fedba50b5e543f0e19b528d5de553b6de6e6 Mon Sep 17 00:00:00 2001 From: James Roseborough Date: Tue, 18 Sep 2007 22:55:22 +0000 Subject: [PATCH] Improve error handling, introduce lua stack trace processing. --- .../java/lua/addon/luacompat/LuaCompat.java | 103 ++++++++++-------- .../java/lua/addon/luacompat/StrLib.java | 25 ++--- src/main/java/lua/Builtin.java | 10 +- src/main/java/lua/VM.java | 42 +++++-- src/main/java/lua/debug/DebugStackState.java | 47 +++++++- src/main/java/lua/io/LoadState.java | 2 + src/main/java/lua/value/LValue.java | 2 +- 7 files changed, 155 insertions(+), 76 deletions(-) diff --git a/src/addon/java/lua/addon/luacompat/LuaCompat.java b/src/addon/java/lua/addon/luacompat/LuaCompat.java index e72f7c30..b90b10fb 100644 --- a/src/addon/java/lua/addon/luacompat/LuaCompat.java +++ b/src/addon/java/lua/addon/luacompat/LuaCompat.java @@ -153,7 +153,7 @@ public class LuaCompat extends LFunction { vm.setResult(); } break; case LOADFILE: - vm.setResult( loadfile(vm, vm.getArg(0)) ); + loadfile(vm, vm.getArgAsString(0)); break; case TONUMBER: vm.setResult( toNumber( vm ) ); @@ -178,13 +178,12 @@ public class LuaCompat extends LFunction { vm.setResult(); break; case DOFILE: - dofile(vm, vm.getArg(0)); - break; + return dofile(vm, vm.getArgAsString(0)); case LOADSTRING: - vm.setResult( loadstring(vm, vm.getArg(0), vm.getArgAsString(1)) ); + loadstring(vm, vm.getArg(0), vm.getArgAsString(1)); break; case LOAD: - vm.setResult( load(vm, vm.getArg(0), vm.getArgAsString(1)) ); + load(vm, vm.getArg(0), vm.getArgAsString(1)); break; case TOSTRING: vm.setResult( tostring(vm, vm.getArg(0)) ); @@ -383,60 +382,63 @@ public class LuaCompat extends LFunction { } } - // closes the input stream - private static LValue inputStreamToClosureThenClose(VM vm, InputStream is, String chunkname ) { + // return true if laoded, false if error put onto the stack + private static boolean loadis(VM vm, InputStream is, String chunkname ) { try { - Proto p = LoadState.undump(vm, is, chunkname); - return new Closure( (StackState) vm, p); - } catch (IOException e) { - e.printStackTrace(); + if ( 0 != vm.lua_load(is, chunkname) ) { + vm.setErrorResult( LNil.NIL, "cannot load "+chunkname+": "+vm.getArgAsString(0) ); + return false; + } else { + return true; + } } finally { closeSafely( is ); } - return LNil.NIL; } - private LValue loadfile( VM vm, LValue fileName ) { + // return true if loaded, false if error put onto stack + public static boolean loadfile( VM vm, String fileName ) { InputStream is; String script; - if ( fileName != null ) { - script = fileName.luaAsString().toJavaString(); - is = getClass().getResourceAsStream( "/"+script ); + if ( ! "".equals(fileName) ) { + script = fileName; + is = vm.getClass().getResourceAsStream( "/"+script ); + if ( is == null ) { + vm.setErrorResult( LNil.NIL, "cannot open "+fileName+": No such file or directory" ); + return false; + } } else { is = STDIN; script = "-"; } - if ( is != null ) - return inputStreamToClosureThenClose( vm, is, script ); - return LNil.NIL; + // use vm to load the script + return loadis( vm, is, script ); } - private LValue dofile( VM vm, LValue fileName ) { - LValue chunk = loadfile( vm, fileName ); - if ( chunk != LNil.NIL ) { - vm.doCall((Closure) chunk, null); + // return true if call placed on the stack & ready to execute, false otherwise + private boolean dofile( VM vm, String fileName ) { + if ( loadfile( vm, fileName ) ) { + vm.prepStackCall(); // TODO: is this necessary? + return true; } else { - vm.setResult(); - vm.push("file not found: "+fileName); - vm.lua_error(); + return false; } - return null; } - private LValue loadstring(VM vm, LValue string, String chunkname) { - InputStream is = string.luaAsString().toInputStream(); - return inputStreamToClosureThenClose( vm, is, chunkname ); + // return true if loaded, false if error put onto stack + private boolean loadstring(VM vm, LValue string, String chunkname) { + return loadis( vm, + string.luaAsString().toInputStream(), + ("".equals(chunkname)? "(string)": chunkname) ); } - // TODO: convert this to stream? - private LValue load(VM vm, LValue chunkPartLoader, String chunkname) { + // return true if loaded, false if error put onto stack + private boolean load(VM vm, LValue chunkPartLoader, String chunkname) { if ( ! (chunkPartLoader instanceof Closure) ) { - vm.setResult(); - vm.push("not a function: "+chunkPartLoader); - vm.lua_error(); + vm.lua_error("not a closure: "+chunkPartLoader); } // load all the parts @@ -444,22 +446,29 @@ public class LuaCompat extends LFunction { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { while ( true ) { - vm.doCall( c, null ); - LValue r = vm.getArg(0); - if ( ! (r instanceof LString) ) + vm.setResult(c); + if ( 0 != vm.lua_pcall(0, 1) ) { + vm.setErrorResult(LNil.NIL, vm.getArgAsString(0)); + return false; + } + LValue v = vm.getArg(0); + if ( v == LNil.NIL ) break; - LString s = (LString) r; + LString s = v.luaAsString(); s.write(baos, 0, s.length()); } - } catch ( Exception e ) { - e.printStackTrace(); + + // load the chunk + return loadis( vm, + new ByteArrayInputStream( baos.toByteArray() ), + ("".equals(chunkname)? "=(load)": chunkname) ); + + } catch (IOException ioe) { + vm.setErrorResult(LNil.NIL, ioe.getMessage()); + return false; } finally { closeSafely( baos ); } - - // load from the byte array - InputStream is = new ByteArrayInputStream( baos.toByteArray() ); - return inputStreamToClosureThenClose( vm, is, chunkname ); } private LValue tostring(VM vm, LValue arg) { @@ -468,9 +477,7 @@ public class LuaCompat extends LFunction { private static LTable toTable(VM vm, LValue list) { if ( ! (list instanceof LTable) ) { - vm.setResult(); - vm.push("not a list: "+list); - vm.lua_error(); + vm.lua_error("not a list: "+list); return null; } return (LTable) list; diff --git a/src/addon/java/lua/addon/luacompat/StrLib.java b/src/addon/java/lua/addon/luacompat/StrLib.java index a7a3d93a..d8055db8 100644 --- a/src/addon/java/lua/addon/luacompat/StrLib.java +++ b/src/addon/java/lua/addon/luacompat/StrLib.java @@ -58,8 +58,7 @@ public class StrLib { * TODO: port dumping code as optional add-on */ static void dump( VM vm ) { - vm.setResult( new LString("not supported") ); - vm.lua_error(); + vm.lua_error("dump() not supported"); } /** @@ -397,7 +396,7 @@ public class StrLib { } else { int l = clen[i]; if ( l == CAP_UNFINISHED ) { - vm.luaL_error( "unfinished capture" ); + vm.lua_error( "unfinished capture" ); } if ( l == CAP_POSITION ) { vm.push( new LInteger( cinit[i] + 1 ) ); @@ -411,7 +410,7 @@ public class StrLib { private int check_capture( int l ) { l -= '1'; if ( l < 0 || l >= level || this.clen[l] == CAP_UNFINISHED ) { - vm.luaL_error("invalid capture index"); + vm.lua_error("invalid capture index"); } return l; } @@ -421,8 +420,7 @@ public class StrLib { for ( level--; level >= 0; level-- ) if ( clen[level] == CAP_UNFINISHED ) return level; - vm.push("invalid pattern capture"); - vm.lua_error(); + vm.lua_error("invalid pattern capture"); return 0; } @@ -430,8 +428,7 @@ public class StrLib { switch ( p.luaByte( poffset++ ) ) { case L_ESC: if ( poffset == p.length() ) { - vm.push( "malformed pattern (ends with %)" ); - vm.lua_error(); + vm.lua_error( "malformed pattern (ends with %)" ); } return poffset + 1; @@ -439,8 +436,7 @@ public class StrLib { if ( p.luaByte( poffset ) == '^' ) poffset++; do { if ( poffset == p.length() ) { - vm.push( "malformed pattern (missing ])" ); - vm.lua_error(); + vm.lua_error( "malformed pattern (missing ])" ); } if ( p.luaByte( poffset++ ) == L_ESC && poffset != p.length() ) poffset++; @@ -534,8 +530,7 @@ public class StrLib { case 'f': { poffset += 2; if ( p.luaByte( poffset ) != '[' ) { - vm.push("Missing [ after %f in pattern"); - vm.lua_error(); + vm.lua_error("Missing [ after %f in pattern"); } int ep = classend( poffset ); int previous = ( soffset == 0 ) ? -1 : s.luaByte( soffset - 1 ); @@ -615,8 +610,7 @@ public class StrLib { int res; int level = this.level; if ( level >= MAX_CAPTURES ) { - vm.push( "too many captures" ); - vm.lua_error(); + vm.lua_error( "too many captures" ); } cinit[ level ] = soff; clen[ level ] = what; @@ -648,8 +642,7 @@ public class StrLib { int matchbalance( int soff, int poff ) { final int plen = p.length(); if ( poff == plen || poff + 1 == plen ) { - vm.push( "unbalanced pattern" ); - vm.lua_error(); + vm.lua_error( "unbalanced pattern" ); } if ( s.luaByte( soff ) != p.luaByte( poff ) ) return -1; diff --git a/src/main/java/lua/Builtin.java b/src/main/java/lua/Builtin.java index b0e1b108..e3d76504 100644 --- a/src/main/java/lua/Builtin.java +++ b/src/main/java/lua/Builtin.java @@ -6,6 +6,7 @@ package lua; import java.io.OutputStream; import java.io.PrintStream; +import lua.value.LBoolean; import lua.value.LFunction; import lua.value.LNil; import lua.value.LTable; @@ -71,8 +72,13 @@ final class Builtin extends LFunction { vm.setResult( vm.getArg(0).luaGetType() ); break; case PCALL: - // TODO: implement pcall - vm.setResult( LNil.NIL ); + if ( 0 != vm.lua_pcall( vm.getArgCount()-1, Lua.LUA_MULTRET ) ) { + LValue v = vm.getArg(0); + vm.setResult( LBoolean.FALSE ); + vm.push( v ); + } else { + vm.setResult( LBoolean.TRUE ); + } break; default: luaUnsupportedOperation(); diff --git a/src/main/java/lua/VM.java b/src/main/java/lua/VM.java index 71f0697c..4891d2c7 100644 --- a/src/main/java/lua/VM.java +++ b/src/main/java/lua/VM.java @@ -1,6 +1,9 @@ package lua; +import java.io.InputStream; + import lua.io.Closure; +import lua.value.LNil; import lua.value.LString; import lua.value.LValue; @@ -140,17 +143,40 @@ public interface VM { */ public void setResult(LValue val); - - /** - * Generates a Lua error. The error message(which can actually be a Lua value of any type) - * must be on the top of the stack. - */ - public void lua_error(); + /** + * Set up an error result on the stack. + * @param value the LValue to return as the first return value + * @param message the String error message to supply + */ + public void setErrorResult(LValue value, String message); + + // ====================== lua Java API ======================= + /** * Raises an error. The message is pushed onto the stack and used as the error message. * It also adds at the beginning of the message the file name and the line number where - * the error occurred, if this information is available. + * the error occurred, if this information is available. + * + * In the java implementation this throws a RuntimeException, possibly filling + * line number information first. */ - public void luaL_error(String message); + public void lua_error(String message); + + /** + * Run the method on the stack in protected mode. + * @param nArgs number of arguments on the stack + * @param nResults number of results on the stack + * @return 0 if successful, LUA_ERRMEM if no memory, LUA_ERRRUN for any other error + */ + public int lua_pcall(int nArgs, int nResults); + + + /** + * + * @param is InputStream providing the data to be loaded + * @param chunkname Name of the chunk to be used in debugging + * @return 0 if successful, LUA_ERRMEM if no memory, LUA_ERRSYNTAX for i/o or any other errors + */ + public int lua_load( InputStream is, String chunkname ); } diff --git a/src/main/java/lua/debug/DebugStackState.java b/src/main/java/lua/debug/DebugStackState.java index 8847cc3b..42609637 100644 --- a/src/main/java/lua/debug/DebugStackState.java +++ b/src/main/java/lua/debug/DebugStackState.java @@ -5,11 +5,15 @@ import java.util.Map; import java.util.StringTokenizer; import lua.CallInfo; +import lua.Print; import lua.StackState; import lua.io.LocVars; +import lua.io.Proto; public class DebugStackState extends StackState implements DebugRequestListener { - + + private static final boolean DEBUG = false; + public Map breakpoints = new HashMap(); private boolean exiting = false; private boolean suspended = false; @@ -19,6 +23,47 @@ public class DebugStackState extends StackState implements DebugRequestListener public DebugStackState() { } + 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.luaAsString().toJavaString(); + } + return source+":"+line+"("+func+")"; + } + + + // override and fill in line number info + public void lua_error(String message) { + super.lua_error( getFileLine(cc)+": "+message ); + } + + private void printLuaTrace() { + System.out.println( "Lua location: "+getFileLine(cc) ); + for ( int cindex=cc-1; cindex>=0; cindex-- ) + System.out.println( "\tin "+getFileLine( cindex ) ); + } + + // intercept exceptions and fill in line numbers + public void exec() { + try { + super.exec(); + } catch ( Exception t ) { + t.printStackTrace(); + printLuaTrace(); + System.out.flush(); + } + } + + // debug hooks public void debugHooks( int pc ) { if ( exiting ) diff --git a/src/main/java/lua/io/LoadState.java b/src/main/java/lua/io/LoadState.java index df5ff83c..edc3cbb8 100644 --- a/src/main/java/lua/io/LoadState.java +++ b/src/main/java/lua/io/LoadState.java @@ -196,6 +196,8 @@ public class LoadState { Proto f = new Proto(); // this.L.push(f); f.source = loadString(); + if ( f.source == null ) + f.source = p; f.linedefined = loadInt(); f.lastlinedefined = loadInt(); f.nups = loadByte(); diff --git a/src/main/java/lua/value/LValue.java b/src/main/java/lua/value/LValue.java index 845f6f3d..9a7dfe70 100644 --- a/src/main/java/lua/value/LValue.java +++ b/src/main/java/lua/value/LValue.java @@ -33,7 +33,7 @@ public class LValue { // perform a lua call, return true if the call is to a lua function, false // if it ran to completion. public boolean luaStackCall(VM vm) { - luaUnsupportedOperation(); + vm.lua_error("attempt to call "+this); return false; }