Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stack printing: use JS stacktrace instead of building one by hand. #2476

Open
denis-migdal opened this issue Jul 17, 2024 · 5 comments
Open

Comments

@denis-migdal
Copy link
Contributor

Hi,

In the past, Brython had some issues related to stack printing, often due to JS errors not properly handled by Brython.

I think that using JS stack and to "convert it", would be more reliable and easier.

1. Get the JS stack:

You can get a stack with :

new Error().stack; // get the stack at this line
//or
try { ... } catch(e) { e.stack } // get the stack of a caught exception.
// or
window.addEventListener("error", e => { // this should catch all uncaught exceptions.
     e.error.stack // to verify.
});

2. Identifies the Brython script in the stack trace:

A stacktrace line corresponding to Brython code, should looks something like that :

anonymous@http://localhost:5500/tools/Editor/index.html line 250 > Function:41:10

The part Function:41:10 identifies a position in the generated JS code (line and column).
You may need to offset the line and column (in my case, I use line-2, and col-1).

Identifying the Python script is a little harder as different python code could produce the exact same stacktrace and I wonder how it'll behave with python imports.

It seems that building a Function inside a Function:

new Function('(new Function("console.log(new Error().stack)"))();')();

is reflected in the stacktrace:

... > Function line 3 > Function:3:13

Maybe there is a way to add lines as a way to identifies the python script in order to get something like ?

... > Function line 3 > Function:3:13 // first python script
... > Function line 4 > Function:3:13 // second python script
... > Function line 5 > Function:3:13 // third python script

This is kind of a dirty h4ck, but I do not see how to do otherwise.
Maybe putting the generated JS code into a <script> tag to execute it would be reflected in the stacktrace and would be cleaner ?

3. Translate JS code position to Python code position:

There are several ways of doing so:

  1. look at the generated JS code string to get the brython position array "[0,0,0]".
  2. -OR- when generating the JS code, put the JS code position into the AST, then translation only requires to look up the AST.
  3. -OR- if you do not keep the AST but keep the Python code : regenerate the AST from the Python code (we generally don't care if stacktrace is a long operation).
  4. -OR- if you want to keep either the AST nor the Python code : generate a kind of source map included either with the generated JS code or in a different file (e.g. X.sourcemap).

With the 2 to 4 ways, I think we could remove all brython position array "[0,0,0]" in the generated JS code.
Maybe, when trace is disabled, some functions call could also be removed.

The 4th solution is, I think, the best one.

@denis-migdal
Copy link
Contributor Author

I created a stackoverflow question to see if there is a better way to identify the script.
https://stackoverflow.com/questions/78758974/stacktraces-how-to-identify-the-script-when-using-dynamic-scripts

@denis-migdal
Copy link
Contributor Author

One issue is that we won't have access to __traceback__.tb_frame.f_local with a JS->Python stack conversion.

I'm not familiar enough with all of Python internals.
But maybe there is a way to have a mix-strategy ?

The JS->Python stack conversion, in association of window.addEventListener("error") could be used to catch exceptions not properly handled by Brython (a "just in case").
Indeed, in such case I'd assume it is hard to get the correct virtual stack (as we can have many due to asynchronous code).

Building a python Exception could be done using :

  • JS->Python stack conversion to ensure no lines were missed (I'd assume it is safer than using the virtual stack).
  • building the virtual stack by adding only (?) the locals+function when entering a function ? as we'd no longer need to update its lineno value at each lines ?
  • checking the virtual stack against the converted stack and raise a warning if a discrepancy is found ?

Alternatively, __traceback__.tb_frame.f_local could also be provided only when the taceback option is enabled, but don't think this would be a good idea ?

@PierreQuentel
Copy link
Contributor

Hi David,

I don't think using the JS stack trace to generate the complete Python error message is possible. Besides the (difficult) issues you raise, you have to take into account that not all browsers generate the same JS trace. With this JS code

function f(){
  a
}
f()

Firefox gives this stack

Uncaught ReferenceError: a is not defined
    f http://localhost:8000/tests/test.html:78
    <anonymous> http://localhost:8000/tests/test.html:80

while Chrome and Edge give

test.html:78 Uncaught ReferenceError: a is not defined
    at f (test.html:78:3)
    at test.html:80:1

So you would have to parse the JS stack with different options... and you can't be sure that a browser won't change the format without notice, this is not standardized.

@denis-migdal
Copy link
Contributor Author

Hum, I understand.

Though, by looking around a bit, I found a really interesting thing:
https://firefox-source-docs.mozilla.org/devtools-user/debugger/how_to/debug_eval_sources/index.html
https://devtoolstips.org/tips/en/name-evaluated-files/

I'll have to test it tomorrow, but it could be interesting to add it to the generated JS, to maybe improve debugging on the generated JS ?

Also, for reference, V8 offers a way to format the stacks, cf https://v8.dev/docs/stack-trace-api . Unfortunately, Firefox doesn't use V8, but MonkeySpider.

@denis-migdal
Copy link
Contributor Author

Yep, this works quite well (Firefox and Chrome) :

new Function(`//# sourceURL=test.js
throw new Error()`)()

E.g. in FF:

Uncaught Error: 
    anonymous test.js:4
    <anonymous> debugger eval code:2

Instead of :

Uncaught Error: 
    anonymous debugger eval code line 1 > Function:3
    <anonymous> debugger eval code:1

If click on it on Chrome we can also see the line in the JS code.

I'm also wondering if, technically, we could even use the source map directive to show instead the Python code in the Browser. I won't test it but it'll be funny if it were the case xD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants