Skip to content

Commit

Permalink
[GR-18163] Fix parsing malformed Complex's string representation
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/3529
  • Loading branch information
andrykonchin committed Nov 3, 2022
2 parents 1888b29 + e5a921e commit fb8887c
Show file tree
Hide file tree
Showing 12 changed files with 396 additions and 143 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Compatibility:
* Added implementations of `rb_gvar_val_getter` and `rb_define_virtual_variable` (#2750, @nirvdrum).
* Implement `rb_warning_category_enabled_p` to support the `syntax_tree` gem (#2764, @andrykonchin).
* Fix desctructuring of a single block argument that implements `#to_ary` dynamically (#2719, @andrykonchin).
* Fix `Kernel#Complex` and raise exception when an argument is formatted incorrectly (#2765, @andrykonchin).

Performance:

Expand Down
89 changes: 88 additions & 1 deletion spec/ruby/core/kernel/Complex_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require_relative '../../spec_helper'
require_relative '../../shared/kernel/complex'
require_relative 'fixtures/Complex'

describe "Kernel.Complex()" do
describe "when passed [Complex, Complex]" do
Expand Down Expand Up @@ -58,7 +60,92 @@
end
end

describe "when passed a String" do
describe "when passed [String]" do
it_behaves_like :kernel_complex, :Complex_method, KernelSpecs

context "invalid argument" do
it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
-> {
Complex("79+4i".encode("UTF-16"))
}.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
end

it "raises ArgumentError for unrecognised Strings" do
-> {
Complex("ruby")
}.should raise_error(ArgumentError, 'invalid value for convert(): "ruby"')
end

it "raises ArgumentError for trailing garbage" do
-> {
Complex("79+4iruby")
}.should raise_error(ArgumentError, 'invalid value for convert(): "79+4iruby"')
end

it "does not understand Float::INFINITY" do
-> {
Complex("Infinity")
}.should raise_error(ArgumentError, 'invalid value for convert(): "Infinity"')

-> {
Complex("-Infinity")
}.should raise_error(ArgumentError, 'invalid value for convert(): "-Infinity"')
end

it "does not understand Float::NAN" do
-> {
Complex("NaN")
}.should raise_error(ArgumentError, 'invalid value for convert(): "NaN"')
end

it "does not understand a sequence of _" do
-> {
Complex("7__9+4__0i")
}.should raise_error(ArgumentError, 'invalid value for convert(): "7__9+4__0i"')
end

it "does not allow null-byte" do
-> {
Complex("1-2i\0")
}.should raise_error(ArgumentError, "string contains null byte")
end
end

context "invalid argument and exception: false passed" do
it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
-> {
Complex("79+4i".encode("UTF-16"), exception: false)
}.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
end

it "returns nil for unrecognised Strings" do
Complex("ruby", exception: false).should == nil
end

it "returns nil when trailing garbage" do
Complex("79+4iruby", exception: false).should == nil
end

it "returns nil for Float::INFINITY" do
Complex("Infinity", exception: false).should == nil
Complex("-Infinity", exception: false).should == nil
end

it "returns nil for Float::NAN" do
Complex("NaN", exception: false).should == nil
end

it "returns nil when there is a sequence of _" do
Complex("7__9+4__0i", exception: false).should == nil
end

it "returns nil when String contains null-byte" do
Complex("1-2i\0", exception: false).should == nil
end
end
end

describe "when passes [String, String]" do
it "needs to be reviewed for spec completeness"
end

Expand Down
5 changes: 5 additions & 0 deletions spec/ruby/core/kernel/fixtures/Complex.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module KernelSpecs
def self.Complex_method(string)
Complex(string)
end
end
5 changes: 5 additions & 0 deletions spec/ruby/core/string/fixtures/to_c.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module StringSpecs
def self.to_c_method(string)
string.to_c
end
end
105 changes: 24 additions & 81 deletions spec/ruby/core/string/to_c_spec.rb
Original file line number Diff line number Diff line change
@@ -1,99 +1,42 @@
require_relative '../../spec_helper'
require_relative '../../shared/kernel/complex'
require_relative 'fixtures/to_c'

describe "String#to_c" do
it "returns a Complex object" do
'9'.to_c.should be_an_instance_of(Complex)
end

it "understands integers" do
'20'.to_c.should == Complex(20)
end

it "understands negative integers" do
'-3'.to_c.should == Complex(-3)
end

it "understands fractions (numerator/denominator) for the real part" do
'2/3'.to_c.should == Complex(Rational(2, 3))
end

it "understands fractions (numerator/denominator) for the imaginary part" do
'4+2/3i'.to_c.should == Complex(4, Rational(2, 3))
end

it "understands negative fractions (-numerator/denominator) for the real part" do
'-2/3'.to_c.should == Complex(Rational(-2, 3))
end

it "understands negative fractions (-numerator/denominator) for the imaginary part" do
'7-2/3i'.to_c.should == Complex(7, Rational(-2, 3))
end

it "understands floats (a.b) for the real part" do
'2.3'.to_c.should == Complex(2.3)
end

it "understands floats (a.b) for the imaginary part" do
'4+2.3i'.to_c.should == Complex(4, 2.3)
end

it "understands negative floats (-a.b) for the real part" do
'-2.33'.to_c.should == Complex(-2.33)
end

it "understands negative floats (-a.b) for the imaginary part" do
'7-28.771i'.to_c.should == Complex(7, -28.771)
end

it "understands an integer followed by 'i' to mean that integer is the imaginary part" do
'35i'.to_c.should == Complex(0,35)
end

it "understands a negative integer followed by 'i' to mean that negative integer is the imaginary part" do
'-29i'.to_c.should == Complex(0,-29)
end

it "understands an 'i' by itself as denoting a complex number with an imaginary part of 1" do
'i'.to_c.should == Complex(0,1)
end

it "understands a '-i' by itself as denoting a complex number with an imaginary part of -1" do
'-i'.to_c.should == Complex(0,-1)
end

it "understands 'a+bi' to mean a complex number with 'a' as the real part, 'b' as the imaginary" do
'79+4i'.to_c.should == Complex(79,4)
end

it "understands 'a-bi' to mean a complex number with 'a' as the real part, '-b' as the imaginary" do
'79-4i'.to_c.should == Complex(79,-4)
end
it_behaves_like :kernel_complex, :to_c_method, StringSpecs
end

it "understands scientific notation for the real part" do
'2e3+4i'.to_c.should == Complex(2e3,4)
describe "String#to_c" do
it "returns a complex number with 0 as the real part, 0 as the imaginary part for unrecognised Strings" do
'ruby'.to_c.should == Complex(0, 0)
end

it "understands negative scientific notation for the real part" do
'-2e3+4i'.to_c.should == Complex(-2e3,4)
it "ignores trailing garbage" do
'79+4iruby'.to_c.should == Complex(79, 4)
end

it "understands scientific notation for the imaginary part" do
'4+2e3i'.to_c.should == Complex(4, 2e3)
it "understands Float::INFINITY" do
'Infinity'.to_c.should == Complex(0, 1)
'-Infinity'.to_c.should == Complex(0, -1)
end

it "understands negative scientific notation for the imaginary part" do
'4-2e3i'.to_c.should == Complex(4, -2e3)
it "understands Float::NAN" do
'NaN'.to_c.should == Complex(0, 0)
end

it "understands scientific notation for the real and imaginary part in the same String" do
'2e3+2e4i'.to_c.should == Complex(2e3,2e4)
it "understands a sequence of _" do
'7__9+4__0i'.to_c.should == Complex(79, 40)
end

it "understands negative scientific notation for the real and imaginary part in the same String" do
'-2e3-2e4i'.to_c.should == Complex(-2e3,-2e4)
it "allows null-byte" do
"1-2i\0".to_c.should == Complex(1, -2)
"1\0-2i".to_c.should == Complex(1, 0)
"\01-2i".to_c.should == Complex(0, 0)
end

it "returns a complex number with 0 as the real part, 0 as the imaginary part for unrecognised Strings" do
'ruby'.to_c.should == Complex(0,0)
it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
-> {
'79+4i'.encode("UTF-16").to_c
}.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
end
end
133 changes: 133 additions & 0 deletions spec/ruby/shared/kernel/complex.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Specs shared by Kernel#Complex() and String#to_c()
describe :kernel_complex, shared: true do

it "returns a Complex object" do
@object.send(@method, '9').should be_an_instance_of(Complex)
end

it "understands integers" do
@object.send(@method, '20').should == Complex(20)
end

it "understands negative integers" do
@object.send(@method, '-3').should == Complex(-3)
end

it "understands fractions (numerator/denominator) for the real part" do
@object.send(@method, '2/3').should == Complex(Rational(2, 3))
end

it "understands fractions (numerator/denominator) for the imaginary part" do
@object.send(@method, '4+2/3i').should == Complex(4, Rational(2, 3))
end

it "understands negative fractions (-numerator/denominator) for the real part" do
@object.send(@method, '-2/3').should == Complex(Rational(-2, 3))
end

it "understands negative fractions (-numerator/denominator) for the imaginary part" do
@object.send(@method, '7-2/3i').should == Complex(7, Rational(-2, 3))
end

it "understands floats (a.b) for the real part" do
@object.send(@method, '2.3').should == Complex(2.3)
end

it "understands floats (a.b) for the imaginary part" do
@object.send(@method, '4+2.3i').should == Complex(4, 2.3)
end

it "understands negative floats (-a.b) for the real part" do
@object.send(@method, '-2.33').should == Complex(-2.33)
end

it "understands negative floats (-a.b) for the imaginary part" do
@object.send(@method, '7-28.771i').should == Complex(7, -28.771)
end

it "understands an integer followed by 'i' to mean that integer is the imaginary part" do
@object.send(@method, '35i').should == Complex(0,35)
end

it "understands a negative integer followed by 'i' to mean that negative integer is the imaginary part" do
@object.send(@method, '-29i').should == Complex(0,-29)
end

it "understands an 'i' by itself as denoting a complex number with an imaginary part of 1" do
@object.send(@method, 'i').should == Complex(0,1)
end

it "understands a '-i' by itself as denoting a complex number with an imaginary part of -1" do
@object.send(@method, '-i').should == Complex(0,-1)
end

it "understands 'a+bi' to mean a complex number with 'a' as the real part, 'b' as the imaginary" do
@object.send(@method, '79+4i').should == Complex(79,4)
end

it "understands 'a-bi' to mean a complex number with 'a' as the real part, '-b' as the imaginary" do
@object.send(@method, '79-4i').should == Complex(79,-4)
end

it "understands 'a+i' to mean a complex number with 'a' as the real part, 1i as the imaginary" do
@object.send(@method, '79+i').should == Complex(79, 1)
end

it "understands 'a-i' to mean a complex number with 'a' as the real part, -1i as the imaginary" do
@object.send(@method, '79-i').should == Complex(79, -1)
end

it "understands i, I, j, and J imaginary units" do
@object.send(@method, '79+4i').should == Complex(79, 4)
@object.send(@method, '79+4I').should == Complex(79, 4)
@object.send(@method, '79+4j').should == Complex(79, 4)
@object.send(@method, '79+4J').should == Complex(79, 4)
end

it "understands scientific notation for the real part" do
@object.send(@method, '2e3+4i').should == Complex(2e3,4)
end

it "understands negative scientific notation for the real part" do
@object.send(@method, '-2e3+4i').should == Complex(-2e3,4)
end

it "understands scientific notation for the imaginary part" do
@object.send(@method, '4+2e3i').should == Complex(4, 2e3)
end

it "understands negative scientific notation for the imaginary part" do
@object.send(@method, '4-2e3i').should == Complex(4, -2e3)
end

it "understands scientific notation for the real and imaginary part in the same String" do
@object.send(@method, '2e3+2e4i').should == Complex(2e3,2e4)
end

it "understands negative scientific notation for the real and imaginary part in the same String" do
@object.send(@method, '-2e3-2e4i').should == Complex(-2e3,-2e4)
end

it "understands scientific notation with e and E" do
@object.send(@method, '2e3+2e4i').should == Complex(2e3, 2e4)
@object.send(@method, '2E3+2E4i').should == Complex(2e3, 2e4)
end

it "understands 'm@a' to mean a complex number in polar form with 'm' as the modulus, 'a' as the argument" do
@object.send(@method, '79@4').should == Complex.polar(79, 4)
@object.send(@method, '-79@4').should == Complex.polar(-79, 4)
@object.send(@method, '79@-4').should == Complex.polar(79, -4)
end

it "ignores leading whitespaces" do
@object.send(@method, ' 79+4i').should == Complex(79, 4)
end

it "ignores trailing whitespaces" do
@object.send(@method, '79+4i ').should == Complex(79, 4)
end

it "understands _" do
@object.send(@method, '7_9+4_0i').should == Complex(79, 40)
end
end
1 change: 1 addition & 0 deletions spec/tags/core/string/to_c_tags.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fails:String#to_c understands a sequence of _
1 change: 1 addition & 0 deletions src/main/java/org/truffleruby/core/CoreLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,7 @@ public boolean isTruffleBootMainMethod(SharedMethodInfo info) {
"/core/lazy_rubygems.rb",
"/core/truffle/boot.rb",
"/core/truffle/concurrent_map.rb",
"/core/truffle/complex_operations.rb",
"/core/truffle/debug.rb",
"/core/truffle/diggable.rb",
"/core/truffle/encoding_operations.rb",
Expand Down
Loading

0 comments on commit fb8887c

Please sign in to comment.