提交 056414eb 编写于 作者: A Aaron Lipman 提交者: George Claghorn

Omit marshal_dump & _dump from delegate_missing_to

Exclude missing marshal_dump and _dump methods from being delegated to
an object's delegation target via the delegate_missing_to extension.
This avoids unintentionally adding instance variables to an object
during marshallization, should the delegation target be a method which
would otherwise add them.

In current versions of Ruby, a bug exists in the way objects are
marshalled, allowing for instance variables to be added or removed
during marshallization (see https://bugs.ruby-lang.org/issues/15968).
This results in a corrupted serialized byte stream, causing an object's
instance variables to "leak" into subsequent serialized objects during
demarshallization.

In Rails, this behavior may be triggered when marshalling an object that
uses the delegate_missing_to extension, if the delegation target is a
method which adds or removes instance variables to an object being
marshalled - when calling Marshal.dump(object), Ruby's built in behavior
will check whether the object responds to :marshal_dump or :_dump, which
in turn triggers the delegation target method in the
responds_to_missing? function defined in
activesupport/lib/active_support/core_ext/module/delegation.rb

While future versions of Ruby will resolve this bug by raising a
RuntimeError, the underlying cause of this error may not be readily
apparent when encountered by Rails developers. By excluding marshal_dump
and _dump from being delegated to an object's target, this commit
eliminates a potential cause of unexpected behavior and/or
RuntimeErrors.

Fixes #36522
上级 23d29c70
* Do not delegate missing `marshal_dump` and `_dump` methods via the
`delegate_missing_to` extension. This avoids unintentionally adding instance
variables when calling `Marshal.dump(object)`, should the delegation target of
`object` be a method which would otherwise add them. Fixes #36522.
*Aaron Lipman*
* `truncate` would return the original string if it was too short to be truncated * `truncate` would return the original string if it was too short to be truncated
and a frozen string if it were long enough to be truncated. Now truncate will and a frozen string if it were long enough to be truncated. Now truncate will
consistently return an unfrozen string regardless. This behavior is consistent consistently return an unfrozen string regardless. This behavior is consistent
......
...@@ -275,6 +275,11 @@ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil) ...@@ -275,6 +275,11 @@ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
# #
# The delegated method must be public on the target, otherwise it will # The delegated method must be public on the target, otherwise it will
# raise +NoMethodError+. # raise +NoMethodError+.
#
# The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from
# delegation due to possible interference when calling
# <tt>Marshal.dump(object)</tt>, should the delegation target method
# of <tt>object</tt> add or remove instance variables.
def delegate_missing_to(target) def delegate_missing_to(target)
target = target.to_s target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
...@@ -284,6 +289,7 @@ def respond_to_missing?(name, include_private = false) ...@@ -284,6 +289,7 @@ def respond_to_missing?(name, include_private = false)
# It may look like an oversight, but we deliberately do not pass # It may look like an oversight, but we deliberately do not pass
# +include_private+, because they do not get delegated. # +include_private+, because they do not get delegated.
return false if name == :marshal_dump || name == :_dump
#{target}.respond_to?(name) || super #{target}.respond_to?(name) || super
end end
......
...@@ -102,6 +102,24 @@ def initialize(kase) ...@@ -102,6 +102,24 @@ def initialize(kase)
end end
end end
class Maze
attr_accessor :cavern, :passages
end
class Cavern
delegate_missing_to :target
attr_reader :maze
def initialize(maze)
@maze = maze
end
def target
@maze.passages = :twisty
end
end
class Block class Block
def hello? def hello?
true true
...@@ -398,6 +416,17 @@ def test_delegate_missing_to_respects_superclass_missing ...@@ -398,6 +416,17 @@ def test_delegate_missing_to_respects_superclass_missing
assert_respond_to DecoratedTester.new(@david), :extra_missing assert_respond_to DecoratedTester.new(@david), :extra_missing
end end
def test_delegate_missing_to_does_not_interfere_with_marshallization
maze = Maze.new
maze.cavern = Cavern.new(maze)
array = [maze, nil]
serialized_array = Marshal.dump(array)
deserialized_array = Marshal.load(serialized_array)
assert_nil deserialized_array[1]
end
def test_delegate_with_case def test_delegate_with_case
event = Event.new(Tester.new) event = Event.new(Tester.new)
assert_equal 1, event.foo assert_equal 1, event.foo
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册