diff --git a/rust/ruby-prism/src/string_query.rs b/rust/ruby-prism/src/string_query.rs index 0e36ebec99..fc2ffee1ed 100644 --- a/rust/ruby-prism/src/string_query.rs +++ b/rust/ruby-prism/src/string_query.rs @@ -176,6 +176,14 @@ mod tests { assert_eq!(Ok(false), query("End").is_keyword()); } + #[test] + fn subslice_test() { + // The byte after the slice is outside it and must not affect the result. + assert_eq!(Ok(true), StringQuery::new(&b"a!"[..1]).is_local()); + assert_eq!(Ok(true), StringQuery::new(&b"Foo="[..3]).is_constant()); + assert_eq!(Ok(true), StringQuery::new(&b"foo?"[..3]).is_method_name()); + } + #[test] fn invalid_encoding_test() { assert_eq!(Err(InvalidEncodingError), query("A").encoding("UTF-16LE").is_local()); diff --git a/src/string_query.c b/src/string_query.c index 00e1e6a8e6..a834efaad7 100644 --- a/src/string_query.c +++ b/src/string_query.c @@ -75,7 +75,7 @@ pm_slice_type(const uint8_t *source, size_t length, const char *encoding_name) { } // accept a ! or ? at the end of the slice as a method name - if (*source == '!' || *source == '?' || *source == '=') { + if (source < end && (*source == '!' || *source == '?' || *source == '=')) { source++; result = PM_SLICE_TYPE_METHOD_NAME; } diff --git a/test/prism/ruby/string_query_test.rb b/test/prism/ruby/string_query_test.rb index 9a0e5e971c..d9da57f42c 100644 --- a/test/prism/ruby/string_query_test.rb +++ b/test/prism/ruby/string_query_test.rb @@ -102,5 +102,19 @@ def test_invalid_encoding StringQuery.new("end".encode("UTF-16LE")).keyword? end end + + if BACKEND == :FFI + # Ruby strings always have a readable byte after their contents, so call + # the C functions directly with a slice that is immediately followed by a + # method name suffix. That byte is outside the slice and must be ignored. + def test_does_not_read_past_slice + local = FFI::MemoryPointer.from_string("foo?") + assert_equal :PM_STRING_QUERY_TRUE, LibRubyParser.pm_string_query_local(local, 3, "UTF-8") + assert_equal :PM_STRING_QUERY_TRUE, LibRubyParser.pm_string_query_method_name(local, 3, "UTF-8") + + constant = FFI::MemoryPointer.from_string("Foo=") + assert_equal :PM_STRING_QUERY_TRUE, LibRubyParser.pm_string_query_constant(constant, 3, "UTF-8") + end + end end end