rubyv8therubyracer

Get ruby exception from V8 context


context = V8::Context.new(timeout: 20000) do |context|
  context['ForbidAccess'] = ->(message) { throw NotImplementedError }
end

begin
  context.eval("ForbidAccess();")
rescue => e
  puts "e.class = #{e.class.name}"
  puts "e.causes = #{e.causes}"
  puts "e.root_cause = #{e.root_cause}"
  puts "e.root_cause.class = #{e.root_cause.class}"
end

The console output:

e.class = V8::Error
e.causes = [#<V8::Error: uncaught throw NotImplementedError>, #<ArgumentError: uncaught throw NotImplementedError>]
e.root_cause = uncaught throw NotImplementedError
e.root_cause.class = ArgumentError

How do I get access to the NotImplementedError object?

(NotImplementedError is just for show. It will get replaced with a custom exception containing a message etc.)


Solution

  • You probably aren't doing what you think you are doing. The throw keyword is not for exceptions. It is actually a local jump similar to goto from other languages. See this snippet:

    catch :done do
      while true
        array = [1,2,3]
        for i in array
          if i > 2
            throw :done
          end
        end
      end
    end
    

    It is just a control flow structure where the "caught" object must match the "thrown" one. But you can't simply catch all throws and figure out which object it was. For exceptions (like NotImplementedError) the correct thing to use is raise:

    context = V8::Context.new(timeout: 20000) do |context|
      context['ForbidAccess'] = ->(message) { raise NotImplementedError }
    end
    
    begin
      context.eval("ForbidAccess();")
    rescue => e
      puts "e.root_cause = #{e.root_cause.inspect}"
      # correctly prints #<NotImplementedError: NotImplementedError>
    end
    

    As for why you see ArgumentError there, it is simple: A throw can't get through a begin-rescue structure (that rescues from exceptions). When an uncaught throw meets a rescue, a new exception is created about it. Check below:

    begin
      throw "whatever"
    rescue e
      p e   #=> ArgumentError: uncaught throw "whatever"
    end
    

    This is what happens internally and all the V8 library see is an ArgumentError popping up.