Есть ряд «изящных» вещей, которые можно сделать в динамических языках, которые можно спрятать в частях кода, которые не сразу очевидны для другого программиста или аудитора в отношении функциональности данного фрагмента кода.
Рассмотрим эту последовательность в irb (интерактивная оболочка ruby):
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
возврата "foobar!"
и затем вызвал его. Это сработало.
Это известно как открытый класс, и каждый раз, когда я думаю о написании кода на ruby, который имеет какой-либо уровень безопасности или целостности, мне снятся кошмары. Конечно, это позволяет вам делать некоторые аккуратные вещи довольно быстро ... но я мог бы делать это каждый раз, когда кто-то сохранял строку, сохранял ее в файле или отправлял по сети. И это маленькое переопределение строки может быть спрятано в любом месте кода.
Многие другие динамические языки имеют схожие вещи, которые можно сделать. В Perl есть Tie :: Scalar, который может за кулисами изменить работу данного скаляра (это немного более очевидно и требует конкретной команды, которую вы можете видеть, но скаляр, передаваемый откуда-то еще, может быть проблемой). Если у вас есть доступ к поваренной книге Perl, посмотрите рецепт 13.15 - Создание магических переменных с помощью tie.
Из-за этих вещей (и других часто являющихся частью динамических языков) многие подходы к статическому анализу безопасности в коде не работают. Perl и Undecidability показывают, что это так, и указывают даже на такие тривиальные проблемы с подсветкой синтаксиса ( whatever / 25 ; # / ; die "this dies!";
возникают проблемы, потому что whatever
можно определить, принимать аргументы или нет во время выполнения, полностью нарушая подсветку синтаксиса или статический анализатор).
Это может стать еще более интересным в Ruby с возможностью доступа к среде, в которой было определено замыкание (см. YouTube: Сохранение разумности Ruby от RubyConf 2011 от Joshua Ballanco). Я узнал об этом видео из комментария 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! (Ошибка выполнения)
из б.р .: 30: в `'
~ / $ ruby foo.rb
бар
> :)
QUX
бр .: 20: в "баре": MWHWAHAW! (Ошибка выполнения)
из б.р .: 29: в `'
В этом коде замыкание получило доступ ко всем методам и другим привязкам, определенным в классе в этой области. Он выбрал случайный метод и переопределил его, чтобы вызвать исключение. (см. класс Binding в 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 новые привязки скрывают старые привязки (а не клонируют среду или запрещают повторную привязку)
С учетом этих четырех вариантов дизайна невозможно понять, что делает код.
Подробнее об этом можно прочитать в блоге Abstract Heresies . Особый пост о Схеме, где проходили такие дебаты. (относится к SO: Почему Scheme не поддерживает среды первого класса? )
Со временем, однако, я понял, что в первоклассных средах было больше трудностей и меньше энергии, чем я первоначально думал. На данный момент я считаю, что первоклассные среды в лучшем случае бесполезны, а в худшем - опасны.
Я надеюсь, что в этом разделе показан опасный аспект среды первого класса и почему будет предложено удалить Ruby из предоставленного решения. Дело не только в том, что Ruby является динамическим языком (как уже упоминалось: в других проектах разрешены другие динамические языки), а в том, что есть определенные проблемы, которые делают некоторые динамические языки еще более трудными для рассуждения.