CCCPaste

0002-libc-scanf-do-not-count-suppressed-conve...

[libc] scanf: do not count suppressed conversions in the return value

scanf_main() incremented the result for every successful conversion except %n,
including assignment-suppressed ones ("%*d") and "%%". C requires the number of
input items *assigned*. Example: sscanf("12 34", "%*d%d", &x) returned 2
instead of 1; sscanf("420jpeg", "%*d%*[pP]%d", &x) returned 1 without writing x
(breaks x264's y4m colorspace/bit depth parsing). Still present in llvm main
as of 2026-10.

diff --git a/libc/src/stdio/scanf_core/scanf_main.h b/libc/src/stdio/scanf_core/scanf_main.h
index f975d85..e1eb5fb 100644
--- a/libc/src/stdio/scanf_core/scanf_main.h
+++ b/libc/src/stdio/scanf_core/scanf_main.h
@@ -32,9 +32,10 @@ int scanf_main(Reader<T> *reader, const char *__restrict str,
cur_section = parser.get_next_section()) {
if (cur_section.has_conv) {
ret_val = convert(reader, cur_section);
- // The %n (current position) conversion doesn't increment the number of
- // assignments.
- if (cur_section.conv_name != 'n')
+ // Only assigned items count: not %n (current position), not %% and not
+ // conversions with assignment suppression ('*', the NO_WRITE flag).
+ if (cur_section.conv_name != 'n' && cur_section.conv_name != '%' &&
+ (cur_section.flags & FormatFlags::NO_WRITE) == 0)
conversions += ret_val == READ_OK ? 1 : 0;
} else {
ret_val = raw_match(reader, cur_section.raw_string);