Існує ряд «акуратних» речей, які можна виконати на динамічних мовах, які можна витягнути в частинах коду, що не очевидно іншому програмісту чи аудитору щодо функціональності певного коду.
Розглянемо цю послідовність у irb (інтерактивна оболонка з рубіну):
irb(main):001:0> "bar".foo
NoMethodError: undefined method `foo' for "bar":String
from (irb):1
from /usr/bin/irb:12:in `<main>'
irb(main):002:0> class String
irb(main):003:1> def foo
irb(main):004:2> "foobar!"
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> "bar".foo
=> "foobar!"
Що там сталося - я намагався викликати метод foo
у константі String. Це не вдалося. Потім я відкрив клас String і визначив метод foo
o return "foobar!"
, а потім назвав його. Це спрацювало.
Це відоме як відкритий клас і дає мені кошмари щоразу, коли я думаю про те, щоб написати код у рубіні, який має будь-яку безпеку чи цілісність. Звичайно, це дозволяє робити якісь акуратні речі досить швидко ... але я міг би зробити це так, кожен раз, коли хтось зберігав рядок, він зберігав її у файл або надсилав по мережі. І ця невелика переосмисленість рядка може бути налаштована в будь-якому місці коду.
Багато інших динамічних мов мають подібні речі, які можна зробити. У Perl є Tie :: Scalar, який може за кадром змінити, як працює даний скаляр (це трохи очевидніше і вимагає певної команди, яку ви можете бачити, але скаляр, який передається з іншого місця, може бути проблемою). Якщо у вас є доступ до кулінарної книги Perl, знайдіть Рецепт 13.15 - Створення магічних змінних за допомогою краватки.
Через ці речі (а інші часто є частиною динамічних мов) багато підходів до статичного аналізу безпеки в коді не працюють. Perl і Нерозбірливість показує, що це так, і вказує навіть на такі тривіальні проблеми з підсвічуванням синтаксису ( whatever / 25 ; # / ; die "this dies!";
ставить перед собою виклики, тому що whatever
можна визначити, щоб брати аргументи чи ні під час виконання, повністю перемагаючи підсвічувач синтаксису або статичний аналізатор).
Це може стати ще цікавішим у Ruby завдяки можливості доступу до середовища, в якому було визначено закриття (див. YouTube: Зберігання Ruby Reasonable від RubyConf 2011 Джошуа Балланко). Мені стало відомо про це відео з коментаря Ars Technica від MouseTheLuckyDog .
Розглянемо наступний код:
def mal(&block)
puts ">:)"
block.call
t = block.binding.eval('(self.methods - Object.methods).sample')
block.binding.eval <<-END
def #{t.to_s}
raise 'MWHWAHAW!'
end
END
end
class Foo
def bar
puts "bar"
end
def qux
mal do
puts "qux"
end
end
end
f = Foo.new
f.bar
f.qux
f.bar
f.qux
Цей код повністю видно, але mal
метод може бути деінде ... і з відкритими класами, звичайно, його можна було б переосмислити десь ще.
Запуск цього коду:
~ / $ ruby foo.rb
бар
> :)
qux
бар
b.rb: 20: у `qux ': MWHWAHAW! (Помилка виконання)
від b.rb: 30: в ``
~ / $ ruby foo.rb
бар
> :)
qux
b.rb: 20: у `барі ': MWHWAHAW! (Помилка виконання)
від b.rb: 29: в ``
У цьому коді закриття отримало доступ до всіх методів та інших прив'язок, визначених у класі в цій області. Він обрав випадковий метод і переробив його, щоб створити виняток. (див. клас Прив'язка в Ruby, щоб отримати уявлення про те, до чого цей об'єкт має доступ)
Змінні, методи, значення self та, можливо, блок ітераторів, до яких можна отримати доступ у цьому контексті, зберігаються.
Більш коротка версія, яка показує переосмислення змінної:
def mal(&block)
block.call
block.binding.eval('a = 43')
end
a = 42
puts a
mal do
puts 1
end
puts a
Що при запуску виробляє:
42
1
43
Це більше, ніж відкритий клас, про який я згадував вище, що робить неможливим статичний аналіз. Що демонструється вище, це те, що закриття, яке передається десь в іншому місці, несе в собі повне середовище, яке було визначено в ньому. Це відоме як середовище першого класу (так само, як коли ви можете обходити функції, вони є функціями першого класу, це оточення та всі прив’язки, наявні на той час). Можна було б визначити будь-яку змінну, яка була визначена в межах закриття.
Добре чи погано, скаржиться на рубін чи ні (тобто використання , де можна було б хотів , щоб мати можливість отримати в середовищі методи (див Safe в Perl)), питання про те, «чому б рубін бути обмежений протягом урядового проекту "дійсно відповідає на це відео, пов'язане вище.
Враховуючи, що:
- Ruby дозволяє витягти навколишнє середовище з будь-якого закриття
- Ruby фіксує всі прив’язки в межах закриття
- Ruby підтримує всі зв'язки як живі, так і змінні
- У Ruby є нові прив'язки тіньових старих прив’язок (замість того, щоб клонувати середовище чи забороняти повторне пов'язування)
З урахуванням цих чотирьох варіантів дизайну неможливо знати, що робить будь-який біт коду.
Більше про це можна прочитати у блозі "Абстрактні єресі" . Конкретна публікація стосується схеми, де були такі дискусії. (пов’язано з SO: Чому схема не підтримує першокласні середовища? )
Однак з часом я зрозумів, що в першокласному середовищі виникає більше труднощів і менше енергії, ніж я вважав спочатку. На даний момент я вважаю, що першокласні середовища в кращому випадку марні, а в гіршому - небезпечні.
Я сподіваюся, що в цьому розділі показано аспект небезпеки середовищ першого класу, і чому було б запропоновано видалити Рубі із запропонованого рішення. Справа не лише в тому, що Ruby - це динамічна мова (як уже згадувалося, відповідь, інші динамічні мови були дозволені в інших проектах), але й у тому, що існують конкретні проблеми, які роблять деякі динамічні мови ще складнішими.