Add a backtrace command to debugger, add console.trace command, set Error.stack in constructor (fix #2490)

Error.stack/stack dump now uses more standard file:line:col format
            Out of memory errors now show a backtrace (previously it was very hard to track these down)
This commit is contained in:
Gordon Williams 2025-05-23 11:36:22 +01:00
parent c1eb6941b6
commit 159239053c
14 changed files with 137 additions and 103 deletions

View File

@ -2,10 +2,10 @@ break jsAssertFail
break jsError break jsError
define jsvTrace define jsvTrace
print jsvTrace($arg0, 0) print jsvTrace($arg0, 0)
end end
define whereami define whereami
print jslPrintPosition(jsiConsolePrintString, 0, lex->tokenLastStart) print jslPrintPosition(jsiConsolePrintString, 0, lex, lex->tokenLastStart)
print jslPrintTokenLineMarker(jsiConsolePrintString, 0, lex->tokenLastStart, 0) print jslPrintTokenLineMarker(jsiConsolePrintString, 0, lex, lex->tokenLastStart, 0)
end end
define typeof define typeof
if (($arg0)->flags&JSV_VARTYPEMASK)>=JSV_NAME_STRING_0 && (($arg0)->flags&JSV_VARTYPEMASK)<=JSV_NAME_STRING_MAX if (($arg0)->flags&JSV_VARTYPEMASK)>=JSV_NAME_STRING_0 && (($arg0)->flags&JSV_VARTYPEMASK)<=JSV_NAME_STRING_MAX
@ -25,7 +25,7 @@ end
define asm define asm
set disassemble-next-line on set disassemble-next-line on
show disassemble-next-line show disassemble-next-line
echo now use stepi echo now use stepi
end end
# Watchdog timer off for NRF52 devices # Watchdog timer off for NRF52 devices
define wdt_off define wdt_off
@ -56,9 +56,6 @@ define execflags
if execInfo.execute&EXEC_ERROR if execInfo.execute&EXEC_ERROR
printf "EXEC_ERROR\n" printf "EXEC_ERROR\n"
end end
if execInfo.execute&EXEC_ERROR_LINE_REPORTED
printf "EXEC_ERROR_LINE_REPORTED\n"
end
if execInfo.execute&EXEC_FOR_INIT if execInfo.execute&EXEC_FOR_INIT
printf "EXEC_FOR_INIT\n" printf "EXEC_FOR_INIT\n"
end end
@ -82,7 +79,7 @@ end
define hook-run define hook-run
set $primask=0 set $primask=0
end end
define hook-continue define hook-continue
set $primask=0 set $primask=0

View File

@ -5,6 +5,9 @@
Bangle.js: g.findFont now attempts to use Intl:2 if there's room. Also fix memory leak Bangle.js: g.findFont now attempts to use Intl:2 if there's room. Also fix memory leak
nRF52840: Add E.getVDDH() method to get VDDH voltage nRF52840: Add E.getVDDH() method to get VDDH voltage
Fix issue handling multi-line tempated strings pasted to REPL (2v26 regression) Fix issue handling multi-line tempated strings pasted to REPL (2v26 regression)
Add a backtrace command to debugger, add console.trace command, set Error.stack in constructor (fix #2490)
Error.stack/stack dump now uses more standard file:line:col format
Out of memory errors now show a backtrace (previously it was very hard to track these down)
2v26 : nRF5x: ensure TIMER1_IRQHandler doesn't always wake idle loop up (fix #1900) 2v26 : nRF5x: ensure TIMER1_IRQHandler doesn't always wake idle loop up (fix #1900)
Puck.js: On v2.1 ensure Puck.mag behaves like other variants - just returning the last reading (avoids glitches when used with Puck.magOn) Puck.js: On v2.1 ensure Puck.mag behaves like other variants - just returning the last reading (avoids glitches when used with Puck.magOn)

View File

@ -1414,8 +1414,10 @@ bool jsfLoadBootCodeFromFlash(bool isReset) {
#endif #endif
if (jsiStatus & JSIS_FIRST_BOOT) { if (jsiStatus & JSIS_FIRST_BOOT) {
JsVar *code = jsfReadFile(jsfNameFromString(".bootPowerOn"),0,0); JsVar *code = jsfReadFile(jsfNameFromString(".bootPowerOn"),0,0);
if (code) if (code) {
jsvUnLock2(jspEvaluateVar(code,0,0), code); jsvUnLock2(jspEvaluateVar(code,0,".bootPowerOn",0), code);
jsiCheckErrors();
}
} }
#endif #endif
// Load code in .boot0/1/2/3 UNLESS BTN1 IS HELD DOWN FOR BANGLE.JS ON FIRST BOOT (BTN3 for Dickens) // Load code in .boot0/1/2/3 UNLESS BTN1 IS HELD DOWN FOR BANGLE.JS ON FIRST BOOT (BTN3 for Dickens)
@ -1434,14 +1436,17 @@ bool jsfLoadBootCodeFromFlash(bool isReset) {
for (int i=0;i<4;i++) { for (int i=0;i<4;i++) {
filename[5] = (char)('0'+i); filename[5] = (char)('0'+i);
JsVar *code = jsfReadFile(jsfNameFromString(filename),0,0); JsVar *code = jsfReadFile(jsfNameFromString(filename),0,0);
if (code) if (code) {
jsvUnLock2(jspEvaluateVar(code,0,0), code); jsvUnLock2(jspEvaluateVar(code,0,filename,0), code);
jsiCheckErrors();
}
} }
} }
// Load normal boot code // Load normal boot code
JsVar *code = jsfGetBootCodeFromFlash(isReset); JsVar *code = jsfGetBootCodeFromFlash(isReset);
if (!code) return false; if (!code) return false;
jsvUnLock2(jspEvaluateVar(code,0,0), code); jsvUnLock2(jspEvaluateVar(code,0,"boot code",0), code);
jsiCheckErrors();
return true; return true;
} }

View File

@ -137,7 +137,6 @@ JsErrorFlags lastJsErrorFlags = 0; ///< Compare with jsErrorFlags in order to re
#ifdef USE_DEBUGGER #ifdef USE_DEBUGGER
void jsiDebuggerLine(JsVar *line); void jsiDebuggerLine(JsVar *line);
#endif #endif
void jsiCheckErrors();
static void jsiPacketFileEnd(); static void jsiPacketFileEnd();
static void jsiPacketExit(); static void jsiPacketExit();
@ -535,11 +534,13 @@ void jsiSoftInit(bool hasBeenReset) {
// Run 'boot code' - textual JS in flash // Run 'boot code' - textual JS in flash
jsfLoadBootCodeFromFlash(hasBeenReset); jsfLoadBootCodeFromFlash(hasBeenReset);
// jsiCheckErrors is performed internally
// Now run initialisation code // Now run initialisation code
JsVar *initCode = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_INIT_CODE_NAME); JsVar *initCode = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_INIT_CODE_NAME);
if (initCode) { if (initCode) {
jsvUnLock2(jspEvaluateVar(initCode, 0, 0), initCode); jsvUnLock2(jspEvaluateVar(initCode, 0, "initcode", 0), initCode);
jsiCheckErrors();
jsvObjectRemoveChild(execInfo.hiddenRoot, JSI_INIT_CODE_NAME); jsvObjectRemoveChild(execInfo.hiddenRoot, JSI_INIT_CODE_NAME);
} }
@ -565,11 +566,13 @@ void jsiSoftInit(bool hasBeenReset) {
// Execute `init` events on `E` // Execute `init` events on `E`
jsiExecuteEventCallbackOn("E", INIT_CALLBACK_NAME, 0, 0); jsiExecuteEventCallbackOn("E", INIT_CALLBACK_NAME, 0, 0);
jsiCheckErrors();
// Execute the `onInit` function // Execute the `onInit` function
JsVar *onInit = jsvObjectGetChildIfExists(execInfo.root, JSI_ONINIT_NAME); JsVar *onInit = jsvObjectGetChildIfExists(execInfo.root, JSI_ONINIT_NAME);
if (onInit) { if (onInit) {
if (jsiEcho()) jsiConsolePrint("Running onInit()...\n"); if (jsiEcho()) jsiConsolePrint("Running onInit()...\n");
jsiExecuteEventCallback(0, onInit, 0, 0); jsiExecuteEventCallback(0, onInit, 0, 0);
jsiCheckErrors();
jsvUnLock(onInit); jsvUnLock(onInit);
} }
} }
@ -1528,7 +1531,7 @@ void jsiHandleNewLine(bool execute) {
#endif #endif
{ {
// execute! // execute!
JsVar *v = jspEvaluateVar(lineToExecute, 0, jsiLineNumberOffset); JsVar *v = jspEvaluateVar(lineToExecute, 0, "REPL", jsiLineNumberOffset);
// add input line to history // add input line to history
bool isEmpty = jsvIsEmptyString(lineToExecute); bool isEmpty = jsvIsEmptyString(lineToExecute);
// Don't store history if we're not echoing back to the console (it probably wasn't typed by the user) // Don't store history if we're not echoing back to the console (it probably wasn't typed by the user)
@ -2040,7 +2043,7 @@ static NO_INLINE bool jsiExecuteEventCallbackInner(JsVar *thisVar, JsVar *callba
} else if (jsvIsFunction(callbackNoNames)) { } else if (jsvIsFunction(callbackNoNames)) {
jsvUnLock(jspExecuteFunction(callbackNoNames, thisVar, (int)argCount, argPtr)); jsvUnLock(jspExecuteFunction(callbackNoNames, thisVar, (int)argCount, argPtr));
} else if (jsvIsString(callbackNoNames)) { } else if (jsvIsString(callbackNoNames)) {
jsvUnLock(jspEvaluateVar(callbackNoNames, 0, 0)); jsvUnLock(jspEvaluateVar(callbackNoNames, 0, "event", 0));
} else } else
jsError("Unknown type of callback in Event Queue"); jsError("Unknown type of callback in Event Queue");
return ok; return ok;
@ -2146,7 +2149,7 @@ void jsiCtrlC() {
/** Take an event for a UART and handle the characters we're getting, potentially /** Take an event for a UART and handle the characters we're getting, potentially
* grabbing more characters as well if it's easy. If more character events are * grabbing more characters as well if it's easy. If more character events are
* grabbed, the number of extra events (not characters) is returned */ * grabbed, the number of extra events (not characters) is returned */
int jsiHandleIOEventForSerial(JsVar *usartClass, IOEventFlags eventFlags, uint8_t *data, int length) { int jsiHandleIOEventForSerial(JsVar *usartClass, IOEventFlags eventFlags, uint8_t *data, unsigned int length) {
int eventsHandled = length+2; int eventsHandled = length+2;
JsVar *stringData = length ? jsvNewStringOfLength(length, (char*)data) : NULL; JsVar *stringData = length ? jsvNewStringOfLength(length, (char*)data) : NULL;
if (stringData) { if (stringData) {
@ -2178,7 +2181,7 @@ void jsiIdle() {
bool wasBusy = false; bool wasBusy = false;
IOEventFlags eventFlags; IOEventFlags eventFlags;
uint8_t eventData[IOEVENT_MAX_LEN]; uint8_t eventData[IOEVENT_MAX_LEN];
int eventLen; unsigned int eventLen;
// ensure we can't get totally swamped by having more events than we can process. // ensure we can't get totally swamped by having more events than we can process.
// Just process what was in the event queue at the start // Just process what was in the event queue at the start
int maxEvents = jshGetEventsUsed(); int maxEvents = jshGetEventsUsed();
@ -2564,8 +2567,8 @@ void jsiIdle() {
jsiSemiInit(false, &filename); // don't autoload code jsiSemiInit(false, &filename); // don't autoload code
// load the code we specified // load the code we specified
JsVar *code = jsfReadFile(filename,0,0); JsVar *code = jsfReadFile(filename,0,0);
if (code) if (code) // only supply the filename if we're sure it's zero terminated
jsvUnLock2(jspEvaluateVar(code,0,0), code); jsvUnLock2(jspEvaluateVar(code,0,filename.c[sizeof(filename.c)-1] ? filename.c : "load",0), code);
} else { } else {
jsiSoftKill(); jsiSoftKill();
jspSoftKill(); jspSoftKill();
@ -2805,14 +2808,14 @@ void jsiDebuggerLoop() {
itostr((JsVarInt)jslGetLineNumber() + (JsVarInt)lex->lineNumberOffset - 1, lineStr, 10); itostr((JsVarInt)jslGetLineNumber() + (JsVarInt)lex->lineNumberOffset - 1, lineStr, 10);
} else } else
#endif #endif
{ { // FIXME: Maybe if executing from a Storage file we use line numbers within that file?
lineStr[0]=0; lineStr[0]=0;
} }
size_t lineLen = strlen(lineStr); size_t lineLen = strlen(lineStr);
while (lineLen < sizeof(lineStr)-1) lineStr[lineLen++]=' '; while (lineLen < sizeof(lineStr)-1) lineStr[lineLen++]=' ';
lineStr[lineLen] = 0; lineStr[lineLen] = 0;
// print the line of code, prefixed by the line number, and with a pointer to the exact character in question // print the line of code, prefixed by the line number, and with a pointer to the exact character in question
jslPrintTokenLineMarker(vcbprintf_callback_jsiConsolePrintString, 0, lex->tokenLastStart, lineStr); jslPrintTokenLineMarker(vcbprintf_callback_jsiConsolePrintString, 0, lex, lex->tokenLastStart, lineStr);
} }
while (!(jsiStatus & JSIS_EXIT_DEBUGGER) && while (!(jsiStatus & JSIS_EXIT_DEBUGGER) &&
@ -2822,7 +2825,7 @@ void jsiDebuggerLoop() {
jshIdle(); jshIdle();
// If we have too many events (> half full) drain the queue // If we have too many events (> half full) drain the queue
uint8_t eventData[IOEVENT_MAX_LEN]; uint8_t eventData[IOEVENT_MAX_LEN];
int eventLen; unsigned int eventLen;
while (jshGetEventsUsed()>IOBUFFERMASK*1/2 && while (jshGetEventsUsed()>IOBUFFERMASK*1/2 &&
!(jsiStatus & JSIS_EXIT_DEBUGGER) && !(jsiStatus & JSIS_EXIT_DEBUGGER) &&
!(execInfo.execute & EXEC_CTRL_C_MASK)) { !(execInfo.execute & EXEC_CTRL_C_MASK)) {
@ -2906,7 +2909,8 @@ void jsiDebuggerLine(JsVar *line) {
"finish / f - finish execution of the function call\n" "finish / f - finish execution of the function call\n"
"print ... / p ... - evaluate and print the next argument\n" "print ... / p ... - evaluate and print the next argument\n"
"info locals / i l) - output local variables\n" "info locals / i l) - output local variables\n"
"info scopechain / i s - output all variables in all scopes\n"); "info scopechain / i s - output all variables in all scopes\n"
"bt - print backtrace\n");
} else if (!strcmp(id,"quit") || !strcmp(id,"q")) { } else if (!strcmp(id,"quit") || !strcmp(id,"q")) {
jsiStatus |= JSIS_EXIT_DEBUGGER; jsiStatus |= JSIS_EXIT_DEBUGGER;
execInfo.execute |= EXEC_INTERRUPTED; execInfo.execute |= EXEC_INTERRUPTED;
@ -2962,6 +2966,8 @@ void jsiDebuggerLine(JsVar *line) {
} else { } else {
jsiConsolePrint("Unknown command\n"); jsiConsolePrint("Unknown command\n");
} }
} else if (!strcmp(id,"bt")) {
jslPrintStackTrace(vcbprintf_callback_jsiConsolePrintString, NULL, oldLex);
} else } else
handled = false; handled = false;
} }

View File

@ -63,6 +63,9 @@ bool jsiExecuteEventCallbackName(JsVar *obj, const char *cbName, unsigned int ar
/// Utility version of jsiExecuteEventCallback for calling events on global variables /// Utility version of jsiExecuteEventCallback for calling events on global variables
bool jsiExecuteEventCallbackOn(const char *objectName, const char *cbName, unsigned int argCount, JsVar **argPtr); bool jsiExecuteEventCallbackOn(const char *objectName, const char *cbName, unsigned int argCount, JsVar **argPtr);
/// Check for and report/handle interpreter errors (can be called after executing JS code)
void jsiCheckErrors();
/// Create a timeout in JS to execute the given native function (outside of an IRQ). Returns the index /// Create a timeout in JS to execute the given native function (outside of an IRQ). Returns the index
JsVar *jsiSetTimeout(void (*functionPtr)(void), JsVarFloat milliseconds); JsVar *jsiSetTimeout(void (*functionPtr)(void), JsVarFloat milliseconds);
/// Clear a timeout in JS given the index returned by jsiSetTimeout /// Clear a timeout in JS given the index returned by jsiSetTimeout

View File

@ -948,6 +948,8 @@ void jslInit(JsVar *var) {
#ifndef ESPR_NO_LINE_NUMBERS #ifndef ESPR_NO_LINE_NUMBERS
lex->lineNumberOffset = 0; lex->lineNumberOffset = 0;
#endif #endif
lex->functionName = NULL;
lex->lastLex = NULL;
// set up iterator // set up iterator
jsvStringIteratorNew(&lex->it, lex->sourceVar, 0); jsvStringIteratorNew(&lex->it, lex->sourceVar, 0);
jsvUnLock(lex->it.var); // see jslGetNextCh jsvUnLock(lex->it.var); // see jslGetNextCh
@ -1502,7 +1504,7 @@ void jslPrintTokenisedString(JsVar *code, vcbprintf_callback user_callback, void
jsvStringIteratorFree(&it); jsvStringIteratorFree(&it);
} }
void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t tokenPos) { void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos) {
size_t line,col; size_t line,col;
#if !defined(SAVE_ON_FLASH) && !defined(ESPR_EMBED) #if !defined(SAVE_ON_FLASH) && !defined(ESPR_EMBED)
if (jsvIsNativeString(lex->sourceVar) || jsvIsFlashString(lex->sourceVar)) { if (jsvIsNativeString(lex->sourceVar) || jsvIsFlashString(lex->sourceVar)) {
@ -1513,7 +1515,7 @@ void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t
JsVar *fileStr = jsvAddressToVar(fileAddr, jsfGetFileSize(&header)); JsVar *fileStr = jsvAddressToVar(fileAddr, jsfGetFileSize(&header));
jsvGetLineAndCol(fileStr, tokenPos + stringAddr - fileAddr, &line, &col); jsvGetLineAndCol(fileStr, tokenPos + stringAddr - fileAddr, &line, &col);
JsVar *name = jsfVarFromName(header.name); JsVar *name = jsfVarFromName(header.name);
cbprintf(user_callback, user_data,"line %d col %d in %v\n", line, col, name); cbprintf(user_callback, user_data,"%v:%d:%d", name, line, col);
jsvUnLock2(fileStr,name); jsvUnLock2(fileStr,name);
return; return;
} }
@ -1524,10 +1526,10 @@ void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t
if (lex->lineNumberOffset) if (lex->lineNumberOffset)
line += (size_t)lex->lineNumberOffset - 1; line += (size_t)lex->lineNumberOffset - 1;
#endif #endif
cbprintf(user_callback, user_data, "line %d col %d\n", line, col); cbprintf(user_callback, user_data, ":%d:%d", line, col);
} }
void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, size_t tokenPos, char *prefix) { void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos, char *prefix) {
size_t line = 1,col = 1; size_t line = 1,col = 1;
jsvGetLineAndCol(lex->sourceVar, tokenPos, &line, &col); jsvGetLineAndCol(lex->sourceVar, tokenPos, &line, &col);
size_t startOfLine = jsvGetIndexFromLineAndCol(lex->sourceVar, line, 1); size_t startOfLine = jsvGetIndexFromLineAndCol(lex->sourceVar, line, 1);
@ -1568,3 +1570,21 @@ void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data,
user_callback("^\n", user_data); user_callback("^\n", user_data);
} }
void jslPrintStackTrace(vcbprintf_callback user_callback, void *user_data, JsLex *lex) {
while (lex) {
user_callback(" at ", user_data);
if (lex->functionName) {
// can't use cbprintf here as it may try and allocate a var
// and we want to be able to use this when we're out of memory
char functionName[JSLEX_MAX_TOKEN_LENGTH];
jsvGetString(lex->functionName, functionName, sizeof(functionName));
user_callback(functionName, user_data);
user_callback(" (", user_data);
}
jslPrintPosition(user_callback, user_data, lex, lex->tokenLastStart);
user_callback(lex->functionName ? ")\n":"\n", user_data);
jslPrintTokenLineMarker(user_callback, user_data, lex, lex->tokenLastStart, 0);
lex = lex->lastLex; // go down to next lexer in list
}
}

View File

@ -174,6 +174,11 @@ typedef struct JsLex
*/ */
JsVar *sourceVar; // the actual string var JsVar *sourceVar; // the actual string var
JsvStringIterator it; // Iterator for the string JsvStringIterator it; // Iterator for the string
/// For stack traces - this is just a pointer to a function name if we have one (it's not 'owned')
JsVar *functionName;
/// For stack traces - this is just a pointer to the previous Lex on the stack
struct JsLex *lastLex;
} JsLex; } JsLex;
// The lexer // The lexer
@ -224,10 +229,13 @@ bool jslNeedSpaceBetween(unsigned char lastch, unsigned char ch);
void jslPrintTokenisedString(JsVar *code, vcbprintf_callback user_callback, void *user_data); void jslPrintTokenisedString(JsVar *code, vcbprintf_callback user_callback, void *user_data);
/// Print position in the form 'line X col Y' /// Print position in the form 'line X col Y'
void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t tokenPos); void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos);
/** Print the line of source code at `tokenPos`, prefixed with the string 'prefix' (0=no string). /** Print the line of source code at `tokenPos`, prefixed with the string 'prefix' (0=no string).
* Then, underneath it, print a '^' marker at the column tokenPos was at */ * Then, underneath it, print a '^' marker at the column tokenPos was at */
void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, size_t tokenPos, char *prefix); void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos, char *prefix);
/** Prints a full stack trace to the current callback function */
void jslPrintStackTrace(vcbprintf_callback user_callback, void *user_data, JsLex *lex);
#endif /* JSLEX_H_ */ #endif /* JSLEX_H_ */

View File

@ -74,10 +74,8 @@ void jspSetInterrupted(bool interrupt) {
} }
/// Set the error flag - set lineReported if we've already output the line number /// Set the error flag - set lineReported if we've already output the line number
void jspSetError(bool lineReported) { void jspSetError() {
execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_YES) | EXEC_ERROR; execInfo.execute = (execInfo.execute & (JsExecFlags)~EXEC_YES) | EXEC_ERROR;
if (lineReported)
execInfo.execute |= EXEC_ERROR_LINE_REPORTED;
} }
bool jspHasError() { bool jspHasError() {
@ -100,8 +98,6 @@ void jspeiRemoveScope() {
if (!execInfo.scopesVar || !jsvGetArrayLength(execInfo.scopesVar)) { if (!execInfo.scopesVar || !jsvGetArrayLength(execInfo.scopesVar)) {
// This should never happen unless there's an interpreter error - no need to have an error message // This should never happen unless there's an interpreter error - no need to have an error message
assert(0); assert(0);
//jsExceptionHere(JSET_INTERNALERROR, "Too many scopes removed");
//jspSetError(false);
return; return;
} }
jsvUnLock(jsvArrayPop(execInfo.scopesVar)); jsvUnLock(jsvArrayPop(execInfo.scopesVar));
@ -237,12 +233,11 @@ void jspSetNoExecute() {
execInfo.execute = (execInfo.execute & (JsExecFlags)(int)~EXEC_RUN_MASK) | EXEC_NO; execInfo.execute = (execInfo.execute & (JsExecFlags)(int)~EXEC_RUN_MASK) | EXEC_NO;
} }
void jspAppendStackTrace(JsVar *stackTrace) { void jspAppendStackTrace(JsVar *stackTrace, JsLex *lex) {
JsvStringIterator it; JsvStringIterator it;
jsvStringIteratorNew(&it, stackTrace, 0); jsvStringIteratorNew(&it, stackTrace, 0);
jsvStringIteratorGotoEnd(&it); jsvStringIteratorGotoEnd(&it);
jslPrintPosition((vcbprintf_callback)jsvStringIteratorPrintfCallback, &it, lex->tokenLastStart); jslPrintStackTrace(jsvStringIteratorPrintfCallback, &it, lex);
jslPrintTokenLineMarker((vcbprintf_callback)jsvStringIteratorPrintfCallback, &it, lex->tokenLastStart, 0);
jsvStringIteratorFree(&it); jsvStringIteratorFree(&it);
} }
@ -256,18 +251,6 @@ void jspSetException(JsVar *value) {
} }
// Set the exception flag // Set the exception flag
execInfo.execute = execInfo.execute | EXEC_EXCEPTION; execInfo.execute = execInfo.execute | EXEC_EXCEPTION;
// Try and do a stack trace
if (lex) {
JsVar *stackTrace = jsvObjectGetChild(execInfo.hiddenRoot, JSPARSE_STACKTRACE_VAR, JSV_STRING_0);
if (stackTrace) {
jsvAppendPrintf(stackTrace, " at ");
jspAppendStackTrace(stackTrace);
jsvUnLock(stackTrace);
// stop us from printing the trace in the same block
execInfo.execute = execInfo.execute | EXEC_ERROR_LINE_REPORTED;
}
}
} }
/** Return the reported exception if there was one (and clear it). May return undefined even if there was an exception - eg `throw undefined` */ /** Return the reported exception if there was one (and clear it). May return undefined even if there was an exception - eg `throw undefined` */
@ -276,13 +259,6 @@ JsVar *jspGetException() {
if (exceptionName) { if (exceptionName) {
JsVar *exception = jsvSkipName(exceptionName); JsVar *exception = jsvSkipName(exceptionName);
jsvRemoveChildAndUnLock(execInfo.hiddenRoot, exceptionName); jsvRemoveChildAndUnLock(execInfo.hiddenRoot, exceptionName);
JsVar *stack = jspGetStackTrace();
if (stack && jsvHasChildren(exception)) {
jsvObjectSetChild(exception, "stack", stack);
}
jsvUnLock(stack);
return exception; return exception;
} }
return 0; return 0;
@ -311,7 +287,7 @@ NO_INLINE bool jspeFunctionArguments(JsVar *funcVar) {
strcpy(&buf[1], jslGetTokenValueAsString()); strcpy(&buf[1], jslGetTokenValueAsString());
JsVar *param = jsvAddNamedChild(funcVar, 0, buf); JsVar *param = jsvAddNamedChild(funcVar, 0, buf);
if (!param) { // out of memory if (!param) { // out of memory
jspSetError(false); jspSetError();
return false; return false;
} }
param = jsvMakeFunctionParameter(param); // force this to be called a function parameter param = jsvMakeFunctionParameter(param); // force this to be called a function parameter
@ -677,7 +653,7 @@ NO_INLINE JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *t
// OPT: Probably when calling a function ONCE, use it, otherwise when recursing, make new? // OPT: Probably when calling a function ONCE, use it, otherwise when recursing, make new?
JsVar *functionRoot = jsvNewWithFlags(JSV_FUNCTION); JsVar *functionRoot = jsvNewWithFlags(JSV_FUNCTION);
if (!functionRoot) { // out of memory if (!functionRoot) { // out of memory
jspSetError(false); jspSetError();
jsvUnLock(thisVar); jsvUnLock(thisVar);
return 0; return 0;
} }
@ -834,11 +810,11 @@ NO_INLINE JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *t
execInfo.execute &= (JsExecFlags)~EXEC_DEBUGGER_NEXT_LINE; execInfo.execute &= (JsExecFlags)~EXEC_DEBUGGER_NEXT_LINE;
} }
#endif #endif
JsLex newLex; JsLex newLex;
JsLex *oldLex = jslSetLex(&newLex); JsLex *oldLex = jslSetLex(&newLex);
jslInit(functionCode); jslInit(functionCode);
newLex.functionName = functionName;
newLex.lastLex = oldLex;
jsvUnLock(functionCode); // unlock function code here to reduce amount of locks needed during recursion jsvUnLock(functionCode); // unlock function code here to reduce amount of locks needed during recursion
functionCode = 0; functionCode = 0;
#ifndef ESPR_NO_LINE_NUMBERS #ifndef ESPR_NO_LINE_NUMBERS
@ -889,9 +865,9 @@ NO_INLINE JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *t
#ifdef USE_DEBUGGER #ifdef USE_DEBUGGER
bool calledDebugger = false; bool calledDebugger = false;
if (execInfo.execute & EXEC_DEBUGGER_MASK) { if (execInfo.execute & EXEC_DEBUGGER_MASK) {
jsiConsolePrint("Value returned is ="); jsiConsolePrintf(functionName?"Value returned from %v is =":"Value returned is =", functionName);
jsfPrintJSON(returnVar, JSON_LIMIT | JSON_SOME_NEWLINES | JSON_PRETTY | JSON_SHOW_DEVICES); jsfPrintJSON(returnVar, JSON_LIMIT | JSON_SOME_NEWLINES | JSON_PRETTY | JSON_SHOW_DEVICES);
jsiConsolePrintChar('\n'); jsiConsolePrintString("\n"); // prints \r too
if (execInfo.execute & EXEC_DEBUGGER_FINISH_FUNCTION) { if (execInfo.execute & EXEC_DEBUGGER_FINISH_FUNCTION) {
calledDebugger = true; calledDebugger = true;
jsiDebuggerLoop(); jsiDebuggerLoop();
@ -904,19 +880,8 @@ NO_INLINE JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *t
jslKill(); jslKill();
jslSetLex(oldLex); jslSetLex(oldLex);
if (hasError) { if (hasError)
execInfo.execute |= hasError; // propogate error execInfo.execute |= hasError; // propogate error
JsVar *stackTrace = jsvObjectGetChild(execInfo.hiddenRoot, JSPARSE_STACKTRACE_VAR, JSV_STRING_0);
if (stackTrace) {
jsvAppendPrintf(stackTrace, jsvIsString(functionName)?"in function %q called from ":
"in function called from ", functionName);
if (lex) {
jspAppendStackTrace(stackTrace);
} else
jsvAppendPrintf(stackTrace, "system\n");
jsvUnLock(stackTrace);
}
}
} }
/* Return to old 'this' var. No need to unlock as we never locked before */ /* Return to old 'this' var. No need to unlock as we never locked before */
@ -1236,7 +1201,7 @@ NO_INLINE JsVar *jspeFactorFunctionCall() {
if (lex->tk==LEX_R_NEW) { if (lex->tk==LEX_R_NEW) {
jsExceptionHere(JSET_ERROR, "Nesting 'new' operators is unsupported"); jsExceptionHere(JSET_ERROR, "Nesting 'new' operators is unsupported");
jspSetError(false); jspSetError();
return 0; return 0;
} }
} }
@ -1373,7 +1338,7 @@ NO_INLINE JsVar *jspeFactorObject() {
if (JSP_SHOULD_EXECUTE) { if (JSP_SHOULD_EXECUTE) {
JsVar *contents = jsvNewObject(); JsVar *contents = jsvNewObject();
if (!contents) { // out of memory if (!contents) { // out of memory
jspSetError(false); jspSetError();
return 0; return 0;
} }
/* JSON-style object definition */ /* JSON-style object definition */
@ -1466,7 +1431,7 @@ NO_INLINE JsVar *jspeFactorArray() {
if (JSP_SHOULD_EXECUTE) { if (JSP_SHOULD_EXECUTE) {
contents = jsvNewEmptyArray(); contents = jsvNewEmptyArray();
if (!contents) { // out of memory if (!contents) { // out of memory
jspSetError(false); jspSetError();
return 0; return 0;
} }
} }
@ -2325,17 +2290,6 @@ NO_INLINE void jspeBlockNoBrackets() {
JsVar *a = jspeStatement(); JsVar *a = jspeStatement();
jsvCheckReferenceError(a); jsvCheckReferenceError(a);
jsvUnLock(a); jsvUnLock(a);
if (JSP_HAS_ERROR) {
if (lex && !(execInfo.execute&EXEC_ERROR_LINE_REPORTED)) {
execInfo.execute = (JsExecFlags)(execInfo.execute | EXEC_ERROR_LINE_REPORTED);
JsVar *stackTrace = jsvObjectGetChild(execInfo.hiddenRoot, JSPARSE_STACKTRACE_VAR, JSV_STRING_0);
if (stackTrace) {
jsvAppendPrintf(stackTrace, "at ");
jspAppendStackTrace(stackTrace);
jsvUnLock(stackTrace);
}
}
}
if (JSP_SHOULDNT_PARSE) if (JSP_SHOULDNT_PARSE)
break; break;
if (!JSP_SHOULD_EXECUTE) { if (!JSP_SHOULD_EXECUTE) {
@ -2415,7 +2369,7 @@ NO_INLINE JsVar *jspeStatementVar() {
jsvUnLock(scope); jsvUnLock(scope);
#endif #endif
if (!a) { // out of memory if (!a) { // out of memory
jspSetError(false); jspSetError();
return lastDefined; return lastDefined;
} }
} }
@ -2909,7 +2863,7 @@ NO_INLINE JsVar *jspeStatementTry() {
} }
if (shouldExecuteBefore) { if (shouldExecuteBefore) {
// Now clear the exception flag (it's handled - we hope!) // Now clear the exception flag (it's handled - we hope!)
execInfo.execute = execInfo.execute & (JsExecFlags)~(EXEC_EXCEPTION|EXEC_ERROR_LINE_REPORTED); execInfo.execute = execInfo.execute & (JsExecFlags)~EXEC_EXCEPTION;
jsvUnLock(exception); jsvUnLock(exception);
} }
@ -3287,17 +3241,18 @@ JsVar *jspEvaluateExpressionVar(JsVar *str) {
/** Execute code form a variable and return the result. If lineNumberOffset /** Execute code form a variable and return the result. If lineNumberOffset
* is nonzero it's added to the line numbers that get reported for errors/debug */ * is nonzero it's added to the line numbers that get reported for errors/debug */
JsVar *jspEvaluateVar(JsVar *str, JsVar *scope, uint16_t lineNumberOffset) { JsVar *jspEvaluateVar(JsVar *str, JsVar *scope, const char *stackTraceName, uint16_t lineNumberOffset) {
JsLex lex; JsLex lex;
assert(jsvIsString(str)); assert(jsvIsString(str));
JsLex *oldLex = jslSetLex(&lex); JsLex *oldLex = jslSetLex(&lex);
jslInit(str); jslInit(str);
lex.lastLex = oldLex;
lex.functionName = stackTraceName?jsvNewFromString(stackTraceName):0;
#ifndef ESPR_NO_LINE_NUMBERS #ifndef ESPR_NO_LINE_NUMBERS
lex.lineNumberOffset = lineNumberOffset; lex.lineNumberOffset = lineNumberOffset;
#endif #endif
JsExecInfo oldExecInfo = execInfo; JsExecInfo oldExecInfo = execInfo;
execInfo.execute = EXEC_YES; execInfo.execute = EXEC_YES;
if (scope) { if (scope) {
@ -3316,6 +3271,7 @@ JsVar *jspEvaluateVar(JsVar *str, JsVar *scope, uint16_t lineNumberOffset) {
// clean up // clean up
if (scope) jspeiClearScopes(); if (scope) jspeiClearScopes();
jslKill(); jslKill();
jsvUnLock(lex.functionName);
jslSetLex(oldLex); jslSetLex(oldLex);
// restore state and execInfo (keep error flags & ctrl-c) // restore state and execInfo (keep error flags & ctrl-c)
@ -3342,7 +3298,7 @@ JsVar *jspEvaluate(const char *str, bool stringIsStatic) {
JsVar *v = 0; JsVar *v = 0;
if (!jsvIsMemoryFull()) if (!jsvIsMemoryFull())
v = jspEvaluateVar(evCode, 0, 0); v = jspEvaluateVar(evCode, 0, "[raw]", 0);
jsvUnLock(evCode); jsvUnLock(evCode);
return v; return v;
@ -3423,7 +3379,7 @@ JsVar *jspEvaluateModule(JsVar *moduleContents) {
execInfo.blockCount = 0; execInfo.blockCount = 0;
#endif #endif
execInfo.thisVar = scopeExports; // set 'this' variable to exports execInfo.thisVar = scopeExports; // set 'this' variable to exports
jsvUnLock(jspEvaluateVar(moduleContents, scope, 0)); jsvUnLock(jspEvaluateVar(moduleContents, scope, "module", 0));
#ifndef ESPR_NO_LET_SCOPING #ifndef ESPR_NO_LET_SCOPING
assert(execInfo.blockCount==0); assert(execInfo.blockCount==0);
assert(execInfo.blockScope==0); assert(execInfo.blockScope==0);

View File

@ -52,19 +52,21 @@ void jspSetInterrupted(bool interrupt);
/// Has there been an error during parsing /// Has there been an error during parsing
bool jspHasError(); bool jspHasError();
/// Set the error flag - set lineReported if we've already output the line number /// Set the error flag - set lineReported if we've already output the line number
void jspSetError(bool lineReported); void jspSetError();
/// We had an exception (argument is the exception's value) /// We had an exception (argument is the exception's value)
void jspSetException(JsVar *value); void jspSetException(JsVar *value);
/** Return the reported exception if there was one (and clear it). May return undefined even if there was an exception - eg `throw undefined` */ /** Return the reported exception if there was one (and clear it). May return undefined even if there was an exception - eg `throw undefined` */
JsVar *jspGetException(); JsVar *jspGetException();
/** Return a stack trace string if there was one (and clear it) */ /** Return a stack trace string if there was one (and clear it) */
JsVar *jspGetStackTrace(); JsVar *jspGetStackTrace();
/** Append a line marker for the current lex instanec to the given string */
void jspAppendStackTrace(JsVar *stackTrace, JsLex *lex);
/** Evaluate the given variable as an expression (in current scope) */ /** Evaluate the given variable as an expression (in current scope) */
JsVar *jspEvaluateExpressionVar(JsVar *str); JsVar *jspEvaluateExpressionVar(JsVar *str);
/** Execute code form a variable and return the result. If lineNumberOffset /** Execute code form a variable and return the result. If lineNumberOffset
* is nonzero it's added to the line numbers that get reported for errors/debug */ * is nonzero it's added to the line numbers that get reported for errors/debug */
JsVar *jspEvaluateVar(JsVar *str, JsVar *scope, uint16_t lineNumberOffset); JsVar *jspEvaluateVar(JsVar *str, JsVar *scope, const char *stackTraceName, uint16_t lineNumberOffset);
/** Execute code form a string and return the result. /** Execute code form a string and return the result.
* You should only set stringIsStatic if the string will hang around for * You should only set stringIsStatic if the string will hang around for
* the life of the interpreter, as then the interpreter will use a pointer * the life of the interpreter, as then the interpreter will use a pointer
@ -97,7 +99,7 @@ typedef enum {
EXEC_INTERRUPTED = 16, ///< true if execution has been interrupted EXEC_INTERRUPTED = 16, ///< true if execution has been interrupted
EXEC_EXCEPTION = 32, ///< we had an exception, so don't execute until we hit a try/catch block EXEC_EXCEPTION = 32, ///< we had an exception, so don't execute until we hit a try/catch block
EXEC_ERROR = 64, EXEC_ERROR = 64,
EXEC_ERROR_LINE_REPORTED = 128, ///< if an error has been reported, set this so we don't do it too much (EXEC_ERROR will STILL be set) // 128 is free now
EXEC_FOR_INIT = 256, ///< when in for initialiser parsing - hack to avoid getting confused about multiple use for IN EXEC_FOR_INIT = 256, ///< when in for initialiser parsing - hack to avoid getting confused about multiple use for IN
EXEC_IN_LOOP = 512, ///< when in a loop, set this - we can then block break/continue outside it EXEC_IN_LOOP = 512, ///< when in a loop, set this - we can then block break/continue outside it

View File

@ -259,7 +259,7 @@ NO_INLINE void jsExceptionHere(JsExceptionType type, const char *fmt, ...) {
JsVar *var = jsvNewFromEmptyString(); JsVar *var = jsvNewFromEmptyString();
if (!var) { if (!var) {
jspSetError(false); jspSetError();
return; // out of memory return; // out of memory
} }
@ -337,7 +337,7 @@ NO_INLINE void jsExceptionHere_flash(JsExceptionType type, const char *ffmt, ...
JsVar *var = jsvNewFromEmptyString(); JsVar *var = jsvNewFromEmptyString();
if (!var) { if (!var) {
jspSetError(false); jspSetError();
return; // out of memory return; // out of memory
} }

View File

@ -21,6 +21,7 @@
#include "jswrap_object.h" // for jswrap_object_toString #include "jswrap_object.h" // for jswrap_object_toString
#include "jswrap_arraybuffer.h" // for jsvNewTypedArray #include "jswrap_arraybuffer.h" // for jsvNewTypedArray
#include "jswrap_dataview.h" // for jsvNewDataViewWithData #include "jswrap_dataview.h" // for jsvNewDataViewWithData
#include "jswrap_functions.h" // jswrap_console_trace
#if defined(ESPR_JIT) && defined(LINUX) #if defined(ESPR_JIT) && defined(LINUX)
#include <sys/mman.h> #include <sys/mman.h>
#endif #endif
@ -645,6 +646,13 @@ JsVar *jsvNewWithFlags(JsVarFlags flags) {
return jsvNewWithFlags(flags); return jsvNewWithFlags(flags);
#else #else
// On a micro, we're screwed. // On a micro, we're screwed.
#ifndef SAVE_ON_FLASH
if (!(jsErrorFlags & JSERR_MEMORY)) { // try and print a stack trace (if not already out of memory) - we should be able to do this without allocation
jsErrorFlags |= JSERR_MEMORY;
jsiConsolePrint("OUT OF MEMORY");
jswrap_console_trace(NULL);
}
#endif
jsErrorFlags |= JSERR_MEMORY; jsErrorFlags |= JSERR_MEMORY;
jspSetInterrupted(true); jspSetInterrupted(true);
return 0; return 0;

View File

@ -55,6 +55,15 @@ JsVar *_jswrap_error_constructor(JsVar *msg, char *type) {
if (msg) if (msg)
jsvObjectSetChildAndUnLock(d, "message", jsvAsString(msg)); jsvObjectSetChildAndUnLock(d, "message", jsvAsString(msg));
jsvObjectSetChildAndUnLock(d, "type", jsvNewFromString(type)); jsvObjectSetChildAndUnLock(d, "type", jsvNewFromString(type));
// add stack trace
if (lex) {
JsVar *stackTrace = jsvNewFromEmptyString();
if (stackTrace) { // memory?
jspAppendStackTrace(stackTrace, lex);
jsvObjectSetChildAndUnLock(d, "stack", stackTrace);
}
}
return d; return d;
} }

View File

@ -163,7 +163,7 @@ Evaluate a string containing JavaScript code
JsVar *jswrap_eval(JsVar *v) { JsVar *jswrap_eval(JsVar *v) {
if (!v) return 0; if (!v) return 0;
JsVar *s = jsvAsString(v); // get as a string JsVar *s = jsvAsString(v); // get as a string
JsVar *result = jspEvaluateVar(s, 0, 0); // don't set scope, so we use the current scope JsVar *result = jspEvaluateVar(s, 0, "eval", 0); // don't set scope, so we use the current scope
jsvUnLock(s); jsvUnLock(s);
return result; return result;
} }
@ -660,4 +660,20 @@ Implemented in Espruino as an alias of `console.log`
] ]
} }
Implemented in Espruino as an alias of `console.log` Implemented in Espruino as an alias of `console.log`
*/ */
/*JSON{
"type" : "staticmethod",
"class" : "console",
"name" : "trace",
"ifndef" : "SAVE_ON_FLASH",
"generate" : "jswrap_console_trace",
"params" : [
["text","JsVarArray","One or more arguments to print"]
]
}
*/
void jswrap_console_trace(JsVar *v) {
if (v) jswrap_print(v);
jslPrintStackTrace(vcbprintf_callback_jsiConsolePrintString, NULL, lex);
}

View File

@ -32,6 +32,7 @@ JsVar *jswrap_decodeURIComponent(JsVar *arg);
void jswrap_trace(JsVar *root); void jswrap_trace(JsVar *root);
void jswrap_print(JsVar *v); void jswrap_print(JsVar *v);
void jswrap_console_trace(JsVar *v);
#endif // JSWRAP_FUNCTIONS_H_ #endif // JSWRAP_FUNCTIONS_H_