From 81484e1415e005e29124c20b09869b422b2c2cf3 Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 17:33:59 +0100 Subject: [PATCH 1/8] Fix dumping ivars for Arrays in Marshal.dump This code is way too repetitive, and should be refactored before we can extend it to other classes. --- spec/core/marshal/dump_spec.rb | 4 +--- src/marshal.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/spec/core/marshal/dump_spec.rb b/spec/core/marshal/dump_spec.rb index d5e1f54973..025cecabe7 100644 --- a/spec/core/marshal/dump_spec.rb +++ b/spec/core/marshal/dump_spec.rb @@ -499,9 +499,7 @@ def _dump(level) it "dumps an Array with instance variables" do a = [] a.instance_variable_set(:@ivar, 1) - NATFIXME 'dumps an Array with instance variables', exception: SpecFailedException do - Marshal.dump(a).should == "\004\bI[\000\006:\n@ivari\006" - end + Marshal.dump(a).should == "\004\bI[\000\006:\n@ivari\006" end it "dumps an extended Array" do diff --git a/src/marshal.rb b/src/marshal.rb index 8436c2c910..a85333f9d4 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -212,11 +212,20 @@ def write_object_link(value) end def write_array(values) + write_char('I') unless values.instance_variables.empty? write_char('[') write_integer_bytes(values.size) values.each do |value| write(value) end + unless values.instance_variables.empty? + ivars = values.instance_variables.map { |ivar_name| [ivar_name, values.instance_variable_get(ivar_name)] } + write_integer_bytes(ivars.size) + ivars.each do |ivar_name, ivar_value| + write(ivar_name) + write(ivar_value) + end + end end def write_hash(values) From aed3b287d42ed2889198aa61854872afaec6987f Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 18:22:00 +0100 Subject: [PATCH 2/8] Simplify write_encoding_bytes in Marshal Convert the encoding string to binary encoding, so we can use the generic write method to store it. --- src/marshal.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/marshal.rb b/src/marshal.rb index a85333f9d4..bbf267fff6 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -111,17 +111,12 @@ def write_encoding_bytes(value) when Encoding::UTF_8 ivars.prepend([:E, true]) else - ivars.prepend([:encoding, value.encoding.name]) + ivars.prepend([:encoding, value.encoding.name.b]) end write_integer_bytes(ivars.size) unless ivars.empty? ivars.each do |ivar_name, ivar_value| write(ivar_name) - if ivar_name == :encoding - write_char('"') - write_string_bytes(ivar_value) - else - write(ivar_value) - end + write(ivar_value) end end From e60cd1704208acb5ce5e0e53fc6ce27712def9bc Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 18:14:17 +0100 Subject: [PATCH 3/8] More generic ivars dump in Marshal --- src/marshal.rb | 49 +++++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/marshal.rb b/src/marshal.rb index bbf267fff6..223d002602 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -101,8 +101,7 @@ def write_string_bytes(value) write_bytes(string) end - def write_encoding_bytes(value) - ivars = value.instance_variables.map { |ivar_name| [ivar_name, value.instance_variable_get(ivar_name)] } + def write_encoding_bytes(value, ivars) case value.encoding when Encoding::ASCII_8BIT nil # no encoding saved @@ -113,11 +112,7 @@ def write_encoding_bytes(value) else ivars.prepend([:encoding, value.encoding.name.b]) end - write_integer_bytes(ivars.size) unless ivars.empty? - ivars.each do |ivar_name, ivar_value| - write(ivar_name) - write(ivar_value) - end + write_ivars(ivars) unless ivars.empty? end def write_char(string) @@ -146,11 +141,11 @@ def write_integer(value) end end - def write_string(value) - write_char('I') if value.encoding != Encoding::ASCII_8BIT || !value.instance_variables.empty? + def write_string(value, ivars) + write_char('I') if value.encoding != Encoding::ASCII_8BIT || !ivars.empty? write_char('"') write_string_bytes(value) - write_encoding_bytes(value) + write_encoding_bytes(value, ivars) end def write_symbol(value) @@ -206,21 +201,14 @@ def write_object_link(value) write_integer_bytes(@object_lookup.fetch(value.object_id)) end - def write_array(values) - write_char('I') unless values.instance_variables.empty? + def write_array(values, ivars) + write_char('I') unless ivars.empty? write_char('[') write_integer_bytes(values.size) values.each do |value| write(value) end - unless values.instance_variables.empty? - ivars = values.instance_variables.map { |ivar_name| [ivar_name, values.instance_variable_get(ivar_name)] } - write_integer_bytes(ivars.size) - ivars.each do |ivar_name, ivar_value| - write(ivar_name) - write(ivar_value) - end - end + write_ivars(ivars) unless ivars.empty? end def write_hash(values) @@ -251,12 +239,12 @@ def write_module(value) write_string_bytes(value.name) end - def write_regexp(value) + def write_regexp(value, ivars) write_char('I') write_char('/') write_string_bytes(value.source) write_byte(value.options) - write_encoding_bytes(value) + write_encoding_bytes(value, ivars) end def write_data(value) @@ -287,11 +275,10 @@ def write_user_marshaled_object_without_allocate(value) write_bytes(value.send(:_dump, -1)) end - def write_object(value) + def write_object(value, ivars) raise TypeError, "can't dump anonymous class #{value.class}" if value.class.name.nil? write_char('o') write(value.class.name.to_sym) - ivars = value.instance_variables.map { |ivar_name| [ivar_name, value.instance_variable_get(ivar_name)] } if value.is_a?(Range) ivars.concat([ [:excl, value.exclude_end?], @@ -299,6 +286,10 @@ def write_object(value) [:end, value.end], ]) end + write_ivars(ivars) + end + + def write_ivars(ivars) write_integer_bytes(ivars.size) ivars.each do |ivar_name, ivar_value| write(ivar_name) @@ -316,6 +307,8 @@ def write(value) @object_lookup[value.object_id] = @object_lookup.size end + ivars = value.instance_variables.map { |ivar_name| [ivar_name, value.instance_variable_get(ivar_name)] } + if value.nil? write_nil elsif value.is_a?(TrueClass) @@ -325,13 +318,13 @@ def write(value) elsif value.is_a?(Integer) write_integer(value) elsif value.is_a?(String) - write_string(value) + write_string(value, ivars) elsif value.is_a?(Symbol) write_symbol(value) elsif value.is_a?(Float) write_float(value) elsif value.is_a?(Array) - write_array(value) + write_array(value, ivars) elsif value.is_a?(Hash) write_hash(value) elsif value.is_a?(Class) @@ -339,7 +332,7 @@ def write(value) elsif value.is_a?(Module) write_module(value) elsif value.is_a?(Regexp) - write_regexp(value) + write_regexp(value, ivars) elsif value.is_a?(Data) write_data(value) elsif value.respond_to?(:marshal_dump, true) @@ -351,7 +344,7 @@ def write(value) elsif value.is_a?(MatchData) || value.is_a?(IO) raise TypeError, "can't dump #{value.class}" elsif value.is_a?(Object) - write_object(value) + write_object(value, ivars) else raise TypeError, "can't dump #{value.class}" end From a80c80fa0a4c06d2ca65781d02f3d2e8014260a1 Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 18:25:09 +0100 Subject: [PATCH 4/8] Support ivars in Hash in Marshal.dump --- spec/core/marshal/dump_spec.rb | 4 +--- spec/core/marshal/shared/load.rb | 6 ++---- src/marshal.rb | 6 ++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/spec/core/marshal/dump_spec.rb b/spec/core/marshal/dump_spec.rb index 025cecabe7..c0d05c8b98 100644 --- a/spec/core/marshal/dump_spec.rb +++ b/spec/core/marshal/dump_spec.rb @@ -564,9 +564,7 @@ def _dump(level) it "dumps a Hash with instance variables" do a = {} a.instance_variable_set(:@ivar, 1) - NATFIXME 'dumps a Hash with instance variables', exception: SpecFailedException do - Marshal.dump(a).should == "\004\bI{\000\006:\n@ivari\006" - end + Marshal.dump(a).should == "\004\bI{\000\006:\n@ivari\006" end it "dumps an extended Hash" do diff --git a/spec/core/marshal/shared/load.rb b/spec/core/marshal/shared/load.rb index d3979e4081..fd92942333 100644 --- a/spec/core/marshal/shared/load.rb +++ b/spec/core/marshal/shared/load.rb @@ -610,10 +610,8 @@ h.instance_variable_set :@hash_ivar, 'hash ivar' unmarshalled = Marshal.send(@method, Marshal.dump(h)) - NATFIXME 'it preserves hash ivars when hash contains a string having ivar', exception: SpecFailedException do - unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar' - unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar' - end + unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar' + unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar' end ruby_version_is "3.1" do diff --git a/src/marshal.rb b/src/marshal.rb index 223d002602..ef605f5c2d 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -211,7 +211,8 @@ def write_array(values, ivars) write_ivars(ivars) unless ivars.empty? end - def write_hash(values) + def write_hash(values, ivars) + write_char('I') unless ivars.empty? if values.default.nil? write_char('{') else @@ -225,6 +226,7 @@ def write_hash(values) unless values.default.nil? write(values.default) end + write_ivars(ivars) unless ivars.empty? end def write_class(value) @@ -326,7 +328,7 @@ def write(value) elsif value.is_a?(Array) write_array(value, ivars) elsif value.is_a?(Hash) - write_hash(value) + write_hash(value, ivars) elsif value.is_a?(Class) write_class(value) elsif value.is_a?(Module) From 89c1bb02f5d4561e869522463573b8e925a4766c Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 18:31:07 +0100 Subject: [PATCH 5/8] Restructure encoding pseudo-ivars in Marshal.dump This makes the code a bit more generic --- src/marshal.rb | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/marshal.rb b/src/marshal.rb index ef605f5c2d..71aab3a272 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -101,20 +101,6 @@ def write_string_bytes(value) write_bytes(string) end - def write_encoding_bytes(value, ivars) - case value.encoding - when Encoding::ASCII_8BIT - nil # no encoding saved - when Encoding::US_ASCII - ivars.prepend([:E, false]) - when Encoding::UTF_8 - ivars.prepend([:E, true]) - else - ivars.prepend([:encoding, value.encoding.name.b]) - end - write_ivars(ivars) unless ivars.empty? - end - def write_char(string) write_byte(string[0]) end @@ -142,10 +128,11 @@ def write_integer(value) end def write_string(value, ivars) - write_char('I') if value.encoding != Encoding::ASCII_8BIT || !ivars.empty? + add_encoding_to_ivars(value, ivars) + write_char('I') unless ivars.empty? write_char('"') write_string_bytes(value) - write_encoding_bytes(value, ivars) + write_ivars(ivars) unless ivars.empty? end def write_symbol(value) @@ -242,11 +229,12 @@ def write_module(value) end def write_regexp(value, ivars) + add_encoding_to_ivars(value, ivars) write_char('I') write_char('/') write_string_bytes(value.source) write_byte(value.options) - write_encoding_bytes(value, ivars) + write_ivars(ivars) end def write_data(value) @@ -299,6 +287,19 @@ def write_ivars(ivars) end end + def add_encoding_to_ivars(value, ivars) + case value.encoding + when Encoding::ASCII_8BIT + nil # no encoding saved + when Encoding::US_ASCII + ivars.prepend([:E, false]) + when Encoding::UTF_8 + ivars.prepend([:E, true]) + else + ivars.prepend([:encoding, value.encoding.name.b]) + end + end + def write(value) if value.respond_to?(:object_id) && @object_lookup.key?(value.object_id) write_object_link(value) From 05a4904e64cee607f0e9ab1edc903f0b963ce36e Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 18:34:14 +0100 Subject: [PATCH 6/8] Extract method to dump Range objects in Marshal.dump Don't clutter the generic Object dump --- src/marshal.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/marshal.rb b/src/marshal.rb index 71aab3a272..63eac8f625 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -249,6 +249,15 @@ def write_data(value) end end + def write_range(value, ivars) + ivars.concat([ + [:excl, value.exclude_end?], + [:begin, value.begin], + [:end, value.end], + ]) + write_object(value, ivars) + end + def write_user_marshaled_object_with_allocate(value) write_char('U') write(value.class.to_s.to_sym) @@ -269,13 +278,6 @@ def write_object(value, ivars) raise TypeError, "can't dump anonymous class #{value.class}" if value.class.name.nil? write_char('o') write(value.class.name.to_sym) - if value.is_a?(Range) - ivars.concat([ - [:excl, value.exclude_end?], - [:begin, value.begin], - [:end, value.end], - ]) - end write_ivars(ivars) end @@ -338,6 +340,8 @@ def write(value) write_regexp(value, ivars) elsif value.is_a?(Data) write_data(value) + elsif value.is_a?(Range) + write_range(value, ivars) elsif value.respond_to?(:marshal_dump, true) write_user_marshaled_object_with_allocate(value) elsif value.respond_to?(:_dump, true) From 3b8d3f08f6c8bae26e9b6ed1706bbf32a4af323d Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 18:44:58 +0100 Subject: [PATCH 7/8] Dump Struct in Marshal.dump And uncover an issue with restoring this. You win some, you loose some. Since the load specs depend on dump, it's probably better to keep the dump and disable the load for now. The old code was passing, but this was a coincidence. --- spec/core/marshal/dump_spec.rb | 8 ++------ spec/core/marshal/shared/load.rb | 8 +++++--- src/marshal.rb | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/spec/core/marshal/dump_spec.rb b/spec/core/marshal/dump_spec.rb index c0d05c8b98..810573f9df 100644 --- a/spec/core/marshal/dump_spec.rb +++ b/spec/core/marshal/dump_spec.rb @@ -589,9 +589,7 @@ def _dump(level) describe "with a Struct" do it "dumps a Struct" do - NATFIXME 'dumps a Struct', exception: SpecFailedException do - Marshal.dump(Struct::Pyramid.new).should == "\004\bS:\024Struct::Pyramid\000" - end + Marshal.dump(Struct::Pyramid.new).should == "\004\bS:\024Struct::Pyramid\000" end it "dumps a Struct" do @@ -603,9 +601,7 @@ def _dump(level) it "dumps a Struct with instance variables" do st = Struct.new("Thick").new st.instance_variable_set(:@ivar, 1) - NATFIXME 'dumps a Struct with instance variables', exception: SpecFailedException do - Marshal.dump(st).should == "\004\bIS:\022Struct::Thick\000\006:\n@ivari\006" - end + Marshal.dump(st).should == "\004\bIS:\022Struct::Thick\000\006:\n@ivari\006" Struct.send(:remove_const, :Thick) end diff --git a/spec/core/marshal/shared/load.rb b/spec/core/marshal/shared/load.rb index fd92942333..5c779bc082 100644 --- a/spec/core/marshal/shared/load.rb +++ b/spec/core/marshal/shared/load.rb @@ -846,10 +846,12 @@ def io.binmode; raise "binmode"; end Thread.current[threadlocal_key] = nil dumped = Marshal.dump(s) - loaded = Marshal.send(@method, dumped) + NATFIXME 'it does not call initialize on the unmarshaled struct', exception: ArgumentError, message: 'dump format error' do + loaded = Marshal.send(@method, dumped) - Thread.current[threadlocal_key].should == nil - loaded.a.should == 'foo' + Thread.current[threadlocal_key].should == nil + loaded.a.should == 'foo' + end end end diff --git a/src/marshal.rb b/src/marshal.rb index 63eac8f625..9c92569fcd 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -249,6 +249,21 @@ def write_data(value) end end + def write_struct(value, ivars) + raise TypeError, "can't dump anonymous class #{value.class}" if value.class.name.nil? + values = value.to_h + ivars.delete_if { |key, _| values.key?(key) } + write_char('I') unless ivars.empty? + write_char('S') + write(value.class.to_s.to_sym) + write_integer_bytes(values.size) + values.each do |name, value| + write(name) + write(value) + end + write_ivars(ivars) unless ivars.empty? + end + def write_range(value, ivars) ivars.concat([ [:excl, value.exclude_end?], @@ -340,6 +355,8 @@ def write(value) write_regexp(value, ivars) elsif value.is_a?(Data) write_data(value) + elsif value.is_a?(Struct) + write_struct(value, ivars) elsif value.is_a?(Range) write_range(value, ivars) elsif value.respond_to?(:marshal_dump, true) From 96bf412af91d08ade78b02278abcf914a8a7b136 Mon Sep 17 00:00:00 2001 From: Herwin Date: Sun, 15 Dec 2024 19:06:39 +0100 Subject: [PATCH 8/8] Support exceptions in Marshal.dump --- spec/core/marshal/dump_spec.rb | 20 ++++----------- spec/core/marshal/shared/load.rb | 42 ++++++++++++++------------------ src/marshal.rb | 17 +++++++++++++ 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/spec/core/marshal/dump_spec.rb b/spec/core/marshal/dump_spec.rb index 810573f9df..5a38976e8d 100644 --- a/spec/core/marshal/dump_spec.rb +++ b/spec/core/marshal/dump_spec.rb @@ -818,31 +818,23 @@ def finalizer.noop(_) describe "with an Exception" do it "dumps an empty Exception" do - NATFIXME 'dumps an empty Exception', exception: SpecFailedException do - Marshal.dump(Exception.new).should == "\x04\bo:\x0EException\a:\tmesg0:\abt0" - end + Marshal.dump(Exception.new).should == "\x04\bo:\x0EException\a:\tmesg0:\abt0" end it "dumps the message for the exception" do - NATFIXME 'dumps the message for the exception', exception: SpecFailedException do - Marshal.dump(Exception.new("foo")).should == "\x04\bo:\x0EException\a:\tmesg\"\bfoo:\abt0" - end + Marshal.dump(Exception.new("foo")).should == "\x04\bo:\x0EException\a:\tmesg\"\bfoo:\abt0" end it "contains the filename in the backtrace" do obj = Exception.new("foo") obj.set_backtrace(["foo/bar.rb:10"]) - NATFIXME 'contains the filename in the backtrace', exception: SpecFailedException do - Marshal.dump(obj).should == "\x04\bo:\x0EException\a:\tmesg\"\bfoo:\abt[\x06\"\x12foo/bar.rb:10" - end + Marshal.dump(obj).should == "\x04\bo:\x0EException\a:\tmesg\"\bfoo:\abt[\x06\"\x12foo/bar.rb:10" end it "dumps instance variables if they exist" do obj = Exception.new("foo") obj.instance_variable_set(:@ivar, 1) - NATFIXME 'dumps instance variables if they exist', exception: SpecFailedException do - Marshal.dump(obj).should == "\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\n@ivari\x06" - end + Marshal.dump(obj).should == "\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\n@ivari\x06" end it "dumps the cause for the exception" do @@ -872,9 +864,7 @@ def finalizer.noop(_) rescue => e end - NATFIXME 'dumps the message for the raised NoMethodError exception', exception: SpecFailedException do - Marshal.dump(e).should =~ /undefined method [`']foo' for ("":String|an instance of String)/ - end + Marshal.dump(e).should =~ /undefined method [`']foo' for ("":String|an instance of String)/ end it "raises TypeError if an Object is an instance of an anonymous class" do diff --git a/spec/core/marshal/shared/load.rb b/spec/core/marshal/shared/load.rb index 5c779bc082..3a551a36fb 100644 --- a/spec/core/marshal/shared/load.rb +++ b/spec/core/marshal/shared/load.rb @@ -886,39 +886,33 @@ def io.binmode; raise "binmode"; end describe "for an Exception" do it "loads a marshalled exception with no message" do obj = Exception.new - NATFIXME 'Support exception', exception: NameError, message: "`bt' is not allowed as an instance variable name" do - loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg0") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesg0:\abt0") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - end + loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg0") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesg0:\abt0") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace end it "loads a marshalled exception with a message" do obj = Exception.new("foo") - NATFIXME 'Support exception with message', exception: NameError, message: "`bt' is not allowed as an instance variable name" do - loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - end + loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace end it "loads a marshalled exception with a backtrace" do obj = Exception.new("foo") obj.set_backtrace(["foo/bar.rb:10"]) - NATFIXME 'Support exception with backtrace', exception: NameError, message: "`bt' is not allowed as an instance variable name" do - loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - end + loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace end it "loads an marshalled exception with ivars" do diff --git a/src/marshal.rb b/src/marshal.rb index 9c92569fcd..c24a9a27ef 100644 --- a/src/marshal.rb +++ b/src/marshal.rb @@ -264,6 +264,18 @@ def write_struct(value, ivars) write_ivars(ivars) unless ivars.empty? end + def write_exception(value, ivars) + message = value.message + if message == value.class.inspect + message = nil + elsif message.ascii_only? + message = message.b + end + ivars.prepend([:bt, value.backtrace]) + ivars.prepend([:mesg, message]) + write_object(value, ivars) + end + def write_range(value, ivars) ivars.concat([ [:excl, value.exclude_end?], @@ -357,6 +369,8 @@ def write(value) write_data(value) elsif value.is_a?(Struct) write_struct(value, ivars) + elsif value.is_a?(Exception) + write_exception(value, ivars) elsif value.is_a?(Range) write_range(value, ivars) elsif value.respond_to?(:marshal_dump, true) @@ -589,6 +603,9 @@ def read_object ivars_hash = read_hash if object_class == Range object = Range.new(ivars_hash.delete(:begin), ivars_hash.delete(:end), ivars_hash.delete(:excl)) + elsif object.is_a?(Exception) + object = object_class.new(ivars_hash.delete(:mesg)) + object.set_backtrace(ivars_hash.delete(:bt)) if ivars_hash.key?(:bt) end ivars_hash.each do |ivar_name, value| object.instance_variable_set(ivar_name, value)