29
Code That Writes Code metaprogramming

Metaprogramming code-that-writes-code

Embed Size (px)

Citation preview

Code That Writes Codemetaprogramming

The “M” word!?

The Boss’ Challenge

1 class Person 2 include CheckedAttributes 3 attr_checked :age do |v| 4 v >= 18 5 end 6 end 7 8 me = Person.new 9 me.age = 39 # OK 10 me.age = 12 # Exception

Requirements• Applies when class includes CheckedAttributs

Module

• Class Macro: attr_checked

• First Argument as attribute name

• Second Argument can accept a block as validator of attribute

Before We Start Reading…

Bill steps

• Kernel method “add_checked_attribute” by Kernel#eval

• Refactoring: remove Kernel#eval

• Block attribute validator

• Replace method to class macro for all classes

• A Module with attr_checked method, hooks to class

Kernel#eval• Spells: String of Code.

• The most Strings of Code feature some kind of string substitution.

• Ex: ……

1 array = [10, 20] 2 element = 30 3 eval("array << element") # => [10, 20, 30]

eval(string [, binding [, filename [,lineno]]]) → obj

• Evaluates the Ruby expression(s) in String

• If binding is given, which must be a Binding object, the evaluation is performed in its context.

• If the optional filename and lineno parameters are present, they will be used when reporting syntax errors

Ex 1. REST Client• Its simple HTTP library for sending HTTP request.

1 #Originally, It needs to declare 4 methods 2 def get(path, *args, &b) 3 r[path].get(*args, &b) 4 end 5 6 def set(path, *args, &b) 7 r[path].set(*args, &b) 8 end 9 10 def put(path, *args, &b) 11 r[path].put(*args, &b) 12 end 13 14 def delete(path, *args, &b) 15 r[path].delete(*args, &b) 16 end

Ex 1. REST Client• Its simple HTTP library for sending HTTP request.

1 # Using "String Of Code" 2 POSSIBLE_VERBS = ['get', 'put', 'post', 3 'delete'] 4 POSSIBLE_VERBS.each do |m| 5 #syntax of heredoc 6 eval <<-end_eval 7 def #{m}(path, *args, &b) 8 r[path].#{m}(*args, &b) 9 end 10 end_eval 11 end

Here Document 1 hdoc = <<-hdoc_end 2 This is here document 3 hdoc_end 4 5 puts "Show:#{hdoc}" 6 #=> Show: This is here document

1 puts("Show:" + <<-hdoc_end) 2 This is here document 3 hdoc_end 4 #=> Show: This is here document

1 puts("Show:" + <<-hdoc_end.gsub(/^\s+/, '')) 2 This is here document 3 hdoc_end 4 #=> Show:This is here document

Binding objects• Objects of class Binding encapsulate the

execution context at some particular place in the code and retain this context for future use.

• Kernel#binding returns the binding object. 1 class MyClass 2 def my_method 3 @x = 1 4 binding 5 end 6 end 7 b = MyClass.new.my_method 8 eval "p @x", b

Strings of Code vs. Blocks

• instance_eval and class_eval also accept String as argument.

• Which one should you choose?

Avoid String of Code whenever you have an alternative.

The Trouble with eval()• Difficult to read and modify.

• Won’t report syntax error until the string is evaluate.

• Security issue: Code injection!

Code Injection 1 def explore_array(method_name) 2 code = "[1,2,3,4].#{method_name}" 3 puts "Evaluating: #{code}" 4 eval code 5 end 6 7 orga_input = "find_index(2)" 8 p explore_array(orga_input) # => 1 9 10 otis_input = "map!(&:next)" 11 p explore_array(otis_input) # => [2, 3, 4, 5] 12 13 sunkai_input = "object_id; Dir.glob('*')" 14 p explore_array(sunkai_input) # => Some 15 sensitive information!!!

Defending • Dynamic Method & Dynamic Dispatch

• Tainted Objects: object#tainted?

• Safe Levels: $SAFE = 0 ~ 3

• Clean Room: proc()

Step 1 - Checked Attributes

• Kernel method: add_checked_attribute.

• Raise exception if value of attribute is nil or false.

• Source Code

Step 2 - Remove eval()• class name is variable: Open Class by class_eval

• method name is variable: Dynamic Methods

• instance variable name is variable: instance_variable_get/instance_variable_set

• Source Code

Step 3 - Block Validator• Attribute validation according to return value of

block.

• Source Code

Step 4 - Class Marco• Class marco: attr_checked

• A class marco for all classes: Instance method of Class/Module.

• Source Code

Hook Methods• The object model is an eventful place.

• Class inherited

• Module included, prepended and extended

• Instance method added, removed and undefined

• Singleton method added, removed and undefined

Quiz• Q: undef_method Vs remove_method?

• undef_method: added an exception to that method.

• remove_method: remove the method at current class, receiver will try to find it from ancestor.

Quiz• Q: which one

method is useless?

1 #Which one method_hook is useless? 2 class Person 3 def self.singleton_method_added(m) 4 p "M1: #{m}" 5 end 6 def self.method_added(m) 7 p "M2: #{m}" 8 end 9 def singleton_method_added(m) 10 p "M3: #{m}" 11 end 12 def method_added(m) 13 p "M4: #{m}" 14 end 15 def hello 16 end 17 def self.hehe 18 end 19 end 20 21 person = Person.new 22 def person.yo 23 end

• method_added

Quiz• Q: Other approaches for hook method?

• Override method and call super in the end of method

• Around Alias

Step 5 - Final step• Declare an CheckedAttributes Module

• Include module as class method: ClassMehtods-plus-hook.

• Source Code

Epilogue

• Are you smart enough to forget what you have learned?

• There’s no such thing as metaprogramming. It’s just programming all the way through

Shuhari (Kanji: 守破離 Hiragana: ゅはり)

• Shu(protect, obey): learning fundamentals, techniques, heuristics, proverbs.

• Ha(detach, digress): detachment from the illusions of self.

• Ri(leave, separate): there are no techniques or proverbs, all moves are natural, becoming one with spirit alone without clinging to forms; transcending the physical

Not Only in “M” world • Design Patterns

• TDD

• Scrum

• More…