diff --git a/CHANGELOG.md b/CHANGELOG.md index c703b91..64d3f43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -# Master (v0.7.1.pre) +# v0.8.0 - Feb 4, 2016 +- (_breaking change_) JS errors are now mapped to their proper Crystal exceptions. i.e. JS `SyntaxError` becomes `Duktape::SyntaxError`. +- (_breaking change_) Make all exception classes more consistent. Instances of `Duktape::Error` are all recoverable exceptions that are thrown by the engine at runtime (eg. `Duktape::TypeError`). Instances of `Duktape::InternalError` are generally non-recoverable for a given context (eg. `Duktape::HeapError`). - Added `call_success`, `call_failure` and `return_undefined` convenience methods that provide the appropriate integer status codes when returning from a native function call. - Added the `push_global_proc` method that simplifies pushing a named native function to the stack. - `Duktape::Runtime` instances may now accept a execution timeout value in milliseconds upon creation. [[#15](https://github.com/jessedoyle/duktape.cr/pull/15), [@raydf](https://github.com/raydf)]. diff --git a/README.md b/README.md index 77bb88c..d7613ee 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ version: 1.0.0 # your project's version dependencies: duktape: github: jessedoyle/duktape.cr - version: ~> 0.7.0 + version: ~> 0.8.0 ``` then execute: @@ -89,7 +89,7 @@ sbx.eval! <<-JS JS ``` -will raise `Duktape::Error "SyntaxError"`. +will raise `Duktape::SyntaxError`. ## Sandbox vs Context @@ -108,7 +108,7 @@ JS `Duktape::Sandbox` instances may optionally take an execution timeout limit in milliseconds. This provides protection against infinite loops when executing untrusted code. -A `Duktape::Error "RangeError"` exception is raised when the following code executes for longer than specified: +A `Duktape::RangeError` exception is raised when the following code executes for longer than specified: ```crystal sbx = Duktape::Sandbox.new 500 # 500ms execution time limit @@ -178,6 +178,27 @@ The `proc` object that is pushed to the Duktape stack accepts a pointer to a `Co **Note**: Because it is currently not possible to pass closures to C bindings in Crystal, one must be careful that any variables used in the `proc` must not be referenced or initialized outside the scope of the `proc`. This is why variable names such as `env` are used. +## Exceptions + +The following exceptions may be thrown at runtime and may be rescued normally: + +* `Duktape::Error` +* `Duktape::EvalError` +* `Duktape::RangeError` +* `Duktape::ReferenceError` +* `Duktape::SyntaxError` +* `Duktape::TypeError` +* `Duktape::URIError` + +These exceptions all inherit from `Duktape::Error`, so it may be used as a catch-all for runtime errors. + +The following exceptions represent errors internal to the Duktape engine and are generally not recoverable when thrown from a context: + +* `Duktape::InternalError` +* `Duktape::HeapError` + +These exceptions all inherit from `Duktape::InternalError`. + ## Contributing I'll accept any pull requests that are well tested for bugs/features with Duktape.cr. diff --git a/shard.yml b/shard.yml index de71fa5..f9a979e 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: duktape -version: 0.7.1.pre +version: 0.8.0 authors: - Jesse Doyle diff --git a/spec/duktape/api/call_spec.cr b/spec/duktape/api/call_spec.cr index 864333e..17074f1 100644 --- a/spec/duktape/api/call_spec.cr +++ b/spec/duktape/api/call_spec.cr @@ -5,7 +5,7 @@ describe Duktape::API::Call do it "should raise if nargs < 0" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /negative argument/ do + expect_raises ArgumentError, /negative argument/ do ctx.call(-1) end end @@ -52,7 +52,7 @@ describe Duktape::API::Call do it "should raise Duktape::Error if error does not exist" do ctx = Duktape::Context.new - expect_raises(Duktape::Error, /invalid error type/) do + expect_raises(Duktape::TypeError, /invalid error type/) do ctx.call_failure :invalid end end @@ -77,7 +77,7 @@ describe Duktape::API::Call do it "should raise if nargs < 0" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /negative argument/ do + expect_raises ArgumentError, /negative argument/ do ctx.call_method -1 end end @@ -140,7 +140,7 @@ describe Duktape::API::Call do it "should raise on negative nargs" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /negative argument/ do + expect_raises ArgumentError, /negative argument/ do ctx.new -1 end end diff --git a/spec/duktape/api/compile_spec.cr b/spec/duktape/api/compile_spec.cr index 501f4bf..193c92b 100644 --- a/spec/duktape/api/compile_spec.cr +++ b/spec/duktape/api/compile_spec.cr @@ -84,7 +84,7 @@ describe Duktape::API::Compile do ctx << invalid_js ctx << "invalid.js" - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /parse error/ do ctx.compile! end end @@ -103,7 +103,7 @@ describe Duktape::API::Compile do it "should raise when provided invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /parse error/ do ctx.compile! invalid_js end end @@ -159,7 +159,7 @@ describe Duktape::API::Compile do it "should raise on invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /eof or line terminator/ do ctx.compile_file! "#{JS_SOURCE_PATH}/invalid.js" end end @@ -195,7 +195,7 @@ describe Duktape::API::Compile do it "should raise on invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /parse error/ do ctx.compile_lstring! invalid_js, invalid_js.size end end @@ -252,7 +252,7 @@ describe Duktape::API::Compile do ctx = Duktape::Context.new ctx << "test.js" - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /parse error/ do ctx.compile_lstring_filename! invalid_js, invalid_js.size end end @@ -291,7 +291,7 @@ describe Duktape::API::Compile do it "should raise on invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /parse error/ do ctx.compile_string! invalid_js end end @@ -341,7 +341,7 @@ describe Duktape::API::Compile do ctx = Duktape::Context.new ctx << "invalid.js" - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /parse error/ do ctx.compile_string_filename! invalid_js end end diff --git a/spec/duktape/api/error_handling_spec.cr b/spec/duktape/api/error_handling_spec.cr index 4d84550..38fb6e1 100644 --- a/spec/duktape/api/error_handling_spec.cr +++ b/spec/duktape/api/error_handling_spec.cr @@ -60,6 +60,91 @@ describe Duktape::API::ErrorHandling do end end + describe "raise_error" do + it "should not raise and return 0 when not given an argument" do + ctx = Duktape::Context.new + val = ctx.raise_error + + val.should eq(0) + end + + it "should rescue with a Duktape::Error" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_TYPE_ERROR, "test" + + begin + ctx.raise_error(-1) + # We should not get this far due to a raise + 1.should_not eq(1) + rescue ex : Duktape::Error + 1.should eq(1) + end + end + + it "should raise a Duktape::Error" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_ERROR, "test" + + expect_raises Duktape::Error, /test/ do + ctx.raise_error(-1) + end + end + + it "should raise a Duktape::EvalError" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_EVAL_ERROR, "test" + + expect_raises Duktape::EvalError, /test/ do + ctx.raise_error(-1) + end + end + + it "should raise a Duktape::RangeError" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_RANGE_ERROR, "test" + + expect_raises Duktape::RangeError, /test/ do + ctx.raise_error(-1) + end + end + + it "should raise a Duktape::ReferenceError" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_REFERENCE_ERROR, "test" + + expect_raises Duktape::ReferenceError, /test/ do + ctx.raise_error(-1) + end + end + + it "should raise a Duktape::SyntaxError" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_SYNTAX_ERROR, "test" + + expect_raises Duktape::SyntaxError, /test/ do + ctx.raise_error(-1) + end + end + + it "should raise a Duktape::TypeError" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_TYPE_ERROR, "test" + + expect_raises Duktape::TypeError, /test/ do + ctx.raise_error(-1) + end + end + + it "should raise a Duktape::URIError" do + ctx = Duktape::Context.new + ctx.push_error_object LibDUK::ERR_URI_ERROR, "test" + + expect_raises Duktape::URIError, /test/ do + ctx.raise_error(-1) + end + end + end + # Note: Can't really test this from a proc as # `push_proc` doesn't get along with Crystal's # spec library. diff --git a/spec/duktape/api/eval_spec.cr b/spec/duktape/api/eval_spec.cr index 2b681f3..b42463e 100644 --- a/spec/duktape/api/eval_spec.cr +++ b/spec/duktape/api/eval_spec.cr @@ -53,7 +53,7 @@ describe Duktape::API::Eval do it "should raise if arg contains invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /ReferenceError/ do + expect_raises Duktape::ReferenceError, /identifier '__invalid_identifier' undefined/ do ctx.eval! invalid_js end end @@ -72,7 +72,7 @@ describe Duktape::API::Eval do ctx = Duktape::Context.new ctx << invalid_js - expect_raises Duktape::Error, /ReferenceError/ do + expect_raises Duktape::ReferenceError, /identifier '__invalid_identifier' undefined/ do ctx.eval! end end @@ -114,7 +114,7 @@ describe Duktape::API::Eval do it "should raise an error on invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /SyntaxError/ do + expect_raises Duktape::SyntaxError, /eof or line terminator/ do ctx.eval_file! "#{JS_SOURCE_PATH}/invalid.js" end end @@ -153,7 +153,7 @@ describe Duktape::API::Eval do # Because the NORESULT flag tells Duktape to # not push the Error object on the stack after # failure, we have to look for a StackError - expect_raises Duktape::StackError, /error object missing/ do + expect_raises Duktape::StackError, /stack empty/ do ctx.eval_file_noresult! "#{JS_SOURCE_PATH}/invalid.js" end end @@ -186,7 +186,7 @@ describe Duktape::API::Eval do it "should raise an error on invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /ReferenceError/ do + expect_raises Duktape::ReferenceError, /identifier '__invalid_identifier' undefined/ do ctx.eval_lstring! invalid_js, invalid_js.size end end @@ -194,7 +194,7 @@ describe Duktape::API::Eval do it "should raise when length is negative" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /negative string length/ do + expect_raises ArgumentError, /negative string length/ do ctx.eval_lstring! valid_js, -1 end end @@ -236,7 +236,7 @@ describe Duktape::API::Eval do it "should raise on invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::StackError, /error object missing/ do + expect_raises Duktape::StackError, /stack empty/ do ctx.eval_lstring_noresult! invalid_js, invalid_js.size end end @@ -244,7 +244,7 @@ describe Duktape::API::Eval do it "should raise when length is negative" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /negative string length/ do + expect_raises ArgumentError, /negative string length/ do ctx.eval_lstring_noresult! valid_js, -1 end end @@ -282,7 +282,7 @@ describe Duktape::API::Eval do ctx = Duktape::Context.new ctx << invalid_js - expect_raises Duktape::StackError, /error object missing/ do + expect_raises Duktape::StackError, /stack empty/ do ctx.eval_noresult! end end @@ -319,7 +319,7 @@ describe Duktape::API::Eval do it "should raise on invalid js strings" do ctx = Duktape::Context.new - expect_raises Duktape::Error, /ReferenceError/ do + expect_raises Duktape::ReferenceError, /identifier '__invalid_identifier' undefined/ do ctx.eval_string! invalid_js end end @@ -361,7 +361,7 @@ describe Duktape::API::Eval do it "should raise StackError on invalid js" do ctx = Duktape::Context.new - expect_raises Duktape::StackError, /error object missing/ do + expect_raises Duktape::StackError, /stack empty/ do ctx.eval_string_noresult! invalid_js end end diff --git a/spec/duktape/api/object_spec.cr b/spec/duktape/api/object_spec.cr index dfcd745..175cae5 100644 --- a/spec/duktape/api/object_spec.cr +++ b/spec/duktape/api/object_spec.cr @@ -179,7 +179,7 @@ describe Duktape::API::Object do ctx.get_prop(-2) ctx.set_global_object - expect_raises Duktape::Error, /ReferenceError/ do + expect_raises Duktape::ReferenceError, /identifier 'Duktape' undefined/ do ctx.eval_string! <<-JS Duktape.version; JS diff --git a/spec/duktape/runtime_spec.cr b/spec/duktape/runtime_spec.cr index abe7998..96adba0 100644 --- a/spec/duktape/runtime_spec.cr +++ b/spec/duktape/runtime_spec.cr @@ -173,10 +173,10 @@ describe Duktape::Runtime do val.floor.should eq(3) end - it "should raise a Duktape::Error if an error was thrown" do + it "should raise a Duktape::TypeError if an error was thrown" do rt = Duktape::Runtime.new - expect_raises(Duktape::Error, /TypeError/) do + expect_raises(Duktape::TypeError, TYPE_REGEX) do rt.call("JSON.__invalid", 123) end end @@ -224,10 +224,10 @@ describe Duktape::Runtime do val.floor.should eq(2) end - it "should raise a Duktape::Error if an error was thrown" do + it "should raise a Duktape::TypeError if an error was thrown" do rt = Duktape::Runtime.new - expect_raises(Duktape::Error, /TypeError/) do + expect_raises(Duktape::TypeError, TYPE_REGEX) do rt.call(["JSON", "invalid"], 123) end end @@ -265,7 +265,7 @@ describe Duktape::Runtime do it "should raise a ReferenceError on invalid syntax" do rt = Duktape::Runtime.new - expect_raises(Duktape::Error, /ReferenceError/) do + expect_raises(Duktape::ReferenceError, REFERENCE_REGEX) do rt.eval("__abc__;") end end @@ -302,7 +302,7 @@ describe Duktape::Runtime do it "should raise on invalid syntax" do rt = Duktape::Runtime.new - expect_raises(Duktape::Error, /SyntaxError/) do + expect_raises(Duktape::SyntaxError, SYNTAX_REGEX) do rt.exec("\"missing") end end diff --git a/spec/duktape/sandbox_spec.cr b/spec/duktape/sandbox_spec.cr index 5ae01bc..5a7be83 100644 --- a/spec/duktape/sandbox_spec.cr +++ b/spec/duktape/sandbox_spec.cr @@ -31,7 +31,7 @@ describe Duktape::Sandbox do var test = require('foo'); JS - expect_raises Duktape::Error, /ReferenceError/ do + expect_raises Duktape::ReferenceError, /identifier 'require' undefined/ do sbx.eval_string! js end end @@ -42,7 +42,7 @@ describe Duktape::Sandbox do Duktape.version; JS - expect_raises Duktape::Error, /ReferenceError/ do + expect_raises Duktape::ReferenceError, /identifier 'Duktape' undefined/ do sbx.eval_string! js end end @@ -107,9 +107,9 @@ describe Duktape::Sandbox do end context "timeout during evaluation" do - it "should raise a RangeError (Duktape::Error) when timeout" do + it "should raise a Duktape::RangeError on timeout" do sbx = Duktape::Sandbox.new(500) - expect_raises Duktape::Error, /RangeError/ do + expect_raises Duktape::RangeError, /execution timeout/ do sbx.eval! <<-JS var times = 1000000; for(var i = 0; i < times; i++){ diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 1a7a15c..f8ceaae 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -10,3 +10,7 @@ require "./support/**" Duktape.logger.level = Logger::Severity::UNKNOWN JS_SOURCE_PATH = "#{__DIR__}/javascripts" + +REFERENCE_REGEX = /identifier '__abc__' undefined/ +SYNTAX_REGEX = /eof or line terminator while parsing string literal/ +TYPE_REGEX = /undefined not callable/ diff --git a/src/duktape/api/call.cr b/src/duktape/api/call.cr index c14dbc3..e8c55fa 100644 --- a/src/duktape/api/call.cr +++ b/src/duktape/api/call.cr @@ -35,7 +35,7 @@ module Duktape def call_failure(value = :error) ERRORS[value] rescue KeyError - raise Error.new "invalid error type: #{value}" + raise TypeError.new "invalid error type: #{value}" end def call_method(nargs : Int32) @@ -77,7 +77,7 @@ module Duktape private def require_valid_nargs(nargs : Int32) # :nodoc: if nargs < 0 - raise Error.new "negative argument count" + raise ArgumentError.new "negative argument count" end end end diff --git a/src/duktape/api/debug.cr b/src/duktape/api/debug.cr index 213548a..c089397 100644 --- a/src/duktape/api/debug.cr +++ b/src/duktape/api/debug.cr @@ -8,9 +8,7 @@ module Duktape module API::Debug def stack push_context_dump - require_string(-1).tap do - pop - end + require_string(-1).tap { pop } end def dump! diff --git a/src/duktape/api/error_handling.cr b/src/duktape/api/error_handling.cr index d3643ba..049eec2 100644 --- a/src/duktape/api/error_handling.cr +++ b/src/duktape/api/error_handling.cr @@ -23,6 +23,38 @@ module Duktape get_error_code(index) != 0 end + def raise_error(err = 0) # :nodoc: + # We want to return the code (0) if no + # error is raised + err.tap do |error| + unless error == 0 + unless valid_index? -1 + raise StackError.new "stack empty" + end + + code = LibDUK.get_error_code ctx, -1 + msg = safe_to_string(-1).gsub(/\A.*Error:\s/, "") + + case code + when LibDUK::ERR_EVAL_ERROR + raise EvalError.new msg + when LibDUK::ERR_RANGE_ERROR + raise RangeError.new msg + when LibDUK::ERR_REFERENCE_ERROR + raise ReferenceError.new msg + when LibDUK::ERR_SYNTAX_ERROR + raise SyntaxError.new msg + when LibDUK::ERR_TYPE_ERROR + raise TypeError.new msg + when LibDUK::ERR_URI_ERROR + raise URIError.new msg + else + raise Error.new msg + end + end + end + end + def throw LibDUK.throw ctx end diff --git a/src/duktape/api/eval.cr b/src/duktape/api/eval.cr index 62aa643..dbf6b5a 100644 --- a/src/duktape/api/eval.cr +++ b/src/duktape/api/eval.cr @@ -78,7 +78,7 @@ module Duktape def eval_lstring!(src : String, length : Int) if length < 0 - raise Error.new "negative string length" + raise ArgumentError.new "negative string length" end err = eval_lstring src, length @@ -101,7 +101,7 @@ module Duktape def eval_lstring_noresult!(src : String, length : Int) if length < 0 - raise Error.new "negative string length" + raise ArgumentError.new "negative string length" end err = eval_lstring_noresult src, length @@ -172,20 +172,5 @@ module Duktape file.close if file end end - - private def raise_error(err) # :nodoc: - # We want to return the code (0) if no - # error is raised - err.tap do |error| - unless error == 0 - code = LibDUK.get_error_code ctx, -1 - if code == 0 - raise StackError.new "error object missing" - else - raise Duktape::Error.new safe_to_string -1 - end - end - end - end end end diff --git a/src/duktape/error.cr b/src/duktape/error.cr index 3292f89..9d9dda2 100644 --- a/src/duktape/error.cr +++ b/src/duktape/error.cr @@ -8,6 +8,11 @@ module Duktape class InternalError < Exception getter msg, err + def initialize(@msg : String) + @err = LibDUK::ERR_INTERNAL_ERROR + super msg + end + def initialize(@ctx : LibDUK::Context, @msg : String, @err : Int32) Duktape.logger.fatal "InternalError: #{msg} - #{err}" Duktape.logger.debug "STACK: #{stack}" @@ -34,35 +39,24 @@ module Duktape end end - class FileError < Exception - def initialize(msg : String) - str = "FileError: #{msg}" - Duktape.logger.error str - super msg - end - end - - class HeapError < Exception - def initialize(msg : String) - str = "HeapError: #{msg}" - Duktape.logger.fatal str - super msg - end - end - - class StackError < Exception - def initialize(msg : String) - str = "StackError: #{msg}" - Duktape.logger.error str - super msg + macro define_error_class(klass, parent) + class {{klass}} < {{parent}} + def initialize(msg : String) + super msg + end end end - class TypeError < Exception - def initialize(msg : String) - str = "TypeError: #{msg}" - Duktape.logger.error str - super msg - end - end + # Runtime Exception Classes + define_error_class EvalError, Error + define_error_class FileError, Error + define_error_class RangeError, Error + define_error_class ReferenceError, Error + define_error_class StackError, Error + define_error_class SyntaxError, Error + define_error_class TypeError, Error + define_error_class URIError, Error + + # Non-recoverable (Engine) Exception Classes + define_error_class HeapError, InternalError end diff --git a/src/duktape/runtime.cr b/src/duktape/runtime.cr index 00ed07f..95c88c9 100644 --- a/src/duktape/runtime.cr +++ b/src/duktape/runtime.cr @@ -155,7 +155,8 @@ module Duktape # :nodoc: private def check_and_raise_error if @context.is_valid_index(-1) && @context.is_error(-1) - raise Duktape::Error.new @context.safe_to_string(-1) + code = @context.get_error_code -1 + @context.raise_error code end end diff --git a/src/duktape/version.cr b/src/duktape/version.cr index ff5767e..6361400 100644 --- a/src/duktape/version.cr +++ b/src/duktape/version.cr @@ -15,9 +15,9 @@ module Duktape module VERSION MAJOR = 0 - MINOR = 7 - TINY = 1 - PRE = "pre" + MINOR = 8 + TINY = 0 + PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join "."