Skip to content

Make string_value raise what StringValue raises - #137

Open
youdie006 wants to merge 1 commit into
ruby:masterfrom
youdie006:string-value-matches-stringvalue
Open

youdie006 wants to merge 1 commit into
ruby:masterfrom
youdie006:string-value-matches-stringvalue

Conversation

@youdie006

Copy link
Copy Markdown

string_value is documented as "Like StringValue in C" (lib/cgi/escape.rb:235), but #{input.class} differs from it two ways:

CGI.escapeHTML(nil)              # C:  no implicit conversion of nil into String
                                 # RB: no implicit conversion of NilClass into String
CGI.escapeHTML(BasicObject.new)  # C:  TypeError
                                 # RB: NoMethodError

The second one matters more than the message: a rescue TypeError that works with the extension does not catch without it.

lib/cgi/escape.rb:167-168 skips the C extension on TruffleRuby, so that engine always takes this path. Confirmed on truffleruby 3.3.7 — CGI.escapeHTML resolves to CGI::Escape, and both rows above reproduce.

Letting String#+ do the conversion makes core raise exactly what the extension raises. String === input returns the argument untouched for strings and subclasses, so there is no extra allocation on the common path, and it never calls a method on the object — which is what keeps BasicObject a TypeError.

The test goes in CGIEscapePureRubyTest, which removes the C methods in setup and therefore covers the fallback on every engine. #131's own nil assertions are in CGIEscapeTest, which on CRuby exercises the C path — that is why this shipped untested.

Verification

Run on truffleruby 3.3.7, the engine that always uses this path:

row result
new test on master 1 failure
with the fix 5 assertions, 0 failures
revert 1 failure
name nil but keep input.class for the rest fails at the true row
String.try_convert(input) || ("" + input) 4 of 5 pass, fails at BasicObject

That last row is worth a note: String.try_convert itself calls respond_to? on TruffleRuby, so it raises NoMethodError before any fallback can run. Keeping try_convert in the expression looks equivalent and is not, on the one engine this fix is for.

CRuby 3.2.11: same red/green, and the full file goes 442 → 443 tests with 0 failures. (3 errors on both pristine and fixed — RbConfig::LIMITS and assert_separately, because I ran the file directly rather than through test/lib/helper.rb.)

Behaviour table against the extension, both engines: String and String subclasses come back as the same object, to_str objects convert, and nil / true / false / Integer / BasicObject all raise the extension's exact TypeError message.

I did not run the jruby row; its escape backend is the Java extension, and CGI::Escape is only reached there when that is absent.

Unrelated to this, I have #136 open on the same file — different method, no textual overlap, and either order works.


Disclosure: I used Claude (an AI assistant) while preparing this change.

It named nil/true/false by their class, and reaching input.class at all
raised NoMethodError for a BasicObject rather than TypeError. Let core
build the message, as the C extension does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant