CrackedRuby logo

CrackedRuby

eval and Binding

A comprehensive guide to Ruby's eval method and Binding class for dynamic code execution and context capturing.

Core Modules Kernel Module
3.1.6

Overview

Ruby's eval method executes a string containing Ruby code within a specified context, while the Binding class captures the execution context including local variables, instance variables, and method definitions. Together, these features enable dynamic code execution and metaprogramming patterns.

The eval method accepts a string of Ruby code and optionally a Binding object that defines the execution context. When no binding is provided, eval executes code in the current context. The Binding class encapsulates the execution environment at a specific point in the program, preserving variable scope and method accessibility.

code = "x + y"
x = 10
y = 20
result = eval(code)
# => 30

Binding objects are created using the binding method, which captures the current execution context:

def create_context
  local_var = "captured"
  binding
end

context = create_context
eval("local_var", context)
# => "captured"

Ruby provides several methods to obtain bindings: binding returns the current context, Proc#binding returns the context where a proc was defined, and Method#binding returns the context of method definitions. Each binding maintains its own variable scope and method visibility.

Basic Usage

The eval method transforms strings into executable Ruby code. Without additional arguments, eval executes code in the current context, accessing local variables and methods:

def greet(name)
  template = "Hello, #{name}!"
  eval("template.upcase")
end

greet("Ruby")
# => "HELLO, RUBY!"

Binding objects capture the complete execution state. The binding method creates a binding for the current context:

def calculator(a, b)
  operation = "+"
  current_binding = binding
  
  code = "a #{operation} b"
  eval(code, current_binding)
end

calculator(15, 25)
# => 40

Bindings preserve local variable values at the time of creation. Variables assigned after binding creation remain inaccessible unless explicitly updated:

x = 100
saved_context = binding
x = 200

eval("x", saved_context)
# => 100

eval("x = 300", saved_context)
eval("x", saved_context)
# => 300

Class and module contexts create bindings with different scoping rules. Instance variables and class variables become accessible within their respective binding contexts:

class Calculator
  def initialize
    @base = 10
  end
  
  def compute(formula)
    eval(formula, binding)
  end
end

calc = Calculator.new
calc.compute("@base * 5")
# => 50

Advanced Usage

Complex metaprogramming scenarios require careful binding manipulation. Closures maintain access to their definition context through their associated bindings:

def create_evaluator(context_vars)
  context_vars.each { |k, v| eval("#{k} = #{v.inspect}", binding) }
  
  lambda do |expression|
    eval(expression, binding)
  end
end

evaluator = create_evaluator(x: 10, y: 20, z: 30)
evaluator.call("x * y + z")
# => 230

Method definitions within eval create methods in the appropriate scope. The binding context determines where methods are defined:

class DynamicMethods
  def self.add_method(name, body)
    class_eval <<-RUBY
      def #{name}
        #{body}
      end
    RUBY
  end
end

DynamicMethods.add_method("triple", "value * 3")
obj = DynamicMethods.new
obj.define_singleton_method(:value) { 7 }
obj.triple
# => 21

Nested binding contexts create layered scoping environments. Inner contexts inherit outer variables while maintaining isolation:

def nested_evaluation
  outer = "outer_scope"
  
  inner_proc = proc do
    inner = "inner_scope"
    eval("outer + ' -> ' + inner", binding)
  end
  
  inner_proc.call
end

nested_evaluation
# => "outer_scope -> inner_scope"

Template systems use binding contexts to provide variable access within templates. The binding captures the template environment:

class TemplateEngine
  def initialize(context_object)
    @context = context_object
  end
  
  def render(template)
    eval(template, @context.instance_eval { binding })
  end
end

class User
  def initialize(name, age)
    @name, @age = name, age
  end
  
  private
  
  def greeting
    "Hello, I'm #{@name}"
  end
end

user = User.new("Alice", 30)
engine = TemplateEngine.new(user)
engine.render("greeting + ' and I am ' + @age.to_s + ' years old'")
# => "Hello, I'm Alice and I am 30 years old"

Error Handling & Debugging

SyntaxError occurs when eval receives malformed Ruby code. The error includes the problematic code and line information:

begin
  eval("invalid syntax here @@")
rescue SyntaxError => e
  puts e.message
  # => (eval):1: syntax error, unexpected end-of-input
end

NameError arises when eval references undefined variables or methods within the binding context:

binding_context = binding
begin
  eval("undefined_variable", binding_context)
rescue NameError => e
  puts e.message
  # => undefined local variable or method `undefined_variable'
end

Debugging dynamically executed code requires careful error context preservation. Stack traces from eval show the evaluation point rather than the original code location:

def debug_eval(code, context = binding)
  eval(code, context)
rescue StandardError => e
  puts "Error in: #{code}"
  puts "Context: #{context.eval('local_variables')}"
  puts "Error: #{e.class} - #{e.message}"
  raise
end

def test_method
  local_var = "test"
  debug_eval("raise 'Custom error in eval'")
end

begin
  test_method
rescue RuntimeError => e
  # Shows debugging information before re-raising
end

Variable scope debugging within bindings requires introspection methods. The binding provides access to local variables and their current values:

def inspect_binding_context(binding_obj)
  {
    local_variables: binding_obj.local_variables,
    local_values: binding_obj.local_variables.map { |var| 
      [var, binding_obj.local_variable_get(var)] 
    }.to_h
  }
end

def sample_method
  x = 10
  y = "hello"
  context = binding
  inspect_binding_context(context)
end

sample_method
# => {
#      local_variables: [:x, :y, :context], 
#      local_values: {:x=>10, :y=>"hello", :context=>#<Binding:...>}
#    }

Common Pitfalls

Security vulnerabilities represent the most critical pitfall with eval. User-provided input executed through eval enables code injection attacks:

# DANGEROUS - Never do this
def unsafe_calculator(expression)
  eval(expression)  # Allows arbitrary code execution
end

# User input: "system('rm -rf /')" would be devastating

Safe evaluation requires input sanitization and restricted contexts. Create isolated binding contexts without dangerous methods:

class SafeEvaluator
  ALLOWED_OPERATIONS = %w[+ - * / % ** < > <= >= == !=].freeze
  
  def self.safe_eval(expression, variables = {})
    # Validate expression contains only safe operations
    unless expression.match?(/\A[\w\s#{Regexp.escape(ALLOWED_OPERATIONS.join)}()]+\z/)
      raise ArgumentError, "Unsafe expression"
    end
    
    # Create restricted context
    context = Object.new.instance_eval do
      variables.each { |k, v| define_singleton_method(k) { v } }
      binding
    end
    
    eval(expression, context)
  end
end

Variable scope confusion occurs when binding contexts don't contain expected variables. Bindings capture variable state at creation time, not current state:

# Confusing behavior
results = []
(1..3).each do |i|
  # All bindings capture the same 'i' reference
  results << binding
end

# Later evaluation shows unexpected results
results.map { |b| eval("i", b) }
# => [3, 3, 3] - all show final value of i

Performance degradation affects applications using eval extensively. String evaluation overhead and lack of optimization create bottlenecks:

# Inefficient repeated evaluation
def slow_template(data)
  template = "#{data[:name]} is #{data[:age]} years old"
  1000.times { eval("\"#{template}\"") }
end

# Better approach using direct interpolation
def fast_template(data)
  1000.times { "#{data[:name]} is #{data[:age]} years old" }
end

Context leakage occurs when bindings retain references to large objects or sensitive data. Bindings prevent garbage collection of referenced objects:

def create_leaky_binding
  large_data = Array.new(1_000_000) { rand }
  sensitive_password = "secret123"
  
  # Binding retains references to large_data and sensitive_password
  binding
end

# These objects remain in memory through the binding
leaked_context = create_leaky_binding
# large_data and sensitive_password cannot be garbage collected

Production Patterns

Domain Specific Languages (DSLs) use eval and binding for configuration and rule definition. The binding provides variable context for DSL execution:

class ConfigurationDSL
  def initialize(&block)
    @config = {}
    instance_eval(&block) if block_given?
  end
  
  def set(key, value)
    @config[key] = value
  end
  
  def get(key)
    @config[key]
  end
  
  def conditional(condition, &block)
    instance_eval(&block) if eval(condition, binding)
  end
end

config = ConfigurationDSL.new do
  set :database_url, "postgresql://localhost/app"
  set :debug_mode, true
  
  conditional "get(:debug_mode)" do
    set :log_level, "DEBUG"
  end
end

Template systems in web frameworks use binding contexts to provide view variables. The binding contains instance variables and helper methods:

class ViewRenderer
  def initialize(view_context)
    @context = view_context
  end
  
  def render_template(template_string)
    binding_context = @context.instance_eval { binding }
    eval(template_string, binding_context)
  end
end

class ViewContext
  def initialize(user:, posts:)
    @user = user
    @posts = posts
  end
  
  private
  
  def format_date(date)
    date.strftime("%B %d, %Y")
  end
  
  def pluralize(count, word)
    count == 1 ? "#{count} #{word}" : "#{count} #{word}s"
  end
end

# Usage in template
template = '"Welcome #{@user.name}! You have #{pluralize(@posts.count, \"post\")}."'
context = ViewContext.new(user: OpenStruct.new(name: "Alice"), posts: [1, 2, 3])
renderer = ViewRenderer.new(context)
renderer.render_template(template)
# => "Welcome Alice! You have 3 posts."

Dynamic method definition in frameworks uses class_eval with binding contexts. This pattern enables plugin architectures and runtime configuration:

module DynamicAttributes
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def attr_calculated(name, formula)
      define_method(name) do
        instance_eval(formula)
      end
    end
    
    def attr_validated(name, validation_rule)
      attr_accessor name
      
      define_method("#{name}=") do |value|
        unless instance_exec(value) { |v| eval(validation_rule) }
          raise ArgumentError, "Invalid #{name}: #{value}"
        end
        instance_variable_set("@#{name}", value)
      end
    end
  end
end

class Product
  include DynamicAttributes
  
  attr_accessor :price, :tax_rate
  attr_calculated :total_price, "@price * (1 + @tax_rate)"
  attr_validated :price, "v > 0 && v < 10000"
end

product = Product.new
product.tax_rate = 0.1
product.price = 100
product.total_price
# => 110.0

Reference

Core Methods

Method Parameters Returns Description
eval(string, binding=nil, filename="(eval)", lineno=1) string (String), binding (Binding), filename (String), lineno (Integer) Object Evaluates Ruby code string in specified binding context
binding None Binding Returns current execution context as Binding object
Binding#eval(string, filename="(eval)", lineno=1) string (String), filename (String), lineno (Integer) Object Evaluates string within the binding's context
Binding#local_variables None Array<Symbol> Returns array of local variable names in binding
Binding#local_variable_get(symbol) symbol (Symbol) Object Gets value of local variable by name
Binding#local_variable_set(symbol, obj) symbol (Symbol), obj (Object) Object Sets local variable value, returns the value
Binding#local_variable_defined?(symbol) symbol (Symbol) Boolean Checks if local variable is defined in binding

Class Methods

Method Parameters Returns Description
Object#instance_eval(string, filename="(eval)", lineno=1) string (String), filename (String), lineno (Integer) Object Evaluates string in context of receiver object
Object#instance_exec(*args, &block) *args (Object), &block (Proc) Object Executes block in context of receiver with arguments
Module#class_eval(string, filename="(eval)", lineno=1) string (String), filename (String), lineno (Integer) Object Evaluates string in context of class/module
Module#module_eval(string, filename="(eval)", lineno=1) string (String), filename (String), lineno (Integer) Object Alias for class_eval
Proc#binding None Binding Returns binding context where proc was defined

Common Exceptions

Exception Trigger Condition Description
SyntaxError Malformed Ruby syntax in eval string Raised when string contains invalid Ruby code
NameError Reference to undefined variable/method Occurs when eval accesses non-existent identifiers
NoMethodError Method call on inappropriate object Subclass of NameError for method-specific errors
ArgumentError Invalid argument to binding methods Raised by local_variable_get with invalid symbol
SecurityError Tainted string evaluation (safe level) Historical security restriction in older Ruby versions

Binding Context Access

Context Element Access Method Returns Description
Local Variables local_variables Array<Symbol> Names of local variables in scope
Variable Value local_variable_get(name) Object Value of specific local variable
Variable Check local_variable_defined?(name) Boolean Whether local variable exists
Self Reference eval("self") Object The receiver object in binding context
Instance Variables eval("instance_variables") Array<Symbol> Instance variables accessible in binding
Class Variables eval("self.class.class_variables") Array<Symbol> Class variables visible in context