Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/raven/base.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'raven/version'
require 'raven/core_ext/object/deep_dup'
require 'raven/backtrace'
require 'raven/breadcrumbs'
require 'raven/processor'
Expand Down
57 changes: 57 additions & 0 deletions lib/raven/core_ext/object/deep_dup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'raven/core_ext/object/duplicable'

#########################################
# This file was copied from Rails 5.2 #
#########################################

class Object
# Returns a deep copy of object if it's duplicable. If it's
# not duplicable, returns +self+.
#
# object = Object.new
# dup = object.deep_dup
# dup.instance_variable_set(:@a, 1)
#
# object.instance_variable_defined?(:@a) # => false
# dup.instance_variable_defined?(:@a) # => true
def deep_dup
duplicable? ? dup : self
end
end

class Array
# Returns a deep copy of array.
#
# array = [1, [2, 3]]
# dup = array.deep_dup
# dup[1][2] = 4
#
# array[1][2] # => nil
# dup[1][2] # => 4
def deep_dup
map(&:deep_dup)
end
end

class Hash
# Returns a deep copy of hash.
#
# hash = { a: { b: 'b' } }
# dup = hash.deep_dup
# dup[:a][:c] = 'c'
#
# hash[:a][:c] # => nil
# dup[:a][:c] # => "c"
def deep_dup
hash = dup
each_pair do |key, value|
if key.frozen? && ::String === key
hash[key] = value.deep_dup
else
hash.delete(key)
hash[key.deep_dup] = value.deep_dup
end
end
hash
end
end
153 changes: 153 additions & 0 deletions lib/raven/core_ext/object/duplicable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# frozen_string_literal: true

#########################################
# This file was copied from Rails 5.2 #
#########################################

#--
# Most objects are cloneable, but not all. For example you can't dup methods:
#
# method(:puts).dup # => TypeError: allocator undefined for Method
#
# Classes may signal their instances are not duplicable removing +dup+/+clone+
# or raising exceptions from them. So, to dup an arbitrary object you normally
# use an optimistic approach and are ready to catch an exception, say:
#
# arbitrary_object.dup rescue object
#
# Rails dups objects in a few critical spots where they are not that arbitrary.
# That rescue is very expensive (like 40 times slower than a predicate), and it
# is often triggered.
#
# That's why we hardcode the following cases and check duplicable? instead of
# using that rescue idiom.
#++
class Object
# Can you safely dup this object?
#
# False for method objects;
# true otherwise.
def duplicable?
true
end
end

class NilClass
begin
nil.dup
rescue TypeError
# +nil+ is not duplicable:
#
# nil.duplicable? # => false
# nil.dup # => TypeError: can't dup NilClass
def duplicable?
false
end
end
end

class FalseClass
begin
false.dup
rescue TypeError
# +false+ is not duplicable:
#
# false.duplicable? # => false
# false.dup # => TypeError: can't dup FalseClass
def duplicable?
false
end
end
end

class TrueClass
begin
true.dup
rescue TypeError
# +true+ is not duplicable:
#
# true.duplicable? # => false
# true.dup # => TypeError: can't dup TrueClass
def duplicable?
false
end
end
end

class Symbol
begin
:symbol.dup # Ruby 2.4.x.
"symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
rescue TypeError
# Symbols are not duplicable:
#
# :my_symbol.duplicable? # => false
# :my_symbol.dup # => TypeError: can't dup Symbol
def duplicable?
false
end
end
end

class Numeric
begin
1.dup
rescue TypeError
# Numbers are not duplicable:
#
# 3.duplicable? # => false
# 3.dup # => TypeError: can't dup Integer
def duplicable?
false
end
end
end

require "bigdecimal"
class BigDecimal
# BigDecimals are duplicable:
#
# BigDecimal("1.2").duplicable? # => true
# BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
end

class Method
# Methods are not duplicable:
#
# method(:puts).duplicable? # => false
# method(:puts).dup # => TypeError: allocator undefined for Method
def duplicable?
false
end
end

class Complex
begin
Complex(1).dup
rescue TypeError
# Complexes are not duplicable:
#
# Complex(1).duplicable? # => false
# Complex(1).dup # => TypeError: can't copy Complex
def duplicable?
false
end
end
end

class Rational
begin
Rational(1).dup
rescue TypeError
# Rationals are not duplicable:
#
# Rational(1).duplicable? # => false
# Rational(1).dup # => TypeError: can't copy Rational
def duplicable?
false
end
end
end
1 change: 1 addition & 0 deletions lib/raven/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def capture_type(obj, options = {})
end

message_or_exc = obj.is_a?(String) ? "message" : "exception"
options = options.deep_dup
options[:configuration] = configuration
options[:context] = context
if evt = Event.send("from_" + message_or_exc, obj, options)
Expand Down
8 changes: 8 additions & 0 deletions spec/raven/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,14 @@ def raven_context
expect(Raven::Event.capture_message(message)).to be_a(Raven::Event)
end

it "doesn't change the option hash" do
h_int = { :abc => :abc }
h = { :k1 => h_int, :k2 => h_int }
Raven.capture_message "Test extra", :extra => { :h1 => h, :h2 => h_int }

expect(h).to eq({ :k1 => h_int, :k2 => h_int })
end

it "sets the message to the value passed" do
expect(hash[:message]).to eq(message)
end
Expand Down
13 changes: 7 additions & 6 deletions spec/raven/instance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
RSpec.describe Raven::Instance do
let(:event) { Raven::Event.new(:id => "event_id") }
let(:options) { { :key => "value" } }
let(:event_options) { options.merge(:context => subject.context, :configuration => configuration) }
let(:context) { nil }
let(:configuration) do
config = Raven::Configuration.new
Expand Down Expand Up @@ -37,7 +38,7 @@
describe '#capture_type' do
describe 'as #capture_message' do
before do
expect(Raven::Event).to receive(:from_message).with(message, options)
expect(Raven::Event).to receive(:from_message).with(message, event_options)
expect(subject).to receive(:send_event).with(event, :exception => nil, :message => message)
end
let(:message) { "Test message" }
Expand All @@ -62,7 +63,7 @@
end

it 'sends the result of Event.capture_type' do
expect(Raven::Event).to receive(:from_message).with(message, options)
expect(Raven::Event).to receive(:from_message).with(message, event_options)
expect(subject).not_to receive(:send_event).with(event)

expect(subject.configuration.async).to receive(:call).with(event.to_json_compatible)
Expand All @@ -79,14 +80,14 @@
let(:exception) { build_exception }

it 'sends the result of Event.capture_exception' do
expect(Raven::Event).to receive(:from_exception).with(exception, options)
expect(Raven::Event).to receive(:from_exception).with(exception, event_options)
expect(subject).to receive(:send_event).with(event, :exception => exception, :message => nil)

subject.capture_exception(exception, options)
end

it 'has an alias' do
expect(Raven::Event).to receive(:from_exception).with(exception, options)
expect(Raven::Event).to receive(:from_exception).with(exception, event_options)
expect(subject).to receive(:send_event).with(event, :exception => exception, :message => nil)

subject.capture_exception(exception, options)
Expand All @@ -105,7 +106,7 @@
end

it 'sends the result of Event.capture_exception' do
expect(Raven::Event).to receive(:from_exception).with(exception, options)
expect(Raven::Event).to receive(:from_exception).with(exception, event_options)
expect(subject).not_to receive(:send_event).with(event)

expect(subject.configuration.async).to receive(:call).with(event.to_json_compatible)
Expand All @@ -127,7 +128,7 @@
end

it 'sends the result of Event.capture_exception via fallback' do
expect(Raven::Event).to receive(:from_exception).with(exception, options)
expect(Raven::Event).to receive(:from_exception).with(exception, event_options)

expect(subject.configuration.async).to receive(:call).with(event.to_json_compatible)
subject.capture_exception(exception, options)
Expand Down