In my application I parse some user input and then run it as Javascipt code using (new Function(...))(). If the input is incorrect, this throws an exception. What I need is a way to get the line number where the exception happened in the parsed string that had been provided to new Function(). Is it possible?
For this we need to write a logic to capture the stacktrace from the error object and find out where exactly the anonymous function has indicated the error has been thrown.
The line number where the error is thrown in Chrome is indicated as <anonymous>:5:17, where as in Firefox it is Function:5:17
try{
(new Function(`var hello = 10;
const world = 20;
let foo = 'bar';
xyz; //simulating error here
`))();
}catch(err){
let line = err.stack.split("\n").find(e => e.includes("<anonymous>:") || e.includes("Function:"));
let lineIndex = (line.includes("<anonymous>:") && line.indexOf("<anonymous>:") + "<anonymous>:".length) || (line.includes("Function:") && line.indexOf("Function:") + "Function:".length);
console.log(+line.substring(lineIndex, lineIndex + 1) - 2);
}
Related
I want know on executing code if some error is occured between try then need to know the linenumer in that function.
I have tried Tracer It gives me Function name,stack trace but line number given by it is wrong because it gives me line number from where trace logger was called not from where errro occured.
I have tried below code which can give me linenumber
debugLine(error:Error) {
if (error.stack!=undefined) {
let frame = error.stack.split("\n")[1];
let lineNumber = frame.split(":")[1];
let functionName = frame.split(" ")[5];
return functionName + ":" + lineNumber + " " + error.message;
}
}
But if stack trace is complex then it does not guarantee the correct linenumber. So which is the accurate and fast way to get the linenumber ?
I am creating a Javascript logger, in which, on error messages, I am also adding the stack trace like this:
function logMessage(logMessage)
{
let stackTrace = new Error().stack;
logMessage.stackTrace = stackTrace;
...
}
This gives me the stack trace, but it also obviously adds the method of logMessage itself as the last item on the stack...
How can I remove the last trace so I'll only see the trace up until the point that the logMessage was called, but without the logMessage itself?
The way to do it is really simple since the stack we are getting is a string divided by \n, in this format:
ERROR \n
at ... \n
at ... \n
so all we need to do is:
let stackTrace = new Error().stack; //get the stack trace string
let arr = stackTrace.split("\n"); //create an array with all lines
arr.splice(1,1); //remove the second line (first line after "ERROR")
stackTrace = arr.join("\n"); //join array back to a string
Stack trace returns a multiline string starting with the error message and then all lines starting with at {function name/position};
You can simply change the multiline string and bypassing the first occurence of at {function name/position} with split, filter and join
stackTrace.split('\n').filter(function(line, index) { return index !== 1 }).join('\n');
See snippet example
function deepFunctionStack() {
return new Error().stack;
}
function layer1Function() {
return deepFunctionStack();
}
function topLayerFunction() {
return layer1Function();
}
var originalStack = topLayerFunction();
var formattedStack = originalStack.split('\n').filter(function(line, index) { return index !== 1 }).join('\n');
document.write('Original Stack:');
document.write('<pre>' + originalStack + '</pre>');
document.write('Formatted Stack:');
document.write('<pre>' + formattedStack + '</pre>');
Here is an additional version which handles the differences between Firefox, Chrome, & IE stack traces. It will remove the initial "ERROR" line from Chrome and IE. It is also significantly faster than other versions listed here.
// stack: string - call stack string
// levels: int - number of levels to remove from the top of the call stack
function trimCallStack(stack, levels) {
if (stack) {
const newLineChar = "\n"; // Each line deliminated by '\n'
const isFirefoxCallStack = stack.indexOf("#") > -1; // If stacktrace contains '#' it is FireFox
// remove an additional line if browser is NOT FireFox (i.e. Chrome or IE) since those start stack trace with the error name
// remove N additional lines specified by `levels`
let iterations = (isFirefoxCallStack ? 0 : 1) + (levels ?? 0);
let start = 0;
while(iterations-- && start !== -1) {
start = stack.indexOf(newLineChar, start + 1);
}
stack = start !== -1 ? stack.substring(start + 1) : ""; // start === -1 if removing more lines than exist, so return "" in that case
}
return stack || "";
}
Examples of stack traces from FireFox and Chrome/IE:
Chrome/IE stacktrace:
Error: fail
at test (<anonymous>:2:8)
at test1 (<anonymous>:5:5)
at test2 (<anonymous>:8:5)
at test3 (<anonymous>:11:5)
at <anonymous>:1:5
Firefox stacktrace:
test#debugger eval code:2:8
test1#debugger eval code:5:5
test2#debugger eval code:8:5
test3#debugger eval code:11:5
#debugger eval code:1:5
After using the provided function:
Chrome/IE stacktrace:
at test (<anonymous>:2:8)
at test1 (<anonymous>:5:5)
at test2 (<anonymous>:8:5)
at test3 (<anonymous>:11:5)
at <anonymous>:1:5
Firefox stacktrace:
test#debugger eval code:2:8
test1#debugger eval code:5:5
test2#debugger eval code:8:5
test3#debugger eval code:11:5
#debugger eval code:1:5
Just built a grammar on latest antlr, compiled to java and tested, works fine.
Compiled to javascript and attempted to test it, but I get an error on line 111 of Lexer.js indicating that var tokenStartMarker = this._input.mark(); <== .mark() is not a function.
Here's my javascript code to load and parse the grammar:
var antlr4 = require('antlr4/index');
var BQLXLexer = require('grammar/BQLXLexer').BQLXLexer;
var BQLXParser = require('grammar/BQLXParser').BQLXParser;
function validatePipeline(script) {
var chars = antlr4.InputStream(script);
var lexer = new BQLXLexer(chars);
var tokens = new antlr4.CommonTokenStream(lexer);
var parser = new BQLXParser(tokens);
parser.buildParseTrees = true;
var ast = parser.pipeline();
console.log(ast);
}
the line var ast = parser.pipeline(); is what eventually calls into the Lexer and produces the error in the runtime.
I have tested on both 4.7.2 and 4.7.1 version of the javascript runtime, and both produce the same error on the same line, 111 of Lexer.js.
Not sure what else to try here...
Ok, after the 5th time reviewing my code, I realised that I forgot a new statement on this line: var chars = antlr4.InputStream(script);.
The line should be revised to read var chars = new antlr4.InputStream(script);, which then resolves the error.
Can anyone explain to me why I get the error message Uncaught ReferenceError: Invalid left-hand side in assignment when I run the below function.
function number(a){
var last = parseInt(stream.charAt(stream.length-1));
if(stream === ''){
stream = a;
}
else if(isNumber(last)){
console.log(last);
stream.charAt(stream.length-1) = last*10 + a;
}
else{
stream += ' '+a;
}
document.getElementById('display').innerHTML = stream;
}
The error is in this line:
stream.charAt(stream.length-1) = last*10 + a;
You can't assign something to stream.charAt(). That function only returns a character.
From what I can gather, you're getting the last character from the stream. If it's a integer, you multiply it by 10, then append a to the stream.
Instead of that, this will give the same result:
stream += '0' + a;
Since you're adding the value back into the array, it really doesn't matter if you multiply a single digit integer with 10, or if you just add a "0" after it.
Your problem seems to be this codepart
stream.charAt(stream.length-1) = last*10 + a;
charAt returns a string, and not a position in your stream (i assume that your stream is a string), so you cant overwrite it.
To solve this you could do something like:
stream = stream.substring(0,stream.length-1)+last*10+a
Im not allowed to comment but Cerbrus answer wont work for last = '0' and will in this case add an additional 0, this should work
the problem is that you can't assign like this
stream.charAt(stream.length-1) = last*10 + a;
I've done some digging on the above topic but am now more confused than when I started.
I have a unit converter that I'm working on.
It's working fine as a base model but I'm now trying to make it more modular.
There are many units and many conversions.
My plan is to have a function that determines what type of conversion is required, temperature, area etc etc, that can then call the appropriate function to carry out the math.
I'm very new to JS which isn't helping matters as it could be a simple mistake that I'm making but it's just as likely that I'm getting huge errors.
I think the problem is passing the object to the next function and then using it.
I've played with the code a great deal and tried many different suggestions online but still no success.
here is my code:
<script type="text/javascript">
function Convert(from, to, units, res){
this.from = from;
this.to = to;
this.units = units;
this.res = res;
}
Convert.convertUnits = function(){
var measurementType = $(".from option:selected").attr("class");
var result = "invalid input";
var input = parseInt(this.units.val());
if(measurementType == "temp"){
var test = new Convert($("#from"), $("#to"), $("#units"), $("#result"));
test.convertTemp();
console.log('Did we get this far?!?! ::', measurementType);
}
console.log('or not???? ::', measurementType);
}
Convert.prototype.convertTemp = function(){
var result = "invalid input";
var input = parseInt(this.units.val());
var f = this.from.val();
var t = this.to.val()
if(!isNaN(input)) {
if(f == "degC"){
if(t == "degF"){
result = input * 1.8 + 32;
}
if(t == "kelvin"){
result = input + 273.15;
}
}
}
console.log('Parsed input is', input, "and result is", result);
this.res.val(result);
return result;
}
//var calcTempTest = new Convert($("#from"), $("#to"), $("#units"), $("#result"));
//var test = new Convert($("#from"), $("#to"), $("#units"), $("#result"));
$("#btnConvert").click.convertUnits();
</script>
The first obvious problem is this line:
$("#btnConvert").click.convertUnits();
This tries to call a convertUnits() method defined on the click method of the jQuery object returned by $("#btnConvert"). There is no such method, so you get'll get an error about how click has no method 'convertUnits'.
What you want to be doing there is binding the convertUnits() function as a click handler, which you do by passing it to the .click() method as an argument:
$("#btnConvert").click(Convert.convertUnits)
It doesn't make sense to have declared convertUnits() as a property of Convert(), though, so (although it will work as is) I'd change it to just be:
function convertUnits() {
// your code here
}
$("#btnConvert").click(convertUnits);
The only other thing stopping the code working is that on this line:
var input = parseInt(this.units.val());
...you use this assuming it will be a Convert object with a units property but you haven't yet created a Convert object - you do that inside the if(measurementType == "temp") block with this line:
var test = new Convert($("#from"), $("#to"), $("#units"), $("#result"));
So move that line to the beginning of the function and then use test instead of this:
function convertUnits(){
var test = new Convert($("#from"), $("#to"), $("#units"), $("#result"));
var measurementType = $(".from option:selected").attr("class");
var result = "invalid input";
var input = parseInt(test.units.val());
if(measurementType == "temp"){
test.convertTemp();
console.log('Did we get this far?!?! ::', measurementType);
}
console.log('or not???? ::', measurementType);
}
Working demo: http://jsfiddle.net/jT2ke/
Some unrelated advice: parseInt() doesn't really make sense for a number to feed into your converter, because the user might want to enter decimal values. You can use parseFloat() instead, or the unary plus operator:
var input = +test.units.val();
But if you want parseInt() it is generally recommended to pass it a second argument to specify the radix:
var input = parseInt(test.units.val(), 10);
...because otherwise if the input text has a leading zero some browsers will assume the value is octal rather than base ten. (parseFloat() and the unary plus don't have that issue.)
I think you should not implement the method convertUnits inside Convert object. And the new code will look like the following:
convertUnits = function(){
var measurementType = $(".from option:selected").attr("class");
var result = "invalid input";
if(measurementType == "temp"){
var test = new Convert($("#from"), $("#to"), $("#units"), $("#result"));
test.convertTemp();
console.log('Did we get this far?!?! ::', measurementType);
}
console.log('or not???? ::', measurementType);
}
Now you can initiate the convertUnits on the button click:
$("#btnConvert").click(function(){new convertUnits()});