CrackedRuby logo

CrackedRuby

eval

Overview

Ruby's eval family of methods executes string representations of Ruby code within specified contexts. The core method Kernel#eval takes a string containing Ruby code and evaluates it as if it were written directly in the program. Ruby provides several variants including instance_eval, class_eval (alias module_eval), and eval itself.

The evaluation occurs within a binding - the execution context that determines variable scope, method availability, and the value of self. Ruby creates bindings automatically, but programmers can capture and manipulate them explicitly using Kernel#binding.

code = "2 + 3"
result = eval(code)
# => 5

x = 10
eval("x * 2")
# => 20

The eval method executes code in the current binding by default, accessing local variables and methods available at the call site. Object-specific evaluation methods like instance_eval execute code within the context of a particular object, changing the value of self and method availability.

class Calculator
  def initialize(value)
    @value = value
  end
end

calc = Calculator.new(15)
calc.instance_eval("@value * 2")
# => 30

Ruby's evaluation methods form the foundation for many metaprogramming techniques including dynamic method definition, configuration DSLs, and template systems. The methods differ primarily in their execution context and how they handle the binding.

Basic Usage

The Kernel#eval method accepts a string of Ruby code and executes it within the current or specified binding. The method returns the value of the last expression evaluated, following standard Ruby return semantics.

# Simple arithmetic evaluation
eval("5 + 3")
# => 8

# Variable access in current scope
name = "Ruby"
greeting = eval('"Hello, #{name}!"')
# => "Hello, Ruby!"

# Method calls within evaluation
def multiply(a, b)
  a * b
end

eval("multiply(4, 6)")
# => 24

The instance_eval method executes code within the context of a specific object, changing self to that object and providing access to its instance variables and private methods.

class Person
  def initialize(name)
    @name = name
  end
  
  private
  
  def secret_method
    "Secret: #{@name}"
  end
end

person = Person.new("Alice")
person.instance_eval do
  @name.upcase
end
# => "ALICE"

person.instance_eval("secret_method")
# => "Secret: Alice"

Class and module evaluation using class_eval (or module_eval) executes code within the context of a class or module, allowing dynamic method definitions and constant access.

class Calculator
end

Calculator.class_eval do
  def add(a, b)
    a + b
  end
  
  PRECISION = 2
end

calc = Calculator.new
calc.add(2.5, 3.7)
# => 6.2

Calculator::PRECISION
# => 2

Binding objects capture execution context for later evaluation. The binding method returns a Binding object representing the current scope, which can be passed to eval for execution in that specific context.

def create_binding(value)
  local_var = value
  binding
end

stored_binding = create_binding(42)
eval("local_var * 2", stored_binding)
# => 84

# The local variable isn't accessible outside the binding
begin
  local_var
rescue NameError => e
  e.message
end
# => "undefined local variable or method `local_var'"

Advanced Usage

Dynamic method definition through eval enables runtime creation of methods based on configuration, data, or patterns. The evaluation context determines where methods are defined and their accessibility.

class ApiClient
  ENDPOINTS = %w[users posts comments]
  
  ENDPOINTS.each do |endpoint|
    class_eval <<-RUBY
      def fetch_#{endpoint}(id = nil)
        path = id ? "/#{endpoint}/\#{id}" : "/#{endpoint}"
        make_request(path)
      end
      
      def create_#{endpoint}(data)
        make_request("/#{endpoint}", :post, data)
      end
    RUBY
  end
  
  private
  
  def make_request(path, method = :get, data = nil)
    "#{method.upcase} #{path}" + (data ? " with #{data}" : "")
  end
end

client = ApiClient.new
client.fetch_users(123)
# => "GET /users/123"

client.create_posts({title: "New Post"})
# => "POST /posts with {:title=>\"New Post\"}"

Template processing systems use eval with binding objects to render dynamic content. The binding provides variable access while maintaining isolation between different evaluation contexts.

class TemplateRenderer
  def initialize(template)
    @template = template
  end
  
  def render(variables = {})
    template_binding = create_template_binding(variables)
    eval(@template, template_binding)
  end
  
  private
  
  def create_template_binding(variables)
    variables.each do |name, value|
      eval("#{name} = #{value.inspect}", binding)
    end
    binding
  end
end

template = TemplateRenderer.new('"Name: #{name}, Age: #{age}"')
result = template.render(name: "Bob", age: 30)
# => "Name: Bob, Age: 30"

Configuration DSLs leverage instance_eval to provide clean syntax for object setup. The evaluation context allows direct method calls without explicit receiver specification.

class DatabaseConfig
  attr_reader :host, :port, :credentials
  
  def initialize
    @credentials = {}
  end
  
  def configure(&block)
    instance_eval(&block) if block_given?
    self
  end
  
  def host(value)
    @host = value
  end
  
  def port(value)
    @port = value
  end
  
  def username(value)
    @credentials[:username] = value
  end
  
  def password(value)
    @credentials[:password] = value
  end
end

config = DatabaseConfig.new.configure do
  host "localhost"
  port 5432
  username "admin"
  password "secret"
end

config.host
# => "localhost"

config.credentials
# => {:username=>"admin", :password=>"secret"}

Metaprogramming libraries often combine multiple evaluation methods to create flexible APIs. The choice of evaluation method determines the execution context and available scope.

class AttributeDefiner
  def self.define_attributes(target_class, attributes)
    attributes.each do |attr_name, options|
      target_class.class_eval do
        attr_reader attr_name
        
        if options[:calculated]
          define_method("calculated_#{attr_name}") do
            instance_eval(options[:calculated])
          end
        end
        
        if options[:validation]
          define_method("validate_#{attr_name}") do |value|
            validation_binding = binding
            eval("value = #{value.inspect}; #{options[:validation]}", validation_binding)
          end
        end
      end
    end
  end
end

class Product
  def initialize(price, quantity)
    @price = price
    @quantity = quantity
  end
end

AttributeDefiner.define_attributes(Product, {
  total_value: {
    calculated: "@price * @quantity"
  },
  price: {
    validation: "value > 0 && value < 10000"
  }
})

product = Product.new(25.99, 3)
product.calculated_total_value
# => 77.97

product.validate_price(50.0)
# => true

product.validate_price(-10)
# => false

Error Handling & Debugging

String evaluation introduces syntax errors, runtime exceptions, and security vulnerabilities that require careful handling. Ruby raises SyntaxError when the evaluated string contains invalid Ruby syntax.

def safe_eval(code, context = binding)
  eval(code, context)
rescue SyntaxError => e
  { error: "Syntax error: #{e.message}" }
rescue StandardError => e
  { error: "Runtime error: #{e.message}" }
end

safe_eval("2 + 2")
# => 4

safe_eval("2 +")
# => {:error=>"Syntax error: (eval):1: syntax error, unexpected end-of-input"}

safe_eval("undefined_method")
# => {:error=>"Runtime error: undefined local variable or method `undefined_method'"}

Input validation prevents code injection attacks when evaluating user-provided strings. Whitelist approaches restrict allowed operations while blacklist approaches block dangerous patterns.

class SafeCalculator
  ALLOWED_OPERATIONS = %w[+ - * / % ** ( ) . to_i to_f abs round ceil floor]
  ALLOWED_NUMBERS = /\A\d+(\.\d+)?\z/
  DANGEROUS_PATTERNS = [
    /system|exec|`|eval|send|method|const_get|class_eval|instance_eval/i,
    /File|IO|Dir|Process|Kernel|Object/i,
    /@|::/
  ]
  
  def self.evaluate(expression)
    return { error: "Empty expression" } if expression.strip.empty?
    
    if dangerous_code?(expression)
      return { error: "Prohibited operation detected" }
    end
    
    unless valid_tokens?(expression)
      return { error: "Invalid characters or operations" }
    end
    
    begin
      result = eval(expression)
      { result: result }
    rescue StandardError => e
      { error: "Calculation error: #{e.message}" }
    end
  end
  
  private
  
  def self.dangerous_code?(expression)
    DANGEROUS_PATTERNS.any? { |pattern| expression.match?(pattern) }
  end
  
  def self.valid_tokens?(expression)
    tokens = expression.scan(/\w+|[+\-*\/%()]/)
    tokens.all? do |token|
      ALLOWED_OPERATIONS.include?(token) || token.match?(ALLOWED_NUMBERS)
    end
  end
end

SafeCalculator.evaluate("2 + 3 * 4")
# => {:result=>14}

SafeCalculator.evaluate("system('rm -rf /')")
# => {:error=>"Prohibited operation detected"}

SafeCalculator.evaluate("File.delete('important.txt')")
# => {:error=>"Prohibited operation detected"}

Binding isolation prevents evaluation from accessing or modifying unintended variables. Creating clean bindings limits the scope available to evaluated code.

class IsolatedEvaluator
  def self.create_clean_binding
    Module.new.module_eval { binding }
  end
  
  def self.evaluate_with_context(code, allowed_variables = {})
    clean_binding = create_clean_binding
    
    allowed_variables.each do |name, value|
      clean_binding.local_variable_set(name, value)
    end
    
    begin
      eval(code, clean_binding)
    rescue StandardError => e
      raise SecurityError, "Evaluation failed: #{e.message}"
    end
  end
end

# This works - only allowed variables accessible
result = IsolatedEvaluator.evaluate_with_context(
  "x + y", 
  { x: 10, y: 5 }
)
# => 15

# This fails - no access to external variables
secret_value = "confidential"
begin
  IsolatedEvaluator.evaluate_with_context("secret_value")
rescue SecurityError => e
  e.message
end
# => "Evaluation failed: undefined local variable or method `secret_value'"

Timeout mechanisms prevent infinite loops and resource exhaustion in evaluated code. Ruby's Timeout module provides execution time limits.

require 'timeout'

class TimedEvaluator
  DEFAULT_TIMEOUT = 5
  
  def self.evaluate(code, timeout_seconds = DEFAULT_TIMEOUT)
    Timeout.timeout(timeout_seconds) do
      eval(code)
    end
  rescue Timeout::Error
    { error: "Evaluation timed out after #{timeout_seconds} seconds" }
  rescue StandardError => e
    { error: "Error: #{e.message}" }
  end
end

TimedEvaluator.evaluate("2 + 2")
# => 4

TimedEvaluator.evaluate("loop { sleep 1 }", 2)
# => {:error=>"Evaluation timed out after 2 seconds"}

Common Pitfalls

Variable scope confusion occurs when evaluated code expects different variable availability than the current binding provides. Local variables, instance variables, and constants follow different scoping rules during evaluation.

class ScopeDemo
  def initialize
    @instance_var = "instance"
  end
  
  def demonstrate_scope_issues
    local_var = "local"
    
    # This works - access to local variable
    result1 = eval("local_var")
    
    # This fails - instance_eval changes scope
    begin
      self.instance_eval("local_var")
    rescue NameError
      result2 = "local variable not accessible in instance_eval"
    end
    
    # This works - instance variable accessible in instance_eval
    result3 = self.instance_eval("@instance_var")
    
    [result1, result2, result3]
  end
end

demo = ScopeDemo.new
demo.demonstrate_scope_issues
# => ["local", "local variable not accessible in instance_eval", "instance"]

String versus block evaluation produces different behavior, particularly with variable capture and performance. Blocks capture their lexical scope while strings are parsed and compiled each time.

class EvaluationComparison
  def compare_approaches(value)
    results = {}
    
    # String evaluation - parsed each time
    start_time = Time.now
    1000.times { eval("#{value} * 2") }
    results[:string_time] = Time.now - start_time
    
    # Block evaluation - compiled once
    start_time = Time.now
    block = proc { value * 2 }
    1000.times { block.call }
    results[:block_time] = Time.now - start_time
    
    results[:performance_ratio] = results[:string_time] / results[:block_time]
    results
  end
end

comparison = EvaluationComparison.new
results = comparison.compare_approaches(42)
# Block evaluation is typically 10-100x faster than string evaluation

Security vulnerabilities arise when user input reaches evaluation without proper sanitization. Code injection attacks can execute arbitrary system commands or access sensitive data.

# DANGEROUS - never do this with user input
def dangerous_calculator(expression)
  eval(expression)  # User could input: "system('rm -rf /')"
end

# BETTER - input validation and restricted context
def safer_calculator(expression)
  # Validate input contains only safe characters
  unless expression.match?(/\A[\d\s+\-*\/\(\)\.]+\z/)
    raise ArgumentError, "Invalid characters in expression"
  end
  
  # Use restricted binding
  restricted_binding = BasicObject.new.instance_eval { binding }
  eval(expression, restricted_binding)
end

safer_calculator("2 + 3 * 4")
# => 14

begin
  safer_calculator("system('echo hacked')")
rescue ArgumentError => e
  e.message
end
# => "Invalid characters in expression"

Method definition context confusion occurs when eval executes in unexpected scopes, creating methods in wrong classes or modules.

class MethodDefinitionIssues
  def self.demonstrate_context_problems
    # This defines method on the class
    class_eval("def class_method; 'class method'; end")
    
    # This attempts to define on instance but fails
    instance = new
    begin
      instance.eval("def instance_method; 'instance method'; end")
    rescue NoMethodError
      puts "Cannot define methods with eval on instances"
    end
    
    # Correct approach for instance method definition
    instance.instance_eval do
      def singleton_method
        "singleton method"
      end
    end
    
    {
      class_responds: respond_to?(:class_method),
      instance_responds: instance.respond_to?(:singleton_method)
    }
  end
end

MethodDefinitionIssues.demonstrate_context_problems
# => {:class_responds=>true, :instance_responds=>true}

Binding lifetime issues occur when binding objects retain references to large objects or sensitive data longer than necessary, causing memory leaks or security concerns.

class BindingLifetimeDemo
  def create_problematic_binding
    large_data = Array.new(1000000) { rand }
    sensitive_password = "secret123"
    
    # Binding captures all local variables
    binding
  end
  
  def create_clean_binding(needed_value)
    # Only capture what's needed
    value = needed_value
    # Clear other variables before creating binding
    binding
  end
  
  def demonstrate_memory_impact
    # Problematic - binding holds references to large objects
    binding1 = create_problematic_binding
    
    # Better - binding only holds necessary data  
    binding2 = create_clean_binding(42)
    
    # Bindings retain their variable references indefinitely
    eval("large_data.size rescue 'not accessible'", binding1)
    # => 1000000
    
    eval("large_data.size rescue 'not accessible'", binding2)
    # => "not accessible"
  end
end

Reference

Core Evaluation Methods

Method Parameters Returns Description
Kernel#eval(string, binding=nil, filename="(eval)", lineno=1) string (String), binding (Binding), filename (String), lineno (Integer) Object Evaluates Ruby code string in specified or current binding
Object#instance_eval(string, filename="(eval)", lineno=1) string (String), filename (String), lineno (Integer) Object Evaluates string in context of receiver object
Object#instance_eval { block } block (Proc) Object Evaluates block in context of receiver object
Module#class_eval(string, filename="(eval)", lineno=1) string (String), filename (String), lineno (Integer) Object Evaluates string in context of class/module
Module#class_eval { block } block (Proc) Object Evaluates block in context of class/module
Module#module_eval Same as class_eval Object Alias for class_eval

Binding Methods

Method Parameters Returns Description
Kernel#binding None Binding Returns binding object for current execution context
Binding#eval(string, filename="(eval)", lineno=1) string (String), filename (String), lineno (Integer) Object Evaluates string within this binding
Binding#local_variables None Array Returns array of local variable names in binding
Binding#local_variable_get(symbol) symbol (Symbol) Object Gets value of local variable from binding
Binding#local_variable_set(symbol, obj) symbol (Symbol), obj (Object) Object Sets local variable in binding
Binding#local_variable_defined?(symbol) symbol (Symbol) Boolean Checks if local variable exists in binding
Binding#receiver None Object Returns the receiver object (value of self)

Exception Classes

Exception Inheritance Common Causes
SyntaxError StandardError Invalid Ruby syntax in evaluated string
NameError StandardError Reference to undefined variable or method
NoMethodError NameError Method call on object that doesn't respond
ArgumentError StandardError Wrong number or type of arguments
SecurityError StandardError Security policy violations during evaluation

Security Considerations

Risk Mitigation Strategy Implementation
Code Injection Input validation Whitelist allowed characters/operations
System Access Restricted binding Use clean binding without system access
Resource Exhaustion Timeout limits Wrap evaluation in Timeout block
Memory Leaks Binding cleanup Clear unnecessary variables before creating binding
Data Exposure Variable isolation Use module-based clean bindings

Performance Characteristics

Operation Relative Cost Optimization Strategy
String eval High (parsing + compilation) Cache compiled code when possible
Block eval Low (pre-compiled) Prefer blocks over strings
Binding creation Medium Reuse bindings when safe
Variable access Low Direct access faster than eval
Method definition High (class modification) Batch multiple definitions

Evaluation Context Matrix

Method self Value Local Variables Instance Variables Constants Method Definition Target
eval Current self Current scope Current object Current scope Current class/module
instance_eval Receiver object Not accessible Receiver's vars Current scope Receiver's singleton class
class_eval Class/module Not accessible Not accessible Class/module scope Class/module
eval(string, binding) Binding's self Binding's scope Binding's object Binding's scope Binding's context