Skip to content

Honor read overrides on IO subclasses when sniffing - #180

Closed
OskarEichler wants to merge 2 commits into
rails:mainfrom
OskarEichler:codex/respect-overridden-io-read
Closed

OskarEichler wants to merge 2 commits into
rails:mainfrom
OskarEichler:codex/respect-overridden-io-read

Conversation

@OskarEichler

Copy link
Copy Markdown

Summary

Respect read overrides on IO/StringIO subclasses and individual instances when choosing the magic-sniffing read mode. Use the native fast path only when the actual method is owned by IO or StringIO; inherited, unmodified native readers keep it.

The existing class check treats every subclass as native. A one-argument override then receives an unexpected buffer argument; an override returning short chunks bypasses the accumulation that already works for other duck-typed readers.

Reproduction

require "marcel"
class SingleArgumentReader < StringIO
  def read(length)
    super
  end
end

class ShortReader < StringIO
  def read(length, buffer = +"")
    super([length, 1].min, buffer)
  end
end

p Marcel::Magic.by_magic(SingleArgumentReader.new("GIF89a"))&.type
p Marcel::Magic.by_magic(ShortReader.new("GIF89a"))&.type

On main 731f57062adad157a6a67f724a6f724ef77339d5, the first call raises ArgumentError; the second, when run separately, returns nil. Both return image/gif after this change. The same problem affects File subclasses, prepended implementations and singleton overrides.

Verification

  • Ruby 4.0.6, through rbenv: existing default Rake task before and after — 680 runs, 1,736 assertions, no failures/errors/skips. This includes the table regeneration/check task; its existing 77 unsupported-rule warnings remain.
  • 6,649 external assertions covering native/inherited/overridden readers, one/two-argument short reads, prepended/singleton methods, actual temporary Files/Pathnames, both magic lookup methods, cursor cleanup, error preservation, parent graphs and extension lookup.
  • Changed-file comparative Lint: the same seven existing offenses; this patch adds none. No repository lint configuration exists.
  • Five alternating runs of 20,000 GIF detections: median 0.11923s before / 0.12024s after on this machine. The method lookup adds one Method allocation per detection. These timings are local observations, not a performance improvement claim.
  • No test files added or modified under the consuming repository's no-test-change policy; the executable reproduction is included above. Existing tests ran unchanged. No Linux/Windows or complete supported-Ruby matrix claim.

Compatibility / breaking changes

No intended breaking change, MIME signature/data change, API addition, dependency or Ruby-floor change. Overrides now use the same supported-reader behavior as other non-native IO implementations. Readers inheriting the native method retain the fast path.

@jeremy jeremy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Test coverage?

@OskarEichler

Copy link
Copy Markdown
Author

Added regression coverage with a StringIO subclass whose read override accepts only the length argument. The full magic_test.rb file passes: 120 tests, 631 assertions.

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.

3 participants