crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.22k stars 1.61k forks source link

`IO#same_content?` returns `true` if `stream1` is a prefix of `stream2` #14623

Closed BlobCodes closed 1 month ago

BlobCodes commented 1 month ago

Bug Report

In the following code, the two given IOs are reportedly equal, even though their content is different:

puts IO.same_content?(
  IO::Memory.new("abc"),
  IO::Memory.new("abcdefg1234567890")
) # => true

This seems to be because same_content? instantly returns true once stream1 reached EOF: https://github.com/crystal-lang/crystal/blob/4cea10199d5006000a129413f6f607697185c83a/src/io.cr#L1226


This probably wasn't noticed because the specs don't compare differently-sized IOs at all.

The crystal compiler also uses this method internally, so as an example, it's easy to corrupt the cache:

crystal eval 'puts "hello world"' # => hello world
> ~/.cache/crystal/eval/_main.o0.bc # clear main bitcode file (nothing is a valid prefix of anything)

crystal eval 'puts "something different"' # => hello world

crystal eval 'puts Base64.encode("more complicated code")'
# => Error: execution of command failed with exit status 1

NOTE: This won't realistically happen under normal circumstances.

straight-shoota commented 1 month ago

I'm wondering if this is intentionally holding up some invariant like it will never consume more bytes from stream2 than from stream1. But that really doesn't make any sense. We cannot make any assumptions to the state of either stream after same_content? has return. Except that only if both streams have exactly identical content, both must be at EOF.

So this is clearly missed and should be fixed.