Tracing the source line of an error in a Javascript eval
Asked Answered
H

5

23

I'm building something that includes javascripts on the fly asynchronously, which works, but I'm looking to improve upon the error detection (so all the errors don't just appear to come from some line near the AJAX call that pulls them down.

If I'm using eval to evaluate a multiline javascript file, is there any way to trace which line an error occurs on?

By keeping references to the variables I need when including, I have no problem determining which file the errors occurs in. My problem is determining which line the error occurs in.

Example:

try {
  eval("var valid_statement = 7; \n invalid_statement())))");
} catch(e) {
  var err = new Error();
  err.message = 'Error in Evald Script: ' + e.message;
  err.lineNumber = ???
  throw err;
}

How can I tell that the error occurred in the second line there? Specifically I'm interested in doing this in Firefox.

I know that error objects have e.stack in Mozilla browsers, but the output doesn't seem to take into account newlines properly.

Hedveh answered 19/8, 2010 at 22:50 Comment(2)
Which "second line" you want? Both the eval and the syntax error in evaled string are on the 2nd line.Cytolysin
In the context of the code displayed above, yes. In the context of the code encapsulated within the string, no. I want that newline parsed as it would be in a file so I can report what line errors happen on within the eval'd string.Hedveh
C
7
  • The line number in an evaled script starts from the one the eval is on.
  • An error object has a line number of the line it was created on.

So something like...

try {
  eval('var valid_statement = 7; \n invalid_statement())))');
} catch(e) {
  var err = e.constructor('Error in Evaled Script: ' + e.message);
  // +3 because `err` has the line number of the `eval` line plus two.
  err.lineNumber = e.lineNumber - err.lineNumber + 3;
  throw err;
}
Cytolysin answered 21/8, 2010 at 4:39 Comment(5)
Beware that e.lineNumber is a non-standard JavaScript feature.Crescint
"Specifically I'm interested in doing this in Firefox"Cytolysin
Perfect! That did exactly what I wanted. It's kind of unfortunate that it relies on hardcoded differences between line numbers, but at least it works. The major problem with that is that it won't work if I run a minifier on the script. Regardless, thank you very much.Hedveh
Which browsers is this supposed to work in? Chrome doesn't have e.lineNumber and Firefox 18 doesn't increment the lineNumber for the eval contents- they all point to the eval call.Krishna
The only supported browser that provides lineNumber is Firefox. See developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Degraw
G
6

The global error event-listener will catch the exception from eval and shows the correct line numbers (maybe not in all browsers):

window.addEventListener('error', function(e) {
    console.log(e.message
        , '\n', e.filename, ':', e.lineno, (e.colno ? ':' + e.colno : '')
        , e.error && e.error.stack ? '\n' : '', e.error ? e.error.stack : undefined
    );
}, false);
Gujranwala answered 14/11, 2014 at 11:47 Comment(1)
Is this a question on an answer?Crux
O
2

I don't think you can do it with eval reliably. However, you can do this instead of eval:

try {
    $("<script />").html(scriptSource).appendTo("head").remove();
} catch (e) {
    alert(e.lineNumber);
}
Overburdensome answered 26/7, 2013 at 8:21 Comment(2)
translation to non jquery please?Lubra
if it's just creating a script tag, and setting the source of that tag to your code, this will apparently not work - errors in the script are not being caught.Lubra
B
0

Join the window.addEventListener('error', ...) with document.createElement('script') works for me:

window.addEventListener('error', function(e) {
  console.log('Error line:', e.lineno)
}, false);  

function runCode (code) {
  let js = document.createElement('script')
  try {
    js.innerHTML = code
    document.head.appendChild(js)
  }
  catch(e) {
    // ...
  }
  document.head.removeChild(js)
}

runCode('// try this:\n1ss') // prints 'Error line: 2'

Thanks to @frederic-leitenberger and @fromin for the solutions parts.

Brenneman answered 16/8, 2022 at 23:17 Comment(0)
M
0

The annoying fact is that only the global error handler will get the detailed script parsing errors when you use eval() or new Function(). If the error is caught by any exception handler, then there is no way to recover the detailed information, even if the handler re-throws it. So, you have to somehow arrange to execute your eval outside the scope of any of your exception handlers.

On Firefox, I had to diddle config settings so it would execute scripts in a local HTML file. Firefox also disagreed slightly on the column number, but that's likely just the vagaries of parser lookahead. Other than that, the outputs were essentially identical. I use the more general "new Function" rather than directly calling eval().

I tested this script with the latest (05/26/2023) versions of Firefox/Chrome/Edge:

<html>
<head>
</head>
<body>BODY</body>
<script>
 function errorHandler(e){
     const {lineno,colno} = event;
     let line = script1.split('\n')[lineno-3];
     console.log(line);
     let marker = "-".repeat(colno-1)+"^";
     console.log(marker);
     console.log(event.message);
     return true; // allow execution to continue
 }
 window.addEventListener('error', errorHandler, true);
 let script1 = "let x = 5;\nx=.x;\n";
 let script2 = "let y = 5;\ny=.y;\n";
 let func;
 func    = new Function(script1);
 try {
     func    = new Function(script2);
 }catch(e){
     console.log(e);
 }
</script>

The outputs looked like this: enter image description here

As you can see, the global error handler gets an event with a rich enough set of properties that it can display detailed information (including the text of the relevant line, if it has access to the script text). The exception handler only gets the less helpful pre-canned message.

What about copying the script to a 'script' node and temporarily inserting it into the document head? You don't get the control you get by using 'new Function'. For example, I want to treat my loadable scripts much like node modules. By using 'Function', I can both pass in information to the script (such as a 'require' function it can call to give controlled access to other information) and get information back (the last expression of the script is the return value of executing the newly created function).

Of course, you can hack around these limitations with global variables and it's fairly impossible to truly sandbox an eval()'ed script, but with such infinite scopes in JavaScript, I prefer to control the scope enough that the eval()'ed script is extremely unlikely to accidentally tread on anything else.

Edit 05/27/23: Forgot to actually test the insert-script-node trick. On all three browsers, it does not provide detailed error info. So, AFAICT, the only way to get detailed error info from a dynamic script is to use eval() or new Function, and make sure the error gets to the global error handler without being caught with an exception handler.

Michaelson answered 26/5, 2023 at 21:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.