Skip to content
Browse files

extract out instruction limiting into a separate Context impl that do…

…es tracking itself - make a native context factory shared by default + allow a factory option when creating a context
  • Loading branch information...
1 parent 9b94864 commit b4ac2b33397200084403f94c664fe658f19db825 @kares committed Feb 15, 2012
Showing with 129 additions and 23 deletions.
  1. +105 −23 lib/rhino/context.rb
  2. +24 −0 spec/rhino/context_spec.rb
View
128 lib/rhino/context.rb
@@ -46,18 +46,28 @@ def open(options = {}, &block)
def eval(javascript)
new.eval(javascript)
end
-
+
end
-
+
+ @@default_factory = nil
+ def self.default_factory
+ @@default_factory ||= ContextFactory.new
+ end
+
+ def self.default_factory=(factory)
+ @@default_factory = factory
+ end
+
attr_reader :scope
# Create a new javascript environment for executing javascript and ruby code.
# * <tt>:sealed</tt> - if this is true, then the standard objects such as Object, Function, Array will not be able to be modified
# * <tt>:with</tt> - use this ruby object as the root scope for all javascript that is evaluated
# * <tt>:java</tt> - if true, java packages will be accessible from within javascript
def initialize(options = {}) #:nodoc:
- @factory = ContextFactory.new
- @factory.call do |context|
+ factory = options[:factory] ||
+ (options[:restrictable] ? RestrictableContextFactory.instance : self.class.default_factory)
+ factory.call do |context|
@native = context
@global = @native.initStandardObjects(nil, options[:sealed] == true)
if with = options[:with]
@@ -73,7 +83,12 @@ def initialize(options = {}) #:nodoc:
end
end
end
-
+
+ # Returns the ContextFactory used while creating this context.
+ def factory
+ @native.getFactory
+ end
+
# Read a value from the global scope of this context
def [](key)
@scope[key]
@@ -117,14 +132,28 @@ def load(filename)
end
end
+ # Returns true if this context supports restrictions.
+ def restrictable?
+ @native.is_a?(RestrictableContextFactory::Context)
+ end
+
+ def instruction_limit
+ restrictable? ? @native.instruction_limit : false
+ end
+
# Set the maximum number of instructions that this context will execute.
- # If this instruction limit is exceeded, then a Rhino::RunawayScriptError
- # will be raised
+ # If this instruction limit is exceeded, then a #Rhino::RunawayScriptError
+ # will be raised.
def instruction_limit=(limit)
- @native.setInstructionObserverThreshold(limit)
- @factory.instruction_limit = limit
+ if restrictable?
+ @native.instruction_limit = limit
+ else
+ warn "setting an instruction_limit has no effect on this context, use " +
+ "Context.open(:restricted => true) to gain a restrictable instance"
+ nil
+ end
end
-
+
def optimization_level
@native.getOptimizationLevel
end
@@ -134,7 +163,7 @@ def optimization_level
# By using the -1 optimization level, you tell Rhino to run in interpretative mode,
# taking a hit to performance but escaping the Java bytecode limit.
def optimization_level=(level)
- if @native.class.isValidOptimizationLevel(level)
+ if JS::Context.isValidOptimizationLevel(level)
@native.setOptimizationLevel(level)
level
else
@@ -157,13 +186,12 @@ def version
def version=(version)
const = version.to_s.gsub('.', '_').upcase
const = "VERSION_#{const}" if const[0, 7] != 'VERSION'
- js_context = @native.class # Context
- if js_context.constants.include?(const)
- const_value = js_context.const_get(const)
+ if JS::Context.constants.include?(const)
+ const_value = JS::Context.const_get(const)
@native.setLanguageVersion(const_value)
const_value
else
- @native.setLanguageVersion(js_context::VERSION_DEFAULT)
+ @native.setLanguageVersion(JS::Context::VERSION_DEFAULT)
nil
end
end
@@ -179,11 +207,11 @@ def open(&block)
private
def do_open
+ factory.enterContext(@native)
begin
- @factory.enterContext(@native)
yield self
ensure
- JS::Context.exit
+ factory.exit
end
end
@@ -216,18 +244,72 @@ def read(buffer, offset, length)
end
- class ContextFactory < JS::ContextFactory # :nodoc:
+ ContextFactory = JS::ContextFactory # :nodoc: backward compatibility
- def observeInstructionCount(cxt, count)
- raise RunawayScriptError, "script exceeded allowable instruction count" if count > @limit
+ class RestrictableContextFactory < ContextFactory # :nodoc:
+
+ @@instance = nil
+ def self.instance
+ @@instance ||= new
end
+
+ # protected Context makeContext()
+ def makeContext
+ Context.new(self)
+ end
+
+ # protected void observeInstructionCount(Context context, int instructionCount)
+ def observeInstructionCount(context, count)
+ if context.is_a?(Context)
+ context.instruction_count += count
+ context.check!
+ end
+ end
+
+ # protected Object doTopCall(Callable callable, Context context,
+ # Scriptable scope, Scriptable thisObj, Object[] args)
+ def doTopCall(callable, context, scope, this, args)
+ context.instruction_count = 0 if context.is_a?(Context)
+ super
+ end
+
+ class Context < JS::Context # :nodoc:
+
+ def initialize(factory)
+ super(factory)
+ reset!
+ end
+
+ attr_reader :instruction_limit
+
+ def instruction_limit=(limit)
+ treshold = getInstructionObserverThreshold
+ if limit && (treshold == 0 || treshold > limit)
+ setInstructionObserverThreshold(limit)
+ end
+ @instruction_limit = limit
+ end
+
+ attr_accessor :instruction_count
+
+ def check!
+ if instruction_limit && instruction_count > instruction_limit
+ raise RunawayScriptError, "script exceeded allowable instruction count: #{instruction_limit}"
+ end
+ end
+
+ private
+
+ def reset!
+ self.instruction_count = 0
+ self.instruction_limit = nil
+ self
+ end
- def instruction_limit=(count)
- @limit = count
end
end
-
+
class ContextError < StandardError # :nodoc:
end
View
24 spec/rhino/context_spec.rb
@@ -65,5 +65,29 @@ def foo(*args); args && 'bar'; end
context.version = '1.7'
context.version.should == 1.7
end
+
+ it "should have a (shared) factory by default" do
+ context1 = Rhino::Context.new
+ context1.factory.should_not be nil
+ context1.factory.should be_a(Rhino::JS::ContextFactory)
+
+ context1.factory.should be Rhino::Context.default_factory
+
+ context2 = Rhino::Context.new
+ context2.factory.should be context1.factory
+ end
+
+ it "allows limiting instruction count" do
+ context = Rhino::Context.new :restrictable => true
+ context.instruction_limit = 100
+ lambda {
+ context.eval %Q{ for (var i = 0; i < 100; i++) Number(i).toString(); }
+ }.should raise_error(Rhino::RunawayScriptError)
+
+ context.instruction_limit = nil
+ lambda {
+ context.eval %Q{ for (var i = 0; i < 100; i++) Number(i).toString(); }
+ }.should_not raise_error(Rhino::RunawayScriptError)
+ end
end

0 comments on commit b4ac2b3

Please sign in to comment.
Something went wrong with that request. Please try again.