Tuesday, February 14, 2012

Ruby: Make any program a Quine

One of the amusing diversions available to writers of compiled-language programs was the challenge of writing a quine (after W.V.O., of course): a program which, when executed, prints its own source code.

Attempts in C range with the ugly but obvious (repeating the source code as a string, and essentially printing the string twice) to the downright obfuscated.

Things are different in dynamic languages, of course, and there's a reason why this problem was never a RubyQuiz (UPDATE: it has! with a necessary extra 'piping' condition): any ruby program can be turned into a quine with the following code:


if __FILE__ == $0
  puts File.open(__FILE__, 'r') { |f| f.read }
end

But perhaps something more can be done? Perhaps the source code and the bytecode can be displayed.
It turns out that the RubyVM object in YARV-based Ruby interpreters makes this possible:

#!/usr/bin/env ruby

def no_op
  $some_variable ||= 1024
end

if __FILE__ == $0
  buf = File.open(__FILE__, 'r') { |f| f.read }
  code = RubyVM::InstructionSequence.compile(buf)
  puts buf
  puts code.disasm
end    

Running this under ruby-1.9.2-head produces the following output:

bash$ rvm ruby-1.9.2-head do ruby quine.rb
#!/usr/bin/env ruby

def no_op
  $some_variable ||= 1024
end

if __FILE__ == $0
  buf = File.open(__FILE__, 'r') { |f| f.read }
  code = RubyVM::InstructionSequence.compile(buf)
  puts buf
  puts code.disasm
end

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
== catch table
| catch type: break  st: 0029 ed: 0046 sp: 0000 cont: 0046
|------------------------------------------------------------------------
local table (size: 3, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 3] buf        [ 2] code       
0000 trace            1                                               (   3)
0002 putspecialobject 1
0004 putspecialobject 2
0006 putobject        :no_op
0008 putiseq          no_op
0010 send             :"core#define_method", 3, nil, 0, <ic:0>
0016 pop              
0017 trace            1                                               (   7)
0019 putstring        "<compiled>"
0021 getglobal        $0
0023 opt_eq           <ic:9>
0025 branchunless     100
0027 trace            1                                               (   8)
0029 getinlinecache   36, <ic:2>
0032 getconstant      :File
0034 setinlinecache   <ic:2>
0036 putstring        "<compiled>"
0038 putstring        "r"
0040 send             :open, 2, block in <compiled>, 0, <ic:3>
0046 setlocal         buf
0048 trace            1                                               (   9)
0050 getinlinecache   59, <ic:4>
0053 getconstant      :RubyVM
0055 getconstant      :InstructionSequence
0057 setinlinecache   <ic:4>
0059 getlocal         buf
0061 send             :compile, 1, nil, 0, <ic:5>
0067 setlocal         code
0069 trace            1                                               (  10)
0071 putnil           
0072 getlocal         buf
0074 send             :puts, 1, nil, 8, <ic:6>
0080 pop              
0081 trace            1                                               (  11)
0083 putnil           
0084 getlocal         code
0086 send             :disasm, 0, nil, 0, <ic:7>
0092 send             :puts, 1, nil, 8, <ic:8>
0098 leave                                                            (   7)
0099 pop              
0100 putnil                                                           (  11)
0101 leave            
== disasm: <RubyVM::InstructionSequence:no_op@<compiled>>===============
0000 trace            8                                               (   3)
0002 trace            1                                               (   4)
0004 putnil           
0005 defined          7, :$some_variable, false
0009 branchunless     17
0011 getglobal        $some_variable
0013 dup              
0014 branchif         22
0016 pop              
0017 putobject        1024
0019 dup              
0020 setglobal        $some_variable
0022 trace            16                                              (   5)
0024 leave                                                            (   4)
== disasm: <RubyVM::InstructionSequence:block in <compiled>@<compiled>>=
== catch table
| catch type: redo   st: 0000 ed: 0011 sp: 0000 cont: 0000
| catch type: next   st: 0000 ed: 0011 sp: 0000 cont: 0011
|------------------------------------------------------------------------
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1] s3)
[ 2] f<Arg>     
0000 trace            1                                               (   8)
0002 getdynamic       f, 0
0005 send             :read, 0, nil, 0, <ic:0>
0011 leave            

No comments:

Post a Comment