diff --git a/lib/rhino/context.rb b/lib/rhino/context.rb index b17cc73..6868e81 100644 --- a/lib/rhino/context.rb +++ b/lib/rhino/context.rb @@ -46,9 +46,18 @@ module Rhino 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. @@ -56,8 +65,9 @@ module Rhino # * :with - use this ruby object as the root scope for all javascript that is evaluated # * :java - 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 @@ module Rhino 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 @@ module Rhino end 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 - def instruction_limit=(limit) - @native.setInstructionObserverThreshold(limit) - @factory.instruction_limit = limit + # 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. + def 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 @@ module Rhino # 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 @@ module Rhino 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 @@ module Rhino 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 @@ module Rhino 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 diff --git a/spec/rhino/context_spec.rb b/spec/rhino/context_spec.rb index 7ff0f7a..7d76f6e 100644 --- a/spec/rhino/context_spec.rb +++ b/spec/rhino/context_spec.rb @@ -65,5 +65,29 @@ describe Rhino::Context do 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 \ No newline at end of file