Skip to content

Instantly share code, notes, and snippets.

@floehopper
Created March 14, 2011 10:56
Show Gist options
  • Save floehopper/868995 to your computer and use it in GitHub Desktop.
Save floehopper/868995 to your computer and use it in GitHub Desktop.
Detects changes made by an arbitrary chunk of Ruby code that may leak into surrounding code with unpleasant consequences
RUBY_PATH = File.expand_path('../../lib/ruby/1.8', `which ruby`)
module Leakygems
class << self
def method_metadata(method)
begin
file = method.__file__
line = method.__line__
if File.expand_path(file).include?(RUBY_PATH)
type = "ruby"
else
type = "custom"
end
rescue ArgumentError
type = "native"
end
{ :name => method.name, :arity => method.arity, :file => file, :line => line, :type => type }
end
def capture_state_of(klass)
{
:included_modules => klass.included_modules.map(&:to_s).sort,
:constants => (klass.constants - klass.included_modules.map(&:constants).flatten).map(&:to_s).sort.map do |name|
{ :name => name, :value => klass.const_get(name.to_sym) }
end,
:singleton_methods => (class << klass; self; end).instance_methods(include_super = false).sort.map do |name|
method_metadata((class << klass; self; end).instance_method(name))
end,
:instance_methods => klass.instance_methods(include_super = false).sort.map do |name|
method_metadata(klass.instance_method(name))
end
}
end
def diff_state_of(klass, &block)
before = capture_state_of(klass)
block.call
after = capture_state_of(klass)
after.each_key do |key|
puts "#{key} added: #{(after[key] - before[key]).inspect}"
puts "#{key} removed: #{(before[key] - after[key]).inspect}"
end
end
end
end
puts "Crack::XML.parse adds two methods to all instances of String :-"
require 'rubygems'
require 'crack'
Leakygems.diff_state_of(String) do
Crack::XML.parse(%{
<element>string</element>
})
end
puts
puts "Let's see what naughtiness we can detect :-"
Leakygems.diff_state_of(String) do
module ExtraModule; end
class String
include ExtraModule
def self.extra_singleton_method; end
def extra_instance_method; end
EXTRA_CONSTANT = 1
def self.new(text = nil); "x"; end # redefine existing singleton method
def squeeze; "y"; end # redefine existing instance method
end
String.send(:remove_method, :strip) # remove existing instance method
end
# Crack::XML.parse adds two methods to all instances of String :-
# singleton_methods added: []
# singleton_methods removed: []
# included_modules added: []
# included_modules removed: []
# constants added: []
# constants removed: []
# instance_methods added: [{:type=>"custom", :arity=>0, :file=>"/Users/jamesmead/.rvm/gems/ree-1.8.7-2010.02@mubi-bundler/gems/crack-0.1.8/lib/crack/xml.rb", :line=>85, :name=>"attributes"}, {:type=>"custom", :arity=>1, :file=>"/Users/jamesmead/.rvm/gems/ree-1.8.7-2010.02@mubi-bundler/gems/crack-0.1.8/lib/crack/xml.rb", :line=>85, :name=>"attributes="}]
# instance_methods removed: []
# Let's see what naughtiness we can detect :-
# singleton_methods added: [{:type=>"custom", :arity=>0, :file=>"/Users/jamesmead/Code/leakygems/leakygems.rb", :line=>63, :name=>"extra_singleton_method"}, {:type=>"custom", :arity=>-1, :file=>"/Users/jamesmead/Code/leakygems/leakygems.rb", :line=>66, :name=>"new"}]
# singleton_methods removed: [{:type=>"native", :arity=>-1, :file=>nil, :line=>nil, :name=>"new"}]
# included_modules added: ["ExtraModule"]
# included_modules removed: []
# constants added: [{:value=>1, :name=>"EXTRA_CONSTANT"}]
# constants removed: []
# instance_methods added: [{:type=>"custom", :arity=>0, :file=>"/Users/jamesmead/Code/leakygems/leakygems.rb", :line=>64, :name=>"extra_instance_method"}, {:type=>"custom", :arity=>0, :file=>"/Users/jamesmead/Code/leakygems/leakygems.rb", :line=>67, :name=>"squeeze"}]
# instance_methods removed: [{:type=>"native", :arity=>-1, :file=>nil, :line=>nil, :name=>"squeeze"}, {:type=>"native", :arity=>0, :file=>nil, :line=>nil, :name=>"strip"}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment