1
0
Fork 0
mirror of https://github.com/rails/execjs synced 2023-03-27 23:21:20 -04:00

Improve Syntax and Runtime error backtraces

This commit is contained in:
Joshua Peek 2015-02-25 14:13:34 -06:00
parent e20941f927
commit 53f8bf9292
7 changed files with 103 additions and 41 deletions

View file

@ -24,12 +24,12 @@ module ExecJS
def exec(source, options = {}) def exec(source, options = {})
source = encode(source) source = encode(source)
source = "#{@source}\n#{source}" if @source source = "#{@source}\n#{source}" if @source != ""
source = @runtime.compile_source(source) source = @runtime.compile_source(source)
tmpfile = write_to_tempfile(source) tmpfile = write_to_tempfile(source)
begin begin
extract_result(@runtime.exec_runtime(tmpfile.path)) extract_result(@runtime.exec_runtime(tmpfile.path), tmpfile.path)
ensure ensure
File.unlink(tmpfile) File.unlink(tmpfile)
end end
@ -57,14 +57,25 @@ module ExecJS
tmpfile tmpfile
end end
def extract_result(output) def extract_result(output, filename)
status, value = output.empty? ? [] : ::JSON.parse(output, create_additions: false) status, value, stack = output.empty? ? [] : ::JSON.parse(output, create_additions: false)
if status == "ok" if status == "ok"
value value
elsif value =~ /SyntaxError:/
raise RuntimeError, value
else else
raise ProgramError, value stack ||= ""
real_filename = File.realpath(filename)
stack = stack.split("\n").map do |line|
line.sub(" at ", "")
.sub(real_filename, "(execjs)")
.sub(filename, "(execjs)")
.strip
end
stack.reject! { |line| ["eval code", "eval@[native code]"].include?(line) }
stack.shift unless stack[0].to_s.include?("(execjs)")
error_class = value =~ /SyntaxError:/ ? RuntimeError : ProgramError
error = error_class.new(value)
error.set_backtrace(stack + caller)
raise error
end end
end end
end end
@ -158,7 +169,7 @@ module ExecJS
if $?.success? if $?.success?
output output
else else
raise RuntimeError, output raise exec_runtime_error(output)
end end
end end
@ -181,7 +192,7 @@ module ExecJS
if $?.success? if $?.success?
output output
else else
raise RuntimeError, output raise exec_runtime_error(output)
end end
end end
else else
@ -193,13 +204,20 @@ module ExecJS
if $?.success? if $?.success?
output output
else else
raise RuntimeError, output raise exec_runtime_error(output)
end end
end end
end end
# Internally exposed for Context. # Internally exposed for Context.
public :exec_runtime public :exec_runtime
def exec_runtime_error(output)
error = RuntimeError.new(output)
lineno = output.split("\n")[0][/:(\d+)$/, 1] || 1
error.set_backtrace(["(execjs):#{lineno}"] + caller)
error
end
def which(command) def which(command)
Array(command).find do |name| Array(command).find do |name|
name, args = name.split(/\s+/, 2) name, args = name.split(/\s+/, 2)

View file

@ -12,11 +12,7 @@ module ExecJS
begin begin
@v8_context.eval(source) @v8_context.eval(source)
rescue ::V8::JSError => e rescue ::V8::JSError => e
if e.value["name"] == "SyntaxError" raise wrap_error(e)
raise RuntimeError, e.value.to_s
else
raise ProgramError, e.value.to_s
end
end end
end end
end end
@ -37,11 +33,7 @@ module ExecJS
begin begin
unbox @v8_context.eval("(#{source})") unbox @v8_context.eval("(#{source})")
rescue ::V8::JSError => e rescue ::V8::JSError => e
if e.value["name"] == "SyntaxError" raise wrap_error(e)
raise RuntimeError, e.value.to_s
else
raise ProgramError, e.value.to_s
end
end end
end end
end end
@ -52,11 +44,7 @@ module ExecJS
begin begin
unbox @v8_context.eval(properties).call(*args) unbox @v8_context.eval(properties).call(*args)
rescue ::V8::JSError => e rescue ::V8::JSError => e
if e.value["name"] == "SyntaxError" raise wrap_error(e)
raise RuntimeError, e.value.to_s
else
raise ProgramError, e.value.to_s
end
end end
end end
end end
@ -96,6 +84,20 @@ module ExecJS
result result
end end
end end
def wrap_error(e)
error_class = e.value["name"] == "SyntaxError" ? RuntimeError : ProgramError
stack = e.value["stack"] || ""
stack = stack.split("\n")
stack.shift
stack = [e.message[/<eval>:\d+:\d+/, 0]].compact if stack.empty?
stack = stack.map { |line| line.sub(" at ", "").sub("<eval>", "(execjs)").strip }
error = error_class.new(e.value.to_s)
error.set_backtrace(stack + caller)
error
end
end end
def name def name

View file

@ -9,10 +9,10 @@
try { try {
print(JSON.stringify(['ok', result])); print(JSON.stringify(['ok', result]));
} catch (err) { } catch (err) {
print('["err"]'); print(JSON.stringify(['err', '' + err, err.stack]));
} }
} }
} catch (err) { } catch (err) {
print(JSON.stringify(['err', '' + err])); print(JSON.stringify(['err', '' + err, err.stack]));
} }
}); });

View file

@ -13,10 +13,10 @@
try { try {
print(JSON.stringify(['ok', result])); print(JSON.stringify(['ok', result]));
} catch (err) { } catch (err) {
print('["err"]'); print(JSON.stringify(['err', err.name + ': ' + err.message, err.stack]));
} }
} }
} catch (err) { } catch (err) {
print(JSON.stringify(['err', err.name + ': ' + err.message])); print(JSON.stringify(['err', err.name + ': ' + err.message, err.stack]));
} }
}); });

View file

@ -11,10 +11,10 @@
try { try {
print(JSON.stringify(['ok', result])); print(JSON.stringify(['ok', result]));
} catch (err) { } catch (err) {
print('["err"]'); print(JSON.stringify(['err', '' + err, err.stack]));
} }
} }
} catch (err) { } catch (err) {
print(JSON.stringify(['err', '' + err])); print(JSON.stringify(['err', '' + err, err.stack]));
} }
}); });

View file

@ -9,10 +9,10 @@
try { try {
print(JSON.stringify(['ok', result])); print(JSON.stringify(['ok', result]));
} catch (err) { } catch (err) {
print('["err"]'); print(JSON.stringify(['err', '' + err, err.stack]));
} }
} }
} catch (err) { } catch (err) {
print(JSON.stringify(['err', '' + err])); print(JSON.stringify(['err', '' + err, err.stack]));
} }
}); });

View file

@ -253,38 +253,80 @@ class TestExecJS < Test
end end
def test_exec_syntax_error def test_exec_syntax_error
assert_raises ExecJS::RuntimeError do begin
ExecJS.exec(")") ExecJS.exec(")")
flunk
rescue ExecJS::RuntimeError => e
assert e
assert e.backtrace[0].include?("(execjs):1"), e.backtrace.join("\n")
end end
end end
def test_eval_syntax_error def test_eval_syntax_error
assert_raises ExecJS::RuntimeError do begin
ExecJS.eval(")") ExecJS.eval(")")
flunk
rescue ExecJS::RuntimeError => e
assert e
assert e.backtrace[0].include?("(execjs):1"), e.backtrace.join("\n")
end end
end end
def test_compile_syntax_error def test_compile_syntax_error
assert_raises ExecJS::RuntimeError do begin
ExecJS.compile(")") ExecJS.compile(")")
flunk
rescue ExecJS::RuntimeError => e
assert e
assert e.backtrace[0].include?("(execjs):1"), e.backtrace.join("\n")
end end
end end
def test_exec_thrown_exception def test_exec_thrown_error
begin
ExecJS.exec("throw new Error('hello')")
flunk
rescue ExecJS::ProgramError => e
assert e
assert e.backtrace[0].include?("(execjs):1"), e.backtrace.join("\n")
end
end
def test_eval_thrown_error
begin
ExecJS.eval("(function(){ throw new Error('hello') })()")
flunk
rescue ExecJS::ProgramError => e
assert e
assert e.backtrace[0].include?("(execjs):1"), e.backtrace.join("\n")
end
end
def test_compile_thrown_error
begin
ExecJS.compile("throw new Error('hello')")
flunk
rescue ExecJS::ProgramError => e
assert e
assert e.backtrace[0].include?("(execjs):1"), e.backtrace.join("\n")
end
end
def test_exec_thrown_string
assert_raises ExecJS::ProgramError do assert_raises ExecJS::ProgramError do
ExecJS.exec("throw 'hello'") ExecJS.exec("throw 'hello'")
end end
end end
def test_eval_thrown_exception def test_eval_thrown_string
assert_raises ExecJS::ProgramError do assert_raises ExecJS::ProgramError do
ExecJS.exec("throw 'hello'") ExecJS.eval("(function(){ throw 'hello' })()")
end end
end end
def test_compile_thrown_exception def test_compile_thrown_string
assert_raises ExecJS::ProgramError do assert_raises ExecJS::ProgramError do
ExecJS.exec("throw 'hello'") ExecJS.compile("throw 'hello'")
end end
end end