These were surely inspired by sferik's Ruby Trivia:
- Ruby Trivia 1
- Ruby Trivia 2
- Still happy to collaborate ^^
Shadow variables (link)
a = 1 # => 1
lambda { |;a| a = 2 }.call # => 2
a # => 1
lambda { a = 2 }.call # => 2
a # => 2Constants you're not supposed to know about. (link)
ObjectSpace.each_object(Module).select { |c| c.to_s[/(^|:)[a-z]/] }
# => [Complex::compatible,
# Rational::compatible,
# NameError::message,
# fatal,
# IO::generic_writable,
# IO::generic_readable]NameError::message hangs onto the receiver so it can calculate the message on demand. (link)
s = String.new
e = s.m rescue $!
def s.inspect() "s1" end
e.message # => "undefined method `m' for s1:String"
def s.inspect() "s2" end
e.message # => "undefined method `m' for s2:String"Core constants are accessed via inheritance, not lexical scope. (link)
class String
Array # => Array
end # => Array
class BasicObject
Array # ~> NameError: uninitialized constant BasicObject::Array\nDid you mean? Array
endThe "scope resolution operator" looks up methods as well as constants. (link)
Array:: # => Array
new(5) { |n| n * 2 }:: # => [0, 2, 4, 6, 8]
reduce(0, :+):: # => 20
even? # => true
Array::new(5) { |n| n * 2 }::reduce(0, :+)::even? # => trueThere are 6 ways to call a proc. Explanation here. (link)
prc = lambda { |n| n + 1 }
prc.call 2 # => 3
prc === 2 # => 3
prc.yield 2 # => 3
prc[2] # => 3
prc.(2) # => 3
prc::(2) # => 3Global functions are private instance methods inherited from Kernel. Explanation here. (link)
# `puts` is a private method inherited from Kernel
self.method(:puts).owner # => Kernel
self.puts("a") rescue $! # => #<NoMethodError: private method `puts' called for main:Object\nDid you mean? putc>
Kernel.module_eval { public :puts }
self.puts("b")
# Now it's public everywhere!
12345.puts("c")
"abc".puts("d")
Array.puts("e").puts("f")
# >> b
# >> c
# >> d
# >> e
# >> fMain is just an object with some methods defined on its singleton class. Explanation here. (link)
self # => main
class << self
remove_method :inspect
end
self # => #<Object:0x007fa3810d6660>
Object.new # => #<Object:0x007fa3818891c8>Main's visibility starts off private, and methods defined there get added to Object. (link)
def x() 1 end
public
def y() 2 end
"abc".x rescue $!.message # => "private method `x' called for \"abc\":String"
"abc".y # => 2
"abc".method(:x).owner # => ObjectLocal variables are known before they are used. (link)
a = 1 # => 1
local_variables # => [:a, :b]
eval('a') # => 1
eval('b') # => nil
eval('c') rescue $! # => #<NameError: undefined local variable or method `c' for main:Object>
b = 2 # => 2You can place ordinal-parameters before and after rest-parameters. (link)
def m(param1, *rest, param2)
param1 # => 1
rest # => [2, 3, 4]
param2 # => 5
end
m(1, 2, 3, 4, 5)Asperands are used to differentiate operators that go in the front vs the back (link)
class Symbol
def -@(*) :front end
def -(*) :back end
end
-:s # => :front
:s - :t # => :back
:s.-@ # => :front
:s.- # => :backYou don't need curly braces when interpolating class variables, instance variables, or global variables (link)
a = 1 # => 1
@@b = 2 # => 2
@c = 3 # => 3
$d = 4 # => 4
"-#{a}-#@@b-#@c-#$d-" # => "-1-2-3-4-"Global variables can be aliased. (link)
alias $a $b
$b = 1
$a # => 1
$a = 2
$b # => 2There are 9 types of arguments. (link)
shadow = :outside
lambda { |
ord1, # ordinal (required) before restargs
opt=:opt, # optional ordinal (defaults if not set)
*rest, # rest args (aggregates ordinals into an array)
ord2, # ordinal after restargs
kw:, # keyword, required
kwopt: :kwopt, # optional keyword (defaults if not set)
**kwrest, # keyword rest args (aggregates keywords into a hash)
█ # wraps block in a proc and assigns to a varaible
shadow # declares a new variable, ignoring the outside one
| ord1 # => :ord1
opt # => :opt
rest # => []
ord2 # => :ord2
kw # => :kw
kwopt # => :kwopt
kwrest # => {}
block.call # => :block
shadow # => nil
}.call(:ord1, :ord2, kw: :kw) { :block }You don't need brackets when assigning an array (there is an implicit array around multiple assignment). (link)
a = 1, 2, 3
a # => [1, 2, 3]Backticks are a method (link)
def `(str)
str.reverse
end
`ls -l` # => "l- sl"Every method can take a block. Pretty sure it's stored here (link)
# Some methods we call all the time
puts(1) { 2 } # => nil
Object.new { 3 } # => #<Object:0x007fe2a2904098>
4.+(5) { 6 } # => 9
# Our own method
def omg() block_given? end # => :omg
omg { 7 } # => true
def bbq() block_given? end # => :bbq
bbq # => false
# >> 1Constant assignment is always based on lexical scope (the word "class" in the source code) (link).
class A
end
class B
::A.module_eval { C = :C }
::A.class_eval { D = :D }
::A.instance_eval { E = :E }
Class.new { F = :F }
end
A.constants # => []
B.constants # => [:C, :D, :E, :F]Block scopes have a target class for the def keyword, the different types of eval modify this
(link).
C1 = Class.new # => C1
class C2
::C1.module_eval { def a() :a end } # C1
::C1.class_eval { def b() :b end } # C1
::C1.instance_eval { def c() :c end } # C1's singleton class
C3 = Class.new { def d() :d end } # C2::C3
lambda { def e() :e end } # C2
end.call # => :e
C1.new.a # => :a
C1.new.b # => :b
C1.c # => :c
C2::C3.new.d # => :d
C2.new.e # => :eThe obvious thing to wonder, IMO, is what happens when you pass a method instead of a block. As of 2.3.1, it does not change self (IIRC, it used to just explode in this situation).
C1, C2 = Class.new, Class.new
def C1.omg(arg)
self # => C1, C1, C1
arg # => C2, C2, C2
end
C2.module_eval &C1.method(:omg)
C2.class_eval &C1.method(:omg)
C2.instance_eval &C1.method(:omg)And it does not change the deftarget (it's still Object).
C, @count = Class.new, 0 # => [C, 0]
def self.omg(*)
case @count += 1 # => 1, 2, 3, 4
when 1 then def a() :a end # => :a
when 2 then def b() :b end # => :b
when 3 then def c() :c end # => :c
when 4 then def d() :d end # => :d
end # => :a, :b, :c, :d
end # => :omg
omg # => :a
C.module_eval &method(:omg) # => :b
C.class_eval &method(:omg) # => :c
C.instance_eval &method(:omg) # => :d
Object.instance_methods false # => [:b, :a, :d, :c]You can write code almost anywhere you like! (link)
In this case, the namespace and superclass can be any code as long as it evaluates to a class. The fork lets us see both branches of the if statement without changing code.
class "In A #{class InANamespace; end} String!".class::AndAlso <
if class InAConditional; end || fork # <-- lets us eval both branches of the if statement
:"In A #{class AndInA; end} Symbol!".class
else
/In A #{class Superclass; end} Regex!/.class
end
end
String::AndAlso.superclass # => Symbol, Regexpnot is just syntactic sugar for !@ (link).
s = ""
def s.!@() "RAWR" end
!s # => "RAWR"
not s # => "RAWR"The -e flag lets you pass a program through ARGV instead of a filename containing a program
(link).
$ ruby -e 'puts "hello, world"'
hello, worldThe -n and -p flags iterate over every line of input, setting it to $_, -p prints $_ out afterwards
(link).
$ printf "abc\ndef\n" | ruby -n -e 'p $_'
"abc\n"
"def\n"
$ printf "abc\ndef\n" | ruby -p -e '$_ = $_.upcase'
ABC
DEFWhen using -n or -p, the global variable $. contains the current input line number
(link).
$ printf "abc\ndef\nghi\n"
abc
def
ghi
$ printf "abc\ndef\nghi\n" | ruby -p -e '$_ = "#{$.}\t#{$_}"'
1 abc
2 def
3 ghiI suppose, to be fair, it's the current input line number regardless of whether you're using -n or -p,
that's the context you would use it in, but it doesn't depend on them.
$stdin, $stdout = IO.pipe
puts "a\nb\nc"
$. # => 0
gets # => "a\n"
$. # => 1
gets # => "b\n"
$. # => 2The -l flag will automatically strip trailing newlines from input
(link).
$ printf "abc\ndef\nghi\n" | ruby -ne 'puts "#{$_.inspect} #$_ #$."'
"abc\n" abc
1
"def\n" def
2
"ghi\n" ghi
3
$ printf "abc\ndef\nghi\n" | ruby -l -ne 'puts "#{$_.inspect} #$_ #$."'
"abc" abc 1
"def" def 2
"ghi" ghi 3Here is an example of where it is useful.
$ printf "abc\ndef\nghi\n" | ruby -lne 'puts "#$_ #$."'
abc 1
def 2
ghi 3
$ printf "abc\ndef\nghi\n" | ruby -ne 'puts "#$_ #$."'
abc
1
def
2
ghi
3The -a flag will split lines of input into an array stored in $F
(link).
Here, we can see that $F is an array of the line being split (it is split by the input record separator, which is whitespace by default).
$ printf "a b c\nd e f\ng h i\n" | ruby -ane 'p $F'
["a", "b", "c"]
["d", "e", "f"]
["g", "h", "i"]
$ printf "a b c\nd e f\ng h i\n" | ruby -ane 'p $F'
["a", "b", "c"]
["d", "e", "f"]
["g", "h", "i"]And here is an example of how we could use this to do arbitrarily convert input.
In this case, we're translating the output of gem list to list each gem and version on its own line
instead of aggregating all the versions behind the gems.
$ gem list | tail -4
webmock (2.1.0)
what_weve_got_here_is_an_error_to_communicate (0.0.8, 0.0.3)
xpath (2.0.0)
yard (0.9.5, 0.8.7.4)
$ gem list | ruby -ane '$F[1..-1].each { |ver| puts "#{$F[0]} #{ver.delete "(),"}" }' | tail -6
webmock 2.1.0
what_weve_got_here_is_an_error_to_communicate 0.0.8
what_weve_got_here_is_an_error_to_communicate 0.0.3
xpath 2.0.0
yard 0.9.5
yard 0.8.7.4print, when invoked without arguments, prints the global variable $_
(link).
$_ = "a"
print
$_ = "b"
print
# >> abHere, we use it to filter the output of gem list to just show gems with 3 or more versions installed.
$ gem list | ruby -ane 'print if $F.length > 3'
minitest (5.9.1, 5.9.0, 5.8.4, 5.8.3, 5.7.0)
psych (2.1.1, default: 2.0.17)
rake (11.2.2, 11.1.2, 10.5.0, 10.4.2, 10.3.2)
rouge (1.11.1, 1.10.1, 1.9.0)
rspec-core (3.5.4, 3.5.3, 3.5.2, 3.5.1, 3.4.4, 3.2.3)
rspec-support (3.5.0, 3.4.1, 3.2.2)
seeing_is_believing (3.0.1, 3.0.0, 3.0.0.beta.7)You can't guess what each line evaluates to! (link)
Quiz:
RUBY_VERSION # => "2.3.1"
true if true # =>
true if false # =>
true if 0 # =>
true if nil # =>
true if "" # =>
true if :"" # =>
true if // # =>
true if [] # =>
true if {} # =>
true if 0...1 # =>
true if 1..2 # =>
if 1..2; true; end # =>
if //; true; end # =>Answers:
RUBY_VERSION # => "2.3.1"
true if true # => true
true if false # => nil
true if 0 # => true
true if nil # => nil
true if "" # => true
true if :"" # => true
true if // # => nil
true if [] # => true
true if {} # => true
true if 0...1 # => true
true if 1..2 # => nil
if 1..2; true; end # => true
if //; true; end # => nilA regex literal as the condition of an if statement is matched against $_
(link).
$_ = 'a'
true if /a/ # => true
true if /b/ # => nilEg you can use it to replace grep as in the examples below. The nice thing about this is we get to use Ruby's regexes and can do a lot more than just filtering.
$ printf "ab\nbc\nac\n" | ruby -ne 'print if /a/'
ab
ac
$ printf "ab\nbc\nac\n" | ruby -ne 'print if /b/'
ab
bc
$ printf "ab\nbc\nac\n" | ruby -ne 'print if /c/'
bc
acA numerical range literal as the condition of an if statement at the top-level (eg not in a method)
is a flip-flop that is matching against $., the current line number. It becomes true when the first
number matches $. and false when the second number matches $.
(link).
$. = 2 # => 2
true if 1..10 # => nil
true if 2..10 # => true
true if 3..10 # => nilHere we use it to filter lines of input based on their line number, without this, we would have to learn sed or awk.
$ printf "FIRST\nSECOND\nTHIRD\nFOURTH\nFIFTH\n" | ruby -ne 'print if 2..4'
SECOND
THIRD
FOURTHFor comparison, the Ruby, sed, and awk implementations of the above:
$ printf "FIRST\nSECOND\nTHIRD\nFOURTH\nFIFTH\n" | ruby -ne 'print if 2..4'
$ printf "FIRST\nSECOND\nTHIRD\nFOURTH\nFIFTH\n" | sed -ne '2,4p'
$ printf "FIRST\nSECOND\nTHIRD\nFOURTH\nFIFTH\n" | awk '2 <= NR && NR<=4'The -n and -p flags add private methods to main that implicitly operate on $_
(link).
$ ruby -e 'p private_methods' | ruby -ne 'p private_methods - eval($_)'
[:chop, :sub, :gsub, :chomp]
$ printf "FIRST\nSECOND\nTHIRD\nFOURTH\nFIFTH\n" | ruby -pe 'gsub "I", "-"'
F-RST
SECOND
TH-RD
FOURTH
F-FTH
-n an -p will pull their input from filenames if they were provided
(link).
# Create 3 files
$ printf "file-A line-1\nfile-A line-2\n" > fa
$ printf "file-B line-1\nfile-B line-2\n" > fb
$ printf "file-C line-1\nfile-C line-2\n" > fc
# Use them as input to the Ruby program
$ ruby -ne 'p line: $_' fa fb fc
{:line=>"file-A line-1\n"}
{:line=>"file-A line-2\n"}
{:line=>"file-B line-1\n"}
{:line=>"file-B line-2\n"}
{:line=>"file-C line-1\n"}
{:line=>"file-C line-2\n"}ARGF is the object implementing -n and -p, it has useful info not contained in globals
(link).
$ printf "file-A line-1\nfile-A line-2\n" > fa
$ printf "file-B line-1\nfile-B line-2\n" > fb
$ printf "file-C line-1\nfile-C line-2\n" > fc
$ ruby -ne 'p [$., ARGF.filename, ARGF.file.lineno, $_]' fa fb fc
[1, "fa", 1, "file-A line-1\n"]
[2, "fa", 2, "file-A line-2\n"]
[3, "fb", 1, "file-B line-1\n"]
[4, "fb", 2, "file-B line-2\n"]
[5, "fc", 1, "file-C line-1\n"]
[6, "fc", 2, "file-C line-2\n"]BEGIN blocks run before anything else in your program, END blocks run after
(link).
BEGIN { p 1 }
END { p 2 }
p 3
BEGIN { p 4 }
END { p 5 }
# >> 1
# >> 4
# >> 3
# >> 5
# >> 2BEGIN and END blocks run only once (they are intended to be used in -n/-p mode)
(link).
$ printf "abc\ndef\nghi\n" | ruby -n -e '
BEGIN { puts "Lines:" }
print(" ", $_)
END { puts "Total:\n #$." }
'
Lines:
abc
def
ghi
Total:
3
BEGIN and END store local variables in their containing scope.
This lets them initialize variables for use in -n / -p mode
(link).
$ printf "333\n55555\n22\n4444\n" | ruby -ln -e '
BEGIN { longest = "" }
longest = $_ if longest.length < $_.length
END { puts longest }
'
55555
Ruby's cryptic Perl variables have English names (link).
Here is a list: https://github.com/ruby/ruby/blob/4aefcbc5327be0f66ba8b1d9494c1573e52ab839/lib/English.rb#L23-L47
# In English
$ printf "ABC\nDEF\nGHI\n" | ruby -r english -ne 'puts "#$INPUT_LINE_NUMBER #$LAST_READ_LINE"'
1 ABC
2 DEF
3 GHI
# In Perl
$ printf "ABC\nDEF\nGHI\n" | ruby -r english -ne 'puts "#$. #$_"'
1 ABC
2 DEF
3 GHIThe opposite of a semicolon is a backslash, it lets you continue on the next line (link).
# turned into 2 expressions
5 # => 5
- 2 # => -2
# expression is continued on next line
5 \
- 2 # => 3Adjacent single/double quoted string literals are concatenated. (link).
# turned into 2 expressions
5 # => 5
- 2 # => -2
# expression is continued on next line
5 \
- 2 # => 3Along with rescue and ensure, there is else,
which runs if the body doesn't explode
(link).
begin
raise if fork # => nil
rescue
$path = :rescue # => :rescue
else
$path = :else # => :else
ensure
$path # => :rescue, :else
end # => :rescue, :elseThe class keyword supports a rescue/else/ensure clause
(link).
class C
1 # => 1
rescue
2
else
3 # => 3
ensure
4 # => 4
end # => 3One of the MRI benchmarks is Optcarrot, a Nintendo emulator written in pure Ruby. This is part of their goal to make MRI 3x faster by version 3.0 (link).
There are 15 string literals. For histories, nuances, and all 15 examples, see 15_string_literals.md, (link).
# Character literal
?c # => "c"
# Single and Double quotes
"double quotes" # => "double quotes"
'single quotes' # => "single quotes"
# Three Perl-style quotes
%Q(percent-Q) # => "percent-Q"
%q(percent-q) # => "percent-q"
%(percent) # => "percent"
# 9 types of heredocument
<<HERE # => "See the \"more info\" link for the other 8 styles\n"
See the "more info" link for the other 8 styles
HEREBackticks around a heredoc delimiter cause it to run in the shell (link).
puts <<`SHELL`
echo "program: $0"
echo "pid: $$"
echo "ppid: $PPID"
echo "dir: $PWD"
SHELL
# >> program: sh
# >> pid: 50417
# >> ppid: 50416
# >> dir: /The first 3 ivars are stored in the object, after that they get moved to a hash (source code) (link).
require 'objspace'
8.times do |n|
# 3 or fewer ivars? stored in the obj more? in a hash
# /------^------\ /------^------\
instance_variables.length # => 0, 1, 2, 3, 4, 5, 6, 7
ObjectSpace.memsize_of(self) # => 40, 40, 40, 40, 80, 80, 96, 96
instance_variable_set "@i#{n}", nil
endYou can discard arguments with an asterisk, this is a special case of variable argument lists, here, (link).
def m(*)
end
m # => nil
m 1 # => nil
m 1, 2 # => nilThe RUBYOPT environment variable lets you add flags to future invocations of Ruby. Eg, this is how Bundler works (link).
$ echo 'puts "pre"' > pre.rb
$ echo 'puts "program"' > program.rb
# Without RUBYOPT set
$ ruby program.rb
program
$ export RUBYOPT='-r ./pre.rb'
# With RUBYOPT set
$ ruby program.rb
pre
programYou can provide an arbitrary backtrace when you raise an exception (link).
begin
raise TypeError, "hello", ['file1.rb', 'file2.rb']
rescue
$!.class # => TypeError
$!.message # => "hello"
$!.backtrace # => ["file1.rb", "file2.rb"]
endThe /o flag caches a regex after compiling it the first time, and repeatedly reuses that (link).
3.times do |n|
/#{n}/ # => /0/, /1/, /2/
/#{n}/o # => /0/, /0/, /0/
endYou can spell 425 words in regex flags here, and here (link).
/here's the/mission
/my/minion
%r/idiculous/
%r/egex/emission
# here's 425 words you can use
File.readlines('/usr/share/dict/words')
.each(&:chomp!)
.reject { |w| (w.chars - %w[m e o i n u s x]).any? }
.length # => 425Fixnums are not in-memory instances. The pointer to their address actually stores their value!
This differnece won't be visible soon since they're merging the two into Integer
(link).
# 1 bit says this is a value not a reference
# 1 bit says whether its pos or neg
# 1 value used to store 0
(2**62 ).class # => Bignum
(2**62-1).class # => Fixnum
RUBY_VERSION # => "2.3.1"In MRI, most floats, like Fixnums, are stored within the value of their reference. This is called a "flonum" https://github.com/ruby/ruby/commit/b3b5e62 (link).
Credit to Chris Seaton for pointing it out.
# 1st is same object b/c its reference is really its encoded value
# 2nd is different object b/c it's allocated in memory
1.0e-76.equal? 1.0e-76 # => true
1.0e-77.equal? 1.0e-77 # => false
# MRI 2.3
RUBY_DESCRIPTION # => "ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]"JavaScript's setTimeout(…, 0) is Ruby's at_exit { … } except it's a queue instead of a stack
(link).
JavaScript's queue:
node -e '
// queue: first in is the first out
setTimeout(() => console.log("a"), 0)
setTimeout(() => {
console.log("b")
setTimeout(() => console.log("c"), 0)
console.log("d")
}, 0)
setTimeout(() => console.log("e"), 0)
'
a
b
d
e
cRuby's stack:
ruby -e '
# stack: first in is the last out
at_exit { puts "a" }
at_exit {
puts "b"
at_exit { puts "c" }
puts "d"
}
at_exit { puts "e" }
'
e
b
d
c
araise turns its args into exceptions by invoking exception and forwarding args
(link.
obj = Object.new
def obj.exception(message)
RuntimeError.new message
end
raise obj, "hello" # ~> RuntimeError: helloNote that this is how its internal exceptions work.
# raise RuntimeError, "a"
RuntimeError.exception("a") # => #<RuntimeError: a>
# raise existing_error
existing_error = RuntimeError.exception("a")
existing_error.exception # => #<RuntimeError: a>Use the -- flag to say you're done passing command-line flags
(link).
$ ruby -e 'p csv: defined?(CSV), argv: ARGV' -r csv
{:csv=>"constant", :argv=>[]}
$ ruby -e 'p csv: defined?(CSV), argv: ARGV' -- -r csv
{:csv=>nil, :argv=>["-r", "csv"]}
The -s flag tells Ruby to parse arguments for you. Eg add it to your script's shebang.
(link).
$ ruby -e 'p(
b: $b,
bool: $bool,
s: $s,
setting: $setting,
argv: ARGV,
)' -s -- -b -bool -s=1 -setting=val nonflag args
{:b=>true, :bool=>true, :s=>"1", :setting=>"val", :argv=>["nonflag", "args"]}Using the % string literal, and a range, the most dots you can get in a row is 5
(link).
%.abc. # => "abc"
%.. # => ""
""..."" # => ""...""
%.. ... %.. # => ""...""
%.....%.. # => ""...""Exempting String bodies,
the most consecutive dollar signs you can get in a row is 3: %$#$$$
(link).
# $$ is a global variable representing the PID
Process.pid # => 29838
$$ # => 29838
# You can omit braces on global variable interpolation
"-#{$$}-" # => "-29838-"
"-#$$-" # => "-29838-"
# You can choose your own String delimiter with % strings
%$abc$ # => "abc"
# This this lets us get 3 dollar signs in a row
%$#{$$}$ # => "29838"
%$#$$$ # => "29838"A metaclass (aka eigenclass) is the singleton class of a class. Its a subclass of the class's superclass's singleton class Ộ.õ (link).
c = Class.new
metaclass = c.singleton_class
metaclass < c.superclass.singleton_class # => trueA non-primitive object's id is half the value of its memory address (true for many primitives, too). The reason is it gives them a bit they can use to flag the pointer as "special", eg (true/false/fixnum/some others) (link).
require 'fiddle' # => true
A = Class.new # => A
a = A.new # => #<A:0x007fa32880bf10>
address = a.object_id * 2 # => 140338735922960
pointer = Fiddle::Pointer.new address # => #<Fiddle::Pointer:0x007fa328529140 ptr=0x007fa32880bf10 size=0 free=0x00000000000000>
pointer.to_value.equal?(a) # => trueBracket access on integers gives access to their bits (link).
# The number thirteen
0b1101 # => 13
# Brackets on integers access their bits
0b1101[0] # => 1
0b1101[1] # => 0
0b1101[2] # => 1
0b1101[3] # => 1The first 8 bytes of an object (on a 64 bit machine) store the flags. The 8th bit is whether it's tainted (link).
require 'fiddle'
object = Object.new
pointer = Fiddle::Pointer.new(object.object_id * 2)
# Flags are stored in the first 8 bytes (on a 64 bit machine)
def get_flag(pointer_to_ruby_object, bit)
flag_int = pointer_to_ruby_object[0, 8].unpack('Q')[0]
flag_int[bit]
end
# The 8th bit stores whether an object is tainted (we'll do one on that at some point)
get_flag pointer, 8 # => 0
object.taint
get_flag pointer, 8 # => 1The first 5 bits of the flags contain type information. Useful for "unviewable" objects (link).
require 'fiddle'
# Note that this won't work for objects whose info is the value of the pointer (eg fixnums)
def get_type(object)
pointer = Fiddle::Pointer.new(object.object_id * 2)
flags = pointer[0, 8].unpack('Q')[0]
{ 0=>:none, 1=>:object, 2=>:class, 3=>:module, 4=>:float, 5=>:string,
6=>:regexp, 7=>:array, 8=>:hash, 9=>:struct, 10=>:bignum, 11=>:file,
12=>:data, 13=>:match, 14=>:complex, 15=>:rational, 17=>:nil, 18=>:true,
19=>:false, 20=>:symbol, 21=>:fixnum, 22=>:undef, 26=>:imemo, 27=>:node,
28=>:iclass, 29=>:zombie, 31=>:mask,
}.fetch(flags & 0x1F)
end
# The first 5 bits of the flags https://github.com/ruby/ruby/blob/b1c68c/include/ruby/ruby.h#L486
0x1F.to_s(2) # => "11111"
# Store a number that maps to an enum of the object's type
# https://github.com/ruby/ruby/blob/b1c68c/include/ruby/ruby.h#L455
get_type Object.new # => :object
get_type "abc" # => :string
get_type $stdout # => :file
get_type /abc/ # => :regexp
get_type Struct.new(:a).new(1) # => :structAfter the flag is a pointer to the object's class. (cool if I say "pointer-sized" from now on?) (link).
require 'fiddle'
# Note that this won't work for objects whose info is the value of the pointer
def get_class(object)
address = object.object_id * 2
Fiddle::Pointer.new(address) # pointer to the object
.+(8) # move past the flags (8 bytes on my machine)
.ptr # follow the memory address to the class
.to_value # return it as a Ruby object
end
get_class Object.new # => Object
get_class "abc" # => String
get_class /abc/ # => RegexpThis blaspheme is for the crass, to curse their dreams just change the class! (link).
require 'fiddle'
class Object
def to_ptr
Fiddle::Pointer.new(object_id*2)
end
def class=(klass)
to_ptr[8, 8] = klass.to_ptr.ref[0, 8]
end
end
A, B = Class.new, Class.new
obj = A.new
obj.class # => A
obj.class = B
obj.class # => B