CrackedRuby logo

CrackedRuby

Runtime Code Modification

Runtime code modification in Ruby allows dynamic alteration of classes, modules, and methods during program execution.

Metaprogramming Code Generation
5.8.3

Overview

Ruby provides extensive runtime code modification capabilities through its metaprogramming features. Classes and modules remain open throughout program execution, allowing developers to add, remove, or modify methods and constants dynamically. This flexibility enables powerful patterns like monkey patching, dynamic method creation, and runtime API adaptation.

The core mechanism operates through Ruby's object model where classes are themselves objects that can be modified. Every class and module maintains method tables that can be altered at runtime using built-in methods like define_method, alias_method, remove_method, and undef_method.

class User
  def name
    @name
  end
end

# Add method at runtime
User.class_eval do
  def email
    @email
  end
end

user = User.new
user.respond_to?(:email)  # => true

Ruby distinguishes between several modification approaches. Class reopening allows adding methods to existing classes. Method aliasing creates new names for existing methods while preserving originals. Method removal deletes methods from the inheritance chain. Method undefinition prevents method calls entirely, even from superclasses.

class String
  alias_method :original_reverse, :reverse
  
  def reverse
    "Modified: #{original_reverse}"
  end
end

"hello".reverse  # => "Modified: olleh"

Module inclusion, prepending, and extension provide additional runtime modification vectors. Including modules adds instance methods, prepending inserts modules earlier in the method lookup chain, and extending adds singleton methods to specific objects.

module Timestamps
  def created_at
    @created_at ||= Time.now
  end
end

class Article
end

# Runtime module inclusion
Article.include(Timestamps)
Article.new.created_at  # => 2025-08-31 ...

Basic Usage

Method definition at runtime uses define_method to create methods programmatically. This approach proves useful for generating similar methods or creating methods based on configuration data.

class APIClient
  %w[get post put delete].each do |verb|
    define_method(verb) do |path, params = {}|
      request(verb.upcase, path, params)
    end
  end
  
  private
  
  def request(method, path, params)
    "#{method} #{path} with #{params}"
  end
end

client = APIClient.new
client.get("/users", limit: 10)  # => "GET /users with {:limit=>10}"

Class reopening modifies existing classes by defining additional methods or overriding existing ones. This technique commonly appears in library extensions and framework customizations.

class Hash
  def deep_merge(other_hash)
    merge(other_hash) do |key, old_val, new_val|
      if old_val.is_a?(Hash) && new_val.is_a?(Hash)
        old_val.deep_merge(new_val)
      else
        new_val
      end
    end
  end
end

hash1 = { a: { b: 1, c: 2 } }
hash2 = { a: { c: 3, d: 4 } }
hash1.deep_merge(hash2)  # => { a: { b: 1, c: 3, d: 4 } }

Method aliasing preserves original behavior while enabling method enhancement or decoration. The pattern creates a backup of the original method before redefining it.

class Logger
  def log(message)
    puts message
  end
  
  alias_method :original_log, :log
  
  def log(message)
    original_log("[#{Time.now}] #{message}")
  end
end

logger = Logger.new
logger.log("Application started")  # => [2025-08-31 ...] Application started

Runtime constant modification changes class and module constants during execution. Ruby issues warnings for constant redefinition but allows the operation.

class Config
  API_VERSION = "v1"
end

puts Config::API_VERSION  # => "v1"

# Suppress warnings for demonstration
Config.send(:remove_const, :API_VERSION)
Config.const_set(:API_VERSION, "v2")

puts Config::API_VERSION  # => "v2"

Advanced Usage

Dynamic method creation based on external data sources enables flexible API generation. This pattern commonly appears in ORM libraries and configuration-driven applications.

class DynamicModel
  def initialize(schema)
    @data = {}
    
    schema.each do |field, type|
      create_accessor(field, type)
      create_validator(field, type) if type[:required]
    end
  end
  
  private
  
  def create_accessor(field, type)
    define_singleton_method(field) do
      @data[field]
    end
    
    define_singleton_method("#{field}=") do |value|
      @data[field] = convert_type(value, type[:class])
    end
  end
  
  def create_validator(field, type)
    define_singleton_method("#{field}_valid?") do
      value = @data[field]
      !value.nil? && value.is_a?(type[:class])
    end
  end
  
  def convert_type(value, klass)
    case klass
    when Integer then value.to_i
    when String then value.to_s
    when Float then value.to_f
    else value
    end
  end
end

schema = {
  name: { class: String, required: true },
  age: { class: Integer },
  email: { class: String, required: true }
}

user = DynamicModel.new(schema)
user.name = "John"
user.age = "30"
user.name_valid?  # => true
user.age         # => 30 (converted to Integer)

Method chain interception modifies method behavior throughout inheritance hierarchies. This advanced technique enables aspect-oriented programming and cross-cutting concerns.

module MethodInterceptor
  def self.included(base)
    base.extend(ClassMethods)
    base.class_variable_set(:@@intercepted_methods, {})
  end
  
  module ClassMethods
    def intercept_method(method_name, &block)
      original_method = "original_#{method_name}"
      
      alias_method original_method, method_name
      
      define_method(method_name) do |*args, **kwargs|
        result = instance_exec(*args, **kwargs, &block)
        if result == :continue
          send(original_method, *args, **kwargs)
        else
          result
        end
      end
      
      class_variable_get(:@@intercepted_methods)[method_name] = original_method
    end
  end
end

class Calculator
  include MethodInterceptor
  
  def add(a, b)
    a + b
  end
  
  def multiply(a, b)
    a * b
  end
  
  intercept_method :add do |a, b|
    if a.negative? || b.negative?
      raise ArgumentError, "Negative numbers not allowed"
    end
    :continue  # Proceed with original method
  end
  
  intercept_method :multiply do |a, b|
    return 0 if a.zero? || b.zero?
    :continue
  end
end

calc = Calculator.new
calc.add(5, 3)      # => 8
calc.multiply(0, 5)  # => 0 (intercepted)
calc.add(-1, 2)     # => ArgumentError

Conditional method definition creates methods based on runtime conditions or feature flags. This pattern enables environment-specific behavior and progressive feature rollouts.

class FeatureFlag
  @flags = {}
  
  class << self
    def enable(flag)
      @flags[flag] = true
    end
    
    def enabled?(flag)
      @flags[flag] == true
    end
  end
end

class PaymentProcessor
  def process_payment(amount)
    "Processing $#{amount}"
  end
  
  # Conditionally define enhanced method
  if FeatureFlag.enabled?(:fraud_detection)
    alias_method :process_payment_basic, :process_payment
    
    def process_payment(amount)
      return "FRAUD DETECTED" if amount > 10000
      process_payment_basic(amount)
    end
  end
  
  # Define debug methods only in development
  if ENV['RACK_ENV'] == 'development'
    def debug_payment_flow(amount)
      {
        amount: amount,
        fraud_check: amount > 10000,
        processor: self.class.name
      }
    end
  end
end

FeatureFlag.enable(:fraud_detection)
processor = PaymentProcessor.new
processor.process_payment(15000)  # => "FRAUD DETECTED"

Common Pitfalls

Method redefinition without aliasing destroys original functionality permanently. This creates brittle code when multiple libraries modify the same methods.

# PROBLEMATIC: Direct override without backup
class String
  def reverse
    upcase  # Original reverse behavior lost
  end
end

"hello".reverse  # => "HELLO" (not "olleh")

# BETTER: Alias first, then override
class String
  alias_method :original_reverse, :reverse
  
  def reverse
    "Reversed: #{original_reverse}"
  end
end

Constant modification creates memory leaks when constants reference large objects. Ruby maintains constant history, preventing garbage collection of previous values.

# PROBLEMATIC: Constant reassignment with large objects
class DataCache
  LARGE_DATASET = Array.new(100_000) { rand }
end

# Each reassignment keeps previous array in memory
5.times do |i|
  DataCache.send(:remove_const, :LARGE_DATASET)
  DataCache.const_set(:LARGE_DATASET, Array.new(100_000) { rand })
end

# BETTER: Use class variables or instance variables for mutable data
class DataCache
  @@dataset = nil
  
  def self.dataset=(data)
    @@dataset = data
  end
  
  def self.dataset
    @@dataset
  end
end

Method visibility changes during runtime create inconsistent APIs when not carefully managed across inheritance chains.

class BaseService
  private
  
  def authenticate
    "authenticated"
  end
end

class PublicService < BaseService
  # PROBLEMATIC: Making private method public
  public :authenticate
end

class RestrictedService < BaseService
  # authenticate remains private
end

# Inconsistent behavior across subclasses
PublicService.new.authenticate     # => "authenticated"
RestrictedService.new.authenticate # => NoMethodError

Dynamic method creation without proper name validation enables method name injection attacks and creates debugging difficulties.

# PROBLEMATIC: Unsafe method creation
class DynamicClass
  def create_method(name, value)
    define_singleton_method(name) { value }
  end
end

obj = DynamicClass.new
# Malicious input could create problematic methods
obj.create_method("class", "hacked")  # Overrides #class method

# BETTER: Validate method names
class SafeDynamicClass
  VALID_METHOD_NAME = /\A[a-z_][a-z0-9_]*[?!]?\z/
  
  def create_method(name, value)
    unless name.match?(VALID_METHOD_NAME)
      raise ArgumentError, "Invalid method name: #{name}"
    end
    
    if respond_to?(name) || Object.instance_methods.include?(name.to_sym)
      raise ArgumentError, "Method already exists: #{name}"
    end
    
    define_singleton_method(name) { value }
  end
end

Module inclusion order affects method resolution and can mask intended behavior when modules contain same-named methods.

module A
  def greet
    "Hello from A"
  end
end

module B
  def greet
    "Hello from B"
  end
end

class TestClass
  include A
  include B  # B's methods override A's methods
end

TestClass.new.greet  # => "Hello from B"

# Use module prepending for explicit ordering
class OrderedClass
  include A
  prepend B  # B comes first in method lookup
end

OrderedClass.ancestors  # => [B, OrderedClass, A, ...]

Thread Safety & Concurrency

Runtime method modification in concurrent environments creates race conditions when multiple threads simultaneously modify the same class. Method definition and removal operations are not atomic, potentially creating inconsistent method tables.

require 'thread'

class ThreadSafeModifier
  @modification_mutex = Mutex.new
  
  class << self
    def safe_define_method(klass, method_name, &block)
      @modification_mutex.synchronize do
        klass.define_method(method_name, &block)
      end
    end
    
    def safe_remove_method(klass, method_name)
      @modification_mutex.synchronize do
        klass.remove_method(method_name) if klass.method_defined?(method_name)
      end
    end
  end
end

# Safe concurrent method modification
threads = 10.times.map do |i|
  Thread.new do
    ThreadSafeModifier.safe_define_method(String, "method_#{i}") do
      "Thread #{i} method"
    end
  end
end

threads.each(&:join)
String.instance_methods.grep(/method_\d+/).size  # => 10 (all methods created)

Singleton method modification on shared objects requires careful synchronization to prevent method table corruption during concurrent access.

class ConcurrentSingletonManager
  def initialize
    @mutex = Mutex.new
    @objects = {}
  end
  
  def add_singleton_method(object_id, method_name, &block)
    @mutex.synchronize do
      object = @objects[object_id]
      return unless object
      
      object.define_singleton_method(method_name, &block)
    end
  end
  
  def register_object(id, object)
    @mutex.synchronize do
      @objects[id] = object
    end
  end
  
  def call_singleton_method(object_id, method_name, *args)
    object = nil
    @mutex.synchronize { object = @objects[object_id] }
    
    object&.send(method_name, *args) if object&.respond_to?(method_name)
  end
end

manager = ConcurrentSingletonManager.new
shared_object = Object.new
manager.register_object(:shared, shared_object)

# Safe concurrent singleton method operations
threads = 5.times.map do |i|
  Thread.new do
    manager.add_singleton_method(:shared, "concurrent_method_#{i}") do
      "Result from thread #{i}"
    end
  end
end

threads.each(&:join)

Constant modification during concurrent execution requires explicit synchronization since Ruby's constant warning mechanism is not thread-safe and can produce garbled output.

class ThreadSafeConstants
  @constant_mutex = Mutex.new
  
  class << self
    def modify_constant(klass, name, value)
      @constant_mutex.synchronize do
        # Capture warnings to prevent garbled output
        original_stderr = $stderr
        $stderr = StringIO.new
        
        begin
          klass.send(:remove_const, name) if klass.const_defined?(name)
          klass.const_set(name, value)
        ensure
          $stderr = original_stderr
        end
      end
    end
    
    def read_constant(klass, name)
      @constant_mutex.synchronize do
        klass.const_get(name) if klass.const_defined?(name)
      end
    end
  end
end

# Concurrent constant modification without warning pollution
class ConfigManager
  API_ENDPOINT = "https://api.example.com"
end

threads = 3.times.map do |i|
  Thread.new do
    ThreadSafeConstants.modify_constant(
      ConfigManager, 
      :API_ENDPOINT, 
      "https://api-v#{i}.example.com"
    )
  end
end

threads.each(&:join)
puts ThreadSafeConstants.read_constant(ConfigManager, :API_ENDPOINT)

Reference

Core Modification Methods

Method Parameters Returns Description
define_method(name, &block) name (Symbol/String), block Symbol Creates instance method with given name and implementation
alias_method(new_name, old_name) new_name, old_name (Symbol/String) Module Creates method alias preserving original
remove_method(name) name (Symbol/String) Module Removes method from current class, allows inheritance
undef_method(name) name (Symbol/String) Module Prevents method calls completely, ignores inheritance
method_defined?(name) name (Symbol/String) Boolean Checks if instance method exists
private_method_defined?(name) name (Symbol/String) Boolean Checks if private instance method exists

Class and Module Evaluation

Method Parameters Returns Description
class_eval(string/&block) string or block, optional filename/line Mixed Evaluates code in class context
module_eval(string/&block) string or block, optional filename/line Mixed Evaluates code in module context
instance_eval(string/&block) string or block, optional filename/line Mixed Evaluates code in object's singleton context
class_exec(*args, &block) arguments and block Mixed Evaluates block in class context with arguments
module_exec(*args, &block) arguments and block Mixed Evaluates block in module context with arguments

Constant Manipulation

Method Parameters Returns Description
const_set(name, value) name (Symbol/String), value Object Sets constant to specified value
const_get(name, inherit=true) name (Symbol/String), inherit flag Object Retrieves constant value
const_defined?(name, inherit=true) name (Symbol/String), inherit flag Boolean Checks constant existence
remove_const(name) name (Symbol/String) Object Removes constant and returns its value
constants(inherit=true) inherit flag Array Lists defined constants

Method Visibility Control

Method Parameters Returns Description
private(*method_names) method names (Symbol/String) Module Makes methods private
protected(*method_names) method names (Symbol/String) Module Makes methods protected
public(*method_names) method names (Symbol/String) Module Makes methods public
private_class_method(*method_names) method names (Symbol/String) Module Makes class methods private
public_class_method(*method_names) method names (Symbol/String) Module Makes class methods public

Module Inclusion Operations

Method Parameters Returns Description
include(*modules) modules Self Adds modules as ancestors
prepend(*modules) modules Self Inserts modules before self in ancestors
extend(*modules) modules Object Adds module methods as singleton methods
included_modules none Array Returns included modules
ancestors none Array Returns ancestor chain including modules

Singleton Method Operations

Method Parameters Returns Description
define_singleton_method(name, &block) name (Symbol/String), block Symbol Defines method on specific object
singleton_methods(all=true) include inherited flag Array Lists singleton methods
singleton_class none Class Returns object's singleton class

Hook Methods for Runtime Modification

Method Context Parameters Description
method_added(method_name) Class/Module method name Called when instance method added
method_removed(method_name) Class/Module method name Called when instance method removed
method_undefined(method_name) Class/Module method name Called when instance method undefined
singleton_method_added(method_name) Object method name Called when singleton method added
const_missing(name) Class/Module constant name Called when constant not found

Common Error Types

Exception Trigger Condition Prevention Strategy
NameError Method or constant not found Use defined? or respond_to? checks
ArgumentError Wrong number of method arguments Validate arguments before method calls
NoMethodError Method called on object that doesn't respond Check with respond_to? first
RuntimeError Method redefinition conflicts Use proper aliasing and namespacing
FrozenError Modification of frozen object Check frozen? before modifications