Skip to content

[clang] Fix silent miscompile of template argument packs with 2^15 or more elements - #323

Open
Cfretz244 wants to merge 1 commit into
bloomberg:p2996from
Cfretz244:reflect-large-pack-arg-counts
Open

[clang] Fix silent miscompile of template argument packs with 2^15 or more elements#323
Cfretz244 wants to merge 1 commit into
bloomberg:p2996from
Cfretz244:reflect-large-pack-arg-counts

Conversation

@Cfretz244

Copy link
Copy Markdown

Fixes #321.

A template argument pack of 2^15 or more elements silently miscompiled: SubstNonTypeTemplateParmPackExpr stored its argument count in a 15-bit bitfield, so a 32768-element pack wrapped to NumArguments == 0 and the expansion produced a bogus "excess elements in array initializer" (nonzero wraps would silently mis-size). Reachable from plain C++ (a 32768-element NTTP pack via make_integer_sequence); trivially reachable through std::define_static_string / reflect_constant_string, where any lifted string of 32K+ characters forms such a pack.

Changes:

  • SubstNonTypeTemplateParmPackExpr::NumArguments stored full-width (the adjacent Index/Final bitfields still pack into 16 bits).
  • SubstNonTypeTemplateParmExpr::PackIndex (index+1 encoding) full-width at no object-size cost on 64-bit hosts.
  • SubstTemplateTypeParmTypeBitfields::PackIndex widened 15 → 26 bits (fills the 64-bit bitfield word) + constructor assert.
  • UncommonTemplateNameStorage's 15-bit Data (pack index or stored-arg count for template template packs) asserts instead of silently wrapping.
  • Regression test libcxx/test/std/experimental/reflection/define-static-string-large.pass.cpp exercising both sides of the old cliff (32767 / 32768 / 50000 round-trips).

Validated on a Release+assertions AArch64/macOS build of this branch: both repros (reflection + plain C++) fail at the base commit and pass with the fix; clang/test/Reflection parity with base; additionally exercised by a 36-library reflection-driven binding-generation corpus.

Note for upstream-of-upstream: the narrow fields exist in llvm/llvm-project too, and the plain-C++ repro should reproduce there — reflection merely makes 32K+ packs easy to form. May be worth forwarding.

… more elements

SubstNonTypeTemplateParmPackExpr stored its argument count in a 15-bit
bitfield: a pack of 32768 elements wrapped to NumArguments == 0 and the
expansion produced a bogus 'excess elements in array initializer' (counts
wrapping to nonzero would silently mis-size instead). Reachable from plain
C++ (a 32768-element NTTP pack via make_integer_sequence); trivially
reachable through std::define_static_string / reflect_constant_string,
where any string of >= 32K characters forms such a pack.

Also hardens the same-family narrow pack fields:
- SubstNonTypeTemplateParmExpr::PackIndex (index+1 encoding) is stored
  full-width; the adjacent Index:15 + Final:1 pack into 16 bits, so object
  size is unchanged on 64-bit hosts.
- SubstTemplateTypeParmTypeBitfields::PackIndex widens 15 -> 26 bits
  (filling the 64-bit bitfield word) with a constructor assert.
- UncommonTemplateNameStorage's 15-bit Data (pack index or stored-arg count
  for template template packs) asserts instead of silently wrapping.

Adds a regression test exercising both sides of the old cliff
(define_static_string at 32767 / 32768 / 50000).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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