From 744baef3a3bd4cf7e3efb2ed8faf7de5e9f72fd6 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 13 Aug 2012 09:01:28 -0500 Subject: [PATCH] track causes of exceptions --- lib/v8/error.rb | 82 +++++++++++++++++++++++++++++-------------- spec/v8/error_spec.rb | 6 ++-- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/lib/v8/error.rb b/lib/v8/error.rb index e406eae..2eef0d0 100644 --- a/lib/v8/error.rb +++ b/lib/v8/error.rb @@ -1,23 +1,59 @@ module V8 - class Error < StandardError - # capture 99 stack frames on exception with normal details. # You can adjust these values for performance or turn of stack capture entirely V8::C::V8::SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kOverview) + class Error < StandardError + include Enumerable + attr_reader :value, :cause, :javascript_backtrace - attr_reader :value - def initialize(message, value) + def initialize(message, value, javascript_backtrace, cause = nil) super(message) @value = value + @cause = cause + @javascript_backtrace = javascript_backtrace + end + + def causes + [].tap do |causes| + current = self + until current.nil? do + causes.push current + current = current.respond_to?(:cause) ? current.cause : nil + end + end + end + + def root_cause + causes.last + end + + def in_javascript? + causes.last.is_a? self.class + end + + def in_ruby? + !in_javascript? + end + + def multilingual_backtrace + causes.reduce(:backtrace => [], :ruby => -1, :javascript => -1) { |accumulator, cause| + ruby_frames = cause.backtrace[0..accumulator[:ruby]] + accumulator[:backtrace].unshift *ruby_frames + accumulator[:ruby] -= ruby_frames.length + if cause.respond_to?(:javascript_backtrace) + javascript_frames = cause.javascript_frames[0, accumulator[:javascript]].map(&:to_s) + accumulator[:backtrace].unshift *javascript_frames + accumulator[:javascript] -= javascript_frames.length + end + }[:backtrace] end module Try def try - context = V8::Context.current V8::C::TryCatch() do |trycatch| result = yield if trycatch.HasCaught() - V8::Error(trycatch.Exception()) + raise V8::Error(trycatch) else result end @@ -28,39 +64,33 @@ module V8 module Protect def protect yield - rescue Football => e - e.kickoff! rescue Exception => e - e.extend Football - e.kickoff! - end - end - - module Football - def kickoff! - error = V8::C::Exception::Error(message) - error.SetHiddenValue("rr::Football", V8::C::External::New(self)) + error = V8::C::Exception::Error(e.message) + error.SetHiddenValue("rr::Cause", V8::C::External::New(e)) V8::C::ThrowException(error) end end end - def self.Error(exception) + def self.Error(trycatch) + exception = trycatch.Exception() value = exception.to_ruby - if !exception.kind_of?(V8::C::Value) - raise V8::Error.new(exception.to_s, value) + cause = nil + message = if !exception.kind_of?(V8::C::Value) + exception.to_s elsif exception.IsNativeError() - if football = exception.GetHiddenValue("rr::Football") - raise football.Value() - else - raise V8::Error.new(exception.Get("message").to_ruby, value) + if cause = exception.GetHiddenValue("rr::Cause") + cause = cause.Value() end + exception.Get("message").to_ruby elsif exception.IsObject() - raise V8::Error.new(value['message'] || value.to_s, value) + value['message'] || value.to_s else - raise V8::Error.new(exception.ToString().to_ruby, value) + value.to_s end + javascript_backtrace = V8::StackTrace.new(trycatch.Message().GetStackTrace()) + V8::Error.new(message, value, javascript_backtrace, cause) end const_set :JSError, Error end \ No newline at end of file diff --git a/spec/v8/error_spec.rb b/spec/v8/error_spec.rb index bb3984e..65cf40d 100644 --- a/spec/v8/error_spec.rb +++ b/spec/v8/error_spec.rb @@ -39,16 +39,14 @@ describe V8::Error do end it "has a reference to the root javascript cause" do - pending throw!('"I am a String"') do |e| e.should_not be_in_ruby e.should be_in_javascript - e.value.should == "I am a String" + e.value['message'].should == "I am a String" end end it "has a reference to the root ruby cause if one exists" do - pending StandardError.new("BOOM!").tap do |bomb| @cxt['boom'] = lambda do raise bomb @@ -58,7 +56,7 @@ describe V8::Error do }.should(raise_error do |raised| raised.should be_in_ruby raised.should_not be_in_javascript - raised.value.should be(bomb) + raised.root_cause.should be(bomb) end) end end