Skip to content

Commit 1592390

Browse files
committed
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)
1 parent c1eb694 commit 1592390

14 files changed

+137
-103
lines changed

.gdbinit

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ break jsAssertFail
22
break jsError
33
define jsvTrace
44
print jsvTrace($arg0, 0)
5-
end
5+
end
66
define whereami
7-
print jslPrintPosition(jsiConsolePrintString, 0, lex->tokenLastStart)
8-
print jslPrintTokenLineMarker(jsiConsolePrintString, 0, lex->tokenLastStart, 0)
7+
print jslPrintPosition(jsiConsolePrintString, 0, lex, lex->tokenLastStart)
8+
print jslPrintTokenLineMarker(jsiConsolePrintString, 0, lex, lex->tokenLastStart, 0)
99
end
1010
define typeof
1111
if (($arg0)->flags&JSV_VARTYPEMASK)>=JSV_NAME_STRING_0 && (($arg0)->flags&JSV_VARTYPEMASK)<=JSV_NAME_STRING_MAX
@@ -25,7 +25,7 @@ end
2525
define asm
2626
set disassemble-next-line on
2727
show disassemble-next-line
28-
echo now use stepi
28+
echo now use stepi
2929
end
3030
# Watchdog timer off for NRF52 devices
3131
define wdt_off
@@ -56,9 +56,6 @@ define execflags
5656
if execInfo.execute&EXEC_ERROR
5757
printf "EXEC_ERROR\n"
5858
end
59-
if execInfo.execute&EXEC_ERROR_LINE_REPORTED
60-
printf "EXEC_ERROR_LINE_REPORTED\n"
61-
end
6259
if execInfo.execute&EXEC_FOR_INIT
6360
printf "EXEC_FOR_INIT\n"
6461
end
@@ -82,7 +79,7 @@ end
8279

8380
define hook-run
8481
set $primask=0
85-
end
82+
end
8683

8784
define hook-continue
8885
set $primask=0

ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
Bangle.js: g.findFont now attempts to use Intl:2 if there's room. Also fix memory leak
66
nRF52840: Add E.getVDDH() method to get VDDH voltage
77
Fix issue handling multi-line tempated strings pasted to REPL (2v26 regression)
8+
Add a backtrace command to debugger, add console.trace command, set Error.stack in constructor (fix #2490)
9+
Error.stack/stack dump now uses more standard file:line:col format
10+
Out of memory errors now show a backtrace (previously it was very hard to track these down)
811

912
2v26 : nRF5x: ensure TIMER1_IRQHandler doesn't always wake idle loop up (fix #1900)
1013
Puck.js: On v2.1 ensure Puck.mag behaves like other variants - just returning the last reading (avoids glitches when used with Puck.magOn)

src/jsflash.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,8 +1414,10 @@ bool jsfLoadBootCodeFromFlash(bool isReset) {
14141414
#endif
14151415
if (jsiStatus & JSIS_FIRST_BOOT) {
14161416
JsVar *code = jsfReadFile(jsfNameFromString(".bootPowerOn"),0,0);
1417-
if (code)
1418-
jsvUnLock2(jspEvaluateVar(code,0,0), code);
1417+
if (code) {
1418+
jsvUnLock2(jspEvaluateVar(code,0,".bootPowerOn",0), code);
1419+
jsiCheckErrors();
1420+
}
14191421
}
14201422
#endif
14211423
// 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) {
14341436
for (int i=0;i<4;i++) {
14351437
filename[5] = (char)('0'+i);
14361438
JsVar *code = jsfReadFile(jsfNameFromString(filename),0,0);
1437-
if (code)
1438-
jsvUnLock2(jspEvaluateVar(code,0,0), code);
1439+
if (code) {
1440+
jsvUnLock2(jspEvaluateVar(code,0,filename,0), code);
1441+
jsiCheckErrors();
1442+
}
14391443
}
14401444
}
14411445
// Load normal boot code
14421446
JsVar *code = jsfGetBootCodeFromFlash(isReset);
14431447
if (!code) return false;
1444-
jsvUnLock2(jspEvaluateVar(code,0,0), code);
1448+
jsvUnLock2(jspEvaluateVar(code,0,"boot code",0), code);
1449+
jsiCheckErrors();
14451450
return true;
14461451
}
14471452

src/jsinteractive.c

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ JsErrorFlags lastJsErrorFlags = 0; ///< Compare with jsErrorFlags in order to re
137137
#ifdef USE_DEBUGGER
138138
void jsiDebuggerLine(JsVar *line);
139139
#endif
140-
void jsiCheckErrors();
141140

142141
static void jsiPacketFileEnd();
143142
static void jsiPacketExit();
@@ -535,11 +534,13 @@ void jsiSoftInit(bool hasBeenReset) {
535534

536535
// Run 'boot code' - textual JS in flash
537536
jsfLoadBootCodeFromFlash(hasBeenReset);
537+
// jsiCheckErrors is performed internally
538538

539539
// Now run initialisation code
540540
JsVar *initCode = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_INIT_CODE_NAME);
541541
if (initCode) {
542-
jsvUnLock2(jspEvaluateVar(initCode, 0, 0), initCode);
542+
jsvUnLock2(jspEvaluateVar(initCode, 0, "initcode", 0), initCode);
543+
jsiCheckErrors();
543544
jsvObjectRemoveChild(execInfo.hiddenRoot, JSI_INIT_CODE_NAME);
544545
}
545546

@@ -565,11 +566,13 @@ void jsiSoftInit(bool hasBeenReset) {
565566

566567
// Execute `init` events on `E`
567568
jsiExecuteEventCallbackOn("E", INIT_CALLBACK_NAME, 0, 0);
569+
jsiCheckErrors();
568570
// Execute the `onInit` function
569571
JsVar *onInit = jsvObjectGetChildIfExists(execInfo.root, JSI_ONINIT_NAME);
570572
if (onInit) {
571573
if (jsiEcho()) jsiConsolePrint("Running onInit()...\n");
572574
jsiExecuteEventCallback(0, onInit, 0, 0);
575+
jsiCheckErrors();
573576
jsvUnLock(onInit);
574577
}
575578
}
@@ -1528,7 +1531,7 @@ void jsiHandleNewLine(bool execute) {
15281531
#endif
15291532
{
15301533
// execute!
1531-
JsVar *v = jspEvaluateVar(lineToExecute, 0, jsiLineNumberOffset);
1534+
JsVar *v = jspEvaluateVar(lineToExecute, 0, "REPL", jsiLineNumberOffset);
15321535
// add input line to history
15331536
bool isEmpty = jsvIsEmptyString(lineToExecute);
15341537
// 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
20402043
} else if (jsvIsFunction(callbackNoNames)) {
20412044
jsvUnLock(jspExecuteFunction(callbackNoNames, thisVar, (int)argCount, argPtr));
20422045
} else if (jsvIsString(callbackNoNames)) {
2043-
jsvUnLock(jspEvaluateVar(callbackNoNames, 0, 0));
2046+
jsvUnLock(jspEvaluateVar(callbackNoNames, 0, "event", 0));
20442047
} else
20452048
jsError("Unknown type of callback in Event Queue");
20462049
return ok;
@@ -2146,7 +2149,7 @@ void jsiCtrlC() {
21462149
/** Take an event for a UART and handle the characters we're getting, potentially
21472150
* grabbing more characters as well if it's easy. If more character events are
21482151
* grabbed, the number of extra events (not characters) is returned */
2149-
int jsiHandleIOEventForSerial(JsVar *usartClass, IOEventFlags eventFlags, uint8_t *data, int length) {
2152+
int jsiHandleIOEventForSerial(JsVar *usartClass, IOEventFlags eventFlags, uint8_t *data, unsigned int length) {
21502153
int eventsHandled = length+2;
21512154
JsVar *stringData = length ? jsvNewStringOfLength(length, (char*)data) : NULL;
21522155
if (stringData) {
@@ -2178,7 +2181,7 @@ void jsiIdle() {
21782181
bool wasBusy = false;
21792182
IOEventFlags eventFlags;
21802183
uint8_t eventData[IOEVENT_MAX_LEN];
2181-
int eventLen;
2184+
unsigned int eventLen;
21822185
// ensure we can't get totally swamped by having more events than we can process.
21832186
// Just process what was in the event queue at the start
21842187
int maxEvents = jshGetEventsUsed();
@@ -2564,8 +2567,8 @@ void jsiIdle() {
25642567
jsiSemiInit(false, &filename); // don't autoload code
25652568
// load the code we specified
25662569
JsVar *code = jsfReadFile(filename,0,0);
2567-
if (code)
2568-
jsvUnLock2(jspEvaluateVar(code,0,0), code);
2570+
if (code) // only supply the filename if we're sure it's zero terminated
2571+
jsvUnLock2(jspEvaluateVar(code,0,filename.c[sizeof(filename.c)-1] ? filename.c : "load",0), code);
25692572
} else {
25702573
jsiSoftKill();
25712574
jspSoftKill();
@@ -2805,14 +2808,14 @@ void jsiDebuggerLoop() {
28052808
itostr((JsVarInt)jslGetLineNumber() + (JsVarInt)lex->lineNumberOffset - 1, lineStr, 10);
28062809
} else
28072810
#endif
2808-
{
2811+
{ // FIXME: Maybe if executing from a Storage file we use line numbers within that file?
28092812
lineStr[0]=0;
28102813
}
28112814
size_t lineLen = strlen(lineStr);
28122815
while (lineLen < sizeof(lineStr)-1) lineStr[lineLen++]=' ';
28132816
lineStr[lineLen] = 0;
28142817
// print the line of code, prefixed by the line number, and with a pointer to the exact character in question
2815-
jslPrintTokenLineMarker(vcbprintf_callback_jsiConsolePrintString, 0, lex->tokenLastStart, lineStr);
2818+
jslPrintTokenLineMarker(vcbprintf_callback_jsiConsolePrintString, 0, lex, lex->tokenLastStart, lineStr);
28162819
}
28172820

28182821
while (!(jsiStatus & JSIS_EXIT_DEBUGGER) &&
@@ -2822,7 +2825,7 @@ void jsiDebuggerLoop() {
28222825
jshIdle();
28232826
// If we have too many events (> half full) drain the queue
28242827
uint8_t eventData[IOEVENT_MAX_LEN];
2825-
int eventLen;
2828+
unsigned int eventLen;
28262829
while (jshGetEventsUsed()>IOBUFFERMASK*1/2 &&
28272830
!(jsiStatus & JSIS_EXIT_DEBUGGER) &&
28282831
!(execInfo.execute & EXEC_CTRL_C_MASK)) {
@@ -2906,7 +2909,8 @@ void jsiDebuggerLine(JsVar *line) {
29062909
"finish / f - finish execution of the function call\n"
29072910
"print ... / p ... - evaluate and print the next argument\n"
29082911
"info locals / i l) - output local variables\n"
2909-
"info scopechain / i s - output all variables in all scopes\n");
2912+
"info scopechain / i s - output all variables in all scopes\n"
2913+
"bt - print backtrace\n");
29102914
} else if (!strcmp(id,"quit") || !strcmp(id,"q")) {
29112915
jsiStatus |= JSIS_EXIT_DEBUGGER;
29122916
execInfo.execute |= EXEC_INTERRUPTED;
@@ -2962,6 +2966,8 @@ void jsiDebuggerLine(JsVar *line) {
29622966
} else {
29632967
jsiConsolePrint("Unknown command\n");
29642968
}
2969+
} else if (!strcmp(id,"bt")) {
2970+
jslPrintStackTrace(vcbprintf_callback_jsiConsolePrintString, NULL, oldLex);
29652971
} else
29662972
handled = false;
29672973
}

src/jsinteractive.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ bool jsiExecuteEventCallbackName(JsVar *obj, const char *cbName, unsigned int ar
6363
/// Utility version of jsiExecuteEventCallback for calling events on global variables
6464
bool jsiExecuteEventCallbackOn(const char *objectName, const char *cbName, unsigned int argCount, JsVar **argPtr);
6565

66+
/// Check for and report/handle interpreter errors (can be called after executing JS code)
67+
void jsiCheckErrors();
68+
6669
/// Create a timeout in JS to execute the given native function (outside of an IRQ). Returns the index
6770
JsVar *jsiSetTimeout(void (*functionPtr)(void), JsVarFloat milliseconds);
6871
/// Clear a timeout in JS given the index returned by jsiSetTimeout

src/jslex.c

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,8 @@ void jslInit(JsVar *var) {
948948
#ifndef ESPR_NO_LINE_NUMBERS
949949
lex->lineNumberOffset = 0;
950950
#endif
951+
lex->functionName = NULL;
952+
lex->lastLex = NULL;
951953
// set up iterator
952954
jsvStringIteratorNew(&lex->it, lex->sourceVar, 0);
953955
jsvUnLock(lex->it.var); // see jslGetNextCh
@@ -1502,7 +1504,7 @@ void jslPrintTokenisedString(JsVar *code, vcbprintf_callback user_callback, void
15021504
jsvStringIteratorFree(&it);
15031505
}
15041506

1505-
void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t tokenPos) {
1507+
void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos) {
15061508
size_t line,col;
15071509
#if !defined(SAVE_ON_FLASH) && !defined(ESPR_EMBED)
15081510
if (jsvIsNativeString(lex->sourceVar) || jsvIsFlashString(lex->sourceVar)) {
@@ -1513,7 +1515,7 @@ void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t
15131515
JsVar *fileStr = jsvAddressToVar(fileAddr, jsfGetFileSize(&header));
15141516
jsvGetLineAndCol(fileStr, tokenPos + stringAddr - fileAddr, &line, &col);
15151517
JsVar *name = jsfVarFromName(header.name);
1516-
cbprintf(user_callback, user_data,"line %d col %d in %v\n", line, col, name);
1518+
cbprintf(user_callback, user_data,"%v:%d:%d", name, line, col);
15171519
jsvUnLock2(fileStr,name);
15181520
return;
15191521
}
@@ -1524,10 +1526,10 @@ void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t
15241526
if (lex->lineNumberOffset)
15251527
line += (size_t)lex->lineNumberOffset - 1;
15261528
#endif
1527-
cbprintf(user_callback, user_data, "line %d col %d\n", line, col);
1529+
cbprintf(user_callback, user_data, ":%d:%d", line, col);
15281530
}
15291531

1530-
void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, size_t tokenPos, char *prefix) {
1532+
void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos, char *prefix) {
15311533
size_t line = 1,col = 1;
15321534
jsvGetLineAndCol(lex->sourceVar, tokenPos, &line, &col);
15331535
size_t startOfLine = jsvGetIndexFromLineAndCol(lex->sourceVar, line, 1);
@@ -1568,3 +1570,21 @@ void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data,
15681570
user_callback("^\n", user_data);
15691571
}
15701572

1573+
void jslPrintStackTrace(vcbprintf_callback user_callback, void *user_data, JsLex *lex) {
1574+
while (lex) {
1575+
user_callback(" at ", user_data);
1576+
if (lex->functionName) {
1577+
// can't use cbprintf here as it may try and allocate a var
1578+
// and we want to be able to use this when we're out of memory
1579+
char functionName[JSLEX_MAX_TOKEN_LENGTH];
1580+
jsvGetString(lex->functionName, functionName, sizeof(functionName));
1581+
user_callback(functionName, user_data);
1582+
user_callback(" (", user_data);
1583+
}
1584+
jslPrintPosition(user_callback, user_data, lex, lex->tokenLastStart);
1585+
user_callback(lex->functionName ? ")\n":"\n", user_data);
1586+
jslPrintTokenLineMarker(user_callback, user_data, lex, lex->tokenLastStart, 0);
1587+
1588+
lex = lex->lastLex; // go down to next lexer in list
1589+
}
1590+
}

src/jslex.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ typedef struct JsLex
174174
*/
175175
JsVar *sourceVar; // the actual string var
176176
JsvStringIterator it; // Iterator for the string
177+
178+
/// For stack traces - this is just a pointer to a function name if we have one (it's not 'owned')
179+
JsVar *functionName;
180+
/// For stack traces - this is just a pointer to the previous Lex on the stack
181+
struct JsLex *lastLex;
177182
} JsLex;
178183

179184
// The lexer
@@ -224,10 +229,13 @@ bool jslNeedSpaceBetween(unsigned char lastch, unsigned char ch);
224229
void jslPrintTokenisedString(JsVar *code, vcbprintf_callback user_callback, void *user_data);
225230

226231
/// Print position in the form 'line X col Y'
227-
void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, size_t tokenPos);
232+
void jslPrintPosition(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos);
228233

229234
/** Print the line of source code at `tokenPos`, prefixed with the string 'prefix' (0=no string).
230235
* Then, underneath it, print a '^' marker at the column tokenPos was at */
231-
void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, size_t tokenPos, char *prefix);
236+
void jslPrintTokenLineMarker(vcbprintf_callback user_callback, void *user_data, JsLex *lex, size_t tokenPos, char *prefix);
237+
238+
/** Prints a full stack trace to the current callback function */
239+
void jslPrintStackTrace(vcbprintf_callback user_callback, void *user_data, JsLex *lex);
232240

233241
#endif /* JSLEX_H_ */

0 commit comments

Comments
 (0)