Isolated Code Execution

Every Ruby programmer is familiar with irban interactive Ruby shell, aka REPL (Read-Eval-Print Loop). It is a handy tool for discovering how Ruby language features or libraries work. It has some limitations though. It is entirely written in Ruby and so you might bump into a problem like this:

C:\Program Files\Ruby\bin> irb
irb(main):001:0> class Fixnum
irb(main):002:1>   remove_method :+
irb(main):003:1* end
=> Fixnum
irb(main):004:0> 1
C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb/input-method.rb:99:in `gets': undefined method `+' for
3:Fixnum (NoMethodError)
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb.rb:132:in `eval_input'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb.rb:259:in `signal_status'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb.rb:131:in `eval_input'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb/ruby-lex.rb:189:in `call'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb/ruby-lex.rb:189:in `buf_input'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb/ruby-lex.rb:104:in `getc'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb/slex.rb:206:in `match_io'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb/slex.rb:76:in `match'
         ... 8 levels...
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb.rb:70:in `start'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb.rb:69:in `catch'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/lib/ruby/1.8/irb.rb:69:in `start'
        from C:/M1/Merlin/External/Languages/Ruby/ruby-1.8.6/bin/irb:13 
C:\Program Files\Ruby\bin> 

Oops. What happened? We removed method “+” from Fixnum class and, accidentally, irb itself uses that method somewhere in the REPL implementation. The solution is obvious. Run any user code typed in an isolated environment (or “virtual machine”) different from the one that drives the REPL. Let’s take a look how to do this in IronRuby.

Hosting API

The Dynamic Language Runtime (DLR), which IronRuby is built on top of, provides API for hosting dynamic languages in an arbitrary .NET application (see Hosting API specification). You only need to know a few concepts to use this API in a powerful way. The first is script runtime. Each instance of ScriptRuntime class provides an environment for script execution. The runtime hosts scripting language engines. A single runtime can host engines of multiple DLR languages (IronRuby, IronPython, etc.), at most one engine for each language. Each engine is represented by an instance of ScriptEngine class. It holds on a global state that the particular language defines. IronRuby engine, for example, maintains a dictionary of global variables, objects that represent Ruby modules and classes, the list of loaded Ruby files, etc. An application can create any number of ScriptRuntimes and thus any number of IronRuby or IronPython “virtual machines” in a single process.

Hosting API is implemented in C# in Microsoft.Scripting.dll assembly distributed with IronRuby. IronRuby has a rich support for .NET interop, so we could load this assembly and use the Hosting API classes directly from IronRuby scripts. Although it would be simple we made it even easier: IronRuby comes with IronRuby module that provides basic hosting API specialized for Ruby.

Ruby REPL Based on Hosting API

A very simple REPL in Ruby might read as follows.

# load IronRuby library
require 'IronRuby'

# create a new IronRuby engine and a new runtime:
engine = IronRuby.create_engine

while true  
  print "> "
  code = gets
  break if code.nil?
  begin
    p engine.execute(code)
  rescue Exception
    puts $!
  end
end

The REPL script itself runs in a script runtime created by ir.exe host. The code typed in the loop is evaluated in the runtime created by IronRuby#create_engine. The result of ScriptEngine#execute is sent to the calling runtime and printed out via Kernel#p method.

C:\IronRuby\Merlin\Main\Bin\Debug\ir.exe repl.rb
> 1+1
2
> Fixnum.send :remove_method, :+
Fixnum@2
> 1+1
undefined method `+' for 1:Fixnum
> 2.times { |i| puts i }
0
1
2
> ^Z

Note that there are as many class objects for each class as there are runtimes in which the class is used. IronRuby appends “@n” to the name of a class that comes from a different runtime, whose id is n, to differentiate it from the class of the same name in the current runtime. Hence Module#remove_method, which returns the class the method has been removed from, returns “Fixnum@2”.

There are many ways in which we can make our REPL better. The most serious deficiency can be easily demonstrated:

> x = 1
1
> puts x
undefined method `x' for main:Object

Why we got an exception? We defined a local variable x in the first line, so why is it not available in the second line? We are missing some kind of variable dictionary shared among multiple snippet executions. If we used Kernel#eval in Ruby we would pass an instance of Binding to it. The Hosting API uses a similar concept: script scope. I’m going to explain DLR scopes in a separate blog post in detail. Let’s just say for now that IronRuby associates a top-level Ruby binding with each ScriptScope class instance against which it executes code. We need to make a very simple change in our REPL script to use DLR scopes:

# github: repl.rb
require 'IronRuby'
engine = IronRuby.create_engine
scope = engine.create_scope

while true  
  print "> "
  code = gets
  break if code.nil?
  begin
    p engine.execute(code, scope) 
  rescue Exception
    puts $!
  end
end

Local variables work as expected now (actually, the fix in IronRuby source code that makes this work is not yet committed to the public repo, it’s coming soon though):

> x = 1
1
> puts x
1
nil

Another feature our simple REPL lacks is handling of multi-line expressions and statements. Let’s keep that and more for the next blog post.

Advertisements

3 Responses to “Isolated Code Execution”

  1. Jirapong Says:

    Tomas, This is awesome! I’m looking forward your next post about DLR Scopes.

  2. Random T. Says:

    After reading the article, I just feel that I really need more info. Could you share some resources please?

  3. InderryMync Says:

    Excellent article, amazing looking blog, added it to my favs.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: