Tôi đã sử dụng tính năng eval
của ruby nhiều lần. Nhưng tôi đã nghe những người nói rằng eval
s là khó chịu. Khi được hỏi, tại sao và như thế nào, tôi không bao giờ có thể có được một lý do thuyết phục để không sử dụng nó. Họ có thực sự khó chịu không? Nếu có, theo cách nào? Các tùy chọn "an toàn" có thể có để đánh giá là gì?'eval' có phải là khó chịu không?
Trả lời
Nếu bạn đang eval
nhập một chuỗi được gửi bởi hoặc có thể sửa đổi bởi người dùng, điều này tương đương với việc cho phép thực thi mã tùy ý. Hãy tưởng tượng nếu chuỗi chứa một hệ điều hành gọi đến rm -rf /
hoặc tương tự. Điều đó nói rằng, trong trường hợp bạn biết các chuỗi bị ràng buộc một cách thích hợp, hoặc trình thông dịch Ruby của bạn được sandboxed một cách thích hợp, hoặc lý tưởng cả hai, eval
có thể cực kỳ mạnh mẽ.
Sự cố tương tự với SQL injection, nếu bạn quen thuộc. Giải pháp ở đây tương tự như giải pháp cho vấn đề tiêm (truy vấn tham số hóa). Tức là, nếu các tuyên bố bạn muốn eval
được biết là có dạng rất cụ thể và không phải tất cả tuyên bố cần được người dùng gửi, chỉ một vài biến, biểu thức toán hoặc tương tự, bạn có thể lấy những mảnh nhỏ này từ người dùng, vệ sinh chúng nếu cần, sau đó đánh giá câu lệnh mẫu an toàn với đầu vào của người dùng được cắm vào các vị trí thích hợp.
Và vệ sinh đã chứng minh là cực kỳ khó khăn để thực sự * làm * đúng cho * tất cả * trường hợp. Câu trả lời hay! – krosenvold
'rm -rf --no-preserve-root /' ngày nay. –
Điều này làm cho việc gỡ lỗi trở nên khó khăn. Nó làm cho việc tối ưu hóa trở nên khó khăn. Nhưng trên hết, nó thường là một dấu hiệu cho thấy có cách tốt hơn để làm bất cứ điều gì bạn đang cố gắng làm.
Nếu bạn cho chúng tôi biết những gì bạn đang cố gắng thực hiện với eval
, bạn có thể nhận được một số câu trả lời có liên quan hơn liên quan đến kịch bản cụ thể của bạn.
Đánh giá là một tính năng cực kỳ mạnh mẽ nên được sử dụng cẩn thận. Bên cạnh các vấn đề an ninh được chỉ ra bởi Matt J, bạn cũng sẽ thấy rằng mã lỗi thời gian chạy được gỡ lỗi là vô cùng khó khăn. Một vấn đề trong một khối mã được đánh giá thời gian chạy sẽ rất khó cho người thông dịch thể hiện - vì vậy việc tìm kiếm nó sẽ rất khó khăn.
Điều đó đang được nói, nếu bạn cảm thấy thoải mái với vấn đề đó và không quan tâm đến vấn đề bảo mật, thì bạn không nên tránh sử dụng một trong các tính năng làm cho ruby hấp dẫn như hiện tại.
Trong một số trường hợp nhất định, một thông số eval
được đặt đúng vị trí và làm giảm số lượng mã được yêu cầu. Ngoài những lo ngại về an ninh đã được đề cập bởi Matt J, bạn cũng cần tự hỏi mình một câu hỏi rất đơn giản:
Khi tất cả được nói và làm, bất kỳ ai khác có thể đọc mã của bạn và hiểu bạn đã làm gì không?
Nếu câu trả lời là không, thì những gì bạn đã đạt được với số eval
bị từ chối để bảo trì. Vấn đề này không chỉ áp dụng nếu bạn làm việc trong một nhóm, nhưng nó cũng có thể áp dụng cho bạn - bạn muốn có thể xem lại các tháng mã của bạn, nếu không phải vài năm kể từ bây giờ và biết bạn đã làm gì.
Trong Ruby có một số mánh lới quảng cáo mà có thể thích hợp hơn eval()
:
- Có
#send
cho phép bạn gọi một phương thức có tên bạn có như chuỗi và truyền tham số cho nó. yield
cho phép bạn chuyển khối mã tới phương thức sẽ được thực hiện trong ngữ cảnh của phương thức nhận.- Thường thì đơn giản
Kernel.const_get("String")
là đủ để có được lớp có tên bạn có như chuỗi.
Tôi nghĩ rằng tôi không thể giải thích chi tiết chúng một cách chính xác, vì vậy tôi chỉ đưa ra các gợi ý nếu bạn quan tâm đến bạn.
Nếu bạn chuyển bất kỳ thứ gì bạn nhận được từ "bên ngoài" tới eval
, bạn đang làm điều gì đó sai và rất khó chịu. Đó là rất khó để thoát khỏi mã đủ để nó được an toàn, vì vậy tôi cho rằng nó khá là không an toàn. Tuy nhiên, nếu bạn đang sử dụng eval để tránh trùng lặp hoặc những thứ tương tự khác, như ví dụ mã sau, bạn có thể sử dụng nó.
class Foo
def self.define_getters(*symbols)
symbols.each do |symbol|
eval "def #{symbol}; @#{symbol}; end"
end
end
define_getters :foo, :bar, :baz
end
Tuy nhiên, ít nhất là trong Ruby 1.9.1, Ruby có phương pháp meta-lập trình thật sự mạnh mẽ, và bạn có thể làm như sau thay vì:
class Foo
def self.define_getters(*symbols)
symbols.each do |symbol|
define_method(symbol) { instance_variable_get(symbol) }
end
end
define_getters :foo, :bar, :baz
end
Đối với hầu hết các mục đích, bạn muốn sử dụng những phương pháp, và không có thoát là cần thiết.
Điều xấu khác về eval
là thực tế là (ít nhất là trong Ruby), nó khá chậm, vì trình thông dịch cần phân tích cú pháp chuỗi và sau đó thực thi mã bên trong liên kết hiện tại. Các phương thức khác gọi hàm C trực tiếp, và do đó bạn sẽ nhận được một tốc độ tăng khá.
'define_method' đã tồn tại trong một thời gian dài - nó không phải là một tính năng 1.9.Nếu bạn đang sử dụng 'eval', nó có thể chỉ có nghĩa là bạn không biết về công cụ thích hợp cho công việc. – Chuck
Rất tiếc, xin lỗi, bây giờ tôi đã hiểu rằng điều đó không rõ ràng. Trong Ruby 1.9 bạn có một số phương pháp lập trình meta mới, tôi không có ý định tham chiếu cụ thể đến 'define_method'. Nhưng tôi đồng ý với hầu hết những người khác đã trả lời bài đăng, OP nên nêu rõ lý do anh ta muốn sử dụng 'eval'. – henrikhodne
-1. 'Define_getters' đầu tiên của bạn có thể là một lỗ hổng! Xem một ví dụ rất giống nhau trong http://stackoverflow.com/questions/3003328/how-do-i-use-class-eval/3003509#3003509 –
eval
không chỉ không an toàn (như đã được chỉ ra ở nơi khác), nó cũng chậm. Mỗi khi nó được thực hiện, AST của mã số eval
cần phải được phân tích cú pháp (và ví dụ JRuby, được chuyển sang bytecode) một lần nữa, đó là một hoạt động chuỗi nặng và cũng có thể là xấu đối với địa phương bộ đệm (theo giả thiết rằng chạy chương trình không eval
rất nhiều, và các bộ phận tương ứng của thông dịch viên do đó cache-lạnh, ngoài việc là lớn).
Tại sao lại có eval
ở tất cả trong Ruby, bạn hỏi? "Bởi vì chúng ta có thể" chủ yếu - Thực ra, khi eval
được phát minh (cho ngôn ngữ lập trình LISP), nó là mostly for show! Hơn nữa, sử dụng eval
là Điều Đúng khi bạn muốn "thêm một thông dịch viên vào trình thông dịch", cho các tác vụ lập trình meta như viết bộ tiền xử lý, trình gỡ rối hoặc công cụ tạo khuôn mẫu. Ý tưởng chung cho các ứng dụng như vậy là để xoa bóp một số mã Ruby và gọi eval
trên đó, và nó chắc chắn đánh bại tái phát minh và thực hiện một ngôn ngữ đồ chơi tên miền cụ thể, một pitfall còn được gọi là Greenspun's Tenth Rule. Các lưu ý là: hãy cẩn thận về các chi phí, ví dụ như cho một động cơ templating, làm tất cả các bạn eval
ing lúc khởi động không chạy thời gian; và không eval
mã không tin cậy trừ khi bạn biết cách "chế ngự" nó, tức là chọn và thực thi một tập con an toàn của ngôn ngữ theo lý thuyết capability discipline. Sau đó là lô của công việc thực sự khó khăn (xem ví dụ: how that was done for Java; Tôi không biết bất kỳ nỗ lực nào như vậy đối với Ruby không may).
Tôi sẽ không nói rằng nó được phát minh "cho hiển thị" trong Lisp. Đây là lần đầu tiên một cấu trúc trong lĩnh vực khoa học máy tính lý thuyết mà sau đó được sử dụng như một kế hoạch chi tiết của một thông dịch viên (xem [bài báo McCarty] (http://www-formal.stanford.edu/jmc/history/lisp/node3). html) trên trang web bạn đang liên kết đến). –
tùy chọn an toàn hơn phụ thuộc vào những gì bạn đang sử dụng eval để làm, bạn có thể cụ thể hơn không? – rampion
Không có gì cụ thể mà tôi muốn yêu cầu. Nhưng bởi "an toàn hơn" tôi có nghĩa là một cái gì đó mà được bạn cùng một kết quả tránh hoặc giảm các lỗ hổng. – Chirantan
gửi an toàn hơn để sử dụng hơn eval? –