linux-sparse.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* (no subject)
@ 2026-11-09 20:31 Bart Van Assche
  2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
                   ` (72 more replies)
  0 siblings, 73 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds

From ddb88c610711c6494c57b2a9df205b2acf9b3cbc Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bvanassche@acm.org>
Date: Mon, 9 Nov 2026 12:27:28 -0800
Subject: [PATCH 00/73] Support more C23 features

Hi,

If nobody objects I will publish this patch series two weeks from now to
the master branch at kernel.org.

The changes fall into five major functional themes:

1. C23 Standard & Preprocessor Support (~25 commits)
Major upgrade adding comprehensive ISO C23 and GNU C23 standard features:
* Preprocessor directives & macros:
  - Add #elifdef and #elifndef.
  - Add __has_include and __has_include_next.
  - Add #embed directive and __has_embed.
  - Add __has_c_attribute.
  - Add __VA_OPT__ for variadic macro expansion.
* C23 Language Features:
  - Keywords & Constants: bool, true, false, nullptr / nullptr_t,
    static_assert, alignas, alignof, thread_local, typeof_unqual, and
    u8 character constants / string literals.
  - Types & Syntax:
    * Support _BitInt(N).
    * Support standard [[]] attribute syntax.
    * Support fixed-underlying-type enumerations (enum foo : int).
    * Support constexpr object specifiers and auto type inference.
    * Support digit separators in numeric literals (e.g. 1'000'000).
    * Support empty initializer braces ({}).
    * Support storage-class specifiers in compound literals.
  - Functions & Statements:
    * Support labels before declarations and at block ends.
    * Support unnamed parameters in function definitions.
    * Support variadic functions without named parameters (void f(...)).
    * Treat empty parameter list () as (void) and reject K&R functions.
* Options:
  - Add -std=c23, -std=c2x, and -std=gnu23.

2. Linux Kernel Compatibility, Builtins & Attributes (~10 commits)
Enable Sparse to parse modern Linux kernel code and headers cleanly:
* Support __attribute__((cleanup(...))):
  - Integrate with linearize and -Wcontext to track lock/unlock semantics
    with scope-based resource management (RAII / guard() / scoped_guard()).
* Builtin Functions:
  - Add __builtin_has_attribute().
  - Add __builtin_infer_alloc_token() as a size_t integer constant.
  - Add kernel builtins (__builtin_object_size, __builtin_counted_by_ref,
    __builtin_get_counted_by).
  - Suppress evaluation of __builtin_clz(0) and __builtin_ctz(0).
* Attributes & Types:
  - Support __attribute__((packed)) and machine mode attributes on enums.
  - Accept conditional expressions with only one void side.
  - Ignore top-level non-array address space in compare_types().
  - Support long token and string expansions dynamically.
  - Undefine conflicting bitness macros when parsing -m flags on x86.

3. Bitwise Types & Conditional Context (Linus Torvalds Series, ~7 commits)
Incorporate and harden Linus Torvalds's patch series:
* Context tracking:
  - Add conditional context increment model (context(0, -1)) for trylock
    functions, with multi-attribute call handling and check_context()
    definition validation.
* Bitwise Types (SYM_RESTRICT):
  - Simplify signed constants early (-5, +5, ~0) during type evaluation.
  - Allow all-bits-set (~0, -1) as valid bitwise constants alongside 0.
  - Allow ordered comparisons (<, <=, >, >=) between unsigned restricted
    types and unrestricted constants (0, ~0).
  - Keep unrestricted constants unrestricted across regular and __force
    casts.
  - Preserve MOD_SPECIFIER on bitwise types to retain MOD_UNSIGNED on
    bitwise typedefs.

4. Hardening, Memory Safety & Bug Fixes (~30 commits)
Eliminate crashes, undefined behavior, and potential overflows:
* Allocators & Tokenizer:
  - Harden slab allocation bounds, zeroing checks, and mmap fallbacks.
  - Support 4096-byte identifiers and fix string escape bounds.
* Parser & AST:
  - Guard against recursive symbols in c2xml.
  - Harden warning and format string buffers in lib.c.
  - Fix attribute propagation, enum overflow, and AST column counting.
* Evaluation, Inlining & Optimization:
  - Fix pointer arithmetic, incomplete types, and qualifiers in
    evaluate.c.
  - Fix generic expression duplication in inline.c and VLA bound copying.
  - Guard against LLONG_MIN overflow on negation in simplify.c.
  - Fix instruction traversal order in memops.c and skip mixed-size SSA.
  - Fix dead store elimination (DSE) and asm operands in linearize/flow.
  - Cast pointers to uintptr_t before relational comparisons in cse.c.
* Backends & Tools:
  - Use LLVMCreateBuilderForModule() for LLVM 24+.
  - Support arbitrary struct member counts and operand types in
    sparse-llvm.
  - Fix buffer bounds and unsigned comparisons in compile-i386.c.
  - Fix ctags, dissect, graph, and scheck helper utilities.

5. Tooling & CI Automation (2 commits)
* Add a comprehensive .clang-format configuration file.
* Add GitHub Actions workflow and test script to build the Linux kernel
  with Sparse.
  
Thanks,

Bart.

Bart Van Assche (66):
  Makefile: Pass '--' to rm in clean target to handle dash-prefixed
    files
  sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later
  typeof_unqual: Strip qualifiers and address space from types and
    arrays
  bitfield: Pack consecutive _Bool bitfields into byte containers
  allocate: Harden slab allocation bounds, zeroing, and mmap checks
  tokenize: Support 4096-byte identifiers and fix escape bounds
  pre-process: Fix macro expansion and include directive handling
  options, lib: Harden warning buffers and command-line option parsing
  builtin: Fix atomic builtin type checking and object_size bounds
  parse, show-parse: Fix attribute propagation, enum overflow, and
    bounds
  target-openrisc: Set target_openrisc.mach to MACH_OPENRISC
  evaluate: Fix pointer arithmetic, incomplete types, and qualifiers
  expand: Fix static initializer folding and floating-point casts
  inline: Copy union cast types and VLA bounds when inlining
  linearize, flow: Fix asm operands, switch case bounds, and DSE
  memops, ssa: Walk parent insns in reverse and skip mixed-size SSA
  simplify: Prevent overflow on LLONG_MIN negation and fix
    simplifications
  sparse: Gate context failure warning on Wcontext and catch underflow
  ast-model: Return AST_N_COLUMNS and inspect child nodes lazily
  c2xml: Guard against recursive symbols and free xmlGetProp() string
  compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null
    checks
  ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL
  dissect: Unwrap array element type via base_type()
  example: Fix show_op() rotating buffer dimensions
  graph: Check PSEUDO_SYM before dereferencing insn->func->sym
  scheck: Fix slice() upper bit index calculation
  sparse-llvm: Support arbitrary struct member counts and fix operand
    types
  inline: Duplicate generic expressions instead of evaluating them
  lib: Avoid mutating fmt before va_start() in warning()
  cse, canon: Cast pointers to uintptr_t before relational comparison
  clang-format: Add .clang-format configuration file
  options: Add support for -std=c23, -std=c2x and -std=gnu23
  preprocessor: Add support for #elifdef and #elifndef
  preprocessor: Add support for __has_include and __has_include_next
  c23: Add support for static_assert, alignas, alignof, thread_local
  c23: Add support for bool, true, and false keywords
  c23: Add support for nullptr and nullptr_t
  c23: Add support for digit separators in numeric literals
  c23: Support unnamed parameters in function definitions
  c23: Support variadic functions without named parameters
  c23: Support labels before declarations and at block ends
  c23: Support auto type inference
  c23: Support constexpr object specifier
  c23: Support enumerations with fixed underlying types
  c23: Support standard [[]] attribute syntax
  preprocessor: Add support for __has_c_attribute
  c23: Support empty initializer braces {}
  preprocessor: Add support for __has_embed
  preprocessor: Add support for #embed directive
  c23: Add support for u8 character constants and string literals
  preprocessor: Add support for __VA_OPT__
  c23: Treat empty parameter list () as (void) and reject K&R functions
  c23: Support storage-class specifiers in compound literals
  c23: Add support for _BitInt(N)
  enum: Support machine mode attributes on enumeration types
  enum: Support __attribute__((packed)) on enumeration types
  expand: Ignore top-level non-array address space in compare_types()
  builtin: Support __builtin_infer_alloc_token() as a size_t ICE
  builtin: Add support for Linux kernel compiler builtins
  builtin: Add support for __builtin_has_attribute()
  cleanup: Support __attribute__((cleanup(...))) in linearize and
    -Wcontext
  builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0)
  pre-process: Support long token and string expansions dynamically
  x86: Undefine conflicting bitness macros when parsing -m flags
  evaluate: Accept conditional expressions with only one void side
  ci: Add Linux kernel GitHub Actions workflow and build script

Linus Torvalds (7):
  context: Add "conditional context increment" model
  expand: Simplify signed constants early
  bitwise: Allow all-bits-set as a restricted value
  bitwise: Allow restricted ordered compares with unrestricted values
  bitwise: Keep unrestricted values unrestricted after a cast
  bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types
  bitwise: Allow both regular casts and forced casts for restricted
    values

 .clang-format                                 |  125 ++
 .../build-linux-kernel-with-sparse.yml        |   28 +
 Documentation/types.rst                       |    2 +-
 Makefile                                      |    2 +-
 allocate.c                                    |   45 +-
 allocate.h                                    |    1 +
 ast-inspect.c                                 |    1 +
 ast-model.c                                   |    9 +-
 builtin.c                                     |  249 ++-
 c2xml.c                                       |   55 +-
 char.c                                        |  146 +-
 ci/build-linux-kernel-with-sparse.sh          |   23 +
 compat/mmap-blob.c                            |    4 +-
 compile-i386.c                                |   76 +-
 cse.c                                         |   17 +-
 ctags.c                                       |    4 +-
 dissect.c                                     |    6 +-
 evaluate.c                                    |  918 ++++++++-
 example.c                                     |    2 +-
 expand.c                                      |  696 ++++++-
 expand.h                                      |    3 +
 expression.c                                  |  482 ++++-
 expression.h                                  |   14 +-
 flow.c                                        |    3 +
 gcc-attr-list.h                               |    2 -
 graph.c                                       |   33 +-
 ident-list.h                                  |   33 +-
 inline.c                                      |  343 +++-
 lib.c                                         |   30 +-
 linearize.c                                   |  624 +++++-
 linearize.h                                   |    6 +-
 memops.c                                      |    4 +-
 options.c                                     |   84 +-
 options.h                                     |    2 +
 parse.c                                       | 1620 ++++++++++++---
 parse.h                                       |    5 +
 pre-process.c                                 | 1824 +++++++++++++++--
 predefine.c                                   |    8 +
 scheck.c                                      |    3 +-
 show-parse.c                                  |  128 +-
 simplify.c                                    |  230 ++-
 simplify.h                                    |    1 +
 sparse-llvm.c                                 |  183 +-
 sparse.c                                      |  207 +-
 ssa.c                                         |    6 +
 storage.c                                     |    5 +-
 symbol.c                                      |  314 ++-
 symbol.h                                      |  150 +-
 target-openrisc.c                             |    2 +-
 token.h                                       |   13 +-
 tokenize.c                                    |  242 ++-
 validation/arch/x86-bitness-override.c        |   17 +
 validation/arch/x86-bitness-undef.c           |   16 +
 validation/atomic-builtins-const.c            |   67 +
 validation/atomic-builtins-types.c            |   47 +
 validation/attr_in_parameter.c                |    2 +
 .../backend/c23-compound-literal-storage.c    |   27 +
 validation/backend/c23-empty-init.c           |   24 +
 validation/backend/c23-nullptr.c              |   21 +
 validation/backend/struct-large-members.c     |   83 +
 validation/bitfield-bool-layout.c             |    9 +-
 validation/bitwise-all-bits-and-casts.c       |  100 +
 validation/builtin-clz-ctz-zero.c             |   11 +
 validation/builtin-counted-by-ref-err.c       |   20 +
 validation/builtin-has-attribute-err.c        |   19 +
 validation/builtin-has-attribute-more.c       |  157 ++
 validation/builtin-has-attribute-nonstring.c  |   35 +
 validation/builtin-infer-alloc-token.c        |   28 +
 validation/builtin-object-size-cycle.c        |   44 +
 validation/builtins-kernel.c                  |   73 +
 validation/c11-c23-keywords-ident.c           |   19 +
 validation/c11-va-start-error.c               |   20 +
 validation/c23-alignof-pedantic.c             |   20 +
 validation/c23-attributes-error.c             |   20 +
 validation/c23-attributes-pedantic.c          |   10 +
 validation/c23-attributes.c                   |  152 ++
 validation/c23-auto-error.c                   |   71 +
 validation/c23-auto.c                         |  107 +
 validation/c23-bitint-error.c                 |   61 +
 validation/c23-bitint-pedantic.c              |   13 +
 validation/c23-bitint.c                       |  214 ++
 validation/c23-bool-error.c                   |   23 +
 validation/c23-bool.c                         |   45 +
 .../c23-compound-literal-register-addr.c      |   17 +
 .../c23-compound-literal-storage-error.c      |   37 +
 .../c23-compound-literal-storage-pedantic.c   |   10 +
 validation/c23-compound-literal-storage.c     |   27 +
 validation/c23-constexpr-error.c              |   92 +
 validation/c23-constexpr-self-init.c          |   41 +
 validation/c23-constexpr.c                    |  100 +
 validation/c23-digit-separators-error.c       |   36 +
 validation/c23-digit-separators-pedantic.c    |   12 +
 validation/c23-digit-separators.c             |   29 +
 validation/c23-empty-init-error.c             |   19 +
 validation/c23-empty-init-pedantic.c          |   14 +
 validation/c23-empty-init.c                   |   64 +
 validation/c23-empty-parameters.c             |   29 +
 validation/c23-empty-params-error.c           |   47 +
 validation/c23-enum-type-cast.c               |   70 +
 validation/c23-enum-type-error.c              |   40 +
 validation/c23-enum-type.c                    |  116 ++
 validation/c23-keywords-error.c               |   48 +
 validation/c23-keywords.c                     |   30 +
 validation/c23-knr-error.c                    |   14 +
 validation/c23-labels-error.c                 |   48 +
 validation/c23-labels-gnu.c                   |   15 +
 validation/c23-labels-pedantic.c              |   15 +
 validation/c23-labels.c                       |   67 +
 validation/c23-nullptr-assign.c               |   35 +
 validation/c23-nullptr-error.c                |  107 +
 validation/c23-nullptr.c                      |  107 +
 validation/c23-param-attribute.c              |   28 +
 validation/c23-stdc-version.c                 |   11 +
 validation/c23-typeof-unqual.c                |  113 +
 validation/c23-u8-char-error.c                |   16 +
 validation/c23-u8-char-pedantic.c             |   10 +
 validation/c23-u8-char-warn.c                 |   14 +
 validation/c23-u8-char.c                      |   37 +
 validation/c23-unnamed-params-error.c         |   27 +
 validation/c23-unnamed-params-pedantic.c      |   12 +
 validation/c23-unnamed-params.c               |   47 +
 validation/c23-va-opt.c                       |   36 +
 validation/c23-variadic-error.c               |   41 +
 validation/c23-variadic-no-named.c            |   47 +
 validation/c2xml-recursive-types.c            |   15 +
 validation/cleanup-context.c                  |  161 ++
 validation/cleanup-errors.c                   |   77 +
 validation/cleanup-inline-loop.c              |   31 +
 validation/cond-err-expand.c                  |    9 -
 validation/cond-one-void-pedantic.c           |   39 +
 validation/cond-one-void.c                    |   47 +
 validation/context-fixes.c                    |   41 +
 validation/context.c                          |  101 +-
 validation/enum+mode.c                        |   26 +-
 validation/enum-packed.c                      |   85 +
 validation/eval-expand-inline-fixes.c         |   49 +
 validation/eval-ptr-incomplete-and-inline.c   |   70 +
 validation/eval-type-checks.c                 |   89 +
 validation/expand-bitint.c                    |   49 +
 validation/expand-const-and-float.c           |   73 +
 validation/inline-generic.c                   |   25 +
 validation/linear/bitfield-bool.c             |   44 +
 validation/linear/c23-nullptr.c               |   20 +
 validation/linear/c23-unnamed-params.c        |   27 +
 validation/linear/c23-va-start.c              |   22 +
 validation/linear/cleanup-control-flow.c      |   62 +
 validation/linear/cleanup-stmt-expr-asm.c     |   48 +
 validation/linear/cond-one-void.c             |   24 +
 validation/linear/control-flow-fixes.c        |   44 +
 validation/long-string-expansion.c            |   30 +
 validation/optim/simplify-memops-ssa-fixes.c  |  103 +
 validation/optim/volatile-dse-and-cfg.c       |   53 +
 validation/parser-enum-overflow.c             |   13 +
 validation/parser-type-fixes.c                |   93 +
 validation/preprocessor/c23-elifdef-error.c   |   35 +
 validation/preprocessor/c23-elifdef.c         |   44 +
 validation/preprocessor/c23-embed-error.c     |   19 +
 validation/preprocessor/c23-embed-pedantic.c  |   12 +
 validation/preprocessor/c23-embed.c           |   72 +
 validation/preprocessor/c23-has-c-attribute.c |   60 +
 validation/preprocessor/c23-has-embed.c       |   69 +
 .../preprocessor/c23-has-include-error.c      |   18 +
 validation/preprocessor/c23-has-include.c     |   51 +
 validation/preprocessor/c23-va-opt-error.c    |   31 +
 validation/preprocessor/c23-va-opt.c          |   73 +
 .../preprocessor/preprocess-crash-fixes.c     |   25 +
 .../preprocess-errors-and-limits.c            |   33 +
 validation/struct-packed-aligned-layout.c     |  135 ++
 validation/types-compatible-address-space.c   |   39 +
 validation/weak-const-no-fold.c               |   15 +
 170 files changed, 13517 insertions(+), 1097 deletions(-)
 create mode 100644 .clang-format
 create mode 100644 .github/workflows/build-linux-kernel-with-sparse.yml
 create mode 100755 ci/build-linux-kernel-with-sparse.sh
 create mode 100644 validation/arch/x86-bitness-override.c
 create mode 100644 validation/arch/x86-bitness-undef.c
 create mode 100644 validation/atomic-builtins-const.c
 create mode 100644 validation/atomic-builtins-types.c
 create mode 100644 validation/backend/c23-compound-literal-storage.c
 create mode 100644 validation/backend/c23-empty-init.c
 create mode 100644 validation/backend/c23-nullptr.c
 create mode 100644 validation/backend/struct-large-members.c
 create mode 100644 validation/bitwise-all-bits-and-casts.c
 create mode 100644 validation/builtin-clz-ctz-zero.c
 create mode 100644 validation/builtin-counted-by-ref-err.c
 create mode 100644 validation/builtin-has-attribute-err.c
 create mode 100644 validation/builtin-has-attribute-more.c
 create mode 100644 validation/builtin-has-attribute-nonstring.c
 create mode 100644 validation/builtin-infer-alloc-token.c
 create mode 100644 validation/builtin-object-size-cycle.c
 create mode 100644 validation/builtins-kernel.c
 create mode 100644 validation/c11-c23-keywords-ident.c
 create mode 100644 validation/c11-va-start-error.c
 create mode 100644 validation/c23-alignof-pedantic.c
 create mode 100644 validation/c23-attributes-error.c
 create mode 100644 validation/c23-attributes-pedantic.c
 create mode 100644 validation/c23-attributes.c
 create mode 100644 validation/c23-auto-error.c
 create mode 100644 validation/c23-auto.c
 create mode 100644 validation/c23-bitint-error.c
 create mode 100644 validation/c23-bitint-pedantic.c
 create mode 100644 validation/c23-bitint.c
 create mode 100644 validation/c23-bool-error.c
 create mode 100644 validation/c23-bool.c
 create mode 100644 validation/c23-compound-literal-register-addr.c
 create mode 100644 validation/c23-compound-literal-storage-error.c
 create mode 100644 validation/c23-compound-literal-storage-pedantic.c
 create mode 100644 validation/c23-compound-literal-storage.c
 create mode 100644 validation/c23-constexpr-error.c
 create mode 100644 validation/c23-constexpr-self-init.c
 create mode 100644 validation/c23-constexpr.c
 create mode 100644 validation/c23-digit-separators-error.c
 create mode 100644 validation/c23-digit-separators-pedantic.c
 create mode 100644 validation/c23-digit-separators.c
 create mode 100644 validation/c23-empty-init-error.c
 create mode 100644 validation/c23-empty-init-pedantic.c
 create mode 100644 validation/c23-empty-init.c
 create mode 100644 validation/c23-empty-parameters.c
 create mode 100644 validation/c23-empty-params-error.c
 create mode 100644 validation/c23-enum-type-cast.c
 create mode 100644 validation/c23-enum-type-error.c
 create mode 100644 validation/c23-enum-type.c
 create mode 100644 validation/c23-keywords-error.c
 create mode 100644 validation/c23-keywords.c
 create mode 100644 validation/c23-knr-error.c
 create mode 100644 validation/c23-labels-error.c
 create mode 100644 validation/c23-labels-gnu.c
 create mode 100644 validation/c23-labels-pedantic.c
 create mode 100644 validation/c23-labels.c
 create mode 100644 validation/c23-nullptr-assign.c
 create mode 100644 validation/c23-nullptr-error.c
 create mode 100644 validation/c23-nullptr.c
 create mode 100644 validation/c23-param-attribute.c
 create mode 100644 validation/c23-stdc-version.c
 create mode 100644 validation/c23-typeof-unqual.c
 create mode 100644 validation/c23-u8-char-error.c
 create mode 100644 validation/c23-u8-char-pedantic.c
 create mode 100644 validation/c23-u8-char-warn.c
 create mode 100644 validation/c23-u8-char.c
 create mode 100644 validation/c23-unnamed-params-error.c
 create mode 100644 validation/c23-unnamed-params-pedantic.c
 create mode 100644 validation/c23-unnamed-params.c
 create mode 100644 validation/c23-va-opt.c
 create mode 100644 validation/c23-variadic-error.c
 create mode 100644 validation/c23-variadic-no-named.c
 create mode 100644 validation/c2xml-recursive-types.c
 create mode 100644 validation/cleanup-context.c
 create mode 100644 validation/cleanup-errors.c
 create mode 100644 validation/cleanup-inline-loop.c
 create mode 100644 validation/cond-one-void-pedantic.c
 create mode 100644 validation/cond-one-void.c
 create mode 100644 validation/context-fixes.c
 create mode 100644 validation/enum-packed.c
 create mode 100644 validation/eval-expand-inline-fixes.c
 create mode 100644 validation/eval-ptr-incomplete-and-inline.c
 create mode 100644 validation/eval-type-checks.c
 create mode 100644 validation/expand-bitint.c
 create mode 100644 validation/expand-const-and-float.c
 create mode 100644 validation/linear/bitfield-bool.c
 create mode 100644 validation/linear/c23-nullptr.c
 create mode 100644 validation/linear/c23-unnamed-params.c
 create mode 100644 validation/linear/c23-va-start.c
 create mode 100644 validation/linear/cleanup-control-flow.c
 create mode 100644 validation/linear/cleanup-stmt-expr-asm.c
 create mode 100644 validation/linear/cond-one-void.c
 create mode 100644 validation/linear/control-flow-fixes.c
 create mode 100644 validation/long-string-expansion.c
 create mode 100644 validation/optim/simplify-memops-ssa-fixes.c
 create mode 100644 validation/optim/volatile-dse-and-cfg.c
 create mode 100644 validation/parser-enum-overflow.c
 create mode 100644 validation/parser-type-fixes.c
 create mode 100644 validation/preprocessor/c23-elifdef-error.c
 create mode 100644 validation/preprocessor/c23-elifdef.c
 create mode 100644 validation/preprocessor/c23-embed-error.c
 create mode 100644 validation/preprocessor/c23-embed-pedantic.c
 create mode 100644 validation/preprocessor/c23-embed.c
 create mode 100644 validation/preprocessor/c23-has-c-attribute.c
 create mode 100644 validation/preprocessor/c23-has-embed.c
 create mode 100644 validation/preprocessor/c23-has-include-error.c
 create mode 100644 validation/preprocessor/c23-has-include.c
 create mode 100644 validation/preprocessor/c23-va-opt-error.c
 create mode 100644 validation/preprocessor/c23-va-opt.c
 create mode 100644 validation/preprocessor/preprocess-crash-fixes.c
 create mode 100644 validation/preprocessor/preprocess-errors-and-limits.c
 create mode 100644 validation/struct-packed-aligned-layout.c
 create mode 100644 validation/types-compatible-address-space.c
 create mode 100644 validation/weak-const-no-fold.c


^ permalink raw reply	[flat|nested] 74+ messages in thread

* [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files
  2026-11-09 20:31 Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
                   ` (71 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Pass '--' to rm in the clean target so that stray files starting with a
hyphen (such as '-.o') are not interpreted as command-line options by
rm.

Fixes: 86eb97a3c2df ("Makefile: stop cleaning files we didn't make...")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index e172758b8b09..4d2700f5cad3 100644
--- a/Makefile
+++ b/Makefile
@@ -288,7 +288,7 @@ validation/%: $(PROGRAMS) FORCE
 
 
 clean: clean-check
-	@rm -f *.[oa] .*.d $(PROGRAMS) version.h
+	@rm -f -- *.[oa] .*.d $(PROGRAMS) version.h
 clean-check:
 	@echo "  CLEAN"
 	@find validation/ \( -name "*.c.output.*" \

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later
  2026-11-09 20:31 Bart Van Assche
  2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
                   ` (70 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Call LLVMCreateBuilderForModule(module) instead of
LLVMCreateBuilderInContext(context) in output_fn() when building against
LLVM 24 or later so that sparse-llvm builds without a deprecation warning
after LLVM commit 46db5f8ab053 ("[llvm-c][ocaml] Accept Module instead of
Context in IRBuilder bindings (#234209)").

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 sparse-llvm.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/sparse-llvm.c b/sparse-llvm.c
index facce6f745ff..c422a13ce2a8 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -1182,7 +1182,11 @@ static void output_fn(LLVMModuleRef module, struct entrypoint *ep)
 	LLVMSetFunctionCallConv(function.fn, LLVMCCallConv);
 	LLVMSetLinkage(function.fn, function_linkage(sym));
 
+#if LLVM_VERSION_MAJOR > 23
+	function.builder = LLVMCreateBuilderForModule(module);
+#else
 	function.builder = LLVMCreateBuilderInContext(context);
+#endif
 
 	/* give a name to each argument */
 	nr_args = symbol_list_size(base_type->arguments);

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays
  2026-11-09 20:31 Bart Van Assche
  2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
  2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
                   ` (69 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Examine the target symbol type and clear MOD_QUALIFIER, non-array
MOD_ATOMIC, and ctype.as in typeof_specifier_helper() and
examine_typeof_helper() when qual is false. Add strip_array_qualifiers()
to recursively clone SYM_ARRAY chains and strip element qualifiers and
address spaces without mutating shared typedef or declaration symbols,
while preserving MOD_ATOMIC on array element types per ISO C23 6.7.2.5p4.

Ensure that typeof_unqual() and __typeof_unqual__() remove qualifiers and
address spaces from type names, expressions, and qualified array types
instead of retaining qualifiers on the resulting type.

Fixes: dc9efe442b89 ("sparse: implement typeof_unqual()")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 parse.c                        |   8 +++
 symbol.c                       |  32 +++++++++-
 symbol.h                       |   1 +
 validation/c23-typeof-unqual.c | 113 +++++++++++++++++++++++++++++++++
 4 files changed, 151 insertions(+), 3 deletions(-)
 create mode 100644 validation/c23-typeof-unqual.c

diff --git a/parse.c b/parse.c
index 1a889ccb6a90..f349cc75bff4 100644
--- a/parse.c
+++ b/parse.c
@@ -1070,6 +1070,14 @@ static struct token *typeof_specifier_helper(struct token *token, struct symbol
 	if (lookup_type(token->next)) {
 		struct symbol *sym;
 		token = typename(token->next, &sym, NULL);
+		sym = examine_symbol_type(sym);
+		if (!qual) {
+			sym->ctype.modifiers &= ~MOD_QUALIFIER;
+			if (!sym->ctype.base_type || sym->ctype.base_type->type != SYM_ARRAY)
+				sym->ctype.modifiers &= ~MOD_ATOMIC;
+			sym->ctype.as = NULL;
+			sym->ctype.base_type = strip_array_qualifiers(sym->ctype.base_type);
+		}
 		ctx->ctype.base_type = sym->ctype.base_type;
 		apply_ctype(token->pos, &ctx->ctype, &sym->ctype);
 	} else {
diff --git a/symbol.c b/symbol.c
index 7060acb666d9..5470880db271 100644
--- a/symbol.c
+++ b/symbol.c
@@ -541,6 +541,27 @@ static struct symbol *examine_pointer_type(struct symbol *sym)
 	return sym;
 }
 
+struct symbol *strip_array_qualifiers(struct symbol *type)
+{
+	struct symbol *elem, *copy;
+	unsigned long strip;
+
+	if (!type)
+		return NULL;
+	if (type->type != SYM_ARRAY && type->type != SYM_NODE)
+		return type;
+	elem = strip_array_qualifiers(type->ctype.base_type);
+	strip = type->ctype.modifiers & MOD_QUALIFIER;
+	if (elem == type->ctype.base_type && !strip && !type->ctype.as)
+		return type;
+	copy = alloc_symbol(type->pos, type->type);
+	*copy = *type;
+	copy->ctype.base_type = elem;
+	copy->ctype.modifiers &= ~MOD_QUALIFIER;
+	copy->ctype.as = NULL;
+	return copy;
+}
+
 static struct symbol *examine_typeof_helper(struct symbol *sym, bool qual)
 {
 	struct symbol *base = evaluate_expression(sym->initializer);
@@ -548,12 +569,17 @@ static struct symbol *examine_typeof_helper(struct symbol *sym, bool qual)
 
 	if (!base)
 		base = &bad_ctype;
-	if (base->type == SYM_NODE) {
+	while (base->type == SYM_NODE) {
 		mod |= base->ctype.modifiers & MOD_TYPEOF;
-		if (!qual)
-			mod &= ~MOD_QUALIFIER;
 		base = base->ctype.base_type;
 	}
+	if (!qual) {
+		mod &= ~MOD_QUALIFIER;
+		if (base->type != SYM_ARRAY)
+			mod &= ~MOD_ATOMIC;
+		sym->ctype.as = NULL;
+		base = strip_array_qualifiers(base);
+	}
 	if (base->type == SYM_BITFIELD)
 		warning(base->pos, "typeof applied to bitfield type");
 	sym->type = SYM_NODE;
diff --git a/symbol.h b/symbol.h
index 3552d4391621..9ff2733021b8 100644
--- a/symbol.h
+++ b/symbol.h
@@ -351,6 +351,7 @@ extern void add_symbol(struct symbol_list **, struct symbol *);
 extern void bind_symbol(struct symbol *, struct ident *, enum namespace);
 extern void bind_symbol_with_scope(struct symbol *, struct ident *, enum namespace, struct scope *);
 
+extern struct symbol *strip_array_qualifiers(struct symbol *);
 extern struct symbol *examine_symbol_type(struct symbol *);
 extern struct symbol *examine_pointer_target(struct symbol *);
 extern const char *show_as(struct ident *as);
diff --git a/validation/c23-typeof-unqual.c b/validation/c23-typeof-unqual.c
new file mode 100644
index 000000000000..5738fafd2fca
--- /dev/null
+++ b/validation/c23-typeof-unqual.c
@@ -0,0 +1,113 @@
+/* Test typeof_unqual on type names */
+typedef const int const_int;
+typedef typeof_unqual(const_int) unqual_int;
+
+typedef volatile int vol_int;
+typedef typeof_unqual(vol_int) nonvol_int;
+
+typedef int * const const_ptr;
+typedef typeof_unqual(const_ptr) mod_ptr;
+
+typedef _Atomic int atomic_int;
+typedef typeof_unqual(atomic_int) nonatomic_int;
+
+typedef const int ca[3];
+typedef typeof_unqual(ca) unqual_ca;
+
+typedef const int cmatrix[2][3];
+typedef typeof_unqual(cmatrix) unqual_matrix;
+
+typedef const _Atomic int const_atomic_arr[2];
+typedef typeof_unqual(const_atomic_arr) unqual_atomic_arr;
+
+typedef const int *cip_arr_t[2];
+typedef int * const cpi_arr_t[2];
+
+#ifdef __CHECKER__
+typedef int __attribute__((address_space(1))) as1_int;
+typedef typeof_unqual(as1_int) as0_int;
+typedef int __attribute__((address_space(1))) as1_arr[2];
+typedef typeof_unqual(as1_arr) as0_arr;
+#endif
+
+_Static_assert(!__builtin_types_compatible_p(ca *, int (*)[3]), "ca stays const");
+_Static_assert(__builtin_types_compatible_p(ca *, const int (*)[3]), "ca is const int[3]");
+_Static_assert(__builtin_types_compatible_p(unqual_ca *, int (*)[3]), "unqual_ca strips const");
+_Static_assert(!__builtin_types_compatible_p(cmatrix *, int (*)[2][3]), "cmatrix stays const");
+_Static_assert(__builtin_types_compatible_p(unqual_matrix *, int (*)[2][3]), "unqual_matrix strips const");
+_Static_assert(!__builtin_types_compatible_p(const_atomic_arr *, _Atomic int (*)[2]), "const_atomic_arr stays const");
+_Static_assert(__builtin_types_compatible_p(unqual_atomic_arr *, _Atomic int (*)[2]), "unqual_atomic_arr strips const, keeps _Atomic");
+_Static_assert(__builtin_types_compatible_p(typeof_unqual(cip_arr_t) *, const int *(*)[2]), "pointee const kept");
+_Static_assert(!__builtin_types_compatible_p(typeof_unqual(cip_arr_t) *, int *(*)[2]), "pointee const not stripped");
+_Static_assert(__builtin_types_compatible_p(typeof_unqual(cpi_arr_t) *, int *(*)[2]), "element ptr const stripped");
+
+static void test_types(void)
+{
+	unqual_int a = 1;
+	a = 2;
+
+	mod_ptr p = (void *)0;
+	p = (int *)0;
+
+	unqual_ca b;
+	b[0] = 1;
+
+	unqual_matrix u;
+	u[0][0] = 1;
+
+	unqual_atomic_arr na;
+	na[0] = 1;
+
+	typeof_unqual(cpi_arr_t) ucpi;
+	ucpi[0] = (int *)0;
+
+#ifdef __CHECKER__
+	as0_int x = 0;
+	int *px = &x;
+	(void)px;
+
+	as0_arr ax;
+	int *pax = ax;
+	(void)pax;
+#endif
+}
+
+/* Test typeof_unqual on expressions */
+static void test_exprs(const int c, _Atomic int a)
+{
+	typeof_unqual(c) uc = 10;
+	uc = 20;
+
+	typeof_unqual(a) ua = 30;
+	ua = 40;
+
+	const int arr[3] = { 0 };
+	typeof_unqual(arr) uarr;
+	uarr[0] = 1;
+	_Static_assert(!__builtin_types_compatible_p(typeof(&arr), int (*)[3]), "arr stays const");
+	_Static_assert(__builtin_types_compatible_p(typeof(&uarr), int (*)[3]), "uarr is int[3]");
+
+	const int m[2][3] = { { 0 } };
+	typeof_unqual(m) um;
+	um[0][0] = 1;
+	_Static_assert(!__builtin_types_compatible_p(typeof(&m), int (*)[2][3]), "m stays const");
+	_Static_assert(__builtin_types_compatible_p(typeof(&um), int (*)[2][3]), "um is int[2][3]");
+
+	const _Atomic int aa[2] = { 0 };
+	typeof_unqual(aa) uaa;
+	uaa[0] = 1;
+	_Static_assert(!__builtin_types_compatible_p(typeof(&aa), _Atomic int (*)[2]), "aa stays const");
+	_Static_assert(__builtin_types_compatible_p(typeof(&uaa), _Atomic int (*)[2]), "uaa is _Atomic int[2]");
+
+#ifdef __CHECKER__
+	static int __attribute__((address_space(1))) asa[2];
+	typeof_unqual(asa) uasa;
+	int *p = uasa;
+	(void)p;
+#endif
+}
+
+/*
+ * check-name: c23-typeof-unqual
+ * check-command: sparse $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers
  2026-11-09 20:31 Bart Van Assche
                   ` (2 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
                   ` (68 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Round bitfield base storage unit sizes smaller than a byte up to
bits_in_char in bitfield_base_size() and widen sub-byte bitfield access
containers to uchar_ctype in bitfield_base_type() so _Bool bitfields use
an 8-bit storage unit during both struct layout and IR linearization.

Allow consecutive _Bool bitfields to pack into a single byte in
examine_struct_union_type() and emit bitfield extract and insert
instructions at each member's bit_offset, matching the System V ABI,
GCC, and Clang. Remove check-known-to-fail from
validation/bitfield-bool-layout.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                        | 18 ++++++-------
 linearize.c                       |  2 ++
 symbol.c                          |  2 ++
 symbol.h                          |  2 ++
 validation/bitfield-bool-layout.c |  9 +++----
 validation/linear/bitfield-bool.c | 44 +++++++++++++++++++++++++++++++
 6 files changed, 62 insertions(+), 15 deletions(-)
 create mode 100644 validation/linear/bitfield-bool.c

diff --git a/evaluate.c b/evaluate.c
index 988ace95abed..40778f4e8a63 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -1406,12 +1406,12 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
 				return 1;
 		} else if (!(sclass & TYPE_RESTRICT))
 			goto Cast;
-                if (t == &bool_ctype) {
-                        if (is_fouled_type(s))
-                                warning((*rp)->pos, "%s degrades to integer",
-                                        show_typename(s->ctype.base_type));
-                        goto Cast;
-                }
+		if (is_bool_type(t)) {
+			if (is_fouled_type(s))
+				warning((*rp)->pos, "%s degrades to integer",
+					show_typename(s->ctype.base_type));
+			goto Cast;
+		}
 		*typediff = "different base types";
 		return 0;
 	}
@@ -1475,7 +1475,7 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
 
 	if (tclass & TYPE_NUM) {
 		/* XXX: need to turn into comparison with NULL */
-		if (t == &bool_ctype && (sclass & TYPE_PTR))
+		if (is_bool_type(t) && (sclass & TYPE_PTR))
 			goto Cast;
 		*typediff = "different base types";
 		return 0;
@@ -3064,7 +3064,7 @@ static struct symbol *evaluate_cast(struct expression *expr)
 			warning(expr->pos, "cast to %s",
 				show_typename(ttype));
 		if (sclass & TYPE_RESTRICT) {
-			if (ttype == &bool_ctype) {
+			if (is_bool_type(ttype)) {
 				if (sclass & TYPE_FOULED)
 					warning(expr->pos, "%s degrades to integer",
 						show_typename(stype));
@@ -3111,7 +3111,7 @@ static struct symbol *evaluate_cast(struct expression *expr)
 		}
 	}
 
-	if (ttype == &bool_ctype)
+	if (is_bool_type(ttype))
 		cast_to_bool(expr);
 
 	// checks pointers to restricted
diff --git a/linearize.c b/linearize.c
index 5bdd6119260f..75850df0a90f 100644
--- a/linearize.c
+++ b/linearize.c
@@ -934,6 +934,8 @@ static struct symbol *bitfield_base_type(struct symbol *sym)
 			base = base->ctype.base_type;
 		if (base->type == SYM_BITFIELD) {
 			base = base->ctype.base_type;
+			if (base->bit_size < bits_in_char)
+				base = &uchar_ctype;
 			if (sym->packed) {
 				int size = bits_to_bytes(sym->bit_offset + sym->bit_size);
 				sym = __alloc_symbol(0);
diff --git a/symbol.c b/symbol.c
index 5470880db271..feaaed3b8a2d 100644
--- a/symbol.c
+++ b/symbol.c
@@ -113,6 +113,8 @@ static int bitfield_base_size(struct symbol *sym)
 		sym = sym->ctype.base_type;
 	if (sym->type == SYM_BITFIELD)
 		sym = sym->ctype.base_type;
+	if (sym->bit_size < bits_in_char)
+		return bits_in_char;
 	return sym->bit_size;
 }
 
diff --git a/symbol.h b/symbol.h
index 9ff2733021b8..bd163450628b 100644
--- a/symbol.h
+++ b/symbol.h
@@ -477,6 +477,8 @@ static inline int is_bool_type(struct symbol *type)
 {
 	if (type->type == SYM_NODE)
 		type = type->ctype.base_type;
+	if (type->type == SYM_BITFIELD)
+		type = type->ctype.base_type;
 	return type == &bool_ctype;
 }
 
diff --git a/validation/bitfield-bool-layout.c b/validation/bitfield-bool-layout.c
index 4e0a2b4a2b60..2f559f98491f 100644
--- a/validation/bitfield-bool-layout.c
+++ b/validation/bitfield-bool-layout.c
@@ -12,14 +12,11 @@ struct bfb foo(struct bfb s)
 
 /*
  * check-name: bitfield-bool-layout
- * check-description: given that bool is here 1-bit wide
- *	each field here above completely 'fill' a bool.
- *	Thus 3 bools need to be allocated, but since the
- *	alignment is 1-byte the result has a size of 3
- *	bytes, 24 bits thus instead of 8.
+ * check-description: Verify that consecutive _Bool bitfields pack into a
+ *	single 1-byte (8-bit) storage unit rather than allocating a separate
+ *	byte per bitfield (24 bits).
  * check-command: test-linearize -Wno-decl $file
  *
- * check-known-to-fail
  * check-output-ignore
  * check-output-excludes: ret\\.24
  * check-output-contains: ret\\.8
diff --git a/validation/linear/bitfield-bool.c b/validation/linear/bitfield-bool.c
new file mode 100644
index 000000000000..91c9111eb91d
--- /dev/null
+++ b/validation/linear/bitfield-bool.c
@@ -0,0 +1,44 @@
+struct bfb {
+	_Bool a:1;
+	_Bool f:1;
+	_Bool z:1;
+};
+
+_Bool get_z(struct bfb *p)
+{
+	return p->z;
+}
+
+void set_f_one(struct bfb *p)
+{
+	p->f = 1;
+}
+
+void set_f_two(struct bfb *p)
+{
+	p->f = 2;
+}
+
+void set_f_ptr(struct bfb *p, int *ptr)
+{
+	p->f = ptr;
+}
+
+int init_bfb(void)
+{
+	struct bfb s = { .a = 1, .z = 1 };
+	return s.a + s.f + s.z;
+}
+
+/*
+ * check-name: bitfield-bool-linearize
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-excludes: load\\.1
+ * check-output-excludes: store\\.1
+ * check-output-contains: load\\.8
+ * check-output-contains: store\\.8
+ * check-output-pattern(2): or\\.8.*\\$2
+ * check-output-contains: ret\\.32.*\\$2
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks
  2026-11-09 20:31 Bart Van Assche
                   ` (3 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
                   ` (67 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Harden allocator bounds and statistics handling in allocate.c and
compat/mmap-blob.c:
- Zero reused freelist blocks with memset() instead of a pointer-sized
  loop in allocate().
- Check size > chunking - offset before calling blob_alloc() so blob
  header alignment overhead cannot underflow blob->left.
- Guard show_allocations() against division by zero when no allocations
  occurred.
- Check power-of-two chunk rounding and 4096-byte page alignment in
  blob_alloc() and blob_free().

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 allocate.c         | 22 +++++++++++-----------
 compat/mmap-blob.c |  4 ++--
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/allocate.c b/allocate.c
index bef85192eab1..2aca1815e5f2 100644
--- a/allocate.c
+++ b/allocate.c
@@ -33,6 +33,7 @@
 #include <stdlib.h>
 #include <stddef.h>
 #include <stdio.h>
+#include <string.h>
 
 #include "lib.h"
 #include "allocate.h"
@@ -88,10 +89,7 @@ void *allocate(struct allocator_struct *desc, unsigned int size)
 		void **p = desc->freelist;
 		retval = p;
 		desc->freelist = *p;
-		do {
-			*p = NULL;
-			p++;
-		} while ((size -= sizeof(void *)) > 0);
+		memset(retval, 0, size);
 		return retval;
 	}
 
@@ -100,17 +98,19 @@ void *allocate(struct allocator_struct *desc, unsigned int size)
 	size = (size + alignment - 1) & ~(alignment-1);
 	if (!blob || blob->left < size) {
 		unsigned int offset, chunking = desc->chunking;
-		struct allocation_blob *newblob = blob_alloc(chunking);
+		struct allocation_blob *newblob;
+
+		offset = offsetof(struct allocation_blob, data);
+		offset = (offset + alignment - 1) & ~(alignment-1);
+		if (size > chunking - offset)
+			die("alloc too big");
+		newblob = blob_alloc(chunking);
 		if (!newblob)
 			die("out of memory");
-		if (size > chunking)
-			die("alloc too big");
 		desc->total_bytes += chunking;
 		newblob->next = blob;
 		blob = newblob;
 		desc->blobs = newblob;
-		offset = offsetof(struct allocation_blob, data);
-		offset = (offset + alignment - 1) & ~(alignment-1);
 		blob->left = chunking - offset;
 		blob->offset = offset - offsetof(struct allocation_blob, data);
 	}
@@ -125,8 +125,8 @@ void show_allocations(struct allocator_struct *x)
 	fprintf(stderr, "%s: %d allocations, %lu bytes (%lu total bytes, "
 			"%6.2f%% usage, %6.2f average size)\n",
 		x->name, x->allocations, x->useful_bytes, x->total_bytes,
-		100 * (double) x->useful_bytes / x->total_bytes,
-		(double) x->useful_bytes / x->allocations);
+		100 * (double) x->useful_bytes / (x->total_bytes ?: 1),
+		(double) x->useful_bytes / (x->allocations ?: 1));
 }
 
 void get_allocator_stats(struct allocator_struct *x, struct allocator_stats *s)
diff --git a/compat/mmap-blob.c b/compat/mmap-blob.c
index 1cab4dead96d..91edeb9bc8d8 100644
--- a/compat/mmap-blob.c
+++ b/compat/mmap-blob.c
@@ -17,7 +17,7 @@ void *blob_alloc(unsigned long size)
 {
 	void *ptr;
 
-	if (size & ~CHUNK)
+	if (!size || (size & (CHUNK - 1)))
 		die("internal error: bad allocation size (%lu bytes)", size);
 	ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 	if (ptr == MAP_FAILED)
@@ -27,7 +27,7 @@ void *blob_alloc(unsigned long size)
 
 void blob_free(void *addr, unsigned long size)
 {
-	if (!size || (size & ~CHUNK) || ((unsigned long) addr & 512))
+	if (!size || (size & (CHUNK - 1)) || ((unsigned long) addr & 4095))
 		die("internal error: bad blob free (%lu bytes at %p)", size, addr);
 #ifndef DEBUG
 	munmap(addr, size);

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds
  2026-11-09 20:31 Bart Van Assche
                   ` (4 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
                   ` (66 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support long identifiers and harden character escape and token handling
in char.c, token.h, and tokenize.c:
- Widen struct ident's len field to unsigned short and enlarge identifier
  buffers in get_one_identifier() and show_ident() to 4096 bytes so
  identifiers longer than 255 bytes are not truncated.
- Guard parse_escape() against reading past the end of the buffer on a
  trailing backslash and cast input bytes to unsigned char.
- Format TOKEN_GNU_KLUDGE in show_token() and quote_token(), and use
  logical && when checking stream->pos && Wnewline_eof.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 char.c     |  5 +++--
 token.h    |  2 +-
 tokenize.c | 29 +++++++++++++++++------------
 3 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/char.c b/char.c
index 730ae3f5d8ca..1f6eb1f6be83 100644
--- a/char.c
+++ b/char.c
@@ -8,14 +8,15 @@
 
 static const char *parse_escape(const char *p, unsigned *val, const char *end, int bits, struct position pos)
 {
-	unsigned c = *p++;
+	unsigned c = (unsigned char)*p++;
 	unsigned d;
 	if (c != '\\') {
 		*val = c;
 		return p;
 	}
 
-	c = *p++;
+	if (p < end)
+		c = (unsigned char)*p++;
 	switch (c) {
 	case 'a': c = '\a'; break;
 	case 'b': c = '\b'; break;
diff --git a/token.h b/token.h
index 9000e0cb6574..8c8a496bee89 100644
--- a/token.h
+++ b/token.h
@@ -70,7 +70,7 @@ extern int *hash_stream(const char *name);
 struct ident {
 	struct ident *next;	/* Hash chain of identifiers */
 	struct symbol *symbols;	/* Pointer to semantic meaning list */
-	unsigned char len;	/* Length of identifier name */
+	unsigned short len;	/* Length of identifier name */
 	unsigned char tainted:1,
 	              reserved:1,
 		      keyword:1;
diff --git a/tokenize.c b/tokenize.c
index 54ea348cab91..76b0278925ed 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -98,14 +98,14 @@ const char *show_special(int val)
 
 const char *show_ident(const struct ident *ident)
 {
-	static char buff[4][256];
+	static char buff[4][4097];
 	static int n;
 	char *buffer;
 
 	if (!ident)
 		return "<noident>";
 	buffer = buff[3 & ++n];
-	sprintf(buffer, "%.*s", ident->len, ident->name);
+	snprintf(buffer, sizeof(buff[0]), "%.*s", ident->len, ident->name);
 	return buffer;
 }
 
@@ -230,11 +230,11 @@ const char *show_token(const struct token *token)
 			token->string->length - 1, 'L', '"');
 
 	case TOKEN_STREAMBEGIN:
-		sprintf(buffer, "<beginning of '%s'>", stream_name(token->pos.stream));
+		snprintf(buffer, sizeof(buffer), "<beginning of '%s'>", stream_name(token->pos.stream));
 		return buffer;
 
 	case TOKEN_STREAMEND:
-		sprintf(buffer, "<end of '%s'>", stream_name(token->pos.stream));
+		snprintf(buffer, sizeof(buffer), "<end of '%s'>", stream_name(token->pos.stream));
 		return buffer;
 
 	case TOKEN_UNTAINT:
@@ -245,6 +245,9 @@ const char *show_token(const struct token *token)
 		sprintf(buffer, "<argcnt>");
 		return buffer;
 
+	case TOKEN_GNU_KLUDGE:
+		return ",";
+
 	default:
 		sprintf(buffer, "unhandled token type '%d' ", token_type(token));
 		return buffer;
@@ -287,6 +290,8 @@ const char *quote_token(const struct token *token)
 	case TOKEN_WIDE_STRING: 
 		return quote_char(token->string->data,
 			token->string->length - 1, 'L', '"');
+	case TOKEN_GNU_KLUDGE:
+		return ",";
 	default:
 		sprintf(buffer, "unhandled token type '%d' ", token_type(token));
 		return buffer;
@@ -427,7 +432,7 @@ got_eof:
 		c = '\\';
 		goto out;
 	}
-	if (stream->pos & Wnewline_eof)
+	if (stream->pos && Wnewline_eof)
 		warning(stream_pos(stream), "no newline at end of file");
 	else if (spliced)
 		warning(stream_pos(stream), "backslash-newline at end of file");
@@ -860,7 +865,7 @@ static struct ident *create_hashed_ident(const char *name, int len, unsigned lon
 
 	p = &hash_table[hash];
 	while ((ident = *p) != NULL) {
-		if (ident->len == (unsigned char) len) {
+		if (ident->len == (unsigned short) len) {
 			if (memcmp(name, ident->name, len) != 0)
 				goto next;
 
@@ -919,7 +924,7 @@ static int get_one_identifier(int c, stream_t *stream)
 	struct token *token;
 	struct ident *ident;
 	unsigned long hash;
-	char buf[256];
+	char buf[4096];
 	int len = 1;
 	int next;
 
@@ -929,11 +934,11 @@ static int get_one_identifier(int c, stream_t *stream)
 		next = nextchar(stream);
 		if (!(cclass[next + 1] & (Letter | Digit)))
 			break;
-		if (len >= sizeof(buf))
-			break;
-		hash = ident_hash_add(hash, next);
-		buf[len] = next;
-		len++;
+		if (len < sizeof(buf)) {
+			hash = ident_hash_add(hash, next);
+			buf[len] = next;
+			len++;
+		}
 	};
 	if (cclass[next + 1] & Quote) {
 		if (len == 1 && buf[0] == 'L') {

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 07/73] pre-process: Fix macro expansion and include directive handling
  2026-11-09 20:31 Bart Van Assche
                   ` (5 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
                   ` (65 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix buffer bounds, unterminated macro arguments, and include path checks
in pre-process.c:
- Handle TOKEN_STREAMEND and TOKEN_STREAMBEGIN on unterminated macro
  argument lists and bound token-pasting buffers in combine().
- Widen token_name_sequence() to PATH_MAX and check remaining capacity
  with pointer subtraction to prevent buffer and pointer overflows.
- Reject directory paths in try_include() via fstat() and diagnose
  incomplete defined() expressions in #if directives.
- Add regression tests in
  validation/preprocessor/preprocess-crash-fixes.c and
  validation/preprocessor/preprocess-errors-and-limits.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 pre-process.c                                 | 83 +++++++++++++++----
 .../preprocessor/preprocess-crash-fixes.c     | 25 ++++++
 .../preprocess-errors-and-limits.c            | 33 ++++++++
 3 files changed, 123 insertions(+), 18 deletions(-)
 create mode 100644 validation/preprocessor/preprocess-crash-fixes.c
 create mode 100644 validation/preprocessor/preprocess-errors-and-limits.c

diff --git a/pre-process.c b/pre-process.c
index 7b6555677570..ca019d704a10 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -35,6 +35,7 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <time.h>
+#include <sys/stat.h>
 
 #include "lib.h"
 #include "allocate.h"
@@ -107,8 +108,12 @@ static int expand(struct token **, struct symbol *);
 
 static void replace_with_string(struct token *token, const char *str)
 {
-	int size = strlen(str) + 1;
-	struct string *s = __alloc_string(size);
+	size_t size = strlen(str) + 1;
+	struct string *s;
+
+	if (size > INT_MAX - sizeof(struct string))
+		die("string too large");
+	s = __alloc_string(size);
 
 	s->length = size;
 	memcpy(s->data, str, size);
@@ -312,6 +317,13 @@ struct arg {
 	int n_str;
 };
 
+static inline bool stream_end_token(const struct token *t)
+{
+	return eof_token(t) ||
+	       token_type(t) == TOKEN_STREAMEND ||
+	       token_type(t) == TOKEN_STREAMBEGIN;
+}
+
 static int collect_arguments(struct token *start, struct token *arglist, struct arg *args, struct token *what)
 {
 	int wanted = arglist->count.normal;
@@ -322,7 +334,7 @@ static int collect_arguments(struct token *start, struct token *arglist, struct
 
 	if (!wanted) {
 		next = collect_arg(start, 0, &what->pos);
-		if (eof_token(next))
+		if (stream_end_token(next))
 			goto Eclosing;
 		if (!eof_token(start->next) || !match_op(next, ')')) {
 			count++;
@@ -332,7 +344,7 @@ static int collect_arguments(struct token *start, struct token *arglist, struct
 		for (count = 0; count < wanted; count++) {
 			struct argcount *p = &arglist->next->count;
 			next = collect_arg(start, p->vararg, &what->pos);
-			if (eof_token(next))
+			if (stream_end_token(next))
 				goto Eclosing;
 			if (p->vararg && wanted == 1 && eof_token(start->next))
 				break;
@@ -373,7 +385,7 @@ Emany:
 		next = collect_arg(next, 0, &what->pos);
 		count++;
 	}
-	if (eof_token(next))
+	if (stream_end_token(next))
 		goto Eclosing;
 	sparse_error(what->pos, "macro \"%s\" passed %d arguments, but takes just %d",
 		show_token(what), count, wanted);
@@ -382,7 +394,10 @@ Eclosing:
 	sparse_error(what->pos, "unterminated argument list invoking macro \"%s\"",
 		show_token(what));
 out:
-	what->next = next->next;
+	if (stream_end_token(next))
+		what->next = next;
+	else
+		what->next = next->next;
 	return 0;
 }
 
@@ -499,12 +514,22 @@ static enum token_type combine(struct token *left, struct token *right, char *p)
 	if (t2 != TOKEN_IDENT && t2 != TOKEN_NUMBER && t2 != TOKEN_SPECIAL)
 		return TOKEN_ERROR;
 
-	strcpy(p, show_token(left));
-	strcat(p, show_token(right));
-	len = strlen(p);
+	{
+		const char *s1 = show_token(left);
+		int l1 = strlen(s1);
+		const char *s2;
+		int l2;
 
-	if (len >= 256)
-		return TOKEN_ERROR;
+		if (l1 > 4096)
+			return TOKEN_ERROR;
+		memcpy(p, s1, l1);
+		s2 = show_token(right);
+		l2 = strlen(s2);
+		if (l1 + l2 > 4096)
+			return TOKEN_ERROR;
+		memcpy(p + l1, s2, l2 + 1);
+		len = l1 + l2;
+	}
 
 	if (t1 == TOKEN_IDENT) {
 		if (t2 == TOKEN_SPECIAL)
@@ -537,7 +562,7 @@ static enum token_type combine(struct token *left, struct token *right, char *p)
 
 static int merge(struct token *left, struct token *right)
 {
-	static char buffer[512];
+	static char buffer[4097];
 	enum token_type res = combine(left, right, buffer);
 	int n;
 
@@ -782,17 +807,22 @@ static int expand(struct token **list, struct symbol *sym)
 
 static const char *token_name_sequence(struct token *token, int endop, struct token *start)
 {
-	static char buffer[256];
+	static char buffer[PATH_MAX];
 	char *ptr = buffer;
 
 	while (!eof_token(token) && !match_op(token, endop)) {
-		int len;
+		size_t len, rem;
 		const char *val;
 		if (token_type(token) == TOKEN_STRING)
 			val = token->string->data;
 		else
 			val = show_token(token);
 		len = strlen(val);
+		rem = sizeof(buffer) - (size_t)(ptr - buffer);
+		if (len >= rem) {
+			sparse_error(token->pos, "header name too long");
+			return NULL;
+		}
 		memcpy(ptr, val, len);
 		ptr += len;
 		token = token->next;
@@ -896,23 +926,30 @@ static void set_stream_include_path(struct stream *stream)
 static int try_include(struct position pos, const char *path, const char *filename, int flen, struct token **where, const char **next_path)
 {
 	int fd;
+	struct stat st;
 	int plen = strlen(path);
-	static char fullname[PATH_MAX];
+	char fullname[PATH_MAX];
 
+	if (!flen || plen + flen + 2 > sizeof(fullname))
+		return 0;
 	memcpy(fullname, path, plen);
 	if (plen && path[plen-1] != '/') {
 		fullname[plen] = '/';
 		plen++;
 	}
 	memcpy(fullname+plen, filename, flen);
+	fullname[plen + flen] = '\0';
 	if (already_tokenized(fullname))
 		return 1;
 	fd = open(fullname, O_RDONLY);
 	if (fd >= 0) {
-		char *streamname = xmemdup(fullname, plen + flen);
-		*where = tokenize(&pos, streamname, fd, *where, next_path);
+		if (fstat(fd, &st) == 0 && !S_ISDIR(st.st_mode)) {
+			char *streamname = xmemdup(fullname, plen + flen + 1);
+			*where = tokenize(&pos, streamname, fd, *where, next_path);
+			close(fd);
+			return 1;
+		}
 		close(fd);
-		return 1;
 	}
 	return 0;
 }
@@ -961,6 +998,8 @@ static int handle_include_path(struct stream *stream, struct token **list, struc
 
 	token = next->next;
 	filename = token_name_sequence(token, expect, token);
+	if (!filename)
+		return 1;
 	flen = strlen(filename) + 1;
 
 	/* Absolute path? */
@@ -1649,6 +1688,14 @@ static int expression_value(struct token **where)
 		list = &p->next;
 	}
 
+	if (state == 1 || state == 2) {
+		sparse_error((*beginning)->pos, "expected identifier after \"defined\"");
+		replace_with_bool(*beginning, 0);
+		(*beginning)->next = &eof_token_entry;
+	} else if (state == 3) {
+		sparse_error((*beginning)->pos, "missing ')' after \"defined\"");
+	}
+
 	p = constant_expression(*where, &expr);
 	if (!eof_token(p))
 		sparse_error(p->pos, "garbage at end: %s", show_token_sequence(p, 0));
diff --git a/validation/preprocessor/preprocess-crash-fixes.c b/validation/preprocessor/preprocess-crash-fixes.c
new file mode 100644
index 000000000000..92ea1ecde717
--- /dev/null
+++ b/validation/preprocessor/preprocess-crash-fixes.c
@@ -0,0 +1,25 @@
+#define REC_A(...) REC_B(__VA_ARGS__)
+#define REC_B(...) REC_A(__VA_ARGS__)
+extern int REC_A(int, ...);
+int rec_nonempty = sizeof(REC_A(1));
+
+/* Recursive macro with empty __VA_ARGS__ must terminate without crash */
+#define REC_E(...) REC_F(__VA_ARGS__)
+#define REC_F(...) REC_E(__VA_ARGS__)
+extern int REC_E(void);
+int rec_empty = sizeof(REC_E());
+
+#define ID(x) x
+int a = ID(1);
+
+/* Octal escape > 0377 */
+int oct_overflow = '\400';
+
+/*
+ * check-name: preprocess-crash-fixes
+ * check-command: sparse -Wno-decl $file
+ *
+ * check-error-start
+preprocessor/preprocess-crash-fixes.c:16:20: warning: octal escape sequence out of range
+ * check-error-end
+ */
diff --git a/validation/preprocessor/preprocess-errors-and-limits.c b/validation/preprocessor/preprocess-errors-and-limits.c
new file mode 100644
index 000000000000..7ad1265ba9c0
--- /dev/null
+++ b/validation/preprocessor/preprocess-errors-and-limits.c
@@ -0,0 +1,33 @@
+#if defined()
+#endif
+#if defined(FOO
+#endif
+#if defined
+#endif
+#if defined(
+#endif
+#include "."
+static const char *s = "\x";
+
+/* 300-character identifier */
+static int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 42;
+
+static int get_val(void)
+{
+	return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
+}
+
+/*
+ * check-name: preprocess-errors-and-limits
+ * check-command: sparse $file
+ * check-exit-value: 1
+ *
+ * check-error-start
+preprocessor/preprocess-errors-and-limits.c:10:27: warning: \x used with no following hex digits
+preprocessor/preprocess-errors-and-limits.c:1:13: error: expected preprocessor identifier
+preprocessor/preprocess-errors-and-limits.c:3:13: error: missing ')' after "defined"
+preprocessor/preprocess-errors-and-limits.c:5:5: error: expected identifier after "defined"
+preprocessor/preprocess-errors-and-limits.c:7:5: error: expected identifier after "defined"
+preprocessor/preprocess-errors-and-limits.c:9:10: error: unable to open '.'
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing
  2026-11-09 20:31 Bart Van Assche
                   ` (6 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
                   ` (64 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix buffer sizing and command-line option parsing safety in lib.c and
options.c:
- Enlarge the warning buffer in do_warn(), format with vsnprintf(),
  track warning counts without underflowing fmax_warnings, and allocate
  oversized add_pre_buffer() strings dynamically.
- Reject trailing characters and honor OPT_INVERSE in
  handle_switch_setval(), and diagnose missing arguments in -G,
  -include, -imacros, and -MF/-MQ/-MT switches.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 lib.c     | 24 ++++++++++++++++++++----
 options.c | 19 +++++++++++--------
 2 files changed, 31 insertions(+), 12 deletions(-)

diff --git a/lib.c b/lib.c
index e282a4fb4d54..ff8efb157f39 100644
--- a/lib.c
+++ b/lib.c
@@ -128,13 +128,13 @@ static const char *show_stream_name(struct position pos)
 
 static void do_warn(const char *type, struct position pos, const char * fmt, va_list args)
 {
-	static char buffer[512];
+	static char buffer[4096];
 
 	/* Shut up warnings if position is bad_token.pos */
 	if (pos.type == TOKEN_BAD)
 		return;
 
-	vsprintf(buffer, fmt, args);	
+	vsnprintf(buffer, sizeof(buffer), fmt, args);
 
 	fflush(stdout);
 	fprintf(stderr, "%s:%d:%d: %s%s%s\n",
@@ -181,6 +181,7 @@ static void do_error(struct position pos, const char * fmt, va_list args)
 void warning(struct position pos, const char * fmt, ...)
 {
 	va_list args;
+	static unsigned int warnings = 0;
 
 	if (Wsparse_error) {
 		va_start(args, fmt);
@@ -194,9 +195,13 @@ void warning(struct position pos, const char * fmt, ...)
 		return;
 	}
 
-	if (!--fmax_warnings) {
+	if (++warnings > fmax_warnings) {
+		static int once = 0;
 		show_info = 0;
+		if (once)
+			return;
 		fmt = "too many warnings";
+		once = 1;
 	}
 
 	va_start(args, fmt);
@@ -256,11 +261,22 @@ void add_pre_buffer(const char *fmt, ...)
 	unsigned int size;
 	struct token *begin, *end;
 	char buffer[4096];
+	char *buf = buffer;
 
 	va_start(args, fmt);
 	size = vsnprintf(buffer, sizeof(buffer), fmt, args);
 	va_end(args);
-	begin = tokenize_buffer(buffer, size, &end);
+	if (size >= sizeof(buffer)) {
+		buf = malloc(size + 1);
+		if (!buf)
+			die("out of memory");
+		va_start(args, fmt);
+		vsnprintf(buf, size + 1, fmt, args);
+		va_end(args);
+	}
+	begin = tokenize_buffer(buf, size, &end);
+	if (buf != buffer)
+		free(buf);
 	*pre_buffer_next = begin;
 	pre_buffer_next = &end->next;
 }
diff --git a/options.c b/options.c
index 6ee4d878a473..5aa3290e07da 100644
--- a/options.c
+++ b/options.c
@@ -315,7 +315,9 @@ static void handle_onoff_switch_finalize(const struct flag flags[])
 
 static int handle_switch_setval(const char *arg, const char *opt, const struct flag *flag, int options)
 {
-	*(flag->flag) = flag->mask;
+	if (*opt != '\0')
+		return 0;
+	*(flag->flag) = (options & OPT_INVERSE) ? 0 : flag->mask;
 	return 1;
 }
 
@@ -548,7 +550,7 @@ static char **handle_switch_f(char *arg, char **next)
 
 static char **handle_switch_G(char *arg, char **next)
 {
-	if (!strcmp(arg, "G") && *next)
+	if (!strcmp(arg, "G") && next[1])
 		return next + 1; // "-G 0"
 	else
 		return next;     // "-G0" or (bogus) terminal "-G"
@@ -599,11 +601,12 @@ static void add_cmdline_include(char *filename)
 
 static char **handle_switch_i(char *arg, char **next)
 {
-	if (*next && !strcmp(arg, "include"))
-		add_cmdline_include(*++next);
-	else if (*next && !strcmp(arg, "imacros"))
-		add_cmdline_include(*++next);
-	else if (*next && !strcmp(arg, "isystem")) {
+	if (!strcmp(arg, "include") || !strcmp(arg, "imacros")) {
+		char *path = *++next;
+		if (!path)
+			die("missing argument for -%s option", arg);
+		add_cmdline_include(path);
+	} else if (*next && !strcmp(arg, "isystem")) {
 		char *path = *++next;
 		if (!path)
 			die("missing argument for -isystem option");
@@ -620,7 +623,7 @@ static char **handle_switch_i(char *arg, char **next)
 static char **handle_switch_M(char *arg, char **next)
 {
 	if (!strcmp(arg, "MF") || !strcmp(arg,"MQ") || !strcmp(arg,"MT")) {
-		if (!*next)
+		if (!next[1])
 			die("missing argument for -%s option", arg);
 		return next + 1;
 	}

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds
  2026-11-09 20:31 Bart Van Assche
                   ` (7 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
                   ` (63 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix builtin argument evaluation and __builtin_object_size() expansion in
builtin.c:
- Initialize sym upfront in eval_args() before reporting argument count
  errors.
- Accept integer, enum, pointer, and restricted bitwise types in
  eval_atomic_common() while preserving per-parameter const and volatile
  pointer qualifiers via make_atomic_ptr_type().
- Bound pointer-chasing hops, unwrap parenthesized expressions, guard
  null symbols, and clamp offset subtraction in expand_object_size().
- Add regression tests in validation/atomic-builtins-types.c,
  validation/atomic-builtins-const.c, and
  validation/builtin-object-size-cycle.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 builtin.c                              | 112 +++++++++++++++++--------
 symbol.c                               |   3 +
 symbol.h                               |   1 +
 validation/atomic-builtins-const.c     |  67 +++++++++++++++
 validation/atomic-builtins-types.c     |  47 +++++++++++
 validation/builtin-object-size-cycle.c |  44 ++++++++++
 6 files changed, 239 insertions(+), 35 deletions(-)
 create mode 100644 validation/atomic-builtins-const.c
 create mode 100644 validation/atomic-builtins-types.c
 create mode 100644 validation/builtin-object-size-cycle.c

diff --git a/builtin.c b/builtin.c
index 1f3568bbad47..78a971ce4bb5 100644
--- a/builtin.c
+++ b/builtin.c
@@ -68,7 +68,7 @@ static int evaluate_pure_unop(struct expression *expr)
 static int eval_args(struct expression *expr, int n)
 {
 	struct expression *arg;
-	struct symbol *sym;
+	struct symbol *sym = expr->fn->ctype;
 	const char *msg;
 	int rc = 1;
 
@@ -87,7 +87,6 @@ static int eval_args(struct expression *expr, int n)
 	return rc;
 
 error:
-	sym = expr->fn->ctype;
 	expression_error(expr, "%s for %s", msg, show_ident(sym->ident));
 	return 0;
 }
@@ -454,47 +453,78 @@ struct symbol_op generic_int_op = {
 };
 
 
+static struct symbol *make_atomic_ptr_type(struct position pos,
+					   struct symbol *target,
+					   unsigned long mods,
+					   struct ident *as)
+{
+	struct symbol *ptr = alloc_symbol(pos, SYM_PTR);
+	ptr->ctype.base_type = target;
+	ptr->ctype.modifiers = mods;
+	ptr->ctype.as = as;
+	ptr->bit_size = bits_in_pointer;
+	ptr->ctype.alignment = pointer_alignment;
+	return ptr;
+}
+
 static int eval_atomic_common(struct expression *expr)
 {
 	struct symbol *fntype = expr->fn->ctype->ctype.base_type;
 	struct symbol_list *types = NULL;
 	struct symbol *ctype = NULL;
+	struct symbol *valtype = NULL;
+	struct symbol *aptr = NULL;
 	struct symbol *t;
 	struct expression *arg;
 	int n = 0;
 
 	// The number of arguments has already be verified.
-	// The first arg must be a pointer to an integral type.
+	// The first arg must be a pointer to an integral or pointer type.
 	PREPARE_PTR_LIST(fntype->arguments, t);
 	FOR_EACH_PTR(expr->args, arg) {
-		struct symbol *ptrtype = NULL;
-
 		if (++n == 1) {
-			t = arg->ctype;
-			if (!t)
+			struct symbol *proto_ptr = t ? t->ctype.base_type : NULL;
+			unsigned long proto_mods = MOD_VOLATILE;
+			if (proto_ptr && proto_ptr->ctype.base_type)
+				proto_mods = proto_ptr->ctype.base_type->ctype.modifiers;
+			aptr = arg->ctype;
+			if (!aptr)
 				return 0;
-			if (t->type == SYM_NODE)
-				t = t->ctype.base_type;
-			if (!t)
+			if (aptr->type == SYM_NODE)
+				aptr = aptr->ctype.base_type;
+			if (!aptr)
 				return 0;
-			if (t->type != SYM_PTR)
+			if (aptr->type != SYM_PTR)
 				goto err;
-			ptrtype = t;
-			t = t->ctype.base_type;
-			if (!t)
+			ctype = examine_pointer_target(aptr);
+			if (!ctype)
 				return 0;
-			if (t->type == SYM_NODE)
-				t = t->ctype.base_type;
-			if (!t)
-				return 0;
-			if (t->type != SYM_PTR && t->ctype.base_type != &int_type)
+			if (ctype->type != SYM_PTR && ctype->ctype.base_type != &int_type &&
+			    !is_int_type(ctype) && !is_restricted_type(ctype))
 				goto err;
-			ctype = t;
-			t = ptrtype;
+			valtype = ctype;
+			if (aptr->ctype.modifiers & MOD_NOCAST) {
+				struct symbol *node = alloc_symbol(arg->pos, SYM_NODE);
+				*node = *ctype;
+				node->type = SYM_NODE;
+				node->ctype.base_type = ctype;
+				node->ctype.modifiers |= MOD_NOCAST;
+				valtype = node;
+			}
+			t = make_atomic_ptr_type(
+				arg->pos, ctype,
+				proto_mods | (aptr->ctype.modifiers & (MOD_ATOMIC | MOD_NOCAST | MOD_NODEREF)),
+				aptr->ctype.as);
+		} else if (!t) {
+			break;
 		} else if (is_dynamic_type(t)) {
-			t = ctype;
-		} else if (t == &ptr_ctype) {
-			t = ptrtype;
+			t = valtype;
+		} else if (t == &ptr_ctype || t->ctype.base_type == &ptr_ctype) {
+			t = make_atomic_ptr_type(arg->pos, ctype,
+						 aptr->ctype.modifiers & MOD_NOCAST, NULL);
+		} else if (t == &const_ptr_ctype || t->ctype.base_type == &const_ptr_ctype) {
+			t = make_atomic_ptr_type(arg->pos, ctype,
+						 MOD_CONST | (aptr->ctype.modifiers & MOD_NOCAST), NULL);
 		}
 		add_ptr_list(&types, t);
 		NEXT_PTR_LIST(t);
@@ -502,7 +532,7 @@ static int eval_atomic_common(struct expression *expr)
 	FINISH_PTR_LIST(t);
 
 	if (!expr->ctype)	// set the return type, if needed
-		expr->ctype = ctype;
+		expr->ctype = valtype;
 	return evaluate_arguments(types, expr->args);
 
 err:
@@ -528,8 +558,13 @@ static int expand_object_size(struct expression *expr, int cost)
 	struct expression *arg = first_expression(expr->args);
 	int type = get_expression_value_silent(ptr_list_nth(expr->args, 1));
 	unsigned long val = -1, off = 0;
+	int hops = 0;
 
 	while (arg) {
+		if (++hops > 64) {
+			val = -1;
+			break;
+		}
 		switch (arg->type) {
 		case EXPR_IMPLIED_CAST:
 		case EXPR_CAST:
@@ -544,15 +579,20 @@ static int expand_object_size(struct expression *expr, int cost)
 			arg = arg->left;
 			continue;
 		case EXPR_PREOP:
+			if (arg->op == '(') {
+				arg = arg->unop;
+				continue;
+			}
 			// a deref is just intermediate variable
 			// and so the offset needs to be zeroed.
 			if (arg->op == '*') {
 				struct expression *parent = arg;
 				arg = arg->unop;
-				off = 0;
+				if (!arg)
+					break;
 				switch (arg->type) {
 				case EXPR_SYMBOL:
-					arg = arg->symbol->initializer;
+					arg = arg->symbol ? arg->symbol->initializer : NULL;
 					if (arg == parent) {
 						// stop at self-initialized vars
 						// and do not expand them.
@@ -562,6 +602,7 @@ static int expand_object_size(struct expression *expr, int cost)
 					}
 					continue;
 				default:
+					off = 0;
 					break;
 				}
 			}
@@ -584,7 +625,7 @@ static int expand_object_size(struct expression *expr, int cost)
 	else if (type & 1)
 		return UNSAFE;
 	else
-		val -= off;
+		val = (off > val) ? 0 : val - off;
 
 	expr->flags |= CEF_SET_ICE;
 	expr->type = EXPR_VALUE;
@@ -678,13 +719,14 @@ static const struct builtin_fn builtins_common[] = {
 #define size_t_ctype	&size_t_alias
 #define va_list_ctype	&ptr_ctype
 #define vol_ptr		&volatile_ptr_ctype
+#define cv_ptr		&const_volatile_ptr_ctype
 	{ "__atomic_add_fetch", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
-	{ "__atomic_always_lock_free", &bool_ctype, 0, { size_t_ctype, vol_ptr }},
+	{ "__atomic_always_lock_free", &bool_ctype, 0, { size_t_ctype, cv_ptr }},
 	{ "__atomic_and_fetch", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_clear", &void_ctype, 0, { &volatile_bool_ptr_ctype, &int_ctype }},
-	{ "__atomic_compare_exchange", &bool_ctype, 0, { vol_ptr, &ptr_ctype, &ptr_ctype, &bool_ctype, &int_ctype, &int_ctype }, .op = &atomic_op },
+	{ "__atomic_compare_exchange", &bool_ctype, 0, { vol_ptr, &ptr_ctype, &const_ptr_ctype, &bool_ctype, &int_ctype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_compare_exchange_n", &bool_ctype, 0, { vol_ptr, &ptr_ctype, &dyntype, &bool_ctype, &int_ctype, &int_ctype }, .op = &atomic_op },
-	{ "__atomic_exchange", &void_ctype, 0, { vol_ptr, &ptr_ctype, &ptr_ctype, &int_ctype }, .op = &atomic_op },
+	{ "__atomic_exchange", &void_ctype, 0, { vol_ptr, &const_ptr_ctype, &ptr_ctype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_exchange_n", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_fetch_add", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_fetch_and", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
@@ -692,13 +734,13 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__atomic_fetch_or",  NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_fetch_sub", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_fetch_xor", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
-	{ "__atomic_is_lock_free", &bool_ctype, 0, { size_t_ctype, vol_ptr }},
-	{ "__atomic_load", &void_ctype, 0, { vol_ptr, &ptr_ctype, &int_ctype }, .op = &atomic_op },
-	{ "__atomic_load_n", NULL, 0, { vol_ptr, &int_ctype }, .op = &atomic_op },
+	{ "__atomic_is_lock_free", &bool_ctype, 0, { size_t_ctype, cv_ptr }},
+	{ "__atomic_load", &void_ctype, 0, { cv_ptr, &ptr_ctype, &int_ctype }, .op = &atomic_op },
+	{ "__atomic_load_n", NULL, 0, { cv_ptr, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_nand_fetch",NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_or_fetch",  NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_signal_fence", &void_ctype, 0, { &int_ctype }},
-	{ "__atomic_store", &void_ctype, 0, { vol_ptr, &ptr_ctype, &int_ctype }, .op = &atomic_op },
+	{ "__atomic_store", &void_ctype, 0, { vol_ptr, &const_ptr_ctype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_store_n", &void_ctype, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_sub_fetch", NULL, 0, { vol_ptr, &dyntype, &int_ctype }, .op = &atomic_op },
 	{ "__atomic_test_and_set", &bool_ctype, 0, { vol_ptr, &int_ctype }},
diff --git a/symbol.c b/symbol.c
index feaaed3b8a2d..abf3a198acc8 100644
--- a/symbol.c
+++ b/symbol.c
@@ -883,6 +883,7 @@ struct symbol	const_void_ctype, const_char_ctype;
 struct symbol	const_ptr_ctype, const_string_ctype;
 struct symbol	const_wchar_ctype, const_wstring_ctype;
 struct symbol	volatile_void_ctype, volatile_ptr_ctype;
+struct symbol	const_volatile_void_ctype, const_volatile_ptr_ctype;
 struct symbol	volatile_bool_ctype, volatile_bool_ptr_ctype;
 
 struct symbol	zero_int;
@@ -990,6 +991,8 @@ static const struct ctype_declare {
 	{ &const_wchar_ctype,  T_CONST(&int_ctype, NULL, NULL) },
 	{ &volatile_void_ctype,T_NODE(MOD_VOLATILE, &void_ctype, NULL, NULL) },
 	{ &volatile_ptr_ctype, T_PTR(&volatile_void_ctype) },
+	{ &const_volatile_void_ctype, T_NODE(MOD_CONST | MOD_VOLATILE, &void_ctype, NULL, NULL) },
+	{ &const_volatile_ptr_ctype, T_PTR(&const_volatile_void_ctype) },
 	{ &volatile_bool_ctype,T_NODE(MOD_VOLATILE, &bool_ctype, NULL, NULL) },
 	{ &volatile_bool_ptr_ctype, T_PTR(&volatile_bool_ctype) },
 	{ NULL, }
diff --git a/symbol.h b/symbol.h
index bd163450628b..9d657fbba260 100644
--- a/symbol.h
+++ b/symbol.h
@@ -316,6 +316,7 @@ extern struct symbol	const_void_ctype, const_char_ctype;
 extern struct symbol	const_ptr_ctype, const_string_ctype;
 extern struct symbol	const_wchar_ctype, const_wstring_ctype;
 extern struct symbol	volatile_void_ctype, volatile_ptr_ctype;
+extern struct symbol	const_volatile_void_ctype, const_volatile_ptr_ctype;
 extern struct symbol	volatile_bool_ctype, volatile_bool_ptr_ctype;
 
 /* Special internal symbols */
diff --git a/validation/atomic-builtins-const.c b/validation/atomic-builtins-const.c
new file mode 100644
index 000000000000..cce40c18cc5e
--- /dev/null
+++ b/validation/atomic-builtins-const.c
@@ -0,0 +1,67 @@
+static void test_atomic_const_writes(const int *cptr, int *ptr,
+				     int *exp, int *out,
+				     const _Bool *cflag)
+{
+	int val = 1;
+
+	__atomic_store(cptr, &val, 0);
+	__atomic_store_n(cptr, val, 0);
+	__atomic_load(ptr, cptr, 0);
+	__atomic_exchange(cptr, &val, out, 0);
+	__atomic_exchange(ptr, &val, cptr, 0);
+	(void)__atomic_exchange_n(cptr, val, 0);
+	(void)__atomic_compare_exchange(cptr, exp, &val, 0, 0, 0);
+	(void)__atomic_compare_exchange(ptr, cptr, &val, 0, 0, 0);
+	(void)__atomic_compare_exchange_n(cptr, exp, val, 0, 0, 0);
+	(void)__atomic_compare_exchange_n(ptr, cptr, val, 0, 0, 0);
+	(void)__atomic_fetch_add(cptr, 1, 0);
+	(void)__atomic_test_and_set(cflag, 0);
+	__atomic_clear(cflag, 0);
+}
+
+/*
+ * check-name: atomic-builtins-const
+ * check-command: sparse $file
+ *
+ * check-error-start
+atomic-builtins-const.c:7:24: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:7:24:    expected int volatile *
+atomic-builtins-const.c:7:24:    got int const *cptr
+atomic-builtins-const.c:8:26: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:8:26:    expected int volatile *
+atomic-builtins-const.c:8:26:    got int const *cptr
+atomic-builtins-const.c:9:28: warning: incorrect type in argument 2 (different modifiers)
+atomic-builtins-const.c:9:28:    expected int *
+atomic-builtins-const.c:9:28:    got int const *cptr
+atomic-builtins-const.c:10:27: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:10:27:    expected int volatile *
+atomic-builtins-const.c:10:27:    got int const *cptr
+atomic-builtins-const.c:11:38: warning: incorrect type in argument 3 (different modifiers)
+atomic-builtins-const.c:11:38:    expected int *
+atomic-builtins-const.c:11:38:    got int const *cptr
+atomic-builtins-const.c:12:35: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:12:35:    expected int volatile *
+atomic-builtins-const.c:12:35:    got int const *cptr
+atomic-builtins-const.c:13:41: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:13:41:    expected int volatile *
+atomic-builtins-const.c:13:41:    got int const *cptr
+atomic-builtins-const.c:14:46: warning: incorrect type in argument 2 (different modifiers)
+atomic-builtins-const.c:14:46:    expected int *
+atomic-builtins-const.c:14:46:    got int const *cptr
+atomic-builtins-const.c:15:43: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:15:43:    expected int volatile *
+atomic-builtins-const.c:15:43:    got int const *cptr
+atomic-builtins-const.c:16:48: warning: incorrect type in argument 2 (different modifiers)
+atomic-builtins-const.c:16:48:    expected int *
+atomic-builtins-const.c:16:48:    got int const *cptr
+atomic-builtins-const.c:17:34: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:17:34:    expected int volatile *
+atomic-builtins-const.c:17:34:    got int const *cptr
+atomic-builtins-const.c:18:37: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:18:37:    expected void volatile *
+atomic-builtins-const.c:18:37:    got bool const *cflag
+atomic-builtins-const.c:19:24: warning: incorrect type in argument 1 (different modifiers)
+atomic-builtins-const.c:19:24:    expected bool volatile *
+atomic-builtins-const.c:19:24:    got bool const *cflag
+ * check-error-end
+ */
diff --git a/validation/atomic-builtins-types.c b/validation/atomic-builtins-types.c
new file mode 100644
index 000000000000..5f163e5e60f8
--- /dev/null
+++ b/validation/atomic-builtins-types.c
@@ -0,0 +1,47 @@
+typedef unsigned int __attribute__((bitwise)) be32;
+enum E { E_ZERO, E_ONE, E_TWO };
+
+static be32 test_atomic_restricted(be32 *ptr, be32 val, be32 *expected)
+{
+	__atomic_store_n(ptr, val, 0);
+	be32 old = __atomic_exchange_n(ptr, val, 0);
+	_Bool ok = __atomic_compare_exchange_n(ptr, expected, val, 0, 0, 0);
+	(void)ok;
+	return old;
+}
+
+static enum E test_atomic_enum(enum E *ptr, enum E val)
+{
+	__atomic_store_n(ptr, val, 0);
+	return __atomic_load_n(ptr, 0);
+}
+
+static int test_atomic_const_reads(int *ptr, const int *cptr,
+				   volatile int *vptr,
+				   const volatile int *cvptr,
+				   int *expected, int *out,
+				   volatile _Bool *vflag)
+{
+	int v = __atomic_load_n(cptr, 0);
+	v += __atomic_load_n(cvptr, 0);
+
+	__atomic_load(cptr, out, 0);
+	__atomic_load(cvptr, out, 0);
+	__atomic_store(ptr, cptr, 0);
+	__atomic_exchange(ptr, cptr, out, 0);
+	(void)__atomic_compare_exchange(ptr, expected, cptr, 0, 0, 0);
+
+	__atomic_store_n(vptr, v, 0);
+	v += __atomic_fetch_add(vptr, 1, 0);
+	(void)__atomic_test_and_set(vflag, 0);
+	__atomic_clear(vflag, 0);
+
+	v += __atomic_always_lock_free(sizeof(*cvptr), cvptr);
+	v += __atomic_is_lock_free(sizeof(*cvptr), cvptr);
+	return v;
+}
+
+/*
+ * check-name: atomic-builtins-types
+ * check-command: sparse -Wbitwise $file
+ */
diff --git a/validation/builtin-object-size-cycle.c b/validation/builtin-object-size-cycle.c
new file mode 100644
index 000000000000..38e2a9786e9a
--- /dev/null
+++ b/validation/builtin-object-size-cycle.c
@@ -0,0 +1,44 @@
+static void *p2;
+static void *p1 = &p2;
+static void *p2 = &p1;
+
+static unsigned long test_static_max(void)
+{
+	return __builtin_object_size(*(void **)p1, 0);
+}
+
+static unsigned long test_static_min(void)
+{
+	return __builtin_object_size(*(void **)p1, 2);
+}
+
+/*
+ * Self-referential local pointer definitions (SSA/def cycle through OP_ADD
+ * and cast) that traverse expand_object_size()'s visited >= 64 cycle guard.
+ */
+static unsigned long test_self_add_max(void)
+{
+	char *p = p + 1;
+	return __builtin_object_size(p, 0);
+}
+
+static unsigned long test_self_add_min(void)
+{
+	char *p = p + 1;
+	return __builtin_object_size(p, 2);
+}
+
+static unsigned long test_self_cast_max(void)
+{
+	void *q = (void *)q;
+	return __builtin_object_size(q, 0);
+}
+
+/*
+ * check-name: builtin-object-size-cycle
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-contains: ret\\..*\\$-1
+ * check-output-contains: ret\\..*\\$0
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds
  2026-11-09 20:31 Bart Van Assche
                   ` (8 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
                   ` (62 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix parser attribute handling, enumerator overflow promotion, and type
formatting bounds in expression.c, parse.c, show-parse.c, and symbol.h:
- Reject invalid integer suffixes and missing _Generic association
  expressions in expression.c.
- Propagate designated_init and transparent_union attributes on forward
  and post-brace tag declarations, promote overflowing enumerators while
  diagnosing ULLONG_MAX wraparound, validate alignment and address_space
  bounds, transfer context attributes across pointer return types, and
  guard null current_fn.
- Enlarge show-parse.c formatting buffers, reserve space for the trailing
  NUL byte, and route all type formatting through prepend() and append()
  with pointer-subtraction capacity checks.
- Add regression tests in validation/parser-type-fixes.c and
  validation/parser-enum-overflow.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 expression.c                      | 16 +++++-
 parse.c                           | 81 ++++++++++++++++++++++++---
 show-parse.c                      | 61 ++++++++++++--------
 symbol.h                          |  2 +
 validation/parser-enum-overflow.c | 13 +++++
 validation/parser-type-fixes.c    | 93 +++++++++++++++++++++++++++++++
 6 files changed, 233 insertions(+), 33 deletions(-)
 create mode 100644 validation/parser-enum-overflow.c
 create mode 100644 validation/parser-type-fixes.c

diff --git a/expression.c b/expression.c
index 727e7056e460..04b1a242d08a 100644
--- a/expression.c
+++ b/expression.c
@@ -290,8 +290,12 @@ static void get_number_value(struct expression *expr, struct token *token)
 				size = 2;
 				end++;
 			}
-		} else
+		} else if (c == '.' || c == 'e' || c == 'E' || c == 'p' || c == 'P' ||
+			   strchr(end, '.') || strchr(end, 'e') || strchr(end, 'E')) {
 			goto Float;
+		} else {
+			goto Enoint;
+		}
 	}
 	if (overflow)
 		goto Eoverflow;
@@ -402,8 +406,12 @@ static struct token *generic_selection(struct token *token, struct expression **
 			token = typename(token, &map->type, NULL);
 			token = expect(token, ':', "after typename");
 			token = assignment_expression(token, &map->expr);
-			*last = map;
-			last = &map->next;
+			if (!map->expr)
+				sparse_error(token->pos, "expected expression after ':'");
+			else {
+				*last = map;
+				last = &map->next;
+			}
 		} else if (match_ident(token, &default_ident)) {
 			if (expr->def) {
 				warning(token->pos, "multiple default in generic expression");
@@ -412,6 +420,8 @@ static struct token *generic_selection(struct token *token, struct expression **
 			token = token->next;
 			token = expect(token, ':', "after typename");
 			token = assignment_expression(token, &expr->def);
+			if (!expr->def)
+				sparse_error(token->pos, "expected expression after ':'");
 		}
 	}
 end:
diff --git a/parse.c b/parse.c
index f349cc75bff4..ea90d088a244 100644
--- a/parse.c
+++ b/parse.c
@@ -753,8 +753,21 @@ static struct token *struct_union_enum_specifier(enum type type,
 		ctx->ctype.base_type = sym;
 		repos = &token->pos;
 		token = token->next;
-		if (!match_op(token, '{'))
+		if (!match_op(token, '{')) {
+			if (attr.designated_init) {
+				if (sym->type == SYM_STRUCT)
+					sym->designated_init = 1;
+				else
+					warning(token->pos, "attribute designated_init applied to non-structure type");
+			}
+			if (attr.transparent_union) {
+				if (sym->type == SYM_UNION)
+					sym->transparent_union = 1;
+				else
+					warning(token->pos, "attribute __transparent_union__ applied to non-union type");
+			}
 			return token;
+		}
 
 		// The following test is actually wrong for empty
 		// structs, but (1) they are not C99, (2) gcc does
@@ -782,6 +795,18 @@ static struct token *struct_union_enum_specifier(enum type type,
 	token = handle_attributes(token, &attr);
 	apply_ctype(token->pos, &sym->ctype, &attr.ctype);
 	sym->packed = attr.packed;
+	if (attr.designated_init) {
+		if (sym->type == SYM_STRUCT)
+			sym->designated_init = 1;
+		else
+			warning(token->pos, "attribute designated_init applied to non-structure type");
+	}
+	if (attr.transparent_union) {
+		if (sym->type == SYM_UNION)
+			sym->transparent_union = 1;
+		else
+			warning(token->pos, "attribute __transparent_union__ applied to non-union type");
+	}
 
 	sym->endpos = token->pos;
 
@@ -935,6 +960,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 		struct token *next = token->next;
 		struct decl_state ctx = { };
 		struct symbol *sym;
+		bool overflow = false;
 
 		// FIXME: only 'deprecated' should be accepted
 		next = handle_attributes(next, &ctx);
@@ -948,14 +974,41 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 		} else if (!ctype) {
 			ctype = &int_ctype;
 		} else if (is_int_type(ctype)) {
-			lastval++;
+			struct range crange = type_range(ctype);
+			if (is_signed_type(ctype) && (long long)lastval == crange.pos) {
+				if (ctype->bit_size < bits_in_int)
+					ctype = &int_ctype;
+				else if (ctype->bit_size < bits_in_long)
+					ctype = &long_ctype;
+				else if (ctype->bit_size < bits_in_longlong)
+					ctype = &llong_ctype;
+				else
+					ctype = &ullong_ctype;
+			} else if (!is_signed_type(ctype) && lastval == crange.pos) {
+				if (ctype->bit_size < bits_in_int - 1)
+					ctype = &int_ctype;
+				else if (ctype->bit_size < bits_in_int)
+					ctype = &uint_ctype;
+				else if (ctype->bit_size < bits_in_long)
+					ctype = &ulong_ctype;
+				else if (ctype->bit_size < bits_in_longlong)
+					ctype = &ullong_ctype;
+				else {
+					sparse_error(token->pos,
+						"enumerator value for '%s' overflows",
+						show_ident(token->ident));
+					overflow = true;
+				}
+			}
+			if (!overflow)
+				lastval++;
 		} else {
 			error_die(token->pos, "can't increment the last enum member");
 		}
 
 		if (!expr) {
 			expr = alloc_expression(token->pos, EXPR_VALUE);
-			expr->value = lastval;
+			expr->value = lastval & bits_mask(ctype->bit_size);
 			expr->ctype = ctype;
 		}
 
@@ -1135,7 +1188,7 @@ static struct token *attribute_packed(struct token *token, struct symbol *attr,
 
 static struct token *attribute_aligned(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
-	int alignment = max_alignment;
+	long long alignment = max_alignment;
 	struct expression *expr = NULL;
 
 	if (match_op(token, '(')) {
@@ -1143,7 +1196,7 @@ static struct token *attribute_aligned(struct token *token, struct symbol *attr,
 		if (expr)
 			alignment = const_expression_value(expr);
 	}
-	if (alignment & (alignment-1)) {
+	if (alignment <= 0 || alignment > INT_MAX || (alignment & (alignment - 1))) {
 		warning(token->pos, "I don't like non-power-of-2 alignments");
 		return token;
 	} else if (alignment > ctx->ctype.alignment)
@@ -1222,7 +1275,7 @@ static struct token *attribute_address_space(struct token *token, struct symbol
 	switch (token_type(token)) {
 	case TOKEN_NUMBER:
 		next = primary_expression(token, &expr);
-		if (expr->type != EXPR_VALUE)
+		if (expr->type != EXPR_VALUE || expr->value > INT_MAX)
 			goto invalid;
 		as = numerical_address_space(expr->value);
 		break;
@@ -1351,6 +1404,8 @@ static struct token *attribute_designated_init(struct token *token, struct symbo
 {
 	if (ctx->ctype.base_type && ctx->ctype.base_type->type == SYM_STRUCT)
 		ctx->ctype.base_type->designated_init = 1;
+	else if (!ctx->ctype.base_type)
+		ctx->designated_init = 1;
 	else
 		warning(token->pos, "attribute designated_init applied to non-structure type");
 	return token;
@@ -1363,6 +1418,8 @@ static struct token *attribute_transparent_union(struct token *token, struct sym
 
 	if (ctx->ctype.base_type && ctx->ctype.base_type->type == SYM_UNION)
 		ctx->ctype.base_type->transparent_union = 1;
+	else if (!ctx->ctype.base_type)
+		ctx->transparent_union = 1;
 	else
 		warning(token->pos, "attribute __transparent_union__ applied to non-union type");
 	return token;
@@ -1856,9 +1913,16 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 
 	if (match_op(token, '(')) {
 		enum kind kind = which_func(token, p, ctx->prefer_abstract);
-		struct symbol *fn;
+		struct symbol *fn, *walk;
 		fn = alloc_indirect_symbol(token->pos, ctype, SYM_FN);
 		ctype->modifiers |= ctx->f_modifiers;
+		for (walk = fn->ctype.base_type; walk && walk->type == SYM_PTR; walk = walk->ctype.base_type) {
+			if (walk->ctype.contexts) {
+				concat_ptr_list((struct ptr_list *)walk->ctype.contexts,
+						(struct ptr_list **)&ctype->contexts);
+				walk->ctype.contexts = NULL;
+			}
+		}
 		token = token->next;
 		if (kind == K_R)
 			token = identifier_list(token, fn);
@@ -2440,7 +2504,8 @@ static void warn_label_usage(struct position def, struct position use, struct id
 	const char *id = show_ident(ident);
 	sparse_error(use, "label '%s' used outside statement expression", id);
 	info(def, "   label '%s' defined here", id);
-	current_fn->bogus_linear = 1;
+	if (current_fn)
+		current_fn->bogus_linear = 1;
 }
 
 void check_label_usage(struct symbol *label, struct position use_pos)
diff --git a/show-parse.c b/show-parse.c
index ceb6b3cb6f82..8ef59b5ee255 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -204,17 +204,27 @@ const char *show_as(struct ident *as)
 struct type_name {
 	char *start;
 	char *end;
+	char *buf_start;
+	char *buf_end;
 };
 
 static void FORMAT_ATTR(2) prepend(struct type_name *name, const char *fmt, ...)
 {
-	static char buffer[512];
+	static char buffer[4096];
 	int n;
 
 	va_list args;
 	va_start(args, fmt);
-	n = vsprintf(buffer, fmt, args);
+	n = vsnprintf(buffer, sizeof(buffer), fmt, args);
 	va_end(args);
+	if (n <= 0)
+		return;
+	if (n >= sizeof(buffer))
+		n = sizeof(buffer) - 1;
+	if (n > name->start - name->buf_start)
+		n = name->start - name->buf_start;
+	if (n <= 0)
+		return;
 
 	name->start -= n;
 	memcpy(name->start, buffer, n);
@@ -222,13 +232,21 @@ static void FORMAT_ATTR(2) prepend(struct type_name *name, const char *fmt, ...)
 
 static void FORMAT_ATTR(2) append(struct type_name *name, const char *fmt, ...)
 {
-	static char buffer[512];
+	static char buffer[4096];
 	int n;
 
 	va_list args;
 	va_start(args, fmt);
-	n = vsprintf(buffer, fmt, args);
+	n = vsnprintf(buffer, sizeof(buffer), fmt, args);
 	va_end(args);
+	if (n <= 0)
+		return;
+	if (n >= sizeof(buffer))
+		n = sizeof(buffer) - 1;
+	if (n > name->buf_end - name->end)
+		n = name->buf_end - name->end;
+	if (n <= 0)
+		return;
 
 	memcpy(name->end, buffer, n);
 	name->end += n;
@@ -314,17 +332,14 @@ deeper:
 	if (sym && (sym->type != SYM_NODE && sym->type != SYM_ARRAY &&
 		     sym->type != SYM_BITFIELD)) {
 		const char *s;
-		size_t len;
 
 		if (as)
 			prepend(name, "%s ", show_as(as));
 
-		if (sym && (sym->type == SYM_BASETYPE || sym->type == SYM_ENUM))
+		if (sym->type == SYM_BASETYPE || sym->type == SYM_ENUM)
 			mod &= ~MOD_SPECIFIER;
 		s = modifier_string(mod);
-		len = strlen(s);
-		name->start -= len;
-		memcpy(name->start, s, len);
+		prepend(name, "%s", s);
 		mod = 0;
 		as = NULL;
 	}
@@ -333,11 +348,9 @@ deeper:
 		goto out;
 
 	if ((typename = builtin_typename(sym))) {
-		int len = strlen(typename);
 		if (name->start != name->end)
-			*--name->start = ' ';
-		name->start -= len;
-		memcpy(name->start, typename, len);
+			prepend(name, " ");
+		prepend(name, "%s", typename);
 		goto out;
 	}
 
@@ -362,13 +375,13 @@ deeper:
 
 	case SYM_STRUCT:
 		if (name->start != name->end)
-			*--name->start = ' ';
+			prepend(name, " ");
 		prepend(name, "struct %s", show_ident(sym->ident));
 		goto out;
 
 	case SYM_UNION:
 		if (name->start != name->end)
-			*--name->start = ' ';
+			prepend(name, " ");
 		prepend(name, "union %s", show_ident(sym->ident));
 		goto out;
 
@@ -401,7 +414,7 @@ deeper:
 			append(name, " )");
 			was_ptr = 0;
 		}
-		append(name, "[%lld]", get_expression_value(sym->array_size));
+		append(name, "[%lld]", get_expression_value_silent(sym->array_size));
 		break;
 
 	case SYM_RESTRICT:
@@ -410,7 +423,7 @@ deeper:
 			break;
 		}
 		if (name->start != name->end)
-			*--name->start = ' ';
+			prepend(name, " ");
 		prepend(name, "restricted %s", show_ident(sym->ident));
 		goto out;
 
@@ -420,7 +433,7 @@ deeper:
 
 	default:
 		if (name->start != name->end)
-			*--name->start = ' ';
+			prepend(name, " ");
 		prepend(name, "unknown type %d", sym->type);
 		goto out;
 	}
@@ -441,10 +454,12 @@ out:
 
 void show_type(struct symbol *sym)
 {
-	char array[200];
+	char array[8192];
 	struct type_name name;
 
-	name.start = name.end = array+100;
+	name.start = name.end = array + 4096;
+	name.buf_start = array;
+	name.buf_end = array + sizeof(array) - 1;
 	do_show_type(sym, &name);
 	*name.end = 0;
 	printf("%s", name.start);
@@ -452,10 +467,12 @@ void show_type(struct symbol *sym)
 
 const char *show_typename(struct symbol *sym)
 {
-	static char array[200];
+	static char array[8192];
 	struct type_name name;
 
-	name.start = name.end = array+100;
+	name.start = name.end = array + 4096;
+	name.buf_start = array;
+	name.buf_end = array + sizeof(array) - 1;
 	do_show_type(sym, &name);
 	*name.end = 0;
 	return name.start;
diff --git a/symbol.h b/symbol.h
index 9d657fbba260..9a8e58d5da28 100644
--- a/symbol.h
+++ b/symbol.h
@@ -115,6 +115,8 @@ struct decl_state {
 	unsigned char autotype;
 	unsigned char forced;
 	unsigned char packed;
+	unsigned char designated_init;
+	unsigned char transparent_union;
 };
 
 struct pseudo;
diff --git a/validation/parser-enum-overflow.c b/validation/parser-enum-overflow.c
new file mode 100644
index 000000000000..3e72a56b9e50
--- /dev/null
+++ b/validation/parser-enum-overflow.c
@@ -0,0 +1,13 @@
+enum {
+	E_ULLMAX = ~0ULL,
+	E_ULL_OVERFLOW,
+};
+
+/*
+ * check-name: parser-enum-overflow
+ * check-command: sparse $file
+ *
+ * check-error-start
+parser-enum-overflow.c:3:9: error: enumerator value for 'E_ULL_OVERFLOW' overflows
+ * check-error-end
+ */
diff --git a/validation/parser-type-fixes.c b/validation/parser-type-fixes.c
new file mode 100644
index 000000000000..dafcdd4a3f75
--- /dev/null
+++ b/validation/parser-type-fixes.c
@@ -0,0 +1,93 @@
+/* 1. designated_init propagation on forward and post-brace declarations */
+struct __attribute__((designated_init)) s_fwd;
+struct s_fwd {
+	int a;
+	int b;
+};
+static struct s_fwd fwd_pos = { 1, 2 };
+static struct s_fwd fwd_des = { .a = 1, .b = 2 };
+
+struct s_post {
+	int a;
+	int b;
+} __attribute__((designated_init));
+static struct s_post post_pos = { 3, 4 };
+static struct s_post post_des = { .a = 3, .b = 4 };
+
+/* 2. transparent_union propagation on forward and post-brace declarations */
+union __attribute__((__transparent_union__)) u_fwd;
+union u_fwd {
+	int *ip;
+	long *lp;
+};
+void take_u_fwd(union u_fwd u);
+
+union u_post {
+	int *ip;
+	long *lp;
+} __attribute__((__transparent_union__));
+void take_u_post(union u_post u);
+
+void call_transparent(int *ip, long *lp)
+{
+	take_u_fwd(ip);
+	take_u_fwd(lp);
+	take_u_post(ip);
+	take_u_post(lp);
+}
+
+/* 3. Function context attribute transferred through pointer return type */
+int *acquire_ptr(void) __attribute__((context(0, 1)));
+
+void good_context(void)
+{
+	(void)acquire_ptr();
+	__context__(-1);
+}
+
+void bad_context(void)
+{
+	(void)acquire_ptr();
+}
+
+/* 4. typeof on restrict-qualified pointer */
+void typeof_restrict(int * restrict p)
+{
+	typeof(p) q = p;
+	(void)q;
+}
+
+/* 5. Sub-int enum auto-increment promotes to int before long/ulong */
+enum {
+	E_SCHAR = (signed char)127,
+	E_SCHAR_NEXT,
+	E_SHRT = (short)32767,
+	E_SHRT_NEXT,
+	E_UCHAR = (unsigned char)255,
+	E_UCHAR_NEXT,
+};
+_Static_assert(E_SCHAR_NEXT == 128, "schar + 1");
+_Static_assert(__builtin_types_compatible_p(typeof(E_SCHAR_NEXT), int), "schar + 1 is int");
+_Static_assert(E_SHRT_NEXT == 32768, "short + 1");
+_Static_assert(__builtin_types_compatible_p(typeof(E_SHRT_NEXT), int), "short + 1 is int");
+_Static_assert(E_UCHAR_NEXT == 256, "uchar + 1");
+_Static_assert(__builtin_types_compatible_p(typeof(E_UCHAR_NEXT), int), "uchar + 1 is int");
+
+/* 6. Out-of-range address_space and aligned warnings */
+int __attribute__((address_space(0x100000000ULL))) *bad_as;
+int __attribute__((aligned(0x100000000ULL))) bad_align;
+
+/*
+ * check-name: parser-type-fixes
+ * check-command: sparse -Wno-decl $file
+ *
+ * check-error-start
+parser-type-fixes.c:77:34: warning: invalid address space name
+parser-type-fixes.c:78:43: warning: I don't like non-power-of-2 alignments
+parser-type-fixes.c:7:33: warning: in initializer for fwd_pos: positional init of field in struct s_fwd, declared with attribute designated_init
+parser-type-fixes.c:7:36: warning: in initializer for fwd_pos: positional init of field in struct s_fwd, declared with attribute designated_init
+parser-type-fixes.c:14:35: warning: in initializer for post_pos: positional init of field in struct s_post, declared with attribute designated_init
+parser-type-fixes.c:14:38: warning: in initializer for post_pos: positional init of field in struct s_post, declared with attribute designated_init
+parser-type-fixes.c:48:6: warning: context imbalance in 'bad_context' - wrong count at exit
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC
  2026-11-09 20:31 Bart Van Assche
                   ` (9 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
                   ` (61 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Set target_openrisc.mach to MACH_OPENRISC instead of MACH_NDS32 in
target-openrisc.c so that OpenRISC targets report the correct machine
architecture identifier.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 target-openrisc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/target-openrisc.c b/target-openrisc.c
index ad25ff2791d3..8f576339858e 100644
--- a/target-openrisc.c
+++ b/target-openrisc.c
@@ -20,7 +20,7 @@ static void predefine_openrisc(const struct target *self)
 }
 
 const struct target target_openrisc = {
-	.mach = MACH_NDS32,
+	.mach = MACH_OPENRISC,
 	.bitness = ARCH_LP32,
 	.big_endian = true,
 

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers
  2026-11-09 20:31 Bart Van Assche
                   ` (10 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
                   ` (60 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix type checking and qualifier handling across evaluate.c, parse.c,
symbol.c, and symbol.h:
- Distinguish forward-declared incomplete structs and unions
  (bit_size = -1) from complete GNU empty structs (bit_size = 0) via
  sym->defined, and preserve bit_size = -1 on arrays of incomplete types.
- Preserve unsigned integer index offsets in evaluate_ptr_add() and
  diagnose pointer addition and subtraction on incomplete types.
- Diagnose incomplete types in evaluate_alignof() and incomplete array or
  multi-dimensional VLA types in evaluate_generic_selection().
- Guard null index types in evaluate_offsetof(), null current_fn in
  check_label_declaration(), and symbolic '[...]' asm constraints.
- Preserve noderef and nocast qualifiers across conditional pointer
  targets and check pointee MOD_NOCAST on pointer casts.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                    | 104 ++++++++++++++++++++++++++++------
 parse.c                       |   2 +
 symbol.c                      |  12 +++-
 symbol.h                      |   2 +-
 validation/eval-type-checks.c |  89 +++++++++++++++++++++++++++++
 5 files changed, 187 insertions(+), 22 deletions(-)
 create mode 100644 validation/eval-type-checks.c

diff --git a/evaluate.c b/evaluate.c
index 40778f4e8a63..0e4e64d33192 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -246,6 +246,8 @@ static struct symbol *base_type(struct symbol *node, unsigned long *modp, struct
 	return node;
 }
 
+#define MOD_IGN (MOD_QUALIFIER | MOD_FUN_ATTR)
+
 static int is_same_type(struct expression *expr, struct symbol *new)
 {
 	struct symbol *old = expr->ctype;
@@ -273,6 +275,13 @@ static int is_same_type(struct expression *expr, struct symbol *new)
 		if (difmod == MOD_CONST)
 			return 0;
 	}
+	if (old->type == SYM_PTR && new->type == SYM_PTR && oldas == newas &&
+	    (oldmod & MOD_NOCAST) == (newmod & MOD_NOCAST)) {
+		if (!type_difference(&old->ctype, &new->ctype,
+				     MOD_IGN | MOD_NODEREF, MOD_IGN | MOD_NODEREF) ||
+		    ((oldmod & MOD_NOCAST) && is_void_type(new->ctype.base_type)))
+			return 0;
+	}
 	if ((oldmod | newmod) & MOD_NOCAST) {
 		const char *tofrom = "to/from";
 		if (!(newmod & MOD_NOCAST))
@@ -588,6 +597,10 @@ static struct symbol *evaluate_ptr_add(struct expression *expr, struct symbol *i
 		expression_error(expr, "arithmetics on pointers to functions");
 		return NULL;
 	}
+	if (!is_void_type(base) && base->bit_size < 0) {
+		expression_error(expr, "arithmetics on pointers to incomplete type");
+		return NULL;
+	}
 
 	/* Get the size of whatever the pointer points to */
 	multiply = is_void_type(base) ? 1 : bits_to_bytes(base->bit_size);
@@ -603,10 +616,10 @@ static struct symbol *evaluate_ptr_add(struct expression *expr, struct symbol *i
 		struct expression *val = alloc_expression(expr->pos, EXPR_VALUE);
 		unsigned long long v = index->value, mask;
 		mask = sign_bit(itype->bit_size);
-		if (v & mask)
+		if (!(itype->ctype.modifiers & MOD_UNSIGNED) && (v & mask))
 			v |= -mask;
 		else
-			v &= mask - 1;
+			v &= mask | (mask - 1);
 		v *= multiply;
 		v &= bits_mask(bits_in_pointer);
 		val->value = v;
@@ -638,8 +651,6 @@ static struct symbol *evaluate_ptr_add(struct expression *expr, struct symbol *i
 
 static void examine_fn_arguments(struct symbol *fn);
 
-#define MOD_IGN (MOD_QUALIFIER | MOD_FUN_ATTR)
-
 const char *type_difference(struct ctype *c1, struct ctype *c2,
 	unsigned long mod1, unsigned long mod2)
 {
@@ -812,6 +823,14 @@ static unsigned long target_qualifiers(struct symbol *type)
 	return mod;
 }
 
+static unsigned long cond_target_qualifiers(struct symbol *type)
+{
+	unsigned long mod = type->ctype.modifiers & (MOD_IGN | MOD_NODEREF | MOD_NOCAST);
+	if (type->ctype.base_type && type->ctype.base_type->type == SYM_ARRAY)
+		mod = 0;
+	return mod;
+}
+
 static struct symbol *evaluate_ptr_sub(struct expression *expr)
 {
 	const char *typediff;
@@ -835,6 +854,10 @@ static struct symbol *evaluate_ptr_sub(struct expression *expr)
 		expression_error(expr, "subtraction of functions? Share your drugs");
 		return NULL;
 	}
+	if (!is_void_type(lbase) && lbase->bit_size < 0) {
+		expression_error(expr, "subtraction of pointers to incomplete type");
+		return NULL;
+	}
 
 	expr->ctype = ssize_t_ctype;
 	if (lbase->bit_size > bits_in_char) {
@@ -1222,7 +1245,7 @@ static struct symbol *evaluate_conditional_expression(struct expression *expr)
 		/* need to be lazier here */
 		lbase = examine_pointer_target(ltype);
 		rbase = examine_pointer_target(rtype);
-		qual = target_qualifiers(ltype) | target_qualifiers(rtype);
+		qual = cond_target_qualifiers(ltype) | cond_target_qualifiers(rtype);
 
 		if (lbase == &void_ctype) {
 			/* XXX: pointers to function should warn here */
@@ -1433,8 +1456,8 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
 		}
 		b1 = examine_pointer_target(t);
 		b2 = examine_pointer_target(s);
-		mod1 = t->ctype.modifiers & MOD_IGN;
-		mod2 = s->ctype.modifiers & MOD_IGN;
+		mod1 = t->ctype.modifiers & (MOD_IGN | MOD_NODEREF);
+		mod2 = s->ctype.modifiers & (MOD_IGN | MOD_NODEREF);
 		if (whitelist_pointers(b1, b2)) {
 			/*
 			 * assignments to/from void * are OK, provided that
@@ -2310,14 +2333,32 @@ static struct symbol *evaluate_ptrsizeof(struct expression *expr)
 	return size_t_ctype;
 }
 
+static bool is_incomplete_alignof_type(struct symbol *type, bool is_type)
+{
+	while (type && type->type == SYM_NODE)
+		type = type->ctype.base_type;
+	while (type && type->type == SYM_ARRAY) {
+		if (is_type && !type->array_size)
+			return true;
+		type = type->ctype.base_type;
+		while (type && type->type == SYM_NODE)
+			type = type->ctype.base_type;
+	}
+	return !type || (type->bit_size < 0 && type->type != SYM_FN);
+}
+
 static struct symbol *evaluate_alignof(struct expression *expr)
 {
 	struct symbol *type;
+	bool is_type = expr->cast_type != NULL;
 
 	type = evaluate_type_information(expr);
 	if (!type)
 		return NULL;
 
+	if (is_incomplete_alignof_type(type, is_type))
+		expression_error(expr, "cannot size alignment of incomplete type");
+
 	expr->type = EXPR_VALUE;
 	expr->value = type->ctype.alignment;
 	expr->taint = 0;
@@ -3268,7 +3309,11 @@ static struct symbol *evaluate_offsetof(struct expression *expr)
 			struct expression *idx = expr->index, *m;
 			struct symbol *i_type = evaluate_expression(idx);
 			unsigned old_idx_flags;
-			int i_class = classify_type(i_type, &i_type);
+			int i_class;
+
+			if (!i_type)
+				return NULL;
+			i_class = classify_type(i_type, &i_type);
 
 			if (!is_int(i_class)) {
 				expression_error(expr, "non-integer index");
@@ -3316,7 +3361,8 @@ static void check_label_declaration(struct position pos, struct symbol *label)
 		sparse_error(pos, "label '%s' was not declared", show_ident(label->ident));
 		/* fallthrough */
 	case NS_NONE:
-		current_fn->bogus_linear = 1;
+		if (current_fn)
+			current_fn->bogus_linear = 1;
 	default:
 		break;
 	}
@@ -3337,10 +3383,14 @@ static struct symbol *evaluate_generic_selection(struct expression *expr)
 	struct symbol source;
 	struct symbol *ctrl;
 
-	if (!evaluate_expression(expr->control))
+	if (!evaluate_expression(expr->control)) {
+		expr->ctype = &bad_ctype;
 		return NULL;
-	if (!(ctrl = degenerate(expr->control)))
+	}
+	if (!(ctrl = degenerate(expr->control))) {
+		expr->ctype = &bad_ctype;
 		return NULL;
+	}
 
 	source = *ctrl;
 	source.ctype.modifiers &= ~(MOD_QUALIFIER|MOD_ATOMIC);
@@ -3352,18 +3402,31 @@ static struct symbol *evaluate_generic_selection(struct expression *expr)
 			continue;
 
 		base = stype->ctype.base_type;
-		if (base->type == SYM_ARRAY && base->array_size) {
-			get_expression_value_silent(base->array_size);
-			if (base->array_size->type == EXPR_VALUE)
+		if (base && base->type == SYM_ARRAY) {
+			bool is_vla = false;
+			struct symbol *arr = base;
+			while (arr && arr->type == SYM_ARRAY) {
+				if (arr->array_size) {
+					get_expression_value_silent(arr->array_size);
+					if (arr->array_size->type != EXPR_VALUE) {
+						is_vla = true;
+						break;
+					}
+				}
+				arr = arr->ctype.base_type;
+				while (arr && arr->type == SYM_NODE)
+					arr = arr->ctype.base_type;
+			}
+			if (is_vla) {
+				sparse_error(stype->pos, "variable length array type in generic selection");
 				continue;
-			sparse_error(stype->pos, "variable length array type in generic selection");
-			continue;
+			}
 		}
 		if (is_func_type(stype)) {
 			sparse_error(stype->pos, "function type in generic selection");
 			continue;
 		}
-		if (stype->bit_size <= 0 || is_void_type(stype)) {
+		if (stype->bit_size < 0 || is_void_type(stype)) {
 			sparse_error(stype->pos, "incomplete type in generic selection");
 			continue;
 		}
@@ -3376,6 +3439,7 @@ static struct symbol *evaluate_generic_selection(struct expression *expr)
 	res = expr->def;
 	if (!res) {
 		sparse_error(expr->pos, "no generic selection for '%s'", show_typename(ctrl));
+		expr->ctype = &bad_ctype;
 		return NULL;
 	}
 
@@ -3682,7 +3746,11 @@ static void parse_asm_constraint(struct asm_operand *op)
 			break;
 		case '[':
 			// FIXME: symbolic matching constraint
-			return;
+			while (*str && *str != ']')
+				str++;
+			if (*str == ']')
+				str++;
+			break;
 
 		default:
 			if (arch_target->asm_constraint)
diff --git a/parse.c b/parse.c
index ea90d088a244..2675a3976099 100644
--- a/parse.c
+++ b/parse.c
@@ -791,6 +791,8 @@ static struct token *struct_union_enum_specifier(enum type type,
 
 	token = parse(token->next, sym);
 	token = expect(token, '}', "at end of specifier");
+	sym->defined = 1;
+	sym->examined = 0;
 	attr.ctype.base_type = sym;
 	token = handle_attributes(token, &attr);
 	apply_ctype(token->pos, &sym->ctype, &attr.ctype);
diff --git a/symbol.c b/symbol.c
index abf3a198acc8..06cd8ca2ede7 100644
--- a/symbol.c
+++ b/symbol.c
@@ -215,6 +215,11 @@ static struct symbol * examine_struct_union_type(struct symbol *sym, int advance
 	void (*fn)(struct symbol *, struct struct_union_info *);
 	struct symbol *member;
 
+	if (!sym->defined) {
+		sym->bit_size = -1;
+		return sym;
+	}
+
 	fn = advance ? lay_out_struct : lay_out_union;
 	FOR_EACH_PTR(sym->symbol_list, member) {
 		if (member->ctype.base_type == &autotype_ctype) {
@@ -298,9 +303,10 @@ static struct symbol * examine_array_type(struct symbol *sym)
 	if (!base_type)
 		return sym;
 
-	if (array_size) {	
-		bit_size = array_element_offset(base_type->bit_size,
-						get_expression_value_silent(array_size));
+	if (array_size) {
+		unsigned long count = get_expression_value_silent(array_size);
+		if (base_type->bit_size >= 0)
+			bit_size = array_element_offset(base_type->bit_size, count);
 		if (array_size->type != EXPR_VALUE) {
 			if (Wvla)
 				warning(array_size->pos, "Variable length array is used.");
diff --git a/symbol.h b/symbol.h
index 9a8e58d5da28..a14686fe863b 100644
--- a/symbol.h
+++ b/symbol.h
@@ -149,7 +149,7 @@ struct symbol_op {
 struct symbol {
 	enum type type:8;
 	enum namespace namespace:9;
-	unsigned char used:1, attr:2, enum_member:1, bound:1;
+	unsigned char used:1, attr:2, enum_member:1, bound:1, defined:1;
 	struct position pos;		/* Where this symbol was declared */
 	struct position endpos;		/* Where this symbol ends*/
 	struct ident *ident;		/* What identifier this symbol is associated with */
diff --git a/validation/eval-type-checks.c b/validation/eval-type-checks.c
new file mode 100644
index 000000000000..13b3ff22210d
--- /dev/null
+++ b/validation/eval-type-checks.c
@@ -0,0 +1,89 @@
+#define __noderef __attribute__((noderef))
+#define __nocast __attribute__((nocast))
+
+struct empty {};
+struct inc;
+struct __attribute__((aligned(16))) inc_aligned;
+
+extern struct empty es;
+extern int ext_arr[];
+
+_Static_assert(_Generic(es, struct empty: 1, default: 0) == 1, "empty struct generic");
+_Static_assert(_Generic(0, int[4]: 1, default: 2) == 2, "fixed array assoc");
+_Static_assert(_Alignof(struct empty) == 1, "empty struct alignof");
+
+static unsigned long valid_alignof(int n)
+{
+	return _Alignof(int[n]) + _Alignof(void(void)) + __alignof__(ext_arr);
+}
+
+static void *valid_void_ptr_add(void *p)
+{
+	return p + 1;
+}
+
+static void test_incomplete_errors(struct inc *pi, int (*pa)[], int n)
+{
+	(void)(pi + 1);
+	(void)(pa + 1);
+	(void)_Alignof(void);
+	(void)_Alignof(int[]);
+	(void)_Alignof(struct inc);
+	(void)_Alignof(struct inc_aligned);
+	(void)_Alignof(struct inc[2]);
+	(void)_Generic(0, int[2][n]: 1, default: 0);
+	(void)_Generic(0, struct inc[2]: 1, default: 0);
+}
+
+static long test_qualifiers(int c, void *vp, void __nocast *vp_nc, int *p,
+			    int __noderef *p_nd, int __nocast *p_nc)
+{
+	void __nocast *ok_both_nc = c ? vp_nc : p_nc;
+	void __nocast *ok_one_nc = c ? vp : p_nc;
+	void __noderef *ok_nd = c ? vp : p_nd;
+	int *bad_nc = c ? vp_nc : p_nc;
+	int *bad_nd = c ? vp : p_nd;
+	long r = 0;
+
+	(void)ok_both_nc;
+	(void)ok_one_nc;
+	(void)ok_nd;
+	(void)bad_nc;
+	(void)bad_nd;
+
+	r += (p_nd == p);
+	r += (p_nd - p);
+	r += (p_nc == p);
+	r += (p_nc - p);
+	return r;
+}
+
+/*
+ * check-name: eval-type-checks
+ * check-command: sparse -Wno-vla $file
+ *
+ * check-error-start
+eval-type-checks.c:27:19: error: arithmetics on pointers to incomplete type
+eval-type-checks.c:28:19: error: arithmetics on pointers to incomplete type
+eval-type-checks.c:29:15: error: cannot size alignment of incomplete type
+eval-type-checks.c:30:15: error: cannot size alignment of incomplete type
+eval-type-checks.c:31:15: error: cannot size alignment of incomplete type
+eval-type-checks.c:32:15: error: cannot size alignment of incomplete type
+eval-type-checks.c:33:15: error: cannot size alignment of incomplete type
+eval-type-checks.c:34:27: error: variable length array type in generic selection
+eval-type-checks.c:35:27: error: incomplete type in generic selection
+eval-type-checks.c:42:40: warning: implicit cast to nocast type
+eval-type-checks.c:44:25: warning: implicit cast from nocast type
+eval-type-checks.c:45:25: warning: incorrect type in initializer (different modifiers)
+eval-type-checks.c:45:25:    expected int *bad_nd
+eval-type-checks.c:45:25:    got void [noderef] *
+eval-type-checks.c:54:20: error: incompatible types in comparison expression (different modifiers):
+eval-type-checks.c:54:20:    int [noderef] *
+eval-type-checks.c:54:20:    int *
+eval-type-checks.c:55:20: error: subtraction of different types can't work (different modifiers)
+eval-type-checks.c:56:20: error: incompatible types in comparison expression (different modifiers):
+eval-type-checks.c:56:20:    int [nocast] *
+eval-type-checks.c:56:20:    int *
+eval-type-checks.c:57:20: error: subtraction of different types can't work (different modifiers)
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 13/73] expand: Fix static initializer folding and floating-point casts
  2026-11-09 20:31 Bart Van Assche
                   ` (11 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
@ 2026-11-09 20:31 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
                   ` (59 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:31 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix constant folding and numeric conversions in expand.c:
- Avoid folding writable or volatile static objects and negative offsets
  in constant_symbol_value(), and guard volatile or bitfield dereferences
  in expand_dereference().
- Preserve unsigned integer-to-float and float-to-integer conversions in
  cast_value() while guarding against out-of-range and NaN host casts.
- Add regression test validation/expand-const-and-float.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 expand.c                            | 76 ++++++++++++++++++++++++++---
 validation/expand-const-and-float.c | 73 +++++++++++++++++++++++++++
 2 files changed, 143 insertions(+), 6 deletions(-)
 create mode 100644 validation/expand-const-and-float.c

diff --git a/expand.c b/expand.c
index bbb30ae605dc..208ce65a2442 100644
--- a/expand.c
+++ b/expand.c
@@ -97,6 +97,11 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
 	if (is_float_type(newtype) || is_float_type(oldtype))
 		goto Float;
 
+	if (!is_bool_type(newtype) && new_size > 64 && old_size >= 64 &&
+	    new_size > old_size && (oldtype->ctype.modifiers & MOD_UNSIGNED) &&
+	    (old->value & (1ULL << 63)))
+		return;
+
 	// For pointers and integers, we can just move the value around
 	expr->type = EXPR_VALUE;
 	expr->taint = old->taint;
@@ -144,16 +149,60 @@ Int:
 
 Float:
 	if (!is_float_type(newtype)) {
-		value = (long long)old->fvalue;
+		long double fval = old->fvalue;
+
+		if (is_bool_type(newtype)) {
+			expr->type = EXPR_VALUE;
+			expr->taint = 0;
+			expr->ctype = newtype;
+			expr->value = (fval != 0);
+			return;
+		}
+		if (new_size <= 0 || new_size > 64 || !__builtin_isfinite(fval))
+			return;
+		if (newtype->ctype.modifiers & MOD_UNSIGNED) {
+			unsigned long long uval;
+			if (!(fval > -1.0L && fval < 0x1p64L))
+				return;
+			uval = (fval <= 0.0L) ? 0ULL : (unsigned long long)fval;
+			if (new_size < 64 && uval >= (1ULL << new_size))
+				return;
+			value = (long long)uval;
+		} else {
+			unsigned long long lim = 1ULL << (new_size - 1);
+			unsigned long long umag;
+			if (fval >= 0.0L) {
+				if (!(fval < 0x1p63L))
+					return;
+				umag = (unsigned long long)fval;
+				if (umag >= lim)
+					return;
+				value = (long long)umag;
+			} else {
+				long double mag = -fval;
+				if (!(mag < 0x1p64L))
+					return;
+				umag = (unsigned long long)mag;
+				if (umag > lim)
+					return;
+				value = (long long)(0ULL - umag);
+			}
+		}
 		expr->type = EXPR_VALUE;
 		expr->taint = 0;
 		expr->ctype = newtype;
 		goto Int;
 	}
 
-	if (!is_float_type(oldtype))
-		expr->fvalue = (long double)get_longlong(old);
-	else
+	if (!is_float_type(oldtype)) {
+		if ((oldtype->ctype.modifiers & MOD_UNSIGNED) &&
+		    old_size > 64 && (old->value & (1ULL << 63)))
+			return;
+		if (oldtype->ctype.modifiers & MOD_UNSIGNED)
+			expr->fvalue = (long double)(unsigned long long)get_longlong(old);
+		else
+			expr->fvalue = (long double)get_longlong(old);
+	} else
 		expr->fvalue = old->fvalue;
 
 	if (newtype->rank <= 0) {
@@ -200,6 +249,8 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
 	}
 	if (left->type != EXPR_VALUE)
 		return 0;
+	if (!ctype || ctype->bit_size <= 0 || ctype->bit_size > 64)
+		return 0;
 	l = left->value; r = right->value;
 	is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
 	mask = sign_bit(ctype->bit_size);
@@ -305,6 +356,8 @@ static int simplify_cmp_binop(struct expression *expr, struct symbol *ctype)
 
 	if (left->type != EXPR_VALUE || right->type != EXPR_VALUE)
 		return 0;
+	if (!ctype || ctype->bit_size <= 0 || ctype->bit_size > 64)
+		return 0;
 	l = left->value; r = right->value;
 	mask = sign_bit(ctype->bit_size);
 	sl = l; sr = r;
@@ -665,8 +718,16 @@ static struct expression *constant_symbol_value(struct symbol *sym, int offset)
 {
 	struct expression *value;
 
+	if (offset < 0)
+		return NULL;
+	if (sym->ctype.modifiers & MOD_VOLATILE)
+		return NULL;
 	if (sym->ctype.modifiers & MOD_ACCESS)
 		return NULL;
+	if ((sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_STATIC)) &&
+	    !(sym->ctype.modifiers & MOD_CONST) &&
+	    !sym->string && sym->ident)
+		return NULL;
 	value = sym->initializer;
 	if (!value)
 		return NULL;
@@ -707,6 +768,8 @@ static int expand_dereference(struct expression *expr)
 	 */
 	if (expr->ctype->ctype.modifiers & MOD_NODEREF)
 		warning(unop->pos, "dereference of noderef expression");
+	if (expr->ctype->ctype.modifiers & MOD_VOLATILE)
+		return SIDE_EFFECTS;
 
 	/*
 	 * Is it "symbol" or "symbol + offset"?
@@ -732,7 +795,7 @@ static int expand_dereference(struct expression *expr)
 			if (value->type == EXPR_VALUE) {
 				if (!is_integral_type(ctype))
 					return UNSAFE;
-				if (is_bitfield_type(value->ctype))
+				if (is_bitfield_type(ctype) || is_bitfield_type(value->ctype))
 					return UNSAFE;
 				expr->type = EXPR_VALUE;
 				expr->value = value->value;
@@ -866,7 +929,8 @@ static int expand_cast(struct expression *expr)
 	/* Simplify normal integer casts.. */
 	if (target->type == EXPR_VALUE || target->type == EXPR_FVALUE) {
 		cast_value(expr, expr->ctype, target);
-		return 0;
+		if (expr->type == EXPR_VALUE || expr->type == EXPR_FVALUE)
+			return 0;
 	}
 	return cost + 1;
 }
diff --git a/validation/expand-const-and-float.c b/validation/expand-const-and-float.c
new file mode 100644
index 000000000000..4f0403134cdc
--- /dev/null
+++ b/validation/expand-const-and-float.c
@@ -0,0 +1,73 @@
+static const volatile int cv = 42;
+
+int read_cv(void)
+{
+	return cv;
+}
+
+void vol_deref(const volatile int *p)
+{
+	*p;
+}
+
+static const int arr[2] = { 10, 20 };
+
+int read_neg_off(void)
+{
+	return *(&arr[0] - 1);
+}
+
+unsigned int float_neg_half(void)
+{
+	return (unsigned int)-0.5;
+}
+
+_Bool float_half_to_bool(void)
+{
+	return (_Bool)0.5;
+}
+
+int float_oob_pos(void)
+{
+	return (int)1e30;
+}
+
+unsigned int float_neg_one(void)
+{
+	return (unsigned int)-1.0;
+}
+
+unsigned long long float_oob_ull(void)
+{
+	return (unsigned long long)1e40;
+}
+
+int float_nan(void)
+{
+	return (int)__builtin_nan("");
+}
+
+int float_inf(void)
+{
+	return (int)__builtin_inf();
+}
+
+int nonconst_float_cast(int x)
+{
+	return (int)(x + 0.0);
+}
+
+/*
+ * check-name: expand-const-and-float
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-contains: load\\.32.*\\[cv\\]
+ * check-output-contains: load\\.32.*%arg1
+ * check-output-contains: ret\\.32.*\\$0
+ * check-output-contains: ret\\.1.*\\$1
+ *
+ * check-error-start
+expand-const-and-float.c:17:26: warning: invalid access below 'arr' (-4 8)
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining
  2026-11-09 20:31 Bart Van Assche
                   ` (12 preceding siblings ...)
  2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
                   ` (58 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Duplicate union EXPR_CAST target symbols and clone variable-length array
SYM_ARRAY chains in inline.c so that union casts and VLA dimension
expressions inside inlined functions do not share or mutate symbols
across call sites.

Add regression tests in validation/eval-expand-inline-fixes.c and
validation/eval-ptr-incomplete-and-inline.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 inline.c                                    | 101 ++++++++++++++++++--
 validation/eval-expand-inline-fixes.c       |  49 ++++++++++
 validation/eval-ptr-incomplete-and-inline.c |  70 ++++++++++++++
 3 files changed, 213 insertions(+), 7 deletions(-)
 create mode 100644 validation/eval-expand-inline-fixes.c
 create mode 100644 validation/eval-ptr-incomplete-and-inline.c

diff --git a/inline.c b/inline.c
index a7ab73d37492..1948bea52ffb 100644
--- a/inline.c
+++ b/inline.c
@@ -35,6 +35,7 @@
 #include "evaluate.h"
 
 static void copy_statement(struct statement *src, struct statement *dst);
+static struct expression *copy_expression(struct expression *expr);
 
 static struct expression * dup_expression(struct expression *expr)
 {
@@ -63,6 +64,64 @@ static struct symbol *copy_symbol(struct position pos, struct symbol *sym)
 	return sym->replace;
 }
 
+static struct symbol *copy_type(struct symbol *type)
+{
+	if (!type)
+		return NULL;
+	switch (type->type) {
+	case SYM_NODE: {
+		struct symbol *base = copy_type(type->ctype.base_type);
+		struct symbol *n;
+		if (base == type->ctype.base_type)
+			return type;
+		n = alloc_symbol(type->pos, SYM_NODE);
+		*n = *type;
+		n->ctype.base_type = base;
+		if (base && base->bit_size < 0)
+			n->examined = 0;
+		return n;
+	}
+	case SYM_ARRAY: {
+		struct symbol *elem = copy_type(type->ctype.base_type);
+		struct expression *size = copy_expression(type->array_size);
+		struct symbol *arr;
+		if (elem == type->ctype.base_type && size == type->array_size && !type->array_size)
+			return type;
+		arr = alloc_symbol(type->pos, SYM_ARRAY);
+		*arr = *type;
+		arr->ctype.base_type = elem;
+		arr->array_size = size;
+		if (type->array_size || elem != type->ctype.base_type)
+			arr->examined = 0;
+		return arr;
+	}
+	case SYM_PTR: {
+		struct symbol *base = copy_type(type->ctype.base_type);
+		struct symbol *ptr;
+		if (base == type->ctype.base_type)
+			return type;
+		ptr = alloc_symbol(type->pos, SYM_PTR);
+		*ptr = *type;
+		ptr->ctype.base_type = base;
+		return ptr;
+	}
+	case SYM_TYPEOF:
+	case SYM_TYPEOF_UNQUAL: {
+		struct expression *init = copy_expression(type->initializer);
+		struct symbol *t;
+		if (init == type->initializer)
+			return type;
+		t = alloc_symbol(type->pos, type->type);
+		*t = *type;
+		t->initializer = init;
+		t->examined = 0;
+		return t;
+	}
+	default:
+		return type;
+	}
+}
+
 static struct symbol_list *copy_symbol_list(struct symbol_list *src)
 {
 	struct symbol_list *dst = NULL;
@@ -155,11 +214,19 @@ static struct expression * copy_expression(struct expression *expr)
 
 	/* Cast/sizeof/__alignof__ */
 	case EXPR_CAST:
-		if (!expr->cast_expression)
-			return NULL;
-		if (expr->cast_expression->type == EXPR_INITIALIZER) {
+		if (!expr->cast_expression) {
+			struct symbol *cast_type = copy_type(expr->cast_type);
+			if (cast_type != expr->cast_type) {
+				expr = dup_expression(expr);
+				expr->cast_type = cast_type;
+			}
+			return expr;
+		}
+		if (expr->cast_expression->type == EXPR_INITIALIZER ||
+		    (expr->cast_type && expr->cast_type->ctype.base_type &&
+		     expr->cast_type->ctype.base_type->type == SYM_UNION)) {
 			struct expression *cast = expr->cast_expression;
-			struct symbol *sym = expr->cast_type;
+			struct symbol *sym = copy_type(expr->cast_type);
 			expr = dup_expression(expr);
 			expr->cast_expression = copy_expression(cast);
 			expr->cast_type = alloc_symbol(sym->pos, sym->type);
@@ -172,10 +239,12 @@ static struct expression * copy_expression(struct expression *expr)
 	case EXPR_PTRSIZEOF:
 	case EXPR_ALIGNOF: {
 		struct expression *cast = copy_expression(expr->cast_expression);
-		if (cast == expr->cast_expression)
+		struct symbol *cast_type = copy_type(expr->cast_type);
+		if (cast == expr->cast_expression && cast_type == expr->cast_type)
 			break;
 		expr = dup_expression(expr);
 		expr->cast_expression = cast;
+		expr->cast_type = cast_type;
 		break;
 	}
 
@@ -343,8 +412,10 @@ static struct statement *copy_one_statement(struct statement *stmt)
 		newstmt->declaration = NULL;
 		FOR_EACH_PTR(stmt->declaration, sym) {
 			struct symbol *newsym = copy_symbol(stmt->pos, sym);
-			if (newsym != sym)
+			if (newsym != sym) {
+				newsym->ctype.base_type = copy_type(newsym->ctype.base_type);
 				newsym->initializer = copy_expression(sym->initializer);
+			}
 			add_symbol(&newstmt->declaration, newsym);
 		} END_FOR_EACH_PTR(sym);
 		stmt = newstmt;
@@ -424,10 +495,20 @@ static struct statement *copy_one_statement(struct statement *stmt)
 		break;		
 	}
 	case STMT_ITERATOR: {
+		struct symbol_list *old_syms = stmt->iterator_syms;
+		struct symbol *sym;
 		stmt = dup_statement(stmt);
 		stmt->iterator_break = copy_symbol(stmt->pos, stmt->iterator_break);
 		stmt->iterator_continue = copy_symbol(stmt->pos, stmt->iterator_continue);
-		stmt->iterator_syms = copy_symbol_list(stmt->iterator_syms);
+		stmt->iterator_syms = NULL;
+		FOR_EACH_PTR(old_syms, sym) {
+			struct symbol *newsym = copy_symbol(stmt->pos, sym);
+			if (newsym != sym) {
+				newsym->ctype.base_type = copy_type(newsym->ctype.base_type);
+				newsym->initializer = copy_expression(sym->initializer);
+			}
+			add_symbol(&stmt->iterator_syms, newsym);
+		} END_FOR_EACH_PTR(sym);
 
 		stmt->iterator_pre_statement = copy_one_statement(stmt->iterator_pre_statement);
 		stmt->iterator_pre_condition = copy_expression(stmt->iterator_pre_condition);
@@ -542,6 +623,8 @@ int inline_function(struct expression *expr, struct symbol *sym)
 
 		if (name) {
 			*a = *name;
+			a->bb_target = NULL;
+			a->pseudo = NULL;
 			set_replace(name, a);
 			add_symbol(&fn_symbol_list, a);
 			a->initializer = arg;
@@ -557,6 +640,10 @@ int inline_function(struct expression *expr, struct symbol *sym)
 	} END_FOR_EACH_PTR(arg);
 	FINISH_PTR_LIST(name);
 
+	FOR_EACH_PTR(arg_decl, name) {
+		name->ctype.base_type = copy_type(name->ctype.base_type);
+	} END_FOR_EACH_PTR(name);
+
 	copy_statement(fn->inline_stmt, stmt);
 
 	if (arg_decl) {
diff --git a/validation/eval-expand-inline-fixes.c b/validation/eval-expand-inline-fixes.c
new file mode 100644
index 000000000000..95600c32ec93
--- /dev/null
+++ b/validation/eval-expand-inline-fixes.c
@@ -0,0 +1,49 @@
+/* 1. _Generic array/function decay and qualifier stripping */
+extern int arr[4];
+extern int fn(void);
+const int ci = 0;
+
+_Static_assert(_Generic(arr, int *: 1, default: 0) == 1, "array decay");
+_Static_assert(_Generic(fn, int (*)(void): 1, default: 0) == 1, "func decay");
+_Static_assert(_Generic(ci, int: 1, const int: 0) == 1, "qual strip");
+
+/* 2. Inlining function with cleanup attribute and _Static_assert */
+void cl(int *p);
+
+static inline int inl_cleanup(int v)
+{
+	_Static_assert(sizeof(v) == sizeof(int), "int size");
+	int x __attribute__((cleanup(cl))) = v;
+	return x + 1;
+}
+
+int call_inl(int v)
+{
+	return inl_cleanup(v);
+}
+
+/* 3. Conditional operator preserving noderef qualifier */
+#define __noderef __attribute__((noderef))
+void cond_noderef(void *vp, int __noderef *p)
+{
+	int *q = (1 ? vp : p);
+	(void)q;
+}
+
+/* 4. Signed division overflow in constant expression */
+int div_ovf(void)
+{
+	return (-9223372036854775807LL - 1LL) / -1LL;
+}
+
+/*
+ * check-name: eval-expand-inline-fixes
+ * check-command: sparse -Wno-decl $file
+ *
+ * check-error-start
+eval-expand-inline-fixes.c:29:21: warning: incorrect type in initializer (different modifiers)
+eval-expand-inline-fixes.c:29:21:    expected int *q
+eval-expand-inline-fixes.c:29:21:    got void [noderef] *
+eval-expand-inline-fixes.c:36:47: warning: constant integer operation overflow
+ * check-error-end
+ */
diff --git a/validation/eval-ptr-incomplete-and-inline.c b/validation/eval-ptr-incomplete-and-inline.c
new file mode 100644
index 000000000000..fb1c7b71496b
--- /dev/null
+++ b/validation/eval-ptr-incomplete-and-inline.c
@@ -0,0 +1,70 @@
+/* 1. Subtraction of pointers to incomplete type */
+extern int arr[];
+static int test_sub(int (*p)[], int (*q)[])
+{
+	return p - q;
+}
+
+/* 2. _Alignof on incomplete type (void) */
+static unsigned long test_align(void)
+{
+	return _Alignof(void);
+}
+
+/* 3. Float <-> unsigned integer constant folding in initializers */
+static unsigned int u = (unsigned int)3.8f;
+static float f = 4000000000U;
+
+/* 4. Unsigned pointer arithmetic */
+static char *test_ptr_add(char *p)
+{
+	return p + 0x80000000U;
+}
+
+/* 5. Named asm constraint */
+static int test_asm_named(int val)
+{
+	int out;
+	asm("mov %0, %[in]" : "=r"(out) : [in] "r"(val));
+	return out;
+}
+
+/* 6. Inlining with local array and union cast */
+union U { int x; float y; };
+static inline int inl_func(int v)
+{
+	int a[4] = { v, 1, 2, 3 };
+	union U u = (union U)v;
+	return a[0] + u.x;
+}
+static int call_inl(int x, int y)
+{
+	return inl_func(x) + inl_func(y);
+}
+
+/* 7. Inlining multi-dimensional VLAs, VLA pointers, and typeof(VLA) */
+static inline unsigned long inl_vla(int n, int m)
+{
+	int mat[n][m];
+	int (*p)[m] = mat;
+	typeof(mat) mat2;
+
+	for (int row[n], k = 0; k < 1; k++)
+		row[0] = n;
+	return sizeof(mat) + sizeof(*p) + sizeof(mat2) + sizeof(int [n][m]);
+}
+
+static unsigned long call_inl_vla(int a, int b, int c, int d)
+{
+	return inl_vla(a, b) + inl_vla(c, d);
+}
+
+/*
+ * check-name: eval-ptr-incomplete-and-inline
+ * check-command: sparse -Wno-vla $file
+ *
+ * check-error-start
+eval-ptr-incomplete-and-inline.c:5:18: error: subtraction of pointers to incomplete type
+eval-ptr-incomplete-and-inline.c:11:16: error: cannot size alignment of incomplete type
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE
  2026-11-09 20:31 Bart Van Assche
                   ` (13 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
                   ` (57 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix IR linearization and dead-store elimination issues in flow.c and
linearize.c:
- Continue scanning past OP_CALL for non-escaping locals while stopping
  at OP_ASM in kill_dead_stores_bb().
- Enlarge show_pseudo() formatting buffers and replace sprintf() with
  bounded snprintf().
- Guard VOID phi sources in linearize_logical(), register read-write
  '+r' asm outputs as inputs, guard non-string asm clobbers, and extend
  switch case values to the controlling expression width.
- Add regression test validation/linear/control-flow-fixes.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 flow.c                                 |  3 ++
 linearize.c                            | 46 ++++++++++++++++----------
 validation/linear/control-flow-fixes.c | 44 ++++++++++++++++++++++++
 3 files changed, 75 insertions(+), 18 deletions(-)
 create mode 100644 validation/linear/control-flow-fixes.c

diff --git a/flow.c b/flow.c
index 48755501e464..f89cd755a1ae 100644
--- a/flow.c
+++ b/flow.c
@@ -518,6 +518,9 @@ static void kill_dead_stores_bb(pseudo_t pseudo, unsigned long generation, struc
 		case OP_CALL:
 			if (!local)
 				return;
+			continue;
+		case OP_ASM:
+			return;
 		default:
 			continue;
 		}
diff --git a/linearize.c b/linearize.c
index 75850df0a90f..c6ec3703b149 100644
--- a/linearize.c
+++ b/linearize.c
@@ -98,7 +98,7 @@ const char *show_label(struct basic_block *bb)
 const char *show_pseudo(pseudo_t pseudo)
 {
 	static int n;
-	static char buffer[4][64];
+	static char buffer[4][256];
 	char *buf;
 	int i;
 
@@ -113,23 +113,23 @@ const char *show_pseudo(pseudo_t pseudo)
 		struct expression *expr;
 
 		if (!sym) {
-			snprintf(buf, 64, "<bad symbol>");
+			snprintf(buf, sizeof(buffer[0]), "<bad symbol>");
 			break;
 		}
 		if (sym->bb_target) {
-			snprintf(buf, 64, "%s", show_label(sym->bb_target));
+			snprintf(buf, sizeof(buffer[0]), "%s", show_label(sym->bb_target));
 			break;
 		}
 		if (sym->ident) {
-			snprintf(buf, 64, "%s", show_ident(sym->ident));
+			snprintf(buf, sizeof(buffer[0]), "%s", show_ident(sym->ident));
 			break;
 		}
 		expr = sym->initializer;
-		snprintf(buf, 64, "<anon symbol:%p>", verbose ? sym : NULL);
+		snprintf(buf, sizeof(buffer[0]), "<anon symbol:%p>", verbose ? sym : NULL);
 		if (expr) {
 			switch (expr->type) {
 			case EXPR_VALUE:
-				snprintf(buf, 64, "<symbol value: %lld>", expr->value);
+				snprintf(buf, sizeof(buffer[0]), "<symbol value: %lld>", expr->value);
 				break;
 			case EXPR_STRING:
 				return show_string(expr->string);
@@ -140,30 +140,30 @@ const char *show_pseudo(pseudo_t pseudo)
 		break;
 	}
 	case PSEUDO_REG:
-		i = snprintf(buf, 64, "%%r%d", pseudo->nr);
-		if (pseudo->ident)
-			sprintf(buf+i, "(%s)", show_ident(pseudo->ident));
+		i = snprintf(buf, sizeof(buffer[0]), "%%r%d", pseudo->nr);
+		if (pseudo->ident && i < (int)sizeof(buffer[0]))
+			snprintf(buf+i, sizeof(buffer[0]) - i, "(%s)", show_ident(pseudo->ident));
 		break;
 	case PSEUDO_VAL: {
 		long long value = pseudo->value;
 		if (value > 1000 || value < -1000)
-			snprintf(buf, 64, "$%#llx", value);
+			snprintf(buf, sizeof(buffer[0]), "$%#llx", value);
 		else
-			snprintf(buf, 64, "$%lld", value);
+			snprintf(buf, sizeof(buffer[0]), "$%lld", value);
 		break;
 	}
 	case PSEUDO_ARG:
-		snprintf(buf, 64, "%%arg%d", pseudo->nr);
+		snprintf(buf, sizeof(buffer[0]), "%%arg%d", pseudo->nr);
 		break;
 	case PSEUDO_PHI:
-		i = snprintf(buf, 64, "%%phi%d", pseudo->nr);
-		if (pseudo->ident)
-			sprintf(buf+i, "(%s)", show_ident(pseudo->ident));
+		i = snprintf(buf, sizeof(buffer[0]), "%%phi%d", pseudo->nr);
+		if (pseudo->ident && i < (int)sizeof(buffer[0]))
+			snprintf(buf+i, sizeof(buffer[0]) - i, "(%s)", show_ident(pseudo->ident));
 		break;
 	case PSEUDO_UNDEF:
 		return "UNDEF";
 	default:
-		snprintf(buf, 64, "<bad pseudo type %d>", pseudo->type);
+		snprintf(buf, sizeof(buffer[0]), "<bad pseudo type %d>", pseudo->type);
 	}
 	return buf;
 }
@@ -1746,7 +1746,8 @@ static pseudo_t linearize_logical(struct entrypoint *ep, struct expression *expr
 	src2 = linearize_expression_to_bool(ep, expr->right);
 	src2 = cast_pseudo(ep, src2, &bool_ctype, ctype);
 	phi2 = alloc_phi(ep->active, src2, ctype);
-	link_phi(node, phi2);
+	if (phi2 != VOID)
+		link_phi(node, phi2);
 
 	// join
 	set_activeblock(ep, merge);
@@ -2154,6 +2155,8 @@ static pseudo_t linearize_asm_statement(struct entrypoint *ep, struct statement
 
 	/* ... and the addresses for memory outputs */
 	FOR_EACH_PTR(stmt->asm_outputs, op) {
+		if (op->is_modify && !op->is_memory)
+			add_asm_input(ep, insn, op);
 		add_asm_output_address(ep, insn, op);
 	} END_FOR_EACH_PTR(op);
 
@@ -2166,7 +2169,8 @@ static pseudo_t linearize_asm_statement(struct entrypoint *ep, struct statement
 
 	/* and finally, look if it clobbers memory */
 	FOR_EACH_PTR(stmt->asm_clobbers, clob) {
-		if (!strcmp(clob->string->data, "memory"))
+		if (clob->type == EXPR_STRING && clob->string &&
+		    !strcmp(clob->string->data, "memory"))
 			insn->clobber_memory = 1;
 	} END_FOR_EACH_PTR(clob);
 
@@ -2262,11 +2266,17 @@ static pseudo_t linearize_switch(struct entrypoint *ep, struct statement *stmt)
 			continue;
 		} else {
 			struct expression *case_to = case_stmt->case_to;
+			struct symbol *ctype = expr->ctype;
 			long long begin, end;
 
 			begin = end = case_stmt->case_expression->value;
 			if (case_to && case_to->type == EXPR_VALUE)
 				end = case_to->value;
+			if (ctype->bit_size > 0) {
+				bool is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
+				begin = bits_extend(begin, ctype->bit_size, is_signed);
+				end = bits_extend(end, ctype->bit_size, is_signed);
+			}
 			if (begin > end)
 				jmp = alloc_multijmp(bb_case, end, begin);
 			else
diff --git a/validation/linear/control-flow-fixes.c b/validation/linear/control-flow-fixes.c
new file mode 100644
index 000000000000..507598d42458
--- /dev/null
+++ b/validation/linear/control-flow-fixes.c
@@ -0,0 +1,44 @@
+/* 1. Statement expression with return inside if condition */
+int stmt_expr_cond(void)
+{
+	if (({ if (0) return 0; 1; }))
+		return 1;
+	return 0;
+}
+
+/* 2. Logical expression with unreachable RHS after return */
+int logical_after_return(int x)
+{
+	return x && ({ return 2; 1; });
+}
+
+/* 3. asm "+r" read-write register and "m" memory operand preserve stores */
+int asm_rw_and_mem(void)
+{
+	int x = 42;
+	int y = 99;
+	asm("" : "+r"(x) : "m"(y));
+	return x;
+}
+
+/* 4. Switch on signed char with negative case label */
+int switch_signed_char(signed char c)
+{
+	switch (c) {
+	case -1:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+/*
+ * check-name: control-flow-fixes
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-excludes: VOID
+ * check-output-contains: store\\.32.*\\$99 -> 0\\[y\\]
+ * check-output-contains: in:.*"+r" (\\$42)
+ * check-output-contains: switch\\.8.*-1 ->
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA
  2026-11-09 20:31 Bart Van Assche
                   ` (14 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
                   ` (56 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix dominated-store elimination and SSA promotion in memops.c and ssa.c:
- Walk parent basic block instructions in reverse order in
  kill_dominated_stores() so intervening loads or calls after a
  dominating store are visited before the store itself.
- Skip SSA promotion in ssa_convert_one_var() when loads or stores
  access the same variable with mixed sizes or non-zero offsets.
- Add regression test validation/optim/volatile-dse-and-cfg.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 memops.c                                |  4 +-
 ssa.c                                   |  6 +++
 validation/optim/volatile-dse-and-cfg.c | 53 +++++++++++++++++++++++++
 3 files changed, 61 insertions(+), 2 deletions(-)
 create mode 100644 validation/optim/volatile-dse-and-cfg.c

diff --git a/memops.c b/memops.c
index bfcdc1987a9d..4d2eac266b12 100644
--- a/memops.c
+++ b/memops.c
@@ -252,14 +252,14 @@ static void kill_dominated_stores(struct basic_block *bb)
 
 				if (bb_list_size(par->children) != 1)
 					goto next_parent;
-				FOR_EACH_PTR(par->insns, dom) {
+				FOR_EACH_PTR_REVERSE(par->insns, dom) {
 					if (!dom->bb)
 						continue;
 					if (dom == insn)
 						goto next_parent;
 					if (!try_to_kill_store(insn, dom, local))
 						goto next_parent;
-				} END_FOR_EACH_PTR(dom);
+				} END_FOR_EACH_PTR_REVERSE(dom);
 next_parent:
 				;
 			} END_FOR_EACH_PTR(par);
diff --git a/ssa.c b/ssa.c
index 4d3ead0c4ea2..0c9352c4958b 100644
--- a/ssa.c
+++ b/ssa.c
@@ -139,6 +139,7 @@ static void ssa_convert_one_var(struct entrypoint *ep, struct symbol *var)
 	bool local = true;
 	int nbr_stores = 0;
 	int nbr_uses   = 0;
+	bool same_size = true;
 	pseudo_t addr;
 
 	/* Never used as a symbol? */
@@ -172,6 +173,8 @@ static void ssa_convert_one_var(struct entrypoint *ep, struct symbol *var)
 			}
 			/* fall through */
 		case OP_LOAD:
+			if (insn->size != var->bit_size || insn->offset != 0)
+				same_size = false;
 			if (local) {
 				if (!samebb)
 					samebb = bb;
@@ -197,6 +200,9 @@ static void ssa_convert_one_var(struct entrypoint *ep, struct symbol *var)
 		return;
 	}
 
+	if (!same_size)
+		goto external_visibility;
+
 	idf_compute(ep, &idf, alpha);
 	FOR_EACH_PTR(idf, bb) {
 		struct instruction *node = insert_phi_node(bb, var);
diff --git a/validation/optim/volatile-dse-and-cfg.c b/validation/optim/volatile-dse-and-cfg.c
new file mode 100644
index 000000000000..8b0c2d21d539
--- /dev/null
+++ b/validation/optim/volatile-dse-and-cfg.c
@@ -0,0 +1,53 @@
+static void test_volatile_stores(volatile int *p)
+{
+	*p = 1;
+	*p = 2;
+}
+
+static void test_asm_mem(int *p)
+{
+	int x = 42;
+	asm volatile("" : : "m"(x));
+}
+
+struct mixed_bf {
+	int a:20;
+	int b:20;
+};
+
+static struct mixed_bf ssa_mixed_load(int c, struct mixed_bf x, struct mixed_bf y, int *out)
+{
+	struct mixed_bf s;
+	if (c)
+		s = x;
+	else
+		s = y;
+	struct mixed_bf copy = s;
+	*out = s.a;
+	return copy;
+}
+
+extern int glob;
+extern void ext_call(void);
+
+static void dom_store_order(int c)
+{
+	glob = 1;
+	if (c)
+		ext_call();
+	else
+		glob = 3;
+	glob = 2;
+}
+
+/*
+ * check-name: volatile-dse-and-cfg
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-excludes: VOID
+ * check-output-contains: store\\.32.*\\$1 -> 0\\[%arg1\\]
+ * check-output-contains: store\\.32.*\\$2 -> 0\\[%arg1\\]
+ * check-output-contains: store\\.32.*\\$42 -> 0\\[x\\]
+ * check-output-contains: store\\.32.*\\$1 -> 0\\[glob\\]
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications
  2026-11-09 20:31 Bart Van Assche
                   ` (15 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
                   ` (55 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Avoid undefined integer overflow and invalid IR transformations in
linearize.h and simplify.c:
- Check that operand and instruction sizes do not exceed 64 bits before
  evaluating 64-bit constant simplifications or calling eval_op() and
  eval_unop().
- Use bits_mask(), sign_extend(), and zero_extend() instead of open-coded
  64-bit shifts to avoid undefined shift overflow on 64-bit types.
- Check for null pseudo pointers in DEF_OPCODE(), canonical_order(), and
  simplification helpers before dereferencing operands.
- Update CFG edges and phi sources accurately in simplify_cgoto().
- Add regression test validation/optim/simplify-memops-ssa-fixes.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 linearize.h                                  |   2 +-
 simplify.c                                   | 225 ++++++++++++++-----
 validation/optim/simplify-memops-ssa-fixes.c | 103 +++++++++
 3 files changed, 278 insertions(+), 52 deletions(-)
 create mode 100644 validation/optim/simplify-memops-ssa-fixes.c

diff --git a/linearize.h b/linearize.h
index cac6082fc886..3412494edbef 100644
--- a/linearize.h
+++ b/linearize.h
@@ -187,7 +187,7 @@ struct basic_block {
 // and OP_BADOP if not. It also assigns the defining instruction
 // to ``DEF``.
 #define DEF_OPCODE(DEF, SRC)	\
-	(((SRC)->type == PSEUDO_REG && (DEF = (SRC)->def)) ? DEF->opcode : OP_BADOP)
+	(((SRC) && (SRC)->type == PSEUDO_REG && (DEF = (SRC)->def)) ? DEF->opcode : OP_BADOP)
 
 
 static inline void add_bb(struct basic_block_list **list, struct basic_block *bb)
diff --git a/simplify.c b/simplify.c
index f20b6f1a9881..2ddaf1c619a8 100644
--- a/simplify.c
+++ b/simplify.c
@@ -57,7 +57,7 @@
 // check if a pseudo is a power of 2
 static inline bool is_pow2(pseudo_t src)
 {
-	if (src->type != PSEUDO_VAL)
+	if (!src || src->type != PSEUDO_VAL)
 		return false;
 	return is_power_of_2(src->value);
 }
@@ -315,6 +315,9 @@ static void kill_asm(struct instruction *insn)
 	FOR_EACH_PTR(insn->asm_rules->inputs, con) {
 		kill_use(&con->pseudo);
 	} END_FOR_EACH_PTR(con);
+	FOR_EACH_PTR(insn->asm_rules->outputs, con) {
+		kill_use(&con->pseudo);
+	} END_FOR_EACH_PTR(con);
 }
 
 ///
@@ -428,7 +431,7 @@ void remove_dead_insns(struct entrypoint *ep)
 
 static inline int constant(pseudo_t pseudo)
 {
-	return pseudo->type == PSEUDO_VAL;
+	return pseudo && pseudo->type == PSEUDO_VAL;
 }
 
 ///
@@ -444,7 +447,7 @@ static inline pseudo_t is_same_op(pseudo_t src, int op, unsigned osize)
 {
 	struct instruction *def;
 
-	if (src->type != PSEUDO_REG)
+	if (!src || src->type != PSEUDO_REG)
 		return NULL;
 	def = src->def;
 	if (def->opcode != op)
@@ -582,7 +585,7 @@ static int swap_select(struct instruction *out, struct instruction *in, pseudo_t
 
 static inline int def_opcode(pseudo_t p)
 {
-	if (p->type != PSEUDO_REG)
+	if (!p || p->type != PSEUDO_REG)
 		return OP_BADOP;
 	return p->def->opcode;
 }
@@ -629,12 +632,18 @@ static unsigned int operand_size(struct instruction *insn, pseudo_t pseudo)
 static pseudo_t eval_op(int op, unsigned size, pseudo_t src1, pseudo_t src2)
 {
 	/* FIXME! Verify signs and sizes!! */
-	long long left = src1->value;
-	long long right = src2->value;
+	long long left;
+	long long right;
 	unsigned long long ul, ur;
 	long long res;
 	unsigned long long mask, bits;
 
+	if (!size || size > 64)
+		return NULL;
+
+	left = src1->value;
+	right = src2->value;
+
 	mask = sign_bit(size);
 	bits = bits_mask(size);
 
@@ -670,7 +679,7 @@ static pseudo_t eval_op(int op, unsigned size, pseudo_t src1, pseudo_t src2)
 	case OP_DIVS:
 		if (!right)
 			goto undef;
-		if (left == mask && right == -1)
+		if (ul == mask && right == -1)
 			goto undef;
 		res = left / right;
 		break;
@@ -682,7 +691,7 @@ static pseudo_t eval_op(int op, unsigned size, pseudo_t src1, pseudo_t src2)
 	case OP_MODS:
 		if (!right)
 			goto undef;
-		if (left == mask && right == -1)
+		if (ul == mask && right == -1)
 			goto undef;
 		res = left % right;
 		break;
@@ -1136,10 +1145,14 @@ static int simplify_seteq_setne(struct instruction *insn, long long value)
 		return REPEAT_CSE;
 
 	case OP_SEXT:
+		if (!def->orig_type)
+			break;
 		if (value && (def->orig_type->bit_size == 1))
 			break;
 		/* Fall through */
 	case OP_ZEXT:
+		if (!def->orig_type)
+			break;
 		// Convert:
 		//	*ext.m	%s <- (1) %a
 		//	setne.1 %r <- %s, $0
@@ -1149,7 +1162,8 @@ static int simplify_seteq_setne(struct instruction *insn, long long value)
 		insn->itype = def->orig_type;
 		return replace_pseudo(insn, &insn->src1, def->src);
 	case OP_TRUNC:
-		if (!one_use(old))
+		if (!one_use(old) || !def->orig_type ||
+		    def->orig_type->bit_size > 64 || def->size >= 64)
 			break;
 		// convert
 		//	trunc.n	%s <- (o) %a
@@ -1159,6 +1173,7 @@ static int simplify_seteq_setne(struct instruction *insn, long long value)
 		//	setne.m %r <- %s, $0
 		// and same for setne/eq ... 0/1
 		osize = def->size;
+		insn->itype = def->orig_type;
 		def->opcode = OP_AND;
 		def->type = def->orig_type;
 		def->size = def->type->bit_size;
@@ -1208,12 +1223,16 @@ static int simplify_unsigned_zero_compare(struct instruction *insn, int result)
 static int simplify_compare_constant(struct instruction *insn, long long value)
 {
 	unsigned size = insn->itype->bit_size;
-	unsigned long long bits = bits_mask(size);
+	unsigned long long bits;
 	struct instruction *def;
 	pseudo_t src1, src2;
 	unsigned int osize;
 	int changed = 0;
 
+	if (!size || size > 64)
+		return 0;
+	bits = bits_mask(size);
+
 	switch (insn->opcode) {
 	case OP_SET_LT:
 		if (!value)
@@ -1457,6 +1476,8 @@ static int simplify_compare_constant(struct instruction *insn, long long value)
 		}
 		break;
 	case OP_TRUNC:
+		if (!def->orig_type || def->orig_type->bit_size > 64 || size >= 64)
+			break;
 		osize = def->orig_type->bit_size;
 		switch (insn->opcode) {
 		case OP_SET_EQ: case OP_SET_NE:
@@ -1547,7 +1568,12 @@ static int simplify_const_rightadd(struct instruction *def, struct instruction *
 	switch (def->opcode) {
 	case OP_SUB:
 		if (constant(def->src1)) { // (C - y) + D --> eval(C+D) - y
-			pseudo_t val = eval_op(OP_ADD, size, def->src1, src2);
+			pseudo_t val;
+			if (size > 64)
+				break;
+			val = eval_op(OP_ADD, size, def->src1, src2);
+			if (!val)
+				break;
 			insn->opcode = OP_SUB;
 			use_pseudo(insn, def->src2, &insn->src2);
 			return replace_pseudo(insn, &insn->src1, val);
@@ -1560,9 +1586,13 @@ static int simplify_const_rightadd(struct instruction *def, struct instruction *
 static int simplify_constant_rightside(struct instruction *insn)
 {
 	long long value = insn->src2->value;
-	unsigned long long bits = bits_mask(insn->size);
+	unsigned long long bits;
 	int changed = 0;
 
+	if (insn->size > 64)
+		return 0;
+	bits = bits_mask(insn->size);
+
 	switch (insn->opcode) {
 	case OP_OR:
 		if ((value & bits) == bits)
@@ -1632,13 +1662,24 @@ static int simplify_const_leftsub(struct instruction *insn, struct instruction *
 	switch (def->opcode) {
 	case OP_ADD:
 		if (constant(def->src2)) { // C - (y + D) --> eval(C-D) - y
-			insn->src1 = eval_op(OP_SUB, size, src1, def->src2);
+			pseudo_t val;
+			if (size > 64)
+				break;
+			val = eval_op(OP_SUB, size, src1, def->src2);
+			if (!val)
+				break;
+			insn->src1 = val;
 			return replace_pseudo(insn, &insn->src2, def->src1);
 		}
 		break;
 	case OP_SUB:
 		if (constant(def->src1)) { // C - (D - z) --> z + eval(C-D)
-			pseudo_t val = eval_op(OP_SUB, size, src1, def->src1);
+			pseudo_t val;
+			if (size > 64)
+				break;
+			val = eval_op(OP_SUB, size, src1, def->src1);
+			if (!val)
+				break;
 			insn->opcode = OP_ADD;
 			use_pseudo(insn, def->src2, &insn->src1);
 			return replace_pseudo(insn, &insn->src2, val);
@@ -1650,7 +1691,11 @@ static int simplify_const_leftsub(struct instruction *insn, struct instruction *
 
 static int simplify_constant_leftside(struct instruction *insn)
 {
-	long long value = insn->src1->value;
+	long long value;
+
+	if (!insn->src1 || !insn->src2 || insn->size > 64)
+		return 0;
+	value = insn->src1->value;
 
 	switch (insn->opcode) {
 	case OP_ADD: case OP_OR: case OP_XOR:
@@ -1717,6 +1762,8 @@ static int simplify_binop_same_args(struct instruction *insn, pseudo_t arg)
 
 static int simplify_binop(struct instruction *insn)
 {
+	if (!insn->src1 || !insn->src2)
+		return 0;
 	if (constant(insn->src1)) {
 		if (constant(insn->src2))
 			return simplify_constant_binop(insn);
@@ -1753,8 +1800,12 @@ static int switch_pseudo(struct instruction *insn1, pseudo_t *pp1, struct instru
 //	* VOID, UNDEF and PHI are uninteresting (but VOID should have type 0)
 static int canonical_order(pseudo_t p1, pseudo_t p2)
 {
-	int t1 = p1->type;
-	int t2 = p2->type;
+	int t1, t2;
+
+	if (!p1 || !p2)
+		return 1;
+	t1 = p1->type;
+	t2 = p2->type;
 
 	/* symbol/constants on the right */
 	if (t1 < t2)
@@ -1793,7 +1844,7 @@ static int canonicalize_compare(struct instruction *insn)
 
 static inline int simple_pseudo(pseudo_t pseudo)
 {
-	return pseudo->type == PSEUDO_VAL || pseudo->type == PSEUDO_SYM;
+	return pseudo && (pseudo->type == PSEUDO_VAL || pseudo->type == PSEUDO_SYM);
 }
 
 ///
@@ -1820,7 +1871,7 @@ static inline bool can_move_to(pseudo_t src, struct instruction *dst)
 
 	if (!one_use(dst->target))
 		return false;
-	if (src->type != PSEUDO_REG)
+	if (!src || src->type != PSEUDO_REG)
 		return true;
 
 	def = src->def;
@@ -1851,8 +1902,14 @@ static int simplify_associative_binop(struct instruction *insn)
 	if (!simple_pseudo(def->src2))
 		return 0;
 	if (constant(def->src2) && constant(insn->src2)) {
+		pseudo_t val;
+		if (insn->size > 64)
+			return 0;
+		val = eval_op(insn->opcode, insn->size, insn->src2, def->src2);
+		if (!val)
+			return 0;
 		// (x # C) # K --> x # eval(C # K)
-		insn->src2 = eval_op(insn->opcode, insn->size, insn->src2, def->src2);
+		insn->src2 = val;
 		return replace_pseudo(insn, &insn->src1, def->src1);
 	}
 
@@ -1873,12 +1930,12 @@ static int simplify_add_one_side(struct instruction *insn, pseudo_t *p1, pseudo_
 	switch (DEF_OPCODE(def, src1)) {
 	case OP_MUL:
 		if (DEF_OPCODE(defr, *p2) == OP_MUL) {
-			if (defr->src2 == def->src2 && can_move_to(def->src2, defr)) {
+			if (defr->src2 == def->src2 && can_move_to(def->src1, defr)) {
 				// ((x * z) + (y * z)) into ((x + y) * z)
 				swap_insn(insn, defr, def->src1, defr->src1, def->src2);
 				return REPEAT_CSE;
 			}
-			if (defr->src1 == def->src1 && can_move_to(def->src1, defr)) {
+			if (defr->src1 == def->src1 && can_move_to(def->src2, defr)) {
 				// ((z * x) + (z * y)) into ((x + y) * z)
 				swap_insn(insn, defr, def->src2, defr->src2, def->src1);
 				return REPEAT_CSE;
@@ -1991,12 +2048,12 @@ static int simplify_and_one_side(struct instruction *insn, pseudo_t *p1, pseudo_
 		break;
 	case OP_OR:
 		if (DEF_OPCODE(defr, *p2) == OP_OR) {
-			if (defr->src2 == def->src2 && can_move_to(def->src2, defr)) {
+			if (defr->src2 == def->src2 && can_move_to(def->src1, defr)) {
 				// ((x | z) & (y | z)) into ((x & y) | z)
 				swap_insn(insn, defr, def->src1, defr->src1, def->src2);
 				return REPEAT_CSE;
 			}
-			if (defr->src1 == def->src1 && can_move_to(def->src1, defr)) {
+			if (defr->src1 == def->src1 && can_move_to(def->src2, defr)) {
 				// ((z | x) & (z | y)) into ((x & y) | z)
 				swap_insn(insn, defr, def->src2, defr->src2, def->src1);
 				return REPEAT_CSE;
@@ -2035,12 +2092,12 @@ static int simplify_ior_one_side(struct instruction *insn, pseudo_t *p1, pseudo_
 	switch (DEF_OPCODE(def, src1)) {
 	case OP_AND:
 		if (DEF_OPCODE(defr, *p2) == OP_AND) {
-			if (defr->src2 == def->src2 && can_move_to(def->src2, defr)) {
+			if (defr->src2 == def->src2 && can_move_to(def->src1, defr)) {
 				// ((x & z) | (y & z)) into ((x | y) & z)
 				swap_insn(insn, defr, def->src1, defr->src1, def->src2);
 				return REPEAT_CSE;
 			}
-			if (defr->src1 == def->src1 && can_move_to(def->src1, defr)) {
+			if (defr->src1 == def->src1 && can_move_to(def->src2, defr)) {
 				// ((z & x) | (z & y)) into ((x | y) & z)
 				swap_insn(insn, defr, def->src2, defr->src2, def->src1);
 				return REPEAT_CSE;
@@ -2053,6 +2110,8 @@ static int simplify_ior_one_side(struct instruction *insn, pseudo_t *p1, pseudo_
 		}
 		break;
 	case OP_NOT:
+		if (insn->size > 64)
+			break;
 		if (def->src == *p2)
 			return replace_with_value(insn, bits_mask(insn->size));
 		break;
@@ -2089,12 +2148,12 @@ static int simplify_xor_one_side(struct instruction *insn, pseudo_t *p1, pseudo_
 	switch (DEF_OPCODE(def, src1)) {
 	case OP_AND:
 		if (DEF_OPCODE(defr, *p2) == OP_AND) {
-			if (defr->src2 == def->src2 && can_move_to(def->src2, defr)) {
+			if (defr->src2 == def->src2 && can_move_to(def->src1, defr)) {
 				// ((x & z) ^ (y & z)) into ((x ^ y) & z)
 				swap_insn(insn, defr, def->src1, defr->src1, def->src2);
 				return REPEAT_CSE;
 			}
-			if (defr->src1 == def->src1 && can_move_to(def->src1, defr)) {
+			if (defr->src1 == def->src1 && can_move_to(def->src2, defr)) {
 				// ((z & x) ^ (z & y)) into ((x ^ y) & z)
 				swap_insn(insn, defr, def->src2, defr->src2, def->src1);
 				return REPEAT_CSE;
@@ -2107,6 +2166,8 @@ static int simplify_xor_one_side(struct instruction *insn, pseudo_t *p1, pseudo_
 		}
 		break;
 	case OP_NOT:
+		if (insn->size > 64)
+			break;
 		if (def->src == *p2)
 			return replace_with_value(insn, bits_mask(insn->size));
 		break;
@@ -2137,8 +2198,11 @@ static int simplify_xor(struct instruction *insn)
 
 static int simplify_constant_unop(struct instruction *insn)
 {
-	long long val = insn->src1->value;
-	unsigned long long res, mask;
+	unsigned long long val = insn->src1->value;
+	unsigned long long res;
+
+	if (!insn->size || insn->size > 64)
+		return 0;
 
 	switch (insn->opcode) {
 	case OP_NOT:
@@ -2148,18 +2212,24 @@ static int simplify_constant_unop(struct instruction *insn)
 		res = -(unsigned long long)val;
 		break;
 	case OP_SEXT:
-		mask = sign_bit(insn->orig_type->bit_size);
-		if (val & mask)
-			val |= ~bits_mask(insn->orig_type->bit_size);
-		/* fall through */
+		if (!insn->orig_type || insn->orig_type->bit_size > 64)
+			return 0;
+		res = sign_extend(val, insn->orig_type->bit_size);
+		break;
 	case OP_ZEXT:
+		if (!insn->orig_type || insn->orig_type->bit_size > 64)
+			return 0;
+		val = zero_extend(val, insn->orig_type->bit_size);
+		/* fall through */
 	case OP_TRUNC:
+		if (!insn->orig_type || insn->orig_type->bit_size > 64)
+			return 0;
 		res = val;
 		break;
 	default:
 		return 0;
 	}
-	res &= bits_mask(insn->size);
+	res = zero_extend(res, insn->size);
 	
 	return replace_with_value(insn, res);
 }
@@ -2176,7 +2246,7 @@ static int simplify_unop(struct instruction *insn)
 	case OP_NOT:
 		switch (DEF_OPCODE(def, src)) {
 		case OP_ADD:
-			if (!constant(def->src2))
+			if (insn->size > 64 || !constant(def->src2))
 				break;
 			insn->opcode = OP_SUB;	// ~(x + C) --> ~C - x
 			src = eval_unop(OP_NOT, insn->size, def->src2);
@@ -2189,13 +2259,13 @@ static int simplify_unop(struct instruction *insn)
 		case OP_NOT:			// ~(~x) --> x
 			return replace_with_pseudo(insn, def->src);
 		case OP_SUB:
-			if (!constant(def->src1))
+			if (insn->size > 64 || !constant(def->src1))
 				break;
 			insn->opcode = OP_ADD;	// ~(C - x) --> x + ~C
 			insn->src2 = eval_unop(OP_NOT, insn->size, def->src1);
 			return replace_pseudo(insn, &insn->src1, def->src2);
 		case OP_XOR:
-			if (!constant(def->src2))
+			if (insn->size > 64 || !constant(def->src2))
 				break;
 			insn->opcode = OP_XOR;	// ~(x ^ C) --> x ^ ~C
 			insn->src2 = eval_unop(OP_NOT, insn->size, def->src2);
@@ -2205,7 +2275,7 @@ static int simplify_unop(struct instruction *insn)
 	case OP_NEG:
 		switch (DEF_OPCODE(def, src)) {
 		case OP_ADD:
-			if (!constant(def->src2))
+			if (insn->size > 64 || !constant(def->src2))
 				break;
 			insn->opcode = OP_SUB;	// -(x + C) --> (-C - x)
 			src = eval_unop(OP_NEG, insn->size, def->src2);
@@ -2314,7 +2384,7 @@ static int simplify_cast(struct instruction *insn)
 	switch (def_opcode(src)) {
 	case OP_AND:
 		val = def->src2;
-		if (val->type != PSEUDO_VAL)
+		if (!constant(val) || size > 64 || def->size > 64)
 			break;
 		/* A cast of a AND might be a no-op.. */
 		switch (insn->opcode) {
@@ -2395,13 +2465,15 @@ static int simplify_cast(struct instruction *insn)
 	case OP_OR:
 		switch (insn->opcode) {
 		case OP_TRUNC:
+			if (size > 64 || def->size > 64)
+				break;
 			mask = bits_mask(insn->size);
 			return simplify_mask_or(insn, mask, def);
 		}
 		break;
 	case OP_LSR:
 	case OP_SHL:
-		if (insn->opcode != OP_TRUNC)
+		if (insn->opcode != OP_TRUNC || size > 64 || def->size > 64)
 			break;
 		mask = bits_mask(insn->size);
 		return simplify_mask_shift(def, mask);
@@ -2411,7 +2483,7 @@ static int simplify_cast(struct instruction *insn)
 			insn->orig_type = def->orig_type;
 			return replace_pseudo(insn, &insn->src1, def->src);
 		case OP_SEXT:
-			if (size != def->orig_type->bit_size)
+			if (!def->orig_type || size > 64 || size != def->orig_type->bit_size)
 				break;
 			if (DEF_OPCODE(def2, def->src) != OP_LSR)
 				break;
@@ -2422,7 +2494,8 @@ static int simplify_cast(struct instruction *insn)
 			insn->src2 = def2->src2;
 			return replace_pseudo(insn, &insn->src1, def2->src1);
 		case OP_ZEXT:
-			if (size != def->orig_type->bit_size)
+			if (!def->orig_type || size > 64 || def->size >= 64 ||
+			    size != def->orig_type->bit_size)
 				break;
 			insn->opcode = OP_AND;
 			insn->src2 = value_pseudo(bits_mask(def->size));
@@ -2451,6 +2524,10 @@ static int simplify_cast(struct instruction *insn)
 			return replace_pseudo(insn, &insn->src, def->src);
 		}
 		switch (insn->opcode) {
+		case OP_ZEXT:
+			if (insn->orig_type != def->orig_type)
+				break;
+			/* fall through */
 		case OP_SEXT:
 			insn->orig_type = def->orig_type;
 			return replace_pseudo(insn, &insn->src, def->src);
@@ -2691,6 +2768,10 @@ static int simplify_switch(struct instruction *insn)
 	if (!constant(cond))
 		return 0;
 	val = insn->cond->value;
+	if (insn->type && insn->size > 0) {
+		bool is_signed = !(insn->type->ctype.modifiers & MOD_UNSIGNED);
+		val = bits_extend(val, insn->size, is_signed);
+	}
 
 	FOR_EACH_PTR(insn->multijmp_list, jmp) {
 		/* Default case */
@@ -2717,34 +2798,76 @@ static struct basic_block *is_label(pseudo_t pseudo)
 
 static int simplify_cgoto(struct instruction *insn)
 {
-	struct basic_block *target, *bb = insn->bb;
-	struct basic_block *bbt, *bbf;
+	struct basic_block *target, *orig_target, *bb = insn->bb;
+	struct basic_block *bbt, *bbf, *orig_bbt, *orig_bbf;
 	struct instruction *def;
 	struct multijmp *jmp;
 
 	switch (DEF_OPCODE(def, insn->src)) {
 	case OP_SEL:	// CGOTO(SEL(x, L1, L2)) --> CBR x, L1, L2
 		if ((bbt = is_label(def->src2)) && (bbf = is_label(def->src3))) {
+			if (!bbt->ep || !bbf->ep)
+				return 0;
+			if (bbt == bbf) {
+				target = bbt;
+				goto convert_to_br;
+			}
+			orig_bbt = bbt;
+			orig_bbf = bbf;
+			FOR_EACH_PTR(insn->multijmp_list, jmp) {
+				if (jmp->target == bbt) {
+					bbt = NULL;
+					continue;
+				}
+				if (jmp->target == bbf) {
+					bbf = NULL;
+					continue;
+				}
+				remove_bb_from_list(&jmp->target->parents, bb, 1);
+				remove_bb_from_list(&bb->children, jmp->target, 1);
+				remove_phisources(bb, jmp->target);
+			} END_FOR_EACH_PTR(jmp);
+			free_ptr_list(&insn->multijmp_list);
 			insn->opcode = OP_CBR;
-			insn->bb_true = bbt;
-			insn->bb_false = bbf;
-			return replace_pseudo(insn, &insn->src1, def->cond);
+			insn->bb_true = orig_bbt;
+			insn->bb_false = orig_bbf;
+			if (bbt) {
+				add_bb(&orig_bbt->parents, bb);
+				add_bb(&bb->children, orig_bbt);
+			}
+			if (bbf) {
+				add_bb(&orig_bbf->parents, bb);
+				add_bb(&bb->children, orig_bbf);
+			}
+			replace_pseudo(insn, &insn->src1, def->cond);
+			return REPEAT_CSE|REPEAT_CFG_CLEANUP;
 		}
 		break;
 	case OP_LABEL:
 		target = def->bb_true;
 		if (!target->ep)
 			return 0;
+	convert_to_br:
+		orig_target = target;
 		FOR_EACH_PTR(insn->multijmp_list, jmp) {
-			if (jmp->target == target)
+			if (jmp->target == target) {
+				target = NULL;
 				continue;
+			}
 			remove_bb_from_list(&jmp->target->parents, bb, 1);
 			remove_bb_from_list(&bb->children, jmp->target, 1);
-			DELETE_CURRENT_PTR(jmp);
+			remove_phisources(bb, jmp->target);
 		} END_FOR_EACH_PTR(jmp);
+		free_ptr_list(&insn->multijmp_list);
+		if (target) {
+			add_bb(&orig_target->parents, bb);
+			add_bb(&bb->children, orig_target);
+		}
 		kill_use(&insn->src);
+		insn->cond = NULL;
 		insn->opcode = OP_BR;
-		insn->bb_true = target;
+		insn->bb_true = orig_target;
+		insn->bb_false = NULL;
 		return REPEAT_CSE|REPEAT_CFG_CLEANUP;
 	}
 	return 0;
diff --git a/validation/optim/simplify-memops-ssa-fixes.c b/validation/optim/simplify-memops-ssa-fixes.c
new file mode 100644
index 000000000000..b94e0182902e
--- /dev/null
+++ b/validation/optim/simplify-memops-ssa-fixes.c
@@ -0,0 +1,103 @@
+/* 1. Negation of LLONG_MIN */
+static long long test_neg_llong_min(void)
+{
+	return -(-9223372036854775807LL - 1LL);
+}
+
+/* 2. Signed bitfield post-increment assigned to another bitfield */
+struct sbf {
+	signed int a:3;
+	signed int b:3;
+	signed int c:3;
+};
+
+int bf_postinc_const(void)
+{
+	struct sbf s = { -1, 0, 0 };
+	s.b = s.a++;
+	return s.c;
+}
+
+/* 3. ((x & mask) | bits) == C with bits outside mask */
+int and_or_equ(unsigned x)
+{
+	return ((x & 0xf0) | 0x01) == 0x11;
+}
+
+/* 4. Commuted (x + 1U) - x */
+unsigned add_sub_cancel(unsigned x)
+{
+	return (x + 1U) - x;
+}
+
+/* 5. 0 / non-constant */
+int zero_div(int x)
+{
+	return 0 / (x ? 1 : 2);
+}
+
+/* 6. Computed goto OP_SEL with identical targets and live PHI */
+int cgoto_same_target_phi(int c, int d)
+{
+	void *p = c ? &&L1 : &&L1;
+	int r = 1;
+	if (d) {
+		r = 2;
+		goto L2;
+	}
+	goto *p;
+L2:
+	r = 3;
+L1:
+	return r;
+}
+
+/* 7. Computed goto OP_LABEL deduplication with live PHI */
+int cgoto_label_dedup(int c)
+{
+	void *p = &&L1;
+	int r = 1;
+	if (c) {
+		r = 2;
+		goto L2;
+	}
+	goto *p;
+L2:
+	r = 3;
+L1:
+	return r;
+}
+
+/* 8. 128-bit zero-extension and addition above 64-bit width */
+__int128 zext_u64_to_s128(void)
+{
+	return (__int128)0x8000000000000000ULL;
+}
+
+unsigned __int128 add_u128_carry(void)
+{
+	return (unsigned __int128)0x8000000000000000ULL + 0x8000000000000000ULL;
+}
+
+/* 9. ZEXT of SEXT with distinct source widths */
+unsigned long long zext_sext_char(signed char c)
+{
+	return (unsigned long long)(unsigned int)(int)c;
+}
+
+/*
+ * check-name: simplify-memops-ssa-fixes
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-excludes: VOID
+ * check-output-contains: ret\\.64.*\\$0x8000000000000000
+ * check-output-contains: zext\\.128
+ * check-output-contains: add\\.128
+ * check-output-contains: sext\\.32
+ * check-output-contains: zext\\.64
+ *
+ * check-error-start
+optim/simplify-memops-ssa-fixes.c:4:16: warning: constant integer operation overflow
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow
  2026-11-09 20:31 Bart Van Assche
                   ` (16 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
                   ` (54 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Gate "context check failure" diagnostics on Wcontext in
context_increase() and return early as soon as entry + sum drops below
zero so an unexpected unlock followed by a lock in the same basic block
still triggers a context imbalance warning.

Add regression test validation/context-fixes.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 sparse.c                   |  5 ++++-
 validation/context-fixes.c | 41 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)
 create mode 100644 validation/context-fixes.c

diff --git a/sparse.c b/sparse.c
index e7cc6f55e503..883a5edec5b1 100644
--- a/sparse.c
+++ b/sparse.c
@@ -59,10 +59,13 @@ static int context_increase(struct basic_block *bb, int entry)
 					continue;
 			} else if (current >= val)
 				continue;
-			warning(insn->pos, "context check failure");
+			if (Wcontext)
+				warning(insn->pos, "context check failure");
 			continue;
 		}
 		sum += val;
+		if (entry + sum < 0)
+			return sum;
 	} END_FOR_EACH_PTR(insn);
 	return sum;
 }
diff --git a/validation/context-fixes.c b/validation/context-fixes.c
new file mode 100644
index 000000000000..ab6c354cacea
--- /dev/null
+++ b/validation/context-fixes.c
@@ -0,0 +1,41 @@
+static void lock(void) __attribute__((context(0, 1)))
+{
+	__context__(1);
+}
+
+static void unlock(void) __attribute__((context(1, 0)))
+{
+	__context__(-1);
+}
+
+/* 1. Context attribute before pointer return type */
+extern __attribute__((context(0, 1))) void *lock_ret_ptr(void);
+
+static void good_ptr_lock(void)
+{
+	void *p = lock_ret_ptr();
+	(void)p;
+	unlock();
+}
+
+static void warn_ptr_lock(void)
+{
+	lock_ret_ptr();
+}
+
+/* 2. Unlock followed by lock in same basic block must warn */
+static void warn_unlock_then_lock(void)
+{
+	unlock();
+	lock();
+}
+
+/*
+ * check-name: context-fixes
+ * check-command: sparse -Wcontext $file
+ *
+ * check-error-start
+context-fixes.c:21:13: warning: context imbalance in 'warn_ptr_lock' - wrong count at exit
+context-fixes.c:27:13: warning: context imbalance in 'warn_unlock_then_lock' - unexpected unlock
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily
  2026-11-09 20:31 Bart Van Assche
                   ` (17 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
                   ` (53 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Return AST_N_COLUMNS instead of 1 from ast_get_n_columns() so GtkTreeModel
exposes the full column count to GTK tree views.

Populate child nodes lazily via inspect_child_node() and reject negative
indices in ast_iter_nth_child() before indexing node->childnodes.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ast-model.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/ast-model.c b/ast-model.c
index cbd82dbb3b38..0cf74ef4ba77 100644
--- a/ast-model.c
+++ b/ast-model.c
@@ -227,7 +227,7 @@ ast_get_flags(GtkTreeModel *tree_model)
 static gint
 ast_get_n_columns(GtkTreeModel *tree_model)
 {
-	return 1;
+	return AST_N_COLUMNS;
 }
 
 
@@ -427,8 +427,11 @@ ast_iter_nth_child(GtkTreeModel *tree_model,
                         gint          n)
 {
 	AstNode    *node = parent ? parent->user_data : (AstNode*) tree_model;
-	GArray *array = node->childnodes;
-	if (n >= array->len)
+	GArray *array;
+
+	inspect_child_node(node);
+	array = node->childnodes;
+	if (n < 0 || n >= array->len)
 		return FALSE;
 	iter->user_data = g_array_index(array, AstNode *, n);
 	return TRUE;

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string
  2026-11-09 20:31 Bart Van Assche
                   ` (18 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
                   ` (52 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Return early in examine_namespace() when sym->aux is already populated so
mutually recursive struct definitions do not trigger infinite recursion.

Guard sym->ctype.base_type->aux in examine_symbol() and release the string
allocated by xmlGetProp() with xmlFree() after xmlNewProp() copies it so
analyzing recursive struct types does not leak memory under LeakSanitizer.

Add a regression test in validation/c2xml-recursive-types.c.

Fixes: f744a1419d92 ("add c2xml program")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 c2xml.c                            | 11 +++++++++--
 validation/c2xml-recursive-types.c | 15 +++++++++++++++
 2 files changed, 24 insertions(+), 2 deletions(-)
 create mode 100644 validation/c2xml-recursive-types.c

diff --git a/c2xml.c b/c2xml.c
index 519769f72a71..f23ec1a72de4 100644
--- a/c2xml.c
+++ b/c2xml.c
@@ -190,8 +190,13 @@ static void examine_symbol(struct symbol *sym, xmlNodePtr node)
 			if (!sym->ctype.base_type->aux) {
 				examine_symbol(sym->ctype.base_type, root_node);
 			}
-			xmlNewProp(child, BAD_CAST "base-type",
-			           xmlGetProp((xmlNodePtr)sym->ctype.base_type->aux, BAD_CAST "id"));
+			if (sym->ctype.base_type->aux) {
+				xmlChar *base_id = xmlGetProp((xmlNodePtr)sym->ctype.base_type->aux, BAD_CAST "id");
+				if (base_id) {
+					xmlNewProp(child, BAD_CAST "base-type", base_id);
+					xmlFree(base_id);
+				}
+			}
 		} else {
 			newProp(child, "base-type-builtin", base);
 		}
@@ -250,6 +255,8 @@ static void examine_macro(struct symbol *sym, xmlNodePtr node)
 
 static void examine_namespace(struct symbol *sym)
 {
+	if (sym->aux)		/*already visited */
+		return;
 	if (sym->ident && sym->ident->reserved)
 		return;
 
diff --git a/validation/c2xml-recursive-types.c b/validation/c2xml-recursive-types.c
new file mode 100644
index 000000000000..210def55086b
--- /dev/null
+++ b/validation/c2xml-recursive-types.c
@@ -0,0 +1,15 @@
+struct B;
+struct A {
+	struct B *b;
+};
+struct B {
+	struct A *a;
+};
+
+/*
+ * check-name: c2xml-recursive-types
+ * check-command: c2xml $file
+ * check-output-ignore
+ * check-output-contains: <symbol type="struct".*ident="A"
+ * check-output-contains: <symbol type="struct".*ident="B"
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks
  2026-11-09 20:31 Bart Van Assche
                   ` (19 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
                   ` (51 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Harden x86 code generation in compile-i386.c:
- Enlarge fixed formatting buffers and replace strcpy()/sprintf() calls
  with bounded snprintf() in stor_op_name(), emit_insn_atom(), and
  emit_global_noinit().
- Guard against zero-length array division by zero in emit_initializer()
  and use sym->bit_size / 8 directly in emit_array_noinit().
- Emit 'setbe' instead of 'setb' for SPECIAL_UNSIGNED_LTE in
  emit_compare().
- Handle SYM_PTR scalar initializers and guard current_func and
  stmt->goto_label before dereferencing them.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 compile-i386.c | 76 ++++++++++++++++++++++++++++----------------------
 1 file changed, 43 insertions(+), 33 deletions(-)

diff --git a/compile-i386.c b/compile-i386.c
index 16b3893a4ef1..81f6a9a7eceb 100644
--- a/compile-i386.c
+++ b/compile-i386.c
@@ -434,30 +434,30 @@ static void stor_sym_init(struct symbol *sym)
 
 static const char *stor_op_name(struct storage *s)
 {
-	static char name[32];
+	static char name[256];
 
 	switch (s->type) {
 	case STOR_PSEUDO:
-		strcpy(name, pretty_offset((int) pseudo_offset(s)));
+		snprintf(name, sizeof(name), "%s", pretty_offset((int) pseudo_offset(s)));
 		break;
 	case STOR_ARG:
-		strcpy(name, pretty_offset((int) arg_offset(s)));
+		snprintf(name, sizeof(name), "%s", pretty_offset((int) arg_offset(s)));
 		break;
 	case STOR_SYM:
-		strcpy(name, show_ident(s->sym->ident));
+		snprintf(name, sizeof(name), "%s", show_ident(s->sym->ident));
 		break;
 	case STOR_REG:
-		strcpy(name, s->reg->name);
+		snprintf(name, sizeof(name), "%s", s->reg->name);
 		break;
 	case STOR_VALUE:
-		sprintf(name, "$%lld", s->value);
+		snprintf(name, sizeof(name), "$%lld", s->value);
 		break;
 	case STOR_LABEL:
-		sprintf(name, "%s.L%d", s->flags & STOR_LABEL_VAL ? "$" : "",
+		snprintf(name, sizeof(name), "%s.L%d", s->flags & STOR_LABEL_VAL ? "$" : "",
 			s->label);
 		break;
 	case STOR_LABELSYM:
-		sprintf(name, "%s.LS%p", s->flags & STOR_LABEL_VAL ? "$" : "",
+		snprintf(name, sizeof(name), "%s.LS%p", s->flags & STOR_LABEL_VAL ? "$" : "",
 			s->labelsym);
 		break;
 	}
@@ -707,27 +707,27 @@ static void emit_section(const char *s)
 
 static void emit_insn_atom(struct function *f, struct atom *atom)
 {
-	char s[128];
-	char comment[64];
+	char s[512];
+	char comment[128];
 	struct storage *op1 = atom->op1;
 	struct storage *op2 = atom->op2;
 
 	if (atom->comment[0])
-		sprintf(comment, "\t\t# %s", atom->comment);
+		snprintf(comment, sizeof(comment), "\t\t# %s", atom->comment);
 	else
 		comment[0] = 0;
 
 	if (atom->op2) {
-		char tmp[16];
-		strcpy(tmp, stor_op_name(op1));
-		sprintf(s, "\t%s\t%s, %s%s\n",
+		char tmp[256];
+		snprintf(tmp, sizeof(tmp), "%s", stor_op_name(op1));
+		snprintf(s, sizeof(s), "\t%s\t%s, %s%s\n",
 			atom->insn, tmp, stor_op_name(op2), comment);
 	} else if (atom->op1)
-		sprintf(s, "\t%s\t%s%s%s\n",
+		snprintf(s, sizeof(s), "\t%s\t%s%s%s\n",
 			atom->insn, stor_op_name(op1),
 			comment[0] ? "\t" : "", comment);
 	else
-		sprintf(s, "\t%s\t%s%s\n",
+		snprintf(s, sizeof(s), "\t%s\t%s%s\n",
 			atom->insn,
 			comment[0] ? "\t\t" : "", comment);
 
@@ -941,16 +941,16 @@ static void emit_scalar(struct expression *expr, unsigned int bit_size)
 static void emit_global_noinit(const char *name, unsigned long modifiers,
 			       unsigned long alignment, unsigned int byte_size)
 {
-	char s[64];
+	char s[256];
 
 	if (modifiers & MOD_STATIC) {
-		sprintf(s, "\t.local\t%s\n", name);
+		snprintf(s, sizeof(s), "\t.local\t%s\n", name);
 		textbuf_push(&unit_post_text, s);
 	}
 	if (alignment)
-		sprintf(s, "\t.comm\t%s,%d,%lu\n", name, byte_size, alignment);
+		snprintf(s, sizeof(s), "\t.comm\t%s,%d,%lu\n", name, byte_size, alignment);
 	else
-		sprintf(s, "\t.comm\t%s,%d\n", name, byte_size);
+		snprintf(s, sizeof(s), "\t.comm\t%s,%d\n", name, byte_size);
 	textbuf_push(&unit_post_text, s);
 }
 
@@ -966,9 +966,14 @@ static void emit_initializer(struct symbol *sym,
 
 	if (expr->type == EXPR_VALUE) {
 		struct symbol *base_type = sym->ctype.base_type;
+		long long nr;
 		assert(base_type != NULL);
 
-		emit_scalar(expr, sym->bit_size / get_expression_value(base_type->array_size));
+		nr = get_expression_value(base_type->array_size);
+		if (nr > 0)
+			emit_scalar(expr, sym->bit_size / nr);
+		else if (base_type->ctype.base_type && base_type->ctype.base_type->bit_size > 0)
+			emit_scalar(expr, base_type->ctype.base_type->bit_size);
 		return;
 	}
 	if (expr->type != EXPR_INITIALIZER)
@@ -1129,7 +1134,7 @@ static void emit_array_noinit(struct symbol *sym)
 {
 	emit_global_noinit(show_ident(sym->ident),
 			   sym->ctype.modifiers, sym->ctype.alignment,
-			   get_expression_value(sym->array_size) * (sym->bit_size / 8));
+			   sym->bit_size / 8);
 	stor_sym_init(sym);
 }
 
@@ -1249,7 +1254,7 @@ static struct storage *emit_compare(struct expression *expr)
 	case SPECIAL_UNSIGNED_GT:
 				opname = "seta";	break;
 	case SPECIAL_UNSIGNED_LTE:
-				opname = "setb";	break;
+				opname = "setbe";	break;
 	case SPECIAL_UNSIGNED_GTE:
 				opname = "setae";	break;
 	default:
@@ -1820,13 +1825,16 @@ static void x86_symbol(struct symbol *sym)
 		break;
 
 	case SYM_BASETYPE:
+	case SYM_PTR:
 		if (sym->initializer) {
-			emit_object_pre(show_ident(sym->ident),
-					sym->ctype.modifiers,
-				        sym->ctype.alignment,
-					sym->bit_size / 8);
-			emit_scalar(sym->initializer, sym->bit_size);
-			stor_sym_init(sym);
+			if (sym->initializer->type == EXPR_VALUE) {
+				emit_object_pre(show_ident(sym->ident),
+						sym->ctype.modifiers,
+					        sym->ctype.alignment,
+						sym->bit_size / 8);
+				emit_scalar(sym->initializer, sym->bit_size);
+				stor_sym_init(sym);
+			}
 		} else
 			emit_scalar_noinit(sym);
 		break;
@@ -1857,8 +1865,8 @@ static void x86_symbol(struct symbol *sym)
 		break;
 	}
 
-	if (sym->initializer && (type->type != SYM_BASETYPE) &&
-	    (type->type != SYM_ARRAY)) {
+	if (current_func && sym->initializer && (type->type != SYM_BASETYPE) &&
+	    (type->type != SYM_ARRAY) && (type->type != SYM_PTR)) {
 		printf(" = \n");
 		x86_expression(sym->initializer);
 	}
@@ -2039,12 +2047,14 @@ static struct storage *x86_statement(struct statement *stmt)
 		if (stmt->goto_expression) {
 			struct storage *val = x86_expression(stmt->goto_expression);
 			printf("\tgoto *v%d\n", val->pseudo);
-		} else if (!strcmp("break", show_ident(stmt->goto_label->ident))) {
+		} else if (stmt->goto_label && stmt->goto_label->ident &&
+			   !strcmp("break", show_ident(stmt->goto_label->ident))) {
 			struct storage *lbv = new_storage(STOR_LABEL);
 			lbv->label = loopstk_break();
 			lbv->flags = STOR_WANTS_FREE;
 			insn("jmp", lbv, NULL, "'break'; go to loop bottom");
-		} else if (!strcmp("continue", show_ident(stmt->goto_label->ident))) {
+		} else if (stmt->goto_label && stmt->goto_label->ident &&
+			   !strcmp("continue", show_ident(stmt->goto_label->ident))) {
 			struct storage *lbv = new_storage(STOR_LABEL);
 			lbv->label = loopstk_continue();
 			lbv->flags = STOR_WANTS_FREE;

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL
  2026-11-09 20:31 Bart Van Assche
                   ` (20 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
                   ` (50 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Return a tri-state comparison result for line numbers in cmp_sym() instead
of a boolean '<' check that violates qsort()'s strict weak ordering.

Handle SYM_RESTRICT by recursing into its base type and ignore SYM_LABEL
cleanly in examine_symbol().

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ctags.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ctags.c b/ctags.c
index afdc42b77b98..e6925510f278 100644
--- a/ctags.c
+++ b/ctags.c
@@ -50,7 +50,7 @@ static int cmp_sym(const void *m, const void *n)
 		ret = strcmp(stream_name(a_pos.stream),
 		             stream_name(b_pos.stream));
 		if (!ret)
-			return a_pos.line < b_pos.line;
+			return (a_pos.line > b_pos.line) - (a_pos.line < b_pos.line);
 	}
 	return ret;
 }
@@ -155,8 +155,10 @@ static void examine_symbol(struct symbol *sym)
 	case SYM_BITFIELD:
 	case SYM_FN:
 	case SYM_ARRAY:
+	case SYM_RESTRICT:
 		examine_symbol(sym->ctype.base_type);
 		break;
+	case SYM_LABEL:
 	case SYM_BASETYPE:
 		break;
 

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 23/73] dissect: Unwrap array element type via base_type()
  2026-11-09 20:31 Bart Van Assche
                   ` (21 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
                   ` (49 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Use base_type(in) instead of in->ctype.base_type when stepping through
array subscripts in dissect.c so SYM_NODE wrappers are unwrapped properly.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 dissect.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dissect.c b/dissect.c
index 9419c5931fbb..5d25e02fa1ad 100644
--- a/dissect.c
+++ b/dissect.c
@@ -467,7 +467,7 @@ again:
 					lookup_member(in, expr->ident, NULL));
 			} else {
 				do_expression(U_R_VAL, expr->index);
-				in = in->ctype.base_type;
+				in = base_type(in);
 			}
 		} while ((expr = expr->down));
 	}

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 24/73] example: Fix show_op() rotating buffer dimensions
  2026-11-09 20:31 Bart Van Assche
                   ` (22 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
                   ` (48 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Declare buf as char buf[4][256] instead of char buf[256][4] in show_op() so
each of the 4 rotating slots holds 256 bytes instead of 4 bytes.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 example.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/example.c b/example.c
index 0c2ddf50c015..94f82d874920 100644
--- a/example.c
+++ b/example.c
@@ -168,7 +168,7 @@ struct operand {
 
 static const char *show_op(struct bb_state *state, struct operand *op)
 {
-	static char buf[256][4];
+	static char buf[4][256];
 	static int bufnr;
 	char *p, *ret;
 	int nr;

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym
  2026-11-09 20:31 Bart Van Assche
                   ` (23 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
                   ` (47 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Verify that insn->func->type is PSEUDO_SYM before reading insn->func->sym
in graph_calls() so indirect calls through non-symbol pseudos do not read
mismatched union members.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 graph.c | 33 ++++++++++++++++-----------------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/graph.c b/graph.c
index be4cf282c0c7..e952b726fd30 100644
--- a/graph.c
+++ b/graph.c
@@ -135,28 +135,27 @@ static void graph_calls(struct entrypoint *ep, int internal)
 			if (!insn->bb)
 				continue;
 			if (insn->opcode == OP_CALL &&
+			    insn->func->type == PSEUDO_SYM &&
 			    internal == !(insn->func->sym->ctype.modifiers & MOD_EXTERN)) {
 
 				/* Find the symbol for the callee's definition */
 				struct symbol * sym;
-				if (insn->func->type == PSEUDO_SYM) {
-					for (sym = insn->func->sym->ident->symbols;
-					     sym; sym = sym->next_id) {
-						if (sym->namespace & NS_SYMBOL && sym->ep)
-							break;
-					}
-
-					if (sym)
-						printf("bb%p -> bb%p"
-						       "[label=%d,line=%d,col=%d,op=call,style=bold,weight=30];\n",
-						       bb, sym->ep->entry->bb,
-						       insn->pos.line, insn->pos.line, insn->pos.pos);
-					else
-						printf("bb%p -> \"%s\" "
-						       "[label=%d,line=%d,col=%d,op=extern,style=dashed];\n",
-						       bb, show_pseudo(insn->func),
-						       insn->pos.line, insn->pos.line, insn->pos.pos);
+				for (sym = insn->func->sym->ident->symbols;
+				     sym; sym = sym->next_id) {
+					if (sym->namespace & NS_SYMBOL && sym->ep)
+						break;
 				}
+
+				if (sym)
+					printf("bb%p -> bb%p"
+					       "[label=%d,line=%d,col=%d,op=call,style=bold,weight=30];\n",
+					       bb, sym->ep->entry->bb,
+					       insn->pos.line, insn->pos.line, insn->pos.pos);
+				else
+					printf("bb%p -> \"%s\" "
+					       "[label=%d,line=%d,col=%d,op=extern,style=dashed];\n",
+					       bb, show_pseudo(insn->func),
+					       insn->pos.line, insn->pos.line, insn->pos.pos);
 			}
 		} END_FOR_EACH_PTR(insn);
 	} END_FOR_EACH_PTR(bb);

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 26/73] scheck: Fix slice() upper bit index calculation
  2026-11-09 20:31 Bart Van Assche
                   ` (24 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
                   ` (46 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Pass new - 1 instead of old - new - 1 as the upper bit index to
boolector_slice() in slice() so narrowing casts extract the low 'new' bits
of the bit-vector.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 scheck.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/scheck.c b/scheck.c
index bb052d97996d..2189f4138d81 100644
--- a/scheck.c
+++ b/scheck.c
@@ -107,9 +107,8 @@ static BoolectorNode *sext(Btor *btor, struct instruction *insn, BoolectorNode *
 
 static BoolectorNode *slice(Btor *btor, struct instruction *insn, BoolectorNode *s)
 {
-	int old = boolector_get_width(btor, s);
 	int new = insn->type->bit_size;
-	return boolector_slice(btor, s, old - new - 1, 0);
+	return boolector_slice(btor, s, new - 1, 0);
 }
 
 static void binary(Btor *btor, BoolectorSort s, struct instruction *insn)

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types
  2026-11-09 20:31 Bart Van Assche
                   ` (25 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
                   ` (45 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Fix LLVM IR generation crashes and limits in sparse-llvm.c:
- Dynamically allocate elem_types in sym_struct_type() instead of asserting
  at 64 members, and handle zero-sized array element types in
  sym_array_type().
- Pass instruction operand types instead of NULL to pseudo_to_value() in
  output_op_compare(), output_op_cbr(), and output_op_sel().
- Read val->label_symbol instead of val->symbol for EXPR_LABEL in
  output_op_setval().
- Add a regression test in validation/backend/struct-large-members.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 sparse-llvm.c                             | 23 +++----
 validation/backend/struct-large-members.c | 83 +++++++++++++++++++++++
 2 files changed, 92 insertions(+), 14 deletions(-)
 create mode 100644 validation/backend/struct-large-members.c

diff --git a/sparse-llvm.c b/sparse-llvm.c
index c422a13ce2a8..8f37bde1b98d 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -86,21 +86,17 @@ static LLVMTypeRef sym_array_type(struct symbol *sym)
 	struct symbol *base_type;
 
 	base_type = sym->ctype.base_type;
-	/* empty struct is undefined [6.7.2.1(8)] */
-	assert(base_type->bit_size > 0);
-
 	elem_type = symbol_type(base_type);
 	if (!elem_type)
 		return NULL;
 
-	return LLVMArrayType(elem_type, sym->bit_size / base_type->bit_size);
+	return LLVMArrayType(elem_type, base_type->bit_size > 0 ? sym->bit_size / base_type->bit_size : 0);
 }
 
-#define MAX_STRUCT_MEMBERS 64
-
 static LLVMTypeRef sym_struct_type(struct symbol *sym)
 {
-	LLVMTypeRef elem_types[MAX_STRUCT_MEMBERS];
+	unsigned n_memb = symbol_list_size(sym->symbol_list);
+	LLVMTypeRef *elem_types = calloc(n_memb, sizeof(LLVMTypeRef));
 	struct symbol *member;
 	char buffer[256];
 	LLVMTypeRef ret;
@@ -114,14 +110,13 @@ static LLVMTypeRef sym_struct_type(struct symbol *sym)
 	FOR_EACH_PTR(sym->symbol_list, member) {
 		LLVMTypeRef member_type;
 
-		assert(nr < MAX_STRUCT_MEMBERS);
-
 		member_type = symbol_type(member);
 
 		elem_types[nr++] = member_type; 
 	} END_FOR_EACH_PTR(member);
 
 	LLVMStructSetBody(ret, elem_types, nr, 0 /* packed? */); 
+	free(elem_types);
 	return ret;
 }
 
@@ -663,11 +658,11 @@ static void output_op_compare(struct function *fn, struct instruction *insn)
 	LLVMValueRef lhs, rhs, target;
 	char target_name[64];
 
-	lhs = pseudo_to_value(fn, NULL, insn->src1);
+	lhs = pseudo_to_value(fn, insn->itype, insn->src1);
 	if (insn->src2->type == PSEUDO_VAL)
 		rhs = constant_value(insn->src2->value, LLVMTypeOf(lhs));
 	else
-		rhs = pseudo_to_value(fn, NULL, insn->src2);
+		rhs = pseudo_to_value(fn, insn->itype, insn->src2);
 	if (!rhs)
 		rhs = LLVMGetUndef(symbol_type(insn->type));
 
@@ -785,7 +780,7 @@ static LLVMValueRef bool_value(struct function *fn, LLVMValueRef value)
 static void output_op_cbr(struct function *fn, struct instruction *br)
 {
 	LLVMValueRef cond = bool_value(fn,
-			pseudo_to_value(fn, NULL, br->cond));
+			pseudo_to_value(fn, br->type ? br->type : &int_ctype, br->cond));
 
 	LLVMBuildCondBr(fn->builder, cond,
 			br->bb_true->priv,
@@ -802,7 +797,7 @@ static void output_op_sel(struct function *fn, struct instruction *insn)
 	LLVMValueRef target, src1, src2, src3;
 	char name[MAX_PSEUDO_NAME];
 
-	src1 = bool_value(fn, pseudo_to_value(fn, NULL, insn->src1));
+	src1 = bool_value(fn, pseudo_to_value(fn, insn->itype ? insn->itype : insn->type, insn->src1));
 	src2 = get_operand(fn, insn->type, insn->src2);
 	src3 = get_operand(fn, insn->type, insn->src3);
 
@@ -1001,7 +996,7 @@ static void output_op_setval(struct function *fn, struct instruction *insn)
 
 	switch (val->type) {
 	case EXPR_LABEL:
-		target = LLVMBlockAddress(fn->fn, val->symbol->bb_target->priv);
+		target = LLVMBlockAddress(fn->fn, val->label_symbol->bb_target->priv);
 		break;
 	default:
 		assert(0);
diff --git a/validation/backend/struct-large-members.c b/validation/backend/struct-large-members.c
new file mode 100644
index 000000000000..65f382194d5d
--- /dev/null
+++ b/validation/backend/struct-large-members.c
@@ -0,0 +1,83 @@
+struct big_struct {
+	int f0;
+	int f1;
+	int f2;
+	int f3;
+	int f4;
+	int f5;
+	int f6;
+	int f7;
+	int f8;
+	int f9;
+	int f10;
+	int f11;
+	int f12;
+	int f13;
+	int f14;
+	int f15;
+	int f16;
+	int f17;
+	int f18;
+	int f19;
+	int f20;
+	int f21;
+	int f22;
+	int f23;
+	int f24;
+	int f25;
+	int f26;
+	int f27;
+	int f28;
+	int f29;
+	int f30;
+	int f31;
+	int f32;
+	int f33;
+	int f34;
+	int f35;
+	int f36;
+	int f37;
+	int f38;
+	int f39;
+	int f40;
+	int f41;
+	int f42;
+	int f43;
+	int f44;
+	int f45;
+	int f46;
+	int f47;
+	int f48;
+	int f49;
+	int f50;
+	int f51;
+	int f52;
+	int f53;
+	int f54;
+	int f55;
+	int f56;
+	int f57;
+	int f58;
+	int f59;
+	int f60;
+	int f61;
+	int f62;
+	int f63;
+	int f64;
+	int f65;
+	int f66;
+	int f67;
+	int f68;
+	int f69;
+};
+
+static struct big_struct s;
+static int get_f69(void)
+{
+	return s.f69;
+}
+
+/*
+ * check-name: struct-large-members
+ * check-command: sparsec -c $file -o tmp.o
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them
  2026-11-09 20:31 Bart Van Assche
                   ` (26 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
                   ` (44 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Duplicate the EXPR_GENERIC expression, its control expression, its
default expression, and each type-expression map entry during function
inlining instead of evaluating the generic selection prematurely.

Prevent copy_symbol() from traversing already-evaluated expressions
where compound literals (such as &(size_t){ 0 }) produce anonymous local
symbols without replacement symbols and trigger false 'unreplaced symbol
<noident>' warnings.

Allow evaluate_statement() to evaluate the selected branch of the copied
generic expression when the expanded inlined function body is evaluated,
and add validation/inline-generic.c.

Fixes: eb6779f6f621 ("generic: fix missing inlining of generic expression")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 inline.c                    | 18 ++++++++++++++----
 validation/inline-generic.c | 25 +++++++++++++++++++++++++
 2 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/inline.c b/inline.c
index 1948bea52ffb..d94fba807d19 100644
--- a/inline.c
+++ b/inline.c
@@ -345,13 +345,23 @@ static struct expression * copy_expression(struct expression *expr)
 		}
 		break;
 	}
-	case EXPR_GENERIC:
+	case EXPR_GENERIC: {
+		struct type_expression *map, **p;
 		expr = dup_expression(expr);
 		expr->control = copy_expression(expr->control);
-		if (!evaluate_expression(expr))
-			return NULL;
-		expr = copy_expression(expr);
+		expr->def = copy_expression(expr->def);
+		p = &expr->map;
+		for (map = expr->map; map; map = map->next) {
+			struct type_expression *new_map = __alloc_type_expression(0);
+			*new_map = *map;
+			new_map->type = copy_type(map->type);
+			new_map->expr = copy_expression(map->expr);
+			*p = new_map;
+			p = &new_map->next;
+		}
+		*p = NULL;
 		break;
+	}
 
 	default:
 		warning(expr->pos, "trying to copy expression type %d", expr->type);
diff --git a/validation/inline-generic.c b/validation/inline-generic.c
index 1f05c07904b5..3bf97eb69c53 100644
--- a/validation/inline-generic.c
+++ b/validation/inline-generic.c
@@ -5,6 +5,31 @@ inline unsigned f(void) { return e(_Generic(b, int: c())); }
 static int g(void) { return f(); }
 static int h(void) { return f(); }
 
+inline void *cl(void)
+{
+	return _Generic((void *)0,
+			void *: &(int){ 0 },
+			default: (void *)0);
+}
+
+static void test_cl(void)
+{
+	cl();
+}
+
+static inline int inl_gen_vla(int n, void *p)
+{
+	return _Generic((int (*)[n])p,
+			int (*)[n]: (int)sizeof(int [n]),
+			default: 0);
+}
+
+static int test_gen_vla(int x, int y, void *p)
+{
+	return inl_gen_vla(x, p) + inl_gen_vla(y, p);
+}
+
 /*
  * check-name: inline-generic
+ * check-command: sparse -Wno-vla $file
  */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning()
  2026-11-09 20:31 Bart Van Assche
                   ` (27 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
                   ` (43 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Introduce a separate local pointer variable warn_fmt for the format
string passed to do_warn(). Avoid reassigning the function parameter fmt
before va_start(args, fmt) to maintain standard variadic argument
initialization semantics across ABIs and compilers.

Fixes: 8fa23ccfe298 ("Add "info()" for informational messages...")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 lib.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lib.c b/lib.c
index ff8efb157f39..6670e3b46657 100644
--- a/lib.c
+++ b/lib.c
@@ -182,6 +182,7 @@ void warning(struct position pos, const char * fmt, ...)
 {
 	va_list args;
 	static unsigned int warnings = 0;
+	const char *warn_fmt = fmt;
 
 	if (Wsparse_error) {
 		va_start(args, fmt);
@@ -200,12 +201,12 @@ void warning(struct position pos, const char * fmt, ...)
 		show_info = 0;
 		if (once)
 			return;
-		fmt = "too many warnings";
+		warn_fmt = "too many warnings";
 		once = 1;
 	}
 
 	va_start(args, fmt);
-	do_warn("warning: ", pos, fmt, args);
+	do_warn("warning: ", pos, warn_fmt, args);
 	va_end(args);
 }
 

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison
  2026-11-09 20:31 Bart Van Assche
                   ` (28 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
                   ` (42 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Cast pointer operands to uintptr_t before comparing them with relational
operators (<) in cse.c, simplify.c, and storage.c.

Avoid undefined behavior under ISO C when comparing pointers that do not
point to elements of the same object, and prevent AddressSanitizer
invalid-pointer-pair aborts under detect_invalid_pointer_pairs=2.

Fixes: fe8df476cce2 ("Add initial CSE pass")
Fixes: 37a29afc774f ("Start splitting out generic "storage"...")
Fixes: 84f84262a122 ("canon: simplify calculation of canonical order")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 cse.c      | 17 +++++++++--------
 simplify.c |  3 ++-
 storage.c  |  5 +++--
 3 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/cse.c b/cse.c
index b59581814ab7..b7d0314a0400 100644
--- a/cse.c
+++ b/cse.c
@@ -10,6 +10,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <assert.h>
 
 #include "parse.h"
@@ -28,9 +29,9 @@ static int phi_compare(pseudo_t phi1, pseudo_t phi2)
 	const struct instruction *def2 = phi2->def;
 
 	if (def1->src1 != def2->src1)
-		return def1->src1 < def2->src1 ? -1 : 1;
+		return (uintptr_t)def1->src1 < (uintptr_t)def2->src1 ? -1 : 1;
 	if (def1->bb != def2->bb)
-		return def1->bb < def2->bb ? -1 : 1;
+		return (uintptr_t)def1->bb < (uintptr_t)def2->bb ? -1 : 1;
 	return 0;
 }
 
@@ -184,7 +185,7 @@ static int insn_compare(const void *_i1, const void *_i2)
 
 	case OP_SEL:
 		if (i1->src3 != i2->src3)
-			return i1->src3 < i2->src3 ? -1 : 1;
+			return (uintptr_t)i1->src3 < (uintptr_t)i2->src3 ? -1 : 1;
 		/* Fall-through to binops */
 
 	/* Binary arithmetic */
@@ -208,7 +209,7 @@ static int insn_compare(const void *_i1, const void *_i2)
 	case OP_FDIV:
 	case_binops:
 		if (i1->src2 != i2->src2)
-			return i1->src2 < i2->src2 ? -1 : 1;
+			return (uintptr_t)i1->src2 < (uintptr_t)i2->src2 ? -1 : 1;
 		/* Fall through to unops */
 
 	/* Unary */
@@ -216,17 +217,17 @@ static int insn_compare(const void *_i1, const void *_i2)
 	case OP_FNEG:
 	case OP_SYMADDR:
 		if (i1->src1 != i2->src1)
-			return i1->src1 < i2->src1 ? -1 : 1;
+			return (uintptr_t)i1->src1 < (uintptr_t)i2->src1 ? -1 : 1;
 		break;
 
 	case OP_LABEL:
 		if (i1->bb_true != i2->bb_true)
-			return i1->bb_true < i2->bb_true ? -1 : 1;
+			return (uintptr_t)i1->bb_true < (uintptr_t)i2->bb_true ? -1 : 1;
 		break;
 
 	case OP_SETVAL:
 		if (i1->val != i2->val)
-			return i1->val < i2->val ? -1 : 1;
+			return (uintptr_t)i1->val < (uintptr_t)i2->val ? -1 : 1;
 		break;
 
 	case OP_SETFVAL:
@@ -244,7 +245,7 @@ static int insn_compare(const void *_i1, const void *_i2)
 	case OP_PTRCAST:
 	case OP_UTPTR: case OP_PTRTU:
 		if (i1->src != i2->src)
-			return i1->src < i2->src ? -1 : 1;
+			return (uintptr_t)i1->src < (uintptr_t)i2->src ? -1 : 1;
 
 		// Note: if it can be guaranted that identical ->src
 		// implies identical orig_type->bit_size, then this
diff --git a/simplify.c b/simplify.c
index 2ddaf1c619a8..88f261da297e 100644
--- a/simplify.c
+++ b/simplify.c
@@ -40,6 +40,7 @@
 // * `SHIFT(x, S)` is used for `LSR(x, S)` or `SHL(x, S)`.
 
 #include <assert.h>
+#include <stdint.h>
 
 #include "parse.h"
 #include "expression.h"
@@ -1814,7 +1815,7 @@ static int canonical_order(pseudo_t p1, pseudo_t p2)
 		return 0;
 	switch (t1) {
 	case PSEUDO_SYM:
-		return p1->sym <= p2->sym;
+		return (uintptr_t)p1->sym <= (uintptr_t)p2->sym;
 	case PSEUDO_REG:
 	case PSEUDO_ARG:
 		return p1->nr <= p2->nr;
diff --git a/storage.c b/storage.c
index 160d424ee1f6..1dafa08dccf0 100644
--- a/storage.c
+++ b/storage.c
@@ -8,6 +8,7 @@
  */
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdint.h>
 #include <assert.h>
 
 #include "symbol.h"
@@ -33,7 +34,7 @@ static int hash_list_cmp(const void *_a, const void *_b)
 	const struct storage_hash *a = _a;
 	const struct storage_hash *b = _b;
 	if (a->pseudo != b->pseudo)
-		return a->pseudo < b->pseudo ? -1 : 1;
+		return (uintptr_t)a->pseudo < (uintptr_t)b->pseudo ? -1 : 1;
 	return 0;
 }
 
@@ -118,7 +119,7 @@ static int storage_hash_cmp(const void *_a, const void *_b)
 	struct storage *bb = b->storage;
 
 	if (a->bb != b->bb)
-		return a->bb < b->bb ? -1 : 1;
+		return (uintptr_t)a->bb < (uintptr_t)b->bb ? -1 : 1;
 	if (a->inout != b->inout)
 		return a->inout < b->inout ? -1 : 1;
 	if (aa->type != bb->type)

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 31/73] context: Add "conditional context increment" model
  2026-11-09 20:31 Bart Van Assche
                   ` (29 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
                   ` (41 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

From: Linus Torvalds <torvalds@linux-foundation.org>

Use a negative output parameter to the "context" attribute to indicate
that the context is increased conditionally.

Support "trylock()" kinds of functions that return success if the lock
operation succeeded, matching Linux kernel commit 4a557a5d1a61 ("sparse:
introduce conditional lock acquire function attribute") for
refcount_dec_and_lock() and friends.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[ bvanassche: Rewrite commit message in imperative mood, support multiple
  context attributes in linearize_call_expression(), validate conditional
  context function definitions in check_context(), and extend
  validation/context.c ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 linearize.c          |  42 ++++++++-
 sparse.c             | 202 +++++++++++++++++++++++++++++++++++++++++--
 validation/context.c | 101 +++++++++++++++++++---
 3 files changed, 318 insertions(+), 27 deletions(-)

diff --git a/linearize.c b/linearize.c
index c6ec3703b149..24b402622b83 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1539,19 +1539,20 @@ static pseudo_t linearize_call_expression(struct entrypoint *ep, struct expressi
 	add_one_insn(ep, insn);
 
 	if (ctype) {
+		struct basic_block *post_call = NULL;
+
 		FOR_EACH_PTR(ctype->contexts, context) {
 			int in = context->in;
 			int out = context->out;
 			int check = 0;
 			int context_diff;
+
+			if (out < 0)
+				continue;
 			if (in < 0) {
 				check = 1;
 				in = 0;
 			}
-			if (out < 0) {
-				check = 0;
-				out = 0;
-			}
 			context_diff = out - in;
 			if (check || context_diff) {
 				insn = alloc_instruction(OP_CONTEXT, 0);
@@ -1562,6 +1563,39 @@ static pseudo_t linearize_call_expression(struct entrypoint *ep, struct expressi
 			}
 		} END_FOR_EACH_PTR(context);
 
+		FOR_EACH_PTR(ctype->contexts, context) {
+			int in = context->in;
+			int out = context->out;
+
+			if (out >= 0)
+				continue;
+			if (retval == VOID) {
+				warning(expr->pos, "nonsensical conditional output context with no condition");
+				continue;
+			}
+			if (in) {
+				warning(expr->pos, "nonsensical conditional input context");
+				continue;
+			}
+			if (!post_call) {
+				struct basic_block *set_context;
+				post_call = alloc_basic_block(ep, expr->pos);
+				set_context = alloc_basic_block(ep, expr->pos);
+				add_branch(ep, retval, set_context, post_call);
+				set_activeblock(ep, set_context);
+			}
+			insn = alloc_instruction(OP_CONTEXT, 0);
+			insn->increment = -out;
+			insn->check = 0;
+			insn->context_expr = context->context;
+			add_one_insn(ep, insn);
+		} END_FOR_EACH_PTR(context);
+
+		if (post_call) {
+			add_goto(ep, post_call);
+			set_activeblock(ep, post_call);
+		}
+
 		if (ctype->modifiers & MOD_NORETURN)
 			add_unreachable(ep);
 	}
diff --git a/sparse.c b/sparse.c
index 883a5edec5b1..19c6ba46e012 100644
--- a/sparse.c
+++ b/sparse.c
@@ -79,9 +79,168 @@ static int imbalance(struct entrypoint *ep, struct basic_block *bb, int entry, i
 	return -1;
 }
 
-static int check_bb_context(struct entrypoint *ep, struct basic_block *bb, int entry, int exit);
+struct cond_context_state {
+	int cond_out;
+	bool saw_base_out;
+	bool saw_cond_out;
+	bool all_zero;
+	bool all_nonzero;
+	struct basic_block *any_exit_bb;
+};
+
+static bool is_pure_return_bb(struct basic_block *bb)
+{
+	struct instruction *insn, *last = NULL;
+
+	if (!bb)
+		return false;
+	FOR_EACH_PTR(bb->insns, insn) {
+		if (!insn->bb)
+			continue;
+		if (insn->opcode == OP_CONTEXT || insn->opcode == OP_CALL ||
+		    insn->opcode == OP_INLINED_CALL || insn->opcode == OP_ASM)
+			return false;
+		last = insn;
+	} END_FOR_EACH_PTR(insn);
+	return last && last->opcode == OP_RET;
+}
+
+static int known_cond_on_edge(pseudo_t cond, struct basic_block *from, struct basic_block *to)
+{
+	int hops;
+
+	for (hops = 0; from && to && hops < 4; hops++) {
+		struct instruction *br = last_instruction(from->insns);
+		if (br && br->opcode == OP_CBR && br->cond == cond &&
+		    br->bb_true != br->bb_false) {
+			if (to == br->bb_true)
+				return 1;
+			if (to == br->bb_false)
+				return 0;
+		}
+		if (ptr_list_size((struct ptr_list *)from->parents) != 1)
+			break;
+		to = from;
+		from = first_basic_block(from->parents);
+		if (from == to)
+			break;
+	}
+	return -1;
+}
+
+static int return_truth_on_edge(pseudo_t val, struct basic_block *from,
+				struct basic_block *ret_bb, int depth)
+{
+	struct instruction *def;
+	int known;
+
+	if (!val || val == VOID || depth > 8)
+		return -1;
+	if (val->type == PSEUDO_VAL)
+		return val->value != 0 ? 1 : 0;
+
+	known = known_cond_on_edge(val, from, ret_bb);
+	if (known >= 0)
+		return known;
+
+	if (val->type != PSEUDO_REG && val->type != PSEUDO_PHI)
+		return -1;
+	def = val->def;
+	if (!def)
+		return -1;
+
+	switch (def->opcode) {
+	case OP_PHI: {
+		struct basic_block *cur = from;
+		int hops;
+		for (hops = 0; cur && hops < 4; hops++) {
+			pseudo_t phi;
+			FOR_EACH_PTR(def->phi_list, phi) {
+				if (!phi || phi == VOID || !phi->def)
+					continue;
+				if (phi->def->bb == cur)
+					return return_truth_on_edge(phi->def->phi_src,
+								    cur, ret_bb, depth + 1);
+			} END_FOR_EACH_PTR(phi);
+			if (ptr_list_size((struct ptr_list *)cur->parents) != 1)
+				break;
+			cur = first_basic_block(cur->parents);
+		}
+		return -1;
+	}
+	case OP_PHISOURCE:
+		return return_truth_on_edge(def->phi_src, def->bb, ret_bb, depth + 1);
+	case OP_ZEXT:
+	case OP_SEXT:
+		if (def->orig_type && def->size >= def->orig_type->bit_size)
+			return return_truth_on_edge(def->src, from, ret_bb, depth + 1);
+		return -1;
+	case OP_SET_EQ:
+		if (def->src2 && def->src2->type == PSEUDO_VAL && def->src2->value == 0) {
+			int t = return_truth_on_edge(def->src1, from, ret_bb, depth + 1);
+			return t >= 0 ? !t : -1;
+		}
+		return -1;
+	case OP_SET_NE:
+		if (def->src2 && def->src2->type == PSEUDO_VAL && def->src2->value == 0)
+			return return_truth_on_edge(def->src1, from, ret_bb, depth + 1);
+		return -1;
+	case OP_SEL: {
+		int ct = return_truth_on_edge(def->cond, from, ret_bb, depth + 1);
+		int tt, ft;
+		if (ct == 1)
+			return return_truth_on_edge(def->src2, from, ret_bb, depth + 1);
+		if (ct == 0)
+			return return_truth_on_edge(def->src3, from, ret_bb, depth + 1);
+		tt = return_truth_on_edge(def->src2, from, ret_bb, depth + 1);
+		ft = return_truth_on_edge(def->src3, from, ret_bb, depth + 1);
+		return (tt >= 0 && tt == ft) ? tt : -1;
+	}
+	default:
+		return -1;
+	}
+}
+
+static int check_exit_edge(struct entrypoint *ep, struct basic_block *from,
+			   struct basic_block *ret_bb, struct instruction *ret_insn,
+			   int entry, int exit, struct cond_context_state *cs)
+{
+	struct basic_block *diag_bb = from ? from : ret_bb;
+	int truth;
+
+	if (!cs || !cs->cond_out)
+		return entry != exit ? imbalance(ep, ret_bb, entry, exit, "wrong count at exit") : 0;
+
+	if (!ret_insn->src || ret_insn->src == VOID)
+		return imbalance(ep, diag_bb, entry, exit + cs->cond_out, "wrong count at exit");
+
+	truth = return_truth_on_edge(ret_insn->src, from, ret_bb, 0);
+	if (!cs->any_exit_bb)
+		cs->any_exit_bb = diag_bb;
+	if (entry == exit)
+		cs->saw_base_out = true;
+	if (entry == exit + cs->cond_out)
+		cs->saw_cond_out = true;
+	if (truth != 0)
+		cs->all_zero = false;
+	if (truth != 1)
+		cs->all_nonzero = false;
+
+	if (truth == 0)
+		return entry != exit ? imbalance(ep, diag_bb, entry, exit, "wrong count at exit") : 0;
+	if (truth == 1)
+		return entry != exit + cs->cond_out ?
+			imbalance(ep, diag_bb, entry, exit + cs->cond_out, "wrong count at exit") : 0;
+	if (entry != exit && entry != exit + cs->cond_out)
+		return imbalance(ep, diag_bb, entry, exit, "wrong count at exit");
+	return 0;
+}
 
-static int check_children(struct entrypoint *ep, struct basic_block *bb, int entry, int exit)
+static int check_bb_context(struct entrypoint *ep, struct basic_block *bb,
+			    int entry, int exit, struct cond_context_state *cs);
+
+static int check_children(struct entrypoint *ep, struct basic_block *bb,
+			  int entry, int exit, struct cond_context_state *cs)
 {
 	struct instruction *insn;
 	struct basic_block *child;
@@ -90,16 +249,23 @@ static int check_children(struct entrypoint *ep, struct basic_block *bb, int ent
 	if (!insn)
 		return 0;
 	if (insn->opcode == OP_RET)
-		return entry != exit ? imbalance(ep, bb, entry, exit, "wrong count at exit") : 0;
+		return check_exit_edge(ep, NULL, bb, insn, entry, exit, cs);
 
 	FOR_EACH_PTR(bb->children, child) {
-		if (check_bb_context(ep, child, entry, exit))
+		if (cs && cs->cond_out && is_pure_return_bb(child)) {
+			if (check_exit_edge(ep, bb, child, last_instruction(child->insns),
+					    entry, exit, cs))
+				return -1;
+			continue;
+		}
+		if (check_bb_context(ep, child, entry, exit, cs))
 			return -1;
 	} END_FOR_EACH_PTR(child);
 	return 0;
 }
 
-static int check_bb_context(struct entrypoint *ep, struct basic_block *bb, int entry, int exit)
+static int check_bb_context(struct entrypoint *ep, struct basic_block *bb,
+			    int entry, int exit, struct cond_context_state *cs)
 {
 	if (!bb)
 		return 0;
@@ -115,7 +281,7 @@ static int check_bb_context(struct entrypoint *ep, struct basic_block *bb, int e
 	if (entry < 0)
 		return imbalance(ep, bb, entry, exit, "unexpected unlock");
 
-	return check_children(ep, bb, entry, exit);
+	return check_children(ep, bb, entry, exit, cs);
 }
 
 static void check_cast_instruction(struct instruction *insn)
@@ -260,11 +426,29 @@ static void check_context(struct entrypoint *ep)
 
 	check_instructions(ep);
 
+	struct cond_context_state cs = {
+		.all_zero = true,
+		.all_nonzero = true,
+	};
+
 	FOR_EACH_PTR(sym->ctype.contexts, context) {
-		in_context += context->in;
-		out_context += context->out;
+		int in = context->in;
+		int out = context->out;
+
+		if (in > 0)
+			in_context += in;
+		if (out > 0)
+			out_context += out;
+		else if (out < 0)
+			cs.cond_out += -out;
 	} END_FOR_EACH_PTR(context);
-	check_bb_context(ep, ep->entry->bb, in_context, out_context);
+	if (!check_bb_context(ep, ep->entry->bb, in_context, out_context, &cs) &&
+	    cs.cond_out > 0 && cs.any_exit_bb) {
+		if ((!cs.all_zero && !cs.saw_cond_out) ||
+		    (!cs.all_nonzero && !cs.saw_base_out))
+			imbalance(ep, cs.any_exit_bb, in_context,
+				  out_context + cs.cond_out, "wrong count at exit");
+	}
 }
 
 /* list_compound_symbol - symbol info for arrays, structures, unions */
diff --git a/validation/context.c b/validation/context.c
index b9500dc7508d..3330482c2195 100644
--- a/validation/context.c
+++ b/validation/context.c
@@ -10,6 +10,10 @@ static void r(void) __attribute__((context(1,0)))
 	__context__(-1);
 }
 
+// Negative output means "conditional positive output"
+extern int cond_get(void) __attribute((context(0,-1)));
+extern void nonsensical_cond_get(void) __attribute((context(0,-1)));
+
 extern int _ca(int fail);
 #define ca(fail) __cond_lock(_ca(fail))
 
@@ -19,6 +23,17 @@ static void good_paired1(void)
 	r();
 }
 
+static void good_conditional(void)
+{
+	if (cond_get())
+		r();
+}
+
+static void nonsensical_conditional(void)
+{
+	nonsensical_cond_get();
+}
+
 static void good_paired2(void)
 {
 	a();
@@ -314,23 +329,81 @@ static void warn_cond_lock1(void)
         condition2 = 1; /* do stuff */
     r();
 }
+
+static int good_trylock_def(int ok) __attribute__((context(0, -1)))
+{
+	if (ok) {
+		__context__(1);
+		return 1;
+	}
+	return 0;
+}
+
+static int good_trylock_wrap(void) __attribute__((context(0, -1)))
+{
+	return cond_get();
+}
+
+static int good_trylock_stub(void) __attribute__((context(0, -1)))
+{
+	return 0;
+}
+
+extern int multi_cond_get(void)
+	__attribute__((context(lock_a, 0, -1)))
+	__attribute__((context(lock_b, 0, 1)));
+static void release_a(void) __attribute__((context(lock_a, 1, 0)))
+{
+	__context__(lock_a, -1);
+}
+static void release_b(void) __attribute__((context(lock_b, 1, 0)))
+{
+	__context__(lock_b, -1);
+}
+static void good_multi_cond(void)
+{
+	if (multi_cond_get())
+		release_a();
+	release_b();
+}
+
+static int warn_trylock_missing(void) __attribute__((context(0, -1)))
+{
+	return 1;
+}
+
+static int warn_trylock_inverted(void) __attribute__((context(0, -1)))
+{
+	__context__(1);
+	return 0;
+}
+
+static int warn_trylock_uncond(int x) __attribute__((context(0, -1)))
+{
+	__context__(1);
+	return x;
+}
 /*
  * check-name: Check -Wcontext
  *
  * check-error-start
-context.c:69:13: warning: context imbalance in 'warn_lock1' - wrong count at exit
-context.c:74:13: warning: context imbalance in 'warn_lock2' - wrong count at exit
-context.c:81:13: warning: context imbalance in 'warn_lock3' - wrong count at exit
-context.c:88:13: warning: context imbalance in 'warn_unlock1' - unexpected unlock
-context.c:93:13: warning: context imbalance in 'warn_unlock2' - unexpected unlock
-context.c:131:12: warning: context imbalance in 'warn_if1' - wrong count at exit
-context.c:140:12: warning: context imbalance in 'warn_if2' - different lock contexts for basic block
-context.c:202:9: warning: context imbalance in 'warn_while1' - different lock contexts for basic block
-context.c:210:17: warning: context imbalance in 'warn_while2' - unexpected unlock
-context.c:216:9: warning: context imbalance in 'warn_while3' - wrong count at exit
-context.c:274:13: warning: context imbalance in 'warn_goto1' - wrong count at exit
-context.c:283:13: warning: context imbalance in 'warn_goto2' - wrong count at exit
-context.c:300:5: warning: context imbalance in 'warn_goto3' - different lock contexts for basic block
-context.c:315:5: warning: context imbalance in 'warn_cond_lock1' - different lock contexts for basic block
+context.c:34:29: warning: nonsensical conditional output context with no condition
+context.c:84:13: warning: context imbalance in 'warn_lock1' - wrong count at exit
+context.c:89:13: warning: context imbalance in 'warn_lock2' - wrong count at exit
+context.c:96:13: warning: context imbalance in 'warn_lock3' - wrong count at exit
+context.c:103:13: warning: context imbalance in 'warn_unlock1' - unexpected unlock
+context.c:108:13: warning: context imbalance in 'warn_unlock2' - unexpected unlock
+context.c:146:12: warning: context imbalance in 'warn_if1' - wrong count at exit
+context.c:155:12: warning: context imbalance in 'warn_if2' - different lock contexts for basic block
+context.c:217:9: warning: context imbalance in 'warn_while1' - different lock contexts for basic block
+context.c:225:17: warning: context imbalance in 'warn_while2' - unexpected unlock
+context.c:231:9: warning: context imbalance in 'warn_while3' - wrong count at exit
+context.c:289:13: warning: context imbalance in 'warn_goto1' - wrong count at exit
+context.c:298:13: warning: context imbalance in 'warn_goto2' - wrong count at exit
+context.c:315:5: warning: context imbalance in 'warn_goto3' - different lock contexts for basic block
+context.c:330:5: warning: context imbalance in 'warn_cond_lock1' - different lock contexts for basic block
+context.c:370:12: warning: context imbalance in 'warn_trylock_missing' - wrong count at exit
+context.c:375:12: warning: context imbalance in 'warn_trylock_inverted' - wrong count at exit
+context.c:381:12: warning: context imbalance in 'warn_trylock_uncond' - wrong count at exit
  * check-error-end
  */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 32/73] expand: Simplify signed constants early
  2026-11-09 20:31 Bart Van Assche
                   ` (30 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
                   ` (40 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

From: Linus Torvalds <torvalds@linux-foundation.org>

Make constant simplification for trivial unary preops on integer
constants ('-5', '+5', '~0') available at type evaluation time via
early_simplify_preop().

Fold unary preop expressions such as '-5' and '~0' before bitwise type
evaluation so that all-bits-set constants such as -1 or ~0 are
recognized as special cases.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[ bvanassche: Combine the two commits from Linus Torvalds ("simplify
  signed constants early" and "Fix early constant simplification"),
  rewrite the commit message in the imperative mood, include simplify.h in
  expand.c to fix make selfcheck, and guard bit_size in
  early_simplify_preop() ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c | 14 +++++++++++++-
 expand.c   | 31 +++++++++++++++++++++++++------
 simplify.h |  1 +
 3 files changed, 39 insertions(+), 7 deletions(-)

diff --git a/evaluate.c b/evaluate.c
index 0e4e64d33192..23f87c8dff5b 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -35,6 +35,7 @@
 #include <limits.h>
 
 #include "evaluate.h"
+#include "simplify.h"
 #include "lib.h"
 #include "allocate.h"
 #include "bits.h"
@@ -1944,10 +1945,21 @@ Normal:
 	} else {
 		return bad_expr_type(expr);
 	}
-	if (expr->op == '+')
+	if (expr->op == '+') {
 		*expr = *expr->unop;
+		expr->flags = flags;
+		expr->ctype = ctype;
+		return ctype;
+	}
 	expr->flags = flags;
 	expr->ctype = ctype;
+
+	/*
+	 * We simplify sign ops of constants early, turning
+	 * a constant with a sign into just a plain constant
+	 * rather than a preop.
+	 */
+	early_simplify_preop(expr);
 	return ctype;
 Restr:
 	if (restricted_unop(expr->op, &ctype))
diff --git a/expand.c b/expand.c
index 208ce65a2442..267aaf00e9ce 100644
--- a/expand.c
+++ b/expand.c
@@ -44,6 +44,7 @@
 #include "expression.h"
 #include "evaluate.h"
 #include "expand.h"
+#include "simplify.h"
 
 
 static int expand_expression(struct expression *);
@@ -817,13 +818,24 @@ static int expand_dereference(struct expression *expr)
 	return UNSAFE;
 }
 
-static int simplify_preop(struct expression *expr)
+/*
+ * We want to do certain trivial constant simplifications
+ * early at the type evaluation stage, because those can
+ * depend on specific constants (ie ~0 is ok for restricted
+ * types).
+ *
+ * Return >0 for successful simplification, 0 for none,
+ * and <0 for "not simplified due to overflow".
+ */
+int early_simplify_preop(struct expression *expr)
 {
 	struct expression *op = expr->unop;
 	unsigned long long v, mask;
 
 	if (op->type != EXPR_VALUE)
 		return 0;
+	if (!expr->ctype || expr->ctype->bit_size <= 0 || expr->ctype->bit_size > 64)
+		return 0;
 
 	mask = sign_bit(expr->ctype->bit_size);
 	v = op->value;
@@ -831,7 +843,7 @@ static int simplify_preop(struct expression *expr)
 	case '+': break;
 	case '-':
 		if (v == mask && !(expr->ctype->ctype.modifiers & MOD_UNSIGNED))
-			goto Overflow;
+			return -1;
 		v = -v;
 		break;
 	case '!': v = !v; break;
@@ -843,11 +855,18 @@ static int simplify_preop(struct expression *expr)
 	expr->type = EXPR_VALUE;
 	expr->taint = op->taint;
 	return 1;
+}
 
-Overflow:
-	if (!conservative)
-		warning(expr->pos, "constant integer operation overflow");
-	return 0;
+static int simplify_preop(struct expression *expr)
+{
+	int simplified = early_simplify_preop(expr);
+
+	if (simplified < 0) {
+		if (!conservative)
+			warning(expr->pos, "constant integer operation overflow");
+		simplified = 0;
+	}
+	return simplified;
 }
 
 static int simplify_float_preop(struct expression *expr)
diff --git a/simplify.h b/simplify.h
index ed3dd9716a3e..15c887376445 100644
--- a/simplify.h
+++ b/simplify.h
@@ -4,6 +4,7 @@
 #include "linearize.h"
 
 int simplify_instruction(struct instruction *insn);
+int early_simplify_preop(struct expression *expr);
 
 int replace_with_pseudo(struct instruction *insn, pseudo_t pseudo);
 

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value
  2026-11-09 20:31 Bart Van Assche
                   ` (31 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
                   ` (39 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

From: Linus Torvalds <torvalds@linux-foundation.org>

Allow all-bits-set (~0) as a valid constant for bitwise restricted types
alongside zero, and suppress restricted value warnings on '~0' unless
-Wtypesign is enabled.

Avoid false-positive bitwise warnings on wait-flag masks and similar
bitwise types that use ~0 to represent all flags set.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[ bvanassche: Rewrite commit message in imperative mood, check bit widths
  and signedness in restricted_value(), and add
  validation/bitwise-all-bits-and-casts.c ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                              | 33 ++++++++++++++++++++++-
 validation/bitwise-all-bits-and-casts.c | 36 +++++++++++++++++++++++++
 2 files changed, 68 insertions(+), 1 deletion(-)
 create mode 100644 validation/bitwise-all-bits-and-casts.c

diff --git a/evaluate.c b/evaluate.c
index 23f87c8dff5b..f4d59d2679d6 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -425,12 +425,43 @@ static struct symbol *bad_expr_type(struct expression *expr)
 	return expr->ctype = &bad_ctype;
 }
 
+static bool is_unsigned_restricted_or_int(struct symbol *sym)
+{
+	while (sym) {
+		if (sym->type == SYM_NODE || sym->type == SYM_ENUM ||
+		    sym->type == SYM_RESTRICT || sym->type == SYM_FOULED ||
+		    sym->type == SYM_BITFIELD) {
+			if (sym->ctype.modifiers & MOD_UNSIGNED)
+				return true;
+			sym = sym->ctype.base_type;
+			continue;
+		}
+		break;
+	}
+	return sym && (sym->ctype.modifiers & MOD_UNSIGNED);
+}
+
 static int restricted_value(struct expression *v, struct symbol *type)
 {
 	if (v->type != EXPR_VALUE)
 		return 1;
-	if (v->value != 0)
+	if (v->value != 0) {
+		int vbits = v->ctype ? v->ctype->bit_size : 0;
+		int tbits = type ? type->bit_size : 0;
+		bool vsigned = v->ctype && !is_unsigned_restricted_or_int(v->ctype);
+		unsigned long long uval, tmask, vmask;
+
+		if (vbits <= 0 || tbits <= 0 || vbits > 64 || tbits > 64)
+			return 1;
+		uval = (unsigned long long)bits_extend(v->value, vbits, vsigned);
+		tmask = bits_mask(tbits);
+		vmask = bits_mask(vbits);
+		if (uval == tmask || uval == ~0ULL)
+			return 0;
+		if (vbits >= tbits && (v->value & vmask) == vmask)
+			return 0;
 		return 1;
+	}
 	return 0;
 }
 
diff --git a/validation/bitwise-all-bits-and-casts.c b/validation/bitwise-all-bits-and-casts.c
new file mode 100644
index 000000000000..c69041f80318
--- /dev/null
+++ b/validation/bitwise-all-bits-and-casts.c
@@ -0,0 +1,36 @@
+typedef unsigned long long __attribute__((bitwise)) be64;
+typedef unsigned int __attribute__((bitwise)) be32;
+typedef unsigned short __attribute__((bitwise)) be16;
+
+static be32 test_all_bits(void)
+{
+	be32 a = ~0;
+	be32 b = ~0U;
+	be32 c = -1;
+	be32 d = 0;
+	be32 e = +0;
+	be32 f = -0;
+
+	return a | b | c | d | e | f;
+}
+
+static be64 test_all_bits_64(void)
+{
+	be64 a = ~0;
+	be64 b = ~0ULL;
+	be64 c = -1;
+
+	return a | b | c;
+}
+
+static be16 test_narrow_all_bits(void)
+{
+	be16 a = ~0;
+	be16 b = 0xffff;
+	return a | b;
+}
+
+/*
+ * check-name: bitwise-all-bits-and-casts
+ * check-command: sparse -Wbitwise $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values
  2026-11-09 20:31 Bart Van Assche
                   ` (32 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
                   ` (38 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

From: Linus Torvalds <torvalds@linux-foundation.org>

Update restricted_binop() to check unrestricted values (0 and ~0) for
ordered comparisons just like equality comparisons, and warn when '<' or
'>' is used against 0 or ~0 because restricted types are unsigned and
those comparisons are always true or false.

Allow idiomatic bounds checks such as 'flags <= 0' or 'flags >= ~0' on
bitwise types while catching tautological comparisons.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[ bvanassche: Rewrite commit message in imperative mood, require unsigned
  types in can_order() without warning on '<' or '>', pass the target
  type to restricted_value(), and extend
  validation/bitwise-all-bits-and-casts.c ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                              | 25 ++++++++++++++++++++++---
 validation/bitwise-all-bits-and-casts.c | 18 ++++++++++++++++++
 2 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/evaluate.c b/evaluate.c
index f4d59d2679d6..23f9e0fcdc4a 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -465,7 +465,19 @@ static int restricted_value(struct expression *v, struct symbol *type)
 	return 0;
 }
 
-static int restricted_binop(int op, struct symbol *type)
+/*
+ * Bitwise types can do ordered comparisons with
+ * the unrestricted values, but not in general
+ */
+static bool can_order(struct symbol *type, struct expression *left, struct expression *right)
+{
+	if (!is_unsigned_restricted_or_int(type))
+		return false;
+	return !restricted_value(left, type) ||
+		!restricted_value(right, type);
+}
+
+static int restricted_binop(int op, struct symbol *type, struct expression *left, struct expression *right)
 {
 	switch (op) {
 		case '&':
@@ -478,6 +490,13 @@ static int restricted_binop(int op, struct symbol *type)
 		case '^':
 		case '?':
 			return 2;	/* keep fouled */
+		case '<':
+		case '>':
+		case SPECIAL_LTE:
+		case SPECIAL_GTE:
+			if (can_order(type, left, right))
+				return 3;
+			return 0;
 		case SPECIAL_EQUAL:
 		case SPECIAL_NOTEQUAL:
 			return 3;	/* warn if fouled */
@@ -530,7 +549,7 @@ static struct symbol *restricted_binop_type(int op,
 		ctype = rtype;
 
 	if (ctype) {
-		switch (restricted_binop(op, ctype)) {
+		switch (restricted_binop(op, ctype, left, right)) {
 		case 1:
 			if ((lclass ^ rclass) & TYPE_FOULED)
 				ctype = unfoul(ctype);
@@ -1364,7 +1383,7 @@ static int evaluate_assign_op(struct expression *expr)
 			return 0;
 		}
 		if (tclass & TYPE_RESTRICT) {
-			if (!restricted_binop(op, t)) {
+			if (!restricted_binop(op, t, expr->left, expr->right)) {
 				warning(expr->pos, "bad assignment (%s) to %s",
 					show_special(op), show_typename(t));
 				expr->right = cast_to(expr->right, target);
diff --git a/validation/bitwise-all-bits-and-casts.c b/validation/bitwise-all-bits-and-casts.c
index c69041f80318..08f2b4c43285 100644
--- a/validation/bitwise-all-bits-and-casts.c
+++ b/validation/bitwise-all-bits-and-casts.c
@@ -30,6 +30,24 @@ static be16 test_narrow_all_bits(void)
 	return a | b;
 }
 
+static int test_ordered_compare(be32 x, be16 y)
+{
+	int res = 0;
+	if (x > 0)
+		res++;
+	if (x <= 0)
+		res++;
+	if (0 < x)
+		res++;
+	if (0 >= x)
+		res++;
+	if (x < ~0)
+		res++;
+	if (y <= 0xffff)
+		res++;
+	return res;
+}
+
 /*
  * check-name: bitwise-all-bits-and-casts
  * check-command: sparse -Wbitwise $file

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast
  2026-11-09 20:31 Bart Van Assche
                   ` (33 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
                   ` (37 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

From: Linus Torvalds <torvalds@linux-foundation.org>

Allow a cast of 0 or ~0 to its own restricted type (such as
'(__le32) 0' or '(__le32) ~0') to remain an unrestricted constant in
restricted_value().

Allow 0 or ~0 cast to a restricted type to match another restricted type
even when the macro definition does not use '__force'.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[ bvanassche: Rewrite commit message in imperative mood, fix typo, reject
  narrowing non-zero casts in restricted_value(), and extend
  validation/bitwise-all-bits-and-casts.c ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                              | 17 +++++++++++++++++
 validation/bitwise-all-bits-and-casts.c | 18 ++++++++++++------
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/evaluate.c b/evaluate.c
index 23f9e0fcdc4a..4e8856aa6bcc 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -443,6 +443,23 @@ static bool is_unsigned_restricted_or_int(struct symbol *sym)
 
 static int restricted_value(struct expression *v, struct symbol *type)
 {
+	if (v->type == EXPR_CAST) {
+		struct symbol *ctype;
+		int cclass = classify_type(v->ctype, &ctype);
+		struct expression *leaf = v->cast_expression;
+
+		if (!is_int(cclass))
+			return 1;
+		if (restricted_value(v->cast_expression, ctype))
+			return 1;
+		while (leaf && leaf->type == EXPR_CAST)
+			leaf = leaf->cast_expression;
+		if (leaf && leaf->type == EXPR_VALUE && leaf->value == 0)
+			return 0;
+		if (ctype->bit_size < type->bit_size && is_unsigned_restricted_or_int(ctype))
+			return 1;
+		return 0;
+	}
 	if (v->type != EXPR_VALUE)
 		return 1;
 	if (v->value != 0) {
diff --git a/validation/bitwise-all-bits-and-casts.c b/validation/bitwise-all-bits-and-casts.c
index 08f2b4c43285..01c8d84fcd67 100644
--- a/validation/bitwise-all-bits-and-casts.c
+++ b/validation/bitwise-all-bits-and-casts.c
@@ -10,8 +10,10 @@ static be32 test_all_bits(void)
 	be32 d = 0;
 	be32 e = +0;
 	be32 f = -0;
+	be32 g = (be32)0;
+	be32 i = (be32)~0;
 
-	return a | b | c | d | e | f;
+	return a | b | c | d | e | f | g | i;
 }
 
 static be64 test_all_bits_64(void)
@@ -19,15 +21,19 @@ static be64 test_all_bits_64(void)
 	be64 a = ~0;
 	be64 b = ~0ULL;
 	be64 c = -1;
+	be64 d = (be64)0;
+	be64 f = (be64)~0;
 
-	return a | b | c;
+	return a | b | c | d | f;
 }
 
-static be16 test_narrow_all_bits(void)
+static be16 test_cross_cast_zero(void)
 {
-	be16 a = ~0;
-	be16 b = 0xffff;
-	return a | b;
+	be16 k = (be32)0;
+	be16 m = (be32)~0;
+	be16 o = (unsigned short)~0;
+
+	return k | m | o;
 }
 
 static int test_ordered_compare(be32 x, be16 y)

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types
  2026-11-09 20:31 Bart Van Assche
                   ` (34 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
                   ` (36 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Al Viro, Bart Van Assche

From: Linus Torvalds <torvalds@linux-foundation.org>

Preserve MOD_SPECIFIER on 'bitwise' (SYM_RESTRICT) types in
declaration_specifiers() so that signedness modifiers such as
MOD_UNSIGNED are not stripped from the restricted type.

Prevent clearing MOD_SPECIFIER from dropping MOD_UNSIGNED on bitwise
typedefs (such as 'typedef unsigned int __attribute__((bitwise)) le32;')
so ordered comparisons like '(le32)-1 <= (le32)0' are not evaluated as
signed comparisons.

Cc: Al Viro <viro@ZenIV.linux.org.uk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[ bvanassche: Rewrite commit message in imperative mood, pass
  expr->cast_expression to is_promoted() in evaluate_type_information(),
  and suppress redundant 'unsigned' modifier output for SYM_RESTRICT in
  show-parse.c ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 Documentation/types.rst | 2 +-
 evaluate.c              | 2 +-
 parse.c                 | 1 -
 show-parse.c            | 3 ++-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Documentation/types.rst b/Documentation/types.rst
index 974f9861afef..dd5bb2107a7b 100644
--- a/Documentation/types.rst
+++ b/Documentation/types.rst
@@ -125,7 +125,7 @@ SYM_RESTRICT
 Used for bitwise types (aka 'restricted' types):
   * .ctype.base_type points to the underlying type (integer)
   * .ctype.modifiers & .as are like for SYM_NODE and the modifiers
-    are inherited from the base type with MOD_SPECIFIER removed
+    are inherited from the base type
   * .ident is the typedef name (if any).
 
 SYM_FOULED
diff --git a/evaluate.c b/evaluate.c
index 4e8856aa6bcc..b84eedbe4d33 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2290,7 +2290,7 @@ static struct symbol *evaluate_type_information(struct expression *expr)
 		 * promoted - check that here
 		 */
 		if (is_restricted_type(sym)) {
-			if (sym->bit_size < bits_in_int && is_promoted(expr))
+			if (sym->bit_size < bits_in_int && is_promoted(expr->cast_expression))
 				sym = &int_ctype;
 		} else if (is_fouled_type(sym)) {
 			sym = &int_ctype;
diff --git a/parse.c b/parse.c
index 2675a3976099..c30a00eafb3e 100644
--- a/parse.c
+++ b/parse.c
@@ -1709,7 +1709,6 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 		}
 		type = alloc_symbol(token->pos, SYM_BASETYPE);
 		*type = *ctx->ctype.base_type;
-		type->ctype.modifiers &= ~MOD_SPECIFIER;
 		type->ctype.base_type = ctx->ctype.base_type;
 		type->type = SYM_RESTRICT;
 		ctx->ctype.base_type = type;
diff --git a/show-parse.c b/show-parse.c
index 8ef59b5ee255..850d6ce5f937 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -336,7 +336,8 @@ deeper:
 		if (as)
 			prepend(name, "%s ", show_as(as));
 
-		if (sym->type == SYM_BASETYPE || sym->type == SYM_ENUM)
+		if (sym->type == SYM_BASETYPE || sym->type == SYM_ENUM ||
+		    sym->type == SYM_RESTRICT)
 			mod &= ~MOD_SPECIFIER;
 		s = modifier_string(mod);
 		prepend(name, "%s", s);

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values
  2026-11-09 20:31 Bart Van Assche
                   ` (35 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
                   ` (35 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

From: Linus Torvalds <torvalds@linux-foundation.org>

Allow EXPR_FORCE_CAST as well as EXPR_CAST in restricted_value() when
checking whether an expression is a cast of 0 or ~0.

Support restricted constants defined with '__force' in kernel headers
(for example, '#define EPOLLIN (__force __poll_t)0x00000001') by
recognizing both forced and unforced casts of 0 and ~0 in
restricted_value().

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[ bvanassche: Rewrite commit message in imperative mood, fix typo, unwrap
  EXPR_FORCE_CAST in restricted_value(), and extend
  validation/bitwise-all-bits-and-casts.c ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                              |  4 +--
 validation/bitwise-all-bits-and-casts.c | 46 +++++++++++++++++++++++--
 2 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/evaluate.c b/evaluate.c
index b84eedbe4d33..ec02e5bbc4fe 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -443,7 +443,7 @@ static bool is_unsigned_restricted_or_int(struct symbol *sym)
 
 static int restricted_value(struct expression *v, struct symbol *type)
 {
-	if (v->type == EXPR_CAST) {
+	if (v->type == EXPR_CAST || v->type == EXPR_FORCE_CAST) {
 		struct symbol *ctype;
 		int cclass = classify_type(v->ctype, &ctype);
 		struct expression *leaf = v->cast_expression;
@@ -452,7 +452,7 @@ static int restricted_value(struct expression *v, struct symbol *type)
 			return 1;
 		if (restricted_value(v->cast_expression, ctype))
 			return 1;
-		while (leaf && leaf->type == EXPR_CAST)
+		while (leaf && (leaf->type == EXPR_CAST || leaf->type == EXPR_FORCE_CAST))
 			leaf = leaf->cast_expression;
 		if (leaf && leaf->type == EXPR_VALUE && leaf->value == 0)
 			return 0;
diff --git a/validation/bitwise-all-bits-and-casts.c b/validation/bitwise-all-bits-and-casts.c
index 01c8d84fcd67..ae0be1269ec4 100644
--- a/validation/bitwise-all-bits-and-casts.c
+++ b/validation/bitwise-all-bits-and-casts.c
@@ -1,6 +1,9 @@
+#define __force __attribute__((force))
+
 typedef unsigned long long __attribute__((bitwise)) be64;
 typedef unsigned int __attribute__((bitwise)) be32;
 typedef unsigned short __attribute__((bitwise)) be16;
+typedef signed int __attribute__((bitwise)) sbe32;
 
 static be32 test_all_bits(void)
 {
@@ -11,9 +14,11 @@ static be32 test_all_bits(void)
 	be32 e = +0;
 	be32 f = -0;
 	be32 g = (be32)0;
+	be32 h = (__force be32)0;
 	be32 i = (be32)~0;
+	be32 j = (__force be32)~0;
 
-	return a | b | c | d | e | f | g | i;
+	return a | b | c | d | e | f | g | h | i | j;
 }
 
 static be64 test_all_bits_64(void)
@@ -22,18 +27,22 @@ static be64 test_all_bits_64(void)
 	be64 b = ~0ULL;
 	be64 c = -1;
 	be64 d = (be64)0;
+	be64 e = (__force be64)0;
 	be64 f = (be64)~0;
+	be64 g = (__force be64)~0;
 
-	return a | b | c | d | f;
+	return a | b | c | d | e | f | g;
 }
 
 static be16 test_cross_cast_zero(void)
 {
 	be16 k = (be32)0;
+	be16 l = (__force be32)0;
 	be16 m = (be32)~0;
+	be16 n = (__force be32)~0;
 	be16 o = (unsigned short)~0;
 
-	return k | m | o;
+	return k | l | m | n | o;
 }
 
 static int test_ordered_compare(be32 x, be16 y)
@@ -54,7 +63,38 @@ static int test_ordered_compare(be32 x, be16 y)
 	return res;
 }
 
+static void test_bad_narrow_and_signed(sbe32 sx)
+{
+	be64 bad_u32 = ~0U;
+	be32 bad_u16 = (unsigned short)~0;
+	be32 bad_c16 = (be16)~0;
+	be32 bad_f16 = (__force be16)~0;
+	int bad_ord = (sx > 0);
+
+	(void)bad_u32;
+	(void)bad_u16;
+	(void)bad_c16;
+	(void)bad_f16;
+	(void)bad_ord;
+}
+
 /*
  * check-name: bitwise-all-bits-and-casts
  * check-command: sparse -Wbitwise $file
+ *
+ * check-error-start
+bitwise-all-bits-and-casts.c:68:24: warning: incorrect type in initializer (different base types)
+bitwise-all-bits-and-casts.c:68:24:    expected restricted be64 [usertype] bad_u32
+bitwise-all-bits-and-casts.c:68:24:    got unsigned int
+bitwise-all-bits-and-casts.c:69:25: warning: incorrect type in initializer (different base types)
+bitwise-all-bits-and-casts.c:69:25:    expected restricted be32 [usertype] bad_u16
+bitwise-all-bits-and-casts.c:69:25:    got unsigned short
+bitwise-all-bits-and-casts.c:70:25: warning: incorrect type in initializer (different base types)
+bitwise-all-bits-and-casts.c:70:25:    expected restricted be32 [usertype] bad_c16
+bitwise-all-bits-and-casts.c:70:25:    got restricted be16 [usertype]
+bitwise-all-bits-and-casts.c:71:25: warning: incorrect type in initializer (different base types)
+bitwise-all-bits-and-casts.c:71:25:    expected restricted be32 [usertype] bad_f16
+bitwise-all-bits-and-casts.c:71:25:    got restricted be16 [usertype]
+bitwise-all-bits-and-casts.c:72:24: warning: restricted sbe32 degrades to integer
+ * check-error-end
  */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 38/73] clang-format: Add .clang-format configuration file
  2026-11-09 20:31 Bart Van Assche
                   ` (36 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
                   ` (34 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add a .clang-format configuration file matching the sparse coding style
(8-column tabs for indentation, 80-column line limit, Linux kernel brace
and pointer alignment conventions, and foreach macro definitions) so
that code formatting can be checked and applied consistently.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 .clang-format | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 .clang-format

diff --git a/.clang-format b/.clang-format
new file mode 100644
index 000000000000..6390eb565a75
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,125 @@
+# SPDX-License-Identifier: MIT
+#
+# clang-format configuration file. Intended for clang-format >= 11.
+#
+# For more information, see:
+#
+#   https://clang.llvm.org/docs/ClangFormat.html
+#   https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+#
+---
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterClass: false
+  AfterControlStatement: false
+  AfterEnum: false
+  AfterFunction: true
+  AfterNamespace: true
+  AfterObjCDeclaration: false
+  AfterStruct: false
+  AfterUnion: false
+  AfterExternBlock: false
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+ForEachMacros:
+  - 'FOR_EACH_PTR'
+  - 'FOR_EACH_PTR_NOTAG'
+  - 'FOR_EACH_PTR_REVERSE'
+  - 'FOR_EACH_PTR_REVERSE_TAG'
+  - 'FOR_EACH_PTR_TAG'
+  - 'RECURSE_PTR_REVERSE'
+
+IncludeBlocks: Preserve
+IncludeCategories:
+  - Regex: '.*'
+    Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentGotoLabels: false
+IndentPPDirectives: None
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 8
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+
+# Taken from git's rules
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: false
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatementsExceptForEachMacros
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp03
+TabWidth: 8
+UseTab: Always
+...

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23
  2026-11-09 20:31 Bart Van Assche
                   ` (37 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
                   ` (33 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add STANDARD_C23 and STANDARD_GNU23 to enum standard, handle the c23,
c2x, iso9899:2024, gnu23, and gnu2x arguments to -std=, and set
__STDC_VERSION__ to 202311L when targeting C23 so that users and build
systems can select ISO C23 and GNU23 language modes and feature-test
macros.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 options.c                     |  5 +++++
 options.h                     |  2 ++
 predefine.c                   |  4 ++++
 validation/c23-stdc-version.c | 11 +++++++++++
 4 files changed, 22 insertions(+)
 create mode 100644 validation/c23-stdc-version.c

diff --git a/options.c b/options.c
index 5aa3290e07da..c140a912e8e9 100644
--- a/options.c
+++ b/options.c
@@ -797,6 +797,11 @@ static char **handle_switch_s(const char *arg, char **next)
 		else if (!strcmp(arg, "gnu17") ||
 			 !strcmp(arg, "gnu18"))
 			standard = STANDARD_GNU17;
+		else if (!strcmp(arg, "c23") || !strcmp(arg, "c2x") ||
+			 !strcmp(arg, "iso9899:2024"))
+			standard = STANDARD_C23;
+		else if (!strcmp(arg, "gnu23") || !strcmp(arg, "gnu2x"))
+			standard = STANDARD_GNU23;
 
 		else
 			die("Unsupported C dialect");
diff --git a/options.h b/options.h
index c2a9551a78e2..94eb7d10e362 100644
--- a/options.h
+++ b/options.h
@@ -27,6 +27,8 @@ enum standard {
 	STANDARD_GNU11 = STANDARD_C11 | STANDARD_GNU,
 	STANDARD_C17,
 	STANDARD_GNU17 = STANDARD_C17 | STANDARD_GNU,
+	STANDARD_C23,
+	STANDARD_GNU23 = STANDARD_C23 | STANDARD_GNU,
 };
 
 extern int die_if_error;
diff --git a/predefine.c b/predefine.c
index 5b0f0caf4c35..d6ab0785abad 100644
--- a/predefine.c
+++ b/predefine.c
@@ -111,6 +111,10 @@ void predefined_macros(void)
 	case STANDARD_GNU17:
 		predefine("__STDC_VERSION__", 1, "201710L");
 		break;
+	case STANDARD_C23:
+	case STANDARD_GNU23:
+		predefine("__STDC_VERSION__", 1, "202311L");
+		break;
 	}
 	if (!(standard & STANDARD_GNU) && (standard != STANDARD_NONE))
 		predefine("__STRICT_ANSI__", 1, "1");
diff --git a/validation/c23-stdc-version.c b/validation/c23-stdc-version.c
new file mode 100644
index 000000000000..a2903362f2f3
--- /dev/null
+++ b/validation/c23-stdc-version.c
@@ -0,0 +1,11 @@
+__STDC_VERSION__
+
+/*
+ * check-name: c23-stdc-version
+ * check-command: sparse -E -std=c23 $file
+ *
+ * check-output-start
+
+202311L
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef
  2026-11-09 20:31 Bart Van Assche
                   ` (38 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
                   ` (32 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support the #elifdef and #elifndef preprocessing directives introduced in
C23 (WG14 N2645) as shorthand for #elif defined(...) and #elif
!defined(...), and emit a pedantic warning when used in language modes
prior to C23.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 pre-process.c                               | 71 +++++++++++++++++++--
 validation/preprocessor/c23-elifdef-error.c | 35 ++++++++++
 validation/preprocessor/c23-elifdef.c       | 44 +++++++++++++
 3 files changed, 145 insertions(+), 5 deletions(-)
 create mode 100644 validation/preprocessor/c23-elifdef-error.c
 create mode 100644 validation/preprocessor/c23-elifdef.c

diff --git a/pre-process.c b/pre-process.c
index ca019d704a10..aa59dca1ae97 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -1713,39 +1713,98 @@ static int handle_if(struct stream *stream, struct token **line, struct token *t
 	return preprocessor_if(stream, token, value);
 }
 
-static int handle_elif(struct stream * stream, struct token **line, struct token *token)
+static int do_handle_elif(struct stream *stream, struct token *token,
+			  int (*eval)(struct token *),
+			  bool (*check)(struct token *))
 {
 	struct token *top_if = stream->top_if;
 	end_group(stream);
 
 	if (!top_if) {
 		nesting_error(stream);
-		sparse_error(token->pos, "unmatched #elif within stream");
+		sparse_error(token->pos, "unmatched #%s within stream",
+			     show_token(token));
 		return 1;
 	}
 
 	if (token_type(top_if) == TOKEN_ELSE) {
 		nesting_error(stream);
-		sparse_error(token->pos, "#elif after #else");
+		sparse_error(token->pos, "#%s after #else", show_token(token));
 		if (!false_nesting)
 			false_nesting = 1;
 		return 1;
 	}
 
 	dirty_stream(stream);
-	if (token_type(top_if) != TOKEN_IF)
+	if (token_type(top_if) != TOKEN_IF) {
+		if (false_nesting <= 1 && check)
+			check(token);
 		return 1;
+	}
 	if (false_nesting) {
 		false_nesting = 0;
-		if (!expression_value(&token->next))
+		if (!eval(token))
 			false_nesting = 1;
 	} else {
+		if (check)
+			check(token);
 		false_nesting = 1;
 		token_type(top_if) = TOKEN_SKIP_GROUPS;
 	}
 	return 1;
 }
 
+static int eval_elif(struct token *token)
+{
+	return expression_value(&token->next);
+}
+
+static bool check_elifdef(struct token *token)
+{
+	struct token *next = token->next;
+
+	if (standard < STANDARD_C23 && Wpedantic)
+		warning(token->pos, "ISO C does not support #%s before C23",
+			show_token(token));
+	if (token_type(next) != TOKEN_IDENT) {
+		sparse_error(token->pos, "expected preprocessor identifier");
+		return false;
+	}
+	if (!eof_token(next->next))
+		warning(next->next->pos,
+			"extra tokens at end of #%s directive",
+			show_token(token));
+	return true;
+}
+
+static int eval_elifdef(struct token *token)
+{
+	return check_elifdef(token) && token_defined(token->next);
+}
+
+static int eval_elifndef(struct token *token)
+{
+	return check_elifdef(token) && !token_defined(token->next);
+}
+
+static int handle_elif(struct stream *stream, struct token **line,
+		       struct token *token)
+{
+	return do_handle_elif(stream, token, eval_elif, NULL);
+}
+
+static int handle_elifdef(struct stream *stream, struct token **line,
+			  struct token *token)
+{
+	return do_handle_elif(stream, token, eval_elifdef, check_elifdef);
+}
+
+static int handle_elifndef(struct stream *stream, struct token **line,
+			   struct token *token)
+{
+	return do_handle_elif(stream, token, eval_elifndef, check_elifdef);
+}
+
 static int handle_else(struct stream *stream, struct token **line, struct token *token)
 {
 	struct token *top_if = stream->top_if;
@@ -2163,6 +2222,8 @@ static void init_preprocessor(void)
 		{ "endif",	handle_endif },
 		{ "if",		handle_if },
 		{ "elif",	handle_elif },
+		{ "elifdef",	handle_elifdef },
+		{ "elifndef",	handle_elifndef },
 	};
 	static struct {
 		const char *name;
diff --git a/validation/preprocessor/c23-elifdef-error.c b/validation/preprocessor/c23-elifdef-error.c
new file mode 100644
index 000000000000..606a780aa6d2
--- /dev/null
+++ b/validation/preprocessor/c23-elifdef-error.c
@@ -0,0 +1,35 @@
+#define FOO 1
+
+#if 1
+int taken = 1;
+#elifdef FOO
+int skipped1 = 1;
+#elifndef BAR
+int skipped2 = 1;
+#elifdef 123
+int skipped3 = 1;
+#endif
+
+#if 0
+#if 1
+#elifdef FOO
+#elifdef 123
+#endif
+#endif
+
+/*
+ * check-name: c23-elifdef-error
+ * check-command: sparse -std=c17 -Wpedantic -E $file
+ *
+ * check-output-start
+
+int taken = 1;
+ * check-output-end
+ *
+ * check-error-start
+preprocessor/c23-elifdef-error.c:5:2: warning: ISO C does not support #elifdef before C23
+preprocessor/c23-elifdef-error.c:7:2: warning: ISO C does not support #elifndef before C23
+preprocessor/c23-elifdef-error.c:9:2: warning: ISO C does not support #elifdef before C23
+preprocessor/c23-elifdef-error.c:9:2: error: expected preprocessor identifier
+ * check-error-end
+ */
diff --git a/validation/preprocessor/c23-elifdef.c b/validation/preprocessor/c23-elifdef.c
new file mode 100644
index 000000000000..5772fb5eb432
--- /dev/null
+++ b/validation/preprocessor/c23-elifdef.c
@@ -0,0 +1,44 @@
+#define FOO 1
+
+#if 0
+int a = 0;
+#elifdef FOO
+int a = 1;
+#else
+int a = 2;
+#endif
+
+#if 0
+int b = 0;
+#elifdef BAR
+int b = 1;
+#elifndef BAR
+int b = 2;
+#else
+int b = 3;
+#endif
+
+#if 1
+int c = 1;
+#elifdef FOO
+int c = 2;
+#endif
+
+#if 1
+int d = 1;
+#elifndef BAR
+int d = 2;
+#endif
+
+/*
+ * check-name: c23-elifdef
+ * check-command: sparse -E $file
+ *
+ * check-output-start
+
+int a = 1;
+int b = 2;
+int c = 1;
+int d = 1;
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next
  2026-11-09 20:31 Bart Van Assche
                   ` (39 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
                   ` (31 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support the C23 __has_include and __has_include_next preprocessor
operators for querying the availability of header files so that code
using feature test macros can conditionally include headers portably.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ident-list.h                                  |   2 +
 pre-process.c                                 | 303 +++++++++++++++++-
 .../preprocessor/c23-has-include-error.c      |  18 ++
 validation/preprocessor/c23-has-include.c     |  51 +++
 4 files changed, 363 insertions(+), 11 deletions(-)
 create mode 100644 validation/preprocessor/c23-has-include-error.c
 create mode 100644 validation/preprocessor/c23-has-include.c

diff --git a/ident-list.h b/ident-list.h
index 3c08e8ca9aa4..5f4d1fef5a81 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -68,6 +68,8 @@ __IDENT(__VA_ARGS___ident, "__VA_ARGS__", 0);
 __IDENT(__func___ident, "__func__", 0);
 __IDENT(__FUNCTION___ident, "__FUNCTION__", 0);
 __IDENT(__PRETTY_FUNCTION___ident, "__PRETTY_FUNCTION__", 0);
+__IDENT(__has_include_ident, "__has_include", 0);
+__IDENT(__has_include_next_ident, "__has_include_next", 0);
 
 /* Sparse commands */
 IDENT_RESERVED(__context__);
diff --git a/pre-process.c b/pre-process.c
index aa59dca1ae97..fbe4ba215532 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -50,6 +50,7 @@ static int false_nesting = 0;
 static int counter_macro = 0;		// __COUNTER__ expansion
 static int include_level = 0;
 static int expanding = 0;
+static int in_cond_expr = 0;
 
 #define INCLUDEPATHS 300
 const char *includepath[INCLUDEPATHS+1] = {
@@ -762,6 +763,32 @@ static struct token **substitute(struct token **list, const struct token *body,
 	return list;
 }
 
+static bool parse_has_include(struct token **list, int how);
+
+static void reject_outside_directive(struct token *token)
+{
+	struct token *next;
+
+	sparse_error(token->pos, "'%s' used outside of preprocessing directive",
+		     show_token(token));
+	next = scan_next(&token->next);
+	if (match_op(next, '(')) {
+		int depth = 1;
+
+		next = next->next;
+		while (!eof_token(next) && depth > 0) {
+			if (match_op(next, '('))
+				depth++;
+			else if (match_op(next, ')'))
+				depth--;
+			next = next->next;
+		}
+	}
+	token_type(token) = TOKEN_NUMBER;
+	token->number = "0";
+	token->next = next;
+}
+
 static int expand(struct token **list, struct symbol *sym)
 {
 	struct token *last;
@@ -777,6 +804,23 @@ static int expand(struct token **list, struct symbol *sym)
 		return 1;
 	}
 
+	if (expanding == &__has_include_ident ||
+	    expanding == &__has_include_next_ident) {
+		bool ok;
+
+		if (!in_cond_expr) {
+			reject_outside_directive(token);
+			return 0;
+		}
+		if (expanding == &__has_include_ident)
+			ok = parse_has_include(list, 0);
+		else
+			ok = parse_has_include(list, 1);
+		if (!ok)
+			replace_with_bool(token, 0);
+		return 0;
+	}
+
 	if (sym->arglist) {
 		if (!match_op(scan_next(&token->next), '('))
 			return 1;
@@ -788,6 +832,9 @@ static int expand(struct token **list, struct symbol *sym)
 	if (sym->expand)
 		return sym->expand(token, args) ? 0 : 1;
 
+	if (!expansion)
+		return 1;
+
 	expanding->tainted = 1;
 
 	last = token->next;
@@ -966,6 +1013,224 @@ static int do_include_path(const char **pptr, struct token **list, struct token
 	return 0;
 }
 
+static int test_include(const char *path, const char *filename)
+{
+	int fd;
+	struct stat st;
+	int plen = strlen(path);
+	int flen = strlen(filename);
+	char fullname[PATH_MAX];
+
+	if (plen + flen + 2 > sizeof(fullname))
+		return 0;
+	memcpy(fullname, path, plen);
+	if (plen && path[plen - 1] != '/') {
+		fullname[plen] = '/';
+		plen++;
+	}
+	memcpy(fullname + plen, filename, flen + 1);
+	if (already_tokenized(fullname))
+		return 1;
+	fd = open(fullname, O_RDONLY);
+	if (fd >= 0) {
+		if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
+			close(fd);
+			return 1;
+		}
+		close(fd);
+	}
+	return 0;
+}
+
+static int check_include(const char **pptr, const char *filename)
+{
+	const char *path;
+	while ((path = *pptr++) != NULL) {
+		if (test_include(path, filename))
+			return 1;
+	}
+	return 0;
+}
+
+static int check_has_include(struct stream *stream, const char *filename,
+			     int expect, int how)
+{
+	const char **path;
+
+	/* Absolute path? */
+	if (filename[0] == '/')
+		return test_include("", filename);
+
+	switch (how) {
+	case 1:
+		path = stream->next_path;
+		break;
+	default:
+		set_stream_include_path(stream);
+		path = expect ? angle_includepath : quote_includepath;
+		break;
+	}
+	if (!path)
+		return 0;
+	return check_include(path, filename);
+}
+
+static struct token *skip_balanced_parens(struct token *lparen)
+{
+	struct token *t;
+	int depth = 1;
+
+	if (!match_op(lparen, '('))
+		return lparen;
+	t = lparen->next;
+	while (!eof_token(t) && depth > 0) {
+		if (match_op(t, '('))
+			depth++;
+		else if (match_op(t, ')'))
+			depth--;
+		t = t->next;
+	}
+	return t;
+}
+
+static bool collect_angle_filename(struct token **next_p, char *buf,
+				   size_t size, struct position pos)
+{
+	struct token *t = *next_p;
+	char *ptr = buf;
+
+	while (!eof_token(t) && !match_op(t, '>')) {
+		const char *s = (token_type(t) == TOKEN_STRING) ?
+					t->string->data :
+					show_token(t);
+		size_t len = strlen(s);
+		size_t rem = size - (size_t)(ptr - buf);
+
+		if (len >= rem) {
+			sparse_error(t->pos, "header name too long");
+			return false;
+		}
+		memcpy(ptr, s, len);
+		ptr += len;
+		t = t->next;
+	}
+	*ptr = '\0';
+	if (!match_op(t, '>')) {
+		sparse_error(pos, "expected '>'");
+		return false;
+	}
+	if (ptr == buf) {
+		sparse_error(pos, "empty filename");
+		return false;
+	}
+	*next_p = t->next;
+	return true;
+}
+
+static bool extract_string_filename(struct token **next_p, char *buf,
+				    size_t size)
+{
+	struct token *t = *next_p;
+	const char *s = t->string->data;
+	size_t len = strlen(s);
+
+	if (!len) {
+		sparse_error(t->pos, "empty filename");
+		return false;
+	}
+	if (len >= size) {
+		sparse_error(t->pos, "header name too long");
+		return false;
+	}
+	memcpy(buf, s, len + 1);
+	*next_p = t->next;
+	return true;
+}
+
+static bool parse_has_include(struct token **list, int how)
+{
+	struct token *token = *list;
+	struct token *lparen = scan_next(&token->next);
+	struct token *next;
+	struct stream *stream = input_streams + token->pos.stream;
+	char filename[PATH_MAX];
+	int expect = 0;
+	int val = 0;
+
+	if (!match_op(lparen, '(')) {
+		sparse_error(token->pos, "expected '(' after %s",
+			     show_token(token));
+		return false;
+	}
+	next = scan_next(&lparen->next);
+	if (match_op(next, '<')) {
+		expect = '>';
+		next = next->next;
+		if (!collect_angle_filename(&next, filename, sizeof(filename),
+					    token->pos))
+			goto err;
+	} else if (token_type(next) == TOKEN_STRING) {
+		if (!extract_string_filename(&next, filename, sizeof(filename)))
+			goto err;
+	} else {
+		struct token **p = &lparen->next;
+		struct token *rparen = scan_next(p);
+		int depth = 1;
+
+		while (!eof_token(rparen)) {
+			if (match_op(rparen, '('))
+				depth++;
+			else if (match_op(rparen, ')') && !--depth)
+				break;
+			p = &rparen->next;
+			rparen = scan_next(p);
+		}
+		if (!match_op(rparen, ')')) {
+			sparse_error(rparen->pos, "expected ')'");
+			token->next = rparen;
+			return false;
+		}
+		*p = &eof_token_entry;
+		expand_list(&lparen->next);
+		p = &lparen->next;
+		while (!eof_token(*p))
+			p = &(*p)->next;
+		*p = rparen;
+		next = lparen->next;
+		if (match_op(next, '<')) {
+			expect = '>';
+			next = next->next;
+			if (!collect_angle_filename(&next, filename,
+						    sizeof(filename),
+						    token->pos))
+				goto err;
+		} else if (token_type(next) == TOKEN_STRING) {
+			if (!extract_string_filename(&next, filename,
+						     sizeof(filename)))
+				goto err;
+		} else {
+			sparse_error(next->pos,
+				     "expected \"FILENAME\" or <FILENAME>");
+			goto err;
+		}
+	}
+	next = scan_next(&next);
+	if (!match_op(next, ')')) {
+		sparse_error(next->pos, "expected ')'");
+		goto err;
+	}
+	next = next->next;
+
+	val = check_has_include(stream, filename, expect, how);
+	replace_with_bool(token, val);
+	token->next = next;
+	return true;
+
+err:
+	token->next = skip_balanced_parens(lparen);
+	return false;
+}
+
 static int free_preprocessor_line(struct token *token)
 {
 	while (token_type(token) != TOKEN_EOF) {
@@ -1645,6 +1910,7 @@ static int expression_value(struct token **where)
 	long long value;
 	int state = 0;
 
+	in_cond_expr++;
 	while (!eof_token(p = scan_next(list))) {
 		switch (state) {
 		case 0:
@@ -1655,6 +1921,18 @@ static int expression_value(struct token **where)
 				beginning = list;
 				break;
 			}
+			if (p->ident == &__has_include_ident) {
+				if (parse_has_include(list, 0))
+					continue;
+				replace_with_bool(p, 0);
+				break;
+			}
+			if (p->ident == &__has_include_next_ident) {
+				if (parse_has_include(list, 1))
+					continue;
+				replace_with_bool(p, 0);
+				break;
+			}
 			if (!expand_one_symbol(list))
 				continue;
 			if (token_type(p) != TOKEN_IDENT)
@@ -1700,6 +1978,7 @@ static int expression_value(struct token **where)
 	if (!eof_token(p))
 		sparse_error(p->pos, "garbage at end: %s", show_token_sequence(p, 0));
 	value = get_expression_value(expr);
+	in_cond_expr--;
 	return value != 0;
 }
 
@@ -2230,17 +2509,19 @@ static void init_preprocessor(void)
 		void (*expand_simple)(struct token *);
 		bool (*expand)(struct token *, struct arg *args);
 	} dynamic[] = {
-		{ "__LINE__",		expand_line },
-		{ "__FILE__",		expand_file },
-		{ "__BASE_FILE__",	expand_basefile },
-		{ "__DATE__",		expand_date },
-		{ "__TIME__",		expand_time },
-		{ "__COUNTER__",	expand_counter },
-		{ "__INCLUDE_LEVEL__",	expand_include_level },
-		{ "__has_attribute",	NULL, expand_has_attribute },
-		{ "__has_builtin",	NULL, expand_has_builtin },
-		{ "__has_extension",	NULL, expand_has_extension },
-		{ "__has_feature",	NULL, expand_has_feature },
+		{ "__LINE__", expand_line },
+		{ "__FILE__", expand_file },
+		{ "__BASE_FILE__", expand_basefile },
+		{ "__DATE__", expand_date },
+		{ "__TIME__", expand_time },
+		{ "__COUNTER__", expand_counter },
+		{ "__INCLUDE_LEVEL__", expand_include_level },
+		{ "__has_attribute", NULL, expand_has_attribute },
+		{ "__has_builtin", NULL, expand_has_builtin },
+		{ "__has_extension", NULL, expand_has_extension },
+		{ "__has_feature", NULL, expand_has_feature },
+		{ "__has_include", NULL, NULL },
+		{ "__has_include_next", NULL, NULL },
 	};
 
 	for (i = 0; i < ARRAY_SIZE(normal); i++) {
diff --git a/validation/preprocessor/c23-has-include-error.c b/validation/preprocessor/c23-has-include-error.c
new file mode 100644
index 000000000000..931eb47998a7
--- /dev/null
+++ b/validation/preprocessor/c23-has-include-error.c
@@ -0,0 +1,18 @@
+int outside = __has_include(<stdio.h>);
+
+#if __has_include("")
+#endif
+
+#if __has_include(<>)
+#endif
+
+/*
+ * check-name: c23-has-include-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+preprocessor/c23-has-include-error.c:1:15: error: '__has_include' used outside of preprocessing directive
+preprocessor/c23-has-include-error.c:3:19: error: empty filename
+preprocessor/c23-has-include-error.c:6:5: error: empty filename
+ * check-error-end
+ */
diff --git a/validation/preprocessor/c23-has-include.c b/validation/preprocessor/c23-has-include.c
new file mode 100644
index 000000000000..fa32cc9d4992
--- /dev/null
+++ b/validation/preprocessor/c23-has-include.c
@@ -0,0 +1,51 @@
+#if defined(__has_include)
+int has_macro = 1;
+#endif
+
+#if __has_include(<stdio.h>)
+int has_stdio = 1;
+#else
+int has_stdio = 0;
+#endif
+
+#if __has_include("options.h")
+int has_options = 1;
+#endif
+
+#if __has_include(<nonexistent_header_12345.h>)
+int has_nonexistent = 1;
+#else
+int has_nonexistent = 0;
+#endif
+
+#define HEADER <stdio.h>
+#if __has_include(HEADER)
+int has_macro_header = 1;
+#endif
+
+#define HAS_INC __has_include
+#if HAS_INC(<stdio.h>) && HAS_INC("options.h")
+int has_aliased_op = 1;
+#endif
+
+#if __has_include("this_is_an_extremely_long_path_name_that_tests_bounds_checking_1234567890_abcdefghijklmnopqrstuvwxyz.h")
+int has_long = 1;
+#else
+int has_long = 0;
+#endif
+
+/*
+ * check-name: c23-has-include
+ * check-command: sparse -std=c23 -E -I.. $file
+ *
+ * check-output-start
+
+int has_macro = 1;
+int has_stdio = 1;
+int has_options = 1;
+int has_nonexistent = 0;
+int has_macro_header = 1;
+int has_aliased_op = 1;
+int has_long = 0;
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local
  2026-11-09 20:31 Bart Van Assche
                   ` (40 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
                   ` (30 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Register static_assert, alignas, alignof, and thread_local as reserved
C23 keywords and support single-argument static_assert(expr) without a
message string per ISO C23 6.7.12:
- Mark static_assert and alignof as reserved identifiers in C23 mode
  while keeping them available as ordinary identifiers in pre-C23 modes.
- Warn under -Wpedantic when alignof or _Alignof is applied to an
  expression rather than a type name.
- Enforce ISO C23 constraints on alignas and thread_local declarations,
  and avoid materializing block-scope thread_local symbols as stack
  locals during linearization.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 expression.c                        |  15 ++++-
 ident-list.h                        |   9 ++-
 linearize.c                         |   2 +-
 options.c                           |   2 +
 parse.c                             | 100 ++++++++++++++++++++++++++--
 parse.h                             |   1 +
 symbol.h                            |   2 +
 validation/c11-c23-keywords-ident.c |  19 ++++++
 validation/c23-alignof-pedantic.c   |  20 ++++++
 validation/c23-keywords-error.c     |  48 +++++++++++++
 validation/c23-keywords.c           |  30 +++++++++
 11 files changed, 241 insertions(+), 7 deletions(-)
 create mode 100644 validation/c11-c23-keywords-ident.c
 create mode 100644 validation/c23-alignof-pedantic.c
 create mode 100644 validation/c23-keywords-error.c
 create mode 100644 validation/c23-keywords.c

diff --git a/expression.c b/expression.c
index 04b1a242d08a..4545adcf4c02 100644
--- a/expression.c
+++ b/expression.c
@@ -646,13 +646,21 @@ static struct token *type_info_expression(struct token *token,
 	struct expression **tree, int type)
 {
 	struct expression *expr = alloc_expression(token->pos, type);
+	struct ident *op_ident = token->ident;
+	bool std_alignof = (op_ident == &_Alignof_ident ||
+			    op_ident == &alignof_ident);
 	struct token *p;
 
 	*tree = expr;
 	expr->flags = CEF_SET_ICE; /* XXX: VLA support will need that changed */
 	token = token->next;
-	if (!match_op(token, '(') || !lookup_type(token->next))
+	if (!match_op(token, '(') || !lookup_type(token->next)) {
+		if (std_alignof && Wpedantic)
+			warning(expr->pos,
+				"ISO C does not allow '%s' on an expression",
+				show_ident(op_ident));
 		return unary_expression(token, &expr->cast_expression);
+	}
 	p = token;
 	token = typename(token->next, &expr->cast_type, NULL);
 
@@ -672,6 +680,10 @@ static struct token *type_info_expression(struct token *token,
 	 */
 	if (match_op(token, '{')) {
 		struct expression *cast = alloc_expression(p->pos, EXPR_CAST);
+		if (std_alignof && Wpedantic)
+			warning(expr->pos,
+				"ISO C does not allow '%s' on an expression",
+				show_ident(op_ident));
 		cast->cast_type = expr->cast_type;
 		expr->cast_type = NULL;
 		expr->cast_expression = cast;
@@ -691,6 +703,7 @@ static struct token *unary_expression(struct token *token, struct expression **t
 				int type;
 			} type_information[] = {
 				{ &sizeof_ident, EXPR_SIZEOF },
+				{ &alignof_ident, EXPR_ALIGNOF },
 				{ &__alignof___ident, EXPR_ALIGNOF },
 				{ &__alignof_ident, EXPR_ALIGNOF },
 				{ &_Alignof_ident, EXPR_ALIGNOF },
diff --git a/ident-list.h b/ident-list.h
index 5f4d1fef5a81..83031b265388 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -43,12 +43,19 @@ IDENT(_Noreturn);
 IDENT_RESERVED(_Static_assert);
 IDENT(_Thread_local);
 
+/* C23 keywords */
+IDENT(alignas);
+IDENT(alignof);
+IDENT(static_assert);
+IDENT(thread_local);
+
 /* Special case for L'\t' */
 IDENT(L);
 
 /* Extended gcc identifiers */
 IDENT(asm); IDENT_RESERVED(__asm); IDENT_RESERVED(__asm__);
-IDENT(alignof); IDENT_RESERVED(__alignof); IDENT_RESERVED(__alignof__); 
+IDENT_RESERVED(__alignof);
+IDENT_RESERVED(__alignof__);
 IDENT_RESERVED(__sizeof_ptr__);
 IDENT_RESERVED(__builtin_types_compatible_p);
 IDENT_RESERVED(__builtin_offsetof);
diff --git a/linearize.c b/linearize.c
index 24b402622b83..bb3a5a9ecf16 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1995,7 +1995,7 @@ static pseudo_t linearize_one_symbol(struct entrypoint *ep, struct symbol *sym)
 		return VOID;
 
 	/* We need to output these puppies some day too.. */
-	if (sym->ctype.modifiers & (MOD_STATIC | MOD_TOPLEVEL))
+	if (sym->ctype.modifiers & (MOD_STATIC | MOD_TOPLEVEL | MOD_TLS))
 		return VOID;
 
 	sym->initialized = 1;
diff --git a/options.c b/options.c
index c140a912e8e9..2de74afc5c35 100644
--- a/options.c
+++ b/options.c
@@ -11,6 +11,7 @@
 #include "lib.h"
 #include "machine.h"
 #include "target.h"
+#include "parse.h"
 #include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -1068,4 +1069,5 @@ void handle_switch_finalize(void)
 {
 	handle_switch_v_finalize();
 	handle_switch_W_finalize();
+	init_parser_finalize();
 }
diff --git a/parse.c b/parse.c
index c30a00eafb3e..d6c23fcdfb5d 100644
--- a/parse.c
+++ b/parse.c
@@ -465,6 +465,7 @@ static struct init_keyword {
 	struct symbol_op *op;
 	struct symbol *type;
 	unsigned long mods;
+	int std;
 } typedefs[] = {
 #define N(I, O,...)	{ I, O,##__VA_ARGS__ }
 #define D(I, O,...)	N(I,O,##__VA_ARGS__ ),		\
@@ -481,6 +482,7 @@ static struct init_keyword {
 	N("extern",		&extern_op,	.mods = MOD_EXTERN),
 	N("__thread",		&thread_op),
 	N("_Thread_local",	&thread_op),
+	N("thread_local",	&thread_op,	.std = STANDARD_C23),
 
 	A("inline",		&inline_op,	.mods = MOD_INLINE),
 
@@ -498,6 +500,7 @@ static struct init_keyword {
 	N("_Atomic",		&atomic_op,	.mods = MOD_ATOMIC),
 	N("_Noreturn",		&noreturn_op,	.mods = MOD_NORETURN),
 	N("_Alignas",		&alignas_op),
+	N("alignas",		&alignas_op,	.std = STANDARD_C23),
 
 	U("attribute",		&attribute_op),
 
@@ -546,6 +549,7 @@ static struct init_keyword {
 	N("__context__",	&__context___op),
 	N("__range__",		&range_op),
 	N("_Static_assert",	&static_assert_op),
+	N("static_assert",	&static_assert_op, .std = STANDARD_C23),
 
 	/* Attributes */
 	D("packed",		&packed_op),
@@ -607,14 +611,23 @@ static void init_keyword(int stream, struct init_keyword *kw, enum namespace ns)
 	sym->op = kw->op;
 }
 
+static int parser_stream;
+
 void init_parser(int stream)
 {
 	int i;
 
-	for (i = 0; i < ARRAY_SIZE(typedefs); i++)
+	parser_stream = stream;
+	for (i = 0; i < ARRAY_SIZE(typedefs); i++) {
+		if (typedefs[i].std)
+			continue;
 		init_keyword(stream, &typedefs[i], NS_TYPEDEF);
-	for (i = 0; i < ARRAY_SIZE(keywords); i++)
+	}
+	for (i = 0; i < ARRAY_SIZE(keywords); i++) {
+		if (keywords[i].std)
+			continue;
 		init_keyword(stream, &keywords[i], NS_KEYWORD);
+	}
 
 	for (i = 0; i < ARRAY_SIZE(ignored_attributes); i++) {
 		const char * name = ignored_attributes[i];
@@ -627,6 +640,23 @@ void init_parser(int stream)
 	}
 }
 
+void init_parser_finalize(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(typedefs); i++) {
+		if (typedefs[i].std && standard >= typedefs[i].std)
+			init_keyword(parser_stream, &typedefs[i], NS_TYPEDEF);
+	}
+	for (i = 0; i < ARRAY_SIZE(keywords); i++) {
+		if (keywords[i].std && standard >= keywords[i].std)
+			init_keyword(parser_stream, &keywords[i], NS_KEYWORD);
+	}
+	if (standard >= STANDARD_C23) {
+		static_assert_ident.reserved = 1;
+		alignof_ident.reserved = 1;
+	}
+}
 
 static struct token *skip_to(struct token *token, int op)
 {
@@ -1513,6 +1543,7 @@ static struct token *alignas_specifier(struct token *token, struct symbol *sym,
 {
 	int alignment = 0;
 
+	ctx->has_alignas = 1;
 	if (!match_op(token, '(')) {
 		sparse_error(token->pos, "expected '(' after _Alignas");
 		return token;
@@ -1986,6 +2017,9 @@ static struct token *handle_bitfield(struct token *token, struct decl_state *ctx
 	}
 
 	bitfield = alloc_indirect_symbol(token->pos, ctype, SYM_BITFIELD);
+	if (ctx->has_alignas)
+		sparse_error(token->pos, "alignment specified for bitfield '%s'",
+			show_ident(*ctx->ident));
 	token = conditional_expression(token->next, &expr);
 	width = const_expression_value(expr);
 	bitfield->bit_size = width;
@@ -2042,6 +2076,11 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 		decl->ctype.modifiers |= mod;
 		decl->cleanup = ctx.cleanup;
 		decl->endpos = token->pos;
+		if (decl->ctype.modifiers & MOD_TLS)
+			sparse_error(
+				decl->pos,
+				"struct or union member '%s' cannot be thread-local",
+				show_ident(decl->ident));
 		add_symbol(list, decl);
 		if (!match_op(token, ','))
 			break;
@@ -2054,7 +2093,9 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 static struct token *struct_declaration_list(struct token *token, struct symbol_list **list)
 {
 	while (!match_op(token, '}')) {
-		if (match_ident(token, &_Static_assert_ident)) {
+		if (match_ident(token, &_Static_assert_ident) ||
+		    (standard >= STANDARD_C23 &&
+		     match_ident(token, &static_assert_ident))) {
 			token = parse_static_assert(token, NULL);
 			continue;
 		}
@@ -2082,6 +2123,14 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	sym->ctype.modifiers |= decl_modifiers(&ctx);
 	sym->endpos = token->pos;
 	sym->forced_arg = ctx.forced;
+	if (ctx.has_alignas)
+		sparse_error(sym->pos,
+			     "alignment specified for parameter '%s'",
+			     show_ident(sym->ident));
+	if (sym->ctype.modifiers & MOD_TLS)
+		sparse_error(sym->pos,
+			     "parameter '%s' cannot be thread-local",
+			     show_ident(sym->ident));
 	return token;
 }
 
@@ -2092,6 +2141,8 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 	struct symbol *sym = alloc_symbol(token->pos, SYM_NODE);
 	*p = sym;
 	token = declaration_specifiers(token, &ctx);
+	if (ctx.has_alignas)
+		sparse_error(sym->pos, "alignment specified for type name");
 	token = declarator(token, &ctx);
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
@@ -2654,7 +2705,9 @@ static struct token * statement_list(struct token *token, struct statement_list
 			break;
 		if (match_op(token, '}'))
 			break;
-		if (match_ident(token, &_Static_assert_ident)) {
+		if (match_ident(token, &_Static_assert_ident) ||
+		    (standard >= STANDARD_C23 &&
+		     match_ident(token, &static_assert_ident))) {
 			token = parse_static_assert(token, NULL);
 			continue;
 		}
@@ -3104,6 +3157,16 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		decl->ctype.modifiers |= ctx.f_modifiers;
 
 		/* K&R argument declaration? */
+		if (lookup_type(token) || match_op(token, '{')) {
+			if (ctx.has_alignas)
+				sparse_error(decl->pos,
+					     "alignment specified for function '%s'",
+					     show_ident(decl->ident));
+			if (decl->ctype.modifiers & MOD_TLS)
+				sparse_error(decl->pos,
+					     "function '%s' cannot be thread-local",
+					     show_ident(decl->ident));
+		}
 		if (lookup_type(token))
 			return parse_k_r_arguments(token, decl, list);
 		if (match_op(token, '{'))
@@ -3167,6 +3230,35 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			}
 		}
 
+		if (ctx.has_alignas) {
+			if (is_typedef)
+				sparse_error(decl->pos,
+					     "alignment specified for typedef '%s'",
+					     show_ident(decl->ident));
+			else if (decl->ctype.base_type &&
+				 decl->ctype.base_type->type == SYM_FN)
+				sparse_error(decl->pos,
+					     "alignment specified for function '%s'",
+					     show_ident(decl->ident));
+			else if (decl->ctype.modifiers & MOD_REGISTER)
+				sparse_error(decl->pos,
+					     "alignment specified for 'register' object '%s'",
+					     show_ident(decl->ident));
+		}
+
+		if (!is_typedef && (decl->ctype.modifiers & MOD_TLS)) {
+			if (decl->ctype.base_type &&
+			    decl->ctype.base_type->type == SYM_FN)
+				sparse_error(decl->pos,
+					     "function '%s' cannot be thread-local",
+					     show_ident(decl->ident));
+			else if (!(decl->ctype.modifiers &
+				   (MOD_TOPLEVEL | MOD_STATIC | MOD_EXTERN)))
+				sparse_error(decl->pos,
+					     "thread-local variable '%s' at block scope must be 'static' or 'extern'",
+					     show_ident(decl->ident));
+		}
+
 		if (!match_op(token, ','))
 			break;
 
diff --git a/parse.h b/parse.h
index e7ea3642b65c..6a528bc8ca4d 100644
--- a/parse.h
+++ b/parse.h
@@ -139,6 +139,7 @@ extern struct symbol *ctype_integer(int size, int want_unsigned);
 extern int inline_function(struct expression *expr, struct symbol *sym);
 extern void uninline(struct symbol *sym);
 extern void init_parser(int);
+extern void init_parser_finalize(void);
 
 struct token *expect(struct token *, int, const char *);
 
diff --git a/symbol.h b/symbol.h
index a14686fe863b..2c6731f88399 100644
--- a/symbol.h
+++ b/symbol.h
@@ -115,6 +115,7 @@ struct decl_state {
 	unsigned char autotype;
 	unsigned char forced;
 	unsigned char packed;
+	unsigned char has_alignas;
 	unsigned char designated_init;
 	unsigned char transparent_union;
 };
@@ -199,6 +200,7 @@ struct symbol {
 					torename:1,
 					packed:1,
 					transparent_union:1;
+			unsigned int has_alignas : 1;
 			int		rank:3;	// arithmetic's rank
 			struct expression *array_size;
 			struct ctype ctype;
diff --git a/validation/c11-c23-keywords-ident.c b/validation/c11-c23-keywords-ident.c
new file mode 100644
index 000000000000..8fdb011268e4
--- /dev/null
+++ b/validation/c11-c23-keywords-ident.c
@@ -0,0 +1,19 @@
+static int test_c11_idents(void)
+{
+	int alignas = 1;
+	int alignof = 2;
+	int static_assert = 3;
+	int thread_local = 4;
+	int bool = 5;
+	int true = 6;
+	int false = 7;
+	int nullptr = 8;
+
+	return alignas + alignof + static_assert + thread_local +
+	       bool + true + false + nullptr;
+}
+
+/*
+ * check-name: c11-c23-keywords-ident
+ * check-command: sparse -std=c11 $file
+ */
diff --git a/validation/c23-alignof-pedantic.c b/validation/c23-alignof-pedantic.c
new file mode 100644
index 000000000000..d17eabb79108
--- /dev/null
+++ b/validation/c23-alignof-pedantic.c
@@ -0,0 +1,20 @@
+static int test(void)
+{
+	alignas(8) int x = 0;
+	int a = alignof(int);
+	int b = alignof(x);
+	int c = _Alignof(x);
+	int d = __alignof__(x);
+	int e = __alignof(x);
+	return a + b + c + d + e;
+}
+
+/*
+ * check-name: c23-alignof-pedantic
+ * check-command: sparse -std=c23 -pedantic $file
+ *
+ * check-error-start
+c23-alignof-pedantic.c:5:17: warning: ISO C does not allow 'alignof' on an expression
+c23-alignof-pedantic.c:6:17: warning: ISO C does not allow '_Alignof' on an expression
+ * check-error-end
+ */
diff --git a/validation/c23-keywords-error.c b/validation/c23-keywords-error.c
new file mode 100644
index 000000000000..25d3adac3ce8
--- /dev/null
+++ b/validation/c23-keywords-error.c
@@ -0,0 +1,48 @@
+static_assert(0);
+static_assert(0, "must fail");
+
+typedef alignas(8) int bad_typedef;
+
+struct bf {
+	alignas(4) unsigned int x : 2;
+};
+
+static void bad_param(alignas(8) int p)
+{
+	(void)p;
+}
+
+static void bad_fn_locals(void)
+{
+	thread_local int local_tl = 0;
+	register alignas(8) int reg_a = 0;
+	int sz = sizeof(alignas(8) int);
+	(void)local_tl;
+	(void)reg_a;
+	(void)sz;
+}
+
+static int (alignas);
+static int (alignof);
+static int (static_assert);
+static int (thread_local);
+
+/*
+ * check-name: c23-keywords-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-keywords-error.c:1:15: error: static assertion failed
+c23-keywords-error.c:2:15: error: static assertion failed: "must fail"
+c23-keywords-error.c:4:24: error: alignment specified for typedef 'bad_typedef'
+c23-keywords-error.c:7:35: error: alignment specified for bitfield 'x'
+c23-keywords-error.c:10:23: error: alignment specified for parameter 'p'
+c23-keywords-error.c:17:26: error: thread-local variable 'local_tl' at block scope must be 'static' or 'extern'
+c23-keywords-error.c:18:33: error: alignment specified for 'register' object 'reg_a'
+c23-keywords-error.c:19:25: error: alignment specified for type name
+c23-keywords-error.c:25:12: error: Trying to use reserved word 'alignas' as identifier
+c23-keywords-error.c:26:12: error: Trying to use reserved word 'alignof' as identifier
+c23-keywords-error.c:27:12: error: Trying to use reserved word 'static_assert' as identifier
+c23-keywords-error.c:28:12: error: Trying to use reserved word 'thread_local' as identifier
+ * check-error-end
+ */
diff --git a/validation/c23-keywords.c b/validation/c23-keywords.c
new file mode 100644
index 000000000000..a6f44a735c7e
--- /dev/null
+++ b/validation/c23-keywords.c
@@ -0,0 +1,30 @@
+static_assert(1);
+static_assert(1, "file scope");
+
+struct s {
+	static_assert(1);
+	static_assert(1, "struct scope");
+	alignas(16) int a;
+};
+
+static_assert(alignof(struct s) == 16);
+
+thread_local int tl;
+alignas(double) static int aligned_dbl;
+
+static int test(void)
+{
+	static_assert(1);
+	static_assert(1, "func scope");
+	alignas(8) int x = 0;
+	static thread_local int local_tl = 3;
+	extern thread_local int tl;
+	static_assert(alignof(int) <= 8);
+	static_assert(alignof(double) <= 16);
+	return x + tl + local_tl + aligned_dbl;
+}
+
+/*
+ * check-name: c23-keywords
+ * check-command: sparse -std=c23 -pedantic -Wno-decl $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 43/73] c23: Add support for bool, true, and false keywords
  2026-11-09 20:31 Bart Van Assche
                   ` (41 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
                   ` (29 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Register bool, true, and false as reserved keywords in C23 mode and
evaluate true and false as integer constant expressions of type bool
with values 1 and 0 in both C expressions and #if conditionals per ISO
C23 6.4.4.5 and 6.10.1.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 expression.c                | 15 ++++++++++++-
 ident-list.h                |  3 +++
 parse.c                     |  3 +++
 pre-process.c               | 10 +++++++++
 validation/c23-bool-error.c | 23 +++++++++++++++++++
 validation/c23-bool.c       | 45 +++++++++++++++++++++++++++++++++++++
 6 files changed, 98 insertions(+), 1 deletion(-)
 create mode 100644 validation/c23-bool-error.c
 create mode 100644 validation/c23-bool.c

diff --git a/expression.c b/expression.c
index 4545adcf4c02..9b07963c914a 100644
--- a/expression.c
+++ b/expression.c
@@ -466,9 +466,22 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 	}
 
 	case TOKEN_IDENT: {
-		struct symbol *sym = lookup_symbol(token->ident, NS_SYMBOL | NS_TYPEDEF);
+		struct symbol *sym;
 		struct token *next = token->next;
 
+		if (standard >= STANDARD_C23 &&
+		    (token->ident == &true_ident ||
+		     token->ident == &false_ident)) {
+			expr = alloc_expression(token->pos, EXPR_VALUE);
+			expr->flags = CEF_SET_INT;
+			expr->ctype = &bool_ctype;
+			expr->value = (token->ident == &true_ident);
+			token = next;
+			break;
+		}
+
+		sym = lookup_symbol(token->ident, NS_SYMBOL | NS_TYPEDEF);
+
 		if (!sym) {
 			sym = handle_func(token);
 			if (token->ident == &__builtin_types_compatible_p_ident) {
diff --git a/ident-list.h b/ident-list.h
index 83031b265388..31a37bede4ed 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -46,8 +46,11 @@ IDENT(_Thread_local);
 /* C23 keywords */
 IDENT(alignas);
 IDENT(alignof);
+IDENT(bool);
+IDENT(false);
 IDENT(static_assert);
 IDENT(thread_local);
+IDENT(true);
 
 /* Special case for L'\t' */
 IDENT(L);
diff --git a/parse.c b/parse.c
index d6c23fcdfb5d..4edfed1cdf97 100644
--- a/parse.c
+++ b/parse.c
@@ -520,6 +520,7 @@ static struct init_keyword {
 	N("unsigned",		&unsigned_op),
 	N("__int128",		&int128_op),
 	N("_Bool",		&spec_op,	.type = &bool_ctype),
+	N("bool",		&spec_op,	.type = &bool_ctype, .std = STANDARD_C23),
 
 	/* Predeclared types */
 	N("__builtin_va_list",	&spec_op,	.type = &ptr_ctype),
@@ -655,6 +656,8 @@ void init_parser_finalize(void)
 	if (standard >= STANDARD_C23) {
 		static_assert_ident.reserved = 1;
 		alignof_ident.reserved = 1;
+		true_ident.reserved = 1;
+		false_ident.reserved = 1;
 	}
 }
 
diff --git a/pre-process.c b/pre-process.c
index fbe4ba215532..a733927c4c14 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -1937,6 +1937,16 @@ static int expression_value(struct token **where)
 				continue;
 			if (token_type(p) != TOKEN_IDENT)
 				break;
+			if (standard >= STANDARD_C23 &&
+			    p->ident == &true_ident) {
+				replace_with_bool(p, 1);
+				break;
+			}
+			if (standard >= STANDARD_C23 &&
+			    p->ident == &false_ident) {
+				replace_with_bool(p, 0);
+				break;
+			}
 			token_type(p) = TOKEN_ZERO_IDENT;
 			break;
 		case 1:
diff --git a/validation/c23-bool-error.c b/validation/c23-bool-error.c
new file mode 100644
index 000000000000..49bfafa5bbd1
--- /dev/null
+++ b/validation/c23-bool-error.c
@@ -0,0 +1,23 @@
+static int (bool);
+static int (true);
+static int (false);
+typedef int bool;
+
+static void bad_lvalue(void)
+{
+	true = false;
+}
+
+/*
+ * check-name: c23-bool-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-bool-error.c:1:12: error: Trying to use reserved word 'bool' as identifier
+c23-bool-error.c:2:12: error: Trying to use reserved word 'true' as identifier
+c23-bool-error.c:3:12: error: Trying to use reserved word 'false' as identifier
+c23-bool-error.c:4:13: error: two or more data types in declaration specifiers
+c23-bool-error.c:4:13: error: Trying to use reserved word 'bool' as identifier
+c23-bool-error.c:8:14: error: not an lvalue
+ * check-error-end
+ */
diff --git a/validation/c23-bool.c b/validation/c23-bool.c
new file mode 100644
index 000000000000..8b68b2ad9261
--- /dev/null
+++ b/validation/c23-bool.c
@@ -0,0 +1,45 @@
+#if true
+int t = 1;
+#endif
+
+#if !false
+int f = 1;
+#endif
+
+#if true + true != 2
+#error "true + true must equal 2 in preprocessor"
+#endif
+
+bool a = true;
+bool b = false;
+
+struct bool_bf {
+	bool flag : 1;
+};
+
+static_assert(true == 1, "true is 1");
+static_assert(false == 0, "false is 0");
+static_assert(true + true == 2, "integer promotion of true");
+static_assert(sizeof(true) == sizeof(bool), "sizeof true is sizeof bool");
+static_assert(sizeof(false) == sizeof(bool), "sizeof false is sizeof bool");
+static_assert(_Generic(true, bool: 1, int: 0, default: 0) == 1,
+	      "type of true is bool");
+static_assert(_Generic(false, bool: 1, int: 0, default: 0) == 1,
+	      "type of false is bool");
+static_assert(_Generic(+true, int: 1, bool: 0, default: 0) == 1,
+	      "+true promotes to int");
+
+static int test_conversions(void)
+{
+	bool b1 = (bool)0.5;
+	bool b2 = (bool)0.0;
+	bool b3 = (bool)&a;
+	struct bool_bf s = { .flag = true };
+
+	return b1 + !b2 + b3 + s.flag;
+}
+
+/*
+ * check-name: c23-bool
+ * check-command: sparse -std=c23 -pedantic -Wno-decl $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 44/73] c23: Add support for nullptr and nullptr_t
  2026-11-09 20:31 Bart Van Assche
                   ` (42 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
                   ` (28 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Introduce the reserved C23 nullptr keyword and a dedicated nullptr_ctype
base type representing nullptr_t per ISO C23 6.2.5 and 6.3.2.3:
- Evaluate nullptr as an address constant of type nullptr_ctype rather
  than an integer constant expression, rejecting nullptr in array bounds,
  enumerators, and switch controlling expressions.
- Support implicit and explicit conversions from nullptr_t expressions to
  any pointer type and bool, equality comparisons with pointers and null
  pointer constants, conditional expressions, _Generic associations, and
  variadic arguments, while rejecting relational comparisons, arithmetic,
  and non-bool/non-void/non-pointer casts.
- Lower nullptr_ctype as a pointer-sized null value in linearize and
  sparse-llvm.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                       | 186 +++++++++++++++++++++++++++++--
 expression.c                     |  10 ++
 ident-list.h                     |   1 +
 linearize.c                      |   2 +
 parse.c                          |   7 ++
 show-parse.c                     |   1 +
 sparse-llvm.c                    |  26 +++--
 symbol.c                         |  12 +-
 symbol.h                         |  13 ++-
 validation/backend/c23-nullptr.c |  21 ++++
 validation/c23-nullptr-assign.c  |  35 ++++++
 validation/c23-nullptr-error.c   | 107 ++++++++++++++++++
 validation/c23-nullptr.c         | 107 ++++++++++++++++++
 validation/linear/c23-nullptr.c  |  20 ++++
 14 files changed, 528 insertions(+), 20 deletions(-)
 create mode 100644 validation/backend/c23-nullptr.c
 create mode 100644 validation/c23-nullptr-assign.c
 create mode 100644 validation/c23-nullptr-error.c
 create mode 100644 validation/c23-nullptr.c
 create mode 100644 validation/linear/c23-nullptr.c

diff --git a/evaluate.c b/evaluate.c
index ec02e5bbc4fe..c0d039105881 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -857,6 +857,8 @@ const char *type_difference(struct ctype *c1, struct ctype *c2,
 				return "different address spaces";
 			if (base1 != base2)
 				return "different base types";
+			if (!base1 && t1->bit_size != t2->bit_size)
+				return "different base types";
 			if (t1->rank != t2->rank)
 				return "different type sizes";
 			diff = (mod1 ^ mod2) & ~MOD_IGNORE;
@@ -1106,7 +1108,11 @@ enum null_constant_type {
 
 static inline int is_null_pointer_constant(struct expression *e)
 {
-	if (e->ctype == &null_ctype)
+	if (e->ctype == &null_ctype ||
+	    (e->ctype && e->ctype->type == SYM_NODE && e->ctype->ctype.base_type == &null_ctype))
+		return NULL_PTR;
+	if (is_nullptr_type(e->ctype) &&
+	    (e->type == EXPR_VALUE || (e->flags & CEF_ADDR)))
 		return NULL_PTR;
 	if (!(e->flags & CEF_ICE))
 		return NON_NULL;
@@ -1148,6 +1154,87 @@ static struct symbol *evaluate_compare(struct expression *expr)
 		goto OK;
 	}
 
+	/* nullptr_t equality comparisons [C23 6.5.9] */
+	if (ltype == &nullptr_ctype || rtype == &nullptr_ctype) {
+		int is_null1, is_null2;
+
+		if (expr->op != SPECIAL_EQUAL && expr->op != SPECIAL_NOTEQUAL)
+			return bad_expr_type(expr);
+
+		is_null1 = is_null_pointer_constant(left);
+		is_null2 = is_null_pointer_constant(right);
+		if (ltype == &nullptr_ctype && rtype == &nullptr_ctype) {
+			if (is_null1 && is_null2) {
+				expr->type = EXPR_VALUE;
+				expr->value = (expr->op == SPECIAL_EQUAL);
+				expr->flags |= CEF_ACE;
+			}
+			goto OK;
+		}
+		if (ltype == &nullptr_ctype) {
+			if (rclass & TYPE_PTR) {
+				if (is_null1 && is_null2) {
+					if (is_null2 == NULL_ZERO)
+						bad_null(right);
+					expr->type = EXPR_VALUE;
+					expr->value = (expr->op == SPECIAL_EQUAL);
+					expr->flags |= CEF_ACE;
+					goto OK;
+				}
+				if (rtype == &null_ctype) {
+					expr->left = cast_to(left, &ptr_ctype);
+					expr->right = cast_to(right, &ptr_ctype);
+				} else {
+					expr->left = cast_to(left, rtype);
+				}
+				goto OK;
+			}
+			if (is_null2) {
+				if (is_null2 == NULL_ZERO)
+					bad_null(right);
+				if (is_null1) {
+					expr->type = EXPR_VALUE;
+					expr->value = (expr->op == SPECIAL_EQUAL);
+					expr->flags |= CEF_ACE;
+				} else {
+					expr->right = cast_to(right, &nullptr_ctype);
+				}
+				goto OK;
+			}
+			return bad_expr_type(expr);
+		}
+		if (lclass & TYPE_PTR) {
+			if (is_null1 && is_null2) {
+				if (is_null1 == NULL_ZERO)
+					bad_null(left);
+				expr->type = EXPR_VALUE;
+				expr->value = (expr->op == SPECIAL_EQUAL);
+				expr->flags |= CEF_ACE;
+				goto OK;
+			}
+			if (ltype == &null_ctype) {
+				expr->left = cast_to(left, &ptr_ctype);
+				expr->right = cast_to(right, &ptr_ctype);
+			} else {
+				expr->right = cast_to(right, ltype);
+			}
+			goto OK;
+		}
+		if (is_null1) {
+			if (is_null1 == NULL_ZERO)
+				bad_null(left);
+			if (is_null2) {
+				expr->type = EXPR_VALUE;
+				expr->value = (expr->op == SPECIAL_EQUAL);
+				expr->flags |= CEF_ACE;
+			} else {
+				expr->left = cast_to(left, &nullptr_ctype);
+			}
+			goto OK;
+		}
+		return bad_expr_type(expr);
+	}
+
 	/* at least one must be a pointer */
 	if (!((lclass | rclass) & TYPE_PTR))
 		return bad_expr_type(expr);
@@ -1276,6 +1363,29 @@ static struct symbol *evaluate_conditional_expression(struct expression *expr)
 		goto out;
 	}
 
+	if (ltype == &nullptr_ctype || rtype == &nullptr_ctype) {
+		if (ltype == &nullptr_ctype && rtype == &nullptr_ctype) {
+			ctype = &nullptr_ctype;
+			goto out;
+		}
+		if (ltype == &nullptr_ctype && (rclass & TYPE_PTR)) {
+			ctype = (rtype == &null_ctype) ? &ptr_ctype : rtype;
+			*cond = cast_to(*cond, ctype);
+			if (rtype == &null_ctype)
+				expr->cond_false = cast_to(expr->cond_false, ctype);
+			goto out;
+		}
+		if (rtype == &nullptr_ctype && (lclass & TYPE_PTR)) {
+			ctype = (ltype == &null_ctype) ? &ptr_ctype : ltype;
+			if (ltype == &null_ctype)
+				*cond = cast_to(*cond, ctype);
+			expr->cond_false = cast_to(expr->cond_false, ctype);
+			goto out;
+		}
+		typediff = "different types";
+		goto Err;
+	}
+
 	if ((lclass | rclass) & TYPE_PTR) {
 		int is_null1 = is_null_pointer_constant(*cond);
 		int is_null2 = is_null_pointer_constant(expr->cond_false);
@@ -1486,6 +1596,20 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
 	int tclass = classify_type(target, &t);
 	int sclass = classify_type(source, &s);
 
+	if (t == &nullptr_ctype) {
+		int is_null;
+		if (s == &nullptr_ctype)
+			return 1;
+		is_null = is_null_pointer_constant(*rp);
+		if (is_null) {
+			if (is_null == NULL_ZERO)
+				bad_null(*rp);
+			goto Cast;
+		}
+		*typediff = "different base types";
+		return 0;
+	}
+
 	if (tclass & sclass & TYPE_NUM) {
 		if (tclass & TYPE_RESTRICT) {
 			/* allowed assignments unfoul */
@@ -1518,10 +1642,21 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
 				bad_null(*rp);
 			goto Cast;
 		}
+		if (s == &nullptr_ctype) {
+			if (t == &null_ctype) {
+				*typediff = "different base types";
+				return 0;
+			}
+			goto Cast;
+		}
 		if (!(sclass & TYPE_PTR)) {
 			*typediff = "different base types";
 			return 0;
 		}
+		if (t == &null_ctype && s != &null_ctype) {
+			*typediff = "different base types";
+			return 0;
+		}
 		b1 = examine_pointer_target(t);
 		b2 = examine_pointer_target(s);
 		mod1 = t->ctype.modifiers & (MOD_IGN | MOD_NODEREF);
@@ -1566,7 +1701,7 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
 
 	if (tclass & TYPE_NUM) {
 		/* XXX: need to turn into comparison with NULL */
-		if (is_bool_type(t) && (sclass & TYPE_PTR))
+		if (is_bool_type(t) && ((sclass & TYPE_PTR) || s == &nullptr_ctype))
 			goto Cast;
 		*typediff = "different base types";
 		return 0;
@@ -2067,9 +2202,13 @@ static struct symbol *evaluate_preop(struct expression *expr)
 		expr->flags = expr->unop->flags & ~CEF_CONST_MASK;
 		/*
 		 * A logical negation never yields an address constant
-		 * [6.6(9)].
+		 * [6.6(9)], though negating an address constant yields
+		 * an arithmetic constant expression.
 		 */
-		expr->flags &= ~CEF_ADDR;
+		if (expr->unop->flags & CEF_ADDR) {
+			expr->flags &= ~CEF_ADDR;
+			expr->flags |= CEF_ACE;
+		}
 
 		if (is_safe_type(ctype))
 			warning(expr->pos, "testing a 'safe expression'");
@@ -2474,6 +2613,8 @@ int evaluate_arguments(struct symbol_list *argtypes, struct expression_list *hea
 					*p = cast_to(expr, &ptr_ctype);
 				else
 					degenerate(expr);
+			} else if (type == &nullptr_ctype) {
+				degenerate(expr);
 			}
 		} else if (!target->forced_arg){
 			static char where[30];
@@ -2984,6 +3125,14 @@ static struct symbol *cast_to_bool(struct expression *expr)
 		return NULL;
 
 	zero = alloc_const_expression(expr->pos, 0);
+	if (otype == &nullptr_ctype) {
+		zero->ctype = otype;
+		expr->op = SPECIAL_NOTEQUAL;
+		expr->type = EXPR_COMPARE;
+		expr->left = old;
+		expr->right = zero;
+		return expr->ctype;
+	}
 	if (oclass & TYPE_PTR)
 		zero->ctype = otype;
 	expr->op = SPECIAL_NOTEQUAL;
@@ -3030,7 +3179,7 @@ static int cast_flags(struct expression *expr, struct expression *old)
 		 */
 		else if (old->flags & CEF_FLOAT)
 			flags = CEF_SET_ICE;
-	} else if (class & TYPE_PTR) {
+	} else if ((class & TYPE_PTR) || t == &nullptr_ctype) {
 		/*
 		 * Casts of integer literals to pointer type yield
 		 * address constants [6.6(9)].
@@ -3145,6 +3294,19 @@ static struct symbol *evaluate_cast(struct expression *expr)
 	if (expr->type == EXPR_FORCE_CAST)
 		goto out;
 
+	if (ttype == &nullptr_ctype) {
+		if (stype != &nullptr_ctype && !is_null_pointer_constant(source))
+			expression_error(expr, "conversion from '%s' to 'nullptr_t'",
+					 show_typename(source->ctype));
+		goto out;
+	}
+	if (stype == &nullptr_ctype) {
+		if (!is_bool_type(ttype) && tclass != TYPE_PTR)
+			expression_error(expr, "conversion from 'nullptr_t' to '%s'",
+					 show_typename(ctype));
+		goto out;
+	}
+
 	if (tclass & (TYPE_COMPOUND | TYPE_FN)) {
 		/*
 		 * Special case: cast to union type (GCC extension)
@@ -3471,8 +3633,13 @@ static struct symbol *evaluate_generic_selection(struct expression *expr)
 		return NULL;
 	}
 
-	source = *ctrl;
-	source.ctype.modifiers &= ~(MOD_QUALIFIER|MOD_ATOMIC);
+	struct symbol *sel_ctrl = ctrl;
+
+	if (ctrl->ctype.modifiers & (MOD_QUALIFIER|MOD_ATOMIC)) {
+		source = *ctrl;
+		source.ctype.modifiers &= ~(MOD_QUALIFIER|MOD_ATOMIC);
+		sel_ctrl = &source;
+	}
 	for (map = expr->map; map; map = map->next) {
 		struct symbol *stype = map->type;
 		struct symbol *base;
@@ -3509,7 +3676,7 @@ static struct symbol *evaluate_generic_selection(struct expression *expr)
 			sparse_error(stype->pos, "incomplete type in generic selection");
 			continue;
 		}
-		if (!type_selection(&source, stype))
+		if (!type_selection(sel_ctrl, stype))
 			continue;
 
 		res = map->expr;
@@ -4009,6 +4176,9 @@ static void evaluate_switch_statement(struct statement *stmt)
 	evaluate_statement(stmt->switch_statement);
 	if (!sel)
 		return;
+	if (valid_type(sel->ctype) && !is_int_type(sel->ctype) &&
+	    !is_restricted_type(sel->ctype) && !is_fouled_type(sel->ctype))
+		expression_error(sel, "switch quantity is not an integer");
 	if (sel->ctype && is_enum_type(sel->ctype))
 		enumcase_holder = NULL; /* Only check cases against switch */
 
diff --git a/expression.c b/expression.c
index 9b07963c914a..da2c14b458db 100644
--- a/expression.c
+++ b/expression.c
@@ -480,6 +480,16 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 			break;
 		}
 
+		if (standard >= STANDARD_C23 &&
+		    token->ident == &nullptr_ident) {
+			expr = alloc_expression(token->pos, EXPR_VALUE);
+			expr->flags = CEF_ADDR;
+			expr->ctype = &nullptr_ctype;
+			expr->value = 0;
+			token = next;
+			break;
+		}
+
 		sym = lookup_symbol(token->ident, NS_SYMBOL | NS_TYPEDEF);
 
 		if (!sym) {
diff --git a/ident-list.h b/ident-list.h
index 31a37bede4ed..18dabf5193b2 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -48,6 +48,7 @@ IDENT(alignas);
 IDENT(alignof);
 IDENT(bool);
 IDENT(false);
+IDENT(nullptr);
 IDENT(static_assert);
 IDENT(thread_local);
 IDENT(true);
diff --git a/linearize.c b/linearize.c
index bb3a5a9ecf16..422cee820ddb 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1276,6 +1276,8 @@ retry:	switch (s->type) {
 	case_int:
 		return sign ? MTYPE_SINT : MTYPE_UINT;
 	case SYM_BASETYPE:
+		if (s == &nullptr_ctype)
+			return MTYPE_VPTR;
 		if (s->ctype.base_type == &fp_type)
 			return MTYPE_FLOAT;
 		if (s->ctype.base_type == &int_type)
diff --git a/parse.c b/parse.c
index 4edfed1cdf97..5e526b0cc840 100644
--- a/parse.c
+++ b/parse.c
@@ -658,6 +658,7 @@ void init_parser_finalize(void)
 		alignof_ident.reserved = 1;
 		true_ident.reserved = 1;
 		false_ident.reserved = 1;
+		nullptr_ident.reserved = 1;
 	}
 }
 
@@ -1006,6 +1007,12 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			ctype = &void_ctype;
 			if (expr && expr->ctype)
 				ctype = expr->ctype;
+			if (is_nullptr_type(ctype)) {
+				sparse_error(expr->pos,
+					     "enumerator value for '%s' is not an integer constant",
+					     show_ident(token->ident));
+				ctype = &int_ctype;
+			}
 		} else if (!ctype) {
 			ctype = &int_ctype;
 		} else if (is_int_type(ctype)) {
diff --git a/show-parse.c b/show-parse.c
index 850d6ce5f937..78d86e20e93a 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -278,6 +278,7 @@ static struct ctype_name {
 
 	{ &void_ctype,   "void", "" },
 	{ &bool_ctype,   "bool", "" },
+	{ &nullptr_ctype, "nullptr_t", "" },
 
 	{ &float_ctype,  "float", "F" },
 	{ &double_ctype, "double", "" },
diff --git a/sparse-llvm.c b/sparse-llvm.c
index 8f37bde1b98d..c7055238ff88 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -154,6 +154,9 @@ static LLVMTypeRef sym_basetype_type(struct symbol *sym)
 {
 	LLVMTypeRef ret = NULL;
 
+	if (sym == &nullptr_ctype)
+		return LLVMPointerType(LLVMInt8TypeInContext(context), 0);
+
 	if (is_float_type(sym)) {
 		switch (sym->bit_size) {
 		case 32:
@@ -458,12 +461,17 @@ static LLVMValueRef value_to_ivalue(struct function *fn, struct symbol *ctype, L
 	return val;
 }
 
+static inline bool is_llvm_ptr_type(struct symbol *type)
+{
+	return is_ptr_type(type) || is_nullptr_type(type);
+}
+
 static LLVMValueRef value_to_pvalue(struct function *fn, struct symbol *ctype, LLVMValueRef val)
 {
 	const char *name = LLVMGetValueName(val);
 	LLVMTypeRef dtype = symbol_type(ctype);
 
-	assert(is_ptr_type(ctype));
+	assert(is_llvm_ptr_type(ctype));
 	switch (LLVMGetTypeKind(LLVMTypeOf(val))) {
 	case LLVMIntegerTypeKind:
 		val = LLVMBuildIntToPtr(fn->builder, val, dtype, name);
@@ -481,7 +489,7 @@ static LLVMValueRef adjust_type(struct function *fn, struct symbol *ctype, LLVMV
 {
 	if (is_int_type(ctype))
 		return value_to_ivalue(fn, ctype, val);
-	if (is_ptr_type(ctype))
+	if (is_llvm_ptr_type(ctype))
 		return value_to_pvalue(fn, ctype, val);
 	return val;
 }
@@ -901,18 +909,18 @@ static void output_op_ptrcast(struct function *fn, struct instruction *insn)
 	case OP_UTPTR:
 	case OP_SEXT:			// FIXME
 		assert(is_int_type(otype));
-		assert(is_ptr_type(insn->type));
+		assert(is_llvm_ptr_type(insn->type));
 		op = LLVMIntToPtr;
 		break;
 	case OP_PTRTU:
-		assert(is_ptr_type(otype));
+		assert(is_llvm_ptr_type(otype));
 		assert(is_int_type(insn->type));
 		op = LLVMPtrToInt;
 		break;
 	case OP_PTRCAST:
 	case OP_ZEXT:			// FIXME
-		assert(is_ptr_type(otype));
-		assert(is_ptr_type(insn->type));
+		assert(is_llvm_ptr_type(otype));
+		assert(is_llvm_ptr_type(insn->type));
 		op = LLVMBitCast;
 		break;
 	default:
@@ -930,7 +938,7 @@ static void output_op_cast(struct function *fn, struct instruction *insn, LLVMOp
 	struct symbol *otype = insn->orig_type;
 	char target_name[64];
 
-	if (is_ptr_type(insn->type))	// cast to void* is OP_CAST ...
+	if (is_llvm_ptr_type(insn->type))	// cast to void* is OP_CAST ...
 		return output_op_ptrcast(fn, insn);
 
 	assert(is_int_type(insn->type));
@@ -939,7 +947,7 @@ static void output_op_cast(struct function *fn, struct instruction *insn, LLVMOp
 	pseudo_name(insn->target, target_name);
 
 	dtype = symbol_type(insn->type);
-	if (is_ptr_type(otype)) {
+	if (is_llvm_ptr_type(otype)) {
 		op = LLVMPtrToInt;
 	} else if (is_float_type(otype)) {
 		assert(op == LLVMFPToUI || op == LLVMFPToSI);
@@ -1252,7 +1260,7 @@ static LLVMValueRef output_data(LLVMModuleRef module, struct symbol *sym)
 	if (initializer) {
 		switch (initializer->type) {
 		case EXPR_VALUE:
-			initial_value = LLVMConstInt(symbol_type(sym), initializer->value, 1);
+			initial_value = constant_value(initializer->value, symbol_type(sym));
 			break;
 		case EXPR_FVALUE:
 			initial_value = LLVMConstReal(symbol_type(sym), initializer->fvalue);
diff --git a/symbol.c b/symbol.c
index 06cd8ca2ede7..a05e240cdfaa 100644
--- a/symbol.c
+++ b/symbol.c
@@ -307,7 +307,14 @@ static struct symbol * examine_array_type(struct symbol *sym)
 		unsigned long count = get_expression_value_silent(array_size);
 		if (base_type->bit_size >= 0)
 			bit_size = array_element_offset(base_type->bit_size, count);
-		if (array_size->type != EXPR_VALUE) {
+		if (valid_type(array_size->ctype) &&
+		    !is_int_type(array_size->ctype) &&
+		    !is_restricted_type(array_size->ctype) &&
+		    !is_fouled_type(array_size->ctype)) {
+			sparse_error(array_size->pos,
+				     "size of array has non-integer type");
+			bit_size = 0;
+		} else if (array_size->type != EXPR_VALUE) {
 			if (Wvla)
 				warning(array_size->pos, "Variable length array is used.");
 			bit_size = -1;
@@ -875,7 +882,7 @@ struct symbol	bool_ctype, void_ctype, type_ctype,
 		float_ctype, double_ctype, ldouble_ctype,
 		string_ctype, ptr_ctype, lazy_ptr_ctype,
 		incomplete_ctype, label_ctype, bad_ctype,
-		null_ctype;
+		null_ctype, nullptr_ctype;
 struct symbol	autotype_ctype;
 struct symbol	schar_ptr_ctype, short_ptr_ctype;
 struct symbol	int_ptr_ctype, uint_ptr_ctype;
@@ -942,6 +949,7 @@ static const struct ctype_declare {
 	{ &incomplete_ctype,   T_BASETYPE },
 	{ &autotype_ctype,     T_BASETYPE },
 	{ &bad_ctype,          T_BASETYPE },
+	{ &nullptr_ctype,      SYM_BASETYPE, 0, 0, &bits_in_pointer, &pointer_alignment, NULL },
 
 	{ &char_ctype,         T__INT(-2, char) },
 	{ &schar_ctype,        T_SINT(-2, char) },
diff --git a/symbol.h b/symbol.h
index 2c6731f88399..acd381fe64fd 100644
--- a/symbol.h
+++ b/symbol.h
@@ -306,7 +306,7 @@ extern struct symbol	bool_ctype, void_ctype, type_ctype,
 			float_ctype, double_ctype, ldouble_ctype,
 			string_ctype, ptr_ctype, lazy_ptr_ctype,
 			incomplete_ctype, label_ctype, bad_ctype,
-			null_ctype;
+			null_ctype, nullptr_ctype;
 extern struct symbol	autotype_ctype;
 extern struct symbol	schar_ptr_ctype, short_ptr_ctype;
 extern struct symbol	int_ptr_ctype, uint_ptr_ctype;
@@ -487,6 +487,13 @@ static inline int is_bool_type(struct symbol *type)
 	return type == &bool_ctype;
 }
 
+static inline int is_nullptr_type(const struct symbol *type)
+{
+	if (type && type->type == SYM_NODE)
+		type = type->ctype.base_type;
+	return type == &nullptr_ctype;
+}
+
 static inline int is_scalar_type(struct symbol *type)
 {
 	if (type->type == SYM_NODE)
@@ -505,6 +512,8 @@ static inline int is_scalar_type(struct symbol *type)
 		return 1;
 	if (type->ctype.base_type == &fp_type)
 		return 1;
+	if (type == &nullptr_ctype)
+		return 1;
 	return 0;
 }
 
@@ -524,6 +533,8 @@ static inline bool is_integral_type(struct symbol *type)
 	}
 	if (type->ctype.base_type == &int_type)
 		return 1;
+	if (type == &nullptr_ctype)
+		return 1;
 	return 0;
 }
 
diff --git a/validation/backend/c23-nullptr.c b/validation/backend/c23-nullptr.c
new file mode 100644
index 000000000000..2cfb27b19f47
--- /dev/null
+++ b/validation/backend/c23-nullptr.c
@@ -0,0 +1,21 @@
+typedef typeof(nullptr) nullptr_t;
+
+extern int sink(const char *fmt, ...);
+
+static nullptr_t g_np = nullptr;
+
+int test_nullptr_llvm(nullptr_t arg, int **out);
+int test_nullptr_llvm(nullptr_t arg, int **out)
+{
+	*out = arg;
+	g_np = nullptr;
+	return sink("test", nullptr, arg, g_np);
+}
+
+/*
+ * check-name: backend-c23-nullptr
+ * check-command: sparse-llvm-dis -m64 -std=c23 $file
+ * check-output-ignore
+ * check-output-contains: define i32 @test_nullptr_llvm(
+ * check-output-contains: @sink(
+ */
diff --git a/validation/c23-nullptr-assign.c b/validation/c23-nullptr-assign.c
new file mode 100644
index 000000000000..e863075f2103
--- /dev/null
+++ b/validation/c23-nullptr-assign.c
@@ -0,0 +1,35 @@
+typedef typeof(nullptr) nullptr_t;
+
+void test(int *p, void *v)
+{
+	nullptr_t np;
+	np = p;
+	np = v;
+	np = 0;
+
+	int i = nullptr;
+	double d = np;
+	(void)i;
+	(void)d;
+}
+
+/*
+ * check-name: c23-nullptr-assign
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-nullptr-assign.c:6:12: warning: incorrect type in assignment (different base types)
+c23-nullptr-assign.c:6:12:    expected nullptr_t [usertype] np
+c23-nullptr-assign.c:6:12:    got int *p
+c23-nullptr-assign.c:7:12: warning: incorrect type in assignment (different base types)
+c23-nullptr-assign.c:7:12:    expected nullptr_t [usertype] np
+c23-nullptr-assign.c:7:12:    got void *v
+c23-nullptr-assign.c:8:14: warning: Using plain integer as NULL pointer
+c23-nullptr-assign.c:10:17: warning: incorrect type in initializer (different base types)
+c23-nullptr-assign.c:10:17:    expected int i
+c23-nullptr-assign.c:10:17:    got nullptr_t
+c23-nullptr-assign.c:11:20: warning: incorrect type in initializer (different base types)
+c23-nullptr-assign.c:11:20:    expected double d
+c23-nullptr-assign.c:11:20:    got nullptr_t [assigned] [usertype] np
+ * check-error-end
+ */
diff --git a/validation/c23-nullptr-error.c b/validation/c23-nullptr-error.c
new file mode 100644
index 000000000000..1e26878519a2
--- /dev/null
+++ b/validation/c23-nullptr-error.c
@@ -0,0 +1,107 @@
+typedef typeof(nullptr) nullptr_t;
+
+static int (nullptr);
+
+enum bad_enum {
+	E_BAD = nullptr,
+	E_NEXT,
+};
+
+struct bad_bf {
+	unsigned int x : nullptr;
+};
+
+static int bad_arr[nullptr];
+
+static void test_errors(int *p, int c)
+{
+	nullptr_t np = nullptr;
+	int i2 = (int)nullptr;
+	long l2 = (long)np;
+	double d2 = (double)nullptr;
+	nullptr_t b1 = (nullptr_t)1;
+	nullptr_t b2 = (nullptr_t)(int *)0;
+	bool r1 = nullptr < nullptr;
+	bool r2 = np > p;
+	bool r3 = nullptr == 1;
+	int t1 = c ? nullptr : 1;
+	nullptr_t t0 = c ? nullptr : 0;
+	void *a1 = nullptr + 1;
+	int u1 = *nullptr;
+	int u2 = +nullptr;
+	int u3 = -nullptr;
+	int u4 = ~nullptr;
+	np++;
+
+	switch (np) {
+	default:
+		break;
+	}
+
+	switch (c) {
+	case nullptr:
+		break;
+	}
+
+	(void)i2;
+	(void)l2;
+	(void)d2;
+	(void)b1;
+	(void)b2;
+	(void)r1;
+	(void)r2;
+	(void)r3;
+	(void)t1;
+	(void)t0;
+	(void)a1;
+	(void)u1;
+	(void)u2;
+	(void)u3;
+	(void)u4;
+}
+
+/*
+ * check-name: c23-nullptr-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-nullptr-error.c:3:12: error: Trying to use reserved word 'nullptr' as identifier
+c23-nullptr-error.c:6:17: error: enumerator value for 'E_BAD' is not an integer constant
+c23-nullptr-error.c:11:26: error: bad integer constant expression
+c23-nullptr-error.c:11:33: error: bitfield 'x' has invalid width (0)
+c23-nullptr-error.c:14:20: error: size of array has non-integer type
+c23-nullptr-error.c:19:19: error: conversion from 'nullptr_t' to 'int'
+c23-nullptr-error.c:20:20: error: conversion from 'nullptr_t' to 'long'
+c23-nullptr-error.c:21:22: error: conversion from 'nullptr_t' to 'double'
+c23-nullptr-error.c:22:25: error: conversion from 'int' to 'nullptr_t'
+c23-nullptr-error.c:23:25: error: conversion from 'int *' to 'nullptr_t'
+c23-nullptr-error.c:24:27: error: incompatible types for operation (<):
+c23-nullptr-error.c:24:27:    nullptr_t
+c23-nullptr-error.c:24:27:    nullptr_t
+c23-nullptr-error.c:25:22: error: incompatible types for operation (>):
+c23-nullptr-error.c:25:22:    nullptr_t [usertype] np
+c23-nullptr-error.c:25:22:    int *p
+c23-nullptr-error.c:26:27: error: incompatible types for operation (==):
+c23-nullptr-error.c:26:27:    nullptr_t
+c23-nullptr-error.c:26:27:    int
+c23-nullptr-error.c:27:20: error: incompatible types in conditional expression (different types):
+c23-nullptr-error.c:27:20:    nullptr_t
+c23-nullptr-error.c:27:20:    int
+c23-nullptr-error.c:28:26: error: incompatible types in conditional expression (different types):
+c23-nullptr-error.c:28:26:    nullptr_t
+c23-nullptr-error.c:28:26:    int
+c23-nullptr-error.c:29:28: error: incompatible types for operation (+):
+c23-nullptr-error.c:29:28:    nullptr_t
+c23-nullptr-error.c:29:28:    int
+c23-nullptr-error.c:30:18: error: cannot dereference this type
+c23-nullptr-error.c:31:18: error: incompatible type for operation (+):
+c23-nullptr-error.c:31:18:    nullptr_t
+c23-nullptr-error.c:32:18: error: incompatible type for operation (-):
+c23-nullptr-error.c:32:18:    nullptr_t
+c23-nullptr-error.c:33:18: error: incompatible type for operation (~):
+c23-nullptr-error.c:33:18:    nullptr_t
+c23-nullptr-error.c:34:11: error: need scalar for ++/--
+c23-nullptr-error.c:36:17: error: switch quantity is not an integer
+c23-nullptr-error.c:42:14: error: incompatible types for 'case' statement
+ * check-error-end
+ */
diff --git a/validation/c23-nullptr.c b/validation/c23-nullptr.c
new file mode 100644
index 000000000000..e7c0d4aa7f45
--- /dev/null
+++ b/validation/c23-nullptr.c
@@ -0,0 +1,107 @@
+typedef typeof(nullptr) nullptr_t;
+
+static_assert(sizeof(nullptr) == sizeof(void *), "sizeof nullptr");
+static_assert(sizeof(nullptr_t) == sizeof(void *), "sizeof nullptr_t");
+static_assert(alignof(nullptr_t) == alignof(void *), "alignof nullptr_t");
+static_assert(_Generic(nullptr, nullptr_t: 1, void *: 0, int: 0, default: 0) == 1,
+	      "typeof nullptr is nullptr_t");
+
+void *p = nullptr;
+int *q = nullptr;
+nullptr_t np = nullptr;
+static nullptr_t np_zero = 0;
+static nullptr_t np_void0 = (void *)0;
+static void (*fp_global)(void) = nullptr;
+
+static void test_call(int *arg1, nullptr_t arg2)
+{
+	(void)arg1;
+	(void)arg2;
+}
+
+static void test_varargs(int n, ...)
+{
+	(void)n;
+}
+
+static nullptr_t test_ret_np(void)
+{
+	return nullptr;
+}
+
+static int *test_ret_ptr(void)
+{
+	return nullptr;
+}
+
+static void dummy_fn(void)
+{
+}
+
+static void test_func(const int *cq_const)
+{
+	p = np;
+	q = np;
+	np = 0;
+	np = (void *)0;
+	np = (nullptr_t)nullptr;
+	np = (const nullptr_t)np;
+	np = test_ret_np();
+	q = test_ret_ptr();
+
+	void (*fp)(void) = np;
+	fp = nullptr;
+	if (fp == nullptr || nullptr != dummy_fn) {
+	}
+
+	test_call(nullptr, nullptr);
+	test_call(np, np);
+	test_call(np_zero, np_void0);
+	test_varargs(2, nullptr, np);
+
+	(void)nullptr;
+	(void)np;
+	(void)fp_global;
+
+	bool b1 = nullptr;
+	bool b2 = np;
+	bool b3 = (bool)nullptr;
+	bool b4 = (bool)np;
+	(void)b1;
+	(void)b2;
+	(void)b3;
+	(void)b4;
+
+	if (nullptr) {
+	}
+	if (!nullptr && !np) {
+	}
+	if (p == nullptr && nullptr == q) {
+	}
+	if (np == p && p == np) {
+	}
+	if (np == nullptr && np == 0 && 0 == np && np == (void *)0) {
+	}
+
+	int cond = 1;
+	void *cp = cond ? nullptr : p;
+	int *cq = cond ? q : nullptr;
+	const int *ccq = cond ? np : cq_const;
+	void *cv0 = cond ? nullptr : (void *)0;
+	nullptr_t cnp1 = cond ? np : nullptr;
+
+	static_assert(_Generic(1 ? nullptr : (int *)0, int *: 1, default: 0) == 1);
+	static_assert(_Generic(1 ? nullptr : (void *)0, void *: 1, default: 0) == 1);
+	static_assert(_Generic(1 ? np : nullptr, nullptr_t: 1, default: 0) == 1);
+
+	(void)cp;
+	(void)cq;
+	(void)ccq;
+	(void)cv0;
+	(void)cnp1;
+}
+
+/*
+ * check-name: c23-nullptr
+ * check-command: sparse -std=c23 -pedantic -Wno-decl -Wno-non-pointer-null $file
+ */
diff --git a/validation/linear/c23-nullptr.c b/validation/linear/c23-nullptr.c
new file mode 100644
index 000000000000..bdf4fb36e32a
--- /dev/null
+++ b/validation/linear/c23-nullptr.c
@@ -0,0 +1,20 @@
+typedef typeof(nullptr) nullptr_t;
+
+static void *np_to_ptr(nullptr_t np)
+{
+	return np;
+}
+
+static bool np_is_null(nullptr_t np)
+{
+	return !np;
+}
+
+/*
+ * check-name: linear-c23-nullptr
+ * check-command: test-linearize -m64 -std=c23 $file
+ *
+ * check-output-ignore
+ * check-output-contains: ret\\.64 *%arg1
+ * check-output-contains: seteq\\.1
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 45/73] c23: Add support for digit separators in numeric literals
  2026-11-09 20:31 Bart Van Assche
                   ` (43 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
                   ` (27 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Allow single quotes (') between digits in preprocessing numbers and
strip them before parsing integer and floating-point values in
expression.c to support C23 digit separators (WG14 N2626) and emit a
pedantic warning in pre-C23 modes.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 expression.c                               | 165 +++++++++++++++++++++
 tokenize.c                                 |  35 ++++-
 validation/c23-digit-separators-error.c    |  36 +++++
 validation/c23-digit-separators-pedantic.c |  12 ++
 validation/c23-digit-separators.c          |  29 ++++
 5 files changed, 275 insertions(+), 2 deletions(-)
 create mode 100644 validation/c23-digit-separators-error.c
 create mode 100644 validation/c23-digit-separators-pedantic.c
 create mode 100644 validation/c23-digit-separators.c

diff --git a/expression.c b/expression.c
index da2c14b458db..1b04d977a2d5 100644
--- a/expression.c
+++ b/expression.c
@@ -258,6 +258,157 @@ static unsigned long long parse_num(const char *nptr, char **end)
 	return strtoull(nptr, end, 0);
 }
 
+static int is_digit_for_base(int c, int base)
+{
+	if (base == 2)
+		return c == '0' || c == '1';
+	if (base == 8)
+		return c >= '0' && c <= '7';
+	if (base == 16)
+		return isxdigit((unsigned char)c);
+	return isdigit((unsigned char)c);
+}
+
+static int validate_digit_separators(struct expression *expr, const char *str)
+{
+	enum {
+		NUM_INT,
+		NUM_FRAC,
+		NUM_EXP_SIGN,
+		NUM_EXP,
+		NUM_SUFFIX,
+	} state = NUM_INT;
+	int is_hex = (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'));
+	int is_bin = (str[0] == '0' && (str[1] == 'b' || str[1] == 'B'));
+	int is_oct = (str[0] == '0' && !is_hex && !is_bin &&
+		      strchr(str, '.') == NULL && strchr(str, 'e') == NULL &&
+		      strchr(str, 'E') == NULL);
+	int base = is_hex ? 16 : is_bin ? 2 : is_oct ? 8 : 10;
+	int in_digits = 0;
+	int i = (is_hex || is_bin) ? 2 : 0;
+
+	for (; str[i]; i++) {
+		char c = str[i];
+
+		if (c == '\'') {
+			char prev = i > 0 ? str[i - 1] : 0;
+			char next = str[i + 1];
+
+			if (prev == '\'' || next == '\'') {
+				sparse_error(expr->pos,
+					     "adjacent digit separators");
+				return 0;
+			}
+			if (i == 2 && (is_hex || is_bin)) {
+				sparse_error(
+					expr->pos,
+					"digit separator after base indicator");
+				return 0;
+			}
+			if (prev == '.' || next == '.') {
+				sparse_error(
+					expr->pos,
+					"digit separator adjacent to decimal point");
+				return 0;
+			}
+			if (state == NUM_EXP_SIGN ||
+			    (state != NUM_SUFFIX &&
+			     ((is_hex && (next == 'p' || next == 'P')) ||
+			      (!is_hex && (next == 'e' || next == 'E')))) ||
+			    (!in_digits && state == NUM_EXP)) {
+				sparse_error(
+					expr->pos,
+					"digit separator adjacent to exponent");
+				return 0;
+			}
+			if (state == NUM_SUFFIX || !in_digits ||
+			    !is_digit_for_base(next, base)) {
+				sparse_error(
+					expr->pos,
+					"digit separator outside digit sequence");
+				return 0;
+			}
+			in_digits = 0;
+			continue;
+		}
+
+		switch (state) {
+		case NUM_INT:
+			if (is_digit_for_base(c, base)) {
+				in_digits = 1;
+			} else if (c == '.' && (base == 10 || base == 16)) {
+				state = NUM_FRAC;
+				in_digits = 0;
+			} else if ((base == 10 && (c == 'e' || c == 'E')) ||
+				   (base == 16 && (c == 'p' || c == 'P'))) {
+				state = NUM_EXP_SIGN;
+				base = 10;
+				in_digits = 0;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_FRAC:
+			if (is_digit_for_base(c, base)) {
+				in_digits = 1;
+			} else if ((base == 10 && (c == 'e' || c == 'E')) ||
+				   (base == 16 && (c == 'p' || c == 'P'))) {
+				state = NUM_EXP_SIGN;
+				base = 10;
+				in_digits = 0;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_EXP_SIGN:
+			state = NUM_EXP;
+			if (c == '+' || c == '-') {
+				in_digits = 0;
+			} else if (isdigit((unsigned char)c)) {
+				in_digits = 1;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_EXP:
+			if (isdigit((unsigned char)c)) {
+				in_digits = 1;
+			} else {
+				state = NUM_SUFFIX;
+				in_digits = 0;
+			}
+			break;
+		case NUM_SUFFIX:
+			break;
+		}
+	}
+	return 1;
+}
+
+static const char *strip_digit_separators(const char *str)
+{
+	size_t len;
+	char *buf, *dst;
+
+	if (!strchr(str, '\''))
+		return str;
+
+	len = strlen(str);
+	buf = __alloc_bytes(len + 1);
+	dst = buf;
+
+	while (*str) {
+		if (*str != '\'')
+			*dst++ = *str;
+		str++;
+	}
+	*dst = '\0';
+	return buf;
+}
+
 static void get_number_value(struct expression *expr, struct token *token)
 {
 	const char *str = token->number;
@@ -268,6 +419,19 @@ static void get_number_value(struct expression *expr, struct token *token)
 	int try_unsigned = 1;
 	int bits;
 
+	if (strchr(str, '\'')) {
+		if (!validate_digit_separators(expr, str)) {
+			expr->type = EXPR_VALUE;
+			expr->flags = CEF_SET_ICE;
+			expr->ctype = &bad_ctype;
+			return;
+		}
+		if (standard < STANDARD_C23 && Wpedantic)
+			warning(expr->pos,
+				"ISO C does not support digit separators before C23");
+		str = strip_digit_separators(str);
+	}
+
 	errno = 0;
 	value = parse_num(str, &end);
 	if (end == str)
@@ -385,6 +549,7 @@ Float:
 Enoint:
 	sparse_error(expr->pos, "constant %s is not a valid number", show_token(token));
 	expr->type = EXPR_VALUE;
+	expr->flags = CEF_SET_ICE;
 	expr->value = 0;
 	expr->ctype = &int_ctype;
 }
diff --git a/tokenize.c b/tokenize.c
index 76b0278925ed..b0d4059c8782 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -50,6 +50,7 @@ typedef struct {
 	int fd, offset, size;
 	int pos, line, nr;
 	int newline, whitespace;
+	int pushed_char;
 	struct token **tokenlist;
 	struct token *token;
 	unsigned char *buffer;
@@ -446,8 +447,14 @@ got_eof:
  */
 static inline int nextchar(stream_t *stream)
 {
-	int offset = stream->offset;
+	int offset;
 
+	if (stream->pushed_char != -2) {
+		int c = stream->pushed_char;
+		stream->pushed_char = -2;
+		return c;
+	}
+	offset = stream->offset;
 	if (offset < stream->size) {
 		int c = stream->buffer[offset++];
 		static const char special[256] = {
@@ -462,6 +469,19 @@ static inline int nextchar(stream_t *stream)
 	return nextchar_slow(stream);
 }
 
+static int peekchar(stream_t *stream)
+{
+	if (stream->pushed_char != -2)
+		return stream->pushed_char;
+	if (stream->offset < stream->size) {
+		int c = stream->buffer[stream->offset];
+		if (c != '\r' && c != '\\')
+			return c;
+	}
+	stream->pushed_char = nextchar_slow(stream);
+	return stream->pushed_char;
+}
+
 struct token eof_token_entry;
 
 static struct token *mark_eof(stream_t *stream)
@@ -558,8 +578,18 @@ static int get_one_number(int c, int next, stream_t *stream)
 	*p++ = c;
 	for (;;) {
 		long class =  cclass[next + 1];
-		if (!(class & (Dot | Digit | Letter)))
+		if (!(class & (Dot | Digit | Letter))) {
+			if (next == '\'') {
+				int after = peekchar(stream);
+				if (cclass[after + 1] & (Digit | Letter)) {
+					if (p != buffer_end)
+						*p++ = next;
+					next = nextchar(stream);
+					continue;
+				}
+			}
 			break;
+		}
 		if (p != buffer_end)
 			*p++ = next;
 		next = nextchar(stream);
@@ -987,6 +1017,7 @@ static struct token *setup_stream(stream_t *stream, int idx, int fd,
 	stream->offset = 0;
 	stream->size = buf_size;
 	stream->buffer = buf;
+	stream->pushed_char = -2;
 
 	begin = alloc_token(stream);
 	token_type(begin) = TOKEN_STREAMBEGIN;
diff --git a/validation/c23-digit-separators-error.c b/validation/c23-digit-separators-error.c
new file mode 100644
index 000000000000..10ddeffcd99c
--- /dev/null
+++ b/validation/c23-digit-separators-error.c
@@ -0,0 +1,36 @@
+int a = 1''0;
+int b = 0x'1;
+int b2 = 0b'1;
+int c = 1'u;
+int c2 = 123'u;
+double d = 1.'0;
+double e = 1'.';
+double f = 1e'0;
+double g = 1'e0;
+double h = 1e+'0;
+float i = 0x1p'0f;
+float j = 0x1.0p1'f;
+float k = 1.0'f;
+
+/*
+ * check-name: c23-digit-separators-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-digit-separators-error.c:1:10: error: Expected ; at end of declaration
+c23-digit-separators-error.c:1:10: error: got ''
+c23-digit-separators-error.c:2:9: error: digit separator after base indicator
+c23-digit-separators-error.c:3:10: error: digit separator after base indicator
+c23-digit-separators-error.c:4:9: error: digit separator outside digit sequence
+c23-digit-separators-error.c:5:10: error: digit separator outside digit sequence
+c23-digit-separators-error.c:6:12: error: digit separator adjacent to decimal point
+c23-digit-separators-error.c:7:13: error: Expected ; at end of declaration
+c23-digit-separators-error.c:7:13: error: got '.'
+c23-digit-separators-error.c:8:12: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:9:12: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:10:12: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:11:11: error: digit separator adjacent to exponent
+c23-digit-separators-error.c:12:11: error: digit separator outside digit sequence
+c23-digit-separators-error.c:13:11: error: digit separator outside digit sequence
+ * check-error-end
+ */
diff --git a/validation/c23-digit-separators-pedantic.c b/validation/c23-digit-separators-pedantic.c
new file mode 100644
index 000000000000..d1f374d805e1
--- /dev/null
+++ b/validation/c23-digit-separators-pedantic.c
@@ -0,0 +1,12 @@
+int a = 1'000;
+double b = 1'0.5;
+
+/*
+ * check-name: c23-digit-separators-pedantic
+ * check-command: sparse -std=c17 -Wpedantic -Wno-decl $file
+ *
+ * check-error-start
+c23-digit-separators-pedantic.c:1:9: warning: ISO C does not support digit separators before C23
+c23-digit-separators-pedantic.c:2:12: warning: ISO C does not support digit separators before C23
+ * check-error-end
+ */
diff --git a/validation/c23-digit-separators.c b/validation/c23-digit-separators.c
new file mode 100644
index 000000000000..65af74822cb6
--- /dev/null
+++ b/validation/c23-digit-separators.c
@@ -0,0 +1,29 @@
+#if 0
+0' '
+1'+'2
+#endif
+
+#if 1'000 + 2'000 == 3'000
+int pp = 1;
+#endif
+
+_Static_assert(1'000'000 == 1000000, "dec");
+_Static_assert(0xdead'beef == 0xdeadbeef, "hex");
+_Static_assert(0XCAFE'BABE == 0xCAFEBABE, "hex upper");
+_Static_assert(0b1010'0101 == 0xa5, "bin");
+_Static_assert(0B1111'0000 == 0xf0, "bin upper");
+_Static_assert(0'123'456 == 0123456, "oct");
+_Static_assert(1'000ULL == 1000ULL, "ull");
+
+static int a = 1'000'000;
+static unsigned int b = 0xdead'beef;
+static int c = 0b1010'0101;
+static int d = 0'123'456;
+static double e = 1'000.000'1;
+static double f = 1'2.3'4e1'0;
+static float g = 0x1'a.b'cp1'0f;
+
+/*
+ * check-name: c23-digit-separators
+ * check-command: sparse -std=c23 -Wno-decl $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 46/73] c23: Support unnamed parameters in function definitions
  2026-11-09 20:31 Bart Van Assche
                   ` (44 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
                   ` (26 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Allow function definitions to omit parameter names in C23 mode per ISO
C23 6.9.1 while rejecting unnamed parameters as an error in pre-C23
standards:
- Check for missing type specifiers before skipping local scope binding
  for unnamed parameters.
- Preserve argument side effects when inlining calls to functions with
  unnamed parameters without triggering unreplaced-symbol warnings.
- Advance parameter slot indices across unnamed parameters during
  linearization so subsequent named parameters keep their ABI slot index.
- Reject qualified, named, or multi-parameter uses of 'void'.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 inline.c                                 |  6 ++-
 linearize.c                              |  8 ++--
 parse.c                                  | 27 +++++++++-----
 validation/c23-unnamed-params-error.c    | 27 ++++++++++++++
 validation/c23-unnamed-params-pedantic.c | 12 ++++++
 validation/c23-unnamed-params.c          | 47 ++++++++++++++++++++++++
 validation/linear/c23-unnamed-params.c   | 27 ++++++++++++++
 7 files changed, 141 insertions(+), 13 deletions(-)
 create mode 100644 validation/c23-unnamed-params-error.c
 create mode 100644 validation/c23-unnamed-params-pedantic.c
 create mode 100644 validation/c23-unnamed-params.c
 create mode 100644 validation/linear/c23-unnamed-params.c

diff --git a/inline.c b/inline.c
index d94fba807d19..d3f27952138b 100644
--- a/inline.c
+++ b/inline.c
@@ -631,7 +631,7 @@ int inline_function(struct expression *expr, struct symbol *sym)
 	FOR_EACH_PTR(expr->args, arg) {
 		struct symbol *a = alloc_symbol(arg->pos, SYM_NODE);
 
-		if (name) {
+		if (name && name->ident) {
 			*a = *name;
 			a->bb_target = NULL;
 			a->pseudo = NULL;
@@ -639,6 +639,10 @@ int inline_function(struct expression *expr, struct symbol *sym)
 			add_symbol(&fn_symbol_list, a);
 			a->initializer = arg;
 			add_symbol(&arg_decl, a);
+		} else if (name) {
+			*a = *name;
+			a->initializer = arg;
+			add_symbol(&arg_decl, a);
 		} else {
 			// This may create a node of a node but it will
 			// be resolved later when the corresponding
diff --git a/linearize.c b/linearize.c
index 422cee820ddb..4179aea03af1 100644
--- a/linearize.c
+++ b/linearize.c
@@ -2607,12 +2607,14 @@ static struct entrypoint *linearize_fn(struct symbol *sym, struct symbol *base_t
 	add_one_insn(ep, entry);
 	ep->entry = entry;
 
-	concat_symbol_list(base_type->arguments, &ep->syms);
-
 	/* FIXME!! We should do something else about varargs.. */
 	i = 0;
 	FOR_EACH_PTR(base_type->arguments, arg) {
-		linearize_argument(ep, arg, ++i);
+		++i;
+		if (!arg->ident)
+			continue;
+		add_symbol(&ep->syms, arg);
+		linearize_argument(ep, arg, i);
 	} END_FOR_EACH_PTR(arg);
 
 	result = linearize_fn_statement(ep, stmt);
diff --git a/parse.c b/parse.c
index 5e526b0cc840..6af5bfba2392 100644
--- a/parse.c
+++ b/parse.c
@@ -1702,6 +1702,8 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 				specifier_conflict(token->pos,
 						   seen & s->op->test,
 						   token->ident);
+				ctx->ctype.base_type = &bad_ctype;
+				seen |= Set_S;
 				break;
 			}
 			seen |= s->op->set;
@@ -2772,10 +2774,12 @@ static struct token *parameter_type_list(struct token *token, struct symbol *fn)
 		sym = alloc_symbol(token->pos, SYM_NODE);
 		token = parameter_declaration(token, sym);
 		if (sym->ctype.base_type == &void_ctype) {
-			/* Special case: (void) */
-			if (!*list && !sym->ident)
+			/* Special case: sole unnamed unqualified (void) */
+			if (!*list && !sym->ident && match_op(token, ')') &&
+			    !(sym->ctype.modifiers & (MOD_QUALIFIER | MOD_STORAGE | MOD_ATOMIC)))
 				break;
-			warning(token->pos, "void parameter");
+			sparse_error(sym->pos, "'void' must be the only parameter");
+			sym->ctype.base_type = &bad_ctype;
 		}
 		add_symbol(list, sym);
 		if (!match_op(token, ','))
@@ -2917,18 +2921,23 @@ struct token *initializer(struct expression **tree, struct token *token)
 
 static void declare_argument(struct symbol *sym, struct symbol *fn)
 {
-	if (!sym->ident) {
-		sparse_error(sym->pos, "no identifier for function argument");
-		return;
-	}
 	if (sym->ctype.base_type == &incomplete_ctype) {
 		sym->ctype.base_type = &int_ctype;
 
 		if (Wimplicit_int) {
-			sparse_error(sym->pos, "missing type declaration for parameter '%s'",
-				show_ident(sym->ident));
+			if (sym->ident)
+				sparse_error(sym->pos, "missing type declaration for parameter '%s'",
+					show_ident(sym->ident));
+			else
+				sparse_error(sym->pos, "missing type declaration for parameter");
 		}
 	}
+	if (!sym->ident) {
+		if (standard < STANDARD_C23 && sym->ctype.base_type != &bad_ctype)
+			sparse_error(sym->pos,
+				     "omitting parameter name in function definition is a C23 feature");
+		return;
+	}
 	bind_symbol(sym, sym->ident, NS_SYMBOL);
 }
 
diff --git a/validation/c23-unnamed-params-error.c b/validation/c23-unnamed-params-error.c
new file mode 100644
index 000000000000..6630f96b53b5
--- /dev/null
+++ b/validation/c23-unnamed-params-error.c
@@ -0,0 +1,27 @@
+static void f_implicit(const)
+{
+}
+
+static void f_void1(void, int)
+{
+}
+
+static void f_void2(int, void)
+{
+}
+
+static void f_cvoid(const void)
+{
+}
+
+/*
+ * check-name: c23-unnamed-params-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-unnamed-params-error.c:1:24: error: missing type declaration for parameter
+c23-unnamed-params-error.c:5:21: error: 'void' must be the only parameter
+c23-unnamed-params-error.c:9:26: error: 'void' must be the only parameter
+c23-unnamed-params-error.c:13:21: error: 'void' must be the only parameter
+ * check-error-end
+ */
diff --git a/validation/c23-unnamed-params-pedantic.c b/validation/c23-unnamed-params-pedantic.c
new file mode 100644
index 000000000000..aa2a204628fe
--- /dev/null
+++ b/validation/c23-unnamed-params-pedantic.c
@@ -0,0 +1,12 @@
+static void f1(int)
+{
+}
+
+/*
+ * check-name: c23-unnamed-params-pedantic
+ * check-command: sparse -std=c11 $file
+ *
+ * check-error-start
+c23-unnamed-params-pedantic.c:1:16: error: omitting parameter name in function definition is a C23 feature
+ * check-error-end
+ */
diff --git a/validation/c23-unnamed-params.c b/validation/c23-unnamed-params.c
new file mode 100644
index 000000000000..ddecb8e9e0b9
--- /dev/null
+++ b/validation/c23-unnamed-params.c
@@ -0,0 +1,47 @@
+static void f1(int)
+{
+}
+
+static void f2(int, double)
+{
+}
+
+static int f3(int a, double, int b)
+{
+	return a + b;
+}
+
+static void f4(void (*)(int))
+{
+}
+
+static void f5(int [static 4], const int)
+{
+}
+
+static inline int inl_unnamed(int a, int, int b)
+{
+	return a + b;
+}
+
+static int bump(int *cnt)
+{
+	return (*cnt)++;
+}
+
+static int test_calls(void)
+{
+	int arr[4] = { 1, 2, 3, 4 };
+	int cnt = 0;
+
+	f1(1);
+	f2(2, 3.0);
+	f4(f1);
+	f5(arr, 7);
+	return f3(10, 0.5, 20) + inl_unnamed(1, bump(&cnt), 2) + cnt;
+}
+
+/*
+ * check-name: c23-unnamed-params
+ * check-command: sparse -std=c23 -pedantic $file
+ */
diff --git a/validation/linear/c23-unnamed-params.c b/validation/linear/c23-unnamed-params.c
new file mode 100644
index 000000000000..add966d3ea71
--- /dev/null
+++ b/validation/linear/c23-unnamed-params.c
@@ -0,0 +1,27 @@
+int f3(int a, double, int b);
+int f3(int a, double, int b)
+{
+	return a + b;
+}
+
+static inline int inl(int a, int, int b)
+{
+	return a + b;
+}
+
+extern int side_effect(void);
+
+int caller(void);
+int caller(void)
+{
+	return inl(10, side_effect(), 20);
+}
+
+/*
+ * check-name: linear-c23-unnamed-params
+ * check-command: test-linearize -m64 -std=c23 $file
+ *
+ * check-output-ignore
+ * check-output-contains: add\\.32 *%r[0-9]* <- %arg1, %arg3
+ * check-output-contains: call\\.32 *%r[0-9]* <- side_effect
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 47/73] c23: Support variadic functions without named parameters
  2026-11-09 20:31 Bart Van Assche
                   ` (45 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
                   ` (25 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Allow variadic function prototypes and definitions of the form 'f(...)'
without a preceding named parameter and support single-argument va_start
in C23 mode per ISO C23 6.7.6.3 and 7.16.1.4:
- Replace the predefined 2-argument __builtin_va_start macro in lib.c
  with builtin handlers for __builtin_va_start and
  __builtin_c23_va_start in builtin.c so <stdarg.h> works with both
  va_start(ap) and va_start(ap, ...).
- Validate that va_start appears inside a variadic function on a
  modifiable va_list lvalue, leave extra C23 va_start arguments
  unevaluated, and lower va_start to initialize ap.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 builtin.c                          | 77 ++++++++++++++++++++++++++++++
 lib.c                              |  1 -
 parse.c                            |  5 +-
 validation/c11-va-start-error.c    | 20 ++++++++
 validation/c23-variadic-error.c    | 41 ++++++++++++++++
 validation/c23-variadic-no-named.c | 47 ++++++++++++++++++
 validation/linear/c23-va-start.c   | 22 +++++++++
 7 files changed, 210 insertions(+), 3 deletions(-)
 create mode 100644 validation/c11-va-start-error.c
 create mode 100644 validation/c23-variadic-error.c
 create mode 100644 validation/c23-variadic-no-named.c
 create mode 100644 validation/linear/c23-va-start.c

diff --git a/builtin.c b/builtin.c
index 78a971ce4bb5..0be03c51aedd 100644
--- a/builtin.c
+++ b/builtin.c
@@ -638,6 +638,81 @@ static struct symbol_op object_size_op = {
 	.expand = expand_object_size,
 };
 
+static int args_va_start(struct expression *expr)
+{
+	struct symbol *sym = expr->fn->ctype;
+	bool is_c23 = !strcmp(show_ident(sym->ident), "__builtin_c23_va_start");
+	int n = expression_list_size(expr->args);
+	struct expression *arg;
+	int i = 0;
+
+	if (n < 1 || (!is_c23 && standard < STANDARD_C23 && n < 2)) {
+		expression_error(expr, "not enough arguments for %s",
+				 show_ident(sym->ident));
+		return 0;
+	}
+	if (!is_c23 && n > 2) {
+		expression_error(expr, "too many arguments for %s",
+				 show_ident(sym->ident));
+		return 0;
+	}
+	FOR_EACH_PTR(expr->args, arg) {
+		if (i == 1 && !is_c23) {
+			if (!evaluate_expression(arg))
+				return 0;
+		}
+		i++;
+	} END_FOR_EACH_PTR(arg);
+	return 1;
+}
+
+static int evaluate_va_start(struct expression *expr)
+{
+	struct expression *ap = first_expression(expr->args);
+	struct symbol *fntype = current_fn ? current_fn->ctype.base_type : NULL;
+	struct expression *assign, *rhs;
+
+	if (current_fn && current_fn->type == SYM_FN)
+		fntype = current_fn;
+	if (!fntype || !fntype->variadic) {
+		expression_error(expr, "'va_start' used in function with fixed args");
+		return 1;
+	}
+
+	if (!evaluate_expression(ap) || !valid_type(ap->ctype))
+		return 1;
+	{
+		struct symbol *atype = ap->ctype;
+		while (atype && atype->type == SYM_NODE)
+			atype = atype->ctype.base_type;
+		if (atype != &ptr_ctype) {
+			expression_error(expr, "incorrect type in 'va_start'");
+			return 1;
+		}
+	}
+
+	rhs = alloc_expression(expr->pos, EXPR_VALUE);
+	rhs->ctype = &ptr_ctype;
+	rhs->value = 0;
+
+	assign = alloc_expression(expr->pos, EXPR_ASSIGNMENT);
+	assign->op = '=';
+	assign->left = ap;
+	assign->right = rhs;
+	evaluate_expression(assign);
+
+	expr->type = EXPR_CAST;
+	expr->cast_type = &void_ctype;
+	expr->cast_expression = assign;
+	expr->ctype = &void_ctype;
+	return 1;
+}
+
+static struct symbol_op va_start_op = {
+	.args = args_va_start,
+	.evaluate = evaluate_va_start,
+};
+
 static int evaluate_strlen(struct expression *expr)
 {
 	struct expression *arg = first_expression(expr->args);
@@ -876,7 +951,9 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__builtin_usub_overflow", &bool_ctype, 0, { &uint_ctype, &uint_ctype, &uint_ptr_ctype }},
 	{ "__builtin_usubl_overflow", &bool_ctype, 0, { &ulong_ctype, &ulong_ctype, &ulong_ptr_ctype }},
 	{ "__builtin_usubll_overflow", &bool_ctype, 0, { &ullong_ctype, &ullong_ctype, &ullong_ptr_ctype }},
+	{ "__builtin_c23_va_start", &void_ctype, 1, .op = &va_start_op },
 	{ "__builtin_va_arg_pack_len", size_t_ctype, 0 },
+	{ "__builtin_va_start", &void_ctype, 1, .op = &va_start_op },
 	{ "__builtin_vprintf", &int_ctype, 0, { &const_string_ctype, va_list_ctype }},
 	{ "__builtin_vsnprintf", &int_ctype, 0, { &string_ctype, size_t_ctype, &const_string_ctype, va_list_ctype }},
 	{ "__builtin_vsprintf", &int_ctype, 0, { &string_ctype, &const_string_ctype, va_list_ctype }},
diff --git a/lib.c b/lib.c
index 6670e3b46657..3c41c41855c4 100644
--- a/lib.c
+++ b/lib.c
@@ -299,7 +299,6 @@ static void create_builtin_stream(void)
 	add_pre_buffer("#add_system \"%s/include-fixed\"\n", gcc_base_dir);
 
 	add_pre_buffer("#define __builtin_stdarg_start(a,b) ((a) = (__builtin_va_list)(&(b)))\n");
-	add_pre_buffer("#define __builtin_va_start(a,b) ((a) = (__builtin_va_list)(&(b)))\n");
 	add_pre_buffer("#define __builtin_ms_va_start(a,b) ((a) = (__builtin_ms_va_list)(&(b)))\n");
 	add_pre_buffer("#define __builtin_va_arg(arg,type)  ({ type __va_arg_ret = *(type *)(arg); arg += sizeof(type); __va_arg_ret; })\n");
 	add_pre_buffer("#define __builtin_va_alist (*(void *)0)\n");
diff --git a/parse.c b/parse.c
index 6af5bfba2392..78ad5f09e2ad 100644
--- a/parse.c
+++ b/parse.c
@@ -1926,8 +1926,9 @@ static enum kind which_func(struct token *token,
 	}
 
 	if (next->special == SPECIAL_ELLIPSIS) {
-		warning(next->pos,
-			"variadic functions must have one named argument");
+		if (standard < STANDARD_C23)
+			warning(next->pos,
+				"variadic functions must have one named argument");
 		return Proto;
 	}
 
diff --git a/validation/c11-va-start-error.c b/validation/c11-va-start-error.c
new file mode 100644
index 000000000000..334ad0cd48ad
--- /dev/null
+++ b/validation/c11-va-start-error.c
@@ -0,0 +1,20 @@
+#include <stdarg.h>
+
+static void f(int n, ...)
+{
+	va_list ap;
+	(void)n;
+	va_start(ap);
+	__builtin_va_start(ap);
+}
+
+/*
+ * check-name: c11-va-start-error
+ * check-command: sparse -std=c11 $file
+ *
+ * check-error-start
+c11-va-start-error.c:7:9: error: macro "va_start" requires 2 arguments, but only 1 given
+c11-va-start-error.c:7:9: error: undefined identifier 'va_start'
+c11-va-start-error.c:8:27: error: not enough arguments for __builtin_va_start
+ * check-error-end
+ */
diff --git a/validation/c23-variadic-error.c b/validation/c23-variadic-error.c
new file mode 100644
index 000000000000..85032625fff3
--- /dev/null
+++ b/validation/c23-variadic-error.c
@@ -0,0 +1,41 @@
+#include <stdarg.h>
+
+static const va_list const_ap;
+
+static void fixed_fn(int x)
+{
+	va_list ap;
+	(void)x;
+	va_start(ap);
+}
+
+static void variadic_errors(...)
+{
+	int not_ap = 0;
+	void *void_ptr = nullptr;
+	va_list ap;
+
+	__builtin_va_start();
+	__builtin_va_start(ap, 1, 2);
+	va_start(not_ap);
+	va_start(void_ptr);
+	va_start(const_ap);
+	(void)(va_start(ap) + 1);
+}
+
+/*
+ * check-name: c23-variadic-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-variadic-error.c:9:9: error: 'va_start' used in function with fixed args
+c23-variadic-error.c:18:27: error: not enough arguments for __builtin_va_start
+c23-variadic-error.c:19:27: error: too many arguments for __builtin_va_start
+c23-variadic-error.c:20:9: error: incorrect type in 'va_start'
+c23-variadic-error.c:21:9: error: incorrect type in 'va_start'
+c23-variadic-error.c:22:9: error: assignment to const expression
+c23-variadic-error.c:23:29: error: incompatible types for operation (+):
+c23-variadic-error.c:23:29:    void
+c23-variadic-error.c:23:29:    int
+ * check-error-end
+ */
diff --git a/validation/c23-variadic-no-named.c b/validation/c23-variadic-no-named.c
new file mode 100644
index 000000000000..35e38f7ae177
--- /dev/null
+++ b/validation/c23-variadic-no-named.c
@@ -0,0 +1,47 @@
+#include <stdarg.h>
+
+static void v1(...)
+{
+}
+
+static int sum_two(...)
+{
+	va_list ap, copy;
+	int a, b;
+
+	va_start(ap);
+	va_copy(copy, ap);
+	a = va_arg(ap, int);
+	b = va_arg(ap, int);
+	va_end(copy);
+	va_end(ap);
+	return a + b;
+}
+
+static int sum_after_n(int n, ...)
+{
+	va_list ap;
+	int a;
+
+	va_start(ap, n);
+	va_end(ap);
+	va_start(ap, undeclared_ignored_token, 1, 2);
+	a = va_arg(ap, int);
+	va_end(ap);
+	return n + a;
+}
+
+static int test(void)
+{
+	int (*fp)(...) = sum_two;
+
+	v1();
+	v1(1);
+	v1(1, 2, "three");
+	return fp(10, 20) + sum_after_n(5, 7);
+}
+
+/*
+ * check-name: c23-variadic-no-named
+ * check-command: sparse -std=c23 -pedantic $file
+ */
diff --git a/validation/linear/c23-va-start.c b/validation/linear/c23-va-start.c
new file mode 100644
index 000000000000..2d5b11d58aa4
--- /dev/null
+++ b/validation/linear/c23-va-start.c
@@ -0,0 +1,22 @@
+#include <stdarg.h>
+
+int first_arg(...);
+int first_arg(...)
+{
+	va_list ap;
+	int v;
+
+	va_start(ap);
+	v = va_arg(ap, int);
+	va_end(ap);
+	return v;
+}
+
+/*
+ * check-name: linear-c23-va-start
+ * check-command: test-linearize -m64 -std=c23 -Wuninitialized $file
+ *
+ * check-output-ignore
+ * check-output-excludes: __builtin_va_start
+ * check-output-excludes: __builtin_c23_va_start
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 48/73] c23: Support labels before declarations and at block ends
  2026-11-09 20:31 Bart Van Assche
                   ` (46 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
                   ` (24 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Allow named, case, and default labels (including chained labels) to
precede declarations and closing '}' braces inside compound statements
per ISO C23 6.8.1 and 6.8.2:
- Handle label chains at the block-item level in statement_list() by
  attaching a synthesized empty statement when a label precedes a
  declaration, static_assert, or '}', and emit pedantic warnings in
  pre-C23 modes.
- Keep substatements of selection and iteration statements restricted to
  true statements in statement() so invalid forms such as
  'if (c) int x = 1;' and '{ if (c) lbl: }' are rejected cleanly.
- Recognize labels whose identifier shadows an outer typedef name.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 parse.c                          | 206 ++++++++++++++++++++++---------
 validation/c23-labels-error.c    |  48 +++++++
 validation/c23-labels-gnu.c      |  15 +++
 validation/c23-labels-pedantic.c |  15 +++
 validation/c23-labels.c          |  67 ++++++++++
 5 files changed, 296 insertions(+), 55 deletions(-)
 create mode 100644 validation/c23-labels-error.c
 create mode 100644 validation/c23-labels-gnu.c
 create mode 100644 validation/c23-labels-pedantic.c
 create mode 100644 validation/c23-labels.c

diff --git a/parse.c b/parse.c
index 78ad5f09e2ad..1e91825f8d38 100644
--- a/parse.c
+++ b/parse.c
@@ -62,8 +62,6 @@ static declarator_t autotype_specifier;
 static struct token *parse_if_statement(struct token *token, struct statement *stmt);
 static struct token *parse_return_statement(struct token *token, struct statement *stmt);
 static struct token *parse_loop_iterator(struct token *token, struct statement *stmt);
-static struct token *parse_default_statement(struct token *token, struct statement *stmt);
-static struct token *parse_case_statement(struct token *token, struct statement *stmt);
 static struct token *parse_switch_statement(struct token *token, struct statement *stmt);
 static struct token *parse_for_statement(struct token *token, struct statement *stmt);
 static struct token *parse_while_statement(struct token *token, struct statement *stmt);
@@ -315,11 +313,9 @@ static struct symbol_op loop_iter_op = {
 };
 
 static struct symbol_op default_op = {
-	.statement = parse_default_statement,
 };
 
 static struct symbol_op case_op = {
-	.statement = parse_case_statement,
 };
 
 static struct symbol_op switch_op = {
@@ -2518,32 +2514,6 @@ static struct token *parse_if_statement(struct token *token, struct statement *s
 	return statement(token->next, &stmt->if_false);
 }
 
-static inline struct token *case_statement(struct token *token, struct statement *stmt)
-{
-	stmt->type = STMT_CASE;
-	token = expect(token, ':', "after default/case");
-	add_case_statement(stmt);
-	if (match_op(token, '}')) {
-		warning(token->pos, "statement expected after case label");
-		stmt->case_statement = alloc_statement(token->pos, STMT_NONE);
-		return token;
-	}
-	return statement(token, &stmt->case_statement);
-}
-
-static struct token *parse_case_statement(struct token *token, struct statement *stmt)
-{
-	token = parse_expression(token->next, &stmt->case_expression);
-	if (match_op(token, SPECIAL_ELLIPSIS))
-		token = parse_expression(token->next, &stmt->case_to);
-	return case_statement(token, stmt);
-}
-
-static struct token *parse_default_statement(struct token *token, struct statement *stmt)
-{
-	return case_statement(token->next, stmt);
-}
-
 static struct token *parse_loop_iterator(struct token *token, struct statement *stmt)
 {
 	struct symbol *target = lookup_symbol(token->ident, NS_ITERATOR);
@@ -2646,46 +2616,129 @@ static struct token *handle_label_attributes(struct token *token, struct symbol
 	return token;
 }
 
+static bool is_label_token(struct token *token)
+{
+	struct token *p = token;
+	struct symbol *s;
+
+	if (token_type(p) != TOKEN_IDENT)
+		return false;
+	if (p->ident == &case_ident || p->ident == &default_ident)
+		return true;
+	s = lookup_keyword(p->ident, NS_KEYWORD);
+	if (s && s->op->statement)
+		return false;
+	if (p->ident->reserved)
+		return false;
+	return match_op(p->next, ':');
+}
+
+static struct token *parse_one_label(struct token *token,
+				     struct statement **stmtp,
+				     bool *is_case)
+{
+	struct decl_state attr_state = { };
+	struct statement *stmt;
+
+	*stmtp = NULL;
+	*is_case = false;
+
+	if (token->ident == &case_ident) {
+		stmt = alloc_statement(token->pos, STMT_CASE);
+		token = parse_expression(token->next, &stmt->case_expression);
+		if (match_op(token, SPECIAL_ELLIPSIS))
+			token = parse_expression(token->next, &stmt->case_to);
+		token = expect(token, ':', "after default/case");
+		add_case_statement(stmt);
+		*stmtp = stmt;
+		*is_case = true;
+		return token;
+	}
+	if (token->ident == &default_ident) {
+		stmt = alloc_statement(token->pos, STMT_CASE);
+		token = expect(token->next, ':', "after default/case");
+		add_case_statement(stmt);
+		*stmtp = stmt;
+		*is_case = true;
+		return token;
+	}
+
+	{
+		struct position pos = token->pos;
+		struct symbol *s = label_symbol(token, 0);
+		s->label_modifiers |= attr_state.ctype.modifiers;
+		token = handle_label_attributes(token->next->next, s);
+		if (s->stmt) {
+			sparse_error(pos, "label '%s' redefined",
+				     show_ident(s->ident));
+			return token;
+		}
+		stmt = alloc_statement(pos, STMT_LABEL);
+		stmt->label_identifier = s;
+		stmt->label_scope = label_scope;
+		if (s->label_scope) {
+			if (!is_in_scope(label_scope, s->label_scope))
+				warn_label_usage(stmt->pos, s->label_pos,
+						 s->ident);
+		}
+		s->stmt = stmt;
+		*stmtp = stmt;
+		return token;
+	}
+}
+
 static struct token *statement(struct token *token, struct statement **tree)
 {
 	struct statement *stmt = alloc_statement(token->pos, STMT_NONE);
 
+	if (is_label_token(token)) {
+		struct statement *lbl = NULL;
+		bool is_case = false;
+
+		token = parse_one_label(token, &lbl, &is_case);
+		if (!lbl)
+			return statement(token, tree);
+		*tree = lbl;
+		tree = is_case ? &lbl->case_statement : &lbl->label_statement;
+		if (match_op(token, '}')) {
+			sparse_error(token->pos,
+				     is_case ? "statement expected after case label" :
+					       "statement expected after label");
+			*tree = alloc_statement(token->pos, STMT_NONE);
+			return token;
+		}
+		return statement(token, tree);
+	}
+
 	*tree = stmt;
 	if (token_type(token) == TOKEN_IDENT) {
 		struct symbol *s = lookup_keyword(token->ident, NS_KEYWORD);
 		if (s && s->op->statement)
 			return s->op->statement(token, stmt);
-
-		if (match_op(token->next, ':')) {
-			struct symbol *s = label_symbol(token, 0);
-			token = handle_label_attributes(token->next->next, s);
-			if (s->stmt) {
-				sparse_error(stmt->pos, "label '%s' redefined", show_ident(s->ident));
-				// skip the label to avoid multiple definitions
-				return statement(token, tree);
-			}
-			stmt->type = STMT_LABEL;
-			stmt->label_identifier = s;
-			stmt->label_scope = label_scope;
-			if (s->label_scope) {
-				if (!is_in_scope(label_scope, s->label_scope))
-					warn_label_usage(stmt->pos, s->label_pos, s->ident);
-			}
-			s->stmt = stmt;
-			if (match_op(token, '}')) {
-				warning(token->pos, "statement expected after label");
-				stmt->label_statement = alloc_statement(token->pos, STMT_NONE);
-				return token;
-			}
-			return statement(token, &stmt->label_statement);
-		}
 	}
 
 	if (match_op(token, '{')) {
 		token = compound_statement(token->next, stmt);
 		return expect(token, '}', "at end of compound statement");
 	}
-			
+
+	if (match_ident(token, &_Static_assert_ident) ||
+	    (standard >= STANDARD_C23 &&
+	     match_ident(token, &static_assert_ident))) {
+		sparse_error(token->pos, "declaration is not a statement");
+		stmt->type = STMT_NONE;
+		return parse_static_assert(token, NULL);
+	}
+
+	if (lookup_type(token)) {
+		sparse_error(token->pos, "declaration is not a statement");
+		stmt->type = STMT_DECLARATION;
+		start_block_scope();
+		token = external_declaration(token, &stmt->declaration, NULL);
+		end_block_scope();
+		return token;
+	}
+
 	stmt->type = STMT_EXPRESSION;
 	return expression_statement(token, &stmt->expression);
 }
@@ -2714,6 +2767,49 @@ static struct token * statement_list(struct token *token, struct statement_list
 		token = label_statement(token->next);
 	for (;;) {
 		struct statement * stmt;
+		struct statement *label_head = NULL;
+		struct statement **label_tail = &label_head;
+		bool last_is_case = false;
+
+		while (is_label_token(token)) {
+			struct statement *lbl = NULL;
+			bool is_case = false;
+
+			token = parse_one_label(token, &lbl, &is_case);
+			if (!lbl)
+				continue;
+			*label_tail = lbl;
+			label_tail = is_case ? &lbl->case_statement :
+					       &lbl->label_statement;
+			last_is_case = is_case;
+		}
+		if (label_head) {
+			if (eof_token(token) || match_op(token, '}')) {
+				if (standard < STANDARD_C23)
+					warning(token->pos,
+						last_is_case ? "statement expected after case label" :
+							       "statement expected after label");
+				*label_tail = alloc_statement(token->pos, STMT_NONE);
+				add_statement(list, label_head);
+				break;
+			}
+			if (match_ident(token, &_Static_assert_ident) ||
+			    (standard >= STANDARD_C23 &&
+			     match_ident(token, &static_assert_ident)) ||
+			    lookup_type(token)) {
+				if (standard < STANDARD_C23 && Wpedantic)
+					warning(token->pos,
+						"a label can only be part of a statement and a declaration is not a statement");
+				*label_tail = alloc_statement(token->pos, STMT_NONE);
+				add_statement(list, label_head);
+			} else {
+				seen_statement = Wdeclarationafterstatement;
+				token = statement(token, label_tail);
+				add_statement(list, label_head);
+				continue;
+			}
+		}
+
 		if (eof_token(token))
 			break;
 		if (match_op(token, '}'))
diff --git a/validation/c23-labels-error.c b/validation/c23-labels-error.c
new file mode 100644
index 000000000000..55ee5bfd5b47
--- /dev/null
+++ b/validation/c23-labels-error.c
@@ -0,0 +1,48 @@
+static void test_dup(void)
+{
+dup:
+	;
+dup:
+	;
+}
+
+static void test_if_decl(int c)
+{
+	if (c)
+		int x = 1;
+}
+
+static void test_if_label_decl(int c)
+{
+	if (c)
+	lbl1:
+		int y = 2;
+}
+
+static void test_if_label_end(int c)
+{
+	if (c)
+	lbl2:
+}
+
+static void test_outside_switch(void)
+{
+	case 1:
+		;
+	default:
+		;
+}
+
+/*
+ * check-name: c23-labels-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-labels-error.c:5:1: error: label 'dup' redefined
+c23-labels-error.c:12:17: error: declaration is not a statement
+c23-labels-error.c:19:17: error: declaration is not a statement
+c23-labels-error.c:26:1: error: statement expected after label
+c23-labels-error.c:30:9: error: not in switch scope
+c23-labels-error.c:32:9: error: not in switch scope
+ * check-error-end
+ */
diff --git a/validation/c23-labels-gnu.c b/validation/c23-labels-gnu.c
new file mode 100644
index 000000000000..15dcb20be1d8
--- /dev/null
+++ b/validation/c23-labels-gnu.c
@@ -0,0 +1,15 @@
+static int test_gnu_case_range(int c)
+{
+	switch (c) {
+	case 1 ... 5:
+		int a = 3;
+		return a;
+	case 6 ... 9:
+	}
+	return 0;
+}
+
+/*
+ * check-name: c23-labels-gnu
+ * check-command: sparse -std=gnu23 -Wno-decl $file
+ */
diff --git a/validation/c23-labels-pedantic.c b/validation/c23-labels-pedantic.c
new file mode 100644
index 000000000000..6048f2e48e96
--- /dev/null
+++ b/validation/c23-labels-pedantic.c
@@ -0,0 +1,15 @@
+static void test_label_decl(void)
+{
+	goto lbl;
+lbl:
+	int x = 42;
+}
+
+/*
+ * check-name: c23-labels-pedantic
+ * check-command: sparse -std=c11 -pedantic -Wno-decl $file
+ *
+ * check-error-start
+c23-labels-pedantic.c:5:9: warning: a label can only be part of a statement and a declaration is not a statement
+ * check-error-end
+ */
diff --git a/validation/c23-labels.c b/validation/c23-labels.c
new file mode 100644
index 000000000000..b4d90edbe666
--- /dev/null
+++ b/validation/c23-labels.c
@@ -0,0 +1,67 @@
+typedef int T_shadow;
+
+static int test_label_decl(void)
+{
+	goto lbl;
+lbl:
+lbl_chain:
+	int x = 42;
+	if (!x)
+		goto lbl_chain;
+	return x;
+}
+
+static int test_case_decl(int c)
+{
+	switch (c) {
+	case 1:
+		int a = 10;
+		return a;
+	default:
+		int b = 20;
+		return b;
+	}
+}
+
+static void test_label_end(int c)
+{
+	if (c == 1)
+		goto end1;
+	if (c == 2)
+		goto end2;
+end1:
+end2:
+}
+
+static void test_case_end(int c)
+{
+	switch (c) {
+	case 0:
+	case 1:
+	}
+	switch (c) {
+	default:
+	}
+}
+
+static void test_label_static_assert(void)
+{
+	goto lbl2;
+lbl2:
+	static_assert(1, "");
+}
+
+static int test_typedef_shadow_label(int c)
+{
+	if (c)
+		goto T_shadow;
+	return 0;
+T_shadow:
+	int v = 1;
+	return v;
+}
+
+/*
+ * check-name: c23-labels
+ * check-command: sparse -std=c23 -pedantic -Wno-decl $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 49/73] c23: Support auto type inference
  2026-11-09 20:31 Bart Van Assche
                   ` (47 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
                   ` (23 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add ISO C23 'auto' type inference when 'auto' appears without a type
specifier, while preserving 'auto' as a block-scope storage-class
specifier when combined with an explicit type:
- Infer the variable type from its assignment-expression initializer
  after array-to-pointer, function-to-pointer, and lvalue conversion so
  top-level qualifiers and _Atomic are stripped per C23 6.7.10p2.
- Bind C23 'auto' symbols before parsing their initializers and mark
  them underspecified during initializer parsing and evaluation so
  self-references are diagnosed per C23 6.2.1p7.
- Diagnose underspecified redeclarations in the same scope per C23
  6.7p12, braced initializers, typedefs, empty declarations, type names,
  parameters, struct/union members, and file-scope 'auto' specifiers.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                  |  14 +++-
 parse.c                     | 129 +++++++++++++++++++++++++++++++++---
 symbol.c                    |  59 ++++++++++++++++-
 symbol.h                    |   3 +
 validation/c23-auto-error.c |  68 +++++++++++++++++++
 validation/c23-auto.c       | 107 ++++++++++++++++++++++++++++++
 6 files changed, 365 insertions(+), 15 deletions(-)
 create mode 100644 validation/c23-auto-error.c
 create mode 100644 validation/c23-auto.c

diff --git a/evaluate.c b/evaluate.c
index c0d039105881..1b1a72577d18 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -87,6 +87,12 @@ static struct symbol *evaluate_symbol_expression(struct expression *expr)
 		expression_error(expr, "undefined identifier '%s'", show_ident(expr->symbol_name));
 		return NULL;
 	}
+	if (sym->underspecified) {
+		expression_error(expr,
+				 "underspecified symbol '%s' used in its own initializer",
+				 show_ident(sym->ident));
+		return NULL;
+	}
 
 	examine_symbol_type(sym);
 
@@ -3853,8 +3859,14 @@ static struct symbol *evaluate_symbol(struct symbol *sym)
 		return NULL;
 
 	/* Evaluate the initializers */
-	if (sym->initializer)
+	if (sym->initializer) {
+		bool mark_underspec = sym->c23_auto;
+		if (mark_underspec)
+			sym->underspecified = 1;
 		evaluate_initializer(sym, &sym->initializer);
+		if (mark_underspec)
+			sym->underspecified = 0;
+	}
 
 	/* And finally, evaluate the body of the symbol too */
 	if (base_type->type == SYM_FN) {
diff --git a/parse.c b/parse.c
index 1e91825f8d38..324da8e40423 100644
--- a/parse.c
+++ b/parse.c
@@ -57,7 +57,7 @@ static declarator_t
 	attribute_specifier, typeof_specifier, typeof_unqual_specifier,
 	storage_specifier, thread_specifier;
 static declarator_t generic_qualifier;
-static declarator_t autotype_specifier;
+static declarator_t autotype_specifier, auto_specifier;
 
 static struct token *parse_if_statement(struct token *token, struct statement *stmt);
 static struct token *parse_return_statement(struct token *token, struct statement *stmt);
@@ -143,7 +143,7 @@ static struct symbol_op alignas_op = {
 
 static struct symbol_op auto_op = {
 	.type = KW_MODIFIER,
-	.declarator = storage_specifier,
+	.declarator = auto_specifier,
 };
 
 static struct symbol_op register_op = {
@@ -472,7 +472,7 @@ static struct init_keyword {
 #define U(I, O,...)	N("__" I,O,##__VA_ARGS__),	\
 			N("__" I "__",O,##__VA_ARGS__)
 	/* Storage classes */
-	N("auto",		&auto_op,	.mods = MOD_AUTO),
+	N("auto",		&auto_op),
 	N("register",		&register_op,	.mods = MOD_REGISTER),
 	N("static",		&static_op,	.mods = MOD_STATIC),
 	N("extern",		&extern_op,	.mods = MOD_EXTERN),
@@ -1202,6 +1202,15 @@ static struct token *autotype_specifier(struct token *token, struct symbol *sym,
 	return token;
 }
 
+static struct token *auto_specifier(struct token *token, struct symbol *sym,
+				    struct decl_state *ctx)
+{
+	if (ctx->has_auto)
+		sparse_error(token->pos, "duplicate auto");
+	ctx->has_auto = 1;
+	return token;
+}
+
 static struct token *ignore_attribute(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
 	struct expression *expr = NULL;
@@ -1732,6 +1741,23 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 		}
 	}
 
+	if (ctx->has_auto) {
+		if (seen & (Set_S | Set_Any)) {
+			if (ctx->storage_class)
+				sparse_error(token->pos,
+					     "multiple storage classes");
+			else
+				ctx->storage_class = MOD_AUTO;
+		} else {
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(token->pos,
+					"'auto' type inference is a C23 feature");
+			ctx->ctype.base_type = &autotype_ctype;
+			ctx->autotype = 1;
+			seen |= Set_S;
+		}
+	}
+
 	if (!(seen & Set_S)) {	/* not set explicitly? */
 		struct symbol *base = &incomplete_ctype;
 		if (seen & Set_Any)
@@ -2085,6 +2111,14 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 		decl->ctype.modifiers |= mod;
 		decl->cleanup = ctx.cleanup;
 		decl->endpos = token->pos;
+		if (ctx.has_auto) {
+			sparse_error(
+				decl->pos,
+				"struct or union member '%s' cannot be declared auto",
+				show_ident(decl->ident));
+			if (ctx.autotype)
+				decl->ctype.base_type = &bad_ctype;
+		}
 		if (decl->ctype.modifiers & MOD_TLS)
 			sparse_error(
 				decl->pos,
@@ -2136,6 +2170,14 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 		sparse_error(sym->pos,
 			     "alignment specified for parameter '%s'",
 			     show_ident(sym->ident));
+	if (ctx.has_auto || ctx.autotype) {
+		sparse_error(sym->pos,
+			     "parameter '%s' cannot be declared %s",
+			     show_ident(sym->ident),
+			     ctx.has_auto ? "auto" : "__auto_type");
+		if (ctx.autotype)
+			sym->ctype.base_type = &bad_ctype;
+	}
 	if (sym->ctype.modifiers & MOD_TLS)
 		sparse_error(sym->pos,
 			     "parameter '%s' cannot be thread-local",
@@ -2152,6 +2194,11 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 	token = declaration_specifiers(token, &ctx);
 	if (ctx.has_alignas)
 		sparse_error(sym->pos, "alignment specified for type name");
+	if (ctx.autotype) {
+		sparse_error(sym->pos, "'%s' in type name",
+			     ctx.has_auto ? "auto" : "__auto_type");
+		ctx.ctype.base_type = &bad_ctype;
+	}
 	token = declarator(token, &ctx);
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
@@ -3199,6 +3246,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	struct symbol *base_type;
 	unsigned long mod;
 	int is_typedef;
+	bool delay_bind = false;
 
 	/* Top-level inline asm or static assertion? */
 	if (token_type(token) == TOKEN_IDENT) {
@@ -3213,6 +3261,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	decl = alloc_symbol(token->pos, SYM_NODE);
 	/* Just a type declaration? */
 	if (match_op(token, ';')) {
+		if (ctx.autotype)
+			sparse_error(token->pos, "%s in empty declaration",
+				     ctx.has_auto ? "auto" : "__auto_type");
 		apply_modifiers(token->pos, &ctx);
 		return token->next;
 	}
@@ -3226,6 +3277,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	decl->ctype = ctx.ctype;
 	decl->ctype.modifiers |= mod;
 	decl->cleanup = ctx.cleanup;
+	decl->c23_auto = ctx.autotype && ctx.has_auto;
 	decl->endpos = token->pos;
 
 	/* Just a type declaration? */
@@ -3241,9 +3293,15 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	if (is_typedef)
 		decl->ctype.modifiers |= MOD_USERTYPE;
 
-	bind_symbol(decl, ident, is_typedef ? NS_TYPEDEF: NS_SYMBOL);
-
 	base_type = decl->ctype.base_type;
+	if (!is_typedef && ctx.autotype && !ctx.has_auto &&
+	    base_type == &autotype_ctype) {
+		struct symbol *prev = lookup_symbol(ident, NS_SYMBOL | NS_TYPEDEF);
+		if (prev && is_outer_scope(prev->scope))
+			delay_bind = true;
+	}
+	if (!delay_bind)
+		bind_symbol(decl, ident, is_typedef ? NS_TYPEDEF: NS_SYMBOL);
 
 	if (is_typedef) {
 		if (base_type && !base_type->ident) {
@@ -3260,8 +3318,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		}
 	} else if (base_type && base_type->type == SYM_FN) {
 		if (base_type->ctype.base_type == &autotype_ctype) {
-			sparse_error(decl->pos, "'%s()' has __auto_type return type",
-				show_ident(decl->ident));
+			sparse_error(decl->pos, "'%s()' has %s return type",
+				     show_ident(decl->ident),
+				     ctx.has_auto ? "auto" : "__auto_type");
 			base_type->ctype.base_type = &int_ctype;
 		}
 		if (base_type->ctype.base_type == &incomplete_ctype) {
@@ -3301,11 +3360,26 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	for (;;) {
 		if (!is_typedef && match_op(token, '=')) {
 			struct token *next = token->next;
+			bool mark_underspec = decl->c23_auto;
+			if (mark_underspec)
+				decl->underspecified = 1;
 			token = initializer(&decl->initializer, next);
+			if (mark_underspec)
+				decl->underspecified = 0;
 			if (token == next)
 				sparse_error(token->pos, "expression expected before '%s'", show_token(token));
 		}
+		if (delay_bind) {
+			bind_symbol(decl, ident, is_typedef ? NS_TYPEDEF : NS_SYMBOL);
+			delay_bind = false;
+		}
 		if (!is_typedef) {
+			if (!ctx.autotype && (ctx.storage_class & MOD_AUTO) &&
+			    toplevel(block_scope))
+				sparse_error(
+					decl->pos,
+					"file-scope declaration of '%s' specifies 'auto'",
+					show_ident(decl->ident));
 			if (validate_decl)
 				validate_decl(decl);
 
@@ -3329,19 +3403,51 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			}
 		}
 
+		if (!is_typedef && decl->c23_auto &&
+		    (!decl->ctype.base_type ||
+		     decl->ctype.base_type->type != SYM_FN)) {
+			struct symbol *prev;
+			for (prev = decl->next_id; prev; prev = prev->next_id) {
+				if (prev->namespace != decl->namespace ||
+				    prev->builtin)
+					continue;
+				if (prev->scope == decl->scope ||
+				    (toplevel(decl->scope) &&
+				     toplevel(prev->scope))) {
+					sparse_error(
+						decl->pos,
+						"underspecified declaration of '%s', which is already declared in this scope",
+						show_ident(decl->ident));
+					break;
+				}
+			}
+		}
+
 		if (ctx.autotype) {
 			const char *msg = NULL;
-			if (decl->ctype.base_type != &autotype_ctype)
+			struct expression *init = decl->initializer;
+			while (init && init->type == EXPR_PREOP && init->op == '(')
+				init = init->unop;
+			if (is_typedef)
+				msg = "cannot be used with typedef";
+			else if (decl->ctype.base_type != &autotype_ctype)
 				msg = "on non-identifier";
 			else if (match_op(token, ','))
 				msg = "on declaration list";
 			else if (!decl->initializer)
 				msg = "without initializer";
-			else if (decl->initializer->type == EXPR_SYMBOL &&
-				 decl->initializer->symbol == decl)
+			else if (decl->initializer->type == EXPR_INITIALIZER) {
+				msg = "requires a scalar initializer expression";
+				decl->initializer = NULL;
+			} else if (!ctx.has_auto && init &&
+				   init->type == EXPR_SYMBOL &&
+				   init->symbol == decl)
 				msg = "on self-init var";
 			if (msg) {
-				sparse_error(decl->pos, "__auto_type %s", msg);
+				sparse_error(decl->pos, "%s %s",
+					     ctx.has_auto ? "auto" :
+							    "__auto_type",
+					     msg);
 				decl->ctype.base_type = &bad_ctype;
 			}
 		}
@@ -3391,6 +3497,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		decl->ctype = ctx.ctype;
 		decl->ctype.modifiers |= mod;
 		decl->cleanup = ctx.cleanup;
+		decl->c23_auto = ctx.autotype && ctx.has_auto;
 		decl->endpos = token->pos;
 		if (!ident) {
 			sparse_error(token->pos, "expected identifier name in type definition");
diff --git a/symbol.c b/symbol.c
index a05e240cdfaa..b0bdf0b1122f 100644
--- a/symbol.c
+++ b/symbol.c
@@ -266,15 +266,68 @@ static struct symbol *examine_base_type(struct symbol *sym)
 	struct symbol *base_type;
 
 	if (sym->ctype.base_type == &autotype_ctype) {
-		struct symbol *type = evaluate_expression(sym->initializer);
-		if (!type)
+		struct symbol *type;
+		sym->ctype.base_type = &bad_ctype;
+		if (!sym->initializer || sym->initializer->type == EXPR_INITIALIZER) {
+			type = &bad_ctype;
+		} else {
+			if (sym->c23_auto)
+				sym->underspecified = 1;
+			type = evaluate_expression(sym->initializer);
+			if (sym->c23_auto)
+				sym->underspecified = 0;
+		}
+		if (!type || type == sym || type->ctype.base_type == &autotype_ctype)
 			type = &bad_ctype;
 		if (is_bitfield_type(type)) {
-			warning(sym->pos, "__auto_type on bitfield");
+			if (!sym->c23_auto)
+				warning(sym->pos, "__auto_type on bitfield");
 			if (type->type == SYM_NODE)
 				type = type->ctype.base_type;
 			type = type->ctype.base_type;
 		}
+		if (type->type == SYM_NODE &&
+		    type->ctype.base_type->type == SYM_ARRAY) {
+			struct symbol *array = type->ctype.base_type;
+			struct symbol *ptr = alloc_symbol(sym->pos, SYM_PTR);
+			ptr->ctype.base_type = array->ctype.base_type;
+			combine_address_space(sym->pos, &ptr->ctype.as, type->ctype.as);
+			combine_address_space(sym->pos, &ptr->ctype.as, array->ctype.as);
+			ptr->ctype.modifiers |= (type->ctype.modifiers | array->ctype.modifiers) & MOD_PTRINHERIT;
+			ptr->bit_size = bits_in_pointer;
+			ptr->ctype.alignment = pointer_alignment;
+			type = ptr;
+		} else if (type->type == SYM_ARRAY) {
+			struct symbol *ptr = alloc_symbol(sym->pos, SYM_PTR);
+			ptr->ctype.base_type = type->ctype.base_type;
+			combine_address_space(sym->pos, &ptr->ctype.as, type->ctype.as);
+			ptr->ctype.modifiers |= type->ctype.modifiers & MOD_PTRINHERIT;
+			ptr->bit_size = bits_in_pointer;
+			ptr->ctype.alignment = pointer_alignment;
+			type = ptr;
+		} else if (type->type == SYM_NODE &&
+			   type->ctype.base_type->type == SYM_FN) {
+			struct symbol *ptr = alloc_symbol(sym->pos, SYM_PTR);
+			ptr->ctype.base_type = type->ctype.base_type;
+			ptr->bit_size = bits_in_pointer;
+			ptr->ctype.alignment = pointer_alignment;
+			type = ptr;
+		} else if (type->type == SYM_FN) {
+			struct symbol *ptr = alloc_symbol(sym->pos, SYM_PTR);
+			ptr->ctype.base_type = type;
+			ptr->bit_size = bits_in_pointer;
+			ptr->ctype.alignment = pointer_alignment;
+			type = ptr;
+		} else if (sym->c23_auto) {
+			/*
+			 * C23 6.7.10p2: inferred type is the type of the
+			 * assignment expression after lvalue, array-to-pointer,
+			 * or function-to-pointer conversion (stripping top-level
+			 * qualifiers and atomic).
+			 */
+			while (type->type == SYM_NODE)
+				type = type->ctype.base_type;
+		}
 		sym->ctype.base_type = type;
 	}
 
diff --git a/symbol.h b/symbol.h
index acd381fe64fd..61ca70afe83b 100644
--- a/symbol.h
+++ b/symbol.h
@@ -115,6 +115,7 @@ struct decl_state {
 	unsigned char autotype;
 	unsigned char forced;
 	unsigned char packed;
+	unsigned char has_auto;
 	unsigned char has_alignas;
 	unsigned char designated_init;
 	unsigned char transparent_union;
@@ -201,6 +202,8 @@ struct symbol {
 					packed:1,
 					transparent_union:1;
 			unsigned int has_alignas : 1;
+			unsigned int underspecified : 1;
+			unsigned int c23_auto : 1;
 			int		rank:3;	// arithmetic's rank
 			struct expression *array_size;
 			struct ctype ctype;
diff --git a/validation/c23-auto-error.c b/validation/c23-auto-error.c
new file mode 100644
index 000000000000..b88ebd37d2d9
--- /dev/null
+++ b/validation/c23-auto-error.c
@@ -0,0 +1,68 @@
+auto no_init;
+auto arr[2] = { 0, 1 };
+auto foo(void)
+{
+}
+auto x = 1, y = 2;
+auto self = self;
+auto auto dup = 1;
+auto self_expr = self_expr + 1;
+auto self_sz = sizeof(self_sz);
+struct bad_s {
+	auto m;
+};
+void bad_param(auto p);
+typedef auto bad_td;
+auto;
+auto int bad_file_storage = 1;
+extern int already_in_scope;
+auto already_in_scope = 1;
+
+void block_errors(void)
+{
+	int shadow = 5;
+	{
+		auto shadow = shadow + 1;
+	}
+	int bad_sz = sizeof(auto);
+	int bad_cast = (auto)1;
+	_Atomic(auto) bad_atomic = 1;
+	auto braced = { 1 };
+	(void)bad_sz;
+	(void)bad_cast;
+	(void)bad_atomic;
+	(void)braced;
+}
+
+/*
+ * check-name: C23 auto error cases
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-auto-error.c:1:6: error: auto without initializer
+c23-auto-error.c:2:6: error: auto on non-identifier
+c23-auto-error.c:3:6: error: 'foo()' has auto return type
+c23-auto-error.c:6:6: error: auto on declaration list
+c23-auto-error.c:8:11: error: duplicate auto
+c23-auto-error.c:12:14: error: struct or union member 'm' cannot be declared auto
+c23-auto-error.c:14:16: error: parameter 'p' cannot be declared auto
+c23-auto-error.c:15:14: error: auto cannot be used with typedef
+c23-auto-error.c:16:5: error: auto in empty declaration
+c23-auto-error.c:17:10: error: file-scope declaration of 'bad_file_storage' specifies 'auto'
+c23-auto-error.c:19:6: error: underspecified declaration of 'already_in_scope', which is already declared in this scope
+c23-auto-error.c:27:29: error: 'auto' in type name
+c23-auto-error.c:28:25: error: 'auto' in type name
+c23-auto-error.c:29:16: error: Trying to use reserved word 'auto' as identifier
+c23-auto-error.c:29:23: error: Expected ; at end of declaration
+c23-auto-error.c:29:23: error: got bad_atomic
+c23-auto-error.c:30:14: error: auto requires a scalar initializer expression
+c23-auto-error.c:2:15: error: invalid initializer
+c23-auto-error.c:7:13: error: underspecified symbol 'self' used in its own initializer
+c23-auto-error.c:9:18: error: underspecified symbol 'self_expr' used in its own initializer
+c23-auto-error.c:10:23: error: underspecified symbol 'self_sz' used in its own initializer
+c23-auto-error.c:10:16: error: cannot size expression
+c23-auto-error.c:25:31: error: underspecified symbol 'shadow' used in its own initializer
+c23-auto-error.c:27:22: error: cannot size expression
+c23-auto-error.c:33:15: error: undefined identifier 'bad_atomic'
+ * check-error-end
+ */
diff --git a/validation/c23-auto.c b/validation/c23-auto.c
new file mode 100644
index 000000000000..f9c461d9bed7
--- /dev/null
+++ b/validation/c23-auto.c
@@ -0,0 +1,107 @@
+#ifdef __CHECKER__
+#define ASSERT_TYPE(expr, T) _Static_assert([typeof(expr)] == [T], "")
+#else
+#define ASSERT_TYPE(expr, T) \
+	_Static_assert(_Generic((typeof(expr) *)0, typeof(T) *: 1, default: 0), "")
+#endif
+
+struct s {
+	int a;
+};
+static void fn(void)
+{
+}
+
+static auto file_static = 42L;
+ASSERT_TYPE(file_static, long);
+
+static void test(void)
+{
+	auto i = 0;
+	ASSERT_TYPE(i, int);
+
+	auto l = 1L;
+	ASSERT_TYPE(l, long);
+
+	auto d = 1.0;
+	ASSERT_TYPE(d, double);
+
+	auto f = 1.0f;
+	ASSERT_TYPE(f, float);
+
+	auto p = &i;
+	ASSERT_TYPE(p, int *);
+
+	const auto ci = 42;
+	ASSERT_TYPE(ci, const int);
+
+	volatile auto vi = 1;
+	ASSERT_TYPE(vi, volatile int);
+
+	_Atomic auto ai = 2;
+	ASSERT_TYPE(ai, _Atomic int);
+
+	/* Top-level qualifiers and _Atomic are stripped by lvalue conversion */
+	auto stripped_c = ci;
+	ASSERT_TYPE(stripped_c, int);
+	stripped_c = 43;
+
+	auto stripped_v = vi;
+	ASSERT_TYPE(stripped_v, int);
+	stripped_v = 2;
+
+	auto stripped_a = ai;
+	ASSERT_TYPE(stripped_a, int);
+	stripped_a = 3;
+
+	/* Pointee qualifiers and decayed array element qualifiers are kept */
+	const int *cp = &ci;
+	auto p_to_const = cp;
+	ASSERT_TYPE(p_to_const, const int *);
+
+	int *const top_const_p = &i;
+	auto stripped_p = top_const_p;
+	ASSERT_TYPE(stripped_p, int *);
+	stripped_p = nullptr;
+
+	const int carr[2] = { 1, 2 };
+	auto decayed_carr = carr;
+	ASSERT_TYPE(decayed_carr, const int *);
+
+	static auto si = 100;
+	ASSERT_TYPE(si, int);
+
+	auto s = "hello";
+	ASSERT_TYPE(s, char *);
+
+	int arr[10];
+	auto a = arr;
+	ASSERT_TYPE(a, int *);
+
+	auto fp = fn;
+	ASSERT_TYPE(fp, void (*)(void));
+
+	struct s st = { 1 };
+	auto sv = st;
+	ASSERT_TYPE(sv, struct s);
+
+	const struct s cst = { 2 };
+	auto stripped_st = cst;
+	ASSERT_TYPE(stripped_st, struct s);
+	stripped_st.a = 3;
+
+	auto int normal = 1;
+	ASSERT_TYPE(normal, int);
+
+	int auto normal2 = 2;
+	ASSERT_TYPE(normal2, int);
+
+	for (auto idx = 0; idx < 1; idx++) {
+		ASSERT_TYPE(idx, int);
+	}
+}
+
+/*
+ * check-name: C23 auto type inference
+ * check-command: sparse -std=c23 $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 50/73] c23: Support constexpr object specifier
  2026-11-09 20:31 Bart Van Assche
                   ` (48 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
                   ` (22 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add support for the ISO C23 'constexpr' object specifier and enforce its
linkage, qualification, and constant-initializer rules:
- Apply 'const' qualification after declarator parsing (cloning array
  chains) so pointers become top-level const ('int *const') and array
  elements become const without mutating shared array typedefs.
- Include MOD_CONSTEXPR in MOD_IGNORE for _Generic selection and give
  file-scope constexpr objects internal linkage per C23 6.2.2p3.
- Allow block-scope 'constexpr register' and 'constexpr auto', while
  rejecting constexpr on functions, parameters, struct/union members,
  empty declarations, typedefs, extern, thread_local, file-scope
  register, VLAs, and volatile, _Atomic, or restrict types.
- Mark constexpr symbols underspecified during initializer evaluation and
  validate scalar and aggregate initializers for null pointers, integer
  constant expressions, and exact target representability.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                           | 261 ++++++++++++++++++++++++++-
 expand.c                             |  90 +++++++--
 expand.h                             |   3 +
 ident-list.h                         |   1 +
 parse.c                              | 108 ++++++++++-
 show-parse.c                         |  51 +++---
 symbol.c                             |   6 +-
 symbol.h                             |  17 +-
 validation/c23-constexpr-error.c     |  92 ++++++++++
 validation/c23-constexpr-self-init.c |  41 +++++
 validation/c23-constexpr.c           | 100 ++++++++++
 11 files changed, 706 insertions(+), 64 deletions(-)
 create mode 100644 validation/c23-constexpr-error.c
 create mode 100644 validation/c23-constexpr-self-init.c
 create mode 100644 validation/c23-constexpr.c

diff --git a/evaluate.c b/evaluate.c
index 1b1a72577d18..b7e352f55e6c 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -35,6 +35,7 @@
 #include <limits.h>
 
 #include "evaluate.h"
+#include "expand.h"
 #include "simplify.h"
 #include "lib.h"
 #include "allocate.h"
@@ -95,6 +96,8 @@ static struct symbol *evaluate_symbol_expression(struct expression *expr)
 	}
 
 	examine_symbol_type(sym);
+	if (sym->ctype.modifiers & MOD_CONSTEXPR)
+		evaluate_symbol(sym);
 
 	base_type = get_base_type(sym);
 	if (!base_type) {
@@ -111,6 +114,14 @@ static struct symbol *evaluate_symbol_expression(struct expression *expr)
 	expr->op = '*';
 	expr->unop = addr;
 	expr->flags = CEF_NONE;
+	if (sym->ctype.modifiers & MOD_CONSTEXPR) {
+		if (is_float_type(base_type))
+			expr->flags = CEF_SET_FLOAT;
+		else if (is_int_type(base_type))
+			expr->flags = CEF_SET_ICE;
+		else
+			expr->flags = CEF_ADDR;
+	}
 
 	/* The type of a symbol is the symbol itself! */
 	expr->ctype = sym;
@@ -2388,6 +2399,14 @@ static struct symbol *evaluate_member_dereference(struct expression *expr)
 	expr->type = EXPR_PREOP;
 	expr->op = '*';
 	expr->unop = add;
+	if (mod & MOD_CONSTEXPR) {
+		if (is_float_type(ctype))
+			expr->flags = CEF_SET_FLOAT;
+		else if (is_int_type(ctype))
+			expr->flags = CEF_SET_ICE;
+		else
+			expr->flags = CEF_ADDR;
+	}
 
 	expr->ctype = member;
 	return member;
@@ -2922,7 +2941,7 @@ found:
 		else
 			v = &top->ident_expression;
 
-		mods |= ctype->ctype.modifiers & MOD_STORAGE;
+		mods |= ctype->ctype.modifiers & (MOD_STORAGE | MOD_CONSTEXPR);
 		if (handle_initializer(v, 1, lclass, top->ctype, mods))
 			continue;
 
@@ -3015,6 +3034,161 @@ static struct expression *handle_scalar(struct expression *e, int nested)
  * { "string", ...} - we need to preserve that string literal recognizable
  * until we dig into the inner struct.
  */
+static void check_constexpr_scalar_init(struct expression *orig,
+					struct symbol *target)
+{
+	struct symbol *ttype, *stype;
+	int tclass = classify_type(target, &ttype);
+	int sclass = classify_type(orig->ctype, &stype);
+	struct expression tmp;
+
+	if (!valid_type(target) || !valid_type(orig->ctype))
+		return;
+
+	if ((tclass & TYPE_PTR) || is_nullptr_type(ttype)) {
+		if (!(orig->flags & (CEF_ACE | CEF_ADDR))) {
+			sparse_error(orig->pos,
+				     "non-constant initializer for constexpr object");
+			return;
+		}
+		tmp = *orig;
+		if (!is_null_pointer_constant(&tmp) &&
+		    !(((sclass & TYPE_PTR) || is_nullptr_type(stype)) && is_zero_constant(&tmp)))
+			sparse_error(orig->pos,
+				     "constexpr pointer initializer is not null");
+		return;
+	}
+
+	if (is_int(tclass)) {
+		int sw, tw;
+		bool s_unsigned, t_unsigned, s_neg;
+
+		if (!(orig->flags & (CEF_ACE | CEF_ADDR))) {
+			sparse_error(orig->pos,
+				     "non-constant initializer for constexpr object");
+			return;
+		}
+		if (!(orig->flags & CEF_ICE) || !is_int(sclass)) {
+			sparse_error(
+				orig->pos,
+				"constexpr integer initializer is not an integer constant expression");
+			return;
+		}
+		tmp = *orig;
+		expand_constexpr_initializer(&tmp);
+		if (tmp.type != EXPR_VALUE) {
+			sparse_error(orig->pos,
+				     "non-constant initializer for constexpr object");
+			return;
+		}
+		sw = stype->bit_size;
+		s_unsigned = (stype->ctype.modifiers & MOD_UNSIGNED) != 0;
+		s_neg = !s_unsigned && sw > 0 && sw <= 64 &&
+			(tmp.value & sign_bit(sw)) != 0;
+		if (is_bool_type(ttype)) {
+			if (s_neg || (tmp.value != 0 && tmp.value != 1))
+				sparse_error(
+					orig->pos,
+					"constexpr initializer not representable in target type");
+			return;
+		}
+		tw = ttype->bit_size;
+		t_unsigned = (ttype->ctype.modifiers & MOD_UNSIGNED) != 0;
+		if (s_neg) {
+			long long sval = sw > 64 ? (long long)tmp.value :
+						   bits_extend(tmp.value, sw, 1);
+			if (t_unsigned ||
+			    (tw < 64 && sval < -(1LL << (tw - 1))))
+				sparse_error(
+					orig->pos,
+					"constexpr initializer not representable in target type");
+		} else {
+			unsigned long long uval =
+				sw > 64 ? tmp.value :
+					  (unsigned long long)zero_extend(tmp.value, sw);
+			int max_bits = t_unsigned ? tw : (tw - 1);
+			if ((max_bits <= 0 && uval != 0) ||
+			    (max_bits > 0 && max_bits < 64 &&
+			     (uval >> max_bits) != 0))
+				sparse_error(
+					orig->pos,
+					"constexpr initializer not representable in target type");
+		}
+		return;
+	}
+
+	if (tclass & TYPE_FLOAT) {
+		if (!(orig->flags & CEF_ACE) || !(sclass & TYPE_NUM)) {
+			sparse_error(orig->pos,
+				     "non-constant initializer for constexpr object");
+			return;
+		}
+		tmp = *orig;
+		expand_constexpr_initializer(&tmp);
+		if (sclass & TYPE_FLOAT) {
+			long double fv;
+			if (tmp.type != EXPR_FVALUE) {
+				sparse_error(
+					orig->pos,
+					"non-constant initializer for constexpr object");
+				return;
+			}
+			fv = tmp.fvalue;
+			if (stype->rank > ttype->rank && fv == fv) {
+				bool ok = ttype->rank < 0 ?
+						  ((long double)(float)fv == fv) :
+						  (ttype->rank == 0 ?
+							   ((long double)(double)fv == fv) :
+							   true);
+				if (!ok)
+					sparse_error(
+						orig->pos,
+						"constexpr initializer not representable in target type");
+			}
+		} else if (is_int(sclass)) {
+			int sw = stype->bit_size;
+			bool s_unsigned = (stype->ctype.modifiers & MOD_UNSIGNED) != 0;
+			bool s_neg = !s_unsigned && sw > 0 && sw <= 64 &&
+				     (tmp.value & sign_bit(sw)) != 0;
+			if (tmp.type != EXPR_VALUE) {
+				sparse_error(
+					orig->pos,
+					"non-constant initializer for constexpr object");
+				return;
+			}
+			if (s_neg) {
+				long long sval = sw > 64 ? (long long)tmp.value :
+							   bits_extend(tmp.value, sw, 1);
+				long double ld = ttype->rank < 0 ?
+							 (long double)(float)sval :
+							 (ttype->rank == 0 ?
+								  (long double)(double)sval :
+								  (long double)sval);
+				if (sval != LLONG_MIN &&
+				    (ld <= (long double)LLONG_MIN ||
+				     (long long)ld != sval))
+					sparse_error(
+						orig->pos,
+						"constexpr initializer not representable in target type");
+			} else {
+				unsigned long long uval =
+					sw > 64 ? tmp.value :
+						  (unsigned long long)zero_extend(tmp.value, sw);
+				long double ld = ttype->rank < 0 ?
+							 (long double)(float)uval :
+							 (ttype->rank == 0 ?
+								  (long double)(double)uval :
+								  (long double)uval);
+				if (ld >= 18446744073709551616.0L ||
+				    (unsigned long long)ld != uval)
+					sparse_error(
+						orig->pos,
+						"constexpr initializer not representable in target type");
+			}
+		}
+	}
+}
+
 static int handle_initializer(struct expression **ep, int nested,
 		int class, struct symbol *ctype, unsigned long mods)
 {
@@ -3026,20 +3200,27 @@ static int handle_initializer(struct expression **ep, int nested,
 
 	/* scalar */
 	if (!(class & TYPE_COMPOUND)) {
+		struct expression orig;
+
 		e = handle_scalar(e, nested);
+
 		if (!e)
 			return 0;
 		*ep = e;
 		if (!evaluate_expression(e))
 			return 1;
+		degenerate(e);
+		orig = *e;
 		compatible_assignment_types(e, ctype, ep, "initializer");
 		/*
 		 * Initializers for static storage duration objects
 		 * shall be constant expressions or a string literal [6.7.8(4)].
 		 */
 		mods |= ctype->ctype.modifiers;
-		mods &= (MOD_TOPLEVEL | MOD_STATIC);
-		if (mods && !(e->flags & (CEF_ACE | CEF_ADDR)))
+		if (mods & MOD_CONSTEXPR)
+			check_constexpr_scalar_init(&orig, ctype);
+		if ((mods & (MOD_TOPLEVEL | MOD_STATIC)) &&
+		    !(e->flags & (CEF_ACE | CEF_ADDR)))
 			if (Wconstexpr_not_const)
 				warning(e->pos, "non-constant initializer for static object");
 
@@ -3084,12 +3265,18 @@ static int handle_initializer(struct expression **ep, int nested,
 	type = evaluate_expression(e);
 	if (!type)
 		return 0;
+	mods |= ctype->ctype.modifiers;
 	if (ctype->type == SYM_NODE)
 		ctype = ctype->ctype.base_type;
 	if (type->type == SYM_NODE)
 		type = type->ctype.base_type;
-	if (ctype == type)
+	if (ctype == type) {
+		if ((mods & MOD_CONSTEXPR) && !(e->flags & (CEF_ACE | CEF_ADDR)))
+			sparse_error(
+				e->pos,
+				"non-constant initializer for constexpr object");
 		return 1;
+	}
 	return 0;
 
 String:
@@ -3843,6 +4030,55 @@ void check_duplicates(struct symbol *sym)
 	}
 }
 
+static bool check_constexpr_vm_type(struct symbol *type)
+{
+	while (type) {
+		examine_symbol_type(type);
+		switch (type->type) {
+		case SYM_NODE:
+		case SYM_PTR:
+			type = type->ctype.base_type;
+			continue;
+		case SYM_ARRAY:
+			if (type->array_size &&
+			    type->array_size->type != EXPR_VALUE)
+				return true;
+			type = type->ctype.base_type;
+			continue;
+		default:
+			return false;
+		}
+	}
+	return false;
+}
+
+static bool has_invalid_constexpr_qualifiers(struct symbol *type)
+{
+	while (type) {
+		if (type->ctype.modifiers &
+		    (MOD_VOLATILE | MOD_ATOMIC | MOD_RESTRICT))
+			return true;
+		switch (type->type) {
+		case SYM_NODE:
+		case SYM_ARRAY:
+			type = type->ctype.base_type;
+			continue;
+		case SYM_STRUCT:
+		case SYM_UNION: {
+			struct symbol *m;
+			FOR_EACH_PTR(type->symbol_list, m) {
+				if (has_invalid_constexpr_qualifiers(m))
+					return true;
+			} END_FOR_EACH_PTR(m);
+			return false;
+		}
+		default:
+			return false;
+		}
+	}
+	return false;
+}
+
 static struct symbol *evaluate_symbol(struct symbol *sym)
 {
 	struct symbol *base_type;
@@ -3858,9 +4094,24 @@ static struct symbol *evaluate_symbol(struct symbol *sym)
 	if (!base_type)
 		return NULL;
 
+	if ((sym->ctype.modifiers & MOD_CONSTEXPR) &&
+	    base_type->type != SYM_FN && !(sym->ctype.modifiers & MOD_USERTYPE)) {
+		if (check_constexpr_vm_type(sym))
+			sparse_error(
+				sym->pos,
+				"constexpr variable '%s' cannot have variably modified type",
+				show_ident(sym->ident));
+		if (has_invalid_constexpr_qualifiers(sym))
+			sparse_error(
+				sym->pos,
+				"constexpr variable '%s' cannot have volatile, atomic, or restrict type",
+				show_ident(sym->ident));
+	}
+
 	/* Evaluate the initializers */
 	if (sym->initializer) {
-		bool mark_underspec = sym->c23_auto;
+		bool mark_underspec = sym->c23_auto ||
+				      (sym->ctype.modifiers & MOD_CONSTEXPR);
 		if (mark_underspec)
 			sym->underspecified = 1;
 		evaluate_initializer(sym, &sym->initializer);
diff --git a/expand.c b/expand.c
index 267aaf00e9ce..73b37b93534e 100644
--- a/expand.c
+++ b/expand.c
@@ -710,6 +710,41 @@ redo:
 	return &value;
 }
 
+static struct expression *lookup_initializer_offset(struct expression *value, unsigned int offset, bool recurse)
+{
+	struct expression *entry;
+
+	FOR_EACH_PTR(value->expr_list, entry) {
+		if (entry->type == EXPR_INITIALIZER && recurse) {
+			struct expression *sub = lookup_initializer_offset(entry, offset, recurse);
+			if (sub)
+				return sub;
+			continue;
+		}
+		if (entry->type != EXPR_POS) {
+			if (offset)
+				continue;
+			return entry;
+		}
+		if (entry->init_nr > 1 && entry->ctype) {
+			int elem_bytes = bits_to_bytes(entry->ctype->bit_size);
+			if (elem_bytes > 0) {
+				unsigned long long end = (unsigned long long)entry->init_offset +
+							 (unsigned long long)entry->init_nr * elem_bytes;
+				if (offset >= entry->init_offset && offset < end &&
+				    ((offset - entry->init_offset) % elem_bytes) == 0)
+					return entry->init_expr;
+			}
+		}
+		if (entry->init_offset < offset)
+			continue;
+		if (entry->init_offset > offset)
+			break;
+		return entry->init_expr;
+	} END_FOR_EACH_PTR(entry);
+	return NULL;
+}
+
 /*
  * Look up a trustable initializer value at the requested offset.
  *
@@ -719,35 +754,36 @@ static struct expression *constant_symbol_value(struct symbol *sym, int offset)
 {
 	struct expression *value;
 
-	if (offset < 0)
+	if (offset < 0 || sym->expanding)
 		return NULL;
 	if (sym->ctype.modifiers & MOD_VOLATILE)
 		return NULL;
-	if (sym->ctype.modifiers & MOD_ACCESS)
+	if ((sym->ctype.modifiers & MOD_ACCESS) &&
+	    !(sym->ctype.modifiers & MOD_CONSTEXPR))
 		return NULL;
 	if ((sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_STATIC)) &&
-	    !(sym->ctype.modifiers & MOD_CONST) &&
+	    !(sym->ctype.modifiers & (MOD_CONST | MOD_CONSTEXPR)) &&
 	    !sym->string && sym->ident)
 		return NULL;
 	value = sym->initializer;
 	if (!value)
 		return NULL;
+	sym->expanding = 1;
+	expand_expression(value);
+	sym->expanding = 0;
+	if (value->type == EXPR_PREOP && value->op == '*' &&
+	    value->unop && value->unop->type == EXPR_SYMBOL &&
+	    (value->unop->symbol->ctype.modifiers & MOD_CONSTEXPR)) {
+		sym->expanding = 1;
+		value = constant_symbol_value(value->unop->symbol, offset);
+		sym->expanding = 0;
+		return value;
+	}
 	if (value->type == EXPR_INITIALIZER) {
-		struct expression *entry;
-		FOR_EACH_PTR(value->expr_list, entry) {
-			if (entry->type != EXPR_POS) {
-				if (offset)
-					continue;
-				return entry;
-			}
-			if (entry->init_offset < offset)
-				continue;
-			if (entry->init_offset > offset)
-				break;
-			return entry->init_expr;
-		} END_FOR_EACH_PTR(entry);
-
-		value = default_initializer(sym, offset);
+		bool recurse = (sym->ctype.modifiers & (MOD_CONST | MOD_CONSTEXPR)) != 0;
+		value = lookup_initializer_offset(value, offset, recurse);
+		if (!value)
+			value = default_initializer(sym, offset);
 	}
 	return value;
 }
@@ -801,12 +837,17 @@ static int expand_dereference(struct expression *expr)
 				expr->type = EXPR_VALUE;
 				expr->value = value->value;
 				expr->taint = 0;
+				if ((sym->ctype.modifiers & MOD_CONSTEXPR) &&
+				    is_int_type(ctype))
+					expr->flags = CEF_SET_ICE;
 				return 0;
 			} else if (value->type == EXPR_FVALUE) {
 				if (!is_float_type(ctype))
 					return UNSAFE;
 				expr->type = EXPR_FVALUE;
 				expr->fvalue = value->fvalue;
+				if (sym->ctype.modifiers & MOD_CONSTEXPR)
+					expr->flags = CEF_SET_FLOAT;
 				return 0;
 			}
 		}
@@ -1086,6 +1127,11 @@ static unsigned long bit_offset(const struct expression *expr)
 		offset += bytes_to_bits(expr->init_offset);
 		expr = expr->init_expr;
 	}
+	if (expr && expr->type == EXPR_INITIALIZER) {
+		struct expression *first = first_ptr_list((struct ptr_list *)expr->expr_list);
+		if (first)
+			return offset + bit_offset(first);
+	}
 	if (expr && expr->ctype)
 		offset += expr->ctype->bit_offset;
 	return offset;
@@ -1537,3 +1583,11 @@ int is_zero_constant(struct expression *expr)
 	conservative = saved;
 	return expr->type == EXPR_VALUE && !expr->value;
 }
+
+void expand_constexpr_initializer(struct expression *expr)
+{
+	const int saved = conservative;
+	conservative = 1;
+	expand_expression(expr);
+	conservative = saved;
+}
diff --git a/expand.h b/expand.h
index 27e10c0a87e3..44b300b2a36e 100644
--- a/expand.h
+++ b/expand.h
@@ -31,4 +31,7 @@
 #define SELECT_COST 20		/* Cut-off for turning a conditional into a select */
 #define BRANCH_COST 10		/* Cost of a conditional branch */
 
+struct expression;
+void expand_constexpr_initializer(struct expression *expr);
+
 #endif
diff --git a/ident-list.h b/ident-list.h
index 18dabf5193b2..46e6cd9e4f61 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -47,6 +47,7 @@ IDENT(_Thread_local);
 IDENT(alignas);
 IDENT(alignof);
 IDENT(bool);
+IDENT(constexpr);
 IDENT(false);
 IDENT(nullptr);
 IDENT(static_assert);
diff --git a/parse.c b/parse.c
index 324da8e40423..a7d31fad1a10 100644
--- a/parse.c
+++ b/parse.c
@@ -57,7 +57,7 @@ static declarator_t
 	attribute_specifier, typeof_specifier, typeof_unqual_specifier,
 	storage_specifier, thread_specifier;
 static declarator_t generic_qualifier;
-static declarator_t autotype_specifier, auto_specifier;
+static declarator_t autotype_specifier, auto_specifier, constexpr_specifier;
 
 static struct token *parse_if_statement(struct token *token, struct statement *stmt);
 static struct token *parse_return_statement(struct token *token, struct statement *stmt);
@@ -146,6 +146,11 @@ static struct symbol_op auto_op = {
 	.declarator = auto_specifier,
 };
 
+static struct symbol_op constexpr_op = {
+	.type = KW_MODIFIER,
+	.declarator = constexpr_specifier,
+};
+
 static struct symbol_op register_op = {
 	.type = KW_MODIFIER,
 	.declarator = storage_specifier,
@@ -479,6 +484,7 @@ static struct init_keyword {
 	N("__thread",		&thread_op),
 	N("_Thread_local",	&thread_op),
 	N("thread_local",	&thread_op,	.std = STANDARD_C23),
+	N("constexpr",		&constexpr_op,	.std = STANDARD_C23),
 
 	A("inline",		&inline_op,	.mods = MOD_INLINE),
 
@@ -1211,6 +1217,37 @@ static struct token *auto_specifier(struct token *token, struct symbol *sym,
 	return token;
 }
 
+static struct token *constexpr_specifier(struct token *token,
+					 struct symbol *sym,
+					 struct decl_state *ctx)
+{
+	if (ctx->is_constexpr)
+		sparse_error(token->pos, "duplicate constexpr");
+	ctx->is_constexpr = 1;
+	ctx->ctype.modifiers |= MOD_CONSTEXPR;
+	return token;
+}
+
+static void apply_constexpr_const(struct symbol *sym)
+{
+	struct ctype *ctype;
+
+	if (!sym)
+		return;
+	sym->ctype.modifiers |= MOD_CONST;
+	ctype = &sym->ctype;
+	while (ctype->base_type) {
+		struct symbol *base = ctype->base_type;
+		if (base->type != SYM_NODE && base->type != SYM_ARRAY)
+			break;
+		struct symbol *copy = alloc_symbol(base->pos, base->type);
+		*copy = *base;
+		copy->ctype.modifiers |= MOD_CONST;
+		ctype->base_type = copy;
+		ctype = &copy->ctype;
+	}
+}
+
 static struct token *ignore_attribute(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
 	struct expression *expr = NULL;
@@ -2119,6 +2156,11 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 			if (ctx.autotype)
 				decl->ctype.base_type = &bad_ctype;
 		}
+		if (ctx.is_constexpr)
+			sparse_error(
+				decl->pos,
+				"struct or union member '%s' cannot be declared constexpr",
+				show_ident(decl->ident));
 		if (decl->ctype.modifiers & MOD_TLS)
 			sparse_error(
 				decl->pos,
@@ -2166,10 +2208,6 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	sym->ctype.modifiers |= decl_modifiers(&ctx);
 	sym->endpos = token->pos;
 	sym->forced_arg = ctx.forced;
-	if (ctx.has_alignas)
-		sparse_error(sym->pos,
-			     "alignment specified for parameter '%s'",
-			     show_ident(sym->ident));
 	if (ctx.has_auto || ctx.autotype) {
 		sparse_error(sym->pos,
 			     "parameter '%s' cannot be declared %s",
@@ -2178,6 +2216,14 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 		if (ctx.autotype)
 			sym->ctype.base_type = &bad_ctype;
 	}
+	if (ctx.is_constexpr)
+		sparse_error(sym->pos,
+			     "parameter '%s' cannot be declared constexpr",
+			     show_ident(sym->ident));
+	if (ctx.has_alignas)
+		sparse_error(sym->pos,
+			     "alignment specified for parameter '%s'",
+			     show_ident(sym->ident));
 	if (sym->ctype.modifiers & MOD_TLS)
 		sparse_error(sym->pos,
 			     "parameter '%s' cannot be thread-local",
@@ -2203,6 +2249,8 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
 	sym->endpos = token->pos;
+	if (ctx.is_constexpr)
+		sparse_error(sym->pos, "constexpr in typename");
 	class = ctx.storage_class;
 	if (forced)
 		*forced = ctx.forced;
@@ -3264,6 +3312,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		if (ctx.autotype)
 			sparse_error(token->pos, "%s in empty declaration",
 				     ctx.has_auto ? "auto" : "__auto_type");
+		if (ctx.is_constexpr)
+			sparse_error(token->pos,
+				     "constexpr in empty declaration");
 		apply_modifiers(token->pos, &ctx);
 		return token->next;
 	}
@@ -3294,6 +3345,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		decl->ctype.modifiers |= MOD_USERTYPE;
 
 	base_type = decl->ctype.base_type;
+	if (ctx.is_constexpr && !is_typedef &&
+	    (!base_type || base_type->type != SYM_FN))
+		apply_constexpr_const(decl);
+
 	if (!is_typedef && ctx.autotype && !ctx.has_auto &&
 	    base_type == &autotype_ctype) {
 		struct symbol *prev = lookup_symbol(ident, NS_SYMBOL | NS_TYPEDEF);
@@ -3317,6 +3372,12 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			}
 		}
 	} else if (base_type && base_type->type == SYM_FN) {
+		if (ctx.is_constexpr) {
+			sparse_error(
+				decl->pos,
+				"function '%s' cannot be declared constexpr",
+				show_ident(decl->ident));
+		}
 		if (base_type->ctype.base_type == &autotype_ctype) {
 			sparse_error(decl->pos, "'%s()' has %s return type",
 				     show_ident(decl->ident),
@@ -3360,7 +3421,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	for (;;) {
 		if (!is_typedef && match_op(token, '=')) {
 			struct token *next = token->next;
-			bool mark_underspec = decl->c23_auto;
+			bool mark_underspec = decl->c23_auto || ctx.is_constexpr;
 			if (mark_underspec)
 				decl->underspecified = 1;
 			token = initializer(&decl->initializer, next);
@@ -3403,7 +3464,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			}
 		}
 
-		if (!is_typedef && decl->c23_auto &&
+		if (!is_typedef && (decl->c23_auto || ctx.is_constexpr) &&
 		    (!decl->ctype.base_type ||
 		     decl->ctype.base_type->type != SYM_FN)) {
 			struct symbol *prev;
@@ -3452,6 +3513,34 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			}
 		}
 
+		if (ctx.is_constexpr) {
+			if (is_typedef)
+				sparse_error(
+					decl->pos,
+					"constexpr cannot be used with typedef");
+			else if (ctx.storage_class & MOD_EXTERN)
+				sparse_error(
+					decl->pos,
+					"constexpr cannot be used with extern");
+			else if ((ctx.storage_class & MOD_REGISTER) &&
+				 toplevel(block_scope))
+				sparse_error(
+					decl->pos,
+					"constexpr register variable '%s' at file scope",
+					show_ident(decl->ident));
+			else if (decl->ctype.modifiers & MOD_TLS)
+				sparse_error(
+					decl->pos,
+					"constexpr cannot be used with thread_local");
+			else if (!decl->initializer &&
+				 (!decl->ctype.base_type ||
+				  decl->ctype.base_type->type != SYM_FN))
+				sparse_error(
+					decl->pos,
+					"constexpr variable '%s' without initializer",
+					show_ident(decl->ident));
+		}
+
 		if (ctx.has_alignas) {
 			if (is_typedef)
 				sparse_error(decl->pos,
@@ -3507,6 +3596,11 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		if (is_typedef)
 			decl->ctype.modifiers |= MOD_USERTYPE;
 
+		base_type = decl->ctype.base_type;
+		if (ctx.is_constexpr && !is_typedef &&
+		    (!base_type || base_type->type != SYM_FN))
+			apply_constexpr_const(decl);
+
 		bind_symbol(decl, ident, is_typedef ? NS_TYPEDEF: NS_SYMBOL);
 
 		/* Function declarations are automatically extern unless specifically static */
diff --git a/show-parse.c b/show-parse.c
index 78d86e20e93a..0a277ef67aac 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -119,31 +119,32 @@ static const char *show_modifiers(unsigned long mod, int term)
 	} *m;
 
 	static struct mod_name mod_names[] = {
-		{MOD_AUTO,		"auto"},
-		{MOD_EXTERN,		"extern"},
-		{MOD_REGISTER,		"register"},
-		{MOD_STATIC,		"static"},
-		{MOD_INLINE,		"inline"},
-		{MOD_CONST,		"const"},
-		{MOD_RESTRICT,		"restrict"},
-		{MOD_VOLATILE,		"volatile"},
-		{MOD_ADDRESSABLE,	"[addressable]"},
-		{MOD_ASSIGNED,		"[assigned]"},
-		{MOD_ATOMIC,		"[atomic]"},
-		{MOD_BITWISE,		"[bitwise]"},
-		{MOD_EXPLICITLY_SIGNED,	"[explicitly-signed]"},
-		{MOD_GNU_INLINE,	"[gnu_inline]"},
-		{MOD_NOCAST,		"[nocast]"},
-		{MOD_NODEREF,		"[noderef]"},
-		{MOD_NORETURN,		"[noreturn]"},
-		{MOD_PURE,		"[pure]"},
-		{MOD_SAFE,		"[safe]"},
-		{MOD_SIGNED,		"[signed]"},
-		{MOD_TLS,		"[tls]"},
-		{MOD_TOPLEVEL,		"[toplevel]"},
-		{MOD_UNSIGNED,		"[unsigned]"},
-		{MOD_UNUSED,		"[unused]"},
-		{MOD_USERTYPE,		"[usertype]"},
+		{ MOD_AUTO, "auto" },
+		{ MOD_EXTERN, "extern" },
+		{ MOD_REGISTER, "register" },
+		{ MOD_STATIC, "static" },
+		{ MOD_CONSTEXPR, "constexpr" },
+		{ MOD_INLINE, "inline" },
+		{ MOD_CONST, "const" },
+		{ MOD_RESTRICT, "restrict" },
+		{ MOD_VOLATILE, "volatile" },
+		{ MOD_ADDRESSABLE, "[addressable]" },
+		{ MOD_ASSIGNED, "[assigned]" },
+		{ MOD_ATOMIC, "[atomic]" },
+		{ MOD_BITWISE, "[bitwise]" },
+		{ MOD_EXPLICITLY_SIGNED, "[explicitly-signed]" },
+		{ MOD_GNU_INLINE, "[gnu_inline]" },
+		{ MOD_NOCAST, "[nocast]" },
+		{ MOD_NODEREF, "[noderef]" },
+		{ MOD_NORETURN, "[noreturn]" },
+		{ MOD_PURE, "[pure]" },
+		{ MOD_SAFE, "[safe]" },
+		{ MOD_SIGNED, "[signed]" },
+		{ MOD_TLS, "[tls]" },
+		{ MOD_TOPLEVEL, "[toplevel]" },
+		{ MOD_UNSIGNED, "[unsigned]" },
+		{ MOD_UNUSED, "[unused]" },
+		{ MOD_USERTYPE, "[usertype]" },
 	};
 
 	for (i = 0; i < ARRAY_SIZE(mod_names); i++) {
diff --git a/symbol.c b/symbol.c
index b0bdf0b1122f..fc11e1bbcec3 100644
--- a/symbol.c
+++ b/symbol.c
@@ -563,7 +563,7 @@ static struct symbol * examine_node_type(struct symbol *sym)
 		sym->ctype.alignment = alignment;
 
 	/* Unsized array? The size might come from the initializer.. */
-	if (bit_size < 0 && base_type->type == SYM_ARRAY) {
+	if (bit_size < 0 && base_type->type == SYM_ARRAY && !base_type->array_size) {
 		struct expression *initializer = get_symbol_initializer(sym);
 		if (initializer) {
 			struct symbol *node_type = base_type->ctype.base_type;
@@ -872,6 +872,10 @@ void bind_symbol_with_scope(struct symbol *sym, struct ident *ident, enum namesp
 	if (ns == NS_SYMBOL && toplevel(scope)) {
 		unsigned mod = MOD_ADDRESSABLE | MOD_TOPLEVEL;
 
+		if ((sym->ctype.modifiers & MOD_CONSTEXPR) &&
+		    !(sym->ctype.modifiers & MOD_EXTERN))
+			sym->ctype.modifiers |= MOD_STATIC;
+
 		inherit_static(sym);
 
 		scope = global_scope;
diff --git a/symbol.h b/symbol.h
index 61ca70afe83b..f8556fdf3b86 100644
--- a/symbol.h
+++ b/symbol.h
@@ -117,6 +117,7 @@ struct decl_state {
 	unsigned char packed;
 	unsigned char has_auto;
 	unsigned char has_alignas;
+	unsigned char is_constexpr;
 	unsigned char designated_init;
 	unsigned char transparent_union;
 };
@@ -254,10 +255,10 @@ struct symbol {
 
 #define MOD_GNU_INLINE		0x00010000
 #define MOD_USERTYPE		0x00020000
-     // MOD UNUSED		0x00040000
-     // MOD UNUSED		0x00080000
-     // MOD UNUSED		0x00100000
-     // MOD UNUSED		0x00200000
+#define MOD_CONSTEXPR 0x00040000
+// MOD UNUSED		0x00080000
+// MOD UNUSED		0x00100000
+// MOD UNUSED		0x00200000
 
 #define MOD_UNUSED		0x00400000
 #define MOD_SAFE		0x00800000	// non-null/non-trapping pointer
@@ -275,7 +276,7 @@ struct symbol {
 #define MOD_ESIGNED	(MOD_SIGNED | MOD_EXPLICITLY_SIGNED)
 #define MOD_SIGNEDNESS	(MOD_SIGNED | MOD_UNSIGNED | MOD_EXPLICITLY_SIGNED)
 #define MOD_SPECIFIER	MOD_SIGNEDNESS
-#define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE)
+#define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE | MOD_CONSTEXPR)
 #define MOD_QUALIFIER	(MOD_CONST | MOD_VOLATILE | MOD_RESTRICT)
 #define MOD_PTRINHERIT	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NODEREF | MOD_NORETURN | MOD_NOCAST)
 /* modifiers preserved by typeof() operator */
@@ -287,9 +288,9 @@ struct symbol {
 /* do not warn when these are duplicated */
 #define MOD_DUP_OK	(MOD_UNUSED|MOD_GNU_INLINE)
 /* must be part of the declared symbol, not its type */
-#define MOD_DECLARE	(MOD_STORAGE|MOD_INLINE|MOD_TLS|MOD_GNU_INLINE|MOD_UNUSED|MOD_PURE|MOD_NORETURN|MOD_EXT_VISIBLE)
-
-
+#define MOD_DECLARE                                                         \
+	(MOD_STORAGE | MOD_INLINE | MOD_TLS | MOD_GNU_INLINE | MOD_UNUSED | \
+	 MOD_PURE | MOD_NORETURN | MOD_EXT_VISIBLE | MOD_CONSTEXPR)
 
 /* Current parsing/evaluation function */
 extern struct symbol *current_fn;
diff --git a/validation/c23-constexpr-error.c b/validation/c23-constexpr-error.c
new file mode 100644
index 000000000000..41ee86d7ae63
--- /dev/null
+++ b/validation/c23-constexpr-error.c
@@ -0,0 +1,92 @@
+constexpr int no_init;
+constexpr int foo(void)
+{
+	return 0;
+}
+typedef constexpr int my_int;
+extern constexpr int ext = 1;
+register constexpr int reg = 1;
+void bar(constexpr int p)
+{
+}
+struct s {
+	constexpr int m;
+};
+constexpr constexpr int double_ce = 1;
+int non_const = 5;
+constexpr int bad_init = non_const;
+thread_local constexpr int tls = 1;
+constexpr struct empty_s {
+	int a;
+};
+constexpr volatile int cv = 1;
+constexpr _Atomic int ca = 1;
+constexpr int *restrict cr = nullptr;
+constexpr int *bad_ptr = &non_const;
+constexpr const char *bad_str = "hi";
+constexpr int bad_flt_ice = 1.0;
+constexpr unsigned int neg_to_u = -1;
+constexpr signed char over_sc = 200;
+constexpr bool bad_bool = 2;
+constexpr float inexact_int = 16777217;
+constexpr float inexact_dbl = 0.1;
+extern int already_in_scope;
+constexpr int already_in_scope = 1;
+
+struct pwrap {
+	int *p;
+	unsigned char u;
+};
+constexpr struct pwrap bad_nested = { &non_const, 300 };
+
+void assign_and_vla_test(int n)
+{
+	constexpr int c = 10;
+	c = 20;
+
+	constexpr int *cp = nullptr;
+	cp = nullptr;
+
+	constexpr int arr[2] = { 1, 2 };
+	arr[0] = 3;
+
+	constexpr int (*vla)[n] = nullptr;
+	(void)vla;
+}
+
+/*
+ * check-name: C23 constexpr error cases
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-constexpr-error.c:1:15: error: constexpr variable 'no_init' without initializer
+c23-constexpr-error.c:2:15: error: function 'foo' cannot be declared constexpr
+c23-constexpr-error.c:6:23: error: constexpr cannot be used with typedef
+c23-constexpr-error.c:7:22: error: constexpr cannot be used with extern
+c23-constexpr-error.c:8:24: error: constexpr register variable 'reg' at file scope
+c23-constexpr-error.c:9:10: error: parameter 'p' cannot be declared constexpr
+c23-constexpr-error.c:13:23: error: struct or union member 'm' cannot be declared constexpr
+c23-constexpr-error.c:15:21: error: duplicate constexpr
+c23-constexpr-error.c:18:28: error: constexpr cannot be used with thread_local
+c23-constexpr-error.c:21:2: error: constexpr in empty declaration
+c23-constexpr-error.c:34:15: error: underspecified declaration of 'already_in_scope', which is already declared in this scope
+c23-constexpr-error.c:17:26: error: non-constant initializer for constexpr object
+c23-constexpr-error.c:22:24: error: constexpr variable 'cv' cannot have volatile, atomic, or restrict type
+c23-constexpr-error.c:23:23: error: constexpr variable 'ca' cannot have volatile, atomic, or restrict type
+c23-constexpr-error.c:24:15: error: constexpr variable 'cr' cannot have volatile, atomic, or restrict type
+c23-constexpr-error.c:25:27: error: constexpr pointer initializer is not null
+c23-constexpr-error.c:26:33: error: constexpr pointer initializer is not null
+c23-constexpr-error.c:27:29: error: constexpr integer initializer is not an integer constant expression
+c23-constexpr-error.c:28:35: error: constexpr initializer not representable in target type
+c23-constexpr-error.c:29:33: error: constexpr initializer not representable in target type
+c23-constexpr-error.c:30:27: error: constexpr initializer not representable in target type
+c23-constexpr-error.c:31:31: error: constexpr initializer not representable in target type
+c23-constexpr-error.c:32:31: error: constexpr initializer not representable in target type
+c23-constexpr-error.c:40:40: error: constexpr pointer initializer is not null
+c23-constexpr-error.c:40:51: error: constexpr initializer not representable in target type
+c23-constexpr-error.c:45:9: error: assignment to const expression
+c23-constexpr-error.c:48:9: error: assignment to const expression
+c23-constexpr-error.c:51:12: error: assignment to const expression
+c23-constexpr-error.c:53:23: error: constexpr variable 'vla' cannot have variably modified type
+ * check-error-end
+ */
diff --git a/validation/c23-constexpr-self-init.c b/validation/c23-constexpr-self-init.c
new file mode 100644
index 000000000000..2af16572a778
--- /dev/null
+++ b/validation/c23-constexpr-self-init.c
@@ -0,0 +1,41 @@
+/*
+ * Verify that self-referencing constexpr initializers do not cause
+ * infinite recursion or stack overflow in constant_symbol_value().
+ */
+
+static constexpr int a = a;
+static constexpr int b = b + 1;
+
+static void test_local(void)
+{
+	constexpr int x = x;
+	constexpr int y = y + 1;
+	int c = 5;
+	{
+		constexpr int c = c;
+		(void)c;
+	}
+	constexpr int sz = sizeof(sz);
+	(void)sz;
+	(void)c;
+}
+
+static void test_eval(void)
+{
+	_Static_assert(b == 0, "");
+}
+
+/*
+ * check-name: C23 constexpr self-initialization recursion
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-constexpr-self-init.c:7:26: error: underspecified symbol 'b' used in its own initializer
+c23-constexpr-self-init.c:25:26: error: bad constant expression
+c23-constexpr-self-init.c:6:26: error: underspecified symbol 'a' used in its own initializer
+c23-constexpr-self-init.c:11:27: error: underspecified symbol 'x' used in its own initializer
+c23-constexpr-self-init.c:12:27: error: underspecified symbol 'y' used in its own initializer
+c23-constexpr-self-init.c:15:35: error: underspecified symbol 'c' used in its own initializer
+c23-constexpr-self-init.c:18:35: error: underspecified symbol 'sz' used in its own initializer
+ * check-error-end
+ */
diff --git a/validation/c23-constexpr.c b/validation/c23-constexpr.c
new file mode 100644
index 000000000000..ec0cf227154f
--- /dev/null
+++ b/validation/c23-constexpr.c
@@ -0,0 +1,100 @@
+#ifdef __CHECKER__
+#define ASSERT_TYPE(expr, T) _Static_assert([typeof(expr)] == [T], "")
+#else
+#define ASSERT_TYPE(expr, T) \
+	_Static_assert(_Generic((typeof(expr) *)0, typeof(T) *: 1, default: 0), "")
+#endif
+
+static constexpr int N = 5;
+static constexpr auto A = 10;
+static constexpr double D = 2.5;
+static constexpr int TRUNC = (int)D;
+constexpr int FILE_CE = 7;
+
+struct inner_c {
+	int a;
+	int b;
+};
+
+struct outer_c {
+	int x;
+	struct inner_c in;
+};
+
+static constexpr struct outer_c OUTER = { 1, { 2, 42 } };
+static constexpr struct outer_c OUTER_DESIG = { .in.b = 99 };
+static constexpr struct outer_c OUTER_COPY = OUTER;
+
+typedef int pair_t[2];
+static constexpr pair_t CE_PAIR = { 10, 20 };
+
+static constexpr int *NP = nullptr;
+static constexpr const int *CNP = (void *)0;
+static constexpr typeof(nullptr) NULLP = nullptr;
+static constexpr int *NP2 = NP;
+
+static constexpr char STR[] = "ok";
+static constexpr bool B0 = 0;
+static constexpr bool B1 = true;
+static constexpr float EXACT_F = 16777216;
+
+struct large_c {
+	int pad[1024];
+	int tail;
+};
+
+static constexpr struct large_c LARGE = { .tail = 77 };
+
+enum my_enum { ENUM_VAL = N * 2 };
+
+struct bitfield_test {
+	int b : N;
+};
+
+static int array[N];
+
+static void test(int val)
+{
+	static_assert(N == 5);
+	static_assert(A == 10);
+	static_assert(TRUNC == 2);
+	static_assert(FILE_CE == 7);
+	static_assert(OUTER.in.b == 42);
+	static_assert(OUTER_DESIG.in.b == 99);
+	static_assert(OUTER_COPY.in.b == 42);
+	static_assert(LARGE.tail == 77);
+	static_assert(sizeof(array) == 5 * sizeof(int));
+	static_assert(ENUM_VAL == 10);
+	static_assert(sizeof(STR) == 3);
+	static_assert(!B0 && B1);
+	static_assert((int)EXACT_F == 16777216);
+
+	ASSERT_TYPE(A, const int);
+	ASSERT_TYPE(NP, int *const);
+	ASSERT_TYPE(CNP, const int *const);
+	ASSERT_TYPE(NULLP, const typeof(nullptr));
+	ASSERT_TYPE(NP2, int *const);
+	ASSERT_TYPE(CE_PAIR[0], const int);
+
+	/* Ensure typedef pair_t itself was not mutated to const */
+	pair_t mut_pair = { 1, 2 };
+	mut_pair[0] = 3;
+
+	constexpr int LOCAL = 3;
+	constexpr register int REG_LOCAL = 4;
+	static_assert(LOCAL == 3);
+	static_assert(REG_LOCAL == 4);
+	switch (val) {
+	case LOCAL:
+	case REG_LOCAL:
+		break;
+	}
+
+	const int *p = &N;
+	(void)p;
+}
+
+/*
+ * check-name: C23 constexpr object declarations
+ * check-command: sparse -std=c23 $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 51/73] c23: Support enumerations with fixed underlying types
  2026-11-09 20:31 Bart Van Assche
                   ` (49 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
                   ` (21 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Implement ISO C23 enumerations with fixed underlying types
('enum [tag] : type { ... }' and standalone 'enum tag : type;'):
- Parse and canonicalize fixed integer underlying types, enforce
  redeclaration compatibility, and reject non-standalone opaque
  specifiers per C23 6.7.2.2p5.
- Disambiguate fixed-type ': type' from unnamed enum bit-fields and
  _Generic 'enum tag: expr' associations.
- Type enumerators per C23 6.7.2.2p12-13 in C23 mode (giving fixed-enum
  members the underlying type during and after the brace list, and giving
  unfixed-enum members 'int' only when the entire enum fits in 'int'),
  while preserving per-enumerator GNU typing in pre-C23 modes.
- Support bool-backed enums and diagnose ULLONG_MAX auto-increment
  overflow before wraparound.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                       |   2 +-
 expand.c                         |  13 +-
 parse.c                          | 269 +++++++++++++++++++++++++------
 symbol.c                         |  19 ++-
 symbol.h                         |  38 ++---
 validation/c23-enum-type-cast.c  |  70 ++++++++
 validation/c23-enum-type-error.c |  40 +++++
 validation/c23-enum-type.c       | 116 +++++++++++++
 8 files changed, 494 insertions(+), 73 deletions(-)
 create mode 100644 validation/c23-enum-type-cast.c
 create mode 100644 validation/c23-enum-type-error.c
 create mode 100644 validation/c23-enum-type.c

diff --git a/evaluate.c b/evaluate.c
index b7e352f55e6c..43ad5f59b860 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2486,7 +2486,7 @@ static struct symbol *evaluate_sizeof(struct expression *expr)
 	}
 
 	if (is_bool_type(type)) {
-		if (Wsizeof_bool)
+		if (Wsizeof_bool && cbase_type(type) == &bool_ctype)
 			warning(expr->pos, "expression using sizeof _Bool");
 		size = bits_to_bytes(bits_in_bool) * bits_in_char;
 	}
diff --git a/expand.c b/expand.c
index 73b37b93534e..13a3bc0457ba 100644
--- a/expand.c
+++ b/expand.c
@@ -106,12 +106,23 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
 	// For pointers and integers, we can just move the value around
 	expr->type = EXPR_VALUE;
 	expr->taint = old->taint;
-	if (old_size == new_size) {
+	if (old_size == new_size && !is_bool_type(newtype)) {
 		expr->value = old->value;
 		expr->ctype = newtype;
 		return;
 	}
 
+	if (is_bool_type(newtype)) {
+		bool non_zero = old->value != 0;
+		bool is_one = old->value == 1;
+		expr->ctype = newtype;
+		expr->value = non_zero;
+		if (!conservative && non_zero && !is_one)
+			warning(old->pos, "odd constant _Bool cast (%llx becomes 1)",
+				old->value);
+		return;
+	}
+
 	// expand it to the full "long long" value
 	value = get_longlong(old);
 	expr->ctype = newtype;
diff --git a/parse.c b/parse.c
index a7d31fad1a10..48793dd51671 100644
--- a/parse.c
+++ b/parse.c
@@ -50,6 +50,7 @@ struct statement_list *function_computed_goto_list;
 
 static struct token *statement(struct token *token, struct statement **tree);
 static struct token *handle_attributes(struct token *token, struct decl_state *ctx);
+static struct token *skip_attributes(struct token *token);
 
 typedef struct token *declarator_t(struct token *, struct symbol *, struct decl_state *);
 static declarator_t
@@ -765,6 +766,32 @@ struct symbol *label_symbol(struct token *token, int used)
 	return sym;
 }
 
+static struct symbol *canonical_enum_underlying_type(struct symbol *sym)
+{
+	sym = cbase_type(sym);
+	if (sym == &sshort_ctype)
+		return &short_ctype;
+	if (sym == &sint_ctype)
+		return &int_ctype;
+	if (sym == &slong_ctype)
+		return &long_ctype;
+	if (sym == &sllong_ctype)
+		return &llong_ctype;
+	if (sym == &sint128_ctype)
+		return &int128_ctype;
+	return sym;
+}
+
+static bool has_enum_type_specifier(struct token *token)
+{
+	struct token *next;
+
+	if (!match_op(token, ':'))
+		return false;
+	next = skip_attributes(token->next);
+	return lookup_type(next) != 0;
+}
+
 static struct token *struct_union_enum_specifier(enum type type,
 	struct token *token, struct decl_state *ctx,
 	struct token *(*parse)(struct token *, struct symbol *))
@@ -778,7 +805,10 @@ static struct token *struct_union_enum_specifier(enum type type,
 		sym = lookup_symbol(token->ident, NS_STRUCT);
 		if (!sym ||
 		    (is_outer_scope(sym->scope) &&
-		     (match_op(token->next,';') || match_op(token->next,'{')))) {
+		     (match_op(token->next, ';') ||
+		      match_op(token->next, '{') ||
+		      (type == SYM_ENUM &&
+		       has_enum_type_specifier(token->next))))) {
 			// Either a new symbol, or else an out-of-scope
 			// symbol being redefined.
 			sym = alloc_symbol(token->pos, type);
@@ -789,6 +819,60 @@ static struct token *struct_union_enum_specifier(enum type type,
 		ctx->ctype.base_type = sym;
 		repos = &token->pos;
 		token = token->next;
+
+		if (type == SYM_ENUM && has_enum_type_specifier(token)) {
+			struct position colon_pos = token->pos;
+			struct symbol *underlying = NULL;
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(token->pos,
+					"ISO C does not support specifying 'enum' underlying types before C23");
+			token = typename(token->next, &underlying, NULL);
+			underlying = examine_symbol_type(underlying);
+			if (!underlying || !is_int_type(underlying) ||
+			    is_enum_type(underlying)) {
+				sparse_error(token->pos,
+					     "invalid 'enum' underlying type");
+				underlying = &int_ctype;
+			}
+			underlying = canonical_enum_underlying_type(underlying);
+			if (sym->fixed_type) {
+				if (canonical_enum_underlying_type(sym->ctype.base_type) !=
+				    underlying)
+					sparse_error(
+						token->pos,
+						"'enum' underlying type incompatible with previous declaration");
+			} else if (sym->examined || sym->symbol_list ||
+				   (sym->ctype.base_type ==
+				    &incomplete_ctype)) {
+				sparse_error(
+					token->pos,
+					"'enum' declared both with and without fixed underlying type");
+			} else {
+				sym->fixed_type = 1;
+				sym->ctype.base_type = underlying;
+				sym->ctype.modifiers |=
+					(underlying->ctype.modifiers &
+					 MOD_UNSIGNED);
+				if (underlying == &bool_ctype)
+					sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+				else
+					sym->bit_size = underlying->bit_size;
+				sym->ctype.alignment =
+					underlying->ctype.alignment;
+				sym->rank = underlying->rank;
+			}
+			token = handle_attributes(token, &attr);
+			if (!match_op(token, '{')) {
+				ctx->opaque_fixed_enum = 1;
+				ctx->opaque_fixed_enum_pos = colon_pos;
+			}
+		} else if (type == SYM_ENUM && sym->fixed_type &&
+			   match_op(token, '{')) {
+			sparse_error(
+				token->pos,
+				"'enum' declared with but defined without fixed underlying type");
+		}
+
 		if (!match_op(token, '{')) {
 			if (attr.designated_init) {
 				if (sym->type == SYM_STRUCT)
@@ -814,11 +898,44 @@ static struct token *struct_union_enum_specifier(enum type type,
 
 		// Mark the structure as needing re-examination
 		sym->examined = 0;
-	} else if (match_op(token, '{')) {
+	} else if (match_op(token, '{') ||
+		   (type == SYM_ENUM && match_op(token, ':'))) {
 		// private struct/union/enum type
 		sym = alloc_symbol(token->pos, type);
 		set_current_scope(sym);		// used by dissect
 		ctx->ctype.base_type = sym;
+		if (type == SYM_ENUM && match_op(token, ':')) {
+			struct symbol *underlying = NULL;
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(token->pos,
+					"ISO C does not support specifying 'enum' underlying types before C23");
+			token = typename(token->next, &underlying, NULL);
+			underlying = examine_symbol_type(underlying);
+			if (!underlying || !is_int_type(underlying) ||
+			    is_enum_type(underlying)) {
+				sparse_error(token->pos,
+					     "invalid 'enum' underlying type");
+				underlying = &int_ctype;
+			}
+			underlying = canonical_enum_underlying_type(underlying);
+			sym->fixed_type = 1;
+			sym->ctype.base_type = underlying;
+			sym->ctype.modifiers |=
+				(underlying->ctype.modifiers & MOD_UNSIGNED);
+			if (underlying == &bool_ctype)
+				sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+			else
+				sym->bit_size = underlying->bit_size;
+			sym->ctype.alignment = underlying->ctype.alignment;
+			sym->rank = underlying->rank;
+			token = handle_attributes(token, &attr);
+			if (!match_op(token, '{')) {
+				sparse_error(
+					token->pos,
+					"expected '{' after enum type specifier");
+				return token;
+			}
+		}
 	} else {
 		sparse_error(token->pos, "expected declaration");
 		ctx->ctype.base_type = &bad_ctype;
@@ -941,8 +1058,8 @@ static struct range type_range(struct symbol *type)
 	long long min;
 
 	if (is_signed_type(type)) {
-		min = sign_bit(size);
-		max = min - 1;
+		min = sign_extend(sign_bit(size), size);
+		max = sign_mask(size);
 	} else {
 		min = 0;
 		max = bits_mask(size);
@@ -963,24 +1080,37 @@ static int val_in_range(struct range *range, long long sval, struct symbol *vtyp
 		return uval <= range->pos;
 }
 
-static void cast_enum_list(struct symbol_list *list, struct symbol *base_type)
+static void cast_enum_list(struct symbol_list *list, struct symbol *base_type,
+			   struct range range)
 {
 	struct range irange = type_range(&int_ctype);
+	struct symbol *target = base_type;
 	struct symbol *sym;
 
+	if (is_int_type(base_type) && range.neg >= irange.neg &&
+	    range.pos <= irange.pos)
+		target = &int_ctype;
+
 	FOR_EACH_PTR(list, sym) {
 		struct expression *expr = sym->initializer;
 		struct symbol *ctype;
-		long long val;
+		struct symbol *etype = target;
+
 		if (expr->type != EXPR_VALUE)
 			continue;
 		ctype = expr->ctype;
-		val = get_expression_value(expr);
-		if (is_int_type(ctype) && val_in_range(&irange, val, ctype)) {
-			expr->ctype = &int_ctype;
-			continue;
+		if (standard < STANDARD_C23 && etype != &int_ctype &&
+		    is_int_type(ctype)) {
+			long long val = get_expression_value(expr);
+
+			if (!(is_signed_type(ctype) && val < 0) &&
+			    (unsigned long long)val <= irange.pos)
+				etype = &int_ctype;
 		}
-		cast_value(expr, base_type, expr);
+		if (!is_int_type(ctype) && etype == &int_ctype)
+			continue;
+		cast_value(expr, etype, expr);
+		expr->ctype = etype;
 	} END_FOR_EACH_PTR(sym);
 }
 
@@ -992,13 +1122,16 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 	int mix_bitwise = 0;
 
 	parent->examined = 1;
-	parent->ctype.base_type = &int_ctype;
+	if (parent->fixed_type)
+		base_type = parent->ctype.base_type;
+	else
+		parent->ctype.base_type = &int_ctype;
 	while (token_type(token) == TOKEN_IDENT) {
 		struct expression *expr = NULL;
 		struct token *next = token->next;
 		struct decl_state ctx = { };
 		struct symbol *sym;
-		bool overflow = false;
+		bool overflowed = false;
 
 		// FIXME: only 'deprecated' should be accepted
 		next = handle_attributes(next, &ctx);
@@ -1016,7 +1149,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 				ctype = &int_ctype;
 			}
 		} else if (!ctype) {
-			ctype = &int_ctype;
+			ctype = parent->fixed_type ? base_type : &int_ctype;
 		} else if (is_int_type(ctype)) {
 			struct range crange = type_range(ctype);
 			if (is_signed_type(ctype) && (long long)lastval == crange.pos) {
@@ -1038,13 +1171,20 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 				else if (ctype->bit_size < bits_in_longlong)
 					ctype = &ullong_ctype;
 				else {
-					sparse_error(token->pos,
-						"enumerator value for '%s' overflows",
-						show_ident(token->ident));
-					overflow = true;
+					if (parent->fixed_type)
+						sparse_error(
+							token->pos,
+							"enumerator value outside the range of underlying type");
+					else
+						sparse_error(
+							token->pos,
+							"enumerator value for '%s' overflows",
+							show_ident(token->ident));
+					ctype = &ullong_ctype;
+					overflowed = true;
 				}
 			}
-			if (!overflow)
+			if (!overflowed)
 				lastval++;
 		} else {
 			error_die(token->pos, "can't increment the last enum member");
@@ -1056,6 +1196,16 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			expr->ctype = ctype;
 		}
 
+		if (parent->fixed_type) {
+			struct range urange = type_range(base_type);
+			if (!overflowed && !val_in_range(&urange, lastval, ctype))
+				sparse_error(
+					token->pos,
+					"enumerator value outside the range of underlying type");
+			cast_value(expr, base_type, expr);
+			ctype = base_type;
+		}
+
 		sym = alloc_symbol(token->pos, SYM_NODE);
 		bind_symbol(sym, token->ident, NS_SYMBOL);
 		sym->ctype.modifiers &= ~MOD_ADDRESSABLE;
@@ -1064,7 +1214,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 		sym->ctype.base_type = parent;
 		add_ptr_list(&parent->symbol_list, sym);
 
-		if (base_type != &bad_ctype) {
+		if (!parent->fixed_type && base_type != &bad_ctype) {
 			if (ctype->type == SYM_NODE)
 				ctype = ctype->ctype.base_type;
 			if (ctype->type == SYM_ENUM) {
@@ -1104,7 +1254,7 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			}
 			parent->ctype.base_type = base_type;
 		}
-		if (is_int_type(base_type)) {
+		if (!parent->fixed_type && is_int_type(base_type)) {
 			update_range(&range, lastval, ctype);
 		}
 		token = next;
@@ -1115,33 +1265,40 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			break;
 		token = token->next;
 	}
-	if (!base_type) {
-		sparse_error(token->pos, "empty enum definition");
-		base_type = &bad_ctype;
-	}
-	else if (!is_int_type(base_type))
-		;
-	else if (type_is_ok(&uint_ctype, range))
-		base_type = &uint_ctype;
-	else if (type_is_ok(&int_ctype, range))
-		base_type = &int_ctype;
-	else if (type_is_ok(&ulong_ctype, range))
-		base_type = &ulong_ctype;
-	else if (type_is_ok(&long_ctype, range))
-		base_type = &long_ctype;
-	else if (type_is_ok(&ullong_ctype, range))
-		base_type = &ullong_ctype;
-	else if (type_is_ok(&llong_ctype, range))
-		base_type = &llong_ctype;
-	else
-		base_type = &bad_ctype;
-	parent->ctype.base_type = base_type;
-	parent->ctype.modifiers |= (base_type->ctype.modifiers & MOD_UNSIGNED);
-	parent->examined = 0;
+	if (!parent->fixed_type) {
+		if (!base_type) {
+			sparse_error(token->pos, "empty enum definition");
+			base_type = &bad_ctype;
+		} else if (!is_int_type(base_type))
+			;
+		else if (type_is_ok(&uint_ctype, range))
+			base_type = &uint_ctype;
+		else if (type_is_ok(&int_ctype, range))
+			base_type = &int_ctype;
+		else if (type_is_ok(&ulong_ctype, range))
+			base_type = &ulong_ctype;
+		else if (type_is_ok(&long_ctype, range))
+			base_type = &long_ctype;
+		else if (type_is_ok(&ullong_ctype, range))
+			base_type = &ullong_ctype;
+		else if (type_is_ok(&llong_ctype, range))
+			base_type = &llong_ctype;
+		else
+			base_type = &bad_ctype;
+		parent->ctype.base_type = base_type;
+		parent->ctype.modifiers |=
+			(base_type->ctype.modifiers & MOD_UNSIGNED);
+		parent->examined = 0;
 
-	if (mix_bitwise)
-		return token;
-	cast_enum_list(parent->symbol_list, base_type);
+		if (mix_bitwise)
+			return token;
+		cast_enum_list(parent->symbol_list, base_type, range);
+	} else {
+		if (!parent->symbol_list) {
+			sparse_error(token->pos, "empty enum definition");
+		}
+		parent->examined = 0;
+	}
 
 	return token;
 }
@@ -2130,6 +2287,9 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 	unsigned long mod;
 
 	token = declaration_specifiers(token, &ctx);
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 	mod = decl_modifiers(&ctx);
 	saved = ctx.ctype;
 	for (;;) {
@@ -2200,6 +2360,9 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	struct decl_state ctx = {.prefer_abstract = 1};
 
 	token = declaration_specifiers(token, &ctx);
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 	ctx.ident = &sym->ident;
 	token = declarator(token, &ctx);
 	token = handle_attributes(token, &ctx);
@@ -2240,6 +2403,9 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 	token = declaration_specifiers(token, &ctx);
 	if (ctx.has_alignas)
 		sparse_error(sym->pos, "alignment specified for type name");
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 	if (ctx.autotype) {
 		sparse_error(sym->pos, "'%s' in type name",
 			     ctx.has_auto ? "auto" : "__auto_type");
@@ -3309,6 +3475,12 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	decl = alloc_symbol(token->pos, SYM_NODE);
 	/* Just a type declaration? */
 	if (match_op(token, ';')) {
+		if (ctx.opaque_fixed_enum &&
+		    (ctx.storage_class || (mod & ~MOD_UNUSED) ||
+		     ctx.ctype.modifiers || ctx.has_auto || ctx.is_constexpr))
+			sparse_error(
+				ctx.opaque_fixed_enum_pos,
+				"'enum' underlying type may not be specified here");
 		if (ctx.autotype)
 			sparse_error(token->pos, "%s in empty declaration",
 				     ctx.has_auto ? "auto" : "__auto_type");
@@ -3318,6 +3490,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		apply_modifiers(token->pos, &ctx);
 		return token->next;
 	}
+	if (ctx.opaque_fixed_enum)
+		sparse_error(ctx.opaque_fixed_enum_pos,
+			     "'enum' underlying type may not be specified here");
 
 	saved = ctx.ctype;
 	token = declarator(token, &ctx);
diff --git a/symbol.c b/symbol.c
index fc11e1bbcec3..28fa12352569 100644
--- a/symbol.c
+++ b/symbol.c
@@ -584,12 +584,21 @@ static struct symbol *examine_enum_type(struct symbol *sym)
 	struct symbol *base_type = examine_base_type(sym);
 
 	sym->ctype.modifiers |= (base_type->ctype.modifiers & MOD_SIGNEDNESS);
-	sym->bit_size = bits_in_enum;
-	if (base_type->bit_size > sym->bit_size)
-		sym->bit_size = base_type->bit_size;
-	sym->ctype.alignment = enum_alignment;
-	if (base_type->ctype.alignment > sym->ctype.alignment)
+	if (sym->fixed_type) {
+		if (base_type == &bool_ctype)
+			sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
+		else
+			sym->bit_size = base_type->bit_size;
 		sym->ctype.alignment = base_type->ctype.alignment;
+	} else {
+		sym->bit_size = bits_in_enum;
+		if (base_type->bit_size > sym->bit_size)
+			sym->bit_size = base_type->bit_size;
+		sym->ctype.alignment = enum_alignment;
+		if (base_type->ctype.alignment > sym->ctype.alignment)
+			sym->ctype.alignment = base_type->ctype.alignment;
+	}
+	sym->rank = base_type->rank;
 	return sym;
 }
 
diff --git a/symbol.h b/symbol.h
index f8556fdf3b86..8035d9d21b3c 100644
--- a/symbol.h
+++ b/symbol.h
@@ -120,6 +120,8 @@ struct decl_state {
 	unsigned char is_constexpr;
 	unsigned char designated_init;
 	unsigned char transparent_union;
+	unsigned char opaque_fixed_enum;
+	struct position opaque_fixed_enum_pos;
 };
 
 struct pseudo;
@@ -186,22 +188,14 @@ struct symbol {
 		struct /* NS_SYMBOL */ {
 			unsigned long	offset;
 			int		bit_size;
-			unsigned int	bit_offset:8,
-					bogus_linear:1,
-					variadic:1,
-					initialized:1,
-					examined:1,
-					expanding:1,
-					evaluated:1,
-					has_flex_array:1,
-					string:1,
-					designated_init:1,
-					forced_arg:1,
-					accessed:1,
-					builtin:1,
-					torename:1,
-					packed:1,
-					transparent_union:1;
+			unsigned int bit_offset : 8, bogus_linear : 1,
+				variadic : 1, initialized : 1, examined : 1,
+				expanding : 1, evaluated : 1,
+				has_flex_array : 1, string : 1,
+				designated_init : 1, forced_arg : 1,
+				accessed : 1, builtin : 1, torename : 1,
+				packed : 1, transparent_union : 1,
+				fixed_type : 1;
 			unsigned int has_alignas : 1;
 			unsigned int underspecified : 1;
 			unsigned int c23_auto : 1;
@@ -435,6 +429,13 @@ static inline int is_func_type(struct symbol *type)
 	return type->type == SYM_FN;
 }
 
+static inline struct symbol *cbase_type(struct symbol *sym)
+{
+	while (sym && sym->type == SYM_NODE)
+		sym = sym->ctype.base_type;
+	return sym;
+}
+
 static inline int is_array_type(struct symbol *type)
 {
 	if (type->type == SYM_NODE)
@@ -484,9 +485,8 @@ static inline int is_void_type(struct symbol *type)
 
 static inline int is_bool_type(struct symbol *type)
 {
-	if (type->type == SYM_NODE)
-		type = type->ctype.base_type;
-	if (type->type == SYM_BITFIELD)
+	while (type && (type->type == SYM_NODE || type->type == SYM_ENUM ||
+			type->type == SYM_BITFIELD))
 		type = type->ctype.base_type;
 	return type == &bool_ctype;
 }
diff --git a/validation/c23-enum-type-cast.c b/validation/c23-enum-type-cast.c
new file mode 100644
index 000000000000..447a9882ca3e
--- /dev/null
+++ b/validation/c23-enum-type-cast.c
@@ -0,0 +1,70 @@
+/*
+ * Check enumerator types and explicit/implicit casts involving enums
+ * with fixed underlying types.
+ */
+
+enum E1 : unsigned long long {
+	A = 0,
+	B = 1ULL << 40,
+};
+
+_Static_assert(_Generic(A, unsigned long long: 1, default: 0) == 1, "A type");
+_Static_assert(_Generic(B, unsigned long long: 1, default: 0) == 1, "B type");
+_Static_assert(sizeof(A) == sizeof(unsigned long long), "A size");
+_Static_assert(sizeof(B) == sizeof(unsigned long long), "B size");
+_Static_assert(A == 0, "A value");
+_Static_assert(B == (1ULL << 40), "B value");
+_Static_assert((enum E1)(1ULL << 40) == B, "cast to E1");
+_Static_assert((unsigned long long)B == (1ULL << 40), "cast from B");
+_Static_assert((short)A == 0, "cast A to short");
+_Static_assert((unsigned short)B == 0, "narrow cast B to ushort");
+
+enum E2 : long long {
+	C = -1LL,
+	D = 1LL << 40,
+};
+
+_Static_assert(_Generic(C, long long: 1, default: 0) == 1, "C type");
+_Static_assert(_Generic(D, long long: 1, default: 0) == 1, "D type");
+_Static_assert(sizeof(C) == sizeof(long long), "C size");
+_Static_assert(sizeof(D) == sizeof(long long), "D size");
+_Static_assert((enum E2)-1 == C, "cast -1 to E2");
+
+enum U8 : unsigned char {
+	U8_ZERO = 0,
+};
+
+_Static_assert((enum U8)200 == 200, "narrow u8 cast");
+_Static_assert((enum U8)258 == 2, "out-of-range u8 cast wraps");
+_Static_assert((enum U8)-1 == 255, "negative u8 cast wraps");
+_Static_assert(_Generic((enum U8)200, enum U8: 1, int: 0, default: 0) == 1,
+	       "cast yields enum U8");
+_Static_assert(_Generic((enum U8)200, unsigned char: 1, int: 0, default: 0) == 1,
+	       "enum U8 compatible with unsigned char");
+
+enum S8 : signed char {
+	S8_NEG = -1,
+};
+
+_Static_assert((enum S8)-128 == -128, "narrow s8 cast");
+_Static_assert(_Generic((enum S8)0, signed char: 1, int: 0, default: 0) == 1,
+	       "enum S8 compatible with signed char");
+
+enum B : bool {
+	B_FALSE = false,
+	B_TRUE = true,
+};
+
+_Static_assert((enum B)2 == 1, "bool enum cast from 2");
+_Static_assert((enum B)-5 == 1, "bool enum cast from -5");
+_Static_assert((enum B)0 == 0, "bool enum cast from 0");
+
+/*
+ * check-name: C23 enum underlying type casting
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-enum-type-cast.c:20:32: warning: cast truncates bits from constant value (10000000000 becomes 0)
+c23-enum-type-cast.c:38:25: warning: cast truncates bits from constant value (102 becomes 2)
+ * check-error-end
+ */
diff --git a/validation/c23-enum-type-error.c b/validation/c23-enum-type-error.c
new file mode 100644
index 000000000000..3d2a9d5ec6a4
--- /dev/null
+++ b/validation/c23-enum-type-error.c
@@ -0,0 +1,40 @@
+enum E1 : char { A = 300 };
+enum E2 : unsigned char { B = -1 };
+enum E3 : unsigned char { C = 255, D };
+enum E4 : short;
+enum E4 : int;
+enum E5;
+enum E5 : short;
+enum E6 : short;
+enum E6 { F };
+enum E7 : float;
+enum E8 : bool { B0 = 0, B1, B2 };
+enum E9 : unsigned long long { UMAX = ~0ULL, UOVER };
+enum E10 : int bad_var;
+typedef enum E11 : short e11_t;
+void bad_enum_param(enum E12 : int p);
+int bad_enum_sizeof = sizeof(enum E13 : int);
+enum E14 : char;
+enum E14 : signed char;
+
+/*
+ * check-name: C23 enum with fixed underlying type errors
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-enum-type-error.c:1:18: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:2:27: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:3:36: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:5:14: error: 'enum' underlying type incompatible with previous declaration
+c23-enum-type-error.c:7:16: error: 'enum' declared both with and without fixed underlying type
+c23-enum-type-error.c:9:9: error: 'enum' declared with but defined without fixed underlying type
+c23-enum-type-error.c:10:16: error: invalid 'enum' underlying type
+c23-enum-type-error.c:11:30: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:12:46: error: enumerator value outside the range of underlying type
+c23-enum-type-error.c:13:10: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:14:18: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:15:30: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:16:39: error: 'enum' underlying type may not be specified here
+c23-enum-type-error.c:18:23: error: 'enum' underlying type incompatible with previous declaration
+ * check-error-end
+ */
diff --git a/validation/c23-enum-type.c b/validation/c23-enum-type.c
new file mode 100644
index 000000000000..03571bc99e06
--- /dev/null
+++ b/validation/c23-enum-type.c
@@ -0,0 +1,116 @@
+enum E_char : char {
+	A = 0,
+	B,
+};
+static_assert(sizeof(enum E_char) == sizeof(char), "sizeof E_char");
+static_assert(sizeof(A) == sizeof(char), "sizeof A");
+static_assert(A == 0, "val A");
+static_assert(B == 1, "val B");
+
+enum E_short : short;
+static_assert(sizeof(enum E_short) == sizeof(short), "sizeof E_short fwd");
+enum E_short : short {
+	C = 10,
+	D,
+};
+static_assert(sizeof(enum E_short) == sizeof(short), "sizeof E_short def");
+static_assert(sizeof(C) == sizeof(short), "sizeof C");
+static_assert(C == 10, "val C");
+static_assert(D == 11, "val D");
+
+enum : unsigned long long {
+	ULL1 = 0x100000000ULL,
+	ULL2,
+};
+static_assert(sizeof(ULL1) == sizeof(unsigned long long), "sizeof ULL1");
+static_assert(ULL1 == 0x100000000ULL, "val ULL1");
+static_assert(ULL2 == 0x100000001ULL, "val ULL2");
+
+enum E_redecl : int;
+enum E_redecl : signed int;
+enum E_redecl : int {
+	R1 = 100,
+	R2 = 200,
+};
+static_assert(sizeof(enum E_redecl) == sizeof(int), "sizeof E_redecl");
+static_assert(R1 == 100, "val R1");
+static_assert(R2 == 200, "val R2");
+
+enum E_compat : signed short;
+enum E_compat : short {
+	EC1 = -10,
+};
+static_assert(sizeof(enum E_compat) == sizeof(short), "sizeof E_compat");
+static_assert(EC1 == -10, "val EC1");
+
+enum E_signed : signed char {
+	NEG = -5,
+	NEG_PLUS_ONE,
+};
+static_assert(sizeof(enum E_signed) == 1, "sizeof E_signed");
+static_assert(NEG == -5, "val NEG");
+static_assert(NEG_PLUS_ONE == -4, "val NEG_PLUS_ONE");
+
+enum E_uchar : unsigned char {
+	MAX_BYTE = 255,
+};
+static_assert(sizeof(enum E_uchar) == 1, "sizeof E_uchar");
+static_assert(MAX_BYTE == 255, "val MAX_BYTE");
+
+typedef unsigned short uint16_t;
+enum E_typedef : uint16_t {
+	T1 = 65535,
+};
+static_assert(sizeof(enum E_typedef) == sizeof(uint16_t), "sizeof E_typedef");
+static_assert(T1 == 65535, "val T1");
+
+enum E_bool : bool {
+	EB_FALSE,
+	EB_TRUE,
+};
+static_assert(sizeof(enum E_bool) == sizeof(bool), "sizeof E_bool");
+static_assert(EB_FALSE == 0 && EB_TRUE == 1, "val E_bool");
+
+enum E_qual : const unsigned int {
+	EQ1 = 42,
+};
+static_assert(_Generic(EQ1, unsigned int: 1, default: 0) == 1, "EQ1 type");
+
+enum E_brace : unsigned long long {
+	EB_A = 1,
+	EB_B = _Generic(EB_A, unsigned long long: 2, default: 0),
+};
+static_assert(EB_B == 2, "inside-brace fixed type");
+static_assert(_Generic(EB_A, unsigned long long: 1, default: 0) == 1,
+	      "after-brace fixed type");
+
+struct E_unnamed_bf {
+	enum E_uchar : 8;
+	int x;
+};
+static_assert(sizeof(struct E_unnamed_bf) >= sizeof(int), "unnamed enum bitfield");
+
+enum E_unfixed_fit {
+	UF_A = 1U,
+};
+static_assert(_Generic(UF_A, int: 1, default: 0) == 1, "unfixed fits int");
+
+enum E_unfixed_wide {
+	UW_A = 1,
+	UW_B = ~0U,
+};
+static_assert(_Generic(UW_A, unsigned int: 1, default: 0) == 1,
+	      "C23 unfixed wide enum gives all members underlying type");
+static_assert(_Generic(UW_B, unsigned int: 1, default: 0) == 1,
+	      "C23 unfixed wide enum second member");
+
+enum FwdFixed : int;
+static int add_fwd_fixed_enum(enum FwdFixed x)
+{
+	return x + 1;
+}
+
+/*
+ * check-name: C23 enumerations with fixed underlying types
+ * check-command: sparse -std=c23 $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 52/73] c23: Support standard [[]] attribute syntax
  2026-11-09 20:31 Bart Van Assche
                   ` (50 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
                   ` (20 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Parse C23 '[[]]' attribute specifier sequences across declarations,
declarators, types, structs, enums, parameters, labels, and statements
per ISO C23 6.7.12:
- Map standard attributes (nodiscard, maybe_unused, deprecated,
  fallthrough, noreturn, _Noreturn, reproducible, unsequenced) and
  gnu::/__gnu__:: scoped attributes to their Sparse modifiers, while
  ignoring unknown vendor-scoped attributes cleanly.
- Support '[[]]' attributes after declarator identifiers and after
  function parameter lists without misapplying function modifiers to
  pointer declarators.
- Distinguish attributed case and default labels from named goto labels
  inside switch statements, and warn under -Wpedantic in pre-C23 modes.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ident-list.h                         |   2 +
 parse.c                              | 275 +++++++++++++++++++++++----
 symbol.h                             |   7 +-
 validation/attr_in_parameter.c       |   2 +
 validation/c23-attributes-error.c    |  20 ++
 validation/c23-attributes-pedantic.c |  10 +
 validation/c23-attributes.c          | 149 +++++++++++++++
 validation/c23-param-attribute.c     |  28 +++
 8 files changed, 448 insertions(+), 45 deletions(-)
 create mode 100644 validation/c23-attributes-error.c
 create mode 100644 validation/c23-attributes-pedantic.c
 create mode 100644 validation/c23-attributes.c
 create mode 100644 validation/c23-param-attribute.c

diff --git a/ident-list.h b/ident-list.h
index 46e6cd9e4f61..bc5df947913a 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -65,6 +65,8 @@ IDENT_RESERVED(__sizeof_ptr__);
 IDENT_RESERVED(__builtin_types_compatible_p);
 IDENT_RESERVED(__builtin_offsetof);
 IDENT_RESERVED(__label__);
+IDENT(gnu);
+IDENT(__gnu__);
 
 /* Preprocessor idents.  Direct use of __IDENT avoids mentioning the keyword
  * itself by name, preventing these tokens from expanding when compiling
diff --git a/parse.c b/parse.c
index 48793dd51671..13b8cf5d43c1 100644
--- a/parse.c
+++ b/parse.c
@@ -50,6 +50,13 @@ struct statement_list *function_computed_goto_list;
 
 static struct token *statement(struct token *token, struct statement **tree);
 static struct token *handle_attributes(struct token *token, struct decl_state *ctx);
+static inline bool match_c23_attribute(struct token *token)
+{
+	return match_op(token, '[') && match_op(token->next, '[');
+}
+static struct token *c23_attribute_specifier(struct token *token,
+					     struct decl_state *ctx);
+static struct token *skip_c23_attributes(struct token *token);
 static struct token *skip_attributes(struct token *token);
 
 typedef struct token *declarator_t(struct token *, struct symbol *, struct decl_state *);
@@ -563,6 +570,7 @@ static struct init_keyword {
 	D("noderef",		&attr_mod_op,		.mods = MOD_NODEREF),
 	D("safe",		&attr_mod_op,		.mods = MOD_SAFE),
 	D("unused",		&attr_mod_op,		.mods = MOD_UNUSED),
+	D("maybe_unused",	&attr_mod_op,		.mods = MOD_UNUSED),
 	D("externally_visible",	&attr_mod_op,		.mods = MOD_EXT_VISIBLE),
 	D("force",		&attr_force_op),
 	D("bitwise",		&attr_bitwise_op,	.mods = MOD_BITWISE),
@@ -570,9 +578,14 @@ static struct init_keyword {
 	D("designated_init",	&designated_init_op),
 	D("transparent_union",	&transparent_union_op),
 	D("noreturn",		&attr_fun_op,		.mods = MOD_NORETURN),
+	D("_Noreturn",		&attr_fun_op,		.mods = MOD_NORETURN),
 	D("pure",		&attr_fun_op,		.mods = MOD_PURE),
 	A("const",		&attr_fun_op,		.mods = MOD_PURE),
 	D("gnu_inline",		&attr_fun_op,		.mods = MOD_GNU_INLINE),
+	D("nodiscard",		&ignore_attr_op),
+	D("fallthrough",	&ignore_attr_op),
+	D("unsequenced",	&attr_fun_op,		.mods = MOD_PURE),
+	D("reproducible",	&attr_fun_op,		.mods = MOD_PURE),
 
 	/* Modes */
 	D("mode",		&mode_op),
@@ -1666,22 +1679,29 @@ static struct token *attribute_transparent_union(struct token *token, struct sym
 	return token;
 }
 
-static struct token *recover_unknown_attribute(struct token *token)
+static struct token *skip_parens(struct token *token)
 {
-	struct expression *expr = NULL;
-
-	if (Wunknown_attribute)
-		warning(token->pos, "unknown attribute '%s'", show_ident(token->ident));
-	token = token->next;
 	if (match_op(token, '(')) {
-		if (match_op(token->next, ')'))
-			token = token->next->next;
-		else
-			token = parens_expression(token, &expr, "in attribute");
+		int depth = 1;
+		token = token->next;
+		while (depth && !eof_token(token)) {
+			if (match_op(token, '('))
+				depth++;
+			else if (match_op(token, ')'))
+				depth--;
+			token = token->next;
+		}
 	}
 	return token;
 }
 
+static struct token *recover_unknown_attribute(struct token *token)
+{
+	if (Wunknown_attribute)
+		warning(token->pos, "unknown attribute '%s'", show_ident(token->ident));
+	return skip_parens(token->next);
+}
+
 static struct token *attribute_specifier(struct token *token, struct symbol *sym, struct decl_state *ctx)
 {
 	token = expect(token, '(', "after attribute");
@@ -1863,7 +1883,11 @@ struct symbol *ctype_integer(int size, int want_unsigned)
 
 static struct token *handle_qualifiers(struct token *t, struct decl_state *ctx)
 {
-	while (token_type(t) == TOKEN_IDENT) {
+	while (token_type(t) == TOKEN_IDENT || match_c23_attribute(t)) {
+		if (match_c23_attribute(t)) {
+			t = c23_attribute_specifier(t, ctx);
+			continue;
+		}
 		struct symbol *s = lookup_keyword(t->ident, NS_TYPEDEF);
 		if (!s || !s->op)
 			break;
@@ -1882,7 +1906,11 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 	int class = CInt;
 	int rank = 0;
 
-	while (token_type(token) == TOKEN_IDENT) {
+	while (token_type(token) == TOKEN_IDENT || match_c23_attribute(token)) {
+		if (match_c23_attribute(token)) {
+			token = c23_attribute_specifier(token, ctx);
+			continue;
+		}
 		struct symbol *s = lookup_symbol(token->ident,
 						 NS_TYPEDEF | NS_SYMBOL);
 		if (!s || !(s->namespace & NS_TYPEDEF))
@@ -2040,16 +2068,22 @@ static bool match_attribute(struct token *token)
 
 static struct token *skip_attribute(struct token *token)
 {
-	token = token->next;
-	if (match_op(token, '(')) {
-		int depth = 1;
-		token = token->next;
-		while (depth && !eof_token(token)) {
-			if (token_type(token) == TOKEN_SPECIAL) {
-				if (token->special == '(')
-					depth++;
-				else if (token->special == ')')
-					depth--;
+	return skip_parens(token->next);
+}
+
+static struct token *skip_c23_attributes(struct token *token)
+{
+	while (match_c23_attribute(token)) {
+		token = token->next->next;
+		while (!eof_token(token)) {
+			if (match_op(token, ']') &&
+			    match_op(token->next, ']')) {
+				token = token->next->next;
+				break;
+			}
+			if (match_op(token, '(')) {
+				token = skip_parens(token);
+				continue;
 			}
 			token = token->next;
 		}
@@ -2057,27 +2091,113 @@ static struct token *skip_attribute(struct token *token)
 	return token;
 }
 
-static struct token *skip_attributes(struct token *token)
+static struct token *c23_attribute_specifier(struct token *token,
+					     struct decl_state *ctx)
 {
-	while (match_attribute(token)) {
-		token = expect(token->next, '(', "after attribute");
-		token = expect(token, '(', "after attribute");
-		while (token_type(token) == TOKEN_IDENT) {
-			token = skip_attribute(token);
-			if (!match_op(token, ','))
+	if (standard < STANDARD_C23 && Wpedantic)
+		warning(token->pos, "C23 attribute syntax is a C23 extension");
+
+	token = expect(token, '[', "in attribute specifier");
+	token = expect(token, '[', "in attribute specifier");
+
+	while (!match_op(token, ']') && !eof_token(token)) {
+		struct ident *prefix = NULL;
+		struct ident *attr_ident = NULL;
+		struct token *id_tok = NULL;
+
+		if (match_op(token, ',')) {
+			token = token->next;
+			continue;
+		}
+
+		if (token_type(token) != TOKEN_IDENT) {
+			sparse_error(token->pos, "expected attribute name");
+			break;
+		}
+
+		id_tok = token;
+		attr_ident = token->ident;
+		token = token->next;
+
+		if (match_op(token, ':') && match_op(token->next, ':')) {
+			prefix = attr_ident;
+			token = token->next->next;
+			if (token_type(token) != TOKEN_IDENT) {
+				sparse_error(
+					token->pos,
+					"expected attribute name after '::'");
 				break;
+			}
+			id_tok = token;
+			attr_ident = token->ident;
 			token = token->next;
 		}
-		token = expect(token, ')', "after attribute");
-		token = expect(token, ')', "after attribute");
+
+		if (!prefix || prefix == &gnu_ident ||
+		    prefix == &__gnu___ident) {
+			struct symbol *attr =
+				lookup_keyword(attr_ident, NS_KEYWORD);
+			if (attr && attr->op && attr->op->attribute)
+				token = attr->op->attribute(token, attr, ctx);
+			else {
+				if (Wunknown_attribute)
+					warning(id_tok->pos,
+						"unknown attribute '%s'",
+						show_ident(attr_ident));
+				token = skip_parens(token);
+			}
+		} else {
+			/* Other vendor prefixes (e.g. clang::...): ignore */
+			token = skip_parens(token);
+		}
+
+		if (match_op(token, ','))
+			token = token->next;
+		else if (match_op(token, ']'))
+			break;
+		else {
+			sparse_error(token->pos,
+				     "expected ']' or ',' after attribute");
+			break;
+		}
+	}
+
+	token = expect(token, ']', "at end of attribute specifier");
+	token = expect(token, ']', "at end of attribute specifier");
+	return token;
+}
+
+static struct token *skip_attributes(struct token *token)
+{
+	for (;;) {
+		if (match_attribute(token)) {
+			token = expect(token->next, '(', "after attribute");
+			token = expect(token, '(', "after attribute");
+			while (token_type(token) == TOKEN_IDENT) {
+				token = skip_attribute(token);
+				if (!match_op(token, ','))
+					break;
+				token = token->next;
+			}
+			token = expect(token, ')', "after attribute");
+			token = expect(token, ')', "after attribute");
+		} else if (match_c23_attribute(token)) {
+			token = skip_c23_attributes(token);
+		} else {
+			break;
+		}
 	}
 	return token;
 }
 
 static struct token *handle_attributes(struct token *token, struct decl_state *ctx)
 {
-	while (match_attribute(token))
-		token = attribute_specifier(token->next, NULL, ctx);
+	while (match_attribute(token) || match_c23_attribute(token)) {
+		if (match_attribute(token))
+			token = attribute_specifier(token->next, NULL, ctx);
+		else
+			token = c23_attribute_specifier(token, ctx);
+	}
 	return token;
 }
 
@@ -2115,10 +2235,10 @@ static enum kind which_func(struct token *token,
 			    struct ident **n,
 			    int prefer_abstract)
 {
-	struct token *next = token->next;
+	struct token *next = skip_attributes(token->next);
 
 	if (token_type(next) == TOKEN_IDENT) {
-		if (lookup_type(next))
+		if (lookup_type(next) || next != token->next)
 			return Proto;
 		/* identifier list not in definition; complain */
 		if (prefer_abstract)
@@ -2160,6 +2280,24 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 	if (ctx->ident && token_type(token) == TOKEN_IDENT) {
 		*ctx->ident = token->ident;
 		token = token->next;
+		if (match_c23_attribute(token)) {
+			struct decl_state attr = { };
+			token = handle_attributes(token, &attr);
+			ctx->ident_modifiers |= (attr.ctype.modifiers & MOD_DECLARE) |
+						attr.f_modifiers;
+			if (attr.cleanup)
+				ctx->cleanup = attr.cleanup;
+			if (attr.packed)
+				ctx->packed = attr.packed;
+			if (attr.transparent_union)
+				ctx->transparent_union = attr.transparent_union;
+			if (attr.designated_init)
+				ctx->designated_init = attr.designated_init;
+			if (attr.ctype.alignment > ctx->ctype.alignment)
+				ctx->ctype.alignment = attr.ctype.alignment;
+			attr.ctype.modifiers &= ~MOD_DECLARE;
+			apply_ctype(token->pos, &ctx->ctype, &attr.ctype);
+		}
 	} else if (match_op(token, '(') &&
 	    is_nested(token, &next, ctx->prefer_abstract)) {
 		struct symbol *base_type = ctype->base_type;
@@ -2191,16 +2329,24 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 			token = parameter_type_list(token, fn);
 		token = expect(token, ')', "in function declarator");
 		fn->endpos = token->pos;
+		if (match_c23_attribute(token)) {
+			struct decl_state attr = { };
+			token = handle_attributes(token, &attr);
+			ctype->modifiers |= attr.f_modifiers;
+			apply_ctype(token->pos, ctype, &attr.ctype);
+		}
 		return token;
 	}
 
-	while (match_op(token, '[')) {
+	while (match_op(token, '[') && !match_c23_attribute(token)) {
 		struct symbol *array;
 		array = alloc_indirect_symbol(token->pos, ctype, SYM_ARRAY);
 		token = abstract_array_declarator(token->next, array);
 		token = expect(token, ']', "in abstract_array_declarator");
 		array->endpos = token->pos;
 		ctype = &array->ctype;
+		if (match_c23_attribute(token))
+			token = handle_attributes(token, ctx);
 	}
 	return token;
 }
@@ -2295,6 +2441,7 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 	for (;;) {
 		struct symbol *decl = alloc_symbol(token->pos, SYM_NODE);
 		ctx.cleanup = NULL;
+		ctx.ident_modifiers = 0;
 		ctx.ident = &decl->ident;
 
 		token = declarator(token, &ctx);
@@ -2302,6 +2449,7 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 			token = handle_bitfield(token, &ctx);
 
 		token = handle_attributes(token, &ctx);
+		ctx.ctype.modifiers |= ctx.ident_modifiers;
 		apply_modifiers(token->pos, &ctx);
 
 		decl->ctype = ctx.ctype;
@@ -2366,6 +2514,7 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	ctx.ident = &sym->ident;
 	token = declarator(token, &ctx);
 	token = handle_attributes(token, &ctx);
+	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
 	sym->ctype.modifiers |= decl_modifiers(&ctx);
@@ -2412,6 +2561,8 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 		ctx.ctype.base_type = &bad_ctype;
 	}
 	token = declarator(token, &ctx);
+	token = handle_attributes(token, &ctx);
+	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
 	sym->endpos = token->pos;
@@ -2700,7 +2851,10 @@ static struct token *parse_for_statement(struct token *token, struct statement *
 	syms = NULL;
 	e1 = NULL;
 	/* C99 variable declaration? */
-	if (lookup_type(token)) {
+	struct token *next = token;
+	if (match_c23_attribute(token))
+		next = skip_c23_attributes(token);
+	if (lookup_type(next)) {
 		token = external_declaration(token, &syms, validate_for_loop_decl);
 	} else {
 		token = parse_expression(token, &e1);
@@ -2873,13 +3027,13 @@ static struct token *handle_label_attributes(struct token *token, struct symbol
 	struct decl_state ctx = { };
 
 	token = handle_attributes(token, &ctx);
-	label->label_modifiers = ctx.ctype.modifiers;
+	label->label_modifiers |= ctx.ctype.modifiers;
 	return token;
 }
 
 static bool is_label_token(struct token *token)
 {
-	struct token *p = token;
+	struct token *p = skip_c23_attributes(token);
 	struct symbol *s;
 
 	if (token_type(p) != TOKEN_IDENT)
@@ -2903,6 +3057,8 @@ static struct token *parse_one_label(struct token *token,
 
 	*stmtp = NULL;
 	*is_case = false;
+	if (match_c23_attribute(token))
+		token = handle_attributes(token, &attr_state);
 
 	if (token->ident == &case_ident) {
 		stmt = alloc_statement(token->pos, STMT_CASE);
@@ -2950,7 +3106,7 @@ static struct token *parse_one_label(struct token *token,
 
 static struct token *statement(struct token *token, struct statement **tree)
 {
-	struct statement *stmt = alloc_statement(token->pos, STMT_NONE);
+	struct statement *stmt;
 
 	if (is_label_token(token)) {
 		struct statement *lbl = NULL;
@@ -2971,6 +3127,27 @@ static struct token *statement(struct token *token, struct statement **tree)
 		return statement(token, tree);
 	}
 
+	if (match_c23_attribute(token)) {
+		struct token *next = skip_c23_attributes(token);
+		if (lookup_type(next)) {
+			sparse_error(token->pos, "declaration is not a statement");
+			stmt = alloc_statement(token->pos, STMT_DECLARATION);
+			*tree = stmt;
+			start_block_scope();
+			token = external_declaration(token, &stmt->declaration, NULL);
+			end_block_scope();
+			return token;
+		}
+		struct decl_state attr_state = {};
+		token = handle_attributes(token, &attr_state);
+		if (match_op(token, ';')) {
+			stmt = alloc_statement(token->pos, STMT_NONE);
+			*tree = stmt;
+			return token->next;
+		}
+		return statement(token, tree);
+	}
+	stmt = alloc_statement(token->pos, STMT_NONE);
 	*tree = stmt;
 	if (token_type(token) == TOKEN_IDENT) {
 		struct symbol *s = lookup_keyword(token->ident, NS_KEYWORD);
@@ -3031,6 +3208,7 @@ static struct token * statement_list(struct token *token, struct statement_list
 		struct statement *label_head = NULL;
 		struct statement **label_tail = &label_head;
 		bool last_is_case = false;
+		struct token *next;
 
 		while (is_label_token(token)) {
 			struct statement *lbl = NULL;
@@ -3054,10 +3232,13 @@ static struct token * statement_list(struct token *token, struct statement_list
 				add_statement(list, label_head);
 				break;
 			}
+			next = match_c23_attribute(token) ?
+				       skip_c23_attributes(token) :
+				       token;
 			if (match_ident(token, &_Static_assert_ident) ||
 			    (standard >= STANDARD_C23 &&
 			     match_ident(token, &static_assert_ident)) ||
-			    lookup_type(token)) {
+			    lookup_type(next)) {
 				if (standard < STANDARD_C23 && Wpedantic)
 					warning(token->pos,
 						"a label can only be part of a statement and a declaration is not a statement");
@@ -3081,7 +3262,10 @@ static struct token * statement_list(struct token *token, struct statement_list
 			token = parse_static_assert(token, NULL);
 			continue;
 		}
-		if (lookup_type(token)) {
+		next = token;
+		if (match_c23_attribute(token))
+			next = skip_c23_attributes(token);
+		if (lookup_type(next)) {
 			if (seen_statement) {
 				warning(token->pos, "mixing declarations and code");
 				seen_statement = 0;
@@ -3457,6 +3641,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	struct symbol *decl;
 	struct decl_state ctx = { .ident = &ident };
 	struct ctype saved;
+	unsigned long saved_f_modifiers;
 	struct symbol *base_type;
 	unsigned long mod;
 	int is_typedef;
@@ -3495,9 +3680,11 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			     "'enum' underlying type may not be specified here");
 
 	saved = ctx.ctype;
+	saved_f_modifiers = ctx.f_modifiers;
 	token = declarator(token, &ctx);
 	token = handle_asm_name(token, &ctx);
 	token = handle_attributes(token, &ctx);
+	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 
 	decl->ctype = ctx.ctype;
@@ -3753,10 +3940,13 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		decl = alloc_symbol(token->pos, SYM_NODE);
 		ctx.ctype = saved;
 		ctx.cleanup = NULL;
+		ctx.ident_modifiers = 0;
+		ctx.f_modifiers = saved_f_modifiers;
 		token = handle_attributes(token, &ctx);
 		token = declarator(token, &ctx);
 		token = handle_asm_name(token, &ctx);
 		token = handle_attributes(token, &ctx);
+		ctx.ctype.modifiers |= ctx.ident_modifiers;
 		apply_modifiers(token->pos, &ctx);
 		decl->ctype = ctx.ctype;
 		decl->ctype.modifiers |= mod;
@@ -3781,6 +3971,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		/* Function declarations are automatically extern unless specifically static */
 		base_type = decl->ctype.base_type;
 		if (!is_typedef && base_type && base_type->type == SYM_FN) {
+			decl->ctype.modifiers |= ctx.f_modifiers;
 			if (!(decl->ctype.modifiers & MOD_STATIC))
 				decl->ctype.modifiers |= MOD_EXTERN;
 		}
diff --git a/symbol.h b/symbol.h
index 8035d9d21b3c..a072ac8fc739 100644
--- a/symbol.h
+++ b/symbol.h
@@ -110,6 +110,7 @@ struct decl_state {
 	struct symbol_op *mode;
 	struct expression *cleanup;
 	unsigned long f_modifiers;		// function attributes
+	unsigned long ident_modifiers;		// declarator-id C23 attributes
 	unsigned long storage_class;
 	unsigned char prefer_abstract;
 	unsigned char autotype;
@@ -272,11 +273,11 @@ struct symbol {
 #define MOD_SPECIFIER	MOD_SIGNEDNESS
 #define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE | MOD_CONSTEXPR)
 #define MOD_QUALIFIER	(MOD_CONST | MOD_VOLATILE | MOD_RESTRICT)
-#define MOD_PTRINHERIT	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NODEREF | MOD_NORETURN | MOD_NOCAST)
-/* modifiers preserved by typeof() operator */
-#define MOD_TYPEOF	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NOCAST | MOD_SPECIFIER)
 /* modifiers for function attributes */
 #define MOD_FUN_ATTR	(MOD_PURE|MOD_NORETURN)
+#define MOD_PTRINHERIT	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NODEREF | MOD_FUN_ATTR | MOD_NOCAST)
+/* modifiers preserved by typeof() operator */
+#define MOD_TYPEOF	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NOCAST | MOD_SPECIFIER)
 /* like cvr-qualifiers but 'reversed' (OK: source <= target) */
 #define MOD_REV_QUAL	(MOD_PURE|MOD_NORETURN)
 /* do not warn when these are duplicated */
diff --git a/validation/attr_in_parameter.c b/validation/attr_in_parameter.c
index 1b104ea43ec5..33d8b85aa5cb 100644
--- a/validation/attr_in_parameter.c
+++ b/validation/attr_in_parameter.c
@@ -7,6 +7,8 @@ static void g(int A *x)
 	f(x, x);
 	p = q;
 }
+static void h(__attribute__((unused)) int x);
+
 /*
  * check-name: attribute after ( in direct-declarator
  */
diff --git a/validation/c23-attributes-error.c b/validation/c23-attributes-error.c
new file mode 100644
index 000000000000..a78086311c1c
--- /dev/null
+++ b/validation/c23-attributes-error.c
@@ -0,0 +1,20 @@
+[[nodiscard] static int unclosed;
+[[gnu::]] static int missing_scoped;
+
+static void test_if_attr_decl(int c)
+{
+	if (c)
+		[[maybe_unused]] int z = 3;
+}
+
+/*
+ * check-name: c23-attributes-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-attributes-error.c:1:14: error: Expected ] at end of attribute specifier
+c23-attributes-error.c:1:14: error: got static
+c23-attributes-error.c:2:8: error: expected attribute name after '::'
+c23-attributes-error.c:7:17: error: declaration is not a statement
+ * check-error-end
+ */
diff --git a/validation/c23-attributes-pedantic.c b/validation/c23-attributes-pedantic.c
new file mode 100644
index 000000000000..224f36f5bdf3
--- /dev/null
+++ b/validation/c23-attributes-pedantic.c
@@ -0,0 +1,10 @@
+[[maybe_unused]] static int x;
+
+/*
+ * check-name: C23 [[]] standard attribute syntax pedantic warning
+ * check-command: sparse -std=c11 -Wpedantic $file
+ *
+ * check-error-start
+c23-attributes-pedantic.c:1:1: warning: C23 attribute syntax is a C23 extension
+ * check-error-end
+ */
diff --git a/validation/c23-attributes.c b/validation/c23-attributes.c
new file mode 100644
index 000000000000..8b53ce06d0cf
--- /dev/null
+++ b/validation/c23-attributes.c
@@ -0,0 +1,149 @@
+[[maybe_unused]] static int unused_var;
+
+[[nodiscard]] static int get_val(void)
+{
+	return 42;
+}
+
+[[deprecated("use new_func instead")]] static void old_func(void)
+{
+}
+
+[[noreturn]] static void fatal(void)
+{
+	for (;;) {
+	}
+}
+
+[[_Noreturn]] static void fatal2(void)
+{
+	for (;;) {
+	}
+}
+
+static int pure_add(int a, int b) [[unsequenced]]
+{
+	return a + b;
+}
+
+static int pure_mul(int a, int b) [[reproducible]]
+{
+	return a * b;
+}
+
+static int (*fp_unseq)(int, int) [[unsequenced]] = pure_add;
+typedef int (*plain_int_fp_t)(int);
+typedef int (__attribute__((__pure__)) *repro_int_fp_t)(int);
+static int plain_inc(int a)
+{
+	return a + 1;
+}
+static int repro_inc(int a) [[reproducible]]
+{
+	return a + 1;
+}
+static int (*get_plain_fp(void) [[reproducible]])(int)
+{
+	return plain_inc;
+}
+static int (*get_repro_fp(void))(int) [[reproducible]]
+{
+	return repro_inc;
+}
+static_assert([typeof(&get_plain_fp)] ==
+	      [plain_int_fp_t (__attribute__((__pure__)) *)(void)],
+	      "inner [[]] applies to outer function");
+static_assert([typeof(get_plain_fp())] == [plain_int_fp_t],
+	      "inner [[]] does not leak to returned function pointer");
+static_assert([typeof(&get_repro_fp)] == [repro_int_fp_t (*)(void)],
+	      "outer [[]] does not leak to outer function");
+static_assert([typeof(get_repro_fp())] == [repro_int_fp_t],
+	      "outer [[]] applies to returned function pointer");
+
+struct [[deprecated]] S {
+	[[maybe_unused]] int field;
+};
+
+enum [[deprecated]] E {
+	ITEM1 [[deprecated]],
+	ITEM2,
+};
+
+typedef int T_attr_label;
+
+static void param_fn([[maybe_unused]] int a, int b [[maybe_unused]],
+		     [[maybe_unused]] int)
+{
+	(void)a;
+	(void)b;
+}
+
+struct [[gnu::packed]] PackedStruct {
+	char c;
+	int i;
+};
+static_assert(sizeof(struct PackedStruct) == sizeof(char) + sizeof(int),
+	      "packed");
+
+struct [[gnu::aligned(16)]] AlignedStruct {
+	int x;
+};
+static_assert(alignof(struct AlignedStruct) == 16, "align");
+
+[[gnu::aligned(16)]] static int aligned_var;
+
+[[maybe_unused]] [[nodiscard]] static int multi_attr(void)
+{
+	return 0;
+}
+
+[[maybe_unused, nodiscard]] static int comma_attr(void)
+{
+	return 0;
+}
+
+[[]] static int empty_attr;
+[[
+	,
+]] static int empty_attr2;
+
+static int test_statements(int val)
+{
+	[[maybe_unused]] int x = val;
+	if (x < 0)
+		goto lbl1;
+	switch (x) {
+	case 1:
+		x++;
+		[[fallthrough]];
+	case 2:
+		return x;
+	[[maybe_unused]] case 3:
+		return 3;
+	[[maybe_unused]] default:
+		break;
+	}
+
+lbl1:
+	[[]];
+[[maybe_unused]] lbl2:
+	[[maybe_unused]] int y = 10;
+	if (!y)
+		goto T_attr_label;
+[[maybe_unused]] T_attr_label:
+	return y;
+}
+
+[[clang::annotate_type("ptr_attr", void *)]] static int *vendor_attr_var;
+[[vendor::custom(struct foo, (1 + 2))]] static int vendor_attr_var2;
+
+static void fn_after_ident [[deprecated]] (void)
+{
+}
+
+static int arr_after_ident [[maybe_unused]] [4];
+
+/*
+ * check-name: C23 [[]] standard attribute syntax
+ * check-command: sparse -std=c23 $file
+ */
diff --git a/validation/c23-param-attribute.c b/validation/c23-param-attribute.c
new file mode 100644
index 000000000000..a89c025ed002
--- /dev/null
+++ b/validation/c23-param-attribute.c
@@ -0,0 +1,28 @@
+/*
+ * Check that function declarators properly parse parameter lists
+ * where parameters have standard C23 attributes.
+ */
+
+static void f1([[maybe_unused]] int x)
+{
+}
+
+static int f2([[maybe_unused]] int a, [[maybe_unused]] int b)
+{
+	return a;
+}
+
+static void (*fp)([[maybe_unused]] int);
+
+static void test_call(void)
+{
+	f1(10);
+	f2(1, 2);
+	fp = f1;
+	fp(20);
+}
+
+/*
+ * check-name: C23 parameter attributes
+ * check-command: sparse -std=c23 $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 53/73] preprocessor: Add support for __has_c_attribute
  2026-11-09 20:31 Bart Van Assche
                   ` (51 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
                   ` (19 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support the C23 __has_c_attribute preprocessor operator for testing
support for standard C23 and vendor attributes so that preprocessor
conditionals can probe whether specific attributes are supported before
using them.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 pre-process.c                                 | 66 +++++++++++++++++++
 validation/c23-attributes.c                   |  3 +
 validation/preprocessor/c23-has-c-attribute.c | 60 +++++++++++++++++
 3 files changed, 129 insertions(+)
 create mode 100644 validation/preprocessor/c23-has-c-attribute.c

diff --git a/pre-process.c b/pre-process.c
index a733927c4c14..aac194cf4c01 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -2364,6 +2364,71 @@ static int handle_nondirective(struct stream *stream, struct token **line, struc
 	return 1;
 }
 
+static bool expand_has_c_attribute(struct token *token, struct arg *args)
+{
+	struct token *arg = args[0].expanded;
+	const char *name;
+	const char *val = "0";
+	char buf[4096];
+
+	if (token_type(arg) != TOKEN_IDENT) {
+		sparse_error(eof_token(arg) ? token->pos : arg->pos,
+			     "identifier expected");
+		return false;
+	}
+
+	if (match_op(arg->next, ':') && match_op(arg->next->next, ':')) {
+		struct token *attr = arg->next->next->next;
+		if (token_type(attr) != TOKEN_IDENT || !eof_token(attr->next)) {
+			sparse_error(eof_token(attr) ? token->pos : attr->pos,
+				     "identifier expected");
+			return false;
+		}
+		if (arg->ident == &gnu_ident || arg->ident == &__gnu___ident) {
+			struct symbol *sym = lookup_symbol(attr->ident, NS_KEYWORD);
+			if (sym && sym->op && sym->op->attribute)
+				val = "1";
+		}
+		token_type(token) = TOKEN_NUMBER;
+		token->number = val;
+		return true;
+	}
+
+	if (!eof_token(arg->next)) {
+		sparse_error(arg->next->pos, "expected ')'");
+		return false;
+	}
+
+	name = show_ident(arg->ident);
+	if (name[0] == '_' && name[1] == '_') {
+		size_t len = strlen(name);
+		if (len > 4 && name[len - 1] == '_' && name[len - 2] == '_') {
+			if (len - 4 < sizeof(buf)) {
+				memcpy(buf, name + 2, len - 4);
+				buf[len - 4] = '\0';
+				name = buf;
+			}
+		}
+	}
+
+	if (!strcmp(name, "deprecated"))
+		val = "201904L";
+	else if (!strcmp(name, "fallthrough"))
+		val = "201910L";
+	else if (!strcmp(name, "nodiscard"))
+		val = "202003L";
+	else if (!strcmp(name, "maybe_unused"))
+		val = "202106L";
+	else if (!strcmp(name, "noreturn") || !strcmp(name, "_Noreturn"))
+		val = "202202L";
+	else if (!strcmp(name, "reproducible") || !strcmp(name, "unsequenced"))
+		val = "202207L";
+
+	token_type(token) = TOKEN_NUMBER;
+	token->number = val;
+	return true;
+}
+
 static bool expand_has_attribute(struct token *token, struct arg *args)
 {
 	struct token *arg = args[0].expanded;
@@ -2528,6 +2593,7 @@ static void init_preprocessor(void)
 		{ "__INCLUDE_LEVEL__", expand_include_level },
 		{ "__has_attribute", NULL, expand_has_attribute },
 		{ "__has_builtin", NULL, expand_has_builtin },
+		{ "__has_c_attribute", NULL, expand_has_c_attribute },
 		{ "__has_extension", NULL, expand_has_extension },
 		{ "__has_feature", NULL, expand_has_feature },
 		{ "__has_include", NULL, NULL },
diff --git a/validation/c23-attributes.c b/validation/c23-attributes.c
index 8b53ce06d0cf..d288574dec1c 100644
--- a/validation/c23-attributes.c
+++ b/validation/c23-attributes.c
@@ -1,3 +1,6 @@
+static_assert(__has_c_attribute(maybe_unused) != 0);
+static_assert(__has_c_attribute(nodiscard) != 0);
+
 [[maybe_unused]] static int unused_var;
 
 [[nodiscard]] static int get_val(void)
diff --git a/validation/preprocessor/c23-has-c-attribute.c b/validation/preprocessor/c23-has-c-attribute.c
new file mode 100644
index 000000000000..583fe98d0b9e
--- /dev/null
+++ b/validation/preprocessor/c23-has-c-attribute.c
@@ -0,0 +1,60 @@
+#if defined(__has_c_attribute)
+int has_macro = 1;
+#endif
+
+#if __has_c_attribute(nodiscard) == 202003L
+int has_nodiscard = __has_c_attribute(nodiscard);
+#endif
+
+#if __has_c_attribute(__maybe_unused__) == 202106L
+int has_maybe_unused = __has_c_attribute(__maybe_unused__);
+#endif
+
+#if __has_c_attribute(deprecated) == 201904L
+int has_deprecated = __has_c_attribute(deprecated);
+#endif
+
+#if __has_c_attribute(fallthrough) == 201910L
+int has_fallthrough = __has_c_attribute(fallthrough);
+#endif
+
+#if __has_c_attribute(noreturn) == 202202L && \
+    __has_c_attribute(_Noreturn) == 202202L
+int has_noreturn = __has_c_attribute(noreturn);
+#endif
+
+#if __has_c_attribute(reproducible) == 202207L && \
+    __has_c_attribute(unsequenced) == 202207L
+long has_reproducible = __has_c_attribute(reproducible);
+#endif
+
+#if !__has_c_attribute(unknown_attr_12345)
+int no_unknown = 1;
+#endif
+
+#if __has_c_attribute(gnu::aligned)
+int has_gnu_aligned = __has_c_attribute(gnu::aligned);
+#endif
+
+#if !__has_c_attribute(nodiscard::invalid)
+int no_scoped_std = 1;
+#endif
+
+/*
+ * check-name: c23-has-c-attribute
+ * check-command: sparse -std=c23 -E $file
+ *
+ * check-output-start
+
+int has_macro = 1;
+int has_nodiscard = 202003L;
+int has_maybe_unused = 202106L;
+int has_deprecated = 201904L;
+int has_fallthrough = 201910L;
+int has_noreturn = 202202L;
+long has_reproducible = 202207L;
+int no_unknown = 1;
+int has_gnu_aligned = 1;
+int no_scoped_std = 1;
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 54/73] c23: Support empty initializer braces {}
  2026-11-09 20:31 Bart Van Assche
                   ` (52 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
                   ` (18 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support empty initializer braces '{}' for scalar, pointer, nullptr_t,
aggregate, union, and variable-length array objects and compound literals
in C23 mode per ISO C23 6.7.11:
- Synthesize zero/null scalar initializers in handle_scalar() without
  emitting spurious nested scalar-brace warnings, and keep warning under
  -Wpedantic before C23.
- Preserve runtime bounds on VLA empty initializers ('int vla[n] = {}')
  and reject empty initializers for incomplete arrays ('int a[] = {}')
  and incomplete structs or unions.
- Lower empty aggregate and null-pointer static initializers cleanly in
  linearize and sparse-llvm.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                           |  23 +++++-
 linearize.c                          |   2 +-
 parse.c                              |   5 ++
 sparse-llvm.c                        | 114 +++++++++++++++++++--------
 symbol.c                             |   6 ++
 validation/backend/c23-empty-init.c  |  24 ++++++
 validation/c23-auto-error.c          |   5 +-
 validation/c23-constexpr-error.c     |   2 +-
 validation/c23-empty-init-error.c    |  19 +++++
 validation/c23-empty-init-pedantic.c |  14 ++++
 validation/c23-empty-init.c          |  64 +++++++++++++++
 11 files changed, 239 insertions(+), 39 deletions(-)
 create mode 100644 validation/backend/c23-empty-init.c
 create mode 100644 validation/c23-empty-init-error.c
 create mode 100644 validation/c23-empty-init-pedantic.c
 create mode 100644 validation/c23-empty-init.c

diff --git a/evaluate.c b/evaluate.c
index 43ad5f59b860..f40e35773639 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2987,7 +2987,8 @@ static int is_string_literal(struct expression **v)
  * an effect of screwup).  In case of anything not legal, we are definitely
  * having an effect of screwup, so just fail and let the caller warn.
  */
-static struct expression *handle_scalar(struct expression *e, int nested)
+static struct expression *handle_scalar(struct expression *e, int nested,
+					int class, struct symbol *ctype)
 {
 	struct expression *v = NULL, *p;
 	int count = 0;
@@ -3001,6 +3002,19 @@ static struct expression *handle_scalar(struct expression *e, int nested)
 			v = p;
 		count++;
 	} END_FOR_EACH_PTR(p);
+	if (count == 0) {
+		if (standard >= STANDARD_C23) {
+			v = alloc_const_expression(e->pos, 0);
+			if (class & TYPE_PTR)
+				v->ctype = &null_ctype;
+			else if (is_nullptr_type(ctype)) {
+				v->ctype = &nullptr_ctype;
+				v->flags = CEF_ADDR;
+			}
+			return v;
+		}
+		return NULL;
+	}
 	if (count != 1)
 		return NULL;
 	switch(v->type) {
@@ -3202,7 +3216,7 @@ static int handle_initializer(struct expression **ep, int nested,
 	if (!(class & TYPE_COMPOUND)) {
 		struct expression orig;
 
-		e = handle_scalar(e, nested);
+		e = handle_scalar(e, nested, class, ctype);
 
 		if (!e)
 			return 0;
@@ -3302,6 +3316,11 @@ static void evaluate_initializer(struct symbol *ctype, struct expression **ep)
 {
 	struct symbol *type;
 	int class = classify_type(ctype, &type);
+	if ((type->type == SYM_STRUCT || type->type == SYM_UNION) &&
+	    (type->bit_size < 0 || !type->endpos.line)) {
+		expression_error(*ep, "initializer for incomplete type");
+		return;
+	}
 	if (!handle_initializer(ep, 0, class, ctype, 0))
 		expression_error(*ep, "invalid initializer");
 }
diff --git a/linearize.c b/linearize.c
index 4179aea03af1..268466f07886 100644
--- a/linearize.c
+++ b/linearize.c
@@ -2003,7 +2003,7 @@ static pseudo_t linearize_one_symbol(struct entrypoint *ep, struct symbol *sym)
 	sym->initialized = 1;
 	ad.address = symbol_pseudo(ep, sym);
 
-	if (sym->initializer && !is_scalar_type(sym)) {
+	if (sym->initializer && !is_scalar_type(sym) && sym->bit_size > 0) {
 		// default zero initialization [6.7.9.21]
 		// FIXME: this init the whole aggregate while
 		// only the existing fields need to be initialized.
diff --git a/parse.c b/parse.c
index 13b8cf5d43c1..f49b3f188c69 100644
--- a/parse.c
+++ b/parse.c
@@ -3456,6 +3456,11 @@ struct token *initializer(struct expression **tree, struct token *token)
 		}
 
 		token = initializer_list(&expr->expr_list, token->next);
+		if (ptr_list_empty((struct ptr_list *)expr->expr_list)) {
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(expr->pos,
+					"ISO C forbids empty initializer braces before C23");
+		}
 		return expect(token, '}', "at end of initializer");
 	}
 	return assignment_expression(token, tree);
diff --git a/sparse-llvm.c b/sparse-llvm.c
index c7055238ff88..f3f6b7e01171 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -309,6 +309,8 @@ static const char *pseudo_name(pseudo_t pseudo, char *buf)
 	return buf;
 }
 
+static LLVMValueRef output_data(LLVMModuleRef module, struct symbol *sym);
+
 static LLVMValueRef get_sym_value(LLVMModuleRef module, struct symbol *sym)
 {
 	const char *name = show_ident(sym->ident);
@@ -1250,6 +1252,83 @@ static void output_fn(LLVMModuleRef module, struct entrypoint *ep)
 	LLVMDisposeBuilder(function.builder);
 }
 
+static LLVMValueRef expr_to_const_val(LLVMModuleRef module, struct symbol *sym,
+				      struct expression *initializer)
+{
+	LLVMTypeRef dtype = symbol_type(sym);
+
+	if (!initializer)
+		return LLVMConstNull(dtype);
+
+	switch (initializer->type) {
+	case EXPR_VALUE:
+		return constant_value(initializer->value, dtype);
+	case EXPR_FVALUE:
+		return LLVMConstReal(dtype, initializer->fvalue);
+	case EXPR_SYMBOL: {
+		struct symbol *isym = initializer->symbol;
+
+		if (!isym->ident) {
+			if (!isym->aux) {
+				expand_symbol(isym);
+				isym->aux = output_data(module, isym);
+			}
+			return isym->aux;
+		}
+		LLVMValueRef gv = LLVMGetNamedGlobal(module, show_ident(isym->ident));
+		if (!gv)
+			gv = output_data(module, isym);
+		return gv;
+	}
+	case EXPR_STRING: {
+		const char *s = initializer->string->data;
+		size_t len = initializer->string->length;
+
+		return const_string(s, len);
+	}
+	case EXPR_INITIALIZER: {
+		if (ptr_list_empty((struct ptr_list *)initializer->expr_list))
+			return LLVMConstNull(dtype);
+		if (is_array_type(sym)) {
+			struct symbol *arr = sym->type == SYM_NODE ? sym->ctype.base_type : sym;
+			struct symbol *elem_sym = arr->ctype.base_type;
+			LLVMTypeRef elem_type = symbol_type(elem_sym);
+			unsigned int elem_bytes = bits_to_bytes(elem_sym->bit_size);
+			unsigned int nr = elem_bytes ? bits_to_bytes(sym->bit_size) / elem_bytes : 0;
+			struct expression *entry;
+			LLVMValueRef *vals;
+			LLVMValueRef res;
+			unsigned int i;
+
+			if (!nr)
+				return LLVMConstNull(dtype);
+			vals = calloc(nr, sizeof(*vals));
+			for (i = 0; i < nr; i++)
+				vals[i] = LLVMConstNull(elem_type);
+			FOR_EACH_PTR(initializer->expr_list, entry) {
+				if (entry->type == EXPR_POS) {
+					unsigned int idx = entry->init_offset / elem_bytes;
+					if (idx < nr) {
+						LLVMValueRef v = expr_to_const_val(module, elem_sym,
+										   entry->init_expr);
+						if (v)
+							vals[idx] = v;
+					}
+				}
+			} END_FOR_EACH_PTR(entry);
+			res = LLVMConstArray(elem_type, vals, nr);
+			free(vals);
+			return res;
+		}
+		warning(initializer->pos, "can't initialize type: %s", show_typename(sym));
+		return NULL;
+	}
+	default:
+		warning(initializer->pos, "can't initialize type: %s", show_typename(sym));
+		return NULL;
+	}
+}
+
 static LLVMValueRef output_data(LLVMModuleRef module, struct symbol *sym)
 {
 	struct expression *initializer = sym->initializer;
@@ -1257,40 +1336,7 @@ static LLVMValueRef output_data(LLVMModuleRef module, struct symbol *sym)
 	LLVMValueRef data;
 	const char *name;
 
-	if (initializer) {
-		switch (initializer->type) {
-		case EXPR_VALUE:
-			initial_value = constant_value(initializer->value, symbol_type(sym));
-			break;
-		case EXPR_FVALUE:
-			initial_value = LLVMConstReal(symbol_type(sym), initializer->fvalue);
-			break;
-		case EXPR_SYMBOL: {
-			struct symbol *sym = initializer->symbol;
-
-			initial_value = LLVMGetNamedGlobal(module, show_ident(sym->ident));
-			if (!initial_value)
-				initial_value = output_data(module, sym);
-			break;
-		}
-		case EXPR_STRING: {
-			const char *s = initializer->string->data;
-			size_t len = initializer->string->length;
-
-			initial_value = const_string(s, len);
-			break;
-		}
-		default:
-			warning(initializer->pos, "can't initialize type: %s", show_typename(sym));
-			initial_value = NULL;
-			break;
-		}
-	} else {
-		LLVMTypeRef type = symbol_type(sym);
-
-		initial_value = LLVMConstNull(type);
-	}
-
+	initial_value = expr_to_const_val(module, sym, initializer);
 	if (!initial_value)
 		return NULL;
 
diff --git a/symbol.c b/symbol.c
index 28fa12352569..270e91d9c480 100644
--- a/symbol.c
+++ b/symbol.c
@@ -569,6 +569,12 @@ static struct symbol * examine_node_type(struct symbol *sym)
 			struct symbol *node_type = base_type->ctype.base_type;
 			int count = count_array_initializer(node_type, initializer);
 
+			if (standard >= STANDARD_C23 &&
+			    initializer->type == EXPR_INITIALIZER &&
+			    ptr_list_empty((struct ptr_list *)initializer->expr_list))
+				sparse_error(sym->pos,
+					     "empty initializer for incomplete array");
+
 			if (node_type && node_type->bit_size >= 0)
 				bit_size = implicit_array_size(sym, count);
 		}
diff --git a/validation/backend/c23-empty-init.c b/validation/backend/c23-empty-init.c
new file mode 100644
index 000000000000..e8741bd5d79a
--- /dev/null
+++ b/validation/backend/c23-empty-init.c
@@ -0,0 +1,24 @@
+typedef typeof(nullptr) nullptr_t;
+
+struct s {
+	int a;
+	void *p;
+};
+
+static nullptr_t g_np = {};
+static void *g_ptr = {};
+static struct s g_s = {};
+
+int test_vla_empty(int n);
+int test_vla_empty(int n)
+{
+	int vla[n] = {};
+	return vla[0] + g_s.a + (g_np != nullptr) + (g_ptr != nullptr);
+}
+
+/*
+ * check-name: backend-c23-empty-init
+ * check-command: sparse-llvm-dis -m64 -std=c23 -Wno-vla $file
+ * check-output-ignore
+ * check-output-contains: define i32 @test_vla_empty(
+ */
diff --git a/validation/c23-auto-error.c b/validation/c23-auto-error.c
index b88ebd37d2d9..16d9c85beb40 100644
--- a/validation/c23-auto-error.c
+++ b/validation/c23-auto-error.c
@@ -28,10 +28,12 @@ void block_errors(void)
 	int bad_cast = (auto)1;
 	_Atomic(auto) bad_atomic = 1;
 	auto braced = { 1 };
+	auto empty = {};
 	(void)bad_sz;
 	(void)bad_cast;
 	(void)bad_atomic;
 	(void)braced;
+	(void)empty;
 }
 
 /*
@@ -56,6 +58,7 @@ c23-auto-error.c:29:16: error: Trying to use reserved word 'auto' as identifier
 c23-auto-error.c:29:23: error: Expected ; at end of declaration
 c23-auto-error.c:29:23: error: got bad_atomic
 c23-auto-error.c:30:14: error: auto requires a scalar initializer expression
+c23-auto-error.c:31:14: error: auto requires a scalar initializer expression
 c23-auto-error.c:2:15: error: invalid initializer
 c23-auto-error.c:7:13: error: underspecified symbol 'self' used in its own initializer
 c23-auto-error.c:9:18: error: underspecified symbol 'self_expr' used in its own initializer
@@ -63,6 +66,6 @@ c23-auto-error.c:10:23: error: underspecified symbol 'self_sz' used in its own i
 c23-auto-error.c:10:16: error: cannot size expression
 c23-auto-error.c:25:31: error: underspecified symbol 'shadow' used in its own initializer
 c23-auto-error.c:27:22: error: cannot size expression
-c23-auto-error.c:33:15: error: undefined identifier 'bad_atomic'
+c23-auto-error.c:34:15: error: undefined identifier 'bad_atomic'
  * check-error-end
  */
diff --git a/validation/c23-constexpr-error.c b/validation/c23-constexpr-error.c
index 41ee86d7ae63..370f2409c99a 100644
--- a/validation/c23-constexpr-error.c
+++ b/validation/c23-constexpr-error.c
@@ -50,7 +50,7 @@ void assign_and_vla_test(int n)
 	constexpr int arr[2] = { 1, 2 };
 	arr[0] = 3;
 
-	constexpr int (*vla)[n] = nullptr;
+	constexpr int vla[n] = {};
 	(void)vla;
 }
 
diff --git a/validation/c23-empty-init-error.c b/validation/c23-empty-init-error.c
new file mode 100644
index 000000000000..b5f39174e1e4
--- /dev/null
+++ b/validation/c23-empty-init-error.c
@@ -0,0 +1,19 @@
+static int inc_arr[] = {};
+
+struct inc_struct;
+
+static void bad_init(void)
+{
+	struct inc_struct x = {};
+	(void)x;
+}
+
+/*
+ * check-name: c23-empty-init-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-empty-init-error.c:1:12: error: empty initializer for incomplete array
+c23-empty-init-error.c:7:31: error: initializer for incomplete type
+ * check-error-end
+ */
diff --git a/validation/c23-empty-init-pedantic.c b/validation/c23-empty-init-pedantic.c
new file mode 100644
index 000000000000..19dfb769f16b
--- /dev/null
+++ b/validation/c23-empty-init-pedantic.c
@@ -0,0 +1,14 @@
+struct s {
+	int a;
+};
+
+static struct s s = {};
+
+/*
+ * check-name: c23 empty initializer pedantic warning
+ * check-command: sparse -std=c11 -Wpedantic $file
+ *
+ * check-error-start
+c23-empty-init-pedantic.c:5:21: warning: ISO C forbids empty initializer braces before C23
+ * check-error-end
+ */
diff --git a/validation/c23-empty-init.c b/validation/c23-empty-init.c
new file mode 100644
index 000000000000..8af4ddf86e42
--- /dev/null
+++ b/validation/c23-empty-init.c
@@ -0,0 +1,64 @@
+struct s {
+	int a;
+	void *ptr;
+};
+
+struct nested {
+	struct s inner;
+	int arr[2];
+};
+
+union u {
+	int a;
+	char *p;
+};
+
+enum e {
+	E0,
+	E1,
+};
+
+typedef typeof(nullptr) nullptr_t;
+
+static int i = {};
+static float f = {};
+static double d = {};
+static void *p = {};
+static bool b = {};
+static nullptr_t np = {};
+static enum e ge = {};
+static struct s s = {};
+static struct s s2 = { {}, {} };
+static struct nested ns = { {}, {} };
+static union u u = {};
+static int arr[3] = {};
+
+static constexpr int ci = {};
+static constexpr bool cb = {};
+static_assert(ci == 0, "ci");
+static_assert(cb == false, "cb");
+
+static int test(int n)
+{
+	int local_i = {};
+	void *local_p = {};
+	struct s local_s = {};
+	int local_arr[2] = {};
+	int vla[n] = {};
+	int cl_i = (int){};
+	struct s cl_s = (struct s){};
+	int *cl_arr = (int [3]){};
+
+	(void)local_p;
+	(void)local_s;
+	(void)cl_s;
+	return local_i + local_arr[0] + vla[0] + cl_i + cl_arr[0] +
+	       i + (int)f + (int)d + (p != nullptr) + b + (np != nullptr) +
+	       ge + s.a + s2.a + (s2.ptr != nullptr) +
+	       ns.inner.a + u.a + arr[0];
+}
+
+/*
+ * check-name: c23 empty initializer braces
+ * check-command: sparse -std=c23 -pedantic -Wno-vla $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 55/73] preprocessor: Add support for __has_embed
  2026-11-09 20:31 Bart Van Assche
                   ` (53 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
                   ` (17 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add support for the C23 __has_embed preprocessor operator to check resource
availability and parameters prior to inclusion via #embed. Predefine the
__STDC_EMBED_NOT_FOUND__, __STDC_EMBED_FOUND__, and __STDC_EMBED_EMPTY__
constants to allow portable feature detection in preprocessor expressions.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ident-list.h                            |   9 +
 pre-process.c                           | 445 +++++++++++++++++++++++-
 predefine.c                             |   3 +
 validation/preprocessor/c23-has-embed.c |  69 ++++
 4 files changed, 522 insertions(+), 4 deletions(-)
 create mode 100644 validation/preprocessor/c23-has-embed.c

diff --git a/ident-list.h b/ident-list.h
index bc5df947913a..f51f2048f5ca 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -84,6 +84,15 @@ __IDENT(__FUNCTION___ident, "__FUNCTION__", 0);
 __IDENT(__PRETTY_FUNCTION___ident, "__PRETTY_FUNCTION__", 0);
 __IDENT(__has_include_ident, "__has_include", 0);
 __IDENT(__has_include_next_ident, "__has_include_next", 0);
+__IDENT(__has_embed_ident, "__has_embed", 0);
+IDENT(limit);
+IDENT(prefix);
+IDENT(suffix);
+IDENT(if_empty);
+__IDENT(__limit___ident, "__limit__", 0);
+__IDENT(__prefix___ident, "__prefix__", 0);
+__IDENT(__suffix___ident, "__suffix__", 0);
+__IDENT(__if_empty___ident, "__if_empty__", 0);
 
 /* Sparse commands */
 IDENT_RESERVED(__context__);
diff --git a/pre-process.c b/pre-process.c
index aac194cf4c01..e8b3511c225e 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -764,6 +764,7 @@ static struct token **substitute(struct token **list, const struct token *body,
 }
 
 static bool parse_has_include(struct token **list, int how);
+static bool parse_has_embed(struct token **list);
 
 static void reject_outside_directive(struct token *token)
 {
@@ -805,7 +806,8 @@ static int expand(struct token **list, struct symbol *sym)
 	}
 
 	if (expanding == &__has_include_ident ||
-	    expanding == &__has_include_next_ident) {
+	    expanding == &__has_include_next_ident ||
+	    expanding == &__has_embed_ident) {
 		bool ok;
 
 		if (!in_cond_expr) {
@@ -814,8 +816,10 @@ static int expand(struct token **list, struct symbol *sym)
 		}
 		if (expanding == &__has_include_ident)
 			ok = parse_has_include(list, 0);
-		else
+		else if (expanding == &__has_include_next_ident)
 			ok = parse_has_include(list, 1);
+		else
+			ok = parse_has_embed(list);
 		if (!ok)
 			replace_with_bool(token, 0);
 		return 0;
@@ -1099,7 +1103,7 @@ static bool collect_angle_filename(struct token **next_p, char *buf,
 	struct token *t = *next_p;
 	char *ptr = buf;
 
-	while (!eof_token(t) && !match_op(t, '>')) {
+	while (!eof_token(t = scan_next(&t)) && !match_op(t, '>')) {
 		const char *s = (token_type(t) == TOKEN_STRING) ?
 					t->string->data :
 					show_token(t);
@@ -1123,7 +1127,7 @@ static bool collect_angle_filename(struct token **next_p, char *buf,
 		sparse_error(pos, "empty filename");
 		return false;
 	}
-	*next_p = t->next;
+	*next_p = scan_next(&t->next);
 	return true;
 }
 
@@ -1231,6 +1235,432 @@ err:
 	return false;
 }
 
+static void replace_with_int(struct token *token, int val)
+{
+	static const char *const digits[] = { "0", "1", "2" };
+	token_type(token) = TOKEN_NUMBER;
+	token->number = (val >= 0 && val <= 2) ? digits[val] : "0";
+}
+
+#define MAX_EMBED_SIZE (16 * 1024 * 1024)
+
+enum embed_param_kind {
+	EMBED_PARAM_NONE = 0,
+	EMBED_PARAM_LIMIT = 1 << 0,
+	EMBED_PARAM_PREFIX = 1 << 1,
+	EMBED_PARAM_SUFFIX = 1 << 2,
+	EMBED_PARAM_IF_EMPTY = 1 << 3,
+};
+
+static enum embed_param_kind classify_embed_param(struct ident *id)
+{
+	if (id == &limit_ident || id == &__limit___ident)
+		return EMBED_PARAM_LIMIT;
+	if (id == &prefix_ident || id == &__prefix___ident)
+		return EMBED_PARAM_PREFIX;
+	if (id == &suffix_ident || id == &__suffix___ident)
+		return EMBED_PARAM_SUFFIX;
+	if (id == &if_empty_ident || id == &__if_empty___ident)
+		return EMBED_PARAM_IF_EMPTY;
+	return EMBED_PARAM_NONE;
+}
+
+static struct token *dup_token_range(struct token *start, struct token *end)
+{
+	struct token *head = &eof_token_entry;
+	struct token **tail = &head;
+	struct token *p;
+
+	if (!start || !end)
+		return &eof_token_entry;
+	for (p = start;; p = p->next) {
+		struct token *t = __alloc_token(0);
+		*t = *p;
+		*tail = t;
+		tail = &t->next;
+		if (p == end)
+			break;
+	}
+	*tail = &eof_token_entry;
+	return head;
+}
+
+static bool eval_embed_limit_expr(struct position pos, struct token *pstart,
+				  struct token *pend,
+				  unsigned long long *limit_val)
+{
+	struct token *copy = dup_token_range(pstart, pend);
+	struct expression *expr = NULL;
+	long long sval;
+
+	if (eof_token(copy)) {
+		sparse_error(pos, "expected expression in #embed limit");
+		return false;
+	}
+	in_cond_expr++;
+	expand_list(&copy);
+	for (struct token *t = copy; !eof_token(t); t = t->next) {
+		if (token_type(t) != TOKEN_IDENT)
+			continue;
+		if (standard >= STANDARD_C23 && t->ident == &true_ident) {
+			replace_with_bool(t, 1);
+		} else if (standard >= STANDARD_C23 &&
+			   t->ident == &false_ident) {
+			replace_with_bool(t, 0);
+		} else if (t->ident == &defined_ident) {
+			in_cond_expr--;
+			sparse_error(t->pos,
+				     "'defined' cannot appear in #embed limit");
+			return false;
+		} else {
+			token_type(t) = TOKEN_ZERO_IDENT;
+		}
+	}
+	in_cond_expr--;
+	copy = constant_expression(copy, &expr);
+	if (!expr || !eof_token(copy)) {
+		sparse_error(pos, "invalid #embed limit");
+		return false;
+	}
+	sval = get_expression_value(expr);
+	if (!expr->ctype || expr->type != EXPR_VALUE)
+		return false;
+	if (sval < 0 && !(expr->ctype->ctype.modifiers & MOD_UNSIGNED)) {
+		sparse_error(expr->pos, "negative #embed limit");
+		return false;
+	}
+	*limit_val = (unsigned long long)sval;
+	return true;
+}
+
+static int test_embed_file(const char *path, const char *filename,
+			   int has_limit, unsigned long long limit_val,
+			   unsigned long long *size)
+{
+	int fd;
+	struct stat st;
+	size_t plen = strlen(path);
+	size_t flen = strlen(filename);
+	char fullname[PATH_MAX];
+
+	if (plen + flen + 2 > sizeof(fullname))
+		return 0;
+	memcpy(fullname, path, plen);
+	if (plen && path[plen - 1] != '/') {
+		fullname[plen] = '/';
+		plen++;
+	}
+	memcpy(fullname + plen, filename, flen + 1);
+	fd = open(fullname, O_RDONLY | O_NONBLOCK);
+	if (fd < 0)
+		return 0;
+	if (fstat(fd, &st) < 0 || S_ISDIR(st.st_mode)) {
+		close(fd);
+		return 0;
+	}
+	if (S_ISREG(st.st_mode)) {
+		unsigned long long eff = st.st_size;
+
+		close(fd);
+		if (has_limit && limit_val < eff)
+			eff = limit_val;
+		if (eff > MAX_EMBED_SIZE)
+			return 0;
+		*size = eff;
+		return 1;
+	}
+	if (S_ISCHR(st.st_mode)) {
+		unsigned char b;
+		ssize_t r;
+
+		if (has_limit && limit_val == 0) {
+			close(fd);
+			*size = 0;
+			return 1;
+		}
+		if (has_limit && limit_val > MAX_EMBED_SIZE) {
+			close(fd);
+			return 0;
+		}
+		r = read(fd, &b, 1);
+		close(fd);
+		if (r == 0) {
+			*size = 0;
+			return 1;
+		}
+		if (r > 0 && has_limit) {
+			*size = 1;
+			return 1;
+		}
+		return 0;
+	}
+	close(fd);
+	return 0;
+}
+
+static int check_has_embed(struct stream *stream, const char *filename,
+			   int expect, int has_limit,
+			   unsigned long long limit_val,
+			   unsigned long long *size)
+{
+	const char **path;
+
+	/* Absolute path? */
+	if (filename[0] == '/')
+		return test_embed_file("", filename, has_limit, limit_val,
+				       size);
+
+	set_stream_include_path(stream);
+	path = expect ? angle_includepath : quote_includepath;
+	if (!path)
+		return 0;
+	while (*path != NULL) {
+		if (test_embed_file(*path++, filename, has_limit, limit_val,
+				    size))
+			return 1;
+	}
+	return 0;
+}
+
+static bool parse_has_embed(struct token **list)
+{
+	struct token *token = *list;
+	struct token *lparen = scan_next(&token->next);
+	struct token *next;
+	struct token *params, **param_p, *rparen, *after_rparen;
+	struct stream *stream = input_streams + token->pos.stream;
+	char filename[PATH_MAX];
+	int expect = 0;
+	int val = 0;
+	unsigned long long filesize = 0;
+	int has_limit = 0;
+	unsigned long long limit_val = ~0ULL;
+	unsigned int seen_params = 0;
+	int supported = 1;
+
+	if (!match_op(lparen, '(')) {
+		sparse_error(token->pos, "expected '(' after %s",
+			     show_token(token));
+		return false;
+	}
+	next = scan_next(&lparen->next);
+	if (match_op(next, '<')) {
+		expect = '>';
+		next = next->next;
+		if (!collect_angle_filename(&next, filename, sizeof(filename),
+					    token->pos))
+			goto err;
+	} else if (token_type(next) == TOKEN_STRING) {
+		if (!extract_string_filename(&next, filename, sizeof(filename)))
+			goto err;
+	} else {
+		struct token **p = &lparen->next;
+		int depth = 1;
+
+		rparen = scan_next(p);
+		while (!eof_token(rparen)) {
+			if (match_op(rparen, '('))
+				depth++;
+			else if (match_op(rparen, ')') && !--depth)
+				break;
+			p = &rparen->next;
+			rparen = scan_next(p);
+		}
+		if (!match_op(rparen, ')')) {
+			sparse_error(rparen->pos, "expected ')'");
+			token->next = rparen;
+			return false;
+		}
+		*p = &eof_token_entry;
+		expand_list(&lparen->next);
+		p = &lparen->next;
+		while (!eof_token(*p))
+			p = &(*p)->next;
+		*p = rparen;
+		next = lparen->next;
+		if (match_op(next, '<')) {
+			expect = '>';
+			next = next->next;
+			if (!collect_angle_filename(&next, filename,
+						    sizeof(filename),
+						    token->pos))
+				goto err;
+		} else if (token_type(next) == TOKEN_STRING) {
+			if (!extract_string_filename(&next, filename,
+						     sizeof(filename)))
+				goto err;
+		} else {
+			sparse_error(next->pos,
+				     "expected \"FILENAME\" or <FILENAME>");
+			goto err;
+		}
+	}
+
+	/* Isolate optional parameters before matching ')' */
+	params = next;
+	{
+		struct token **p = &params;
+		int depth = 1;
+
+		rparen = scan_next(p);
+		while (!eof_token(rparen)) {
+			if (match_op(rparen, '('))
+				depth++;
+			else if (match_op(rparen, ')') && !--depth)
+				break;
+			p = &rparen->next;
+			rparen = scan_next(p);
+		}
+		if (!match_op(rparen, ')')) {
+			sparse_error(rparen->pos, "expected ')'");
+			token->next = rparen;
+			return false;
+		}
+		after_rparen = rparen->next;
+		*p = &eof_token_entry;
+	}
+
+	param_p = &params;
+	while (!eof_token(next = scan_next(param_p))) {
+		if (token_type(next) == TOKEN_IDENT) {
+			struct ident *id;
+			struct position id_pos;
+			enum embed_param_kind kind;
+
+			if (!expand_one_symbol(param_p))
+				continue;
+			next = *param_p;
+			if (token_type(next) != TOKEN_IDENT) {
+				supported = 0;
+				param_p = &next->next;
+				continue;
+			}
+			id = next->ident;
+			id_pos = next->pos;
+			if (match_op(next->next, ':') &&
+			    match_op(next->next->next, ':')) {
+				struct token *attr = next->next->next->next;
+
+				if (token_type(attr) != TOKEN_IDENT) {
+					sparse_error(attr->pos,
+						     "identifier expected");
+					token->next = after_rparen;
+					return false;
+				}
+				supported = 0;
+				next = attr->next;
+				if (match_op(next, '(')) {
+					int depth = 1;
+
+					next = next->next;
+					while (!eof_token(next) && depth > 0) {
+						if (match_op(next, '('))
+							depth++;
+						else if (match_op(next, ')'))
+							depth--;
+						next = next->next;
+					}
+				}
+				param_p = &next;
+				continue;
+			}
+
+			kind = classify_embed_param(id);
+			if (kind != EMBED_PARAM_NONE) {
+				struct token *pstart, *pend = NULL;
+				int depth = 1;
+
+				if (seen_params & kind) {
+					sparse_error(
+						id_pos,
+						"duplicate embed parameter '%s'",
+						show_ident(id));
+					token->next = after_rparen;
+					return false;
+				}
+				seen_params |= kind;
+
+				next = scan_next(&next->next);
+				if (!match_op(next, '(')) {
+					sparse_error(
+						id_pos,
+						"expected '(' after '%s'",
+						show_ident(id));
+					token->next = after_rparen;
+					return false;
+				}
+				pstart = next->next;
+				next = next->next;
+				while (!eof_token(next) && depth > 0) {
+					if (match_op(next, '('))
+						depth++;
+					else if (match_op(next, ')') &&
+						 !--depth)
+						break;
+					pend = next;
+					next = next->next;
+				}
+				if (!match_op(next, ')')) {
+					sparse_error(id_pos, "expected ')'");
+					token->next = after_rparen;
+					return false;
+				}
+				next = next->next;
+				if (kind == EMBED_PARAM_LIMIT) {
+					if (!eval_embed_limit_expr(
+						    id_pos, pstart, pend,
+						    &limit_val)) {
+						token->next = after_rparen;
+						return false;
+					}
+					has_limit = 1;
+				}
+				param_p = &next;
+				continue;
+			}
+
+			/* Unknown non-prefixed parameter */
+			supported = 0;
+			next = scan_next(&next->next);
+			if (match_op(next, '(')) {
+				int depth = 1;
+
+				next = next->next;
+				while (!eof_token(next) && depth > 0) {
+					if (match_op(next, '('))
+						depth++;
+					else if (match_op(next, ')'))
+						depth--;
+					next = next->next;
+				}
+			}
+			param_p = &next;
+			continue;
+		}
+		supported = 0;
+		param_p = &next->next;
+	}
+
+	if (!supported) {
+		val = 0;
+	} else if (!check_has_embed(stream, filename, expect, has_limit,
+				    limit_val, &filesize)) {
+		val = 0;
+	} else if (filesize == 0 || (has_limit && limit_val == 0)) {
+		val = 2;
+	} else {
+		val = 1;
+	}
+
+	replace_with_int(token, val);
+	token->next = after_rparen;
+	return true;
+
+err:
+	token->next = skip_balanced_parens(lparen);
+	return false;
+}
+
 static int free_preprocessor_line(struct token *token)
 {
 	while (token_type(token) != TOKEN_EOF) {
@@ -1933,6 +2363,12 @@ static int expression_value(struct token **where)
 				replace_with_bool(p, 0);
 				break;
 			}
+			if (p->ident == &__has_embed_ident) {
+				if (parse_has_embed(list))
+					continue;
+				replace_with_bool(p, 0);
+				break;
+			}
 			if (!expand_one_symbol(list))
 				continue;
 			if (token_type(p) != TOKEN_IDENT)
@@ -2598,6 +3034,7 @@ static void init_preprocessor(void)
 		{ "__has_feature", NULL, expand_has_feature },
 		{ "__has_include", NULL, NULL },
 		{ "__has_include_next", NULL, NULL },
+		{ "__has_embed", NULL, NULL },
 	};
 
 	for (i = 0; i < ARRAY_SIZE(normal); i++) {
diff --git a/predefine.c b/predefine.c
index d6ab0785abad..5639fc6862e3 100644
--- a/predefine.c
+++ b/predefine.c
@@ -123,6 +123,9 @@ void predefined_macros(void)
 		predefine("__STDC_NO_COMPLEX__", 1, "1");
 		predefine("__STDC_NO_THREADS__", 1, "1");
 	}
+	predefine("__STDC_EMBED_NOT_FOUND__", 1, "0");
+	predefine("__STDC_EMBED_FOUND__", 1, "1");
+	predefine("__STDC_EMBED_EMPTY__", 1, "2");
 
 	predefine("__CHAR_BIT__", 1, "%d", bits_in_char);
 	if (funsigned_char)
diff --git a/validation/preprocessor/c23-has-embed.c b/validation/preprocessor/c23-has-embed.c
new file mode 100644
index 000000000000..8ae7bb1d081f
--- /dev/null
+++ b/validation/preprocessor/c23-has-embed.c
@@ -0,0 +1,69 @@
+#if defined(__has_embed)
+int has_macro = 1;
+#endif
+
+#if __has_embed("options.h") == __STDC_EMBED_FOUND__
+int found_options = 1;
+#endif
+
+#if __has_embed("options.h" limit(0)) == __STDC_EMBED_EMPTY__
+int limit_zero_empty = 1;
+#endif
+
+#if __has_embed("options.h" __limit__(0)) == __STDC_EMBED_EMPTY__
+int dunder_limit_zero_empty = 1;
+#endif
+
+#if __has_embed("options.h" limit(5)) == __STDC_EMBED_FOUND__
+int limit_five_found = 1;
+#endif
+
+#if __has_embed("nonexistent_embed_12345.bin") == __STDC_EMBED_NOT_FOUND__
+int not_found = 1;
+#endif
+
+#if __has_embed("options.h" prefix(1) suffix(2)) == __STDC_EMBED_FOUND__ && \
+    __has_embed("options.h" __prefix__(1) __suffix__(2) __if_empty__(3)) == \
+	__STDC_EMBED_FOUND__
+int standard_params = 1;
+#endif
+
+#if __has_embed("options.h" unknown_vendor::param(1)) == \
+	__STDC_EMBED_NOT_FOUND__
+int unknown_param = 1;
+#endif
+
+#define HAS_EMB __has_embed
+#if HAS_EMB("options.h") == __STDC_EMBED_FOUND__
+int aliased_has_embed = 1;
+#endif
+
+#if __has_embed("/dev/null") == __STDC_EMBED_EMPTY__
+int dev_null_empty = 1;
+#endif
+
+#if __has_embed("options.h" limit(1ULL << 63)) == __STDC_EMBED_FOUND__ && \
+    __has_embed("options.h" limit(0xffffffffffffffffULL)) == \
+	__STDC_EMBED_FOUND__
+int large_unsigned_limit = 1;
+#endif
+
+/*
+ * check-name: c23-has-embed
+ * check-command: sparse -std=c23 -E -I.. $file
+ *
+ * check-output-start
+
+int has_macro = 1;
+int found_options = 1;
+int limit_zero_empty = 1;
+int dunder_limit_zero_empty = 1;
+int limit_five_found = 1;
+int not_found = 1;
+int standard_params = 1;
+int unknown_param = 1;
+int aliased_has_embed = 1;
+int dev_null_empty = 1;
+int large_unsigned_limit = 1;
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 56/73] preprocessor: Add support for #embed directive
  2026-11-09 20:31 Bart Van Assche
                   ` (54 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
                   ` (16 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add support for the C23 #embed preprocessor directive to embed binary
resource files into source code as comma-delimited integer constant
sequences, supporting the standard limit, prefix, suffix, and if_empty
parameters.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 pre-process.c                                | 396 +++++++++++++++++++
 validation/preprocessor/c23-embed-error.c    |  19 +
 validation/preprocessor/c23-embed-pedantic.c |  12 +
 validation/preprocessor/c23-embed.c          |  72 ++++
 4 files changed, 499 insertions(+)
 create mode 100644 validation/preprocessor/c23-embed-error.c
 create mode 100644 validation/preprocessor/c23-embed-pedantic.c
 create mode 100644 validation/preprocessor/c23-embed.c

diff --git a/pre-process.c b/pre-process.c
index e8b3511c225e..428b96f990fb 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -35,6 +35,7 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <time.h>
+#include <errno.h>
 #include <sys/stat.h>
 
 #include "lib.h"
@@ -1661,6 +1662,400 @@ err:
 	return false;
 }
 
+static int open_embed_file(struct stream *stream, const char *filename,
+			   int expect)
+{
+	char fullname[PATH_MAX];
+	const char **path;
+	size_t flen = strlen(filename);
+
+	if (filename[0] == '/')
+		return open(filename, O_RDONLY);
+
+	set_stream_include_path(stream);
+	path = expect ? angle_includepath : quote_includepath;
+	if (!path)
+		return -1;
+	while (*path != NULL) {
+		const char *p = *path++;
+		size_t plen = strlen(p);
+		if (plen + flen + 2 > sizeof(fullname))
+			continue;
+		memcpy(fullname, p, plen);
+		if (plen && p[plen - 1] != '/') {
+			fullname[plen] = '/';
+			plen++;
+		}
+		memcpy(fullname + plen, filename, flen + 1);
+		int fd = open(fullname, O_RDONLY);
+		if (fd >= 0)
+			return fd;
+	}
+	return -1;
+}
+
+static int free_preprocessor_line(struct token *token);
+
+static int handle_embed(struct stream *stream, struct token **line,
+			struct token *token)
+{
+	char filename[PATH_MAX];
+	int expect = 0;
+	struct token *next = token->next;
+	struct token **param_p;
+	struct token *prefix_start = NULL, *prefix_end = NULL;
+	struct token *suffix_start = NULL, *suffix_end = NULL;
+	struct token *if_empty_start = NULL, *if_empty_end = NULL;
+	int has_limit = 0;
+	unsigned long long limit_val = ~0ULL;
+	unsigned int seen_params = 0;
+	int fd;
+	struct stat st;
+	unsigned char *buf = NULL;
+	ssize_t bytes_read = 0;
+	struct token *head = NULL, **tail = &head;
+	static char num_strs[256][4];
+	static int num_strs_inited = 0;
+
+	if (standard < STANDARD_C23 && Wpedantic)
+		warning(token->pos, "#embed before C23 is an extension");
+
+	next = scan_next(&token->next);
+	if (match_op(next, '<')) {
+		expect = '>';
+		next = next->next;
+		if (!collect_angle_filename(&next, filename, sizeof(filename),
+					    token->pos))
+			return 1;
+	} else if (token_type(next) == TOKEN_STRING) {
+		if (!extract_string_filename(&next, filename, sizeof(filename)))
+			return 1;
+	} else {
+		while (token_type(next = scan_next(&token->next)) ==
+			       TOKEN_IDENT &&
+		       !expand_one_symbol(&token->next))
+			;
+		next = scan_next(&token->next);
+		if (match_op(next, '<')) {
+			struct token **p = &next->next;
+			struct token *t;
+
+			while (!eof_token(t = scan_next(p)) &&
+			       !match_op(t, '>')) {
+				if (token_type(t) != TOKEN_IDENT ||
+				    expand_one_symbol(p))
+					p = &t->next;
+			}
+			expect = '>';
+			next = next->next;
+			if (!collect_angle_filename(&next, filename,
+						    sizeof(filename),
+						    token->pos))
+				return 1;
+		} else if (token_type(next) == TOKEN_STRING) {
+			if (!extract_string_filename(&next, filename,
+						     sizeof(filename)))
+				return 1;
+		} else {
+			sparse_error(next->pos,
+				     "expected \"FILENAME\" or <FILENAME>");
+			return 1;
+		}
+	}
+
+	param_p = &next;
+	while (!eof_token(next = scan_next(param_p))) {
+		if (token_type(next) == TOKEN_IDENT) {
+			struct ident *id;
+			struct position id_pos;
+			enum embed_param_kind kind;
+			struct token *pstart, *pend = NULL;
+			int depth = 1;
+
+			if (!expand_one_symbol(param_p))
+				continue;
+			next = *param_p;
+			if (token_type(next) != TOKEN_IDENT) {
+				sparse_error(next->pos,
+					     "unexpected token in #embed");
+				return 1;
+			}
+			id = next->ident;
+			id_pos = next->pos;
+			if (match_op(next->next, ':') &&
+			    match_op(next->next->next, ':')) {
+				struct token *attr = next->next->next->next;
+
+				if (token_type(attr) == TOKEN_IDENT)
+					sparse_error(
+						id_pos,
+						"unknown embed parameter '%s::%s'",
+						show_ident(id),
+						show_ident(attr->ident));
+				else
+					sparse_error(attr->pos,
+						     "identifier expected");
+				return 1;
+			}
+
+			kind = classify_embed_param(id);
+			if (kind == EMBED_PARAM_NONE) {
+				sparse_error(id_pos,
+					     "unknown embed parameter '%s'",
+					     show_ident(id));
+				return 1;
+			}
+			if (seen_params & kind) {
+				sparse_error(id_pos,
+					     "duplicate embed parameter '%s'",
+					     show_ident(id));
+				return 1;
+			}
+			seen_params |= kind;
+
+			next = scan_next(&next->next);
+			if (!match_op(next, '(')) {
+				sparse_error(id_pos, "expected '(' after '%s'",
+					     show_ident(id));
+				return 1;
+			}
+			pstart = next->next;
+			next = next->next;
+			while (!eof_token(next) && depth > 0) {
+				if (match_op(next, '('))
+					depth++;
+				else if (match_op(next, ')') && !--depth)
+					break;
+				pend = next;
+				next = next->next;
+			}
+			if (!match_op(next, ')')) {
+				sparse_error(id_pos, "expected ')'");
+				return 1;
+			}
+			next = next->next;
+			if (kind == EMBED_PARAM_LIMIT) {
+				if (!eval_embed_limit_expr(id_pos, pstart, pend,
+							   &limit_val))
+					return 1;
+				has_limit = 1;
+			} else if (kind == EMBED_PARAM_PREFIX) {
+				prefix_start = pstart;
+				prefix_end = pend;
+			} else if (kind == EMBED_PARAM_SUFFIX) {
+				suffix_start = pstart;
+				suffix_end = pend;
+			} else if (kind == EMBED_PARAM_IF_EMPTY) {
+				if_empty_start = pstart;
+				if_empty_end = pend;
+			}
+			param_p = &next;
+			continue;
+		}
+		sparse_error(next->pos, "unexpected token in #embed");
+		return 1;
+	}
+
+	fd = open_embed_file(stream, filename, expect);
+	if (fd < 0) {
+		sparse_error(token->pos, "unable to open '%s'", filename);
+		return 1;
+	}
+
+	if (fstat(fd, &st) < 0) {
+		close(fd);
+		sparse_error(token->pos, "unable to stat '%s'", filename);
+		return 1;
+	}
+	if (S_ISDIR(st.st_mode)) {
+		close(fd);
+		sparse_error(token->pos, "'%s' is a directory", filename);
+		return 1;
+	}
+
+	if (S_ISREG(st.st_mode) || has_limit) {
+		unsigned long long to_read =
+			S_ISREG(st.st_mode) ? (unsigned long long)st.st_size :
+					      limit_val;
+
+		if (has_limit && limit_val < to_read)
+			to_read = limit_val;
+		if (to_read > MAX_EMBED_SIZE) {
+			sparse_error(
+				token->pos,
+				"'%s' is too large to embed (%llu bytes, max %d bytes)",
+				filename, to_read, MAX_EMBED_SIZE);
+			close(fd);
+			return 1;
+		}
+		buf = malloc(to_read ? to_read : 1);
+		if (!buf) {
+			sparse_error(
+				token->pos,
+				"out of memory allocating #embed buffer for '%s'",
+				filename);
+			close(fd);
+			return 1;
+		}
+		if (to_read > 0) {
+			size_t total = 0;
+
+			while (total < to_read) {
+				ssize_t r =
+					read(fd, buf + total, to_read - total);
+				if (r < 0) {
+					if (errno == EINTR || errno == EAGAIN)
+						continue;
+					sparse_error(token->pos,
+						     "error reading '%s': %s",
+						     filename, strerror(errno));
+					free(buf);
+					close(fd);
+					return 1;
+				}
+				if (r == 0)
+					break;
+				total += r;
+			}
+			bytes_read = total;
+		}
+	} else {
+		size_t cap = 4096;
+		size_t total = 0;
+
+		buf = malloc(cap);
+		if (!buf) {
+			sparse_error(
+				token->pos,
+				"out of memory allocating #embed buffer for '%s'",
+				filename);
+			close(fd);
+			return 1;
+		}
+		for (;;) {
+			ssize_t r;
+
+			if (total == cap) {
+				size_t new_cap = cap * 2;
+				unsigned char *nbuf;
+
+				if (new_cap > MAX_EMBED_SIZE + 1)
+					new_cap = MAX_EMBED_SIZE + 1;
+				nbuf = realloc(buf, new_cap);
+				if (!nbuf) {
+					sparse_error(
+						token->pos,
+						"out of memory allocating #embed buffer for '%s'",
+						filename);
+					free(buf);
+					close(fd);
+					return 1;
+				}
+				buf = nbuf;
+				cap = new_cap;
+			}
+			r = read(fd, buf + total, cap - total);
+			if (r < 0) {
+				if (errno == EINTR || errno == EAGAIN)
+					continue;
+				sparse_error(token->pos,
+					     "error reading '%s': %s",
+					     filename, strerror(errno));
+				free(buf);
+				close(fd);
+				return 1;
+			}
+			if (r == 0)
+				break;
+			total += r;
+			if (total > MAX_EMBED_SIZE) {
+				sparse_error(
+					token->pos,
+					"'%s' is too large to embed (max %d bytes)",
+					filename, MAX_EMBED_SIZE);
+				free(buf);
+				close(fd);
+				return 1;
+			}
+		}
+		bytes_read = total;
+	}
+	close(fd);
+
+	if (bytes_read == 0) {
+		if (if_empty_start && if_empty_end) {
+			struct token *p = if_empty_start;
+			while (p) {
+				struct token *t = dup_token(p, &token->pos);
+				*tail = t;
+				tail = &t->next;
+				if (p == if_empty_end)
+					break;
+				p = p->next;
+			}
+			*tail = *line;
+			*line = head;
+		}
+		free(buf);
+		free_preprocessor_line(token);
+		return 0;
+	}
+
+	if (prefix_start && prefix_end) {
+		struct token *p = prefix_start;
+		while (p) {
+			struct token *t = dup_token(p, &token->pos);
+			*tail = t;
+			tail = &t->next;
+			if (p == prefix_end)
+				break;
+			p = p->next;
+		}
+	}
+
+	if (!num_strs_inited) {
+		for (unsigned int i = 0; i < 256; i++)
+			snprintf(num_strs[i], sizeof(num_strs[i]), "%u",
+				 i & 0xff);
+		num_strs_inited = 1;
+	}
+
+	for (ssize_t i = 0; i < bytes_read; i++) {
+		if (i > 0) {
+			struct token *comma = alloc_token(&token->pos);
+			token_type(comma) = TOKEN_SPECIAL;
+			comma->special = ',';
+			*tail = comma;
+			tail = &comma->next;
+		}
+		struct token *num = alloc_token(&token->pos);
+		token_type(num) = TOKEN_NUMBER;
+		num->number = num_strs[buf[i]];
+		*tail = num;
+		tail = &num->next;
+	}
+
+	if (suffix_start && suffix_end) {
+		struct token *p = suffix_start;
+		while (p) {
+			struct token *t = dup_token(p, &token->pos);
+			*tail = t;
+			tail = &t->next;
+			if (p == suffix_end)
+				break;
+			p = p->next;
+		}
+	}
+
+	if (head) {
+		*tail = *line;
+		*line = head;
+	}
+	free(buf);
+	free_preprocessor_line(token);
+	return 0;
+}
+
 static int free_preprocessor_line(struct token *token)
 {
 	while (token_type(token) != TOKEN_EOF) {
@@ -2993,6 +3388,7 @@ static void init_preprocessor(void)
 		{ "error",		handle_error },
 		{ "include",		handle_include },
 		{ "include_next",	handle_include_next },
+		{ "embed",		handle_embed },
 		{ "pragma",		handle_pragma },
 		{ "line",		handle_line },
 		{ "ident",		handle_ident },
diff --git a/validation/preprocessor/c23-embed-error.c b/validation/preprocessor/c23-embed-error.c
new file mode 100644
index 000000000000..26bfd4bc40c7
--- /dev/null
+++ b/validation/preprocessor/c23-embed-error.c
@@ -0,0 +1,19 @@
+int outside = __has_embed("options.h");
+
+#if __has_embed("options.h" limit(1) limit(2))
+#endif
+
+#embed "options.h" limit(1) __limit__(2)
+#embed "options.h" vendor::param(1)
+
+/*
+ * check-name: c23-embed-error
+ * check-command: sparse -std=c23 -Wno-decl -I.. $file
+ *
+ * check-error-start
+preprocessor/c23-embed-error.c:1:15: error: '__has_embed' used outside of preprocessing directive
+preprocessor/c23-embed-error.c:3:38: error: duplicate embed parameter 'limit'
+preprocessor/c23-embed-error.c:6:29: error: duplicate embed parameter '__limit__'
+preprocessor/c23-embed-error.c:7:20: error: unknown embed parameter 'vendor::param'
+ * check-error-end
+ */
diff --git a/validation/preprocessor/c23-embed-pedantic.c b/validation/preprocessor/c23-embed-pedantic.c
new file mode 100644
index 000000000000..8e78aa4121da
--- /dev/null
+++ b/validation/preprocessor/c23-embed-pedantic.c
@@ -0,0 +1,12 @@
+static const unsigned char data[] = {
+#embed "options.h" limit(1)
+};
+
+/*
+ * check-name: c23-embed-pedantic
+ * check-command: sparse -std=c11 -Wpedantic -I.. $file
+ *
+ * check-error-start
+preprocessor/c23-embed-pedantic.c:2:2: warning: #embed before C23 is an extension
+ * check-error-end
+ */
diff --git a/validation/preprocessor/c23-embed.c b/validation/preprocessor/c23-embed.c
new file mode 100644
index 000000000000..a4411847b9cb
--- /dev/null
+++ b/validation/preprocessor/c23-embed.c
@@ -0,0 +1,72 @@
+static const unsigned char data[] = {
+#embed "options.h" limit(4)
+};
+
+static const unsigned char with_params[] = {
+#embed "options.h" limit(2) prefix(10, 20, ) suffix(, 30, 40)
+};
+
+static const unsigned char dunder_params[] = {
+#embed "options.h" __limit__(2) __prefix__(10, ) __suffix__(, 20)
+};
+
+static const unsigned char empty_buf[] = {
+#embed "options.h" limit(0) if_empty(99)
+};
+
+static const unsigned char dev_null_buf[] = {
+#embed "/dev/null" __if_empty__(77)
+};
+
+static const unsigned char zero_buf[] = {
+#embed "/dev/zero" limit(4)
+};
+
+static const unsigned char nested_has_embed[] = {
+#embed "options.h" limit(__has_embed("options.h"))
+};
+
+static_assert(sizeof(data) == 4, "size data");
+static_assert(sizeof(with_params) == 6, "size with_params");
+static_assert(sizeof(dunder_params) == 4, "size dunder_params");
+static_assert(sizeof(empty_buf) == 1, "size empty_buf");
+static_assert(sizeof(dev_null_buf) == 1, "size dev_null_buf");
+static_assert(sizeof(zero_buf) == 4, "size zero_buf");
+static_assert(sizeof(nested_has_embed) == 1, "size nested_has_embed");
+
+static constexpr unsigned char p0 =
+#embed "options.h" limit(1) prefix(10 + ) suffix( + 5)
+	;
+static_assert(p0 == 50, "p0");
+
+static constexpr unsigned char z0 =
+#embed "/dev/zero" limit(1)
+	;
+static_assert(z0 == 0, "z0");
+
+static constexpr unsigned char b0 =
+#embed "options.h" limit(1)
+	;
+static_assert(b0 == 35, "b0");
+
+#define EMBED_HDR "options.h"
+static const unsigned char macro_hdr_params[] = {
+#embed EMBED_HDR limit(2) if_empty(__COUNTER__) prefix(11, )
+};
+static_assert(__COUNTER__ == 0, "if_empty not expanded when non-empty");
+static_assert(sizeof(macro_hdr_params) == 3, "size macro_hdr_params");
+
+static constexpr unsigned char macro_hdr_p0 =
+#embed EMBED_HDR limit(1) if_empty(__COUNTER__) prefix(10 + )
+	;
+static_assert(macro_hdr_p0 == 45, "macro_hdr_p0");
+
+static const unsigned char large_limit_buf[] = {
+#embed "options.h" limit(1ULL << 63)
+};
+static_assert(sizeof(large_limit_buf) > 4, "size large_limit_buf");
+
+/*
+ * check-name: c23-embed
+ * check-command: sparse -std=c23 -Wno-decl -I.. $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 57/73] c23: Add support for u8 character constants and string literals
  2026-11-09 20:31 Bart Van Assche
                   ` (55 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
                   ` (15 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support C23 UTF-8 character constants (u8'c') and UTF-8 string literals
(u8"...") with unsigned char (char8_t) element type per ISO C23 6.4.4.4
and 6.4.5:
- Type u8"..." literals (including narrow + u8"" concatenations) as
  arrays of unsigned char in C23 mode while preserving char[] typing in
  C11/C17, and diagnose pointer signedness mismatches with char * in C23.
- Parse \uXXXX and \UXXXXXXXX universal character names in character and
  string literals, reject u8"" concatenation with wide strings, and emit
  errors on multi-byte or multi-character u8'...' constants.
- Warn under -Wpedantic when u8 character constants are used before C23.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 char.c                            | 111 ++++++++++++++++++++++++++++--
 evaluate.c                        |   7 +-
 expression.c                      |  15 ++--
 expression.h                      |   3 +-
 ident-list.h                      |   3 +-
 pre-process.c                     |  21 +++++-
 token.h                           |   6 ++
 tokenize.c                        | 102 ++++++++++++++++++---------
 validation/c23-u8-char-error.c    |  16 +++++
 validation/c23-u8-char-pedantic.c |  10 +++
 validation/c23-u8-char-warn.c     |  14 ++++
 validation/c23-u8-char.c          |  37 ++++++++++
 12 files changed, 296 insertions(+), 49 deletions(-)
 create mode 100644 validation/c23-u8-char-error.c
 create mode 100644 validation/c23-u8-char-pedantic.c
 create mode 100644 validation/c23-u8-char-warn.c
 create mode 100644 validation/c23-u8-char.c

diff --git a/char.c b/char.c
index 1f6eb1f6be83..a0776b560e29 100644
--- a/char.c
+++ b/char.c
@@ -6,6 +6,54 @@
 #include "expression.h"
 #include "char.h"
 
+static const char *parse_ucn(const char *p, const char *end, int digits,
+			     unsigned *cp, struct position pos)
+{
+	unsigned val = 0;
+	int i;
+
+	for (i = 0; i < digits; i++) {
+		unsigned d;
+		if (p >= end || (d = hexval(*p)) > 15) {
+			sparse_error(pos, "incomplete universal character name");
+			*cp = 0;
+			return p;
+		}
+		val = (val << 4) + d;
+		p++;
+	}
+	if ((val >= 0xd800 && val <= 0xdfff) || val > 0x10ffff) {
+		sparse_error(pos, "invalid universal character name");
+		val = 0;
+	}
+	*cp = val;
+	return p;
+}
+
+static int encode_utf8(unsigned cp, char out[4])
+{
+	if (cp <= 0x7f) {
+		out[0] = cp;
+		return 1;
+	}
+	if (cp <= 0x7ff) {
+		out[0] = 0xc0 | (cp >> 6);
+		out[1] = 0x80 | (cp & 0x3f);
+		return 2;
+	}
+	if (cp <= 0xffff) {
+		out[0] = 0xe0 | (cp >> 12);
+		out[1] = 0x80 | ((cp >> 6) & 0x3f);
+		out[2] = 0x80 | (cp & 0x3f);
+		return 3;
+	}
+	out[0] = 0xf0 | (cp >> 18);
+	out[1] = 0x80 | ((cp >> 12) & 0x3f);
+	out[2] = 0x80 | ((cp >> 6) & 0x3f);
+	out[3] = 0x80 | (cp & 0x3f);
+	return 4;
+}
+
 static const char *parse_escape(const char *p, unsigned *val, const char *end, int bits, struct position pos)
 {
 	unsigned c = (unsigned char)*p++;
@@ -26,6 +74,11 @@ static const char *parse_escape(const char *p, unsigned *val, const char *end, i
 	case 'f': c = '\f'; break;
 	case 'r': c = '\r'; break;
 	case 'e': c = '\e'; break;
+	case 'u':
+	case 'U':
+		p = parse_ucn(p, end, c == 'u' ? 4 : 8, &c, pos);
+		*val = c;
+		return p;
 	case 'x': {
 		unsigned mask = -(1U << (bits - 4));
 		for (c = 0; p < end; c = (c << 4) + d) {
@@ -75,6 +128,7 @@ void get_char_constant(struct token *token, unsigned long long *val)
 	switch (type) {
 	case TOKEN_CHAR:
 	case TOKEN_WIDE_CHAR:
+	case TOKEN_UTF8_CHAR:
 		p = token->string->data;
 		end = p + token->string->length - 1;
 		if (end == p) {
@@ -86,14 +140,34 @@ void get_char_constant(struct token *token, unsigned long long *val)
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 		end = p + type - TOKEN_CHAR;
 		break;
-	default:
+	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
 		end = p + type - TOKEN_WIDE_CHAR;
+		break;
+	default:
+		end = p + type - TOKEN_UTF8_CHAR;
+		break;
 	}
 	p = parse_escape(p, &v, end,
-			type < TOKEN_WIDE_CHAR ? bits_in_char : wchar_ctype->bit_size, token->pos);
-	if (p != end)
+			 (type >= TOKEN_WIDE_CHAR &&
+			  type <= TOKEN_WIDE_CHAR_EMBEDDED_3) ?
+				 wchar_ctype->bit_size :
+				 bits_in_char,
+			 token->pos);
+	if (type >= TOKEN_UTF8_CHAR && type <= TOKEN_UTF8_CHAR_EMBEDDED_3) {
+		if (p != end || v > 0xff ||
+		    (token->embedded[0] == '\\' &&
+		     (token->embedded[1] == 'u' || token->embedded[1] == 'U') &&
+		     v > 0x7f) ||
+		    (type == TOKEN_UTF8_CHAR && token->string->data[0] == '\\' &&
+		     (token->string->data[1] == 'u' ||
+		      token->string->data[1] == 'U') &&
+		     v > 0x7f))
+			sparse_error(token->pos,
+				     "multi-character UTF-8 character constant");
+	} else if (p != end) {
 		warning(token->pos,
 			"multi-character character constant");
+	}
 	*val = v;
 }
 
@@ -103,6 +177,7 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 	struct token *next = token->next, *done = NULL;
 	int stringtype = token_type(token);
 	int is_wide = stringtype == TOKEN_WIDE_STRING;
+	int is_utf8 = stringtype == TOKEN_UTF8_STRING;
 	static char buffer[MAX_STRING];
 	int len = 0;
 	int bits;
@@ -112,6 +187,12 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 		switch (token_type(next)) {
 		case TOKEN_WIDE_STRING:
 			is_wide = 1;
+			next = next->next;
+			break;
+		case TOKEN_UTF8_STRING:
+			is_utf8 = 1;
+			next = next->next;
+			break;
 		case TOKEN_STRING:
 			next = next->next;
 			break;
@@ -119,18 +200,35 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 			done = next;
 		}
 	}
+	if (is_wide && is_utf8) {
+		sparse_error(token->pos,
+			     "unsupported non-standard concatenation of string literals");
+		is_utf8 = 0;
+	}
 	bits = is_wide ? wchar_ctype->bit_size: bits_in_char;
 	while (token != done) {
 		unsigned v;
 		const char *p = token->string->data;
 		const char *end = p + token->string->length - 1;
 		while (p < end) {
+			bool is_ucn = (*p == '\\' && p + 1 < end &&
+				       (p[1] == 'u' || p[1] == 'U'));
 			if (*p == '\\')
 				esc_count++;
 			p = parse_escape(p, &v, end, bits, token->pos);
-			if (len < MAX_STRING)
-				buffer[len] = v;
-			len++;
+			if (is_ucn && !is_wide) {
+				char utf8[4];
+				int i, n = encode_utf8(v, utf8);
+				for (i = 0; i < n; i++) {
+					if (len < MAX_STRING)
+						buffer[len] = utf8[i];
+					len++;
+				}
+			} else {
+				if (len < MAX_STRING)
+					buffer[len] = v;
+				len++;
+			}
 		}
 		token = token->next;
 	}
@@ -148,5 +246,6 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 	}
 	expr->string = string;
 	expr->wide = is_wide;
+	expr->utf8 = is_utf8;
 	return token;
 }
diff --git a/evaluate.c b/evaluate.c
index f40e35773639..e46b1a7d4f70 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -135,7 +135,8 @@ static struct symbol *evaluate_string(struct expression *expr)
 	struct expression *addr = alloc_expression(expr->pos, EXPR_SYMBOL);
 	struct expression *initstr = alloc_expression(expr->pos, EXPR_STRING);
 	unsigned int length = expr->string->length;
-	struct symbol *char_type = expr->wide ? wchar_ctype : &char_ctype;
+	struct symbol *char_type = expr->wide ? wchar_ctype :
+		(expr->utf8 && standard >= STANDARD_C23) ? &uchar_ctype : &char_ctype;
 
 	sym->array_size = alloc_const_expression(expr->pos, length);
 	sym->bit_size = length * char_type->bit_size;
@@ -148,6 +149,8 @@ static struct symbol *evaluate_string(struct expression *expr)
 	sym->evaluated = 1;
 
 	initstr->ctype = sym;
+	initstr->wide = expr->wide;
+	initstr->utf8 = expr->utf8;
 	initstr->string = expr->string;
 
 	array->array_size = sym->array_size;
@@ -1599,7 +1602,7 @@ static int whitelist_pointers(struct symbol *t1, struct symbol *t2)
 	if (t1 == t2)
 		return 1;
 	if (t1->rank == -2 && t2->rank == -2)
-		return 1;
+		return !Wtypesign && !Wpedantic && standard < STANDARD_C23;
 	if (t1->rank != t2->rank)
 		return 0;
 	return !Wtypesign;
diff --git a/expression.c b/expression.c
index 1b04d977a2d5..ace0bf6d1a31 100644
--- a/expression.c
+++ b/expression.c
@@ -599,17 +599,23 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 	struct expression *expr = NULL;
 
 	switch (token_type(token)) {
-	case TOKEN_CHAR ... TOKEN_WIDE_CHAR_EMBEDDED_3:
+	case TOKEN_CHAR ... TOKEN_UTF8_CHAR_EMBEDDED_3:
 		expr = alloc_expression(token->pos, EXPR_VALUE);
 		expr->flags = CEF_SET_CHAR;
 		get_char_constant(token, &expr->value);
 
-		// TODO: handle 'u8', 'u' & 'U' prefixes.
-		if (token_type(token) < TOKEN_WIDE_CHAR) {
+		// TODO: handle 'u' & 'U' prefixes.
+		if (token_type(token) <= TOKEN_CHAR_EMBEDDED_3) {
 			expr->ctype = &char_ctype;
 			cast_value(expr, &int_ctype, expr);
-		} else {
+		} else if (token_type(token) <= TOKEN_WIDE_CHAR_EMBEDDED_3) {
 			expr->ctype = wchar_ctype;
+		} else {
+			if (standard < STANDARD_C23 && Wpedantic)
+				warning(token->pos,
+					"UTF-8 character constants are a C23 feature");
+			expr->ctype = &uchar_ctype;
+			cast_value(expr, &uchar_ctype, expr);
 		}
 		token = token->next;
 		break;
@@ -709,6 +715,7 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 
 	case TOKEN_STRING:
 	case TOKEN_WIDE_STRING:
+	case TOKEN_UTF8_STRING:
 		expr = alloc_expression(token->pos, EXPR_STRING);
 		token = get_string_constant(token, expr);
 		break;
diff --git a/expression.h b/expression.h
index 8bf40d32e10b..8c387adf9d5b 100644
--- a/expression.h
+++ b/expression.h
@@ -175,7 +175,8 @@ struct expression {
 
 		// EXPR_STRING
 		struct {
-			int wide;
+			char wide;
+			char utf8;
 			struct string *string;
 		};
 
diff --git a/ident-list.h b/ident-list.h
index f51f2048f5ca..3340f11641fa 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -54,8 +54,9 @@ IDENT(static_assert);
 IDENT(thread_local);
 IDENT(true);
 
-/* Special case for L'\t' */
+/* Special case for L'\t' and u8'a' */
 IDENT(L);
+IDENT(u8);
 
 /* Extended gcc identifiers */
 IDENT(asm); IDENT_RESERVED(__asm); IDENT_RESERVED(__asm__);
diff --git a/pre-process.c b/pre-process.c
index 428b96f990fb..9ebf09cffbdd 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -507,12 +507,19 @@ static enum token_type combine(struct token *left, struct token *right, char *p)
 		return TOKEN_ERROR;
 
 	if (t1 == TOKEN_IDENT && left->ident == &L_ident) {
-		if (t2 >= TOKEN_CHAR && t2 < TOKEN_WIDE_CHAR)
+		if (t2 >= TOKEN_CHAR && t2 <= TOKEN_CHAR_EMBEDDED_3)
 			return t2 + TOKEN_WIDE_CHAR - TOKEN_CHAR;
 		if (t2 == TOKEN_STRING)
 			return TOKEN_WIDE_STRING;
 	}
 
+	if (t1 == TOKEN_IDENT && left->ident == &u8_ident) {
+		if (t2 >= TOKEN_CHAR && t2 <= TOKEN_CHAR_EMBEDDED_3)
+			return t2 + TOKEN_UTF8_CHAR - TOKEN_CHAR;
+		if (t2 == TOKEN_STRING)
+			return TOKEN_UTF8_STRING;
+	}
+
 	if (t2 != TOKEN_IDENT && t2 != TOKEN_NUMBER && t2 != TOKEN_SPECIAL)
 		return TOKEN_ERROR;
 
@@ -592,12 +599,15 @@ static int merge(struct token *left, struct token *right)
 
 	case TOKEN_WIDE_CHAR:
 	case TOKEN_WIDE_STRING:
+	case TOKEN_UTF8_CHAR:
+	case TOKEN_UTF8_STRING:
 		token_type(left) = res;
 		left->pos.noexpand = 0;
 		left->string = right->string;
 		return 1;
 
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
 		token_type(left) = res;
 		left->pos.noexpand = 0;
 		memcpy(left->embedded, right->embedded, 4);
@@ -620,7 +630,9 @@ static struct token *dup_token(const struct token *token, struct position *strea
 	alloc->pos.stream = pos.stream;
 	alloc->pos.line = pos.line;
 	alloc->pos.pos = pos.pos;
-	if (token_type(alloc) == TOKEN_STRING || token_type(alloc) == TOKEN_WIDE_STRING)
+	if (token_type(alloc) == TOKEN_STRING ||
+	    token_type(alloc) == TOKEN_WIDE_STRING ||
+	    token_type(alloc) == TOKEN_UTF8_STRING)
 		token->string->immutable = 1;
 	return alloc;	
 }
@@ -2165,12 +2177,15 @@ static int token_different(struct token *t1, struct token *t2)
 		break;
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
 		different = memcmp(t1->embedded, t2->embedded, 4);
 		break;
 	case TOKEN_CHAR:
 	case TOKEN_WIDE_CHAR:
+	case TOKEN_UTF8_CHAR:
 	case TOKEN_STRING:
-	case TOKEN_WIDE_STRING: {
+	case TOKEN_WIDE_STRING:
+	case TOKEN_UTF8_STRING: {
 		struct string *s1, *s2;
 
 		s1 = t1->string;
diff --git a/token.h b/token.h
index 8c8a496bee89..b421f166f1f7 100644
--- a/token.h
+++ b/token.h
@@ -94,8 +94,14 @@ enum token_type {
 	TOKEN_WIDE_CHAR_EMBEDDED_1,
 	TOKEN_WIDE_CHAR_EMBEDDED_2,
 	TOKEN_WIDE_CHAR_EMBEDDED_3,
+	TOKEN_UTF8_CHAR,
+	TOKEN_UTF8_CHAR_EMBEDDED_0,
+	TOKEN_UTF8_CHAR_EMBEDDED_1,
+	TOKEN_UTF8_CHAR_EMBEDDED_2,
+	TOKEN_UTF8_CHAR_EMBEDDED_3,
 	TOKEN_STRING,
 	TOKEN_WIDE_STRING,
+	TOKEN_UTF8_STRING,
 	TOKEN_SPECIAL,
 	TOKEN_STREAMBEGIN,
 	TOKEN_STREAMEND,
diff --git a/tokenize.c b/tokenize.c
index b0d4059c8782..4ac40e9e1506 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -152,12 +152,16 @@ const char *show_string(const struct string *string)
 	return buffer;
 }
 
-static const char *show_char(const char *s, size_t len, char prefix, char delim)
+static const char *show_char(const char *s, size_t len, const char *prefix,
+			     char delim)
 {
-	static char buffer[MAX_STRING + 4];
+	static char buffer[MAX_STRING + 8];
 	char *p = buffer;
-	if (prefix)
-		*p++ = prefix;
+	if (prefix) {
+		size_t plen = strlen(prefix);
+		memcpy(p, prefix, plen);
+		p += plen;
+	}
 	*p++ = delim;
 	memcpy(p, s, len);
 	p += len;
@@ -166,13 +170,17 @@ static const char *show_char(const char *s, size_t len, char prefix, char delim)
 	return buffer;
 }
 
-static const char *quote_char(const char *s, size_t len, char prefix, char delim)
+static const char *quote_char(const char *s, size_t len, const char *prefix,
+			      char delim)
 {
-	static char buffer[2*MAX_STRING + 6];
+	static char buffer[2 * MAX_STRING + 8];
 	size_t i;
 	char *p = buffer;
-	if (prefix)
-		*p++ = prefix;
+	if (prefix) {
+		size_t plen = strlen(prefix);
+		memcpy(p, prefix, plen);
+		p += plen;
+	}
 	if (delim == '"')
 		*p++ = '\\';
 	*p++ = delim;
@@ -211,24 +219,35 @@ const char *show_token(const struct token *token)
 	case TOKEN_SPECIAL:
 		return show_special(token->special);
 
-	case TOKEN_CHAR: 
-		return show_char(token->string->data,
-			token->string->length - 1, 0, '\'');
+	case TOKEN_CHAR:
+		return show_char(token->string->data, token->string->length - 1,
+				 NULL, '\'');
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 		return show_char(token->embedded,
-			token_type(token) - TOKEN_CHAR, 0, '\'');
-	case TOKEN_WIDE_CHAR: 
-		return show_char(token->string->data,
-			token->string->length - 1, 'L', '\'');
+				 token_type(token) - TOKEN_CHAR, NULL, '\'');
+	case TOKEN_WIDE_CHAR:
+		return show_char(token->string->data, token->string->length - 1,
+				 "L", '\'');
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
 		return show_char(token->embedded,
-			token_type(token) - TOKEN_WIDE_CHAR, 'L', '\'');
-	case TOKEN_STRING: 
-		return show_char(token->string->data,
-			token->string->length - 1, 0, '"');
-	case TOKEN_WIDE_STRING: 
-		return show_char(token->string->data,
-			token->string->length - 1, 'L', '"');
+				 token_type(token) - TOKEN_WIDE_CHAR, "L",
+				 '\'');
+	case TOKEN_UTF8_CHAR:
+		return show_char(token->string->data, token->string->length - 1,
+				 "u8", '\'');
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
+		return show_char(token->embedded,
+				 token_type(token) - TOKEN_UTF8_CHAR, "u8",
+				 '\'');
+	case TOKEN_STRING:
+		return show_char(token->string->data, token->string->length - 1,
+				 NULL, '"');
+	case TOKEN_WIDE_STRING:
+		return show_char(token->string->data, token->string->length - 1,
+				 "L", '"');
+	case TOKEN_UTF8_STRING:
+		return show_char(token->string->data, token->string->length - 1,
+				 "u8", '"');
 
 	case TOKEN_STREAMBEGIN:
 		snprintf(buffer, sizeof(buffer), "<beginning of '%s'>", stream_name(token->pos.stream));
@@ -273,24 +292,35 @@ const char *quote_token(const struct token *token)
 	case TOKEN_SPECIAL:
 		return show_special(token->special);
 
-	case TOKEN_CHAR: 
+	case TOKEN_CHAR:
 		return quote_char(token->string->data,
-			token->string->length - 1, 0, '\'');
+				  token->string->length - 1, NULL, '\'');
 	case TOKEN_CHAR_EMBEDDED_0 ... TOKEN_CHAR_EMBEDDED_3:
 		return quote_char(token->embedded,
-			token_type(token) - TOKEN_CHAR, 0, '\'');
-	case TOKEN_WIDE_CHAR: 
+				  token_type(token) - TOKEN_CHAR, NULL, '\'');
+	case TOKEN_WIDE_CHAR:
 		return quote_char(token->string->data,
-			token->string->length - 1, 'L', '\'');
+				  token->string->length - 1, "L", '\'');
 	case TOKEN_WIDE_CHAR_EMBEDDED_0 ... TOKEN_WIDE_CHAR_EMBEDDED_3:
 		return quote_char(token->embedded,
-			token_type(token) - TOKEN_WIDE_CHAR, 'L', '\'');
-	case TOKEN_STRING: 
+				  token_type(token) - TOKEN_WIDE_CHAR, "L",
+				  '\'');
+	case TOKEN_UTF8_CHAR:
+		return quote_char(token->string->data,
+				  token->string->length - 1, "u8", '\'');
+	case TOKEN_UTF8_CHAR_EMBEDDED_0 ... TOKEN_UTF8_CHAR_EMBEDDED_3:
+		return quote_char(token->embedded,
+				  token_type(token) - TOKEN_UTF8_CHAR, "u8",
+				  '\'');
+	case TOKEN_STRING:
+		return quote_char(token->string->data,
+				  token->string->length - 1, NULL, '"');
+	case TOKEN_WIDE_STRING:
 		return quote_char(token->string->data,
-			token->string->length - 1, 0, '"');
-	case TOKEN_WIDE_STRING: 
+				  token->string->length - 1, "L", '"');
+	case TOKEN_UTF8_STRING:
 		return quote_char(token->string->data,
-			token->string->length - 1, 'L', '"');
+				  token->string->length - 1, "u8", '"');
 	case TOKEN_GNU_KLUDGE:
 		return ",";
 	default:
@@ -979,6 +1009,14 @@ static int get_one_identifier(int c, stream_t *stream)
 				return eat_string(nextchar(stream), stream,
 							TOKEN_WIDE_STRING);
 		}
+		if (len == 2 && buf[0] == 'u' && buf[1] == '8') {
+			if (next == '\'')
+				return eat_string(nextchar(stream), stream,
+						  TOKEN_UTF8_CHAR);
+			else
+				return eat_string(nextchar(stream), stream,
+						  TOKEN_UTF8_STRING);
+		}
 	}
 	hash = ident_hash_end(hash);
 	ident = create_hashed_ident(buf, len, hash);
diff --git a/validation/c23-u8-char-error.c b/validation/c23-u8-char-error.c
new file mode 100644
index 000000000000..da03b604e8cd
--- /dev/null
+++ b/validation/c23-u8-char-error.c
@@ -0,0 +1,16 @@
+static unsigned char e1 = u8'';
+static unsigned char e2 = u8'ab';
+static unsigned char e3 = u8'\u00e9';
+static const void *e4 = u8"a" L"b";
+
+/*
+ * check-name: c23-u8-char-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-u8-char-error.c:1:27: error: empty character constant
+c23-u8-char-error.c:2:27: error: multi-character UTF-8 character constant
+c23-u8-char-error.c:3:27: error: multi-character UTF-8 character constant
+c23-u8-char-error.c:4:25: error: unsupported non-standard concatenation of string literals
+ * check-error-end
+ */
diff --git a/validation/c23-u8-char-pedantic.c b/validation/c23-u8-char-pedantic.c
new file mode 100644
index 000000000000..8c2fbbaeecf6
--- /dev/null
+++ b/validation/c23-u8-char-pedantic.c
@@ -0,0 +1,10 @@
+static int x = u8'a';
+
+/*
+ * check-name: c23-u8-char-pedantic
+ * check-command: sparse -std=c11 -Wpedantic $file
+ *
+ * check-error-start
+c23-u8-char-pedantic.c:1:16: warning: UTF-8 character constants are a C23 feature
+ * check-error-end
+ */
diff --git a/validation/c23-u8-char-warn.c b/validation/c23-u8-char-warn.c
new file mode 100644
index 000000000000..2ccf459e01f3
--- /dev/null
+++ b/validation/c23-u8-char-warn.c
@@ -0,0 +1,14 @@
+static unsigned char w1 = u8'\x100';
+static const char *w2 = u8"test";
+
+/*
+ * check-name: c23-u8-char-warn
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-u8-char-warn.c:1:27: warning: hex escape sequence out of range
+c23-u8-char-warn.c:2:25: warning: incorrect type in initializer (different signedness)
+c23-u8-char-warn.c:2:25:    expected char const *static [toplevel] w2
+c23-u8-char-warn.c:2:25:    got unsigned char *
+ * check-error-end
+ */
diff --git a/validation/c23-u8-char.c b/validation/c23-u8-char.c
new file mode 100644
index 000000000000..60e0c2a02e36
--- /dev/null
+++ b/validation/c23-u8-char.c
@@ -0,0 +1,37 @@
+static_assert(sizeof(u8'a') == 1, "sizeof u8 char");
+static_assert(u8'a' == 97, "value u8 char");
+static_assert(u8'\0' == 0, "u8 nul");
+static_assert(u8'\xff' == 255, "u8 0xff is unsigned");
+static_assert(u8'\u0041' == 65, "u8 UCN ASCII");
+static_assert(_Generic(u8'a', unsigned char: 1, char: 0, int: 0, default: 0) == 1,
+	      "type u8 char");
+
+static_assert(sizeof(u8"hello") == 6, "sizeof u8 string");
+static_assert(sizeof(u8"\u00e9") == 3, "sizeof 2-byte UTF-8 UCN string");
+static_assert(sizeof(u8"\U0001F600") == 5, "sizeof 4-byte UTF-8 UCN string");
+static_assert(sizeof(u8"ab" "cd") == 5, "concat u8 + narrow");
+static_assert(sizeof("ab" u8"cd") == 5, "concat narrow + u8");
+static_assert(_Generic(u8"hello", unsigned char *: 1, char *: 0, default: 0) == 1,
+	      "u8 string decays to unsigned char *");
+static_assert(_Generic("ab" u8"cd", unsigned char *: 1, char *: 0, default: 0) == 1,
+	      "narrow + u8 string decays to unsigned char *");
+
+static const unsigned char c = u8'z';
+static const unsigned char *s = u8"test";
+static char s1[] = u8"abc";
+static unsigned char s2[] = u8"abc";
+static signed char s3[] = u8"abc";
+
+#define CAT(a, b) a##b
+static const unsigned char c2 = CAT(u8, 'x');
+static const unsigned char *s_cat = CAT(u8, "yz");
+
+static int use_vars(void)
+{
+	return c + s[0] + s1[0] + s2[0] + s3[0] + c2 + s_cat[0];
+}
+
+/*
+ * check-name: c23-u8-char
+ * check-command: sparse -std=c23 -pedantic $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 58/73] preprocessor: Add support for __VA_OPT__
  2026-11-09 20:31 Bart Van Assche
                   ` (56 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
                   ` (14 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add support for the __VA_OPT__ functional macro replacement operator
introduced in C23. Expand the contents of __VA_OPT__(...) if and only if
variable arguments are passed to the variadic macro, eliminating trailing
delimiter issues in variadic macro expansion portably.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ident-list.h                               |   1 +
 pre-process.c                              | 331 ++++++++++++++++++---
 token.h                                    |   3 +
 tokenize.c                                 |  10 +
 validation/c23-va-opt.c                    |  36 +++
 validation/preprocessor/c23-va-opt-error.c |  31 ++
 validation/preprocessor/c23-va-opt.c       |  73 +++++
 7 files changed, 446 insertions(+), 39 deletions(-)
 create mode 100644 validation/c23-va-opt.c
 create mode 100644 validation/preprocessor/c23-va-opt-error.c
 create mode 100644 validation/preprocessor/c23-va-opt.c

diff --git a/ident-list.h b/ident-list.h
index 3340f11641fa..b4e01e0a423c 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -80,6 +80,7 @@ IDENT(c_generic_selections);
 IDENT(c_static_assert);
 __IDENT(pragma_ident, "__pragma__", 0);
 __IDENT(__VA_ARGS___ident, "__VA_ARGS__", 0);
+__IDENT(__VA_OPT___ident, "__VA_OPT__", 0);
 __IDENT(__func___ident, "__func__", 0);
 __IDENT(__FUNCTION___ident, "__FUNCTION__", 0);
 __IDENT(__PRETTY_FUNCTION___ident, "__PRETTY_FUNCTION__", 0);
diff --git a/pre-process.c b/pre-process.c
index 9ebf09cffbdd..8eb70ca7a1a4 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -640,7 +640,7 @@ static struct token *dup_token(const struct token *token, struct position *strea
 static struct token **copy(struct token **where, struct token *list, int *count)
 {
 	int need_copy = --*count;
-	while (!eof_token(list)) {
+	do {
 		struct token *token;
 		if (need_copy)
 			token = dup_token(list, &list->pos);
@@ -651,7 +651,7 @@ static struct token **copy(struct token **where, struct token *list, int *count)
 		*where = token;
 		where = &token->next;
 		list = list->next;
-	}
+	} while (!eof_token(list));
 	*where = &eof_token_entry;
 	return where;
 }
@@ -677,11 +677,26 @@ static int handle_kludge(const struct token **p, struct arg *args)
 	}
 }
 
-static struct token **substitute(struct token **list, const struct token *body, struct arg *args)
+static inline int arg_empty(struct arg *arg)
+{
+	struct token *t = arg->expanded;
+
+	if (!t)
+		return 1;
+	while (token_type(t) == TOKEN_UNTAINT)
+		t = t->next;
+	return eof_token(t);
+}
+
+static struct token **substitute_tokens(struct token **list,
+					struct position *base_pos,
+					const struct token *body,
+					struct arg *args, int vararg_num)
 {
-	struct position *base_pos = &(*list)->pos;
 	int *count;
+	int one;
 	enum {Normal, Placeholder, Concat} state = Normal;
+	int extra_whitespace = 0;
 
 	for (; !eof_token(body); body = body->next) {
 		struct token *added, *arg;
@@ -712,9 +727,57 @@ static struct token **substitute(struct token **list, const struct token *body,
 			tail = &added->next;
 			break;
 
+		case TOKEN_STR_VA_OPT: {
+			struct token *inner = &eof_token_entry;
+
+			if (vararg_num >= 0 && !arg_empty(&args[vararg_num]))
+				substitute_tokens(&inner, base_pos,
+						  body->target, args,
+						  vararg_num);
+			added = stringify(inner);
+			added->pos.stream = base_pos->stream;
+			added->pos.line = base_pos->line;
+			added->pos.pos = base_pos->pos;
+			added->pos.newline = body->pos.newline;
+			added->pos.whitespace =
+				body->pos.whitespace || extra_whitespace;
+			extra_whitespace = 0;
+			tail = &added->next;
+			break;
+		}
+
+		case TOKEN_VA_OPT: {
+			struct token *inner = &eof_token_entry;
+
+			if (vararg_num >= 0 && !arg_empty(&args[vararg_num]))
+				substitute_tokens(&inner, base_pos,
+						  body->target, args,
+						  vararg_num);
+			if (eof_token(inner)) {
+				if (body->pos.whitespace && state != Concat &&
+				    token_type(body->next) != TOKEN_CONCAT)
+					extra_whitespace = 1;
+				if (state == Concat)
+					state = Normal;
+				else if (token_type(body->next) == TOKEN_CONCAT)
+					state = Placeholder;
+				else
+					state = Normal;
+				continue;
+			}
+			arg = inner;
+			one = 1;
+			count = &one;
+			goto copy_arg;
+		}
+
 		case TOKEN_STR_ARGUMENT:
 			arg = args[body->argnum].str;
 			count = &args[body->argnum].n_str;
+			if (eof_token(arg)) {
+				state = Normal;
+				continue;
+			}
 			goto copy_arg;
 
 		case TOKEN_QUOTED_ARGUMENT:
@@ -740,6 +803,10 @@ static struct token **substitute(struct token **list, const struct token *body,
 			tail = copy(&added, arg, count);
 			added->pos.newline = body->pos.newline;
 			added->pos.whitespace = body->pos.whitespace;
+			if (extra_whitespace) {
+				added->pos.whitespace = 1;
+				extra_whitespace = 0;
+			}
 			break;
 
 		case TOKEN_CONCAT:
@@ -754,6 +821,10 @@ static struct token **substitute(struct token **list, const struct token *body,
 			if (token_type(body) == TOKEN_IDENT &&
 			    added->ident->tainted)
 				added->pos.noexpand = 1;
+			if (extra_whitespace) {
+				added->pos.whitespace = 1;
+				extra_whitespace = 0;
+			}
 			tail = &added->next;
 			break;
 		}
@@ -776,6 +847,14 @@ static struct token **substitute(struct token **list, const struct token *body,
 	return list;
 }
 
+static struct token **substitute(struct token **list, const struct token *body,
+				 struct arg *args, int vararg_num)
+{
+	struct position base_pos = (*list)->pos;
+
+	return substitute_tokens(list, &base_pos, body, args, vararg_num);
+}
+
 static bool parse_has_include(struct token **list, int how);
 static bool parse_has_embed(struct token **list);
 
@@ -811,8 +890,14 @@ static int expand(struct token **list, struct symbol *sym)
 	struct token **tail;
 	struct token *expansion = sym->expansion;
 	int nargs = sym->arglist ? sym->arglist->count.normal : 0;
+	int vararg_num = (sym->arglist && sym->arglist->count.vararg) ?
+				 (sym->arglist->count.normal - 1) :
+				 -1;
 	struct arg args[nargs ? nargs : 1];
 
+	if (nargs)
+		memset(args, 0, nargs * sizeof(args[0]));
+
 	if (expanding->tainted) {
 		token->pos.noexpand = 1;
 		return 1;
@@ -855,7 +940,7 @@ static int expand(struct token **list, struct symbol *sym)
 	expanding->tainted = 1;
 
 	last = token->next;
-	tail = substitute(list, expansion, args);
+	tail = substitute(list, expansion, args, vararg_num);
 	/*
 	 * Note that it won't be eof - at least TOKEN_UNTAINT will be there.
 	 * We still can lose the newline flag if the sucker expands to nothing,
@@ -2147,6 +2232,8 @@ static int handle_argv_include(struct stream *stream, struct token **list, struc
 	return handle_include_path(stream, list, token, 2);
 }
 
+static int token_list_different(struct token *list1, struct token *list2);
+
 static int token_different(struct token *t1, struct token *t2)
 {
 	int different;
@@ -2164,6 +2251,10 @@ static int token_different(struct token *t1, struct token *t2)
 	case TOKEN_GNU_KLUDGE:
 		different = 0;
 		break;
+	case TOKEN_VA_OPT:
+	case TOKEN_STR_VA_OPT:
+		different = token_list_different(t1->target, t2->target);
+		break;
 	case TOKEN_NUMBER:
 		different = strcmp(t1->number, t2->number);
 		break;
@@ -2240,6 +2331,8 @@ static struct token *parse_arguments(struct token *list)
 	while (token_type(arg) == TOKEN_IDENT) {
 		if (arg->ident == &__VA_ARGS___ident)
 			goto Eva_args;
+		if (arg->ident == &__VA_OPT___ident)
+			goto Eva_opt;
 		if (!++count->normal)
 			goto Eargs;
 		next = arg->next;
@@ -2263,6 +2356,7 @@ static struct token *parse_arguments(struct token *list)
 			if (match_op(next->next, ')')) {
 				set_arg_count(next);
 				next->count.vararg = 1;
+				count->vararg = 1;
 				next = next->next;
 				arg->next->next = &eof_token_entry;
 				return next->next;
@@ -2290,6 +2384,7 @@ static struct token *parse_arguments(struct token *list)
 			goto Eargs;
 		set_arg_count(next);
 		next->count.vararg = 1;
+		count->vararg = 1;
 		next = next->next;
 		arg->next->next = &eof_token_entry;
 		return next;
@@ -2315,6 +2410,10 @@ Ebadstuff:
 Enotclosed:
 	sparse_error(arg->pos, "missing ')' in macro parameter list");
 	return NULL;
+Eva_opt:
+	sparse_error(arg->pos,
+		     "__VA_OPT__ cannot appear in a macro parameter list");
+	return NULL;
 Eva_args:
 	sparse_error(arg->pos, "__VA_ARGS__ can only appear in the expansion of a C99 variadic macro");
 	return NULL;
@@ -2363,11 +2462,26 @@ static int try_arg(struct token *token, enum token_type type, struct token *argl
 	return 0;
 }
 
+static struct token *handle_va_opt(struct token **p, struct token *arglist);
+static int parse_expansion_tokens(struct token **expansion_p,
+				  struct token *arglist);
+
 static struct token *handle_hash(struct token **p, struct token *arglist)
 {
 	struct token *token = *p;
 	if (arglist) {
 		struct token *next = token->next;
+		if (token_type(next) == TOKEN_IDENT &&
+		    next->ident == &__VA_OPT___ident) {
+			next = handle_va_opt(&token->next, arglist);
+			if (!next)
+				return NULL;
+			token_type(next) = TOKEN_STR_VA_OPT;
+			next->pos.whitespace = token->pos.whitespace;
+			__free_token(token);
+			*p = next;
+			return next;
+		}
 		if (!try_arg(next, TOKEN_STR_ARGUMENT, arglist))
 			goto Equote;
 		next->pos.whitespace = token->pos.whitespace;
@@ -2412,6 +2526,11 @@ static struct token *handle_hashhash(struct token *token, struct token *arglist)
 			t = handle_hash(&concat->next, arglist);
 			if (!t)
 				return NULL;
+		} else if (token_type(t) == TOKEN_IDENT &&
+			   t->ident == &__VA_OPT___ident) {
+			t = handle_va_opt(&concat->next, arglist);
+			if (!t)
+				return NULL;
 		}
 
 		is_arg = try_arg(t, TOKEN_QUOTED_ARGUMENT, arglist);
@@ -2437,43 +2556,146 @@ Econcat:
 	return NULL;
 }
 
-static struct token *parse_expansion(struct token *expansion, struct token *arglist, struct ident *name)
+static struct token *handle_va_opt(struct token **p, struct token *arglist)
 {
-	struct token *token = expansion;
+	struct token *token = *p;
+	struct token *lparen = token->next;
+	struct token *cur, *prev, *body;
+	int nesting = 1;
+
+	if (!arglist || !arglist->count.vararg) {
+		sparse_error(
+			token->pos,
+			"__VA_OPT__ can only appear in the expansion of a variadic macro");
+		return NULL;
+	}
+	if (!match_op(lparen, '(')) {
+		sparse_error(token->pos, "expected '(' after __VA_OPT__");
+		return NULL;
+	}
+	if (standard < STANDARD_C23 && Wpedantic)
+		warning(token->pos, "__VA_OPT__ is a C23 feature");
+
+	for (cur = arglist->next; !eof_token(cur); cur = cur->next->next) {
+		if (cur->next->count.vararg) {
+			if (!++cur->next->count.normal) {
+				sparse_error(
+					token->pos,
+					"too many instances of argument in body");
+				return NULL;
+			}
+			break;
+		}
+	}
+
+	prev = lparen;
+	for (cur = lparen->next; !eof_token(cur); prev = cur, cur = cur->next) {
+		if (token_type(cur) == TOKEN_IDENT &&
+		    cur->ident == &__VA_OPT___ident) {
+			sparse_error(
+				cur->pos,
+				"__VA_OPT__ cannot appear within __VA_OPT__");
+			return NULL;
+		}
+		if (match_op(cur, '('))
+			nesting++;
+		else if (match_op(cur, ')')) {
+			if (!--nesting)
+				break;
+		}
+	}
+	if (nesting) {
+		sparse_error(token->pos, "unterminated __VA_OPT__");
+		return NULL;
+	}
+
+	/* cur is ')' */
+	if (lparen->next != cur && match_op(lparen->next, SPECIAL_HASHHASH)) {
+		sparse_error(
+			lparen->next->pos,
+			"'##' cannot appear at the beginning of __VA_OPT__");
+		return NULL;
+	}
+	if (prev != lparen && match_op(prev, SPECIAL_HASHHASH)) {
+		sparse_error(prev->pos,
+			     "'##' cannot appear at the end of __VA_OPT__");
+		return NULL;
+	}
+
+	if (lparen->next == cur) {
+		body = &eof_token_entry;
+	} else {
+		body = lparen->next;
+		prev->next = &eof_token_entry;
+		if (parse_expansion_tokens(&body, arglist) < 0)
+			return NULL;
+	}
+
+	token_type(token) = TOKEN_VA_OPT;
+	token->target = body;
+	token->next = cur->next;
+	__free_token(lparen);
+	__free_token(cur);
+	return token;
+}
+
+static int parse_expansion_tokens(struct token **expansion_p,
+				  struct token *arglist)
+{
+	struct token *token = *expansion_p;
 	struct token **p;
 
 	if (match_op(token, SPECIAL_HASHHASH))
 		goto Econcat;
 
-	for (p = &expansion; !eof_token(token); p = &token->next, token = *p) {
+	for (p = expansion_p; !eof_token(token); p = &token->next, token = *p) {
 		if (match_op(token, '#')) {
 			token = handle_hash(p, arglist);
 			if (!token)
-				return NULL;
+				return -1;
+		} else if (token_type(token) == TOKEN_IDENT &&
+			   token->ident == &__VA_OPT___ident) {
+			token = handle_va_opt(p, arglist);
+			if (!token)
+				return -1;
 		}
 		if (match_op(token->next, SPECIAL_HASHHASH)) {
 			token = handle_hashhash(token, arglist);
 			if (!token)
-				return NULL;
-		} else {
+				return -1;
+		} else if (token_type(token) == TOKEN_IDENT) {
 			try_arg(token, TOKEN_MACRO_ARGUMENT, arglist);
 		}
 		if (token_type(token) == TOKEN_ERROR)
 			goto Earg;
 	}
+	return 0;
+
+Econcat:
+	sparse_error(token->pos,
+		     "'##' cannot appear at the ends of macro expansion");
+	return -1;
+Earg:
+	sparse_error(token->pos, "too many instances of argument in body");
+	return -1;
+}
+
+static struct token *parse_expansion(struct token *expansion, struct token *arglist, struct ident *name)
+{
+	struct token *token;
+	struct token **p;
+
+	if (parse_expansion_tokens(&expansion, arglist) < 0)
+		return NULL;
+
+	for (p = &expansion; !eof_token(*p); p = &(*p)->next)
+		;
 	token = alloc_token(&expansion->pos);
 	token_type(token) = TOKEN_UNTAINT;
 	token->ident = name;
 	token->next = *p;
 	*p = token;
 	return expansion;
-
-Econcat:
-	sparse_error(token->pos, "'##' cannot appear at the ends of macro expansion");
-	return NULL;
-Earg:
-	sparse_error(token->pos, "too many instances of argument in body");
-	return NULL;
 }
 
 static int do_define(struct position pos, struct token *token, struct ident *name,
@@ -2482,6 +2704,12 @@ static int do_define(struct position pos, struct token *token, struct ident *nam
 	struct symbol *sym;
 	int ret = 1;
 
+	if (name == &__VA_ARGS___ident || name == &__VA_OPT___ident) {
+		sparse_error(pos, "\"%s\" cannot be used as a macro name",
+			     show_ident(name));
+		return 1;
+	}
+
 	expansion = parse_expansion(expansion, arglist, name);
 	if (!expansion)
 		return 1;
@@ -2656,6 +2884,13 @@ static int do_handle_undef(struct stream *stream, struct token **line, struct to
 		return 1;
 	}
 
+	if (left->ident == &__VA_ARGS___ident ||
+	    left->ident == &__VA_OPT___ident) {
+		warning(token->pos, "undefining \"%s\"",
+			show_ident(left->ident));
+		return 1;
+	}
+
 	sym = lookup_symbol(left->ident, NS_MACRO | NS_UNDEF);
 	if (sym) {
 		if (attr < sym->attr)
@@ -3597,6 +3832,44 @@ static int is_VA_ARGS_token(struct token *token)
 		(token->ident == &__VA_ARGS___ident);
 }
 
+static void dump_macro_tokens(struct token *token, struct token **args,
+			      bool top_level)
+{
+	bool first = !top_level;
+
+	while (top_level ? token_type(token) != TOKEN_UNTAINT :
+			   !eof_token(token)) {
+		struct token *next = token->next;
+
+		if (!first && token->pos.whitespace)
+			putchar(' ');
+		first = false;
+		switch (token_type(token)) {
+		case TOKEN_CONCAT:
+			printf("##");
+			break;
+		case TOKEN_STR_VA_OPT:
+			printf("#");
+			/* fall-through */
+		case TOKEN_VA_OPT:
+			printf("__VA_OPT__(");
+			dump_macro_tokens(token->target, args, false);
+			putchar(')');
+			break;
+		case TOKEN_STR_ARGUMENT:
+			printf("#");
+			/* fall-through */
+		case TOKEN_QUOTED_ARGUMENT:
+		case TOKEN_MACRO_ARGUMENT:
+			token = args[token->argnum];
+			/* fall-through */
+		default:
+			printf("%s", show_token(token));
+		}
+		token = next;
+	}
+}
+
 static void dump_macro(struct symbol *sym)
 {
 	int nargs = sym->arglist ? sym->arglist->count.normal : 0;
@@ -3622,27 +3895,7 @@ static void dump_macro(struct symbol *sym)
 		putchar(')');
 	}
 
-	token = sym->expansion;
-	while (token_type(token) != TOKEN_UNTAINT) {
-		struct token *next = token->next;
-		if (token->pos.whitespace)
-			putchar(' ');
-		switch (token_type(token)) {
-		case TOKEN_CONCAT:
-			printf("##");
-			break;
-		case TOKEN_STR_ARGUMENT:
-			printf("#");
-			/* fall-through */
-		case TOKEN_QUOTED_ARGUMENT:
-		case TOKEN_MACRO_ARGUMENT:
-			token = args[token->argnum];
-			/* fall-through */
-		default:
-			printf("%s", show_token(token));
-		}
-		token = next;
-	}
+	dump_macro_tokens(sym->expansion, args, true);
 	putchar('\n');
 }
 
diff --git a/token.h b/token.h
index b421f166f1f7..998d83a123c9 100644
--- a/token.h
+++ b/token.h
@@ -110,6 +110,8 @@ enum token_type {
 	TOKEN_QUOTED_ARGUMENT,
 	TOKEN_CONCAT,
 	TOKEN_GNU_KLUDGE,
+	TOKEN_VA_OPT,
+	TOKEN_STR_VA_OPT,
 	TOKEN_UNTAINT,
 	TOKEN_ARG_COUNT,
 	TOKEN_IF,
@@ -200,6 +202,7 @@ struct token {
 		int argnum;
 		struct argcount count;
 		char embedded[4];
+		struct token *target;
 	};
 };
 
diff --git a/tokenize.c b/tokenize.c
index 4ac40e9e1506..775c54667af7 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -265,6 +265,12 @@ const char *show_token(const struct token *token)
 		sprintf(buffer, "<argcnt>");
 		return buffer;
 
+	case TOKEN_VA_OPT:
+		return "__VA_OPT__(...)";
+
+	case TOKEN_STR_VA_OPT:
+		return "#__VA_OPT__(...)";
+
 	case TOKEN_GNU_KLUDGE:
 		return ",";
 
@@ -321,6 +327,10 @@ const char *quote_token(const struct token *token)
 	case TOKEN_UTF8_STRING:
 		return quote_char(token->string->data,
 				  token->string->length - 1, "u8", '"');
+	case TOKEN_VA_OPT:
+		return "__VA_OPT__(...)";
+	case TOKEN_STR_VA_OPT:
+		return "#__VA_OPT__(...)";
 	case TOKEN_GNU_KLUDGE:
 		return ",";
 	default:
diff --git a/validation/c23-va-opt.c b/validation/c23-va-opt.c
new file mode 100644
index 000000000000..bef475812fdd
--- /dev/null
+++ b/validation/c23-va-opt.c
@@ -0,0 +1,36 @@
+#define EMPTY
+#define COUNT_VA(...) 0 __VA_OPT__(+1)
+static_assert(COUNT_VA() == 0, "empty");
+static_assert(COUNT_VA(EMPTY) == 0, "empty macro");
+static_assert(COUNT_VA(x) == 1, "one");
+static_assert(COUNT_VA(x, y) == 1, "two");
+
+#define ADD_OPT(x, ...) (x __VA_OPT__(+__VA_ARGS__))
+static_assert(ADD_OPT(10) == 10, "add 1");
+static_assert(ADD_OPT(10, 20) == 30, "add 2");
+
+#define STR_OPT(...) #__VA_OPT__(__VA_ARGS__)
+static_assert(sizeof(STR_OPT()) == 1, "str empty");
+static_assert(sizeof(STR_OPT(EMPTY)) == 1, "str empty macro");
+static_assert(sizeof(STR_OPT(abc)) == 4, "str abc");
+
+#define PASTE_OPT(p, ...) p ## __VA_OPT__(_ok)
+enum { val = 10, val_ok = 20 };
+static_assert(PASTE_OPT(val) == 10, "paste empty");
+static_assert(PASTE_OPT(val, 1) == 20, "paste non-empty");
+
+static int foo(int a, ...)
+{
+	return a;
+}
+
+#define CALL_FOO(...) foo(0 __VA_OPT__(, __VA_ARGS__))
+static int test(void)
+{
+	return CALL_FOO() + CALL_FOO(1) + CALL_FOO(1, 2);
+}
+
+/*
+ * check-name: c23-va-opt-compile
+ * check-command: sparse -std=c23 -Wno-decl $file
+ */
diff --git a/validation/preprocessor/c23-va-opt-error.c b/validation/preprocessor/c23-va-opt-error.c
new file mode 100644
index 000000000000..c494bc6f2a16
--- /dev/null
+++ b/validation/preprocessor/c23-va-opt-error.c
@@ -0,0 +1,31 @@
+#define BAD0 __VA_OPT__(1)
+#define BAD1(x) __VA_OPT__(x)
+#define BAD2(...) __VA_OPT__
+#define BAD3(...) __VA_OPT__(1
+#define BAD4(...) __VA_OPT__(__VA_OPT__(1))
+#define BAD5(...) __VA_OPT__(##x)
+#define BAD6(...) __VA_OPT__(x##)
+#define BAD7(__VA_OPT__) 1
+#define __VA_OPT__ 1
+
+/*
+ * check-name: c23-va-opt-error
+ * check-command: sparse -std=c23 -E $file
+ *
+ * check-output-start
+
+
+ * check-output-end
+ *
+ * check-error-start
+preprocessor/c23-va-opt-error.c:1:14: error: __VA_OPT__ can only appear in the expansion of a variadic macro
+preprocessor/c23-va-opt-error.c:2:17: error: __VA_OPT__ can only appear in the expansion of a variadic macro
+preprocessor/c23-va-opt-error.c:3:19: error: expected '(' after __VA_OPT__
+preprocessor/c23-va-opt-error.c:4:19: error: unterminated __VA_OPT__
+preprocessor/c23-va-opt-error.c:5:30: error: __VA_OPT__ cannot appear within __VA_OPT__
+preprocessor/c23-va-opt-error.c:6:30: error: '##' cannot appear at the beginning of __VA_OPT__
+preprocessor/c23-va-opt-error.c:7:31: error: '##' cannot appear at the end of __VA_OPT__
+preprocessor/c23-va-opt-error.c:8:14: error: __VA_OPT__ cannot appear in a macro parameter list
+preprocessor/c23-va-opt-error.c:9:9: error: "__VA_OPT__" cannot be used as a macro name
+ * check-error-end
+ */
diff --git a/validation/preprocessor/c23-va-opt.c b/validation/preprocessor/c23-va-opt.c
new file mode 100644
index 000000000000..b2318faf298f
--- /dev/null
+++ b/validation/preprocessor/c23-va-opt.c
@@ -0,0 +1,73 @@
+#define LOG(fmt, ...) printf(fmt __VA_OPT__(, __VA_ARGS__))
+LOG("empty")
+LOG("empty", )
+LOG("one", 1)
+LOG("two", 1, 2)
+
+#define M(...) __VA_OPT__(1) 2 __VA_OPT__(3)
+M()
+M(a)
+
+#define N(...) __VA_OPT__(foo(bar(baz)))
+N()
+N(1)
+
+#define G(a, b, ...) a b __VA_OPT__(c)
+G(1, 2)
+G(1, 2, )
+G(1, 2, 3)
+
+#define EMPTY
+#define VA_ORDER1(...) __VA_ARGS__ __VA_OPT__(opt)
+#define VA_ORDER2(...) __VA_OPT__(opt) __VA_ARGS__
+VA_ORDER1()
+VA_ORDER2()
+VA_ORDER1(EMPTY)
+VA_ORDER2(EMPTY)
+
+#define K 42
+#define X hello
+#define S(a, ...) #__VA_OPT__(a K __VA_ARGS__)
+S(X)
+S(X, Y)
+S(X, K)
+
+#define PASTE_L(x, ...) x ## __VA_OPT__(_suffix)
+#define PASTE_R(y, ...) __VA_OPT__(prefix_) ## y
+PASTE_L(foo)
+PASTE_L(foo, 1)
+PASTE_R(bar)
+PASTE_R(bar, 1)
+
+#define FN(v) ((v) + 1)
+#define CALL(...) __VA_OPT__(FN)(10)
+CALL()
+CALL(1)
+
+/*
+ * check-name: c23-va-opt
+ * check-command: sparse -std=c23 -E $file
+ *
+ * check-output-start
+
+printf("empty" )
+printf("empty" )
+printf("one" , 1)
+printf("two" , 1, 2)
+2
+1 2 3
+foo(bar(baz))
+1 2
+1 2
+1 2 c
+""
+"hello K Y"
+"hello K 42"
+foo
+foo_suffix
+bar
+prefix_bar
+(10)
+((10) + 1)
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions
  2026-11-09 20:31 Bart Van Assche
                   ` (57 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
                   ` (13 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Treat an empty parameter list '()' in a function declarator as '(void)'
and reject K&R identifier-list function declarations and definitions in
C23 mode per ISO C23 6.7.6.3 and 6.9.1:
- Parse '()' as a zero-parameter prototype in C23 mode so calls with
  arguments and conflicting redeclarations are diagnosed.
- Track identifier-list declarators via kr_args on SYM_FN and report an
  error in C23 mode on K&R function definitions (with or without
  parameter declarations) and K&R function or function-pointer
  declarations.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 parse.c                             | 34 ++++++++++++++++++---
 symbol.h                            |  1 +
 validation/c23-empty-parameters.c   | 29 ++++++++++++++++++
 validation/c23-empty-params-error.c | 47 +++++++++++++++++++++++++++++
 validation/c23-knr-error.c          | 14 +++++++++
 5 files changed, 120 insertions(+), 5 deletions(-)
 create mode 100644 validation/c23-empty-parameters.c
 create mode 100644 validation/c23-empty-params-error.c
 create mode 100644 validation/c23-knr-error.c

diff --git a/parse.c b/parse.c
index f49b3f188c69..782683a84fba 100644
--- a/parse.c
+++ b/parse.c
@@ -2241,9 +2241,14 @@ static enum kind which_func(struct token *token,
 		if (lookup_type(next) || next != token->next)
 			return Proto;
 		/* identifier list not in definition; complain */
-		if (prefer_abstract)
-			warning(token->pos,
-				"identifier list not in definition");
+		if (prefer_abstract || !n) {
+			if (standard >= STANDARD_C23)
+				sparse_error(token->pos,
+					     "identifier list not in definition");
+			else
+				warning(token->pos,
+					"identifier list not in definition");
+		}
 		return K_R;
 	}
 
@@ -2251,6 +2256,8 @@ static enum kind which_func(struct token *token,
 		return Bad_Func;
 
 	if (next->special == ')') {
+		if (standard >= STANDARD_C23)
+			return Proto;
 		/* don't complain about those */
 		if (!n || match_op(next->next, ';') || match_op(next->next, ','))
 			return Empty;
@@ -3284,6 +3291,7 @@ static struct token * statement_list(struct token *token, struct statement_list
 static struct token *identifier_list(struct token *token, struct symbol *fn)
 {
 	struct symbol_list **list = &fn->arguments;
+	fn->kr_args = 1;
 	for (;;) {
 		struct symbol *sym = alloc_symbol(token->pos, SYM_NODE);
 		sym->ident = token->ident;
@@ -3304,6 +3312,9 @@ static struct token *parameter_type_list(struct token *token, struct symbol *fn)
 {
 	struct symbol_list **list = &fn->arguments;
 
+	if (match_op(token, ')'))
+		return token;
+
 	for (;;) {
 		struct symbol *sym;
 
@@ -3602,7 +3613,10 @@ static struct token *parse_k_r_arguments(struct token *token, struct symbol *dec
 {
 	struct symbol_list *args = NULL;
 
-	if (Wold_style_definition)
+	if (standard >= STANDARD_C23)
+		sparse_error(token->pos,
+			     "old-style function definition is invalid in C23");
+	else if (Wold_style_definition)
 		warning(token->pos, "non-ANSI definition of function '%s'", show_ident(decl->ident));
 
 	do {
@@ -3772,8 +3786,15 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		}
 		if (lookup_type(token))
 			return parse_k_r_arguments(token, decl, list);
-		if (match_op(token, '{'))
+		if (match_op(token, '{')) {
+			if (standard >= STANDARD_C23 && base_type->kr_args)
+				sparse_error(decl->pos,
+					     "old-style function definition is invalid in C23");
 			return parse_function_body(token, decl, list);
+		}
+		if (standard >= STANDARD_C23 && base_type->kr_args)
+			sparse_error(decl->pos,
+				     "identifier list not in function definition");
 
 		if (!(decl->ctype.modifiers & MOD_STATIC))
 			decl->ctype.modifiers |= MOD_EXTERN;
@@ -3977,6 +3998,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		base_type = decl->ctype.base_type;
 		if (!is_typedef && base_type && base_type->type == SYM_FN) {
 			decl->ctype.modifiers |= ctx.f_modifiers;
+			if (standard >= STANDARD_C23 && base_type->kr_args)
+				sparse_error(decl->pos,
+					     "identifier list not in function definition");
 			if (!(decl->ctype.modifiers & MOD_STATIC))
 				decl->ctype.modifiers |= MOD_EXTERN;
 		}
diff --git a/symbol.h b/symbol.h
index a072ac8fc739..942baad97df5 100644
--- a/symbol.h
+++ b/symbol.h
@@ -198,6 +198,7 @@ struct symbol {
 				packed : 1, transparent_union : 1,
 				fixed_type : 1;
 			unsigned int has_alignas : 1;
+			unsigned int kr_args : 1;
 			unsigned int underspecified : 1;
 			unsigned int c23_auto : 1;
 			int		rank:3;	// arithmetic's rank
diff --git a/validation/c23-empty-parameters.c b/validation/c23-empty-parameters.c
new file mode 100644
index 000000000000..9c493256d54b
--- /dev/null
+++ b/validation/c23-empty-parameters.c
@@ -0,0 +1,29 @@
+int f();
+int f(void)
+{
+	return 0;
+}
+
+int g(void);
+int g()
+{
+	return 1;
+}
+
+static int h()
+{
+	return 2;
+}
+
+static int test(void)
+{
+	int (*fp)() = f;
+	int (*fp2)(void) = h;
+
+	return fp() + fp2() + g();
+}
+
+/*
+ * check-name: c23-empty-parameters
+ * check-command: sparse -std=c23 -Wstrict-prototypes -pedantic $file
+ */
diff --git a/validation/c23-empty-params-error.c b/validation/c23-empty-params-error.c
new file mode 100644
index 000000000000..4c0efe972358
--- /dev/null
+++ b/validation/c23-empty-params-error.c
@@ -0,0 +1,47 @@
+int f();
+
+static int h()
+{
+	return 0;
+}
+
+int mismatch();
+int mismatch(int x)
+{
+	return x;
+}
+
+int ident_decl(a, b);
+int (*bad_fp)(a, b);
+
+int old_implicit(a, b)
+{
+	return 0;
+}
+
+static void bad_calls(int (*fp)())
+{
+	f(1);
+	h(1, 2);
+	fp(3);
+}
+
+/*
+ * check-name: c23-empty-params-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-empty-params-error.c:14:5: error: identifier list not in function definition
+c23-empty-params-error.c:15:14: error: identifier list not in definition
+c23-empty-params-error.c:17:5: error: old-style function definition is invalid in C23
+c23-empty-params-error.c:17:18: error: missing type declaration for parameter 'a'
+c23-empty-params-error.c:17:21: error: missing type declaration for parameter 'b'
+c23-empty-params-error.c:9:5: error: symbol 'mismatch' redeclared with different type (different argument counts):
+c23-empty-params-error.c:9:5:    int extern [addressable] [signed] [toplevel] mismatch( ... )
+c23-empty-params-error.c:8:5: note: previously declared as:
+c23-empty-params-error.c:8:5:    int extern [addressable] [signed] [toplevel] mismatch( ... )
+c23-empty-params-error.c:24:10: error: too many arguments for function f
+c23-empty-params-error.c:25:10: error: too many arguments for function h
+c23-empty-params-error.c:26:11: error: too many arguments for function fp
+ * check-error-end
+ */
diff --git a/validation/c23-knr-error.c b/validation/c23-knr-error.c
new file mode 100644
index 000000000000..f7ae837815b5
--- /dev/null
+++ b/validation/c23-knr-error.c
@@ -0,0 +1,14 @@
+int old_func(a, b)
+int a, b;
+{
+	return a + b;
+}
+
+/*
+ * check-name: c23-knr-error
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-knr-error.c:2:1: error: old-style function definition is invalid in C23
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 60/73] c23: Support storage-class specifiers in compound literals
  2026-11-09 20:31 Bart Van Assche
                   ` (58 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
                   ` (12 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support C23 storage-class specifiers (constexpr, static, register, and
thread_local) in compound literals per ISO C23 6.5.2.5:
- Allow static, register, constexpr, and static thread_local compound
  literals while rejecting extern, typedef, auto, file-scope register,
  and un-static block-scope thread_local compound literals.
- Keep block-scope constexpr compound literals at automatic storage
  duration unless static is also specified, enforce constant initializers
  on static and thread_local objects in C23 mode, and warn when taking
  the address or decaying an array of a register compound literal.
- Emit anonymous static and thread_local compound literal globals and
  their constant scalar or array initializers in sparse-llvm, and warn
  under -Wpedantic before C23.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                                    | 28 +++++++--
 expand.c                                      |  5 +-
 expression.c                                  | 61 ++++++++++++++++---
 expression.h                                  |  1 +
 parse.c                                       | 59 +++++++++++++-----
 sparse-llvm.c                                 | 10 ++-
 symbol.h                                      |  4 +-
 .../backend/c23-compound-literal-storage.c    | 27 ++++++++
 .../c23-compound-literal-register-addr.c      | 17 ++++++
 .../c23-compound-literal-storage-error.c      | 37 +++++++++++
 .../c23-compound-literal-storage-pedantic.c   | 10 +++
 validation/c23-compound-literal-storage.c     | 27 ++++++++
 12 files changed, 252 insertions(+), 34 deletions(-)
 create mode 100644 validation/backend/c23-compound-literal-storage.c
 create mode 100644 validation/c23-compound-literal-register-addr.c
 create mode 100644 validation/c23-compound-literal-storage-error.c
 create mode 100644 validation/c23-compound-literal-storage-pedantic.c
 create mode 100644 validation/c23-compound-literal-storage.c

diff --git a/evaluate.c b/evaluate.c
index e46b1a7d4f70..32621d04a17e 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -1918,7 +1918,10 @@ static struct symbol *create_pointer(struct expression *expr, struct symbol *sym
 
 	access_symbol(sym);
 	if (sym->ctype.modifiers & MOD_REGISTER) {
-		warning(expr->pos, "taking address of 'register' variable '%s'", show_ident(sym->ident));
+		if (sym->ident)
+			warning(expr->pos, "taking address of 'register' variable '%s'", show_ident(sym->ident));
+		else
+			warning(expr->pos, "taking address of 'register' compound literal");
 		sym->ctype.modifiers &= ~MOD_REGISTER;
 	}
 	if (sym->type == SYM_NODE) {
@@ -2944,7 +2947,7 @@ found:
 		else
 			v = &top->ident_expression;
 
-		mods |= ctype->ctype.modifiers & (MOD_STORAGE | MOD_CONSTEXPR);
+		mods |= ctype->ctype.modifiers & (MOD_STORAGE | MOD_TLS | MOD_CONSTEXPR);
 		if (handle_initializer(v, 1, lclass, top->ctype, mods))
 			continue;
 
@@ -3236,10 +3239,14 @@ static int handle_initializer(struct expression **ep, int nested,
 		mods |= ctype->ctype.modifiers;
 		if (mods & MOD_CONSTEXPR)
 			check_constexpr_scalar_init(&orig, ctype);
-		if ((mods & (MOD_TOPLEVEL | MOD_STATIC)) &&
-		    !(e->flags & (CEF_ACE | CEF_ADDR)))
-			if (Wconstexpr_not_const)
+		else if ((mods & (MOD_TOPLEVEL | MOD_STATIC | MOD_TLS)) &&
+			 valid_type(ctype->ctype.base_type) &&
+			 !(e->flags & (CEF_ACE | CEF_ADDR))) {
+			if (standard >= STANDARD_C23)
+				sparse_error(e->pos, "non-constant initializer for static object");
+			else if (Wconstexpr_not_const)
 				warning(e->pos, "non-constant initializer for static object");
+		}
 
 		return 1;
 	}
@@ -3443,13 +3450,22 @@ static struct symbol *evaluate_compound_literal(struct expression *expr, struct
 
 	addr->ctype = &lazy_ptr_ctype;	/* Lazy eval */
 	addr->symbol = sym;
-	if (sym->ctype.modifiers & MOD_TOPLEVEL)
+	if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_STATIC | MOD_TLS))
 		addr->flags |= CEF_ADDR;
 
 	expr->type = EXPR_PREOP;
 	expr->op = '*';
 	expr->deref = addr;
 	expr->ctype = sym;
+	if (sym->ctype.modifiers & MOD_CONSTEXPR) {
+		struct symbol *base_type = get_base_type(sym);
+		if (is_float_type(base_type))
+			expr->flags = CEF_SET_FLOAT;
+		else if (is_int_type(base_type))
+			expr->flags = CEF_SET_ICE;
+		else
+			expr->flags = CEF_ADDR;
+	}
 	return sym;
 }
 
diff --git a/expand.c b/expand.c
index 13a3bc0457ba..7c85b66ab02f 100644
--- a/expand.c
+++ b/expand.c
@@ -848,9 +848,8 @@ static int expand_dereference(struct expression *expr)
 				expr->type = EXPR_VALUE;
 				expr->value = value->value;
 				expr->taint = 0;
-				if ((sym->ctype.modifiers & MOD_CONSTEXPR) &&
-				    is_int_type(ctype))
-					expr->flags = CEF_SET_ICE;
+				if (sym->ctype.modifiers & MOD_CONSTEXPR)
+					expr->flags = is_int_type(ctype) ? CEF_SET_ICE : CEF_ADDR;
 				return 0;
 			} else if (value->type == EXPR_FVALUE) {
 				if (!is_float_type(ctype))
diff --git a/expression.c b/expression.c
index ace0bf6d1a31..65efff3b61fe 100644
--- a/expression.c
+++ b/expression.c
@@ -150,6 +150,7 @@ static struct token *parse_type(struct token *token, struct expression **tree)
 	struct symbol *sym;
 	*tree = alloc_expression(token->pos, EXPR_TYPE);
 	token = typename(token, &sym, NULL);
+	check_typename_storage(sym);
 	if (sym->ident)
 		sparse_error(token->pos,
 			     "type expression should not include identifier "
@@ -198,6 +199,7 @@ static struct token *builtin_offsetof_expr(struct token *token,
 
 	token = token->next;
 	token = typename(token, &sym, NULL);
+	check_typename_storage(sym);
 	if (sym->ident)
 		sparse_error(token->pos,
 			     "type expression should not include identifier "
@@ -569,6 +571,7 @@ static struct token *generic_selection(struct token *token, struct expression **
 		if (lookup_type(token)) {
 			struct type_expression *map = __alloc_type_expression(0);
 			token = typename(token, &map->type, NULL);
+			check_typename_storage(map->type);
 			token = expect(token, ':', "after typename");
 			token = assignment_expression(token, &map->expr);
 			if (!map->expr)
@@ -730,6 +733,7 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 		if (token->special == '[' && lookup_type(token->next)) {
 			expr = alloc_expression(token->pos, EXPR_TYPE);
 			token = typename(token->next, &expr->symbol, NULL);
+			check_typename_storage(expr->symbol);
 			token = expect(token, ']', "in type expression");
 			break;
 		}
@@ -837,6 +841,46 @@ static struct token *postfix_expression(struct token *token, struct expression *
 static struct token *cast_expression(struct token *token, struct expression **tree);
 static struct token *unary_expression(struct token *token, struct expression **tree);
 
+static void check_compound_literal_storage(struct symbol *sym, int is_force)
+{
+	unsigned long mods;
+
+	if (toplevel(block_scope))
+		sym->ctype.modifiers |= MOD_TOPLEVEL;
+	if (is_force)
+		warning(sym->pos, "[force] in compound literal");
+	mods = sym->ctype.modifiers;
+	if (mods & MOD_EXTERN) {
+		sparse_error(sym->pos, "'extern' is not allowed in compound literal");
+		return;
+	}
+	if (sym->explicit_typedef) {
+		sparse_error(sym->pos, "'typedef' is not allowed in compound literal");
+		return;
+	}
+	if ((mods & MOD_AUTO) || sym->ctype.base_type == &autotype_ctype) {
+		sparse_error(sym->pos, "'auto' is not allowed in compound literal");
+		if (sym->ctype.base_type == &autotype_ctype)
+			sym->ctype.base_type = &int_ctype;
+		return;
+	}
+	if ((mods & MOD_TOPLEVEL) && (mods & MOD_REGISTER)) {
+		sparse_error(sym->pos,
+			     "file-scope compound literal cannot specify 'register'");
+		return;
+	}
+	if (!(mods & MOD_TOPLEVEL) && (mods & MOD_TLS) && !(mods & MOD_STATIC)) {
+		sparse_error(sym->pos,
+			     "block-scope compound literal with 'thread_local' requires 'static'");
+		return;
+	}
+	if (mods & (MOD_STATIC | MOD_REGISTER | MOD_TLS | MOD_CONSTEXPR)) {
+		if (standard < STANDARD_C23 && Wpedantic)
+			warning(sym->pos,
+				"storage-class specifiers in compound literals are a C23 feature");
+	}
+}
+
 static struct token *type_info_expression(struct token *token,
 	struct expression **tree, int type)
 {
@@ -845,6 +889,7 @@ static struct token *type_info_expression(struct token *token,
 	bool std_alignof = (op_ident == &_Alignof_ident ||
 			    op_ident == &alignof_ident);
 	struct token *p;
+	int is_force = 0;
 
 	*tree = expr;
 	expr->flags = CEF_SET_ICE; /* XXX: VLA support will need that changed */
@@ -857,7 +902,7 @@ static struct token *type_info_expression(struct token *token,
 		return unary_expression(token, &expr->cast_expression);
 	}
 	p = token;
-	token = typename(token->next, &expr->cast_type, NULL);
+	token = typename(token->next, &expr->cast_type, &is_force);
 
 	if (!match_op(token, ')')) {
 		static const char * error[] = {
@@ -865,6 +910,7 @@ static struct token *type_info_expression(struct token *token,
 			[EXPR_ALIGNOF] = "at end of __alignof__",
 			[EXPR_PTRSIZEOF] = "at end of __sizeof_ptr__"
 		};
+		check_typename_storage(expr->cast_type);
 		return expect(token, ')', error[type]);
 	}
 
@@ -874,16 +920,20 @@ static struct token *type_info_expression(struct token *token,
 	 * of a typed initializer expression..
 	 */
 	if (match_op(token, '{')) {
+		struct symbol *sym = expr->cast_type;
 		struct expression *cast = alloc_expression(p->pos, EXPR_CAST);
 		if (std_alignof && Wpedantic)
 			warning(expr->pos,
 				"ISO C does not allow '%s' on an expression",
 				show_ident(op_ident));
-		cast->cast_type = expr->cast_type;
+		check_compound_literal_storage(sym, is_force);
+		cast->cast_type = sym;
 		expr->cast_type = NULL;
 		expr->cast_expression = cast;
 		token = initializer(&cast->cast_expression, token);
 		token = postfix_expression(token, &expr->cast_expression, cast);
+	} else {
+		check_typename_storage(expr->cast_type);
 	}
 	return token;
 }
@@ -993,14 +1043,11 @@ static struct token *cast_expression(struct token *token, struct expression **tr
 			cast->cast_type = sym;
 			token = expect(token, ')', "at end of cast operator");
 			if (match_op(token, '{')) {
-				if (toplevel(block_scope))
-					sym->ctype.modifiers |= MOD_TOPLEVEL;
-				if (is_force)
-					warning(sym->pos,
-						"[force] in compound literal");
+				check_compound_literal_storage(sym, is_force);
 				token = initializer(&cast->cast_expression, token);
 				return postfix_expression(token, tree, cast);
 			}
+			check_typename_storage(sym);
 			*tree = cast;
 			if (is_force)
 				cast->type = EXPR_FORCE_CAST;
diff --git a/expression.h b/expression.h
index 8c387adf9d5b..213c31d246aa 100644
--- a/expression.h
+++ b/expression.h
@@ -319,6 +319,7 @@ static inline struct expression *alloc_const_expression(struct position pos, int
 
 /* Type name parsing */
 struct token *typename(struct token *, struct symbol **, int *);
+void check_typename_storage(struct symbol *sym);
 
 static inline int lookup_type(struct token *token)
 {
diff --git a/parse.c b/parse.c
index 782683a84fba..0787ad2af576 100644
--- a/parse.c
+++ b/parse.c
@@ -840,6 +840,7 @@ static struct token *struct_union_enum_specifier(enum type type,
 				warning(token->pos,
 					"ISO C does not support specifying 'enum' underlying types before C23");
 			token = typename(token->next, &underlying, NULL);
+			check_typename_storage(underlying);
 			underlying = examine_symbol_type(underlying);
 			if (!underlying || !is_int_type(underlying) ||
 			    is_enum_type(underlying)) {
@@ -923,6 +924,7 @@ static struct token *struct_union_enum_specifier(enum type type,
 				warning(token->pos,
 					"ISO C does not support specifying 'enum' underlying types before C23");
 			token = typename(token->next, &underlying, NULL);
+			check_typename_storage(underlying);
 			underlying = examine_symbol_type(underlying);
 			if (!underlying || !is_int_type(underlying) ||
 			    is_enum_type(underlying)) {
@@ -1337,6 +1339,7 @@ static struct token *typeof_specifier_helper(struct token *token, struct symbol
 	if (lookup_type(token->next)) {
 		struct symbol *sym;
 		token = typename(token->next, &sym, NULL);
+		check_typename_storage(sym);
 		sym = examine_symbol_type(sym);
 		if (!qual) {
 			sym->ctype.modifiers &= ~MOD_QUALIFIER;
@@ -1780,6 +1783,7 @@ static struct token *alignas_specifier(struct token *token, struct symbol *sym,
 	if (lookup_type(token->next)) {
 		struct symbol *sym = NULL;
 		token = typename(token->next, &sym, NULL);
+		check_typename_storage(sym);
 		sym = examine_symbol_type(sym);
 		alignment = sym->ctype.alignment;
 		token = expect(token, ')', "after _Alignas(...");
@@ -2550,37 +2554,62 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	return token;
 }
 
-struct token *typename(struct token *token, struct symbol **p, int *forced)
+void check_typename_storage(struct symbol *sym)
 {
-	struct decl_state ctx = {.prefer_abstract = 1};
+	struct symbol *base;
 	unsigned long class;
+	if (!sym)
+		return;
+	for (base = sym; base && (base->type == SYM_NODE ||
+				  base->type == SYM_PTR ||
+				  base->type == SYM_ARRAY);
+	     base = base->ctype.base_type)
+		;
+	if (base == &autotype_ctype) {
+		sparse_error(sym->pos, "'%s' in type name",
+			     sym->c23_auto ? "auto" : "__auto_type");
+		sym->ctype.base_type = &bad_ctype;
+		return;
+	}
+	if (sym->has_alignas)
+		sparse_error(sym->pos, "alignment specified for type name");
+	if (sym->ctype.modifiers & MOD_CONSTEXPR)
+		sparse_error(sym->pos, "constexpr in typename");
+	class = sym->ctype.modifiers & (MOD_STORAGE | MOD_TLS);
+	if (class)
+		warning(sym->pos, "storage class in typename (%s%s)",
+			modifier_string(class), show_typename(sym));
+}
+
+struct token *typename(struct token *token, struct symbol **p, int *forced)
+{
+	struct decl_state ctx = { .prefer_abstract = 1 };
 	struct symbol *sym = alloc_symbol(token->pos, SYM_NODE);
+	unsigned long mod;
 	*p = sym;
 	token = declaration_specifiers(token, &ctx);
-	if (ctx.has_alignas)
-		sparse_error(sym->pos, "alignment specified for type name");
+	mod = decl_modifiers(&ctx);
 	if (ctx.opaque_fixed_enum)
 		sparse_error(ctx.opaque_fixed_enum_pos,
 			     "'enum' underlying type may not be specified here");
-	if (ctx.autotype) {
-		sparse_error(sym->pos, "'%s' in type name",
-			     ctx.has_auto ? "auto" : "__auto_type");
-		ctx.ctype.base_type = &bad_ctype;
-	}
 	token = declarator(token, &ctx);
 	token = handle_attributes(token, &ctx);
 	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
+	sym->c23_auto = ctx.autotype && ctx.has_auto;
 	sym->endpos = token->pos;
-	if (ctx.is_constexpr)
-		sparse_error(sym->pos, "constexpr in typename");
-	class = ctx.storage_class;
+	if (ctx.storage_class == MOD_USERTYPE)
+		sym->explicit_typedef = 1;
+	if (ctx.has_alignas)
+		sym->has_alignas = 1;
+	if (ctx.is_constexpr) {
+		apply_constexpr_const(sym);
+		sym->ctype.modifiers |= MOD_CONSTEXPR;
+	}
+	sym->ctype.modifiers |= mod | decl_modifiers(&ctx);
 	if (forced)
 		*forced = ctx.forced;
-	if (class)
-		warning(sym->pos, "storage class in typename (%s%s)",
-			modifier_string(class), show_typename(sym));
 	return token;
 }
 
diff --git a/sparse-llvm.c b/sparse-llvm.c
index f3f6b7e01171..7d15d7a70e54 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -318,7 +318,7 @@ static LLVMValueRef get_sym_value(LLVMModuleRef module, struct symbol *sym)
 	LLVMValueRef result = NULL;
 	struct expression *expr;
 
-	assert(sym->bb_target == NULL);
+	assert(sym->namespace != NS_LABEL);
 
 	expr = sym->initializer;
 	if (expr && !sym->ident) {
@@ -348,6 +348,14 @@ static LLVMValueRef get_sym_value(LLVMModuleRef module, struct symbol *sym)
 		}
 	}
 
+	if (!sym->ident && (sym->ctype.modifiers & (MOD_STATIC | MOD_TOPLEVEL | MOD_TLS))) {
+		if (!sym->aux) {
+			expand_symbol(sym);
+			sym->aux = output_data(module, sym);
+		}
+		return sym->aux;
+	}
+
 	if (LLVMGetTypeKind(type) == LLVMFunctionTypeKind) {
 		result = LLVMGetNamedFunction(module, name);
 		if (!result)
diff --git a/symbol.h b/symbol.h
index 942baad97df5..435fa18a4990 100644
--- a/symbol.h
+++ b/symbol.h
@@ -196,7 +196,7 @@ struct symbol {
 				designated_init : 1, forced_arg : 1,
 				accessed : 1, builtin : 1, torename : 1,
 				packed : 1, transparent_union : 1,
-				fixed_type : 1;
+				fixed_type : 1, explicit_typedef : 1;
 			unsigned int has_alignas : 1;
 			unsigned int kr_args : 1;
 			unsigned int underspecified : 1;
@@ -282,7 +282,7 @@ struct symbol {
 /* like cvr-qualifiers but 'reversed' (OK: source <= target) */
 #define MOD_REV_QUAL	(MOD_PURE|MOD_NORETURN)
 /* do not warn when these are duplicated */
-#define MOD_DUP_OK	(MOD_UNUSED|MOD_GNU_INLINE)
+#define MOD_DUP_OK	(MOD_UNUSED|MOD_GNU_INLINE|MOD_USERTYPE)
 /* must be part of the declared symbol, not its type */
 #define MOD_DECLARE                                                         \
 	(MOD_STORAGE | MOD_INLINE | MOD_TLS | MOD_GNU_INLINE | MOD_UNUSED | \
diff --git a/validation/backend/c23-compound-literal-storage.c b/validation/backend/c23-compound-literal-storage.c
new file mode 100644
index 000000000000..e4f0d430e6c8
--- /dev/null
+++ b/validation/backend/c23-compound-literal-storage.c
@@ -0,0 +1,27 @@
+static const int *g_arr = (static int[]){ 1, 2, 3 };
+
+int *test_cl_static_scalar_addr(void);
+int *test_cl_static_scalar_addr(void)
+{
+	return &(static int){ 42 };
+}
+
+int test_cl_llvm(void);
+int test_cl_llvm(void)
+{
+	int *lp = (static int[]){ 7, 8, 9 };
+	int *tp = (static thread_local int[]){ 14 };
+	register int rv = (register int){ 12 };
+	int ce = (constexpr int){ 42 };
+
+	lp[0] += 1;
+	return g_arr[0] + lp[0] + tp[0] + rv + ce;
+}
+
+/*
+ * check-name: backend-c23-compound-literal-storage
+ * check-command: sparse-llvm-dis -m64 -std=c23 $file
+ * check-output-ignore
+ * check-output-contains: private global i32 42
+ * check-output-contains: define i32 @test_cl_llvm(
+ */
diff --git a/validation/c23-compound-literal-register-addr.c b/validation/c23-compound-literal-register-addr.c
new file mode 100644
index 000000000000..0bd817efcc6a
--- /dev/null
+++ b/validation/c23-compound-literal-register-addr.c
@@ -0,0 +1,17 @@
+static void bad_reg_addr(void)
+{
+	int *p = &(register int){ 1 };
+	int *q = (register int[]){ 1, 2 };
+	(void)p;
+	(void)q;
+}
+
+/*
+ * check-name: c23-compound-literal-register-addr
+ * check-command: sparse -std=c23 $file
+ *
+ * check-error-start
+c23-compound-literal-register-addr.c:3:20: warning: taking address of 'register' compound literal
+c23-compound-literal-register-addr.c:4:19: warning: taking address of 'register' compound literal
+ * check-error-end
+ */
diff --git a/validation/c23-compound-literal-storage-error.c b/validation/c23-compound-literal-storage-error.c
new file mode 100644
index 000000000000..c6bf45d7cb1d
--- /dev/null
+++ b/validation/c23-compound-literal-storage-error.c
@@ -0,0 +1,37 @@
+void *e = (extern int[]){ 1 };
+void *t = (typedef int[]){ 1 };
+int f_reg = sizeof((register int){ 1 });
+
+static void bad_block_literals(int non_const)
+{
+	int *a1 = (auto int[]){ 1, 2 };
+	int a2 = (auto){ 1 };
+	int *tl = (thread_local int[]){ 14 };
+	int *nc = (static int[]){ non_const };
+	int *dup_s = (static static int[]){ 1 };
+	int s_reg = (static register int){ 1 };
+
+	(void)a1;
+	(void)a2;
+	(void)tl;
+	(void)nc;
+	(void)dup_s;
+	(void)s_reg;
+}
+
+/*
+ * check-name: c23-compound-literal-storage-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-compound-literal-storage-error.c:1:12: error: 'extern' is not allowed in compound literal
+c23-compound-literal-storage-error.c:2:12: error: 'typedef' is not allowed in compound literal
+c23-compound-literal-storage-error.c:3:21: error: file-scope compound literal cannot specify 'register'
+c23-compound-literal-storage-error.c:7:20: error: 'auto' is not allowed in compound literal
+c23-compound-literal-storage-error.c:8:19: error: 'auto' is not allowed in compound literal
+c23-compound-literal-storage-error.c:9:20: error: block-scope compound literal with 'thread_local' requires 'static'
+c23-compound-literal-storage-error.c:11:37: error: duplicate static
+c23-compound-literal-storage-error.c:12:38: error: multiple storage classes
+c23-compound-literal-storage-error.c:10:35: error: non-constant initializer for static object
+ * check-error-end
+ */
diff --git a/validation/c23-compound-literal-storage-pedantic.c b/validation/c23-compound-literal-storage-pedantic.c
new file mode 100644
index 000000000000..a4ddc95fdb0f
--- /dev/null
+++ b/validation/c23-compound-literal-storage-pedantic.c
@@ -0,0 +1,10 @@
+static const int *p = (static int[]){ 1, 2, 3 };
+
+/*
+ * check-name: c23-compound-literal-storage-pedantic
+ * check-command: sparse -std=c11 -Wpedantic $file
+ *
+ * check-error-start
+c23-compound-literal-storage-pedantic.c:1:24: warning: storage-class specifiers in compound literals are a C23 feature
+ * check-error-end
+ */
diff --git a/validation/c23-compound-literal-storage.c b/validation/c23-compound-literal-storage.c
new file mode 100644
index 000000000000..75a4552eecde
--- /dev/null
+++ b/validation/c23-compound-literal-storage.c
@@ -0,0 +1,27 @@
+static const int *p = (static int[]){ 1, 2, 3 };
+static const int *cp = (constexpr int[]){ 4, 5, 6 };
+
+static int test(void)
+{
+	static int *lp = (static int[]){ 7, 8, 9 };
+	register int rv = (register int){ 12 };
+	int *tp = (static thread_local int[]){ 14 };
+	const int *lcp = &(static constexpr int){ 99 };
+	int auto_ce = (constexpr int){ 42 };
+
+	return lp[0] + rv + tp[0] + *lcp + auto_ce + p[0] + cp[0];
+}
+
+static_assert(sizeof((static int[]){ 1, 2, 3 }) == 3 * sizeof(int),
+	      "sizeof static compound literal");
+static_assert(sizeof((constexpr int[]){ 1, 2, 3, 4 }) == 4 * sizeof(int),
+	      "sizeof constexpr compound literal");
+static_assert(sizeof((thread_local int[]){ 14, 15 }) == 2 * sizeof(int),
+	      "sizeof file-scope thread_local compound literal");
+static_assert((constexpr int){ 42 } == 42,
+	      "constexpr scalar compound literal in ICE");
+
+/*
+ * check-name: c23-compound-literal-storage
+ * check-command: sparse -std=c23 -pedantic $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 61/73] c23: Add support for _BitInt(N)
  2026-11-09 20:31 Bart Van Assche
                   ` (59 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
                   ` (11 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Implement ISO C23 bit-precise integer types _BitInt(N) and unsigned
_BitInt(N) up to __BITINT_MAXWIDTH__ (128):
- Parse _BitInt(N) specifiers, validate signed (N >= 2) and unsigned
  (N >= 1) width bounds, and parse case-matching wb/WB and uwb/UWB
  integer constant suffixes while rejecting mixed-case wB/Wb.
- Keep _BitInt(N) unpromoted during integer promotions per C23
  6.3.1.1p2 and implement C23 6.3.1.8 usual arithmetic conversions
  between _BitInt(N) and standard integer types.
- Mask and sign-extend constant expressions to effective_int_width()
  and store the upper 64 bits in struct expression (value_hi) so
  65..128-bit constants parse, fold, and compare with full precision.
- Enforce _BitInt bit-field width/sign rules, reject string-literal
  array initialization of _BitInt(8)[], and check pointer width/rank
  compatibility.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                       | 105 ++++++++-
 expand.c                         | 366 +++++++++++++++++++++++++++----
 expression.c                     | 103 ++++++++-
 expression.h                     |   2 +
 ident-list.h                     |   1 +
 linearize.c                      |  23 +-
 parse.c                          |  89 +++++++-
 predefine.c                      |   1 +
 show-parse.c                     |   9 +
 sparse-llvm.c                    |   8 +
 symbol.c                         |  54 ++++-
 symbol.h                         |  61 +++++-
 validation/c23-bitint-error.c    |  61 ++++++
 validation/c23-bitint-pedantic.c |  13 ++
 validation/c23-bitint.c          | 214 ++++++++++++++++++
 validation/expand-bitint.c       |  49 +++++
 16 files changed, 1086 insertions(+), 73 deletions(-)
 create mode 100644 validation/c23-bitint-error.c
 create mode 100644 validation/c23-bitint-pedantic.c
 create mode 100644 validation/c23-bitint.c
 create mode 100644 validation/expand-bitint.c

diff --git a/evaluate.c b/evaluate.c
index 32621d04a17e..1c169ad60382 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -187,6 +187,8 @@ static inline struct symbol *integer_promotion(struct symbol *type)
 		type = type->ctype.base_type;
 	}
 	mod = type->ctype.modifiers;
+	if (is_bitint_type(type))
+		return type;
 	if (width < bits_in_int)
 		return &int_ctype;
 
@@ -229,6 +231,43 @@ static struct symbol *bigger_int_type(struct symbol *left, struct symbol *right)
 	if (left == right)
 		return left;
 
+	if (is_bitint_type(left) || is_bitint_type(right)) {
+		int lwidth = effective_int_width(left);
+		int rwidth = effective_int_width(right);
+		int lunsigned = (left->ctype.modifiers & MOD_UNSIGNED) != 0;
+		int runsigned = (right->ctype.modifiers & MOD_UNSIGNED) != 0;
+		int uwidth, swidth;
+
+		if (lunsigned == runsigned) {
+			if (lwidth > rwidth)
+				return left;
+			if (rwidth > lwidth)
+				return right;
+			return is_bitint_type(left) ? right : left;
+		}
+
+		if (lunsigned) {
+			utype = left;
+			stype = right;
+			uwidth = lwidth;
+			swidth = rwidth;
+		} else {
+			utype = right;
+			stype = left;
+			uwidth = rwidth;
+			swidth = lwidth;
+		}
+
+		if (uwidth > swidth)
+			return utype;
+		if (uwidth == swidth) {
+			if (!is_bitint_type(utype) || is_bitint_type(stype))
+				return utype;
+			return unsigned_types[stype->rank];
+		}
+		return stype;
+	}
+
 	lmod = left->ctype.modifiers;
 	rmod = right->ctype.modifiers;
 	if (((lmod ^ rmod) & MOD_UNSIGNED) == 0)
@@ -879,8 +918,15 @@ const char *type_difference(struct ctype *c1, struct ctype *c2,
 				return "different base types";
 			if (!base1 && t1->bit_size != t2->bit_size)
 				return "different base types";
-			if (t1->rank != t2->rank)
-				return "different type sizes";
+			if (is_bitint_type(t1) || is_bitint_type(t2)) {
+				if (!is_bitint_type(t1) || !is_bitint_type(t2))
+					return "different base types";
+				if (t1->bitint_width != t2->bitint_width)
+					return "different bit widths";
+			} else {
+				if (t1->rank != t2->rank)
+					return "different type sizes";
+			}
 			diff = (mod1 ^ mod2) & ~MOD_IGNORE;
 			if (!diff)
 				return NULL;
@@ -1601,6 +1647,13 @@ static int whitelist_pointers(struct symbol *t1, struct symbol *t2)
 		return 0;
 	if (t1 == t2)
 		return 1;
+	if (is_bitint_type(t1) || is_bitint_type(t2)) {
+		if (!is_bitint_type(t1) || !is_bitint_type(t2))
+			return 0;
+		if (t1->bitint_width != t2->bitint_width)
+			return 0;
+		return !Wtypesign;
+	}
 	if (t1->rank == -2 && t2->rank == -2)
 		return !Wtypesign && !Wpedantic && standard < STANDARD_C23;
 	if (t1->rank != t2->rank)
@@ -3101,19 +3154,49 @@ static void check_constexpr_scalar_init(struct expression *orig,
 				     "non-constant initializer for constexpr object");
 			return;
 		}
-		sw = stype->bit_size;
+		sw = effective_int_width(stype);
 		s_unsigned = (stype->ctype.modifiers & MOD_UNSIGNED) != 0;
-		s_neg = !s_unsigned && sw > 0 && sw <= 64 &&
-			(tmp.value & sign_bit(sw)) != 0;
+		s_neg = !s_unsigned &&
+			(sw > 64 ? tmp.value_neg :
+				   (sw > 0 && (tmp.value & sign_bit(sw)) != 0));
 		if (is_bool_type(ttype)) {
-			if (s_neg || (tmp.value != 0 && tmp.value != 1))
+			if (s_neg || tmp.value_hi != 0 ||
+			    (tmp.value != 0 && tmp.value != 1))
 				sparse_error(
 					orig->pos,
 					"constexpr initializer not representable in target type");
 			return;
 		}
-		tw = ttype->bit_size;
+		tw = effective_int_width(ttype);
 		t_unsigned = (ttype->ctype.modifiers & MOD_UNSIGNED) != 0;
+#ifdef __SIZEOF_INT128__
+		if (s_neg) {
+			__int128 sval =
+				sw > 64 ?
+					(__int128)(((unsigned __int128)tmp.value_hi << 64) |
+						   tmp.value) :
+					(__int128)bits_extend(tmp.value, sw, 1);
+			if (t_unsigned ||
+			    (tw < 128 && sval < -((__int128)1 << (tw - 1))))
+				sparse_error(
+					orig->pos,
+					"constexpr initializer not representable in target type");
+		} else {
+			unsigned __int128 uval =
+				sw > 64 ?
+					(((unsigned __int128)tmp.value_hi << 64) |
+					 tmp.value) :
+					(unsigned __int128)(unsigned long long)zero_extend(
+						tmp.value, sw);
+			int max_bits = t_unsigned ? tw : (tw - 1);
+			if ((max_bits <= 0 && uval != 0) ||
+			    (max_bits > 0 && max_bits < 128 &&
+			     (uval >> max_bits) != 0))
+				sparse_error(
+					orig->pos,
+					"constexpr initializer not representable in target type");
+		}
+#else
 		if (s_neg) {
 			long long sval = sw > 64 ? (long long)tmp.value :
 						   bits_extend(tmp.value, sw, 1);
@@ -3134,6 +3217,7 @@ static void check_constexpr_scalar_init(struct expression *orig,
 					orig->pos,
 					"constexpr initializer not representable in target type");
 		}
+#endif
 		return;
 	}
 
@@ -3166,10 +3250,11 @@ static void check_constexpr_scalar_init(struct expression *orig,
 						"constexpr initializer not representable in target type");
 			}
 		} else if (is_int(sclass)) {
-			int sw = stype->bit_size;
+			int sw = effective_int_width(stype);
 			bool s_unsigned = (stype->ctype.modifiers & MOD_UNSIGNED) != 0;
-			bool s_neg = !s_unsigned && sw > 0 && sw <= 64 &&
-				     (tmp.value & sign_bit(sw)) != 0;
+			bool s_neg = !s_unsigned &&
+				     (sw > 64 ? tmp.value_neg :
+						(sw > 0 && (tmp.value & sign_bit(sw)) != 0));
 			if (tmp.type != EXPR_VALUE) {
 				sparse_error(
 					orig->pos,
diff --git a/expand.c b/expand.c
index 7c85b66ab02f..815ff24ac487 100644
--- a/expand.c
+++ b/expand.c
@@ -80,17 +80,85 @@ static int expand_symbol_expression(struct expression *expr)
 	return (sym->ctype.modifiers & (MOD_STATIC | MOD_EXTERN)) ? 2 : 1;
 }
 
+static inline int clamp_mask_bits(int bits)
+{
+	if (bits <= 0)
+		return 0;
+	if (bits > 64)
+		return 64;
+	return bits;
+}
+
+static bool expr_is_neg_int(const struct expression *e)
+{
+	int w = effective_int_width(e->ctype);
+
+	if (e->ctype->ctype.modifiers & MOD_UNSIGNED)
+		return false;
+	if (w > 64)
+		return e->value_neg;
+	return w > 0 && (e->value & sign_bit(w)) != 0;
+}
+
+#ifdef __SIZEOF_INT128__
+static unsigned __int128 get_uint128(const struct expression *e)
+{
+	int w = effective_int_width(e->ctype);
+
+	if (w > 64) {
+		if (e->value_neg && !e->value_hi)
+			return (unsigned __int128)(__int128)(long long)e->value;
+		return ((unsigned __int128)e->value_hi << 64) | e->value;
+	}
+	if (e->ctype->ctype.modifiers & MOD_UNSIGNED)
+		return (unsigned __int128)(unsigned long long)zero_extend(e->value, w);
+	return (unsigned __int128)(__int128)bits_extend(e->value, w, 1);
+}
+
+static bool store_int128(struct expression *expr, struct symbol *ctype,
+			 unsigned __int128 uval)
+{
+	int w = effective_int_width(ctype);
+	int is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
+	unsigned __int128 mask = w >= 128 ? ~(unsigned __int128)0 :
+					    (((unsigned __int128)1 << w) - 1);
+	unsigned long long lo, hi;
+	int neg;
+
+	uval &= mask;
+	if (is_signed && (uval & ((unsigned __int128)1 << (w - 1)))) {
+		uval |= ~mask;
+		neg = 1;
+	} else {
+		neg = 0;
+	}
+	lo = (unsigned long long)uval;
+	hi = (unsigned long long)(uval >> 64);
+	if (!is_bitint_type(ctype) &&
+	    (hi != (neg ? ~0ULL : 0ULL) || (neg && !(lo & (1ULL << 63)))))
+		return false;
+	expr->value = lo;
+	expr->value_hi = hi;
+	expr->value_neg = neg;
+	return true;
+}
+#endif
+
 static long long get_longlong(struct expression *expr)
 {
+	int w = effective_int_width(expr->ctype);
 	int is_signed = !(expr->ctype->ctype.modifiers & MOD_UNSIGNED);
-	return bits_extend(expr->value, expr->ctype->bit_size, is_signed);
+
+	if (w > 64)
+		return (long long)expr->value;
+	return bits_extend(expr->value, w, is_signed);
 }
 
 void cast_value(struct expression *expr, struct symbol *newtype, struct expression *old)
 {
 	struct symbol *oldtype = old->ctype;
-	int old_size = oldtype->bit_size;
-	int new_size = newtype->bit_size;
+	int old_size = effective_int_width(oldtype);
+	int new_size = effective_int_width(newtype);
 	long long value;
 	unsigned long long mask, signmask;
 	unsigned long long oldmask, oldsignmask, dropped;
@@ -98,28 +166,44 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
 	if (is_float_type(newtype) || is_float_type(oldtype))
 		goto Float;
 
-	if (!is_bool_type(newtype) && new_size > 64 && old_size >= 64 &&
-	    new_size > old_size && (oldtype->ctype.modifiers & MOD_UNSIGNED) &&
-	    (old->value & (1ULL << 63)))
+	if (!is_bool_type(newtype) && new_size > 64) {
+#ifdef __SIZEOF_INT128__
+		struct expression tmp = *expr;
+		if (!store_int128(&tmp, newtype, get_uint128(old)))
+			return;
+		expr->type = EXPR_VALUE;
+		expr->taint = old->taint;
+		expr->ctype = newtype;
+		expr->value = tmp.value;
+		expr->value_hi = tmp.value_hi;
+		expr->value_neg = tmp.value_neg;
+#endif
 		return;
+	}
 
 	// For pointers and integers, we can just move the value around
 	expr->type = EXPR_VALUE;
 	expr->taint = old->taint;
-	if (old_size == new_size && !is_bool_type(newtype)) {
-		expr->value = old->value;
+	if (old_size == new_size && old_size <= 64 && !is_bool_type(newtype)) {
+		expr->value = old->value & bits_mask(new_size);
+		expr->value_hi = 0;
+		expr->value_neg = 0;
 		expr->ctype = newtype;
 		return;
 	}
 
 	if (is_bool_type(newtype)) {
-		bool non_zero = old->value != 0;
-		bool is_one = old->value == 1;
+		bool non_zero = old->value != 0 || old->value_hi != 0;
+		bool is_one = old->value == 1 && old->value_hi == 0 &&
+			      !expr_is_neg_int(old);
+		unsigned long long oval = old->value;
 		expr->ctype = newtype;
 		expr->value = non_zero;
+		expr->value_hi = 0;
+		expr->value_neg = 0;
 		if (!conservative && non_zero && !is_one)
 			warning(old->pos, "odd constant _Bool cast (%llx becomes 1)",
-				old->value);
+				oval);
 		return;
 	}
 
@@ -128,6 +212,8 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
 	expr->ctype = newtype;
 
 Int:
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	// _Bool requires a zero test rather than truncation.
 	if (is_bool_type(newtype)) {
 		expr->value = !!value;
@@ -136,13 +222,28 @@ Int:
 		return;
 	}
 
+	if (new_size <= 0) {
+		expr->value = 0;
+		return;
+	}
+
+	if (new_size > 64) {
+		expr->value = value;
+		expr->value_neg = !(newtype->ctype.modifiers & MOD_UNSIGNED) &&
+				  value < 0;
+		expr->value_hi = expr->value_neg ? ~0ULL : 0ULL;
+		return;
+	}
+
 	// Truncate it to the new size
 	signmask = sign_bit(new_size);
 	mask = bits_mask(new_size);
 	expr->value = value & mask;
 
 	// Stop here unless checking for truncation
-	if (!Wcast_truncate || conservative)
+	if (!Wcast_truncate || conservative || old_size <= 0)
+		return;
+	if (old_size > 64 && !old->value_neg && !old->value_hi && new_size == 64)
 		return;
 	
 	// Check if we dropped any bits..
@@ -168,6 +269,8 @@ Float:
 			expr->taint = 0;
 			expr->ctype = newtype;
 			expr->value = (fval != 0);
+			expr->value_hi = 0;
+			expr->value_neg = 0;
 			return;
 		}
 		if (new_size <= 0 || new_size > 64 || !__builtin_isfinite(fval))
@@ -207,10 +310,19 @@ Float:
 	}
 
 	if (!is_float_type(oldtype)) {
-		if ((oldtype->ctype.modifiers & MOD_UNSIGNED) &&
-		    old_size > 64 && (old->value & (1ULL << 63)))
-			return;
-		if (oldtype->ctype.modifiers & MOD_UNSIGNED)
+		if (old_size > 64) {
+#ifdef __SIZEOF_INT128__
+			if (old->value_neg)
+				expr->fvalue = (long double)(__int128)get_uint128(old);
+			else
+				expr->fvalue = (long double)get_uint128(old);
+#else
+			if (old->value_neg)
+				expr->fvalue = (long double)(long long)old->value;
+			else
+				expr->fvalue = (long double)(unsigned long long)old->value;
+#endif
+		} else if (oldtype->ctype.modifiers & MOD_UNSIGNED)
 			expr->fvalue = (long double)(unsigned long long)get_longlong(old);
 		else
 			expr->fvalue = (long double)get_longlong(old);
@@ -231,10 +343,12 @@ Float:
 static bool check_shift_count(struct expression *expr, struct expression *right)
 {
 	struct symbol *ctype = expr->ctype;
-	long long count = get_longlong(right);
+	int width = effective_int_width(ctype);
 
-	if (count >= 0 && count < ctype->bit_size)
-		return true;
+	if (expr_is_neg_int(right) || right->value_hi != 0)
+		return false;
+	if (right->value < (unsigned long long)width && right->value < 128)
+		return width > 64 || right->value < 64;
 	return false;
 }
 
@@ -250,7 +364,7 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
 	struct expression *left = expr->left, *right = expr->right;
 	unsigned long long v, l, r, mask;
 	signed long long sl, sr;
-	int is_signed;
+	int is_signed, bits, raw_bits;
 
 	if (right->type != EXPR_VALUE)
 		return 0;
@@ -261,11 +375,92 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
 	}
 	if (left->type != EXPR_VALUE)
 		return 0;
-	if (!ctype || ctype->bit_size <= 0 || ctype->bit_size > 64)
+	raw_bits = effective_int_width(ctype);
+	if (raw_bits <= 0)
 		return 0;
-	l = left->value; r = right->value;
 	is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
-	mask = sign_bit(ctype->bit_size);
+	if (raw_bits > 64) {
+#ifdef __SIZEOF_INT128__
+		unsigned __int128 ul = get_uint128(left);
+		unsigned __int128 ur = get_uint128(right);
+		__int128 sl128 = (__int128)ul, sr128 = (__int128)ur;
+		unsigned __int128 sbit128 = (unsigned __int128)1 << (raw_bits - 1);
+		unsigned __int128 ures;
+
+		switch (CONVERT(expr->op, is_signed)) {
+		case SIGNED('+'):
+		case UNSIGNED('+'):
+			ures = ul + ur;
+			break;
+		case SIGNED('-'):
+		case UNSIGNED('-'):
+			ures = ul - ur;
+			break;
+		case SIGNED('&'):
+		case UNSIGNED('&'):
+			ures = ul & ur;
+			break;
+		case SIGNED('|'):
+		case UNSIGNED('|'):
+			ures = ul | ur;
+			break;
+		case SIGNED('^'):
+		case UNSIGNED('^'):
+			ures = ul ^ ur;
+			break;
+		case SIGNED('*'):
+		case UNSIGNED('*'):
+			ures = ul * ur;
+			break;
+		case SIGNED('/'):
+			if (!ur)
+				goto Div;
+			if ((ul & ((sbit128 << 1) - 1)) == sbit128 && sr128 == -1)
+				goto Overflow;
+			ures = (unsigned __int128)(sl128 / sr128);
+			break;
+		case UNSIGNED('/'):
+			if (!ur)
+				goto Div;
+			ures = ul / ur;
+			break;
+		case SIGNED('%'):
+			if (!ur)
+				goto Div;
+			if ((ul & ((sbit128 << 1) - 1)) == sbit128 && sr128 == -1)
+				goto Overflow;
+			ures = (unsigned __int128)(sl128 % sr128);
+			break;
+		case UNSIGNED('%'):
+			if (!ur)
+				goto Div;
+			ures = ul % ur;
+			break;
+		case SIGNED(SPECIAL_LEFTSHIFT):
+		case UNSIGNED(SPECIAL_LEFTSHIFT):
+			ures = r < 128 ? (ul << r) : 0;
+			break;
+		case SIGNED(SPECIAL_RIGHTSHIFT):
+			ures = (unsigned __int128)(sl128 >> (r < 127 ? r : 127));
+			break;
+		case UNSIGNED(SPECIAL_RIGHTSHIFT):
+			ures = r < 128 ? (ul >> r) : 0;
+			break;
+		default:
+			return 0;
+		}
+		if (!store_int128(expr, ctype, ures))
+			return 0;
+		expr->type = EXPR_VALUE;
+		expr->taint = left->taint | right->taint;
+		return 1;
+#else
+		return 0;
+#endif
+	}
+	bits = clamp_mask_bits(raw_bits);
+	l = left->value; r = right->value;
+	mask = sign_bit(bits);
 	sl = l; sr = r;
 	if (is_signed && (sl & mask))
 		sl |= ~(mask-1);
@@ -345,8 +540,10 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
 	default:
 		return 0;
 	}
-	mask = bits_mask(ctype->bit_size);
+	mask = bits_mask(bits);
 	expr->value = v & mask;
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = left->taint | right->taint;
 	return 1;
@@ -365,13 +562,43 @@ static int simplify_cmp_binop(struct expression *expr, struct symbol *ctype)
 	struct expression *left = expr->left, *right = expr->right;
 	unsigned long long l, r, mask;
 	signed long long sl, sr;
+	int bits, raw_bits;
 
 	if (left->type != EXPR_VALUE || right->type != EXPR_VALUE)
 		return 0;
-	if (!ctype || ctype->bit_size <= 0 || ctype->bit_size > 64)
+	raw_bits = effective_int_width(ctype);
+	if (raw_bits <= 0)
 		return 0;
+	if (raw_bits > 64) {
+#ifdef __SIZEOF_INT128__
+		unsigned __int128 ul = get_uint128(left);
+		unsigned __int128 ur = get_uint128(right);
+		__int128 sl128 = (__int128)ul, sr128 = (__int128)ur;
+
+		switch (expr->op) {
+		case '<':		expr->value = sl128 < sr128; break;
+		case '>':		expr->value = sl128 > sr128; break;
+		case SPECIAL_LTE:	expr->value = sl128 <= sr128; break;
+		case SPECIAL_GTE:	expr->value = sl128 >= sr128; break;
+		case SPECIAL_EQUAL:	expr->value = ul == ur; break;
+		case SPECIAL_NOTEQUAL:	expr->value = ul != ur; break;
+		case SPECIAL_UNSIGNED_LT:expr->value = ul < ur; break;
+		case SPECIAL_UNSIGNED_GT:expr->value = ul > ur; break;
+		case SPECIAL_UNSIGNED_LTE:expr->value = ul <= ur; break;
+		case SPECIAL_UNSIGNED_GTE:expr->value = ul >= ur; break;
+		}
+		expr->value_hi = 0;
+		expr->value_neg = 0;
+		expr->type = EXPR_VALUE;
+		expr->taint = left->taint | right->taint;
+		return 1;
+#else
+		return 0;
+#endif
+	}
+	bits = clamp_mask_bits(raw_bits);
 	l = left->value; r = right->value;
-	mask = sign_bit(ctype->bit_size);
+	mask = sign_bit(bits);
 	sl = l; sr = r;
 	if (sl & mask)
 		sl |= ~(mask-1);
@@ -389,6 +616,8 @@ static int simplify_cmp_binop(struct expression *expr, struct symbol *ctype)
 	case SPECIAL_UNSIGNED_LTE:expr->value = l <= r; break;
 	case SPECIAL_UNSIGNED_GTE:expr->value = l >= r; break;
 	}
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = left->taint | right->taint;
 	return 1;
@@ -461,6 +690,8 @@ static int simplify_float_cmp(struct expression *expr, struct symbol *ctype)
 	case SPECIAL_EQUAL:	expr->value = l == r; break;
 	case SPECIAL_NOTEQUAL:	expr->value = l != r; break;
 	}
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = 0;
 	return 1;
@@ -489,16 +720,20 @@ static int expand_logical(struct expression *expr)
 	cost = expand_expression(left);
 	if (left->type == EXPR_VALUE) {
 		if (expr->op == SPECIAL_LOGICAL_AND) {
-			if (!left->value) {
+			if (!left->value && !left->value_hi) {
 				expr->type = EXPR_VALUE;
 				expr->value = 0;
+				expr->value_hi = 0;
+				expr->value_neg = 0;
 				expr->taint = left->taint;
 				return 0;
 			}
 		} else {
-			if (left->value) {
+			if (left->value || left->value_hi) {
 				expr->type = EXPR_VALUE;
 				expr->value = 1;
+				expr->value_hi = 0;
+				expr->value_neg = 0;
 				expr->taint = left->taint;
 				return 0;
 			}
@@ -513,7 +748,9 @@ static int expand_logical(struct expression *expr)
 		 * otherwise we would have short-circuited it..
 		 */
 		expr->type = EXPR_VALUE;
-		expr->value = right->value != 0;
+		expr->value = (right->value != 0 || right->value_hi != 0);
+		expr->value_hi = 0;
+		expr->value_neg = 0;
 		expr->taint = left->taint | right->taint;
 		return 0;
 	}
@@ -586,6 +823,8 @@ static int expand_compare(struct expression *expr)
 			int op = expr->op;
 			expr->type = EXPR_VALUE;
 			expr->value = compare_types(op, left->symbol, right->symbol);
+			expr->value_hi = 0;
+			expr->value_neg = 0;
 			expr->taint = 0;
 			return 0;
 		}
@@ -607,7 +846,7 @@ static int expand_conditional(struct expression *expr)
 	cond_cost = expand_expression(cond);
 	if (cond->type == EXPR_VALUE) {
 		unsigned flags = expr->flags;
-		if (!cond->value)
+		if (!cond->value && !cond->value_hi)
 			valt = valf;
 		if (!valt)
 			valt = cond;
@@ -838,7 +1077,8 @@ static int expand_dereference(struct expression *expr)
 
 		/* Const symbol with a constant initializer? */
 		if (value && value->ctype) {
-			if (ctype->bit_size != value->ctype->bit_size)
+			if (ctype->bit_size != value->ctype->bit_size ||
+			    effective_int_width(ctype) != effective_int_width(value->ctype))
 				return UNSAFE;
 			if (value->type == EXPR_VALUE) {
 				if (!is_integral_type(ctype))
@@ -847,6 +1087,8 @@ static int expand_dereference(struct expression *expr)
 					return UNSAFE;
 				expr->type = EXPR_VALUE;
 				expr->value = value->value;
+				expr->value_hi = value->value_hi;
+				expr->value_neg = value->value_neg;
 				expr->taint = 0;
 				if (sym->ctype.modifiers & MOD_CONSTEXPR)
 					expr->flags = is_int_type(ctype) ? CEF_SET_ICE : CEF_ADDR;
@@ -882,13 +1124,48 @@ int early_simplify_preop(struct expression *expr)
 {
 	struct expression *op = expr->unop;
 	unsigned long long v, mask;
+	int bits, raw_bits;
 
 	if (op->type != EXPR_VALUE)
 		return 0;
-	if (!expr->ctype || expr->ctype->bit_size <= 0 || expr->ctype->bit_size > 64)
+
+	raw_bits = effective_int_width(expr->ctype);
+	if (raw_bits <= 0)
 		return 0;
+	if (raw_bits > 64) {
+#ifdef __SIZEOF_INT128__
+		unsigned __int128 u = get_uint128(op);
 
-	mask = sign_bit(expr->ctype->bit_size);
+		switch (expr->op) {
+		case '+':
+			break;
+		case '!':
+			expr->value = (u == 0);
+			expr->value_hi = 0;
+			expr->value_neg = 0;
+			expr->type = EXPR_VALUE;
+			expr->taint = op->taint;
+			return 1;
+		case '-':
+			u = -u;
+			break;
+		case '~':
+			u = ~u;
+			break;
+		default:
+			return 0;
+		}
+		if (!store_int128(expr, expr->ctype, u))
+			return 0;
+		expr->type = EXPR_VALUE;
+		expr->taint = op->taint;
+		return 1;
+#else
+		return 0;
+#endif
+	}
+	bits = clamp_mask_bits(raw_bits);
+	mask = sign_bit(bits);
 	v = op->value;
 	switch (expr->op) {
 	case '+': break;
@@ -901,8 +1178,10 @@ int early_simplify_preop(struct expression *expr)
 	case '~': v = ~v; break;
 	default: return 0;
 	}
-	mask = bits_mask(expr->ctype->bit_size);
+	mask = bits_mask(bits);
 	expr->value = v & mask;
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = op->taint;
 	return 1;
@@ -998,6 +1277,11 @@ static int expand_cast(struct expression *expr)
 
 	/* Simplify normal integer casts.. */
 	if (target->type == EXPR_VALUE || target->type == EXPR_FVALUE) {
+		if (target->type == EXPR_VALUE &&
+		    effective_int_width(expr->ctype) > 64 &&
+		    (expr->ctype->ctype.modifiers & MOD_UNSIGNED) &&
+		    expr_is_neg_int(target))
+			return cost + 1;
 		cast_value(expr, expr->ctype, target);
 		if (expr->type == EXPR_VALUE || expr->type == EXPR_FVALUE)
 			return 0;
@@ -1508,6 +1792,7 @@ static long long __get_expression_value(struct expression *expr, int strict)
 	long long value;
 	unsigned long long mask;
 	struct symbol *ctype;
+	int bits;
 
 	if (!expr)
 		return 0;
@@ -1528,13 +1813,18 @@ static long long __get_expression_value(struct expression *expr, int strict)
 	}
 
 	value = expr->value;
-	mask = sign_bit(ctype->bit_size);
+	if (effective_int_width(ctype) > 64)
+		return value;
+	bits = clamp_mask_bits(effective_int_width(ctype));
+	if (!bits)
+		return value;
+	mask = sign_bit(bits);
 
 	if (value & mask) {
 		while (ctype->type != SYM_BASETYPE)
 			ctype = ctype->ctype.base_type;
 		if (!(ctype->ctype.modifiers & MOD_UNSIGNED))
-			value = value | mask | ~(mask-1);
+			value = value | mask | ~(mask - 1);
 	}
 	return value;
 }
@@ -1577,7 +1867,7 @@ redo:
 		expr = expr->right;
 		goto redo;
 	case EXPR_VALUE:
-		return expr->value != 0;
+		return expr->value != 0 || expr->value_hi != 0;
 	case EXPR_FVALUE:
 		return expr->fvalue != 0;
 	default:
@@ -1591,7 +1881,7 @@ int is_zero_constant(struct expression *expr)
 	conservative = 1;
 	expand_expression(expr);
 	conservative = saved;
-	return expr->type == EXPR_VALUE && !expr->value;
+	return expr->type == EXPR_VALUE && !expr->value && !expr->value_hi;
 }
 
 void expand_constexpr_initializer(struct expression *expr)
diff --git a/expression.c b/expression.c
index 65efff3b61fe..15b9eca351cb 100644
--- a/expression.c
+++ b/expression.c
@@ -260,6 +260,49 @@ static unsigned long long parse_num(const char *nptr, char **end)
 	return strtoull(nptr, end, 0);
 }
 
+#ifdef __SIZEOF_INT128__
+static bool parse_num128(const char *nptr, unsigned long long *lo,
+			 unsigned long long *hi)
+{
+	unsigned __int128 val = 0;
+	int base = 10;
+	const char *p = nptr;
+
+	if (p[0] == '0') {
+		if (tolower((unsigned char)p[1]) == 'x') {
+			base = 16;
+			p += 2;
+		} else if (tolower((unsigned char)p[1]) == 'b') {
+			base = 2;
+			p += 2;
+		} else {
+			base = 8;
+		}
+	}
+	for (; *p; p++) {
+		unsigned int d;
+		unsigned char c = *p;
+
+		if (c >= '0' && c <= '9')
+			d = c - '0';
+		else if (c >= 'a' && c <= 'f')
+			d = c - 'a' + 10;
+		else if (c >= 'A' && c <= 'F')
+			d = c - 'A' + 10;
+		else
+			break;
+		if (d >= (unsigned int)base)
+			break;
+		if (val > (~(unsigned __int128)0 - d) / base)
+			return false;
+		val = val * base + d;
+	}
+	*lo = (unsigned long long)val;
+	*hi = (unsigned long long)(val >> 64);
+	return true;
+}
+#endif
+
 static int is_digit_for_base(int c, int base)
 {
 	if (base == 2)
@@ -417,6 +460,7 @@ static void get_number_value(struct expression *expr, struct token *token)
 	unsigned long long value;
 	char *end;
 	int size = 0, want_unsigned = 0;
+	int is_bitint = 0;
 	int overflow = 0, do_warn = 0;
 	int try_unsigned = 1;
 	int bits;
@@ -444,12 +488,19 @@ static void get_number_value(struct expression *expr, struct token *token)
 		char c = *end++;
 		if (!c) {
 			break;
+		} else if (c == 'w' || c == 'W') {
+			if (is_bitint || size)
+				goto Enoint;
+			if (*end != (c == 'w' ? 'b' : 'B'))
+				goto Enoint;
+			end++;
+			is_bitint = 1;
 		} else if (c == 'u' || c == 'U') {
 			if (want_unsigned)
 				goto Enoint;
 			want_unsigned = 1;
 		} else if (c == 'l' || c == 'L') {
-			if (size)
+			if (size || is_bitint)
 				goto Enoint;
 			size = 1;
 			if (*end == c) {
@@ -463,6 +514,56 @@ static void get_number_value(struct expression *expr, struct token *token)
 			goto Enoint;
 		}
 	}
+	if (is_bitint) {
+		unsigned long long value_hi = 0;
+		int width;
+
+		if (overflow) {
+#ifdef __SIZEOF_INT128__
+			if (!parse_num128(str, &value, &value_hi))
+				goto Ebitint_overflow;
+#else
+			goto Ebitint_overflow;
+#endif
+		}
+		if (standard < STANDARD_C23 && Wpedantic)
+			warning(expr->pos,
+				"bit-precise integer suffix is a C23 feature");
+		if (value_hi != 0) {
+			int mag_bits = 128 - __builtin_clzll(value_hi);
+
+			width = want_unsigned ? mag_bits : (mag_bits + 1);
+			if (width > bits_in_longlonglong)
+				goto Ebitint_overflow;
+		} else if (want_unsigned) {
+			if (value == 0)
+				width = 1;
+			else
+				width = 64 - __builtin_clzll(value);
+		} else {
+			if (value == 0)
+				width = 2;
+			else
+				width = 64 - __builtin_clzll(value) + 1;
+		}
+		expr->type = EXPR_VALUE;
+		expr->flags = CEF_SET_INT;
+		expr->ctype = get_bitint_type(width, !want_unsigned);
+		expr->value = value;
+		expr->value_hi = value_hi;
+		return;
+Ebitint_overflow:
+		sparse_error(expr->pos,
+			     "constant %s is too big for %s_BitInt(%d)",
+			     show_token(token),
+			     want_unsigned ? "unsigned " : "",
+			     bits_in_longlonglong);
+		expr->type = EXPR_VALUE;
+		expr->flags = CEF_SET_ICE;
+		expr->ctype = get_bitint_type(bits_in_longlonglong,
+					      !want_unsigned);
+		return;
+	}
 	if (overflow)
 		goto Eoverflow;
 	/* OK, it's a valid integer */
diff --git a/expression.h b/expression.h
index 213c31d246aa..83857cffc7dc 100644
--- a/expression.h
+++ b/expression.h
@@ -160,6 +160,7 @@ struct expression {
 	enum expression_type type:8;
 	unsigned flags:8;
 	unsigned zero_init:1;
+	unsigned value_neg:1;
 	int op;
 	struct position pos;
 	struct symbol *ctype;
@@ -167,6 +168,7 @@ struct expression {
 		// EXPR_VALUE
 		struct {
 			unsigned long long value;
+			unsigned long long value_hi;
 			unsigned taint;
 		};
 
diff --git a/ident-list.h b/ident-list.h
index b4e01e0a423c..4427527580ee 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -44,6 +44,7 @@ IDENT_RESERVED(_Static_assert);
 IDENT(_Thread_local);
 
 /* C23 keywords */
+IDENT(_BitInt);
 IDENT(alignas);
 IDENT(alignof);
 IDENT(bool);
diff --git a/linearize.c b/linearize.c
index 268466f07886..f43502577649 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1824,7 +1824,7 @@ static pseudo_t linearize_cond_branch(struct entrypoint *ep, struct expression *
 
 	case EXPR_STRING:
 	case EXPR_VALUE:
-		add_goto(ep, expr->value ? bb_true : bb_false);
+		add_goto(ep, (expr->value || expr->value_hi) ? bb_true : bb_false);
 		break;
 	case EXPR_FVALUE:
 		add_goto(ep, expr->fvalue ? bb_true : bb_false);
@@ -1920,6 +1920,27 @@ static pseudo_t linearize_expression(struct entrypoint *ep, struct expression *e
 		return add_symbol_address(ep, expr);
 
 	case EXPR_VALUE:
+		if (effective_int_width(expr->ctype) > 64) {
+			if (expr->value_hi != (expr->value_neg ? ~0ULL : 0ULL) ||
+			    (expr->value_neg && !(expr->value & (1ULL << 63)))) {
+				pseudo_t hi = cast_pseudo(
+					ep, value_pseudo(expr->value_hi),
+					&ullong_ctype, expr->ctype);
+				hi = add_binary_op(ep, expr->ctype, OP_SHL, hi,
+						   value_pseudo(64));
+				if (expr->value) {
+					pseudo_t lo = cast_pseudo(
+						ep, value_pseudo(expr->value),
+						&ullong_ctype, expr->ctype);
+					hi = add_binary_op(ep, expr->ctype,
+							   OP_OR, hi, lo);
+				}
+				return hi;
+			}
+			if (!expr->value_neg && (expr->value & (1ULL << 63)))
+				return cast_pseudo(ep, value_pseudo(expr->value),
+						   &ullong_ctype, expr->ctype);
+		}
 		return value_pseudo(expr->value);
 
 	case EXPR_STRING:
diff --git a/parse.c b/parse.c
index 0787ad2af576..d89f60113152 100644
--- a/parse.c
+++ b/parse.c
@@ -66,6 +66,7 @@ static declarator_t
 	storage_specifier, thread_specifier;
 static declarator_t generic_qualifier;
 static declarator_t autotype_specifier, auto_specifier, constexpr_specifier;
+static declarator_t bitint_specifier;
 
 static struct token *parse_if_statement(struct token *token, struct statement *stmt);
 static struct token *parse_return_statement(struct token *token, struct statement *stmt);
@@ -113,7 +114,9 @@ enum {
 	Set_Long = 512,
 	Set_Vlong = 1024,
 	Set_Int128 = 2048,
-	Set_Any = Set_T | Set_Short | Set_Long | Set_Signed | Set_Unsigned
+	Set_BitInt = 4096,
+	Set_Any = Set_T | Set_Short | Set_Long | Set_Signed | Set_Unsigned |
+		Set_BitInt
 };
 
 enum {
@@ -282,7 +285,8 @@ static struct symbol_op float_op = {
 
 static struct symbol_op short_op = {
 	.type = KW_SPECIFIER,
-	.test = Set_S|Set_Char|Set_Float|Set_Double|Set_Long|Set_Short,
+	.test = Set_S | Set_Char | Set_Float | Set_Double | Set_Long |
+		Set_Short | Set_BitInt,
 	.set = Set_Short,
 };
 
@@ -302,17 +306,27 @@ static struct symbol_op unsigned_op = {
 
 static struct symbol_op long_op = {
 	.type = KW_SPECIFIER,
-	.test = Set_S|Set_Char|Set_Float|Set_Short|Set_Vlong,
+	.test = Set_S | Set_Char | Set_Float | Set_Short | Set_Vlong |
+		Set_BitInt,
 	.set = Set_Long,
 };
 
 static struct symbol_op int128_op = {
 	.type = KW_SPECIFIER,
-	.test = Set_S|Set_T|Set_Char|Set_Short|Set_Int|Set_Float|Set_Double|Set_Long|Set_Vlong|Set_Int128,
-	.set =  Set_T|Set_Int128|Set_Vlong,
+	.test = Set_S | Set_T | Set_Char | Set_Short | Set_Int | Set_Float |
+		Set_Double | Set_Long | Set_Vlong | Set_Int128 | Set_BitInt,
+	.set = Set_T | Set_Int128 | Set_Vlong,
 	.class = CInt,
 };
 
+static struct symbol_op bitint_op = {
+	.type = KW_SPECIFIER,
+	.test = Set_S | Set_T | Set_Char | Set_Short | Set_Int | Set_Float |
+		Set_Double | Set_Long | Set_Vlong | Set_Int128 | Set_BitInt,
+	.set = Set_T | Set_BitInt,
+	.declarator = bitint_specifier,
+};
+
 static struct symbol_op if_op = {
 	.statement = parse_if_statement,
 };
@@ -531,6 +545,7 @@ static struct init_keyword {
 	N("__int128",		&int128_op),
 	N("_Bool",		&spec_op,	.type = &bool_ctype),
 	N("bool",		&spec_op,	.type = &bool_ctype, .std = STANDARD_C23),
+	N("_BitInt",		&bitint_op),
 
 	/* Predeclared types */
 	N("__builtin_va_list",	&spec_op,	.type = &ptr_ctype),
@@ -843,7 +858,7 @@ static struct token *struct_union_enum_specifier(enum type type,
 			check_typename_storage(underlying);
 			underlying = examine_symbol_type(underlying);
 			if (!underlying || !is_int_type(underlying) ||
-			    is_enum_type(underlying)) {
+			    is_enum_type(underlying) || is_bitint_type(underlying)) {
 				sparse_error(token->pos,
 					     "invalid 'enum' underlying type");
 				underlying = &int_ctype;
@@ -927,7 +942,7 @@ static struct token *struct_union_enum_specifier(enum type type,
 			check_typename_storage(underlying);
 			underlying = examine_symbol_type(underlying);
 			if (!underlying || !is_int_type(underlying) ||
-			    is_enum_type(underlying)) {
+			    is_enum_type(underlying) || is_bitint_type(underlying)) {
 				sparse_error(token->pos,
 					     "invalid 'enum' underlying type");
 				underlying = &int_ctype;
@@ -1421,6 +1436,32 @@ static void apply_constexpr_const(struct symbol *sym)
 	}
 }
 
+static struct token *bitint_specifier(struct token *token, struct symbol *sym,
+				      struct decl_state *ctx)
+{
+	struct expression *expr = NULL;
+
+	if (standard < STANDARD_C23 && Wpedantic)
+		warning(token->pos, "'_BitInt' is a C23 feature");
+
+	ctx->bitint_pos = token->pos;
+	if (!match_op(token, '(')) {
+		sparse_error(token->pos, "expected '(' after '_BitInt'");
+		ctx->bitint_width = -1;
+		return token;
+	}
+	token = conditional_expression(token->next, &expr);
+	if (!expr) {
+		sparse_error(token->pos,
+			     "expected expression for _BitInt width");
+		ctx->bitint_width = -1;
+	} else {
+		ctx->bitint_width = const_expression_value(expr);
+	}
+	token = expect(token, ')', "after _BitInt width");
+	return token;
+}
+
 static struct token *ignore_attribute(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
 	struct expression *expr = NULL;
@@ -1837,7 +1878,7 @@ static void apply_ctype(struct position pos, struct ctype *dst, struct ctype *sr
 static void specifier_conflict(struct position pos, int what, struct ident *new)
 {
 	const char *old;
-	if (what & (Set_S | Set_T))
+	if (what & (Set_S | Set_T | Set_BitInt))
 		goto Catch_all;
 	if (what & Set_Char)
 		old = "char";
@@ -1984,6 +2025,37 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 		}
 	}
 
+	if (seen & Set_BitInt) {
+		int is_signed = !(seen & Set_Unsigned);
+		long long width = ctx->bitint_width;
+		struct position pos = ctx->bitint_pos;
+
+		if (width <= 0) {
+			sparse_error(pos, "invalid bit width %lld for _BitInt",
+				     width);
+			ctx->ctype.base_type = &bad_ctype;
+		} else if (is_signed && width < 2) {
+			sparse_error(pos,
+				     "signed _BitInt width must be at least 2");
+			ctx->ctype.base_type = &bad_ctype;
+		} else if (!is_signed && width < 1) {
+			sparse_error(
+				pos,
+				"unsigned _BitInt width must be at least 1");
+			ctx->ctype.base_type = &bad_ctype;
+		} else if (width > bits_in_longlonglong) {
+			sparse_error(
+				pos,
+				"_BitInt width %lld exceeds __BITINT_MAXWIDTH__ (%d)",
+				width, bits_in_longlonglong);
+			ctx->ctype.base_type = &bad_ctype;
+		} else {
+			ctx->ctype.base_type =
+				get_bitint_type(width, is_signed);
+		}
+		seen |= Set_S;
+	}
+
 	if (!(seen & Set_S)) {	/* not set explicitly? */
 		struct symbol *base = &incomplete_ctype;
 		if (seen & Set_Any)
@@ -2425,6 +2497,7 @@ static struct token *handle_bitfield(struct token *token, struct decl_state *ctx
 		}
 		if (Wdefault_bitfield_sign &&
 		    bitfield_type->type != SYM_ENUM &&
+		    !is_bitint_type(bitfield_type) &&
 		    !(bitfield_type->ctype.modifiers & MOD_EXPLICITLY_SIGNED) &&
 		    is_signed) {
 			// The sign of bitfields is unspecified by default.
diff --git a/predefine.c b/predefine.c
index 5639fc6862e3..3ead07003637 100644
--- a/predefine.c
+++ b/predefine.c
@@ -128,6 +128,7 @@ void predefined_macros(void)
 	predefine("__STDC_EMBED_EMPTY__", 1, "2");
 
 	predefine("__CHAR_BIT__", 1, "%d", bits_in_char);
+	predefine("__BITINT_MAXWIDTH__", 1, "%d", bits_in_longlonglong);
 	if (funsigned_char)
 		predefine("__CHAR_UNSIGNED__", 1, "1");
 
diff --git a/show-parse.c b/show-parse.c
index 0a277ef67aac..9ed95f141cfe 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -350,6 +350,15 @@ deeper:
 	if (!sym)
 		goto out;
 
+	if (sym->type == SYM_BASETYPE && is_bitint_type(sym)) {
+		if (sym->ctype.modifiers & MOD_UNSIGNED)
+			prepend(name, "unsigned _BitInt(%d)",
+				sym->bitint_width);
+		else
+			prepend(name, "_BitInt(%d)", sym->bitint_width);
+		goto out;
+	}
+
 	if ((typename = builtin_typename(sym))) {
 		if (name->start != name->end)
 			prepend(name, " ");
diff --git a/sparse-llvm.c b/sparse-llvm.c
index 7d15d7a70e54..b6a0b6ce9134 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -1270,6 +1270,14 @@ static LLVMValueRef expr_to_const_val(LLVMModuleRef module, struct symbol *sym,
 
 	switch (initializer->type) {
 	case EXPR_VALUE:
+		if (effective_int_width(initializer->ctype) > 64 &&
+		    LLVMGetTypeKind(dtype) == LLVMIntegerTypeKind) {
+			uint64_t words[2] = {
+				initializer->value,
+				initializer->value_hi,
+			};
+			return LLVMConstIntOfArbitraryPrecision(dtype, 2, words);
+		}
 		return constant_value(initializer->value, dtype);
 	case EXPR_FVALUE:
 		return LLVMConstReal(dtype, initializer->fvalue);
diff --git a/symbol.c b/symbol.c
index 270e91d9c480..721dd1e81ae5 100644
--- a/symbol.c
+++ b/symbol.c
@@ -389,7 +389,7 @@ static struct symbol *examine_bitfield_type(struct symbol *sym)
 
 	if (!base_type)
 		return sym;
-	if (sym->bit_size > base_type->bit_size) {
+	if (sym->bit_size > effective_int_width(base_type)) {
 		sparse_error(sym->pos, "bitfield '%s' is wider (%d) than its type (%s)",
 			show_ident(sym->ident), sym->bit_size, show_typename(base_type));
 		sym->bit_size = -1;
@@ -401,7 +401,8 @@ static struct symbol *examine_bitfield_type(struct symbol *sym)
 	modifiers = base_type->ctype.modifiers;
 
 	/* use -funsigned-bitfields to determine the sign if not explicit */
-	if (!(modifiers & MOD_EXPLICITLY_SIGNED) && funsigned_bitfields)
+	if (!(modifiers & MOD_EXPLICITLY_SIGNED) &&
+	    !is_bitint_type(base_type) && funsigned_bitfields)
 		modifiers = (modifiers & ~MOD_SIGNED) | MOD_UNSIGNED;
 	sym->ctype.modifiers |= modifiers & MOD_SIGNEDNESS;
 	return sym;
@@ -456,7 +457,7 @@ static int count_array_initializer(struct symbol *t, struct expression *expr)
 	 * on T - if it's a character type, we get the length of string literal
 	 * (including NUL), otherwise we have one element here.
 	 */
-	if (t->ctype.base_type == &int_type && t->rank == -2)
+	if (t->ctype.base_type == &int_type && t->rank == -2 && !is_bitint_type(t))
 		is_char = 1;
 	else if (t == wchar_ctype && is_wstring_expr(expr))
 		is_char = 1;
@@ -581,6 +582,7 @@ static struct symbol * examine_node_type(struct symbol *sym)
 	}
 	
 	sym->bit_size = bit_size;
+	sym->bitint_width = base_type->bitint_width;
 	sym->rank = base_type->rank;
 	return sym;
 }
@@ -1135,3 +1137,49 @@ void init_ctype(void)
 	const_wchar_ctype.ctype.alignment = wchar_ctype->ctype.alignment;
 	const_wchar_ctype.bit_size = wchar_ctype->bit_size;
 }
+
+struct symbol *get_bitint_type(int width, int is_signed)
+{
+	static struct symbol *bitint_types[2][129];
+	struct position pos = { .stream = 0 };
+	struct symbol *sym;
+	int u = !is_signed;
+
+	if (width < 1 || width > 128)
+		return &bad_ctype;
+
+	sym = bitint_types[u][width];
+	if (sym)
+		return sym;
+
+	sym = alloc_symbol(pos, SYM_BASETYPE);
+	sym->ctype.base_type = &int_type;
+	sym->ctype.modifiers = (is_signed ? MOD_SIGNED : MOD_UNSIGNED) |
+			       MOD_BITINT;
+	sym->bitint_width = width;
+
+	if (width <= 8) {
+		sym->bit_size = 8;
+		sym->ctype.alignment = 1;
+		sym->rank = -2;
+	} else if (width <= 16) {
+		sym->bit_size = 16;
+		sym->ctype.alignment = 2;
+		sym->rank = -1;
+	} else if (width <= 32) {
+		sym->bit_size = 32;
+		sym->ctype.alignment = 4;
+		sym->rank = 0;
+	} else if (width <= 64) {
+		sym->bit_size = 64;
+		sym->ctype.alignment = 8;
+		sym->rank = 2;
+	} else {
+		sym->bit_size = 128;
+		sym->ctype.alignment = 8;
+		sym->rank = 3;
+	}
+
+	bitint_types[u][width] = sym;
+	return sym;
+}
diff --git a/symbol.h b/symbol.h
index 435fa18a4990..bb063dc622e0 100644
--- a/symbol.h
+++ b/symbol.h
@@ -122,6 +122,8 @@ struct decl_state {
 	unsigned char designated_init;
 	unsigned char transparent_union;
 	unsigned char opaque_fixed_enum;
+	long long bitint_width;
+	struct position bitint_pos;
 	struct position opaque_fixed_enum_pos;
 };
 
@@ -189,14 +191,25 @@ struct symbol {
 		struct /* NS_SYMBOL */ {
 			unsigned long	offset;
 			int		bit_size;
-			unsigned int bit_offset : 8, bogus_linear : 1,
-				variadic : 1, initialized : 1, examined : 1,
-				expanding : 1, evaluated : 1,
-				has_flex_array : 1, string : 1,
-				designated_init : 1, forced_arg : 1,
-				accessed : 1, builtin : 1, torename : 1,
-				packed : 1, transparent_union : 1,
-				fixed_type : 1, explicit_typedef : 1;
+			int bitint_width;
+			unsigned int bit_offset : 8;
+			unsigned int bogus_linear : 1;
+			unsigned int variadic : 1;
+			unsigned int initialized : 1;
+			unsigned int examined : 1;
+			unsigned int expanding : 1;
+			unsigned int evaluated : 1;
+			unsigned int has_flex_array : 1;
+			unsigned int string : 1;
+			unsigned int designated_init : 1;
+			unsigned int forced_arg : 1;
+			unsigned int accessed : 1;
+			unsigned int builtin : 1;
+			unsigned int torename : 1;
+			unsigned int packed : 1;
+			unsigned int transparent_union : 1;
+			unsigned int fixed_type : 1;
+			unsigned int explicit_typedef : 1;
 			unsigned int has_alignas : 1;
 			unsigned int kr_args : 1;
 			unsigned int underspecified : 1;
@@ -252,7 +265,7 @@ struct symbol {
 #define MOD_GNU_INLINE		0x00010000
 #define MOD_USERTYPE		0x00020000
 #define MOD_CONSTEXPR 0x00040000
-// MOD UNUSED		0x00080000
+#define MOD_BITINT 0x00080000
 // MOD UNUSED		0x00100000
 // MOD UNUSED		0x00200000
 
@@ -271,7 +284,7 @@ struct symbol {
 #define MOD_STORAGE	(MOD_AUTO | MOD_REGISTER | MOD_STATIC | MOD_EXTERN | MOD_INLINE | MOD_TOPLEVEL)
 #define MOD_ESIGNED	(MOD_SIGNED | MOD_EXPLICITLY_SIGNED)
 #define MOD_SIGNEDNESS	(MOD_SIGNED | MOD_UNSIGNED | MOD_EXPLICITLY_SIGNED)
-#define MOD_SPECIFIER	MOD_SIGNEDNESS
+#define MOD_SPECIFIER (MOD_SIGNEDNESS | MOD_BITINT)
 #define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE | MOD_CONSTEXPR)
 #define MOD_QUALIFIER	(MOD_CONST | MOD_VOLATILE | MOD_RESTRICT)
 /* modifiers for function attributes */
@@ -412,6 +425,29 @@ static inline int is_signed_type(struct symbol *sym)
 	return !(sym->ctype.modifiers & MOD_UNSIGNED);
 }
 
+static inline int is_bitint_type(const struct symbol *sym)
+{
+	while (sym && sym->type == SYM_NODE)
+		sym = sym->ctype.base_type;
+	return sym && (sym->ctype.modifiers & MOD_BITINT);
+}
+
+static inline int effective_int_width(const struct symbol *type)
+{
+	if (!type)
+		return 0;
+	while (type->type == SYM_NODE) {
+		type = type->ctype.base_type;
+		if (!type)
+			return 0;
+	}
+	if ((type->ctype.modifiers & MOD_BITINT) && type->bitint_width > 0)
+		return type->bitint_width;
+	return type->bit_size;
+}
+
+extern struct symbol *get_bitint_type(int width, int is_signed);
+
 static inline int is_type_type(struct symbol *type)
 {
 	return type == &type_ctype;
@@ -468,7 +504,8 @@ static inline int is_float_type(struct symbol *type)
 
 static inline int is_byte_type(struct symbol *type)
 {
-	return type->bit_size == bits_in_char && type->type != SYM_BITFIELD;
+	return type->bit_size == bits_in_char && type->type != SYM_BITFIELD &&
+	       !is_bitint_type(type);
 }
 
 static inline int is_wchar_type(struct symbol *type)
@@ -575,7 +612,7 @@ static inline int get_sym_type(struct symbol *type)
 static inline long long extend_value(long long val, struct symbol *ctype)
 {
 	int is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
-	unsigned size = ctype->bit_size;
+	unsigned size = effective_int_width(ctype);
 
 	return bits_extend(val, size, is_signed);
 }
diff --git a/validation/c23-bitint-error.c b/validation/c23-bitint-error.c
new file mode 100644
index 000000000000..1172334b3a71
--- /dev/null
+++ b/validation/c23-bitint-error.c
@@ -0,0 +1,61 @@
+static _BitInt(1) a;
+static unsigned _BitInt(0) b;
+static _BitInt(200) c;
+static _BitInt(8) bad_str[] = "abc";
+static struct bad_bf {
+	_BitInt(5) x : 7;
+} bf_instance;
+static _BitInt(8) bad_wb1 = 1wB;
+static _BitInt(8) bad_wb2 = 1Wb;
+enum E_bitint : _BitInt(8) { BI = 0 };
+static constexpr _BitInt(4) bad_ce_bi = 8;
+
+static void ptr_errors(_BitInt(8) *p8, _BitInt(5) *p5, char *pc,
+		       signed char *psc, _BitInt(7) *p7)
+{
+	p8 = pc;
+	p8 = psc;
+	p5 = p7;
+}
+
+static int _BitInt(5) d;
+static _BitInt(5) short e;
+static _BitInt(128) bad_wb_ovf = 0x80000000000000000000000000000000wb;
+static unsigned _BitInt(128) bad_uwb_ovf = 0x100000000000000000000000000000000uwb;
+
+/*
+ * check-name: C23 _BitInt errors
+ * check-command: sparse -std=c23 -Wsparse-error $file
+ * check-exit-value: 1
+ *
+ * check-error-start
+c23-bitint-error.c:1:15: error: signed _BitInt width must be at least 2
+c23-bitint-error.c:2:24: error: invalid bit width 0 for _BitInt
+c23-bitint-error.c:3:15: error: _BitInt width 200 exceeds __BITINT_MAXWIDTH__ (128)
+c23-bitint-error.c:8:29: error: constant 1wB is not a valid number
+c23-bitint-error.c:9:29: error: constant 1Wb is not a valid number
+c23-bitint-error.c:10:28: error: invalid 'enum' underlying type
+c23-bitint-error.c:21:12: error: two or more data types in declaration specifiers
+c23-bitint-error.c:21:20: error: Expected ) in function declarator
+c23-bitint-error.c:21:20: error: got 5
+c23-bitint-error.c:21:12: error: Trying to use reserved word '_BitInt' as identifier
+c23-bitint-error.c:22:19: error: two or more data types in declaration specifiers
+c23-bitint-error.c:22:19: error: Trying to use reserved word 'short' as identifier
+c23-bitint-error.c:22:25: error: Expected ; at end of declaration
+c23-bitint-error.c:22:25: error: got e
+c23-bitint-error.c:23:34: error: constant 0x80000000000000000000000000000000wb is too big for _BitInt(128)
+c23-bitint-error.c:24:44: error: constant 0x100000000000000000000000000000000uwb is too big for unsigned _BitInt(128)
+c23-bitint-error.c:4:31: error: invalid initializer
+c23-bitint-error.c:6:22: error: bitfield 'x' is wider (7) than its type (_BitInt(5))
+c23-bitint-error.c:11:41: error: constexpr initializer not representable in target type
+c23-bitint-error.c:16:12: error: incorrect type in assignment (different base types)
+c23-bitint-error.c:16:12:    expected _BitInt(8)*p8
+c23-bitint-error.c:16:12:    got char *pc
+c23-bitint-error.c:17:12: error: incorrect type in assignment (different base types)
+c23-bitint-error.c:17:12:    expected _BitInt(8)*p8
+c23-bitint-error.c:17:12:    got signed char *psc
+c23-bitint-error.c:18:12: error: incorrect type in assignment (different bit widths)
+c23-bitint-error.c:18:12:    expected _BitInt(5)*p5
+c23-bitint-error.c:18:12:    got _BitInt(7)*p7
+ * check-error-end
+ */
diff --git a/validation/c23-bitint-pedantic.c b/validation/c23-bitint-pedantic.c
new file mode 100644
index 000000000000..d564d3c134b9
--- /dev/null
+++ b/validation/c23-bitint-pedantic.c
@@ -0,0 +1,13 @@
+static _BitInt(10) a;
+static auto b = 42wb;
+
+/*
+ * check-name: C23 _BitInt pedantic
+ * check-command: sparse -std=c17 -Wpedantic $file
+ *
+ * check-error-start
+c23-bitint-pedantic.c:1:15: warning: '_BitInt' is a C23 feature
+c23-bitint-pedantic.c:2:13: warning: 'auto' type inference is a C23 feature
+c23-bitint-pedantic.c:2:17: warning: bit-precise integer suffix is a C23 feature
+ * check-error-end
+ */
diff --git a/validation/c23-bitint.c b/validation/c23-bitint.c
new file mode 100644
index 000000000000..dd86d23ac48e
--- /dev/null
+++ b/validation/c23-bitint.c
@@ -0,0 +1,214 @@
+/*
+ * Check C23 bit-precise integer types: _BitInt(N) and unsigned _BitInt(N)
+ */
+
+_Static_assert(__BITINT_MAXWIDTH__ >= 64, "BITINT_MAXWIDTH");
+
+/* Basic declarations */
+static _BitInt(2) s2;
+static _BitInt(7) s7;
+static _BitInt(8) s8;
+static _BitInt(9) s9;
+static _BitInt(16) s16;
+static _BitInt(24) s24;
+static _BitInt(32) s32;
+static _BitInt(40) s40;
+static _BitInt(64) s64;
+static _BitInt(65) s65;
+static _BitInt(128) s128;
+
+static unsigned _BitInt(1) u1;
+static unsigned _BitInt(7) u7;
+static unsigned _BitInt(8) u8;
+static unsigned _BitInt(9) u9;
+static unsigned _BitInt(16) u16;
+static unsigned _BitInt(24) u24;
+static unsigned _BitInt(32) u32;
+static unsigned _BitInt(40) u40;
+static unsigned _BitInt(64) u64;
+static unsigned _BitInt(65) u65;
+static unsigned _BitInt(128) u128;
+
+/* Sizing */
+_Static_assert(sizeof(s2) == 1, "s2 size");
+_Static_assert(sizeof(s7) == 1, "s7 size");
+_Static_assert(sizeof(s8) == 1, "s8 size");
+_Static_assert(sizeof(s9) == 2, "s9 size");
+_Static_assert(sizeof(s16) == 2, "s16 size");
+_Static_assert(sizeof(s24) == 4, "s24 size");
+_Static_assert(sizeof(s32) == 4, "s32 size");
+_Static_assert(sizeof(s40) == 8, "s40 size");
+_Static_assert(sizeof(s64) == 8, "s64 size");
+_Static_assert(sizeof(s65) == 16, "s65 size");
+_Static_assert(sizeof(s128) == 16, "s128 size");
+
+_Static_assert(sizeof(u1) == 1, "u1 size");
+_Static_assert(sizeof(u7) == 1, "u7 size");
+_Static_assert(sizeof(u8) == 1, "u8 size");
+_Static_assert(sizeof(u9) == 2, "u9 size");
+_Static_assert(sizeof(u16) == 2, "u16 size");
+_Static_assert(sizeof(u24) == 4, "u24 size");
+_Static_assert(sizeof(u32) == 4, "u32 size");
+_Static_assert(sizeof(u40) == 8, "u40 size");
+_Static_assert(sizeof(u64) == 8, "u64 size");
+_Static_assert(sizeof(u65) == 16, "u65 size");
+_Static_assert(sizeof(u128) == 16, "u128 size");
+
+/* Alignment */
+_Static_assert(_Alignof(_BitInt(7)) == 1, "align 7");
+_Static_assert(_Alignof(_BitInt(12)) == 2, "align 12");
+_Static_assert(_Alignof(_BitInt(24)) == 4, "align 24");
+_Static_assert(_Alignof(_BitInt(40)) == 8, "align 40");
+_Static_assert(_Alignof(_BitInt(65)) == 8, "align 65");
+
+/* wb and uwb literal suffixes */
+_Static_assert(_Generic(0wb, _BitInt(2): 1, default: 0) == 1, "0wb");
+_Static_assert(_Generic(1wb, _BitInt(2): 1, default: 0) == 1, "1wb");
+_Static_assert(_Generic(1WB, _BitInt(2): 1, default: 0) == 1, "1WB");
+_Static_assert(_Generic(0uwb, unsigned _BitInt(1): 1, default: 0) == 1, "0uwb");
+_Static_assert(_Generic(1uwb, unsigned _BitInt(1): 1, default: 0) == 1, "1uwb");
+_Static_assert(_Generic(1UWB, unsigned _BitInt(1): 1, default: 0) == 1, "1UWB");
+_Static_assert(_Generic(1wbU, unsigned _BitInt(1): 1, default: 0) == 1, "1wbU");
+_Static_assert(_Generic(42wb, _BitInt(7): 1, default: 0) == 1, "42wb");
+_Static_assert(_Generic(42uwb, unsigned _BitInt(6): 1, default: 0) == 1,
+	       "42uwb");
+
+/* Type compatibility and distinctness */
+_Static_assert(_Generic(s8, _BitInt(8): 1, char: 0, signed char: 0, default: 0) == 1,
+	       "distinct s8");
+_Static_assert(_Generic(u8, unsigned _BitInt(8): 1, unsigned char: 0, default: 0) == 1,
+	       "distinct u8");
+_Static_assert(_Generic(s24, _BitInt(24): 1, default: 0) == 1, "distinct s24");
+_Static_assert(_Generic(s32, _BitInt(32): 1, int: 0, default: 0) == 1,
+	       "distinct s32");
+_Static_assert(_Generic(u32,
+			       unsigned _BitInt(32): 1,
+			       unsigned int: 0,
+			       default: 0) == 1,
+	       "distinct u32");
+
+/* Compound types and bit-fields */
+struct S {
+	_BitInt(7) a;
+	unsigned _BitInt(9) b;
+};
+_Static_assert(sizeof(struct S) == 4, "struct size");
+
+struct BF {
+	_BitInt(5) sbf : 3;
+	unsigned _BitInt(5) ubf : 3;
+};
+static struct BF bf_val;
+_Static_assert(_Generic(+bf_val.sbf, _BitInt(5): 1, default: 0) == 1,
+	       "signed _BitInt bitfield");
+_Static_assert(_Generic(+bf_val.ubf, unsigned _BitInt(5): 1, default: 0) == 1,
+	       "unsigned _BitInt bitfield");
+
+static _BitInt(10) arr[5];
+_Static_assert(sizeof(arr) == 5 * sizeof(_BitInt(10)), "array size");
+
+/* Exact-width masking and >64-bit constant folding */
+_Static_assert((unsigned _BitInt(3))7uwb + 1uwb == 0uwb, "u3 wrap");
+_Static_assert(~0uwb == 1uwb, "u1 ~0");
+_Static_assert((unsigned _BitInt(5))~0wb == 31uwb, "u5 ~0wb");
+_Static_assert((unsigned _BitInt(33))0x1ffffffffULL + 1uwb == 0uwb, "u33 wrap");
+_Static_assert((_BitInt(4))-8 == -8, "s4 min");
+
+_Static_assert(_Generic(0x8000000000000000wb, _BitInt(65): 1, default: 0) == 1,
+	       "2^63 wb width is 65");
+_Static_assert(0x8000000000000000wb > 0wb, "+2^63 > 0");
+_Static_assert(0x8000000000000000wb > -1wb, "+2^63 > -1");
+_Static_assert((_BitInt(65))0xffffffffffffffffULL > 0wb, "2^64-1 in s65 > 0");
+_Static_assert((_BitInt(65))0xffffffffffffffffULL != -1wb, "2^64-1 in s65 != -1");
+_Static_assert(-0x8000000000000000wb < 0wb, "-2^63 < 0");
+_Static_assert(0x8000000000000000wb + (-0x8000000000000000wb) == 0wb,
+	       "+2^63 + -2^63 == 0");
+_Static_assert(_Generic(0x1'00000000'00000000uwb,
+			unsigned _BitInt(65): 1, default: 0) == 1,
+	       "2^64 uwb width is 65");
+_Static_assert(_Generic(0x1'00000000'00000000wb,
+			_BitInt(66): 1, default: 0) == 1,
+	       "2^64 wb width is 66");
+_Static_assert(_Generic(0x7fffffffffffffff'ffffffffffffffffwb,
+			_BitInt(128): 1, default: 0) == 1,
+	       "2^127-1 wb width is 128");
+_Static_assert(_Generic(0xffffffffffffffff'ffffffffffffffffuwb,
+			unsigned _BitInt(128): 1, default: 0) == 1,
+	       "2^128-1 uwb width is 128");
+_Static_assert(0x1'00000000'00000000uwb > 0xffffffffffffffffuwb, "2^64 > 2^64-1");
+_Static_assert((0x1'00000000'00000000uwb >> 64) == 1uwb, "2^64 >> 64 == 1");
+_Static_assert(((unsigned _BitInt(128))1 << 100) >> 100 == 1, "1 << 100 >> 100");
+_Static_assert(~(unsigned _BitInt(100))0 ==
+	       ((unsigned _BitInt(128))1 << 100) - 1, "~u100");
+
+/* Arithmetic conversions and lack of integer promotion (C23 6.3.1.1, 6.3.1.8) */
+static void test_ops(void)
+{
+	_BitInt(40) a = 1;
+	_BitInt(50) b = 2;
+	_Static_assert(_Generic(a + b, _BitInt(50): 1, default: 0) == 1,
+		       "a + b");
+
+	unsigned _BitInt(40) u = 1;
+	_Static_assert(_Generic(u + a, unsigned _BitInt(40): 1, default: 0) ==
+			       1,
+		       "u + a");
+	_Static_assert(_Generic(u + b, _BitInt(50): 1, default: 0) == 1,
+		       "u + b");
+
+	_BitInt(10) x = 1;
+	_BitInt(10) y = 2;
+	_Static_assert(_Generic(x + y, _BitInt(10): 1, default: 0) == 1,
+		       "narrow _BitInt does not promote to int");
+	_Static_assert(_Generic(+x, _BitInt(10): 1, default: 0) == 1,
+		       "unary + does not promote _BitInt");
+
+	_BitInt(32) z = 1;
+	_Static_assert(_Generic(z + z, _BitInt(32): 1, default: 0) == 1,
+		       "no promotion");
+
+	int si = 1;
+	unsigned int ui = 1;
+	unsigned _BitInt(32) uz = 1;
+	unsigned _BitInt(31) u31 = 1;
+	_BitInt(33) s33 = 1;
+	unsigned _BitInt(8) ub8 = 1;
+	_BitInt(16) sb16 = 1;
+	unsigned _BitInt(16) ub16 = 1;
+
+	_Static_assert(_Generic(z + si, int: 1, default: 0) == 1,
+		       "_BitInt(32) + int -> int");
+	_Static_assert(_Generic(uz + ui, unsigned int: 1, default: 0) == 1,
+		       "u_BitInt(32) + uint -> uint");
+	_Static_assert(_Generic(z + ui, unsigned int: 1, default: 0) == 1,
+		       "_BitInt(32) + uint -> uint");
+	_Static_assert(_Generic(uz + si, unsigned int: 1, default: 0) == 1,
+		       "u_BitInt(32) + int -> uint");
+	_Static_assert(_Generic(u31 + si, int: 1, default: 0) == 1,
+		       "u_BitInt(31) + int -> int");
+	_Static_assert(_Generic(s33 + si, _BitInt(33): 1, default: 0) == 1,
+		       "_BitInt(33) + int -> _BitInt(33)");
+	_Static_assert(_Generic(ub8 + sb16, _BitInt(16): 1, default: 0) == 1,
+		       "u_BitInt(8) + _BitInt(16) -> _BitInt(16)");
+	_Static_assert(_Generic(ub16 + sb16, unsigned _BitInt(16): 1, default: 0) == 1,
+		       "u_BitInt(16) + _BitInt(16) -> u_BitInt(16)");
+
+	constexpr _BitInt(8) ce_bi8 = -128wb;
+	constexpr _BitInt(70) ce_bi70 = 0x8000000000000000wb;
+	_Static_assert(ce_bi8 == -128, "constexpr _BitInt(8)");
+	_Static_assert(ce_bi70 > 0, "constexpr _BitInt(70) >64-bit positive");
+}
+
+struct bitint128_bf {
+	unsigned _BitInt(128) f : 100;
+};
+
+static unsigned _BitInt(65) get_bitint65(void)
+{
+	return 1uwb;
+}
+
+/*
+ * check-name: C23 _BitInt(N)
+ * check-command: sparse -std=c23 $file
+ */
diff --git a/validation/expand-bitint.c b/validation/expand-bitint.c
new file mode 100644
index 000000000000..99122f0f401f
--- /dev/null
+++ b/validation/expand-bitint.c
@@ -0,0 +1,49 @@
+/*
+ * Expand constant folding tests for C23 _BitInt(N) widths != ABI bit_size
+ * and widths > 64 bits.
+ */
+
+_Static_assert((~(unsigned _BitInt(37))0) == 0x1fffffffffULL, "~u37");
+_Static_assert(((unsigned _BitInt(37))0x1fffffffffULL +
+		(unsigned _BitInt(37))1) == 0, "wrap u37");
+_Static_assert((~(_BitInt(37))0) == -1, "~s37");
+_Static_assert(-(_BitInt(128))0x8000000000000000ULL < 0, "-2^63 < 0");
+
+int cmp_u128_max(void)
+{
+	unsigned _BitInt(128) hi = (unsigned _BitInt(128))-1;
+	unsigned _BitInt(128) lo = (unsigned _BitInt(128))0xffffffffffffffffULL;
+
+	return hi == lo;
+}
+
+int neg_s128_bit63(void)
+{
+	_BitInt(128) x = (_BitInt(128))0x8000000000000000ULL;
+	_BitInt(128) y = -x;
+
+	return y < 0;
+}
+
+_BitInt(128) neg_s128_var(_BitInt(128) x)
+{
+	return -x;
+}
+
+double cast_u128_max_to_double(void)
+{
+	unsigned _BitInt(128) hi = (unsigned _BitInt(128))-1;
+
+	return (double)hi;
+}
+
+/*
+ * check-name: expand-bitint
+ * check-command: test-linearize -Wno-decl -std=c23 $file
+ *
+ * check-output-ignore
+ * check-output-contains: seteq\\.
+ * check-output-contains: ret\\.32\\s*\\$1
+ * check-output-contains: neg\\.128
+ * check-output-contains: ucvtf\\.64
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 62/73] enum: Support machine mode attributes on enumeration types
  2026-11-09 20:31 Bart Van Assche
                   ` (60 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
                   ` (10 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support GCC machine mode attributes (such as mode(__byte__)) on both
enum declarations and enum type definitions:
- Apply prefix and postfix mode attributes on enum definitions to the
  enum's underlying type, size, alignment, and rank when no C23 fixed
  underlying type is present.
- Inspect underlying SYM_NODE and SYM_ENUM signedness in to_*_mode()
  so unsigned enums preserve unsignedness when converted to a mode.
- Expand validation/enum+mode.c and remove check-known-to-fail.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 parse.c                | 84 ++++++++++++++++++++++++++++++------------
 symbol.c               |  2 +-
 symbol.h               |  1 +
 validation/enum+mode.c | 26 ++++++++++++-
 4 files changed, 87 insertions(+), 26 deletions(-)

diff --git a/parse.c b/parse.c
index d89f60113152..b686d2d55c36 100644
--- a/parse.c
+++ b/parse.c
@@ -992,6 +992,20 @@ static struct token *struct_union_enum_specifier(enum type type,
 		else
 			warning(token->pos, "attribute __transparent_union__ applied to non-union type");
 	}
+	if (sym->type == SYM_ENUM && attr.mode && attr.mode->to_mode &&
+	    !sym->fixed_type && sym->ctype.base_type &&
+	    is_int_type(sym->ctype.base_type)) {
+		struct symbol *mtype = attr.mode->to_mode(sym);
+		if (mtype) {
+			sym->ctype.base_type = mtype;
+			sym->has_mode = 1;
+			sym->ctype.modifiers &= ~MOD_SIGNEDNESS;
+			sym->ctype.modifiers |= (mtype->ctype.modifiers & MOD_SIGNEDNESS);
+			sym->bit_size = mtype->bit_size;
+			sym->ctype.alignment = mtype->ctype.alignment;
+			sym->rank = mtype->rank;
+		}
+	}
 
 	sym->endpos = token->pos;
 
@@ -1599,62 +1613,86 @@ static struct token *attribute_address_space(struct token *token, struct symbol
 	return token;
 }
 
+static inline bool is_int_or_enum(struct symbol *ctype)
+{
+	if (!ctype)
+		return false;
+	if (ctype->type == SYM_ENUM)
+		return true;
+	if (ctype->ctype.base_type == &int_type)
+		return true;
+	if (ctype->type == SYM_NODE && ctype->ctype.base_type &&
+	    (ctype->ctype.base_type->type == SYM_ENUM ||
+	     ctype->ctype.base_type->ctype.base_type == &int_type))
+		return true;
+	return false;
+}
+
+static inline bool is_unsigned_mode_type(struct symbol *ctype)
+{
+	if (!ctype)
+		return false;
+	if (ctype->type == SYM_NODE || ctype->type == SYM_ENUM)
+		examine_symbol_type(ctype);
+	while (ctype && (ctype->type == SYM_NODE || ctype->type == SYM_ENUM)) {
+		if (ctype->ctype.modifiers & MOD_UNSIGNED)
+			return true;
+		if (ctype->ctype.modifiers & MOD_SIGNED)
+			return false;
+		ctype = ctype->ctype.base_type;
+	}
+	return ctype && (ctype->ctype.modifiers & MOD_UNSIGNED);
+}
+
 static struct symbol *to_QI_mode(struct symbol *ctype)
 {
-	if (ctype->ctype.base_type != &int_type)
+	if (!is_int_or_enum(ctype))
 		return NULL;
-	if (ctype == &char_ctype)
-		return ctype;
-	return ctype->ctype.modifiers & MOD_UNSIGNED ? &uchar_ctype
-						     : &schar_ctype;
+	if (cbase_type(ctype) == &char_ctype)
+		return &char_ctype;
+	return is_unsigned_mode_type(ctype) ? &uchar_ctype : &schar_ctype;
 }
 
 static struct symbol *to_HI_mode(struct symbol *ctype)
 {
-	if (ctype->ctype.base_type != &int_type)
+	if (!is_int_or_enum(ctype))
 		return NULL;
-	return ctype->ctype.modifiers & MOD_UNSIGNED ? &ushort_ctype
-						     : &sshort_ctype;
+	return is_unsigned_mode_type(ctype) ? &ushort_ctype : &sshort_ctype;
 }
 
 static struct symbol *to_SI_mode(struct symbol *ctype)
 {
-	if (ctype->ctype.base_type != &int_type)
+	if (!is_int_or_enum(ctype))
 		return NULL;
-	return ctype->ctype.modifiers & MOD_UNSIGNED ? &uint_ctype
-						     : &sint_ctype;
+	return is_unsigned_mode_type(ctype) ? &uint_ctype : &sint_ctype;
 }
 
 static struct symbol *to_DI_mode(struct symbol *ctype)
 {
-	if (ctype->ctype.base_type != &int_type)
+	if (!is_int_or_enum(ctype))
 		return NULL;
-	return ctype->ctype.modifiers & MOD_UNSIGNED ? &ullong_ctype
-						     : &sllong_ctype;
+	return is_unsigned_mode_type(ctype) ? &ullong_ctype : &sllong_ctype;
 }
 
 static struct symbol *to_TI_mode(struct symbol *ctype)
 {
-	if (ctype->ctype.base_type != &int_type)
+	if (!is_int_or_enum(ctype))
 		return NULL;
-	return ctype->ctype.modifiers & MOD_UNSIGNED ? &uint128_ctype
-						     : &sint128_ctype;
+	return is_unsigned_mode_type(ctype) ? &uint128_ctype : &sint128_ctype;
 }
 
 static struct symbol *to_pointer_mode(struct symbol *ctype)
 {
-	if (ctype->ctype.base_type != &int_type)
+	if (!is_int_or_enum(ctype))
 		return NULL;
-	return ctype->ctype.modifiers & MOD_UNSIGNED ? uintptr_ctype
-						     :  intptr_ctype;
+	return is_unsigned_mode_type(ctype) ? uintptr_ctype : intptr_ctype;
 }
 
 static struct symbol *to_word_mode(struct symbol *ctype)
 {
-	if (ctype->ctype.base_type != &int_type)
+	if (!is_int_or_enum(ctype))
 		return NULL;
-	return ctype->ctype.modifiers & MOD_UNSIGNED ? &ulong_ctype
-						     : &slong_ctype;
+	return is_unsigned_mode_type(ctype) ? &ulong_ctype : &slong_ctype;
 }
 
 static struct token *attribute_mode(struct token *token, struct symbol *attr, struct decl_state *ctx)
diff --git a/symbol.c b/symbol.c
index 721dd1e81ae5..d4b945d9e161 100644
--- a/symbol.c
+++ b/symbol.c
@@ -592,7 +592,7 @@ static struct symbol *examine_enum_type(struct symbol *sym)
 	struct symbol *base_type = examine_base_type(sym);
 
 	sym->ctype.modifiers |= (base_type->ctype.modifiers & MOD_SIGNEDNESS);
-	if (sym->fixed_type) {
+	if (sym->fixed_type || sym->has_mode) {
 		if (base_type == &bool_ctype)
 			sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
 		else
diff --git a/symbol.h b/symbol.h
index bb063dc622e0..aa180ac4f514 100644
--- a/symbol.h
+++ b/symbol.h
@@ -214,6 +214,7 @@ struct symbol {
 			unsigned int kr_args : 1;
 			unsigned int underspecified : 1;
 			unsigned int c23_auto : 1;
+			unsigned int has_mode : 1;
 			int		rank:3;	// arithmetic's rank
 			struct expression *array_size;
 			struct ctype ctype;
diff --git a/validation/enum+mode.c b/validation/enum+mode.c
index 182af1895d49..cfd037e8617b 100644
--- a/validation/enum+mode.c
+++ b/validation/enum+mode.c
@@ -3,6 +3,7 @@ enum e { ZERO, ONE, TWO };
 struct s {
 	enum e __attribute__ ((mode(__byte__))) b;
 	enum e __attribute__ ((mode(__word__))) w;
+	enum e __attribute__ ((mode(__DI__))) d;
 	enum e __attribute__ ((mode(__TI__))) t;
 };
 
@@ -10,9 +11,30 @@ static struct s s;
 
 _Static_assert(sizeof(s.b) == 1, "");
 _Static_assert(sizeof(s.w) == sizeof(long), "");
-_Static_assert(sizeof(s.t) == sizeof(long long), "");
+_Static_assert(sizeof(s.d) == sizeof(long long), "");
+_Static_assert(sizeof(s.t) == 16, "");
+
+typedef enum { M_ZERO, M_ONE } __attribute__((mode(__byte__))) byte_enum_t;
+_Static_assert(sizeof(byte_enum_t) == 1, "typedef anon enum mode");
+_Static_assert((byte_enum_t)-1 > 0, "unsigned enum mode stays unsigned");
+
+typedef enum tagged_m { TM_0, TM_1 } __attribute__((mode(__byte__))) tagged_byte_t;
+_Static_assert(sizeof(tagged_byte_t) == 1, "typedef tagged enum mode");
+_Static_assert(sizeof(enum tagged_m) == 1, "tagged enum itself gets mode");
+
+enum __attribute__((mode(__HI__))) hi_enum { HI_0 = -1, HI_1 = 1 };
+_Static_assert(sizeof(enum hi_enum) == 2, "prefix mode on enum");
+_Static_assert((enum hi_enum)-1 < 0, "signed enum mode stays signed");
+
+enum post_qi { PQ_0 = 0, PQ_1 = 200 } __attribute__((mode(__QI__)));
+_Static_assert(sizeof(enum post_qi) == 1, "postfix mode on enum");
+_Static_assert((enum post_qi)-1 > 0, "postfix QI mode unsigned");
+
+typedef enum { UE_0 = 0, UE_1 = 1 } uenum_t;
+static uenum_t __attribute__((mode(__byte__))) ub;
+_Static_assert(sizeof(ub) == 1, "typedef var mode size");
+_Static_assert((typeof(ub))-1 > 0, "typedef var mode unsigned");
 
 /*
  * check-name: enum+mode
- * check-known-to-fail
  */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 63/73] enum: Support __attribute__((packed)) on enumeration types
  2026-11-09 20:31 Bart Van Assche
                   ` (61 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
                   ` (9 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Support GCC/Clang __attribute__((packed)) on enumeration types so
packed enums select the narrowest integer type fitting their range:
- Define get_enum_base_type() and record the enumerator range on the
  enum symbol so prefix and postfix packed attributes shrink the enum
  size and alignment.
- Preserve sym->packed across subsequent tag lookups with '|=' instead
  of '='.
- Warn when both 'packed' and 'aligned' attributes are applied to an
  enum type since Clang and GCC treat that combination differently, and
  diagnose array element types whose size is not a multiple of their
  alignment in examine_array_type().
- Avoid overwriting invalid (&bad_ctype), incomplete, bitwise
  (SYM_RESTRICT), mode-attributed, or C23 fixed-underlying-type enums.
- Add regression tests in validation/enum-packed.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 parse.c                  | 92 +++++++++++++++++-----------------------
 symbol.c                 | 87 ++++++++++++++++++++++++++++++++++---
 symbol.h                 | 10 +++++
 validation/enum-packed.c | 85 +++++++++++++++++++++++++++++++++++++
 4 files changed, 214 insertions(+), 60 deletions(-)
 create mode 100644 validation/enum-packed.c

diff --git a/parse.c b/parse.c
index b686d2d55c36..3235274e6659 100644
--- a/parse.c
+++ b/parse.c
@@ -891,6 +891,8 @@ static struct token *struct_union_enum_specifier(enum type type,
 				sym->rank = underlying->rank;
 			}
 			token = handle_attributes(token, &attr);
+			sym->packed |= attr.packed;
+			sym->has_aligned |= attr.has_aligned;
 			if (!match_op(token, '{')) {
 				ctx->opaque_fixed_enum = 1;
 				ctx->opaque_fixed_enum_pos = colon_pos;
@@ -972,6 +974,8 @@ static struct token *struct_union_enum_specifier(enum type type,
 		return token;
 	}
 
+	sym->packed |= attr.packed;
+	sym->has_aligned |= attr.has_aligned;
 	token = parse(token->next, sym);
 	token = expect(token, '}', "at end of specifier");
 	sym->defined = 1;
@@ -979,7 +983,11 @@ static struct token *struct_union_enum_specifier(enum type type,
 	attr.ctype.base_type = sym;
 	token = handle_attributes(token, &attr);
 	apply_ctype(token->pos, &sym->ctype, &attr.ctype);
-	sym->packed = attr.packed;
+	sym->packed |= attr.packed;
+	sym->has_aligned |= attr.has_aligned;
+	if (sym->type == SYM_ENUM && sym->packed && sym->has_aligned)
+		warning(sym->pos,
+			"Clang and GCC treat 'packed' and 'aligned' attributes on enum types differently");
 	if (attr.designated_init) {
 		if (sym->type == SYM_STRUCT)
 			sym->designated_init = 1;
@@ -1006,6 +1014,16 @@ static struct token *struct_union_enum_specifier(enum type type,
 			sym->rank = mtype->rank;
 		}
 	}
+	if (sym->type == SYM_ENUM && sym->packed && !sym->fixed_type &&
+	    !sym->has_mode && sym->ctype.base_type &&
+	    is_int_type(sym->ctype.base_type)) {
+		struct symbol *base = get_enum_base_type(sym->range, 1);
+		if (base && base != &bad_ctype) {
+			sym->ctype.base_type = base;
+			sym->ctype.modifiers &= ~MOD_SIGNEDNESS;
+			sym->ctype.modifiers |= (base->ctype.modifiers & MOD_SIGNEDNESS);
+		}
+	}
 
 	sym->endpos = token->pos;
 
@@ -1045,24 +1063,6 @@ static struct token *union_specifier(struct token *token, struct symbol *sym, st
 	return struct_union_enum_specifier(SYM_UNION, token, ctx, parse_union_declaration);
 }
 
-///
-// safe right shift
-//
-// This allow to use a shift amount as big (or bigger)
-// than the width of the value to be shifted, in which case
-// the result is, of course, 0.
-static unsigned long long rshift(unsigned long long val, unsigned int n)
-{
-	if (n >= (sizeof(val) * 8))
-		return 0;
-	return val >> n;
-}
-
-struct range {
-	long long		neg;
-	unsigned long long	pos;
-};
-
 static void update_range(struct range *range, unsigned long long uval, struct symbol *vtype)
 {
 	long long sval = uval;
@@ -1076,24 +1076,6 @@ static void update_range(struct range *range, unsigned long long uval, struct sy
 	}
 }
 
-static int type_is_ok(struct symbol *type, struct range range)
-{
-	int shift = type->bit_size;
-	int is_unsigned = type->ctype.modifiers & MOD_UNSIGNED;
-
-	if (!is_unsigned)
-		shift--;
-	if (rshift(range.pos, shift))
-		return 0;
-	if (range.neg == 0)
-		return 1;
-	if (is_unsigned)
-		return 0;
-	if (rshift(~range.neg, shift))
-		return 0;
-	return 1;
-}
-
 static struct range type_range(struct symbol *type)
 {
 	struct range range;
@@ -1315,20 +1297,9 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
 			base_type = &bad_ctype;
 		} else if (!is_int_type(base_type))
 			;
-		else if (type_is_ok(&uint_ctype, range))
-			base_type = &uint_ctype;
-		else if (type_is_ok(&int_ctype, range))
-			base_type = &int_ctype;
-		else if (type_is_ok(&ulong_ctype, range))
-			base_type = &ulong_ctype;
-		else if (type_is_ok(&long_ctype, range))
-			base_type = &long_ctype;
-		else if (type_is_ok(&ullong_ctype, range))
-			base_type = &ullong_ctype;
-		else if (type_is_ok(&llong_ctype, range))
-			base_type = &llong_ctype;
 		else
-			base_type = &bad_ctype;
+			base_type = get_enum_base_type(range, parent->packed);
+		parent->range = range;
 		parent->ctype.base_type = base_type;
 		parent->ctype.modifiers |=
 			(base_type->ctype.modifiers & MOD_UNSIGNED);
@@ -1491,10 +1462,9 @@ static struct token *ignore_attribute(struct token *token, struct symbol *attr,
 
 static struct token *attribute_packed(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
-	if (!ctx->ctype.alignment) {
+	if (!ctx->ctype.alignment)
 		ctx->ctype.alignment = 1;
-		ctx->packed = 1;
-	}
+	ctx->packed = 1;
 	return token;
 }
 
@@ -1513,6 +1483,7 @@ static struct token *attribute_aligned(struct token *token, struct symbol *attr,
 		return token;
 	} else if (alignment > ctx->ctype.alignment)
 		ctx->ctype.alignment = alignment;
+	ctx->has_aligned = 1;
 	return token;
 }
 
@@ -2004,6 +1975,8 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 			seen |= Set_S | Set_T;
 			ctx->ctype.base_type = s->ctype.base_type;
 			apply_ctype(token->pos, &ctx->ctype, &s->ctype);
+			if (s->ctype.alignment > ctx->typedef_alignment)
+				ctx->typedef_alignment = s->ctype.alignment;
 			token = token->next;
 			continue;
 		}
@@ -2422,12 +2395,14 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 	} else if (match_op(token, '(') &&
 	    is_nested(token, &next, ctx->prefer_abstract)) {
 		struct symbol *base_type = ctype->base_type;
+		unsigned int saved_td_align = ctx->typedef_alignment;
 		if (token->next != next)
 			next = handle_attributes(token->next, ctx);
 		token = declarator(next, ctx);
 		token = expect(token, ')', "in nested declarator");
 		while (ctype->base_type != base_type)
 			ctype = &ctype->base_type->ctype;
+		ctx->typedef_alignment = saved_td_align;
 		p = NULL;
 	}
 
@@ -2459,6 +2434,7 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 		return token;
 	}
 
+	bool saw_array = false;
 	while (match_op(token, '[') && !match_c23_attribute(token)) {
 		struct symbol *array;
 		array = alloc_indirect_symbol(token->pos, ctype, SYM_ARRAY);
@@ -2466,9 +2442,12 @@ static struct token *direct_declarator(struct token *token, struct decl_state *c
 		token = expect(token, ']', "in abstract_array_declarator");
 		array->endpos = token->pos;
 		ctype = &array->ctype;
+		saw_array = true;
 		if (match_c23_attribute(token))
 			token = handle_attributes(token, ctx);
 	}
+	if (saw_array && ctx->typedef_alignment > ctype->alignment)
+		ctype->alignment = ctx->typedef_alignment;
 	return token;
 }
 
@@ -2485,6 +2464,7 @@ static struct token *pointer(struct token *token, struct decl_state *ctx)
 		ctx->ctype.as = NULL;
 		ctx->ctype.contexts = NULL;
 		ctx->ctype.alignment = 0;
+		ctx->typedef_alignment = 0;
 
 		token = handle_qualifiers(token->next, ctx);
 		ctx->ctype.base_type->endpos = token->pos;
@@ -2553,6 +2533,7 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 	struct decl_state ctx = {.prefer_abstract = 0};
 	struct ctype saved;
 	unsigned long mod;
+	unsigned int saved_td_align;
 
 	token = declaration_specifiers(token, &ctx);
 	if (ctx.opaque_fixed_enum)
@@ -2560,10 +2541,12 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 			     "'enum' underlying type may not be specified here");
 	mod = decl_modifiers(&ctx);
 	saved = ctx.ctype;
+	saved_td_align = ctx.typedef_alignment;
 	for (;;) {
 		struct symbol *decl = alloc_symbol(token->pos, SYM_NODE);
 		ctx.cleanup = NULL;
 		ctx.ident_modifiers = 0;
+		ctx.typedef_alignment = saved_td_align;
 		ctx.ident = &decl->ident;
 
 		token = declarator(token, &ctx);
@@ -3801,6 +3784,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	struct decl_state ctx = { .ident = &ident };
 	struct ctype saved;
 	unsigned long saved_f_modifiers;
+	unsigned int saved_td_align;
 	struct symbol *base_type;
 	unsigned long mod;
 	int is_typedef;
@@ -3840,6 +3824,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 
 	saved = ctx.ctype;
 	saved_f_modifiers = ctx.f_modifiers;
+	saved_td_align = ctx.typedef_alignment;
 	token = declarator(token, &ctx);
 	token = handle_asm_name(token, &ctx);
 	token = handle_attributes(token, &ctx);
@@ -4108,6 +4093,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		ctx.cleanup = NULL;
 		ctx.ident_modifiers = 0;
 		ctx.f_modifiers = saved_f_modifiers;
+		ctx.typedef_alignment = saved_td_align;
 		token = handle_attributes(token, &ctx);
 		token = declarator(token, &ctx);
 		token = handle_asm_name(token, &ctx);
diff --git a/symbol.c b/symbol.c
index d4b945d9e161..69fb0670c341 100644
--- a/symbol.c
+++ b/symbol.c
@@ -352,6 +352,7 @@ static struct symbol * examine_array_type(struct symbol *sym)
 	struct symbol *base_type = examine_base_type(sym);
 	unsigned long bit_size = -1, alignment;
 	struct expression *array_size = sym->array_size;
+	int elem_size;
 
 	if (!base_type)
 		return sym;
@@ -376,8 +377,13 @@ static struct symbol * examine_array_type(struct symbol *sym)
 	if (has_flexible_array(base_type) && Wflexible_array_array)
 		warning(sym->pos, "array of flexible structures");
 	alignment = base_type->ctype.alignment;
-	if (!sym->ctype.alignment)
+	if (!sym->ctype.alignment || sym->ctype.alignment < alignment)
 		sym->ctype.alignment = alignment;
+	elem_size = bits_to_bytes(base_type->bit_size);
+	if (elem_size > 0 && sym->ctype.alignment &&
+	    (elem_size % sym->ctype.alignment))
+		sparse_error(sym->pos,
+			     "size of array element is not a multiple of its alignment");
 	sym->bit_size = bit_size;
 	return sym;
 }
@@ -587,25 +593,92 @@ static struct symbol * examine_node_type(struct symbol *sym)
 	return sym;
 }
 
+static unsigned long long rshift(unsigned long long val, unsigned int n)
+{
+	if (n >= (sizeof(val) * 8))
+		return 0;
+	return val >> n;
+}
+
+static int type_is_ok(struct symbol *type, struct range range)
+{
+	int shift = type->bit_size;
+	int is_unsigned = type->ctype.modifiers & MOD_UNSIGNED;
+
+	if (!is_unsigned)
+		shift--;
+	if (rshift(range.pos, shift))
+		return 0;
+	if (range.neg == 0)
+		return 1;
+	if (is_unsigned)
+		return 0;
+	if (rshift(~range.neg, shift))
+		return 0;
+	return 1;
+}
+
+struct symbol *get_enum_base_type(struct range range, int packed)
+{
+	if (packed) {
+		if (type_is_ok(&uchar_ctype, range))
+			return &uchar_ctype;
+		if (type_is_ok(&schar_ctype, range))
+			return &schar_ctype;
+		if (type_is_ok(&ushort_ctype, range))
+			return &ushort_ctype;
+		if (type_is_ok(&short_ctype, range))
+			return &short_ctype;
+	}
+	if (type_is_ok(&uint_ctype, range))
+		return &uint_ctype;
+	if (type_is_ok(&int_ctype, range))
+		return &int_ctype;
+	if (type_is_ok(&ulong_ctype, range))
+		return &ulong_ctype;
+	if (type_is_ok(&long_ctype, range))
+		return &long_ctype;
+	if (type_is_ok(&ullong_ctype, range))
+		return &ullong_ctype;
+	if (type_is_ok(&llong_ctype, range))
+		return &llong_ctype;
+	return &bad_ctype;
+}
+
 static struct symbol *examine_enum_type(struct symbol *sym)
 {
-	struct symbol *base_type = examine_base_type(sym);
+	struct symbol *base_type;
+	unsigned long alignment;
+
+	if (sym->packed && !sym->fixed_type && !sym->has_mode &&
+	    sym->ctype.base_type && is_int_type(sym->ctype.base_type) &&
+	    sym->ctype.base_type != &incomplete_ctype) {
+		base_type = get_enum_base_type(sym->range, 1);
+		if (base_type && base_type != &bad_ctype) {
+			sym->ctype.base_type = base_type;
+			sym->ctype.modifiers &= ~MOD_SIGNEDNESS;
+			sym->ctype.modifiers |= (base_type->ctype.modifiers & MOD_SIGNEDNESS);
+		}
+	}
+	base_type = examine_base_type(sym);
 
 	sym->ctype.modifiers |= (base_type->ctype.modifiers & MOD_SIGNEDNESS);
-	if (sym->fixed_type || sym->has_mode) {
+	if (sym->fixed_type || sym->packed || sym->has_mode) {
 		if (base_type == &bool_ctype)
 			sym->bit_size = bits_to_bytes(bits_in_bool) * bits_in_char;
 		else
 			sym->bit_size = base_type->bit_size;
-		sym->ctype.alignment = base_type->ctype.alignment;
+		alignment = base_type->ctype.alignment;
 	} else {
 		sym->bit_size = bits_in_enum;
 		if (base_type->bit_size > sym->bit_size)
 			sym->bit_size = base_type->bit_size;
-		sym->ctype.alignment = enum_alignment;
-		if (base_type->ctype.alignment > sym->ctype.alignment)
-			sym->ctype.alignment = base_type->ctype.alignment;
+		alignment = enum_alignment;
+		if (base_type->ctype.alignment > alignment)
+			alignment = base_type->ctype.alignment;
 	}
+	if (sym->ctype.alignment < alignment)
+		sym->ctype.alignment = alignment;
 	sym->rank = base_type->rank;
 	return sym;
 }
diff --git a/symbol.h b/symbol.h
index aa180ac4f514..691213a0b454 100644
--- a/symbol.h
+++ b/symbol.h
@@ -87,6 +87,11 @@ enum keyword {
 	KW_EXACT	= 1 << 9,
 };
 
+struct range {
+	long long		neg;
+	unsigned long long	pos;
+};
+
 struct context {
 	struct expression *context;
 	unsigned int in, out;
@@ -112,10 +117,12 @@ struct decl_state {
 	unsigned long f_modifiers;		// function attributes
 	unsigned long ident_modifiers;		// declarator-id C23 attributes
 	unsigned long storage_class;
+	unsigned int typedef_alignment;
 	unsigned char prefer_abstract;
 	unsigned char autotype;
 	unsigned char forced;
 	unsigned char packed;
+	unsigned char has_aligned;
 	unsigned char has_auto;
 	unsigned char has_alignas;
 	unsigned char is_constexpr;
@@ -215,6 +222,7 @@ struct symbol {
 			unsigned int underspecified : 1;
 			unsigned int c23_auto : 1;
 			unsigned int has_mode : 1;
+			unsigned int has_aligned : 1;
 			int		rank:3;	// arithmetic's rank
 			struct expression *array_size;
 			struct ctype ctype;
@@ -225,6 +233,7 @@ struct symbol {
 			struct symbol_list *inline_symbol_list;
 			struct expression *initializer;
 			struct expression *cleanup;
+			struct range range;
 			struct entrypoint *ep;
 			struct symbol *definition;
 		};
@@ -373,6 +382,7 @@ extern void bind_symbol_with_scope(struct symbol *, struct ident *, enum namespa
 extern struct symbol *strip_array_qualifiers(struct symbol *);
 extern struct symbol *examine_symbol_type(struct symbol *);
 extern struct symbol *examine_pointer_target(struct symbol *);
+extern struct symbol *get_enum_base_type(struct range range, int packed);
 extern const char *show_as(struct ident *as);
 extern const char *show_typename(struct symbol *sym);
 extern const char *builtin_typename(struct symbol *sym);
diff --git a/validation/enum-packed.c b/validation/enum-packed.c
new file mode 100644
index 000000000000..4d1ba40a2781
--- /dev/null
+++ b/validation/enum-packed.c
@@ -0,0 +1,85 @@
+/*
+ * Check that __attribute__((packed)) on enums selects the smallest
+ * integer type that can hold all enumerator values.
+ */
+
+enum e_u8 {
+	U8_0 = 0,
+	U8_1 = 200,
+} __attribute__((packed));
+
+enum e_s8 {
+	S8_0 = -10,
+	S8_1 = 100,
+} __attribute__((packed));
+
+enum __attribute__((packed)) e_u16 {
+	U16_0 = 0,
+	U16_1 = 1000,
+};
+
+enum e_s16 {
+	S16_0 = -1000,
+	S16_1 = 50,
+} __attribute__((__packed__));
+
+enum e_u32 {
+	U32_0 = 0,
+	U32_1 = 70000,
+} __attribute__((packed));
+
+_Static_assert(sizeof(enum e_u8) == 1, "e_u8 size");
+_Static_assert(_Alignof(enum e_u8) == 1, "e_u8 align");
+_Static_assert(sizeof(enum e_s8) == 1, "e_s8 size");
+_Static_assert(_Alignof(enum e_s8) == 1, "e_s8 align");
+
+_Static_assert(sizeof(enum e_u16) == 2, "e_u16 size");
+_Static_assert(_Alignof(enum e_u16) == 2, "e_u16 align");
+_Static_assert(sizeof(enum e_s16) == 2, "e_s16 size");
+_Static_assert(_Alignof(enum e_s16) == 2, "e_s16 align");
+
+_Static_assert(sizeof(enum e_u32) == 4, "e_u32 size");
+
+/* Subsequent tag lookups must not clear the packed flag */
+static enum e_u8 var_u8;
+static enum e_u16 var_u16;
+_Static_assert(sizeof(var_u8) == 1, "var_u8 size");
+_Static_assert(sizeof(var_u16) == 2, "var_u16 size");
+
+enum __attribute__((aligned(2), packed)) e_aligned_packed {
+	AP_0 = 0,
+};
+_Static_assert(sizeof(enum e_aligned_packed) == 1, "e_aligned_packed size");
+_Static_assert(_Alignof(enum e_aligned_packed) == 2, "e_aligned_packed align");
+
+static enum e_aligned_packed bad_enum_arr[2];
+typedef char __attribute__((aligned(2))) aligned_char;
+static aligned_char bad_char_arr[2];
+static aligned_char *ok_char_ptr_arr[2];
+__attribute__((aligned(16))) static char ok_aligned_arr[2];
+static enum e_u8 ok_u8_arr[4];
+
+enum __attribute__((packed, mode(__SI__))) e_packed_si {
+	PSI_0 = 0,
+};
+_Static_assert(sizeof(enum e_packed_si) == 4, "mode overrides packed");
+
+enum __attribute__((packed)) e_fixed_packed : int {
+	EFP_0 = 0,
+};
+_Static_assert(sizeof(enum e_fixed_packed) == sizeof(int),
+	       "fixed type overrides packed");
+
+_Static_assert(sizeof(U8_0) == sizeof(int), "enumerator has int size");
+_Static_assert(sizeof(S8_0) == sizeof(int), "enumerator has int size");
+
+/*
+ * check-name: packed enum support
+ * check-command: sparse $file
+ *
+ * check-error-start
+enum-packed.c:49:42: warning: Clang and GCC treat 'packed' and 'aligned' attributes on enum types differently
+enum-packed.c:55:42: error: size of array element is not a multiple of its alignment
+enum-packed.c:57:33: error: size of array element is not a multiple of its alignment
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types()
  2026-11-09 20:31 Bart Van Assche
                   ` (62 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
                   ` (8 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Clear c1.as and c2.as in compare_types() only when base_type is not
SYM_ARRAY so that __builtin_types_compatible_p() ignores top-level scalar
address spaces while still distinguishing both pointer target address
spaces and array element address spaces.

Add active validation tests in validation/types-compatible-address-space.c
instead of placing assertions in check-known-to-fail typeof-addresspace.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 expand.c                                    |  9 +++--
 validation/types-compatible-address-space.c | 39 +++++++++++++++++++++
 2 files changed, 46 insertions(+), 2 deletions(-)
 create mode 100644 validation/types-compatible-address-space.c

diff --git a/expand.c b/expand.c
index 815ff24ac487..57d3aeba0f4e 100644
--- a/expand.c
+++ b/expand.c
@@ -790,8 +790,13 @@ static int expand_comma(struct expression *expr)
 
 static int compare_types(int op, struct symbol *left, struct symbol *right)
 {
-	struct ctype c1 = {.base_type = left};
-	struct ctype c2 = {.base_type = right};
+	struct ctype c1 = left->type == SYM_NODE ? left->ctype : (struct ctype){.base_type = left};
+	struct ctype c2 = right->type == SYM_NODE ? right->ctype : (struct ctype){.base_type = right};
+
+	if (!c1.base_type || c1.base_type->type != SYM_ARRAY)
+		c1.as = NULL;
+	if (!c2.base_type || c2.base_type->type != SYM_ARRAY)
+		c2.as = NULL;
 	switch (op) {
 	case SPECIAL_EQUAL:
 		return !type_difference(&c1, &c2, MOD_IGN, MOD_IGN);
diff --git a/validation/types-compatible-address-space.c b/validation/types-compatible-address-space.c
new file mode 100644
index 000000000000..38953eca77e2
--- /dev/null
+++ b/validation/types-compatible-address-space.c
@@ -0,0 +1,39 @@
+#define __as1 __attribute__((address_space(1)))
+#define __as2 __attribute__((address_space(2)))
+
+typedef int __as1 as1_int_t;
+typedef int __as1 as1_arr_t[4];
+typedef int __as1 as1_mat_t[2][4];
+
+static void test_types_compatible_as(void)
+{
+	int __as1 obj, *ptr;
+
+	/* Top-level address space on scalar / pointer itself is ignored */
+	_Static_assert(__builtin_types_compatible_p(int __as1, int), "scalar as1 vs int");
+	_Static_assert(__builtin_types_compatible_p(as1_int_t, int), "typedef scalar as1 vs int");
+	_Static_assert(__builtin_types_compatible_p(typeof(obj), int), "typeof(obj) vs int");
+	_Static_assert(__builtin_types_compatible_p(typeof(*ptr), int), "typeof(*ptr) vs int");
+	_Static_assert(__builtin_types_compatible_p(int * __as1, int *), "ptr own as1 vs int *");
+
+	/* Pointee address space must match */
+	_Static_assert(!__builtin_types_compatible_p(int __as1 *, int *), "pointee as1 vs default");
+	_Static_assert(!__builtin_types_compatible_p(typeof(ptr), int *), "typeof(ptr) vs int *");
+	_Static_assert(!__builtin_types_compatible_p(int __as1 *, int __as2 *), "pointee as1 vs as2");
+	_Static_assert(__builtin_types_compatible_p(int __as1 *, int __as1 *), "pointee as1 vs as1");
+
+	/* Array element address space must match */
+	_Static_assert(!__builtin_types_compatible_p(int __as1[4], int[4]), "array elem as1 vs default");
+	_Static_assert(!__builtin_types_compatible_p(as1_arr_t, int[4]), "typedef array as1 vs int[4]");
+	_Static_assert(!__builtin_types_compatible_p(typeof(as1_arr_t), int[4]), "typeof(as1_arr_t) vs int[4]");
+	_Static_assert(!__builtin_types_compatible_p(int __as1[4], int __as2[4]), "array elem as1 vs as2");
+	_Static_assert(__builtin_types_compatible_p(int __as1[4], int __as1[4]), "array elem as1 vs as1");
+	_Static_assert(!__builtin_types_compatible_p(int __as1[2][4], int[2][4]), "2d array elem as1 vs default");
+	_Static_assert(!__builtin_types_compatible_p(as1_mat_t, int[2][4]), "typedef 2d array as1 vs int[2][4]");
+	_Static_assert(__builtin_types_compatible_p(int __as1[2][4], int __as1[2][4]), "2d array elem as1 vs as1");
+}
+
+/*
+ * check-name: types-compatible-address-space
+ * check-command: sparse $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE
  2026-11-09 20:31 Bart Van Assche
                   ` (63 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
                   ` (7 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Declare __builtin_infer_alloc_token() returning size_t_ctype and fold
calls to integer constant expression 0 (CEF_SET_ICE) while evaluating all
arguments without type restrictions.

Support __kmalloc_token(...) in the Linux kernel's slab allocator on both
32-bit and 64-bit targets without an #ifdef __CHECKER__ workaround, and
add validation/builtin-infer-alloc-token.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 builtin.c                              | 21 +++++++++++++++++++
 validation/builtin-infer-alloc-token.c | 28 ++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)
 create mode 100644 validation/builtin-infer-alloc-token.c

diff --git a/builtin.c b/builtin.c
index 0be03c51aedd..ba3326970391 100644
--- a/builtin.c
+++ b/builtin.c
@@ -638,6 +638,26 @@ static struct symbol_op object_size_op = {
 	.expand = expand_object_size,
 };
 
+static int evaluate_infer_alloc_token(struct expression *expr)
+{
+	expr->flags |= CEF_SET_ICE;
+	return 1;
+}
+
+static int expand_infer_alloc_token(struct expression *expr, int cost)
+{
+	expr->flags = CEF_SET_ICE;
+	expr->type = EXPR_VALUE;
+	expr->value = 0;
+	expr->taint = 0;
+	return 0;
+}
+
+static struct symbol_op infer_alloc_token_op = {
+	.evaluate = evaluate_infer_alloc_token,
+	.expand = expand_infer_alloc_token,
+};
+
 static int args_va_start(struct expression *expr)
 {
 	struct symbol *sym = expr->fn->ctype;
@@ -865,6 +885,7 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__builtin_index", &string_ctype, 0, { &const_string_ctype, &int_ctype }},
 	{ "__builtin_inf", &double_ctype, 0 },
 	{ "__builtin_inff", &float_ctype, 0 },
+	{ "__builtin_infer_alloc_token", size_t_ctype, 1, .op = &infer_alloc_token_op },
 	{ "__builtin_infl", &ldouble_ctype, 0 },
 	{ "__builtin_isdigit", &int_ctype, 0, { &int_ctype }, .op = &isdigit_op },
 	{ "__builtin_isfinite", &int_ctype, 1, .op = &fp_unop_op },
diff --git a/validation/builtin-infer-alloc-token.c b/validation/builtin-infer-alloc-token.c
new file mode 100644
index 000000000000..0ec460d80edb
--- /dev/null
+++ b/validation/builtin-infer-alloc-token.c
@@ -0,0 +1,28 @@
+typedef struct { __SIZE_TYPE__ v; } kmalloc_token_t;
+
+_Static_assert(__builtin_infer_alloc_token(1) == 0, "infer_alloc_token ICE");
+_Static_assert(__builtin_infer_alloc_token(1, 2) == 0, "infer_alloc_token 2-arg ICE");
+_Static_assert(__builtin_types_compatible_p(typeof(__builtin_infer_alloc_token(1)),
+					    __SIZE_TYPE__), "size_t return type");
+
+static const __SIZE_TYPE__ static_tok = __builtin_infer_alloc_token(64);
+
+static __SIZE_TYPE__ test_infer(int size, int flags)
+{
+	__SIZE_TYPE__ tok = __builtin_infer_alloc_token(size, flags);
+	return tok + static_tok;
+}
+
+static __SIZE_TYPE__ test_infer_single(int size)
+{
+	return __builtin_infer_alloc_token(size);
+}
+
+static kmalloc_token_t test_struct(int size)
+{
+	return (kmalloc_token_t){ .v = __builtin_infer_alloc_token(size) };
+}
+
+/*
+ * check-name: builtin-infer-alloc-token
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins
  2026-11-09 20:31 Bart Van Assche
                   ` (64 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
                   ` (6 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add support for compiler builtins used across the Linux kernel:
- Support __builtin_dynamic_object_size(ptr, type) via object_size_op.
- Support __builtin_thread_pointer() returning void * and
  __builtin_alloca_uninitialized(size) returning void *.
- Validate that __builtin_counted_by_ref() receives one argument and fold
  it to (void *)0 for flexible array members.

Add validation tests in validation/builtins-kernel.c and
validation/builtin-counted-by-ref-err.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 builtin.c                               | 22 +++++++++++++
 validation/builtin-counted-by-ref-err.c | 20 ++++++++++++
 validation/builtins-kernel.c            | 42 +++++++++++++++++++++++++
 3 files changed, 84 insertions(+)
 create mode 100644 validation/builtin-counted-by-ref-err.c
 create mode 100644 validation/builtins-kernel.c

diff --git a/builtin.c b/builtin.c
index ba3326970391..e8ea7721b771 100644
--- a/builtin.c
+++ b/builtin.c
@@ -733,6 +733,24 @@ static struct symbol_op va_start_op = {
 	.evaluate = evaluate_va_start,
 };
 
+static int args_counted_by_ref(struct expression *expr)
+{
+	return eval_args(expr, 1);
+}
+
+static int expand_counted_by_ref(struct expression *expr, int cost)
+{
+	expr->type = EXPR_VALUE;
+	expr->value = 0;
+	expr->taint = 0;
+	return 0;
+}
+
+static struct symbol_op counted_by_ref_op = {
+	.args = args_counted_by_ref,
+	.expand = expand_counted_by_ref,
+};
+
 static int evaluate_strlen(struct expression *expr)
 {
 	struct expression *arg = first_expression(expr->args);
@@ -852,6 +870,7 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__builtin_add_overflow", &bool_ctype, 1, .op = &overflow_op },
 	{ "__builtin_add_overflow_p", &bool_ctype, 1, .op = &overflow_p_op },
 	{ "__builtin_alloca", &ptr_ctype, 0, { size_t_ctype }},
+	{ "__builtin_alloca_uninitialized", &ptr_ctype, 0, { size_t_ctype }},
 	{ "__builtin_bcmp", &int_ctype , 0, { &const_ptr_ctype, &const_ptr_ctype, size_t_ctype }},
 	{ "__builtin_bcopy", &void_ctype, 0, { &const_ptr_ctype, &ptr_ctype, size_t_ctype }},
 	{ "__builtin_bswap16", &ushort_ctype, 0, { &ushort_ctype }, .op = &bswap_op },
@@ -859,6 +878,7 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__builtin_bswap64", &ullong_ctype, 0, { &ullong_ctype }, .op = &bswap_op },
 	{ "__builtin_bzero", &void_ctype, 0, { &ptr_ctype, size_t_ctype }},
 	{ "__builtin_calloc", &ptr_ctype, 0, { size_t_ctype, size_t_ctype }},
+	{ "__builtin_counted_by_ref", &ptr_ctype, 1, .op = &counted_by_ref_op },
 	{ "__builtin_clrsb", &int_ctype, 0, { &int_ctype }, .op = &clrsb_op },
 	{ "__builtin_clrsbl", &int_ctype, 0, { &long_ctype }, .op = &clrsb_op },
 	{ "__builtin_clrsbll", &int_ctype, 0, { &llong_ctype }, .op = &clrsb_op },
@@ -868,6 +888,7 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__builtin_ctz", &int_ctype, 0, { &int_ctype }, .op = &ctz_op },
 	{ "__builtin_ctzl", &int_ctype, 0, { &long_ctype }, .op = &ctz_op },
 	{ "__builtin_ctzll", &int_ctype, 0, { &llong_ctype }, .op = &ctz_op },
+	{ "__builtin_dynamic_object_size", size_t_ctype, 0, { &const_ptr_ctype, &int_ctype }, .op = &object_size_op},
 	{ "__builtin_exit", &void_ctype, 0, { &int_ctype }},
 	{ "__builtin_extract_return_addr", &ptr_ctype, 0, { &ptr_ctype }},
 	{ "__builtin_fabs", &double_ctype, 0, { &double_ctype }},
@@ -961,6 +982,7 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__builtin_strstr", &string_ctype, 0, { &const_string_ctype, &const_string_ctype }},
 	{ "__builtin_sub_overflow", &bool_ctype, 1, .op = &overflow_op },
 	{ "__builtin_sub_overflow_p", &bool_ctype, 1, .op = &overflow_p_op },
+	{ "__builtin_thread_pointer", &ptr_ctype, 0 },
 	{ "__builtin_trap", &void_ctype, 0 },
 	{ "__builtin_uadd_overflow", &bool_ctype, 0, { &uint_ctype, &uint_ctype, &uint_ptr_ctype }},
 	{ "__builtin_uaddl_overflow", &bool_ctype, 0, { &ulong_ctype, &ulong_ctype, &ulong_ptr_ctype }},
diff --git a/validation/builtin-counted-by-ref-err.c b/validation/builtin-counted-by-ref-err.c
new file mode 100644
index 000000000000..242e1a3f441c
--- /dev/null
+++ b/validation/builtin-counted-by-ref-err.c
@@ -0,0 +1,20 @@
+struct fam {
+	int count;
+	char arr[];
+};
+
+static void test_cbr_args(struct fam *f)
+{
+	(void)__builtin_counted_by_ref();
+	(void)__builtin_counted_by_ref(f->arr, 1);
+}
+
+/*
+ * check-name: builtin-counted-by-ref-err
+ * check-command: sparse $file
+ *
+ * check-error-start
+builtin-counted-by-ref-err.c:8:39: error: not enough arguments for __builtin_counted_by_ref
+builtin-counted-by-ref-err.c:9:39: error: too many arguments for __builtin_counted_by_ref
+ * check-error-end
+ */
diff --git a/validation/builtins-kernel.c b/validation/builtins-kernel.c
new file mode 100644
index 000000000000..05c13f4c1eae
--- /dev/null
+++ b/validation/builtins-kernel.c
@@ -0,0 +1,42 @@
+#if !__has_builtin(__builtin_dynamic_object_size)
+#error "missing __builtin_dynamic_object_size"
+#endif
+
+#if !__has_builtin(__builtin_thread_pointer)
+#error "missing __builtin_thread_pointer"
+#endif
+
+#if !__has_builtin(__builtin_alloca_uninitialized)
+#error "missing __builtin_alloca_uninitialized"
+#endif
+
+#if !__has_builtin(__builtin_counted_by_ref)
+#error "missing __builtin_counted_by_ref"
+#endif
+
+struct fam {
+	int count;
+	char arr[];
+};
+
+static void test_builtins(struct fam *f)
+{
+	char buf[10];
+	void *tp = __builtin_thread_pointer();
+	void *al = __builtin_alloca_uninitialized(32);
+	void *cbr = __builtin_counted_by_ref(f->arr);
+	int is_null = (__builtin_counted_by_ref(f->arr) == (void *)0);
+	unsigned long sz0 = __builtin_dynamic_object_size(buf, 0);
+	unsigned long sz1 = __builtin_dynamic_object_size(buf, 1);
+
+	(void)tp;
+	(void)al;
+	(void)cbr;
+	(void)is_null;
+	(void)sz0;
+	(void)sz1;
+}
+
+/*
+ * check-name: builtins-kernel
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 67/73] builtin: Add support for __builtin_has_attribute()
  2026-11-09 20:31 Bart Van Assche
                   ` (65 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
                   ` (5 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Implement EXPR_HAS_ATTRIBUTE to evaluate __builtin_has_attribute queries
at compile time across symbols, expressions, and anonymous type names:
- Distinguish __attribute__((const)) and [[unsequenced]] from pure via
  MOD_FCONST (0x80000000UL) and report const on const-qualified object
  declarations without conflating pure functions or pointer targets.
- Define MOD_NONSTRING (0x40000000) for the nonstring attribute instead of
  ignoring it, and support nonstring in __builtin_has_attribute() so Linux
  kernel strtomem_pad() static assertions succeed.
- Match GCC packed+aligned struct, union, bitfield, typedef, and _Alignas
  ABI layout via explicit_alignment on decl_state and symbol, and prevent
  typedef MOD_ALIGNED, MOD_WEAK, and MOD_NONSTRING modifiers from leaking
  onto pointers.
- Support bitwise SYM_RESTRICT types, struct member attribute queries
  without parent leakage, redeclaration attribute inheritance, and
  TOKEN_IDENT validation.

Add validation/struct-packed-aligned-layout.c,
validation/builtin-has-attribute-more.c,
validation/builtin-has-attribute-nonstring.c, and
validation/builtin-has-attribute-err.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 ast-inspect.c                                |   1 +
 builtin.c                                    |   1 +
 c2xml.c                                      |  44 +++---
 dissect.c                                    |   4 +
 evaluate.c                                   |  36 ++++-
 expand.c                                     | 121 +++++++++++++-
 expression.c                                 |  82 ++++++++++
 expression.h                                 |   8 +
 gcc-attr-list.h                              |   2 -
 ident-list.h                                 |   1 +
 inline.c                                     |   6 +
 parse.c                                      | 108 ++++++++++---
 show-parse.c                                 |   5 +
 simplify.c                                   |   2 +-
 symbol.c                                     |  30 ++--
 symbol.h                                     |  20 ++-
 validation/builtin-has-attribute-err.c       |  19 +++
 validation/builtin-has-attribute-more.c      | 157 +++++++++++++++++++
 validation/builtin-has-attribute-nonstring.c |  35 +++++
 validation/builtins-kernel.c                 |  31 ++++
 validation/struct-packed-aligned-layout.c    | 135 ++++++++++++++++
 validation/weak-const-no-fold.c              |  15 ++
 22 files changed, 801 insertions(+), 62 deletions(-)
 create mode 100644 validation/builtin-has-attribute-err.c
 create mode 100644 validation/builtin-has-attribute-more.c
 create mode 100644 validation/builtin-has-attribute-nonstring.c
 create mode 100644 validation/struct-packed-aligned-layout.c
 create mode 100644 validation/weak-const-no-fold.c

diff --git a/ast-inspect.c b/ast-inspect.c
index e940a93a411e..578dfca0e1cf 100644
--- a/ast-inspect.c
+++ b/ast-inspect.c
@@ -180,6 +180,7 @@ static const char *expression_type_name(enum expression_type type)
 		[EXPR_FVALUE] = "EXPR_FVALUE",
 		[EXPR_SLICE] = "EXPR_SLICE",
 		[EXPR_OFFSETOF] = "EXPR_OFFSETOF",
+		[EXPR_HAS_ATTRIBUTE] = "EXPR_HAS_ATTRIBUTE",
 	};
 	return expression_type_name[type] ?: "UNKNOWN_EXPRESSION_TYPE";
 }
diff --git a/builtin.c b/builtin.c
index e8ea7721b771..34af6d82b846 100644
--- a/builtin.c
+++ b/builtin.c
@@ -900,6 +900,7 @@ static const struct builtin_fn builtins_common[] = {
 	{ "__builtin_fmal", &ldouble_ctype, 0, { &ldouble_ctype, &ldouble_ctype, &ldouble_ctype }},
 	{ "__builtin_frame_address", &ptr_ctype, 0, { &uint_ctype }},
 	{ "__builtin_free", &void_ctype, 0, { &ptr_ctype }},
+	{ "__builtin_has_attribute", &int_ctype, 1 },
 	{ "__builtin_huge_val", &double_ctype, 0 },
 	{ "__builtin_huge_valf", &float_ctype, 0 },
 	{ "__builtin_huge_vall", &ldouble_ctype, 0 },
diff --git a/c2xml.c b/c2xml.c
index f23ec1a72de4..8abcadcd2320 100644
--- a/c2xml.c
+++ b/c2xml.c
@@ -113,34 +113,34 @@ static void examine_modifiers(struct symbol *sym, xmlNodePtr node)
 			"register",
 			"static",
 			"extern",
+			"toplevel",
+			"tls",
+			"inline",
+			"assigned",
+			"addressable",
 			"const",
 			"volatile",
+			"restrict",
+			"atomic",
 			"signed",
 			"unsigned",
-			"char",
-			"short",
-			"long",
-			"long-long",
-			"typedef",
-			NULL,
-			NULL,
-			NULL,
-			NULL,
-			NULL,
-			"inline",
-			"addressable",
+			"explicitly-signed",
+			"gnu-inline",
+			"user-type",
+			"constexpr",
+			"bitint",
+			"weak",
+			"aligned",
+			"unused",
+			"safe",
+			"pure",
+			"bitwise",
 			"nocast",
 			"noderef",
-			"accessed",
-			"toplevel",
-			"label",
-			"assigned",
-			"type-type",
-			"safe",
-			"user-type",
-			"force",
-			"explicitly-signed",
-			"bitwise"};
+			"noreturn",
+			"ext-visible",
+			"nonstring",
+			"fconst"};
 
 	int i;
 
diff --git a/dissect.c b/dissect.c
index 5d25e02fa1ad..7dd6161a29fa 100644
--- a/dissect.c
+++ b/dissect.c
@@ -483,6 +483,10 @@ again:
 			ret = do_expression(mode, expr->def);
 	}
 
+	break; case EXPR_HAS_ATTRIBUTE:
+		if (expr->has_attr_expr)
+			do_expression(mode, expr->has_attr_expr);
+
 	break; case EXPR_SYMBOL:
 		ret = report_symbol(mode, expr);
 	}
diff --git a/evaluate.c b/evaluate.c
index 1c169ad60382..27bfe1c0fe88 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -2433,7 +2433,8 @@ static struct symbol *evaluate_member_dereference(struct expression *expr)
 	 * The member needs to take on the address space and modifiers of
 	 * the "parent" type.
 	 */
-	member = convert_to_as_mod(member, address_space, mod);
+	member = convert_to_as_mod(member, address_space,
+				    mod & (MOD_PTRINHERIT | MOD_CONSTEXPR));
 	ctype = get_base_type(member);
 
 	if (!lvalue_expression(deref)) {
@@ -4098,6 +4099,39 @@ struct symbol *evaluate_expression(struct expression *expr)
 	case EXPR_GENERIC:
 		return evaluate_generic_selection(expr);
 
+	case EXPR_HAS_ATTRIBUTE:
+		if (expr->has_attr_expr) {
+			struct expression *sub = expr->has_attr_expr;
+			while (sub && sub->type == EXPR_PREOP && sub->op == '(')
+				sub = sub->unop;
+			if (sub && sub->type == EXPR_SYMBOL) {
+				expr->has_attr_decl = true;
+				evaluate_expression(expr->has_attr_expr);
+			} else if (sub && sub->type == EXPR_DEREF) {
+				struct symbol *dctype = evaluate_expression(sub->deref);
+				if (dctype && sub->member) {
+					int offset = 0;
+					examine_symbol_type(dctype);
+					if (dctype->type == SYM_NODE)
+						dctype = dctype->ctype.base_type;
+					if (dctype && (dctype->type == SYM_STRUCT ||
+						       dctype->type == SYM_UNION)) {
+						expr->has_attr_sym = find_identifier(
+							sub->member, dctype->symbol_list, &offset);
+						expr->has_attr_decl = true;
+					}
+				}
+				evaluate_expression(expr->has_attr_expr);
+			} else {
+				evaluate_expression(expr->has_attr_expr);
+			}
+		}
+		if (expr->has_attr_sym)
+			examine_symbol_type(expr->has_attr_sym);
+		expr->ctype = &int_ctype;
+		expr->flags = CEF_SET_ICE;
+		return &int_ctype;
+
 	/* These can not exist as stand-alone expressions */
 	case EXPR_INITIALIZER:
 	case EXPR_IDENTIFIER:
diff --git a/expand.c b/expand.c
index 57d3aeba0f4e..696662ad11ea 100644
--- a/expand.c
+++ b/expand.c
@@ -1011,7 +1011,7 @@ static struct expression *constant_symbol_value(struct symbol *sym, int offset)
 
 	if (offset < 0 || sym->expanding)
 		return NULL;
-	if (sym->ctype.modifiers & MOD_VOLATILE)
+	if (sym->ctype.modifiers & (MOD_VOLATILE | MOD_WEAK))
 		return NULL;
 	if ((sym->ctype.modifiers & MOD_ACCESS) &&
 	    !(sym->ctype.modifiers & MOD_CONSTEXPR))
@@ -1330,7 +1330,7 @@ static int expand_symbol_call(struct expression *expr, int cost)
 	if (ctype->op && ctype->op->expand)
 		return ctype->op->expand(expr, cost);
 
-	if (ctype->ctype.modifiers & MOD_PURE)
+	if (ctype->ctype.modifiers & (MOD_PURE | MOD_FCONST))
 		return cost + 1;
 
 	return SIDE_EFFECTS;
@@ -1502,6 +1502,120 @@ static void verify_nonoverlapping(struct expression_list **list, struct expressi
 	} END_FOR_EACH_PTR(b);
 }
 
+static const char *normalize_attr_name(const char *name)
+{
+	static char buf[64];
+	size_t len;
+	if (!name)
+		return "";
+	len = strlen(name);
+	if (len >= 4 && name[0] == '_' && name[1] == '_' &&
+	    name[len-1] == '_' && name[len-2] == '_') {
+		if (len - 4 < sizeof(buf)) {
+			memcpy(buf, name + 2, len - 4);
+			buf[len - 4] = '\0';
+			return buf;
+		}
+	}
+	return name;
+}
+
+static bool is_fun_attr_name(const char *name)
+{
+	return !strcmp(name, "pure") || !strcmp(name, "reproducible") ||
+	       !strcmp(name, "const") || !strcmp(name, "unsequenced") ||
+	       !strcmp(name, "noreturn") || !strcmp(name, "_Noreturn") ||
+	       !strcmp(name, "gnu_inline");
+}
+
+static bool check_symbol_has_attr(struct symbol *sym, const char *name, bool is_decl)
+{
+	if (!sym || !name)
+		return false;
+
+	if (!strcmp(name, "packed"))
+		return sym->packed != 0;
+	if (!strcmp(name, "aligned"))
+		return (sym->ctype.modifiers & MOD_ALIGNED) != 0;
+	if (!strcmp(name, "weak"))
+		return (sym->ctype.modifiers & MOD_WEAK) != 0;
+	if (!strcmp(name, "pure") || !strcmp(name, "reproducible"))
+		return (sym->ctype.modifiers & MOD_PURE) != 0;
+	if (!strcmp(name, "unsequenced"))
+		return (sym->ctype.modifiers & MOD_FCONST) != 0;
+	if (!strcmp(name, "const")) {
+		if (sym->ctype.modifiers & MOD_FCONST)
+			return true;
+		return is_decl && (sym->ctype.modifiers & MOD_CONST) != 0;
+	}
+	if (!strcmp(name, "noreturn") || !strcmp(name, "_Noreturn"))
+		return (sym->ctype.modifiers & MOD_NORETURN) != 0;
+	if (!strcmp(name, "noderef"))
+		return (sym->ctype.modifiers & MOD_NODEREF) != 0;
+	if (!strcmp(name, "unused") || !strcmp(name, "maybe_unused"))
+		return (sym->ctype.modifiers & MOD_UNUSED) != 0;
+	if (!strcmp(name, "externally_visible"))
+		return (sym->ctype.modifiers & MOD_EXT_VISIBLE) != 0;
+	if (!strcmp(name, "gnu_inline"))
+		return (sym->ctype.modifiers & MOD_GNU_INLINE) != 0;
+	if (!strcmp(name, "address_space"))
+		return sym->ctype.as != NULL;
+	if (!strcmp(name, "bitwise"))
+		return (sym->ctype.modifiers & MOD_BITWISE) != 0 ||
+		       sym->type == SYM_RESTRICT;
+	if (!strcmp(name, "nocast"))
+		return (sym->ctype.modifiers & MOD_NOCAST) != 0;
+	if (!strcmp(name, "transparent_union"))
+		return sym->transparent_union != 0;
+	if (!strcmp(name, "designated_init"))
+		return sym->designated_init != 0;
+	if (!strcmp(name, "nonstring"))
+		return (sym->ctype.modifiers & MOD_NONSTRING) != 0;
+
+	return false;
+}
+
+static int expand_has_attribute(struct expression *expr)
+{
+	struct symbol *sym = expr->has_attr_sym;
+	struct expression *sub = expr->has_attr_expr;
+	struct ident *attr_id = expr->has_attr_ident;
+	const char *attr_name = attr_id ? normalize_attr_name(show_ident(attr_id)) : "";
+	bool has_attr = false;
+
+	if (!sym && sub) {
+		if (sub->type == EXPR_PREOP && sub->op == '*' &&
+		    sub->unop && sub->unop->type == EXPR_SYMBOL)
+			sym = sub->unop->symbol;
+		else if (sub->type == EXPR_SYMBOL)
+			sym = sub->symbol;
+		else if (sub->ctype)
+			sym = sub->ctype;
+	}
+
+	if (sym) {
+		bool is_decl = expr->has_attr_decl;
+		if (sym->type == SYM_NODE && sym->ctype.base_type) {
+			struct symbol *base = sym->ctype.base_type;
+			if (check_symbol_has_attr(sym, attr_name, is_decl))
+				has_attr = true;
+			else if (base->type != SYM_PTR)
+				has_attr = check_symbol_has_attr(base, attr_name, false);
+			else if (base->ctype.base_type &&
+				 base->ctype.base_type->type == SYM_FN &&
+				 is_fun_attr_name(attr_name))
+				has_attr = check_symbol_has_attr(base, attr_name, false);
+		} else {
+			has_attr = check_symbol_has_attr(sym, attr_name, is_decl);
+		}
+	}
+
+	expr->type = EXPR_VALUE;
+	expr->value = has_attr ? 1 : 0;
+	expr->taint = 0;
+	return 0;
+}
+
 static int expand_expression(struct expression *expr)
 {
 	if (!expr)
@@ -1577,6 +1691,9 @@ static int expand_expression(struct expression *expr)
 	case EXPR_INDEX:
 		return UNSAFE;
 
+	case EXPR_HAS_ATTRIBUTE:
+		return expand_has_attribute(expr);
+
 	case EXPR_SLICE:
 		return expand_expression(expr->base) + 1;
 
diff --git a/expression.c b/expression.c
index 15b9eca351cb..e6f58df84e39 100644
--- a/expression.c
+++ b/expression.c
@@ -185,6 +185,83 @@ static struct token *builtin_types_compatible_p_expr(struct token *token,
 	return token;
 }
 
+static struct token *builtin_has_attribute_expr(struct token *token,
+						struct expression **tree)
+{
+	struct expression *expr = alloc_expression(token->pos, EXPR_HAS_ATTRIBUTE);
+	struct symbol *type_sym = NULL;
+	struct expression *val_expr = NULL;
+	struct ident *attr_ident = NULL;
+
+	token = token->next;
+	if (!match_op(token, '('))
+		return expect(token, '(', "after __builtin_has_attribute");
+	token = token->next;
+
+	if (lookup_type(token)) {
+		token = typename(token, &type_sym, NULL);
+		check_typename_storage(type_sym);
+	} else {
+		token = assignment_expression(token, &val_expr);
+	}
+
+	if (!match_op(token, ','))
+		return expect(token, ',', "in __builtin_has_attribute");
+	token = token->next;
+
+	if (token_type(token) == TOKEN_IDENT &&
+	    (token->ident == &__attribute_ident ||
+	     token->ident == &__attribute___ident)) {
+		token = token->next;
+		if (match_op(token, '(')) {
+			int depth = 1;
+			token = token->next;
+			while (depth && !eof_token(token)) {
+				if (token_type(token) == TOKEN_IDENT && !attr_ident)
+					attr_ident = token->ident;
+				if (token_type(token) == TOKEN_SPECIAL) {
+					if (token->special == '(')
+						depth++;
+					else if (token->special == ')')
+						depth--;
+				}
+				token = token->next;
+			}
+		}
+		if (!attr_ident)
+			sparse_error(token->pos, "expected attribute name");
+	} else if (token_type(token) == TOKEN_IDENT) {
+		attr_ident = token->ident;
+		token = token->next;
+		if (match_op(token, '(')) {
+			int depth = 1;
+			token = token->next;
+			while (depth && !eof_token(token)) {
+				if (token_type(token) == TOKEN_SPECIAL) {
+					if (token->special == '(')
+						depth++;
+					else if (token->special == ')')
+						depth--;
+				}
+				token = token->next;
+			}
+		}
+	} else {
+		sparse_error(token->pos, "expected attribute name");
+		while (!eof_token(token) && !match_op(token, ')'))
+			token = token->next;
+	}
+
+	token = expect(token, ')', "at end of __builtin_has_attribute");
+
+	expr->has_attr_sym = type_sym;
+	expr->has_attr_expr = val_expr;
+	expr->has_attr_ident = attr_ident;
+	expr->flags = CEF_SET_ICE;
+	*tree = expr;
+	return token;
+}
+
 static struct token *builtin_offsetof_expr(struct token *token,
 					   struct expression **tree)
 {
@@ -765,6 +842,11 @@ struct token *primary_expression(struct token *token, struct expression **tree)
 			break;
 		}
 
+		if (token->ident == &__builtin_has_attribute_ident) {
+			token = builtin_has_attribute_expr(token, &expr);
+			break;
+		}
+
 		sym = lookup_symbol(token->ident, NS_SYMBOL | NS_TYPEDEF);
 
 		if (!sym) {
diff --git a/expression.h b/expression.h
index 83857cffc7dc..747056fed5d4 100644
--- a/expression.h
+++ b/expression.h
@@ -65,6 +65,7 @@ enum expression_type {
 	EXPR_SLICE,
 	EXPR_OFFSETOF,
 	EXPR_GENERIC,
+	EXPR_HAS_ATTRIBUTE,
 };
 
 
@@ -265,6 +266,13 @@ struct expression {
 			struct expression *def;
 			struct type_expression *map;
 		};
+		// EXPR_HAS_ATTRIBUTE
+		struct {
+			struct symbol *has_attr_sym;
+			struct expression *has_attr_expr;
+			struct ident *has_attr_ident;
+			bool has_attr_decl;
+		};
 	};
 };
 
diff --git a/gcc-attr-list.h b/gcc-attr-list.h
index 928ea3889e01..3db28e8cc592 100644
--- a/gcc-attr-list.h
+++ b/gcc-attr-list.h
@@ -127,7 +127,6 @@ GCC_ATTR(noipa)
 GCC_ATTR(nomicromips)
 GCC_ATTR(nomips16)
 GCC_ATTR(nonnull)
-GCC_ATTR(nonstring)
 GCC_ATTR(noplt)
 GCC_ATTR(nosave_low_regs)
 GCC_ATTR(not_nested)
@@ -204,6 +203,5 @@ GCC_ATTR(warn_if_not_aligned)
 GCC_ATTR(warn_unused)
 GCC_ATTR(warn_unused_result)
 GCC_ATTR(warning)
-GCC_ATTR(weak)
 GCC_ATTR(weakref)
 GCC_ATTR(zda)
diff --git a/ident-list.h b/ident-list.h
index 4427527580ee..c4a0179ca523 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -66,6 +66,7 @@ IDENT_RESERVED(__alignof__);
 IDENT_RESERVED(__sizeof_ptr__);
 IDENT_RESERVED(__builtin_types_compatible_p);
 IDENT_RESERVED(__builtin_offsetof);
+IDENT(__builtin_has_attribute);
 IDENT_RESERVED(__label__);
 IDENT(gnu);
 IDENT(__gnu__);
diff --git a/inline.c b/inline.c
index d3f27952138b..c857cd953702 100644
--- a/inline.c
+++ b/inline.c
@@ -363,6 +363,12 @@ static struct expression * copy_expression(struct expression *expr)
 		break;
 	}
 
+	case EXPR_HAS_ATTRIBUTE:
+		expr = dup_expression(expr);
+		if (expr->has_attr_expr)
+			expr->has_attr_expr = copy_expression(expr->has_attr_expr);
+		break;
+
 	default:
 		warning(expr->pos, "trying to copy expression type %d", expr->type);
 	}
diff --git a/parse.c b/parse.c
index 3235274e6659..7010d412fdab 100644
--- a/parse.c
+++ b/parse.c
@@ -587,6 +587,8 @@ static struct init_keyword {
 	D("unused",		&attr_mod_op,		.mods = MOD_UNUSED),
 	D("maybe_unused",	&attr_mod_op,		.mods = MOD_UNUSED),
 	D("externally_visible",	&attr_mod_op,		.mods = MOD_EXT_VISIBLE),
+	D("weak",		&attr_mod_op,		.mods = MOD_WEAK),
+	D("nonstring",		&attr_mod_op,		.mods = MOD_NONSTRING),
 	D("force",		&attr_force_op),
 	D("bitwise",		&attr_bitwise_op,	.mods = MOD_BITWISE),
 	D("address_space",	&address_space_op),
@@ -595,11 +597,11 @@ static struct init_keyword {
 	D("noreturn",		&attr_fun_op,		.mods = MOD_NORETURN),
 	D("_Noreturn",		&attr_fun_op,		.mods = MOD_NORETURN),
 	D("pure",		&attr_fun_op,		.mods = MOD_PURE),
-	A("const",		&attr_fun_op,		.mods = MOD_PURE),
+	A("const",		&attr_fun_op,		.mods = MOD_FCONST),
 	D("gnu_inline",		&attr_fun_op,		.mods = MOD_GNU_INLINE),
 	D("nodiscard",		&ignore_attr_op),
 	D("fallthrough",	&ignore_attr_op),
-	D("unsequenced",	&attr_fun_op,		.mods = MOD_PURE),
+	D("unsequenced",	&attr_fun_op,		.mods = MOD_FCONST),
 	D("reproducible",	&attr_fun_op,		.mods = MOD_PURE),
 
 	/* Modes */
@@ -985,6 +987,8 @@ static struct token *struct_union_enum_specifier(enum type type,
 	apply_ctype(token->pos, &sym->ctype, &attr.ctype);
 	sym->packed |= attr.packed;
 	sym->has_aligned |= attr.has_aligned;
+	if (attr.explicit_alignment > sym->explicit_alignment)
+		sym->explicit_alignment = attr.explicit_alignment;
 	if (sym->type == SYM_ENUM && sym->packed && sym->has_aligned)
 		warning(sym->pos,
 			"Clang and GCC treat 'packed' and 'aligned' attributes on enum types differently");
@@ -1462,8 +1466,10 @@ static struct token *ignore_attribute(struct token *token, struct symbol *attr,
 
 static struct token *attribute_packed(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
-	if (!ctx->ctype.alignment)
+	if (!ctx->explicit_alignment)
 		ctx->ctype.alignment = 1;
+	else
+		ctx->ctype.alignment = ctx->explicit_alignment;
 	ctx->packed = 1;
 	return token;
 }
@@ -1481,9 +1487,17 @@ static struct token *attribute_aligned(struct token *token, struct symbol *attr,
 	if (alignment <= 0 || alignment > INT_MAX || (alignment & (alignment - 1))) {
 		warning(token->pos, "I don't like non-power-of-2 alignments");
 		return token;
-	} else if (alignment > ctx->ctype.alignment)
-		ctx->ctype.alignment = alignment;
+	} else {
+		if (alignment > ctx->explicit_alignment)
+			ctx->explicit_alignment = alignment;
+		if (ctx->packed)
+			ctx->ctype.alignment = ctx->explicit_alignment;
+		else if (alignment > ctx->ctype.alignment)
+			ctx->ctype.alignment = alignment;
+	}
 	ctx->has_aligned = 1;
+	ctx->ctype.modifiers |= MOD_ALIGNED;
+	ctx->typedef_mods &= ~MOD_ALIGNED;
 	return token;
 }
 
@@ -1522,6 +1536,7 @@ static void apply_qualifier(struct position *pos, struct ctype *ctx, unsigned lo
 static struct token *attribute_modifier(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
 	apply_mod(&token->pos, &ctx->ctype.modifiers, attr->ctype.modifiers);
+	ctx->typedef_mods &= ~attr->ctype.modifiers;
 	return token;
 }
 
@@ -1779,8 +1794,9 @@ static struct token *attribute_specifier(struct token *token, struct symbol *sym
 
 static unsigned long decl_modifiers(struct decl_state *ctx)
 {
-	unsigned long mods = ctx->ctype.modifiers & MOD_DECLARE;
-	ctx->ctype.modifiers &= ~MOD_DECLARE;
+	unsigned long mask = MOD_DECLARE & ~ctx->typedef_mods;
+	unsigned long mods = ctx->ctype.modifiers & mask;
+	ctx->ctype.modifiers &= ~mask;
 	return ctx->storage_class | mods;
 }
 
@@ -1853,8 +1869,16 @@ static struct token *alignas_specifier(struct token *token, struct symbol *sym,
 		warning(token->pos, "non-power-of-2 alignment");
 		return token;
 	}
-	if (alignment > ctx->ctype.alignment)
-		ctx->ctype.alignment = alignment;
+	if (alignment > 0) {
+		if ((unsigned int)alignment > ctx->explicit_alignment)
+			ctx->explicit_alignment = alignment;
+		if (ctx->packed)
+			ctx->ctype.alignment = ctx->explicit_alignment;
+		else if ((unsigned int)alignment > ctx->ctype.alignment)
+			ctx->ctype.alignment = alignment;
+		ctx->ctype.modifiers |= MOD_ALIGNED;
+		ctx->typedef_mods &= ~MOD_ALIGNED;
+	}
 	return token;
 }
 
@@ -1972,9 +1996,12 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 		if (s->type != SYM_KEYWORD) {
 			if (seen & Set_Any)
 				break;
+			unsigned long prev_mods = ctx->ctype.modifiers;
 			seen |= Set_S | Set_T;
 			ctx->ctype.base_type = s->ctype.base_type;
 			apply_ctype(token->pos, &ctx->ctype, &s->ctype);
+			ctx->typedef_mods |= (s->ctype.modifiers & ~prev_mods) &
+					     (MOD_ALIGNED | MOD_WEAK | MOD_NONSTRING);
 			if (s->ctype.alignment > ctx->typedef_alignment)
 				ctx->typedef_alignment = s->ctype.alignment;
 			token = token->next;
@@ -2464,6 +2491,7 @@ static struct token *pointer(struct token *token, struct decl_state *ctx)
 		ctx->ctype.as = NULL;
 		ctx->ctype.contexts = NULL;
 		ctx->ctype.alignment = 0;
+		ctx->explicit_alignment = 0;
 		ctx->typedef_alignment = 0;
 
 		token = handle_qualifiers(token->next, ctx);
@@ -2528,12 +2556,28 @@ static struct token *handle_bitfield(struct token *token, struct decl_state *ctx
 	return token;
 }
 
+static void apply_fn_ptr_modifiers(struct symbol *decl, unsigned long f_modifiers)
+{
+	struct symbol *base;
+
+	if (!decl)
+		return;
+	f_modifiers |= decl->ctype.modifiers & MOD_FUN_ATTR;
+	if (!f_modifiers)
+		return;
+	base = decl->ctype.base_type;
+	if (base && base->type == SYM_PTR &&
+	    base->ctype.base_type && base->ctype.base_type->type == SYM_FN)
+		base->ctype.modifiers |= f_modifiers;
+}
+
 static struct token *declaration_list(struct token *token, struct symbol_list **list)
 {
 	struct decl_state ctx = {.prefer_abstract = 0};
 	struct ctype saved;
-	unsigned long mod;
-	unsigned int saved_td_align;
+	unsigned long mod, saved_f_modifiers;
+	unsigned int saved_explicit_alignment, saved_td_align;
+	unsigned char saved_packed;
 
 	token = declaration_specifiers(token, &ctx);
 	if (ctx.opaque_fixed_enum)
@@ -2541,12 +2585,18 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 			     "'enum' underlying type may not be specified here");
 	mod = decl_modifiers(&ctx);
 	saved = ctx.ctype;
+	saved_packed = ctx.packed;
+	saved_explicit_alignment = ctx.explicit_alignment;
 	saved_td_align = ctx.typedef_alignment;
+	saved_f_modifiers = ctx.f_modifiers;
 	for (;;) {
 		struct symbol *decl = alloc_symbol(token->pos, SYM_NODE);
 		ctx.cleanup = NULL;
 		ctx.ident_modifiers = 0;
+		ctx.packed = saved_packed;
+		ctx.explicit_alignment = saved_explicit_alignment;
 		ctx.typedef_alignment = saved_td_align;
+		ctx.f_modifiers = saved_f_modifiers;
 		ctx.ident = &decl->ident;
 
 		token = declarator(token, &ctx);
@@ -2560,6 +2610,9 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 		decl->ctype = ctx.ctype;
 		decl->ctype.modifiers |= mod;
 		decl->cleanup = ctx.cleanup;
+		decl->packed = ctx.packed;
+		decl->explicit_alignment = ctx.explicit_alignment;
+		apply_fn_ptr_modifiers(decl, ctx.f_modifiers);
 		decl->endpos = token->pos;
 		if (ctx.has_auto) {
 			sparse_error(
@@ -2623,6 +2676,9 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
 	sym->ctype.modifiers |= decl_modifiers(&ctx);
+	sym->packed = ctx.packed;
+	sym->explicit_alignment = ctx.explicit_alignment;
+	apply_fn_ptr_modifiers(sym, ctx.f_modifiers);
 	sym->endpos = token->pos;
 	sym->forced_arg = ctx.forced;
 	if (ctx.has_auto || ctx.autotype) {
@@ -2691,7 +2747,9 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 	ctx.ctype.modifiers |= ctx.ident_modifiers;
 	apply_modifiers(token->pos, &ctx);
 	sym->ctype = ctx.ctype;
+	sym->packed = ctx.packed;
 	sym->c23_auto = ctx.autotype && ctx.has_auto;
+	sym->explicit_alignment = ctx.explicit_alignment;
 	sym->endpos = token->pos;
 	if (ctx.storage_class == MOD_USERTYPE)
 		sym->explicit_typedef = 1;
@@ -2702,6 +2760,10 @@ struct token *typename(struct token *token, struct symbol **p, int *forced)
 		sym->ctype.modifiers |= MOD_CONSTEXPR;
 	}
 	sym->ctype.modifiers |= mod | decl_modifiers(&ctx);
+	if (sym->ctype.base_type && sym->ctype.base_type->type == SYM_FN)
+		sym->ctype.modifiers |= ctx.f_modifiers;
+	else
+		apply_fn_ptr_modifiers(sym, ctx.f_modifiers);
 	if (forced)
 		*forced = ctx.forced;
 	return token;
@@ -3783,10 +3845,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	struct symbol *decl;
 	struct decl_state ctx = { .ident = &ident };
 	struct ctype saved;
-	unsigned long saved_f_modifiers;
-	unsigned int saved_td_align;
 	struct symbol *base_type;
-	unsigned long mod;
+	unsigned long mod, saved_f_modifiers;
+	unsigned int saved_explicit_alignment, saved_td_align;
+	unsigned char saved_packed;
 	int is_typedef;
 	bool delay_bind = false;
 
@@ -3822,9 +3884,11 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		sparse_error(ctx.opaque_fixed_enum_pos,
 			     "'enum' underlying type may not be specified here");
 
-	saved = ctx.ctype;
-	saved_f_modifiers = ctx.f_modifiers;
+	saved_packed = ctx.packed;
+	saved_explicit_alignment = ctx.explicit_alignment;
 	saved_td_align = ctx.typedef_alignment;
+	saved_f_modifiers = ctx.f_modifiers;
+	saved = ctx.ctype;
 	token = declarator(token, &ctx);
 	token = handle_asm_name(token, &ctx);
 	token = handle_attributes(token, &ctx);
@@ -3834,7 +3898,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	decl->ctype = ctx.ctype;
 	decl->ctype.modifiers |= mod;
 	decl->cleanup = ctx.cleanup;
+	decl->packed = ctx.packed;
 	decl->c23_auto = ctx.autotype && ctx.has_auto;
+	decl->explicit_alignment = ctx.explicit_alignment;
+	apply_fn_ptr_modifiers(decl, ctx.f_modifiers);
 	decl->endpos = token->pos;
 
 	/* Just a type declaration? */
@@ -4089,11 +4156,13 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		token = token->next;
 		ident = NULL;
 		decl = alloc_symbol(token->pos, SYM_NODE);
+		ctx.packed = saved_packed;
+		ctx.explicit_alignment = saved_explicit_alignment;
+		ctx.typedef_alignment = saved_td_align;
+		ctx.f_modifiers = saved_f_modifiers;
 		ctx.ctype = saved;
 		ctx.cleanup = NULL;
 		ctx.ident_modifiers = 0;
-		ctx.f_modifiers = saved_f_modifiers;
-		ctx.typedef_alignment = saved_td_align;
 		token = handle_attributes(token, &ctx);
 		token = declarator(token, &ctx);
 		token = handle_asm_name(token, &ctx);
@@ -4103,7 +4172,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		decl->ctype = ctx.ctype;
 		decl->ctype.modifiers |= mod;
 		decl->cleanup = ctx.cleanup;
+		decl->packed = ctx.packed;
 		decl->c23_auto = ctx.autotype && ctx.has_auto;
+		decl->explicit_alignment = ctx.explicit_alignment;
+		apply_fn_ptr_modifiers(decl, ctx.f_modifiers);
 		decl->endpos = token->pos;
 		if (!ident) {
 			sparse_error(token->pos, "expected identifier name in type definition");
diff --git a/show-parse.c b/show-parse.c
index 9ed95f141cfe..f44a5f052ae7 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -138,6 +138,7 @@ static const char *show_modifiers(unsigned long mod, int term)
 		{ MOD_NODEREF, "[noderef]" },
 		{ MOD_NORETURN, "[noreturn]" },
 		{ MOD_PURE, "[pure]" },
+		{ MOD_FCONST, "[const]" },
 		{ MOD_SAFE, "[safe]" },
 		{ MOD_SIGNED, "[signed]" },
 		{ MOD_TLS, "[tls]" },
@@ -145,6 +146,9 @@ static const char *show_modifiers(unsigned long mod, int term)
 		{ MOD_UNSIGNED, "[unsigned]" },
 		{ MOD_UNUSED, "[unused]" },
 		{ MOD_USERTYPE, "[usertype]" },
+		{ MOD_WEAK, "[weak]" },
+		{ MOD_ALIGNED, "[aligned]" },
+		{ MOD_NONSTRING, "[nonstring]" },
 	};
 
 	for (i = 0; i < ARRAY_SIZE(mod_names); i++) {
@@ -1188,6 +1192,7 @@ int show_expression(struct expression *expr)
 	case EXPR_PTRSIZEOF:
 	case EXPR_ALIGNOF:
 	case EXPR_OFFSETOF:
+	case EXPR_HAS_ATTRIBUTE:
 		warning(expr->pos, "invalid expression after evaluation");
 		return 0;
 	case EXPR_CAST:
diff --git a/simplify.c b/simplify.c
index 88f261da297e..b93fbf950d3d 100644
--- a/simplify.c
+++ b/simplify.c
@@ -365,7 +365,7 @@ int kill_insn(struct instruction *insn, int force)
 			/* a "pure" function can be killed too */
 			struct symbol *fntype = first_symbol(insn->fntypes);
 
-			if (!(fntype->ctype.modifiers & MOD_PURE))
+			if (!(fntype->ctype.modifiers & (MOD_PURE | MOD_FCONST)))
 				return 0;
 		}
 		kill_use_list(insn->arguments);
diff --git a/symbol.c b/symbol.c
index 69fb0670c341..d2f308519cdc 100644
--- a/symbol.c
+++ b/symbol.c
@@ -141,19 +141,23 @@ static void lay_out_struct(struct symbol *sym, struct struct_union_info *info)
 		info->flex_array = sym;
 	}
 
-	alignment = info->packed ? 1 : sym->ctype.alignment;
+	bool packed = info->packed || sym->packed;
+
+	alignment = packed ? (sym->explicit_alignment ? : 1) : sym->ctype.alignment;
 	align_bit_mask = bytes_to_bits(alignment) - 1;
 
 	/*
 	 * Bitfields have some very special rules..
 	 */
 	if (is_bitfield_type (sym)) {
+		if (sym->explicit_alignment)
+			bit_size = (bit_size + align_bit_mask) & ~align_bit_mask;
 		unsigned long bit_offset = bit_size & align_bit_mask;
 		int room = bitfield_base_size(sym) - bit_offset;
 		// Zero-width fields just fill up the unit.
 		int width = base_size ? : (bit_offset ? room : 0);
 
-		if (width > room && !info->packed) {
+		if (width > room && !packed) {
 			bit_size = (bit_size + align_bit_mask) & ~align_bit_mask;
 			bit_offset = 0;
 		}
@@ -163,7 +167,7 @@ static void lay_out_struct(struct symbol *sym, struct struct_union_info *info)
 		info->bit_size = bit_size + width;
 		// warning (sym->pos, "bitfield: offset=%d:%d  size=:%d", sym->offset, sym->bit_offset, width);
 
-		if (info->packed && sym->type == SYM_NODE)
+		if (packed && sym->type == SYM_NODE)
 			sym->packed = 1;
 		return;
 	}
@@ -232,10 +236,13 @@ static struct symbol * examine_struct_union_type(struct symbol *sym, int advance
 		if (!member->ident)
 			examine_anonymous_member(member);
 
-		if (member->ctype.alignment > info.max_align && !sym->packed) {
+		unsigned long member_align = (sym->packed || member->packed)
+			? (member->explicit_alignment ? : 1)
+			: member->ctype.alignment;
+		if (member_align > info.max_align) {
 			// Unnamed bitfields do not affect alignment.
 			if (member->ident || !is_bitfield_type(member))
-				info.max_align = member->ctype.alignment;
+				info.max_align = member_align;
 		}
 
 		if (has_flexible_array(member))
@@ -245,7 +252,7 @@ static struct symbol * examine_struct_union_type(struct symbol *sym, int advance
 		fn(member, &info);
 	} END_FOR_EACH_PTR(member);
 
-	if (!sym->ctype.alignment)
+	if (sym->ctype.alignment < info.max_align)
 		sym->ctype.alignment = info.max_align;
 	bit_size = info.bit_size;
 	if (info.align_size) {
@@ -566,7 +573,7 @@ static struct symbol * examine_node_type(struct symbol *sym)
 	/* Pick up signedness information into the node */
 	sym->ctype.modifiers |= (MOD_SIGNEDNESS & base_type->ctype.modifiers);
 
-	if (!sym->ctype.alignment)
+	if (!sym->ctype.alignment || (!sym->packed && sym->ctype.alignment < alignment))
 		sym->ctype.alignment = alignment;
 
 	/* Unsized array? The size might come from the initializer.. */
@@ -885,8 +892,12 @@ static void inherit_declaration(struct symbol *sym, struct symbol *prev)
 {
 	unsigned long mods = prev->ctype.modifiers;
 
-	// inherit function attributes
-	sym->ctype.modifiers |= mods & MOD_FUN_ATTR;
+	// inherit function and declaration attributes
+	sym->ctype.modifiers |= mods & (MOD_FUN_ATTR | MOD_WEAK | MOD_ALIGNED | MOD_NONSTRING);
+	if ((mods & MOD_ALIGNED) && prev->ctype.alignment > sym->ctype.alignment)
+		sym->ctype.alignment = prev->ctype.alignment;
+	if (prev->explicit_alignment > sym->explicit_alignment)
+		sym->explicit_alignment = prev->explicit_alignment;
 }
 
 void check_declaration(struct symbol *sym)
@@ -906,6 +917,7 @@ void check_declaration(struct symbol *sym)
 		if (sym->ctype.modifiers & MOD_EXTERN) {
 			if ((next->ctype.modifiers & (MOD_TOPLEVEL|MOD_STATIC)) == MOD_TOPLEVEL) {
 				sym->same_symbol = next;
+				inherit_declaration(sym, next);
 				return;
 			}
 		}
diff --git a/symbol.h b/symbol.h
index 691213a0b454..cd9268703192 100644
--- a/symbol.h
+++ b/symbol.h
@@ -117,6 +117,8 @@ struct decl_state {
 	unsigned long f_modifiers;		// function attributes
 	unsigned long ident_modifiers;		// declarator-id C23 attributes
 	unsigned long storage_class;
+	unsigned long typedef_mods;
+	unsigned int explicit_alignment;
 	unsigned int typedef_alignment;
 	unsigned char prefer_abstract;
 	unsigned char autotype;
@@ -199,6 +201,7 @@ struct symbol {
 			unsigned long	offset;
 			int		bit_size;
 			int bitint_width;
+			unsigned int explicit_alignment;
 			unsigned int bit_offset : 8;
 			unsigned int bogus_linear : 1;
 			unsigned int variadic : 1;
@@ -276,8 +279,8 @@ struct symbol {
 #define MOD_USERTYPE		0x00020000
 #define MOD_CONSTEXPR 0x00040000
 #define MOD_BITINT 0x00080000
-// MOD UNUSED		0x00100000
-// MOD UNUSED		0x00200000
+#define MOD_WEAK		0x00100000
+#define MOD_ALIGNED		0x00200000
 
 #define MOD_UNUSED		0x00400000
 #define MOD_SAFE		0x00800000	// non-null/non-trapping pointer
@@ -287,6 +290,8 @@ struct symbol {
 #define MOD_NODEREF		0x08000000
 #define MOD_NORETURN		0x10000000
 #define MOD_EXT_VISIBLE		0x20000000
+#define MOD_NONSTRING		0x40000000
+#define MOD_FCONST		0x80000000UL
 
 
 #define MOD_ACCESS	(MOD_ASSIGNED | MOD_ADDRESSABLE)
@@ -295,21 +300,22 @@ struct symbol {
 #define MOD_ESIGNED	(MOD_SIGNED | MOD_EXPLICITLY_SIGNED)
 #define MOD_SIGNEDNESS	(MOD_SIGNED | MOD_UNSIGNED | MOD_EXPLICITLY_SIGNED)
 #define MOD_SPECIFIER (MOD_SIGNEDNESS | MOD_BITINT)
-#define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE | MOD_CONSTEXPR)
+#define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE | MOD_WEAK | MOD_ALIGNED | MOD_NONSTRING | MOD_CONSTEXPR)
 #define MOD_QUALIFIER	(MOD_CONST | MOD_VOLATILE | MOD_RESTRICT)
 /* modifiers for function attributes */
-#define MOD_FUN_ATTR	(MOD_PURE|MOD_NORETURN)
+#define MOD_FUN_ATTR	(MOD_PURE|MOD_NORETURN|MOD_FCONST)
 #define MOD_PTRINHERIT	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NODEREF | MOD_FUN_ATTR | MOD_NOCAST)
 /* modifiers preserved by typeof() operator */
 #define MOD_TYPEOF	(MOD_QUALIFIER | MOD_ATOMIC | MOD_NOCAST | MOD_SPECIFIER)
 /* like cvr-qualifiers but 'reversed' (OK: source <= target) */
-#define MOD_REV_QUAL	(MOD_PURE|MOD_NORETURN)
+#define MOD_REV_QUAL	(MOD_PURE|MOD_NORETURN|MOD_FCONST)
 /* do not warn when these are duplicated */
-#define MOD_DUP_OK	(MOD_UNUSED|MOD_GNU_INLINE|MOD_USERTYPE)
+#define MOD_DUP_OK	(MOD_UNUSED|MOD_GNU_INLINE|MOD_WEAK|MOD_ALIGNED|MOD_USERTYPE|MOD_NONSTRING|MOD_FUN_ATTR)
 /* must be part of the declared symbol, not its type */
 #define MOD_DECLARE                                                         \
 	(MOD_STORAGE | MOD_INLINE | MOD_TLS | MOD_GNU_INLINE | MOD_UNUSED | \
-	 MOD_PURE | MOD_NORETURN | MOD_EXT_VISIBLE | MOD_CONSTEXPR)
+	 MOD_PURE | MOD_NORETURN | MOD_FCONST | MOD_EXT_VISIBLE |           \
+	 MOD_CONSTEXPR | MOD_WEAK | MOD_ALIGNED | MOD_NONSTRING)
 
 /* Current parsing/evaluation function */
 extern struct symbol *current_fn;
diff --git a/validation/builtin-has-attribute-err.c b/validation/builtin-has-attribute-err.c
new file mode 100644
index 000000000000..cb756fdd2bab
--- /dev/null
+++ b/validation/builtin-has-attribute-err.c
@@ -0,0 +1,19 @@
+static int x;
+
+static int test_err(void)
+{
+	return __builtin_has_attribute(x, 123) +
+	       __builtin_has_attribute(x, "aligned") +
+	       __builtin_has_attribute(x, );
+}
+
+/*
+ * check-name: builtin-has-attribute-err
+ * check-command: sparse $file
+ *
+ * check-error-start
+builtin-has-attribute-err.c:5:43: error: expected attribute name
+builtin-has-attribute-err.c:6:43: error: expected attribute name
+builtin-has-attribute-err.c:7:43: error: expected attribute name
+ * check-error-end
+ */
diff --git a/validation/builtin-has-attribute-more.c b/validation/builtin-has-attribute-more.c
new file mode 100644
index 000000000000..295301d06930
--- /dev/null
+++ b/validation/builtin-has-attribute-more.c
@@ -0,0 +1,157 @@
+extern void fn_noreturn(void) __attribute__((noreturn));
+_Static_assert(__builtin_has_attribute(fn_noreturn, noreturn), "noreturn");
+_Static_assert(__builtin_has_attribute(fn_noreturn, __noreturn__), "__noreturn__");
+
+extern int fn_pure(int) __attribute__((pure));
+_Static_assert(__builtin_has_attribute(fn_pure, pure), "pure");
+_Static_assert(__builtin_has_attribute(fn_pure, __pure__), "__pure__");
+_Static_assert(!__builtin_has_attribute(fn_pure, const), "pure is not const");
+_Static_assert(!__builtin_has_attribute(fn_pure, __const__), "pure is not __const__");
+
+extern int fn_const(int) __attribute__((const));
+_Static_assert(__builtin_has_attribute(fn_const, const), "const");
+_Static_assert(__builtin_has_attribute(fn_const, __const__), "__const__");
+_Static_assert(!__builtin_has_attribute(fn_const, pure), "const is not pure");
+_Static_assert(!__builtin_has_attribute(fn_const, __pure__), "const is not __pure__");
+
+/* const on object/member declarations vs expressions/types */
+static const int ci = 5;
+static int vi = 5;
+static const int ca[3] = { 0 };
+static const int *cip;
+static int * const cpi = (void *)0;
+
+_Static_assert(__builtin_has_attribute(ci, const), "const var");
+_Static_assert(!__builtin_has_attribute(vi, const), "non-const var");
+_Static_assert(__builtin_has_attribute(ca, const), "const array");
+_Static_assert(!__builtin_has_attribute(cip, const), "ptr to const");
+_Static_assert(!__builtin_has_attribute(*cip, const), "deref ptr to const");
+_Static_assert(__builtin_has_attribute(cpi, const), "const ptr");
+_Static_assert(!__builtin_has_attribute(const int, const), "const int typename");
+
+struct SConst {
+	const int cfield;
+	int plain;
+};
+static struct SConst sc;
+static const struct SConst csc = { 0, 0 };
+
+_Static_assert(__builtin_has_attribute(sc.cfield, const), "const field");
+_Static_assert(__builtin_has_attribute(((struct SConst *)0)->cfield, const), "arrow const field");
+_Static_assert(!__builtin_has_attribute(sc.plain, const), "plain field");
+_Static_assert(!__builtin_has_attribute(csc.plain, const), "const struct plain field");
+_Static_assert(!__builtin_has_attribute(((const struct SConst *)0)->plain, const), "const arrow plain field");
+
+/* aligned, _Alignas, typedef pointer non-leakage, and member non-inheritance */
+typedef int aligned_int __attribute__((aligned(16)));
+static aligned_int ax;
+static aligned_int *ap;
+static aligned_int mx, *mp;
+static _Alignas(16) int a16;
+static _Alignas(0) int a0;
+
+_Static_assert(_Alignof(ax) == 16, "ax align 16");
+_Static_assert(_Alignof(ap) == sizeof(void *), "ap ptr align");
+_Static_assert(_Alignof(mx) == 16, "mx align 16");
+_Static_assert(_Alignof(mp) == sizeof(void *), "mp ptr align");
+_Static_assert(__builtin_has_attribute(aligned_int, aligned), "typedef aligned");
+_Static_assert(__builtin_has_attribute(ax, aligned), "ax aligned");
+_Static_assert(!__builtin_has_attribute(ap, aligned), "ap not aligned");
+_Static_assert(__builtin_has_attribute(*ap, aligned), "*ap aligned");
+_Static_assert(__builtin_has_attribute(mx, aligned), "mx aligned");
+_Static_assert(!__builtin_has_attribute(mp, aligned), "mp not aligned");
+_Static_assert(__builtin_has_attribute(*mp, aligned), "*mp aligned");
+_Static_assert(__builtin_has_attribute(a16, aligned), "_Alignas(16) aligned");
+_Static_assert(!__builtin_has_attribute(a0, aligned), "_Alignas(0) not aligned");
+
+struct SAligned {
+	int a;
+	int b __attribute__((aligned(8)));
+} __attribute__((aligned(16)));
+static struct SAligned sa;
+static struct SAligned sa_var __attribute__((aligned(32)));
+
+_Static_assert(__builtin_has_attribute(struct SAligned, aligned), "struct SAligned aligned");
+_Static_assert(!__builtin_has_attribute(sa.a, aligned), "sa.a not aligned");
+_Static_assert(!__builtin_has_attribute(sa_var.a, aligned), "sa_var.a not aligned");
+_Static_assert(__builtin_has_attribute(sa.b, aligned), "sa.b aligned");
+
+struct SOuter {
+	struct SAligned inner;
+	struct SAligned arr[2];
+};
+static struct SOuter *so_ptr;
+static struct SAligned sa_arr[2];
+
+_Static_assert(__builtin_has_attribute(so_ptr->inner.b, aligned), "nested member aligned");
+_Static_assert(!__builtin_has_attribute(so_ptr->inner.a, aligned), "nested member not aligned");
+_Static_assert(__builtin_has_attribute(sa_arr[1].b, aligned), "array subscript member aligned");
+_Static_assert(!__builtin_has_attribute(sa_arr[1].a, aligned), "array subscript member not aligned");
+_Static_assert(__builtin_has_attribute(so_ptr->arr[1].b, aligned), "nested array member aligned");
+
+/* typename operands */
+_Static_assert(__builtin_has_attribute(int __attribute__((aligned(8))), aligned), "typename aligned");
+_Static_assert(!__builtin_has_attribute(int, aligned), "typename int not aligned");
+_Static_assert(__builtin_has_attribute(struct __attribute__((packed)) { int x; }, packed), "typename pre-packed");
+_Static_assert(__builtin_has_attribute(struct { int x; } __attribute__((packed)), packed), "typename post-packed");
+_Static_assert(!__builtin_has_attribute(struct { int x; }, packed), "typename unpacked");
+extern void (*fp_noreturn)(void) __attribute__((noreturn));
+_Static_assert(__builtin_has_attribute(fp_noreturn, noreturn), "fnptr decl noreturn");
+extern int (*fp_after_ident [[unsequenced]])(int);
+extern int (*fn_ret_fp_a(void) [[unsequenced]])(int);
+extern int (*fn_ret_fp_b(void))(int) [[unsequenced]];
+extern int (*fn_ret_fp_c [[unsequenced]] (void))(int);
+_Static_assert(__builtin_has_attribute(fp_after_ident, unsequenced), "fnptr after ident");
+_Static_assert(__builtin_has_attribute(fn_ret_fp_a, unsequenced), "nested fn unseq");
+_Static_assert(!__builtin_has_attribute(fn_ret_fp_a(), unsequenced), "ret fp not unseq");
+_Static_assert(!__builtin_has_attribute(fn_ret_fp_b, unsequenced), "outer fn not unseq");
+_Static_assert(__builtin_has_attribute(fn_ret_fp_b(), unsequenced), "ret fp unseq");
+_Static_assert(__builtin_has_attribute(fn_ret_fp_c, unsequenced), "fn after ident unseq");
+_Static_assert(!__builtin_has_attribute(fn_ret_fp_c(), unsequenced), "fn after ident ret fp");
+
+/* Redeclaration attribute inheritance */
+void ext_redecl_weak(void) __attribute__((weak));
+void ext_redecl_weak(void) {}
+_Static_assert(__builtin_has_attribute(ext_redecl_weak, weak), "redecl weak");
+
+int ext_redecl_const(int x) __attribute__((const));
+int ext_redecl_const(int x) { return x; }
+_Static_assert(__builtin_has_attribute(ext_redecl_const, const), "redecl const");
+_Static_assert(!__builtin_has_attribute(ext_redecl_const, pure), "redecl const not pure");
+
+int ext_redecl_pure(int x) __attribute__((pure));
+int ext_redecl_pure(int x) { return x; }
+_Static_assert(__builtin_has_attribute(ext_redecl_pure, pure), "redecl pure");
+_Static_assert(!__builtin_has_attribute(ext_redecl_pure, const), "redecl pure not const");
+
+extern int ext_redecl_aligned __attribute__((aligned(16)));
+int ext_redecl_aligned;
+_Static_assert(__builtin_has_attribute(ext_redecl_aligned, aligned), "redecl aligned");
+_Static_assert(_Alignof(ext_redecl_aligned) == 16, "redecl align 16");
+
+#ifdef __CHECKER__
+static int __attribute__((noderef)) *ptr_noderef;
+_Static_assert(__builtin_has_attribute(*ptr_noderef, noderef), "noderef");
+_Static_assert(__builtin_has_attribute(*ptr_noderef, __noderef__), "__noderef__");
+_Static_assert(!__builtin_has_attribute(ptr_noderef, noderef), "ptr itself not noderef");
+
+static int __attribute__((address_space(1))) *ptr_as;
+_Static_assert(__builtin_has_attribute(*ptr_as, address_space), "address_space");
+_Static_assert(__builtin_has_attribute(*ptr_as, __address_space__), "__address_space__");
+_Static_assert(!__builtin_has_attribute(ptr_as, address_space), "ptr itself not address_space");
+
+typedef unsigned int __attribute__((bitwise)) __be32;
+static __be32 be_var;
+static __be32 *be_ptr;
+_Static_assert(__builtin_has_attribute(__be32, bitwise), "__be32 bitwise");
+_Static_assert(__builtin_has_attribute(__be32, __bitwise__), "__be32 __bitwise__");
+_Static_assert(__builtin_has_attribute(be_var, bitwise), "be_var bitwise");
+_Static_assert(__builtin_has_attribute(*be_ptr, bitwise), "*be_ptr bitwise");
+_Static_assert(!__builtin_has_attribute(be_ptr, bitwise), "be_ptr not bitwise");
+_Static_assert(!__builtin_has_attribute(int, bitwise), "int not bitwise");
+#endif
+
+/*
+ * check-name: builtin-has-attribute-more
+ * check-command: sparse -Wno-decl $file
+ */
diff --git a/validation/builtin-has-attribute-nonstring.c b/validation/builtin-has-attribute-nonstring.c
new file mode 100644
index 000000000000..7662cbc13040
--- /dev/null
+++ b/validation/builtin-has-attribute-nonstring.c
@@ -0,0 +1,35 @@
+struct s_msg {
+	char str[16];
+	char nonstr[16] __attribute__((__nonstring__));
+};
+
+static struct s_msg msg;
+static char global_nonstr[32] __attribute__((nonstring));
+static char global_str[32];
+static char *ptr_nonstr __attribute__((nonstring));
+static char *ptr_str;
+
+extern char ext_nonstr[8] __attribute__((nonstring));
+char ext_nonstr[8];
+
+_Static_assert(__has_attribute(nonstring), "has_attribute nonstring");
+_Static_assert(__has_attribute(__nonstring__), "has_attribute __nonstring__");
+_Static_assert(__builtin_has_attribute(msg.nonstr, nonstring), "msg.nonstr has nonstring");
+_Static_assert(__builtin_has_attribute(msg.nonstr, __nonstring__), "msg.nonstr has __nonstring__");
+_Static_assert(!__builtin_has_attribute(msg.str, nonstring), "msg.str does not have nonstring");
+_Static_assert(__builtin_has_attribute(global_nonstr, nonstring), "global_nonstr has nonstring");
+_Static_assert(!__builtin_has_attribute(global_str, nonstring), "global_str does not have nonstring");
+_Static_assert(__builtin_has_attribute(ptr_nonstr, nonstring), "ptr_nonstr has nonstring");
+_Static_assert(!__builtin_has_attribute(ptr_str, nonstring), "ptr_str does not have nonstring");
+_Static_assert(__builtin_has_attribute(ext_nonstr, nonstring), "redecl nonstring");
+
+#ifdef __CHECKER__
+typedef char nonstr_arr[8] __attribute__((nonstring));
+static nonstr_arr *ns_p;
+_Static_assert(!__builtin_has_attribute(ns_p, nonstring), "ptr to typedef nonstring does not leak");
+#endif
+
+/*
+ * check-name: builtin-has-attribute-nonstring
+ * check-command: sparse -Wno-decl $file
+ */
diff --git a/validation/builtins-kernel.c b/validation/builtins-kernel.c
index 05c13f4c1eae..f4b1d4b45b68 100644
--- a/validation/builtins-kernel.c
+++ b/validation/builtins-kernel.c
@@ -2,6 +2,10 @@
 #error "missing __builtin_dynamic_object_size"
 #endif
 
+#if !__has_builtin(__builtin_has_attribute)
+#error "missing __builtin_has_attribute"
+#endif
+
 #if !__has_builtin(__builtin_thread_pointer)
 #error "missing __builtin_thread_pointer"
 #endif
@@ -14,6 +18,33 @@
 #error "missing __builtin_counted_by_ref"
 #endif
 
+extern int weak_var __attribute__((__weak__));
+_Static_assert(__builtin_has_attribute(weak_var, __weak__), "weak_var has __weak__");
+_Static_assert(__builtin_has_attribute(weak_var, weak), "weak_var has weak");
+_Static_assert(__builtin_has_attribute(*&weak_var, __weak__), "*&weak_var has __weak__");
+
+struct s_packed {
+	int a;
+	char b;
+} __attribute__((__packed__));
+
+_Static_assert(__builtin_has_attribute(struct s_packed, packed), "s_packed has packed");
+_Static_assert(__builtin_has_attribute(struct s_packed, __packed__), "s_packed has __packed__");
+
+struct s_normal {
+	int a;
+	char b;
+	int c __attribute__((__packed__));
+};
+static struct s_normal s_norm_inst;
+
+_Static_assert(!__builtin_has_attribute(struct s_normal, packed), "s_normal not packed");
+_Static_assert(__builtin_has_attribute(s_norm_inst.c, packed), "s_norm_inst.c packed");
+_Static_assert(!__builtin_has_attribute(weak_var, packed), "weak_var not packed");
+
+static int aligned_var __attribute__((aligned(16)));
+_Static_assert(__builtin_has_attribute(aligned_var, aligned), "aligned_var has aligned");
+
 struct fam {
 	int count;
 	char arr[];
diff --git a/validation/struct-packed-aligned-layout.c b/validation/struct-packed-aligned-layout.c
new file mode 100644
index 000000000000..c85a4a24ac7f
--- /dev/null
+++ b/validation/struct-packed-aligned-layout.c
@@ -0,0 +1,135 @@
+typedef int aligned16 __attribute__((aligned(16)));
+
+/* 1. Post-brace packed struct with explicitly aligned member */
+struct S1 {
+	char a;
+	int b __attribute__((aligned(8)));
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct S1, b) == 8, "S1 offset b");
+_Static_assert(_Alignof(struct S1) == 8, "S1 align");
+_Static_assert(sizeof(struct S1) == 16, "S1 size");
+
+/* 2. Pre-tag packed struct with explicitly aligned member */
+struct __attribute__((packed)) S2 {
+	char a;
+	int b __attribute__((aligned(8)));
+};
+_Static_assert(__builtin_offsetof(struct S2, b) == 8, "S2 offset b");
+_Static_assert(_Alignof(struct S2) == 8, "S2 align");
+_Static_assert(sizeof(struct S2) == 16, "S2 size");
+
+/* 3. Struct with both packed and aligned(8) on the struct itself */
+struct S3 {
+	char a;
+	int b;
+} __attribute__((packed, aligned(8)));
+_Static_assert(__builtin_offsetof(struct S3, b) == 1, "S3 offset b");
+_Static_assert(_Alignof(struct S3) == 8, "S3 align");
+_Static_assert(sizeof(struct S3) == 8, "S3 size");
+
+/* 4. Nested S1 inside packed S4 without explicit aligned on inner */
+struct S4 {
+	char a;
+	struct S1 inner;
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct S4, inner) == 1, "S4 offset inner");
+_Static_assert(_Alignof(struct S4) == 1, "S4 align");
+_Static_assert(sizeof(struct S4) == 17, "S4 size");
+
+/* 5. Nested S3 inside packed S5 without explicit aligned on inner */
+struct S5 {
+	char a;
+	struct S3 inner;
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct S5, inner) == 1, "S5 offset inner");
+_Static_assert(_Alignof(struct S5) == 1, "S5 align");
+_Static_assert(sizeof(struct S5) == 9, "S5 size");
+
+/* 6. Nested S1 inside packed S6 with smaller explicit aligned(4) on inner */
+struct S6 {
+	char a;
+	struct S1 inner __attribute__((aligned(4)));
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct S6, inner) == 4, "S6 offset inner");
+_Static_assert(_Alignof(struct S6) == 4, "S6 align");
+_Static_assert(sizeof(struct S6) == 20, "S6 size");
+
+/* 7. Unpacked struct with member carrying both packed and aligned(8) */
+struct S7 {
+	char a;
+	int b __attribute__((packed, aligned(8)));
+};
+_Static_assert(__builtin_offsetof(struct S7, b) == 8, "S7 offset b");
+_Static_assert(_Alignof(struct S7) == 8, "S7 align");
+_Static_assert(sizeof(struct S7) == 16, "S7 size");
+
+/* 8. _Alignas(8) and _Alignas(0) inside packed structs */
+struct S8 {
+	char a;
+	_Alignas(8) int b;
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct S8, b) == 8, "S8 offset b");
+_Static_assert(_Alignof(struct S8) == 8, "S8 align");
+_Static_assert(sizeof(struct S8) == 16, "S8 size");
+
+struct S9 {
+	char a;
+	_Alignas(0) int b;
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct S9, b) == 1, "S9 offset b");
+_Static_assert(_Alignof(struct S9) == 1, "S9 align");
+_Static_assert(sizeof(struct S9) == 5, "S9 size");
+
+/* 9. Aligned typedef inside packed struct / packed field / pointer */
+struct ST1 {
+	char a;
+	aligned16 b;
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct ST1, b) == 1, "ST1 offset b");
+_Static_assert(_Alignof(struct ST1) == 1, "ST1 align");
+_Static_assert(sizeof(struct ST1) == 5, "ST1 size");
+
+struct ST2 {
+	char a;
+	aligned16 b __attribute__((packed));
+};
+_Static_assert(__builtin_offsetof(struct ST2, b) == 1, "ST2 offset b");
+_Static_assert(_Alignof(struct ST2) == 1, "ST2 align");
+_Static_assert(sizeof(struct ST2) == 5, "ST2 size");
+
+struct ST3 {
+	char a;
+	aligned16 b __attribute__((aligned(8)));
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct ST3, b) == 8, "ST3 offset b");
+_Static_assert(_Alignof(struct ST3) == 8, "ST3 align");
+_Static_assert(sizeof(struct ST3) == 16, "ST3 size");
+
+struct ST4 {
+	char a;
+	aligned16 *p;
+} __attribute__((packed));
+_Static_assert(__builtin_offsetof(struct ST4, p) == 1, "ST4 offset p");
+_Static_assert(_Alignof(struct ST4) == 1, "ST4 align");
+_Static_assert(sizeof(struct ST4) == 1 + sizeof(void *), "ST4 size");
+
+/* 10. Packed union with explicitly aligned member */
+union U1 {
+	char a;
+	int b __attribute__((aligned(8)));
+} __attribute__((packed));
+_Static_assert(_Alignof(union U1) == 8, "U1 align");
+_Static_assert(sizeof(union U1) == 8, "U1 size");
+
+/* 11. Packed struct with explicitly aligned bitfield */
+struct BF1 {
+	unsigned char a : 1;
+	unsigned int b : 3 __attribute__((aligned(8)));
+} __attribute__((packed));
+_Static_assert(_Alignof(struct BF1) == 8, "BF1 align");
+_Static_assert(sizeof(struct BF1) == 16, "BF1 size");
+
+/*
+ * check-name: struct-packed-aligned-layout
+ * check-command: sparse $file
+ */
diff --git a/validation/weak-const-no-fold.c b/validation/weak-const-no-fold.c
new file mode 100644
index 000000000000..5486a759226b
--- /dev/null
+++ b/validation/weak-const-no-fold.c
@@ -0,0 +1,15 @@
+const int __attribute__((weak)) weak_const = 10;
+
+int read_weak_const(void)
+{
+	return weak_const;
+}
+
+/*
+ * check-name: weak-const-no-fold
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-contains: load\\.32.*\\[weak_const\\]
+ * check-output-excludes: ret\\.32.*\\$10
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext
  2026-11-09 20:31 Bart Van Assche
                   ` (66 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
                   ` (4 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Implement evaluation and linearization for __attribute__((cleanup(...)))
so that scope-based resource management in the Linux kernel (such as
guard(mutex)(&lock), scoped_guard, and __free(...) in
include/linux/cleanup.h) is modeled accurately by -Wcontext:
- Validate in parse.c and evaluate.c that the cleanup attribute names a
  function, applies only to local automatic variables, and type-checks
  calls to fn(&var).
- Maintain a lexical cleanup_scope tree in linearize.c across compound
  statements, declarations, iterators, and switches, and record cleanup
  variables as their declarations are linearized.
- Emit cleanup calls in reverse declaration order (LIFO) on block exit,
  loop/switch break and continue, goto, and return after evaluating the
  return expression.
- Copy expanded cleanup expressions correctly in inline.c: handle
  statement expressions that expand_compound() collapsed into a single
  non-compound statement and preserve the constraint flags of asm
  operands since cleanup expressions are not re-evaluated after copying.

Add validation tests in validation/cleanup-context.c,
validation/cleanup-errors.c, validation/cleanup-inline-loop.c,
validation/linear/cleanup-control-flow.c, and
validation/linear/cleanup-stmt-expr-asm.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                                |  38 ++
 expand.c                                  |   8 +-
 inline.c                                  | 218 +++++++++-
 linearize.c                               | 478 +++++++++++++++++++++-
 linearize.h                               |   4 +
 parse.c                                   |  22 +-
 parse.h                                   |   4 +
 symbol.h                                  |   1 +
 validation/cleanup-context.c              | 161 ++++++++
 validation/cleanup-errors.c               |  77 ++++
 validation/cleanup-inline-loop.c          |  31 ++
 validation/linear/cleanup-control-flow.c  |  62 +++
 validation/linear/cleanup-stmt-expr-asm.c |  48 +++
 13 files changed, 1129 insertions(+), 23 deletions(-)
 create mode 100644 validation/cleanup-context.c
 create mode 100644 validation/cleanup-errors.c
 create mode 100644 validation/cleanup-inline-loop.c
 create mode 100644 validation/linear/cleanup-control-flow.c
 create mode 100644 validation/linear/cleanup-stmt-expr-asm.c

diff --git a/evaluate.c b/evaluate.c
index 27bfe1c0fe88..6e1be32f929f 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -4236,6 +4236,41 @@ static bool has_invalid_constexpr_qualifiers(struct symbol *type)
 	return false;
 }
 
+static void evaluate_cleanup(struct symbol *sym)
+{
+	struct expression *var_expr, *addr_expr, *fn_expr, *call;
+
+	if (!sym->cleanup || sym->cleanup->type != EXPR_SYMBOL)
+		return;
+
+	if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_STATIC | MOD_EXTERN)) {
+		sparse_error(sym->pos, "cleanup attribute may only be applied to local variables");
+		sym->cleanup = NULL;
+		return;
+	}
+
+	var_expr = alloc_expression(sym->pos, EXPR_SYMBOL);
+	var_expr->symbol = sym;
+	var_expr->symbol_name = sym->ident;
+	evaluate_expression(var_expr);
+
+	addr_expr = alloc_expression(sym->pos, EXPR_PREOP);
+	addr_expr->op = '&';
+	addr_expr->unop = var_expr;
+	evaluate_expression(addr_expr);
+
+	fn_expr = alloc_expression(sym->cleanup->pos, EXPR_SYMBOL);
+	fn_expr->symbol = sym->cleanup->symbol;
+	fn_expr->symbol_name = sym->cleanup->symbol_name;
+
+	call = alloc_expression(sym->pos, EXPR_CALL);
+	call->fn = fn_expr;
+	add_expression(&call->args, addr_expr);
+	evaluate_call(call);
+
+	sym->cleanup = call;
+}
+
 static struct symbol *evaluate_symbol(struct symbol *sym)
 {
 	struct symbol *base_type;
@@ -4276,6 +4311,9 @@ static struct symbol *evaluate_symbol(struct symbol *sym)
 			sym->underspecified = 0;
 	}
 
+	if (sym->cleanup)
+		evaluate_cleanup(sym);
+
 	/* And finally, evaluate the body of the symbol too */
 	if (base_type->type == SYM_FN) {
 		struct symbol *curr = current_fn;
diff --git a/expand.c b/expand.c
index 696662ad11ea..5c3777ff3a23 100644
--- a/expand.c
+++ b/expand.c
@@ -1736,6 +1736,7 @@ int expand_symbol(struct symbol *sym)
 		return 0;
 
 	retval = expand_expression(sym->initializer);
+	expand_expression(sym->cleanup);
 	/* expand the body of the symbol */
 	if (base_type->type == SYM_FN) {
 		if (base_type->stmt)
@@ -1856,13 +1857,18 @@ static int expand_statement(struct statement *stmt)
 	case STMT_IF:
 		return expand_if_statement(stmt);
 
-	case STMT_ITERATOR:
+	case STMT_ITERATOR: {
+		struct symbol *sym;
+		FOR_EACH_PTR(stmt->iterator_syms, sym) {
+			expand_symbol(sym);
+		} END_FOR_EACH_PTR(sym);
 		expand_expression(stmt->iterator_pre_condition);
 		expand_expression(stmt->iterator_post_condition);
 		expand_statement(stmt->iterator_pre_statement);
 		expand_statement(stmt->iterator_statement);
 		expand_statement(stmt->iterator_post_statement);
 		return SIDE_EFFECTS;
+	}
 
 	case STMT_SWITCH:
 		expand_expression(stmt->switch_expression);
diff --git a/inline.c b/inline.c
index c857cd953702..4fa087098160 100644
--- a/inline.c
+++ b/inline.c
@@ -35,7 +35,9 @@
 #include "evaluate.h"
 
 static void copy_statement(struct statement *src, struct statement *dst);
+static struct statement *copy_one_statement(struct statement *stmt);
 static struct expression *copy_expression(struct expression *expr);
+static bool copy_allow_unreplaced;
 
 static struct expression * dup_expression(struct expression *expr)
 {
@@ -48,6 +50,7 @@ static struct statement * dup_statement(struct statement *stmt)
 {
 	struct statement *dup = alloc_statement(stmt->pos, stmt->type);
 	*dup = *stmt;
+	dup->cleanup_scope = NULL;
 	return dup;
 }
 
@@ -58,7 +61,8 @@ static struct symbol *copy_symbol(struct position pos, struct symbol *sym)
 	if (sym->ctype.modifiers & (MOD_STATIC | MOD_EXTERN | MOD_TOPLEVEL | MOD_INLINE))
 		return sym;
 	if (!sym->replace) {
-		warning(pos, "unreplaced symbol '%s'", show_ident(sym->ident));
+		if (!copy_allow_unreplaced)
+			warning(pos, "unreplaced symbol '%s'", show_ident(sym->ident));
 		return sym;
 	}
 	return sym->replace;
@@ -77,7 +81,7 @@ static struct symbol *copy_type(struct symbol *type)
 		n = alloc_symbol(type->pos, SYM_NODE);
 		*n = *type;
 		n->ctype.base_type = base;
-		if (base && base->bit_size < 0)
+		if (!copy_allow_unreplaced && base && base->bit_size < 0)
 			n->examined = 0;
 		return n;
 	}
@@ -91,7 +95,8 @@ static struct symbol *copy_type(struct symbol *type)
 		*arr = *type;
 		arr->ctype.base_type = elem;
 		arr->array_size = size;
-		if (type->array_size || elem != type->ctype.base_type)
+		if (!copy_allow_unreplaced &&
+		    (type->array_size || elem != type->ctype.base_type))
 			arr->examined = 0;
 		return arr;
 	}
@@ -114,7 +119,8 @@ static struct symbol *copy_type(struct symbol *type)
 		t = alloc_symbol(type->pos, type->type);
 		*t = *type;
 		t->initializer = init;
-		t->examined = 0;
+		if (!copy_allow_unreplaced)
+			t->examined = 0;
 		return t;
 	}
 	default:
@@ -265,8 +271,20 @@ static struct expression * copy_expression(struct expression *expr)
 
 	/* Statement expression */
 	case EXPR_STATEMENT: {
-		struct statement *stmt = alloc_statement(expr->pos, STMT_COMPOUND);
-		copy_statement(expr->statement, stmt);
+		struct statement *stmt;
+
+		/*
+		 * expand_compound() collapses a compound statement that
+		 * contains a single statement into that statement. Hence,
+		 * after expansion, the statement of a statement expression
+		 * is not necessarily a compound statement.
+		 */
+		if (expr->statement->type == STMT_COMPOUND) {
+			stmt = alloc_statement(expr->pos, STMT_COMPOUND);
+			copy_statement(expr->statement, stmt);
+		} else {
+			stmt = copy_one_statement(expr->statement);
+		}
 		expr = dup_expression(expr);
 		expr->statement = stmt;
 		break;
@@ -382,7 +400,11 @@ static struct asm_operand_list *copy_asm_operands(struct asm_operand_list *in)
 
 	FOR_EACH_PTR(in, old) {
 		struct asm_operand *new = __alloc_asm_operand(0);
-		new->name = old->name;
+		/*
+		 * Copy the constraint flags too since cleanup expressions
+		 * are copied after evaluation and are not re-evaluated.
+		 */
+		*new = *old;
 		new->constraint = copy_expression(old->constraint);
 		new->expr = copy_expression(old->expr);
 		add_ptr_list(&out, new);
@@ -431,6 +453,7 @@ static struct statement *copy_one_statement(struct statement *stmt)
 			if (newsym != sym) {
 				newsym->ctype.base_type = copy_type(newsym->ctype.base_type);
 				newsym->initializer = copy_expression(sym->initializer);
+				newsym->cleanup = copy_expression(sym->cleanup);
 			}
 			add_symbol(&newstmt->declaration, newsym);
 		} END_FOR_EACH_PTR(sym);
@@ -522,6 +545,7 @@ static struct statement *copy_one_statement(struct statement *stmt)
 			if (newsym != sym) {
 				newsym->ctype.base_type = copy_type(newsym->ctype.base_type);
 				newsym->initializer = copy_expression(sym->initializer);
+				newsym->cleanup = copy_expression(sym->cleanup);
 			}
 			add_symbol(&stmt->iterator_syms, newsym);
 		} END_FOR_EACH_PTR(sym);
@@ -591,6 +615,7 @@ static struct symbol *create_copy_symbol(struct symbol *orig)
 		*sym = *orig;
 		sym->bb_target = NULL;
 		sym->pseudo = NULL;
+		sym->cleanup_scope = NULL;
 		set_replace(orig, sym);
 		orig = sym;
 	}
@@ -609,6 +634,184 @@ static struct symbol_list *create_symbol_list(struct symbol_list *src)
 	return dst;
 }
 
+static void collect_stmt_local_symbols(struct statement *stmt, struct symbol_list **list);
+
+static void register_local_sym(struct symbol *sym, struct symbol_list **list)
+{
+	if (!sym || sym->replace)
+		return;
+	if (sym->ctype.modifiers & (MOD_STATIC | MOD_EXTERN | MOD_TOPLEVEL | MOD_INLINE))
+		return;
+	add_symbol(list, create_copy_symbol(sym));
+}
+
+static void collect_expr_local_symbols(struct expression *expr, struct symbol_list **list)
+{
+	if (!expr)
+		return;
+	switch (expr->type) {
+	case EXPR_STATEMENT:
+		collect_stmt_local_symbols(expr->statement, list);
+		break;
+	case EXPR_PREOP:
+	case EXPR_POSTOP:
+		collect_expr_local_symbols(expr->unop, list);
+		break;
+	case EXPR_SLICE:
+		collect_expr_local_symbols(expr->base, list);
+		break;
+	case EXPR_BINOP:
+	case EXPR_COMMA:
+	case EXPR_COMPARE:
+	case EXPR_LOGICAL:
+	case EXPR_ASSIGNMENT:
+		collect_expr_local_symbols(expr->left, list);
+		collect_expr_local_symbols(expr->right, list);
+		break;
+	case EXPR_DEREF:
+		collect_expr_local_symbols(expr->deref, list);
+		break;
+	case EXPR_CAST:
+	case EXPR_FORCE_CAST:
+	case EXPR_IMPLIED_CAST:
+	case EXPR_SIZEOF:
+	case EXPR_PTRSIZEOF:
+	case EXPR_ALIGNOF:
+		collect_expr_local_symbols(expr->cast_expression, list);
+		break;
+	case EXPR_SELECT:
+	case EXPR_CONDITIONAL:
+		collect_expr_local_symbols(expr->conditional, list);
+		collect_expr_local_symbols(expr->cond_true, list);
+		collect_expr_local_symbols(expr->cond_false, list);
+		break;
+	case EXPR_CALL: {
+		struct expression *arg;
+		collect_expr_local_symbols(expr->fn, list);
+		FOR_EACH_PTR(expr->args, arg) {
+			collect_expr_local_symbols(arg, list);
+		} END_FOR_EACH_PTR(arg);
+		break;
+	}
+	case EXPR_INITIALIZER: {
+		struct expression *entry;
+		FOR_EACH_PTR(expr->expr_list, entry) {
+			collect_expr_local_symbols(entry, list);
+		} END_FOR_EACH_PTR(entry);
+		break;
+	}
+	case EXPR_INDEX:
+		collect_expr_local_symbols(expr->idx_expression, list);
+		break;
+	case EXPR_IDENTIFIER:
+		collect_expr_local_symbols(expr->ident_expression, list);
+		break;
+	case EXPR_POS:
+		collect_expr_local_symbols(expr->init_expr, list);
+		break;
+	default:
+		break;
+	}
+}
+
+static void collect_stmt_local_symbols(struct statement *stmt, struct symbol_list **list)
+{
+	struct symbol *sym;
+
+	if (!stmt)
+		return;
+	switch (stmt->type) {
+	case STMT_COMPOUND: {
+		struct statement *s;
+		register_local_sym(stmt->ret, list);
+		collect_stmt_local_symbols(stmt->args, list);
+		FOR_EACH_PTR(stmt->stmts, s) {
+			collect_stmt_local_symbols(s, list);
+		} END_FOR_EACH_PTR(s);
+		break;
+	}
+	case STMT_DECLARATION:
+		FOR_EACH_PTR(stmt->declaration, sym) {
+			register_local_sym(sym, list);
+			collect_expr_local_symbols(sym->initializer, list);
+			collect_expr_local_symbols(sym->cleanup, list);
+		} END_FOR_EACH_PTR(sym);
+		break;
+	case STMT_ITERATOR:
+		register_local_sym(stmt->iterator_break, list);
+		register_local_sym(stmt->iterator_continue, list);
+		FOR_EACH_PTR(stmt->iterator_syms, sym) {
+			register_local_sym(sym, list);
+			collect_expr_local_symbols(sym->initializer, list);
+			collect_expr_local_symbols(sym->cleanup, list);
+		} END_FOR_EACH_PTR(sym);
+		collect_stmt_local_symbols(stmt->iterator_pre_statement, list);
+		collect_expr_local_symbols(stmt->iterator_pre_condition, list);
+		collect_stmt_local_symbols(stmt->iterator_statement, list);
+		collect_stmt_local_symbols(stmt->iterator_post_statement, list);
+		collect_expr_local_symbols(stmt->iterator_post_condition, list);
+		break;
+	case STMT_SWITCH:
+		register_local_sym(stmt->switch_break, list);
+		register_local_sym(stmt->switch_case, list);
+		collect_expr_local_symbols(stmt->switch_expression, list);
+		collect_stmt_local_symbols(stmt->switch_statement, list);
+		break;
+	case STMT_CASE:
+		register_local_sym(stmt->case_label, list);
+		collect_stmt_local_symbols(stmt->case_statement, list);
+		break;
+	case STMT_LABEL:
+		register_local_sym(stmt->label_identifier, list);
+		collect_stmt_local_symbols(stmt->label_statement, list);
+		break;
+	case STMT_IF:
+		collect_expr_local_symbols(stmt->if_conditional, list);
+		collect_stmt_local_symbols(stmt->if_true, list);
+		collect_stmt_local_symbols(stmt->if_false, list);
+		break;
+	case STMT_EXPRESSION:
+	case STMT_CONTEXT:
+		collect_expr_local_symbols(stmt->expression, list);
+		break;
+	case STMT_RETURN:
+		collect_expr_local_symbols(stmt->ret_value, list);
+		break;
+	case STMT_GOTO:
+		collect_expr_local_symbols(stmt->goto_expression, list);
+		break;
+	case STMT_ASM: {
+		struct asm_operand *op;
+		FOR_EACH_PTR(stmt->asm_inputs, op) {
+			collect_expr_local_symbols(op->expr, list);
+		} END_FOR_EACH_PTR(op);
+		FOR_EACH_PTR(stmt->asm_outputs, op) {
+			collect_expr_local_symbols(op->expr, list);
+		} END_FOR_EACH_PTR(op);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+struct expression *copy_cleanup_expression(struct expression *expr)
+{
+	struct symbol_list *syms = NULL;
+	struct expression *dup;
+
+	if (!expr || expr->type != EXPR_STATEMENT)
+		return expr;
+
+	collect_expr_local_symbols(expr, &syms);
+	copy_allow_unreplaced = true;
+	dup = copy_expression(expr);
+	copy_allow_unreplaced = false;
+	unset_replace_list(syms);
+	free_ptr_list(&syms);
+	return dup;
+}
+
 int inline_function(struct expression *expr, struct symbol *sym)
 {
 	struct symbol_list * fn_symbol_list;
@@ -641,6 +844,7 @@ int inline_function(struct expression *expr, struct symbol *sym)
 			*a = *name;
 			a->bb_target = NULL;
 			a->pseudo = NULL;
+			a->cleanup_scope = NULL;
 			set_replace(name, a);
 			add_symbol(&fn_symbol_list, a);
 			a->initializer = arg;
diff --git a/linearize.c b/linearize.c
index f43502577649..9d36415551d6 100644
--- a/linearize.c
+++ b/linearize.c
@@ -2039,16 +2039,383 @@ static pseudo_t linearize_one_symbol(struct entrypoint *ep, struct symbol *sym)
 	return value;
 }
 
+struct cleanup_scope {
+	struct cleanup_scope *parent;
+	struct statement *stmt;
+	struct symbol_list *cleanups;
+	int depth;
+};
+
+ALLOCATOR(cleanup_scope, "cleanup scope");
+
+static inline struct cleanup_scope *alloc_cleanup_scope(struct cleanup_scope *parent, struct statement *stmt)
+{
+	struct cleanup_scope *scope = __alloc_cleanup_scope(0);
+	scope->parent = parent;
+	scope->stmt = stmt;
+	scope->cleanups = NULL;
+	scope->depth = parent ? parent->depth + 1 : 0;
+	return scope;
+}
+
+static struct cleanup_scope *cleanup_scope_lca(struct cleanup_scope *s1, struct cleanup_scope *s2)
+{
+	if (!s1 || !s2)
+		return NULL;
+	while (s1->depth > s2->depth)
+		s1 = s1->parent;
+	while (s2->depth > s1->depth)
+		s2 = s2->parent;
+	while (s1 && s2 && s1 != s2) {
+		s1 = s1->parent;
+		s2 = s2->parent;
+	}
+	return s1;
+}
+
+static struct symbol *first_cleanup_decl(struct symbol_list *list)
+{
+	struct symbol *sym;
+
+	FOR_EACH_PTR(list, sym) {
+		if (sym->cleanup)
+			return sym;
+	} END_FOR_EACH_PTR(sym);
+	return NULL;
+}
+
+static bool has_cleanup_decl(struct symbol_list *list)
+{
+	return first_cleanup_decl(list) != NULL;
+}
+
+static struct symbol *bypasses_cleanup_decl(struct cleanup_scope *from, struct cleanup_scope *to)
+{
+	struct cleanup_scope *lca = cleanup_scope_lca(from, to);
+	struct cleanup_scope *s;
+
+	for (s = to; s && s != lca; s = s->parent) {
+		if (!s->stmt)
+			continue;
+		if (s->stmt->type == STMT_DECLARATION) {
+			struct symbol *sym = first_cleanup_decl(s->stmt->declaration);
+			if (sym)
+				return sym;
+		} else if (s->stmt->type == STMT_ITERATOR) {
+			struct symbol *sym = first_cleanup_decl(s->stmt->iterator_syms);
+			if (sym)
+				return sym;
+		}
+	}
+	return NULL;
+}
+
+static int count_cleanups_to(struct cleanup_scope *from, struct cleanup_scope *to)
+{
+	struct cleanup_scope *s;
+	int count = 0;
+
+	for (s = from; s && s != to; s = s->parent)
+		count += symbol_list_size(s->cleanups);
+	return count;
+}
+
+static void prepare_expr_cleanup_scopes(struct expression *expr, struct cleanup_scope *parent);
+
+static void prepare_cleanup_scopes(struct statement *stmt, struct cleanup_scope *parent)
+{
+	if (!stmt)
+		return;
+
+	switch (stmt->type) {
+	case STMT_COMPOUND: {
+		struct cleanup_scope *scope = stmt->cleanup_scope;
+		struct cleanup_scope *cur;
+		struct statement *s;
+		if (!scope) {
+			scope = alloc_cleanup_scope(parent, stmt);
+			stmt->cleanup_scope = scope;
+		} else {
+			scope->parent = parent;
+			scope->depth = parent ? parent->depth + 1 : 0;
+			scope->cleanups = NULL;
+		}
+		if (stmt->ret) {
+			stmt->ret->cleanup_scope = parent;
+			stmt->ret->bb_target = NULL;
+		}
+		if (stmt->args)
+			prepare_cleanup_scopes(stmt->args, scope);
+		cur = scope;
+		FOR_EACH_PTR(stmt->stmts, s) {
+			struct statement *inner = s;
+			prepare_cleanup_scopes(s, cur);
+			while (inner && (inner->type == STMT_LABEL || inner->type == STMT_CASE))
+				inner = (inner->type == STMT_LABEL) ? inner->label_statement : inner->case_statement;
+			if (inner && inner->type == STMT_DECLARATION && inner->cleanup_scope)
+				cur = inner->cleanup_scope;
+		} END_FOR_EACH_PTR(s);
+		break;
+	}
+	case STMT_DECLARATION: {
+		struct cleanup_scope *scope = NULL;
+		struct symbol *sym;
+
+		if (has_cleanup_decl(stmt->declaration)) {
+			scope = stmt->cleanup_scope;
+			if (!scope) {
+				scope = alloc_cleanup_scope(parent, stmt);
+				stmt->cleanup_scope = scope;
+			} else {
+				scope->parent = parent;
+				scope->depth = parent ? parent->depth + 1 : 0;
+				scope->cleanups = NULL;
+			}
+		}
+		FOR_EACH_PTR(stmt->declaration, sym) {
+			sym->initialized = 0;
+			prepare_expr_cleanup_scopes(sym->initializer,
+						    scope ? scope : parent);
+		} END_FOR_EACH_PTR(sym);
+		break;
+	}
+	case STMT_SWITCH: {
+		struct cleanup_scope *scope = stmt->cleanup_scope;
+		if (!scope) {
+			scope = alloc_cleanup_scope(parent, stmt);
+			stmt->cleanup_scope = scope;
+		} else {
+			scope->parent = parent;
+			scope->depth = parent ? parent->depth + 1 : 0;
+			scope->cleanups = NULL;
+		}
+		if (stmt->switch_break) {
+			stmt->switch_break->cleanup_scope = parent;
+			stmt->switch_break->bb_target = NULL;
+		}
+		if (stmt->switch_case) {
+			struct symbol *sym;
+			FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
+				sym->bb_target = NULL;
+			} END_FOR_EACH_PTR(sym);
+		}
+		prepare_expr_cleanup_scopes(stmt->switch_expression, parent);
+		prepare_cleanup_scopes(stmt->switch_statement, scope);
+		break;
+	}
+	case STMT_ITERATOR: {
+		struct cleanup_scope *scope = stmt->cleanup_scope;
+		struct symbol *sym;
+		if (!scope) {
+			scope = alloc_cleanup_scope(parent, stmt);
+			stmt->cleanup_scope = scope;
+		} else {
+			scope->parent = parent;
+			scope->depth = parent ? parent->depth + 1 : 0;
+			scope->cleanups = NULL;
+		}
+		if (stmt->iterator_break) {
+			stmt->iterator_break->cleanup_scope = scope;
+			stmt->iterator_break->bb_target = NULL;
+		}
+		if (stmt->iterator_continue) {
+			stmt->iterator_continue->cleanup_scope = scope;
+			stmt->iterator_continue->bb_target = NULL;
+		}
+		FOR_EACH_PTR(stmt->iterator_syms, sym) {
+			sym->initialized = 0;
+			prepare_expr_cleanup_scopes(sym->initializer, scope);
+		} END_FOR_EACH_PTR(sym);
+		prepare_cleanup_scopes(stmt->iterator_pre_statement, scope);
+		prepare_expr_cleanup_scopes(stmt->iterator_pre_condition, scope);
+		prepare_cleanup_scopes(stmt->iterator_statement, scope);
+		prepare_cleanup_scopes(stmt->iterator_post_statement, scope);
+		prepare_expr_cleanup_scopes(stmt->iterator_post_condition, scope);
+		break;
+	}
+	case STMT_LABEL: {
+		struct symbol *label = stmt->label_identifier;
+		if (label) {
+			label->cleanup_scope = parent;
+			label->bb_target = NULL;
+		}
+		prepare_cleanup_scopes(stmt->label_statement, parent);
+		break;
+	}
+	case STMT_CASE:
+		if (stmt->case_label) {
+			stmt->case_label->cleanup_scope = parent;
+			stmt->case_label->bb_target = NULL;
+		}
+		prepare_expr_cleanup_scopes(stmt->case_expression, parent);
+		prepare_expr_cleanup_scopes(stmt->case_to, parent);
+		prepare_cleanup_scopes(stmt->case_statement, parent);
+		break;
+	case STMT_IF:
+		prepare_expr_cleanup_scopes(stmt->if_conditional, parent);
+		prepare_cleanup_scopes(stmt->if_true, parent);
+		prepare_cleanup_scopes(stmt->if_false, parent);
+		break;
+	case STMT_EXPRESSION:
+		prepare_expr_cleanup_scopes(stmt->expression, parent);
+		break;
+	case STMT_RETURN:
+		prepare_expr_cleanup_scopes(stmt->ret_value, parent);
+		break;
+	case STMT_CONTEXT:
+		prepare_expr_cleanup_scopes(stmt->expression, parent);
+		prepare_expr_cleanup_scopes(stmt->context, parent);
+		break;
+	case STMT_RANGE:
+		prepare_expr_cleanup_scopes(stmt->range_expression, parent);
+		prepare_expr_cleanup_scopes(stmt->range_low, parent);
+		prepare_expr_cleanup_scopes(stmt->range_high, parent);
+		break;
+	case STMT_GOTO:
+		prepare_expr_cleanup_scopes(stmt->goto_expression, parent);
+		break;
+	case STMT_ASM: {
+		struct asm_operand *op;
+		FOR_EACH_PTR(stmt->asm_inputs, op) {
+			prepare_expr_cleanup_scopes(op->expr, parent);
+		} END_FOR_EACH_PTR(op);
+		FOR_EACH_PTR(stmt->asm_outputs, op) {
+			prepare_expr_cleanup_scopes(op->expr, parent);
+		} END_FOR_EACH_PTR(op);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+static void prepare_expr_cleanup_scopes(struct expression *expr, struct cleanup_scope *parent)
+{
+	if (!expr)
+		return;
+	switch (expr->type) {
+	case EXPR_STATEMENT:
+		prepare_cleanup_scopes(expr->statement, parent);
+		break;
+	case EXPR_BINOP:
+	case EXPR_COMMA:
+	case EXPR_COMPARE:
+	case EXPR_LOGICAL:
+	case EXPR_ASSIGNMENT:
+		prepare_expr_cleanup_scopes(expr->left, parent);
+		prepare_expr_cleanup_scopes(expr->right, parent);
+		break;
+	case EXPR_PREOP:
+	case EXPR_POSTOP:
+		prepare_expr_cleanup_scopes(expr->unop, parent);
+		break;
+	case EXPR_CONDITIONAL:
+	case EXPR_SELECT:
+		prepare_expr_cleanup_scopes(expr->conditional, parent);
+		prepare_expr_cleanup_scopes(expr->cond_true, parent);
+		prepare_expr_cleanup_scopes(expr->cond_false, parent);
+		break;
+	case EXPR_CALL: {
+		struct expression *arg;
+		prepare_expr_cleanup_scopes(expr->fn, parent);
+		FOR_EACH_PTR(expr->args, arg) {
+			prepare_expr_cleanup_scopes(arg, parent);
+		} END_FOR_EACH_PTR(arg);
+		break;
+	}
+	case EXPR_CAST:
+	case EXPR_FORCE_CAST:
+	case EXPR_IMPLIED_CAST:
+	case EXPR_SIZEOF:
+	case EXPR_PTRSIZEOF:
+	case EXPR_ALIGNOF:
+		prepare_expr_cleanup_scopes(expr->cast_expression, parent);
+		break;
+	case EXPR_SLICE:
+		prepare_expr_cleanup_scopes(expr->base, parent);
+		break;
+	case EXPR_DEREF:
+		prepare_expr_cleanup_scopes(expr->deref, parent);
+		break;
+	case EXPR_INITIALIZER: {
+		struct expression *entry;
+		FOR_EACH_PTR(expr->expr_list, entry) {
+			prepare_expr_cleanup_scopes(entry, parent);
+		} END_FOR_EACH_PTR(entry);
+		break;
+	}
+	case EXPR_POS:
+		prepare_expr_cleanup_scopes(expr->init_expr, parent);
+		break;
+	case EXPR_INDEX:
+		prepare_expr_cleanup_scopes(expr->idx_expression, parent);
+		break;
+	case EXPR_IDENTIFIER:
+		prepare_expr_cleanup_scopes(expr->ident_expression, parent);
+		break;
+	case EXPR_GENERIC: {
+		struct type_expression *map;
+		prepare_expr_cleanup_scopes(expr->control, parent);
+		prepare_expr_cleanup_scopes(expr->def, parent);
+		for (map = expr->map; map; map = map->next)
+			prepare_expr_cleanup_scopes(map->expr, parent);
+		break;
+	}
+	case EXPR_OFFSETOF:
+		prepare_expr_cleanup_scopes(expr->down, parent);
+		if (expr->op != '.')
+			prepare_expr_cleanup_scopes(expr->index, parent);
+		break;
+	case EXPR_HAS_ATTRIBUTE:
+		prepare_expr_cleanup_scopes(expr->has_attr_expr, parent);
+		break;
+	default:
+		break;
+	}
+}
+
+static void emit_cleanups_to(struct entrypoint *ep, struct cleanup_scope *target_scope)
+{
+	struct cleanup_scope *saved_scope = ep->cleanup_scope;
+	struct cleanup_scope *scope;
+
+	if (!ep->active || !bb_reachable(ep->active))
+		return;
+
+	for (scope = saved_scope; scope && scope != target_scope; scope = scope->parent) {
+		struct symbol *sym;
+		ep->cleanup_scope = scope;
+		FOR_EACH_PTR_REVERSE(scope->cleanups, sym) {
+			if (sym->cleanup && sym->cleanup->ctype) {
+				struct expression *cleanup = copy_cleanup_expression(sym->cleanup);
+				prepare_expr_cleanup_scopes(cleanup, scope);
+				linearize_expression(ep, cleanup);
+			}
+		} END_FOR_EACH_PTR_REVERSE(sym);
+	}
+	ep->cleanup_scope = saved_scope;
+}
+
 static pseudo_t linearize_compound_statement(struct entrypoint *ep, struct statement *stmt)
 {
+	struct cleanup_scope *scope = stmt->cleanup_scope;
 	pseudo_t pseudo;
 	struct statement *s;
 
+	if (!scope) {
+		prepare_cleanup_scopes(stmt, ep->cleanup_scope);
+		scope = stmt->cleanup_scope;
+	}
+
+	ep->cleanup_scope = scope;
 	pseudo = VOID;
 	FOR_EACH_PTR(stmt->stmts, s) {
 		pseudo = linearize_statement(ep, s);
 	} END_FOR_EACH_PTR(s);
 
+	emit_cleanups_to(ep, scope->parent);
+	ep->cleanup_scope = scope->parent;
+
 	return pseudo;
 }
 
@@ -2266,8 +2633,14 @@ static pseudo_t linearize_declaration(struct entrypoint *ep, struct statement *s
 
 	concat_symbol_list(stmt->declaration, &ep->syms);
 
+	if (stmt->cleanup_scope) {
+		stmt->cleanup_scope->cleanups = NULL;
+		ep->cleanup_scope = stmt->cleanup_scope;
+	}
 	FOR_EACH_PTR(stmt->declaration, sym) {
 		linearize_one_symbol(ep, sym);
+		if (sym->cleanup && ep->cleanup_scope)
+			add_symbol(&ep->cleanup_scope->cleanups, sym);
 	} END_FOR_EACH_PTR(sym);
 	return VOID;
 }
@@ -2277,10 +2650,10 @@ static pseudo_t linearize_return(struct entrypoint *ep, struct statement *stmt)
 	struct expression *expr = stmt->expression;
 	struct symbol *ret = stmt->ret_target;
 	struct basic_block *bb_return = get_bound_block(ep, ret);
-	struct basic_block *active;
 	pseudo_t src = linearize_expression(ep, expr);
-	active = ep->active;
-	if (active && !is_void_type(ret)) {
+
+	emit_cleanups_to(ep, ret ? ret->cleanup_scope : NULL);
+	if (ep->active && !is_void_type(ret)) {
 		add_return(ep, bb_return, ret, src);
 	}
 	add_goto(ep, bb_return);
@@ -2311,10 +2684,27 @@ static pseudo_t linearize_switch(struct entrypoint *ep, struct statement *stmt)
 	add_one_insn(ep, switch_ins);
 	finish_block(ep);
 
+	struct cleanup_scope *switch_scope = stmt->cleanup_scope;
+	if (!switch_scope) {
+		prepare_cleanup_scopes(stmt, ep->cleanup_scope);
+		switch_scope = stmt->cleanup_scope;
+	}
+	ep->cleanup_scope = switch_scope;
+	stmt->switch_break->cleanup_scope = switch_scope->parent;
+
 	default_case = NULL;
 	FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
 		struct statement *case_stmt = sym->stmt;
-		struct basic_block *bb_case = get_bound_block(ep, sym);
+		struct symbol *bypass = bypasses_cleanup_decl(switch_scope, sym->cleanup_scope);
+		struct basic_block *bb_case;
+
+		if (bypass) {
+			sparse_error(case_stmt->pos,
+				     "jump bypasses initialization of variable '%s' with cleanup attribute",
+				     show_ident(bypass->ident));
+			continue;
+		}
+		bb_case = get_bound_block(ep, sym);
 
 		if (!case_stmt->case_expression) {
 			default_case = bb_case;
@@ -2349,6 +2739,7 @@ static pseudo_t linearize_switch(struct entrypoint *ep, struct statement *stmt)
 
 	/* And linearize the actual statement */
 	linearize_statement(ep, stmt->switch_statement);
+	emit_cleanups_to(ep, switch_scope->parent);
 	set_activeblock(ep, switch_end);
 
 	if (!default_case)
@@ -2360,6 +2751,7 @@ static pseudo_t linearize_switch(struct entrypoint *ep, struct statement *stmt)
 	add_bb(&active->children, default_case);
 	sort_switch_cases(switch_ins);
 
+	ep->cleanup_scope = switch_scope->parent;
 	return VOID;
 }
 
@@ -2372,9 +2764,18 @@ static pseudo_t linearize_iterator(struct entrypoint *ep, struct statement *stmt
 	struct expression *post_condition = stmt->iterator_post_condition;
 	struct basic_block *loop_top, *loop_body, *loop_continue, *loop_end;
 	struct symbol *sym;
+	struct cleanup_scope *iter_scope = stmt->cleanup_scope;
+	if (!iter_scope) {
+		prepare_cleanup_scopes(stmt, ep->cleanup_scope);
+		iter_scope = stmt->cleanup_scope;
+	}
+
+	ep->cleanup_scope = iter_scope;
 
 	FOR_EACH_PTR(stmt->iterator_syms, sym) {
 		linearize_one_symbol(ep, sym);
+		if (sym->cleanup)
+			add_symbol(&iter_scope->cleanups, sym);
 	} END_FOR_EACH_PTR(sym);
 	concat_symbol_list(stmt->iterator_syms, &ep->syms);
 	linearize_statement(ep, pre_statement);
@@ -2383,6 +2784,9 @@ static pseudo_t linearize_iterator(struct entrypoint *ep, struct statement *stmt
 	loop_continue = alloc_basic_block(ep, stmt->pos);
 	loop_end = alloc_basic_block(ep, stmt->pos);
 
+	stmt->iterator_continue->cleanup_scope = iter_scope;
+	stmt->iterator_break->cleanup_scope = iter_scope;
+
 	/* An empty post-condition means that it's the same as the pre-condition */
 	if (!post_condition) {
 		loop_top = alloc_basic_block(ep, stmt->pos);
@@ -2407,6 +2811,9 @@ static pseudo_t linearize_iterator(struct entrypoint *ep, struct statement *stmt
 		linearize_cond_branch(ep, post_condition, loop_top, loop_end);
 	set_activeblock(ep, loop_end);
 
+	emit_cleanups_to(ep, iter_scope->parent);
+	ep->cleanup_scope = iter_scope->parent;
+
 	return VOID;
 }
 
@@ -2464,28 +2871,72 @@ static pseudo_t linearize_statement(struct entrypoint *ep, struct statement *stm
 		struct expression *expr;
 		struct instruction *goto_ins;
 		struct basic_block *active;
+		struct cleanup_scope *common_lca = NULL;
+		int expected_cleanups = -1;
 		pseudo_t pseudo;
 
 		active = ep->active;
 		if (!bb_reachable(active))
 			break;
 
-		if (stmt->goto_label) {
-			add_goto(ep, get_bound_block(ep, stmt->goto_label));
+		sym = stmt->goto_label;
+		expr = stmt->goto_expression;
+		if (!sym && expr && expr->type == EXPR_LABEL)
+			sym = expr->label_symbol;
+
+		if (sym) {
+			struct symbol *bypass = bypasses_cleanup_decl(ep->cleanup_scope, sym->cleanup_scope);
+			if (bypass) {
+				sparse_error(stmt->pos,
+					     "jump bypasses initialization of variable '%s' with cleanup attribute",
+					     show_ident(bypass->ident));
+				add_unreachable(ep);
+				break;
+			}
+			emit_cleanups_to(ep, cleanup_scope_lca(ep->cleanup_scope, sym->cleanup_scope));
+			add_goto(ep, get_bound_block(ep, sym));
 			break;
 		}
 
-		expr = stmt->goto_expression;
 		if (!expr)
 			break;
 
-		/* This can happen as part of simplification */
-		if (expr->type == EXPR_LABEL) {
-			add_goto(ep, get_bound_block(ep, expr->label_symbol));
+		pseudo = linearize_expression(ep, expr);
+		active = ep->active;
+		if (!bb_reachable(active))
 			break;
+
+		FOR_EACH_PTR(stmt->target_list, sym) {
+			struct symbol *bypass = bypasses_cleanup_decl(ep->cleanup_scope, sym->cleanup_scope);
+			struct cleanup_scope *lca;
+			int n;
+
+			if (bypass) {
+				sparse_error(stmt->pos,
+					     "jump bypasses initialization of variable '%s' with cleanup attribute",
+					     show_ident(bypass->ident));
+				add_unreachable(ep);
+				goto out_goto;
+			}
+			lca = cleanup_scope_lca(ep->cleanup_scope, sym->cleanup_scope);
+			n = count_cleanups_to(ep->cleanup_scope, lca);
+			if (expected_cleanups < 0) {
+				expected_cleanups = n;
+				common_lca = lca;
+			} else if (n != expected_cleanups) {
+				sparse_error(stmt->pos, "computed goto crosses cleanup scope");
+				add_unreachable(ep);
+				goto out_goto;
+			}
+		} END_FOR_EACH_PTR(sym);
+
+		if (expected_cleanups > 0) {
+			emit_cleanups_to(ep, common_lca);
+			active = ep->active;
+			if (!bb_reachable(active))
+				break;
 		}
 
-		pseudo = linearize_expression(ep, expr);
 		goto_ins = alloc_instruction(OP_COMPUTEDGOTO, 0);
 		use_pseudo(goto_ins, pseudo, &goto_ins->src);
 		add_one_insn(ep, goto_ins);
@@ -2494,11 +2945,12 @@ static pseudo_t linearize_statement(struct entrypoint *ep, struct statement *stm
 			struct basic_block *bb_computed = get_bound_block(ep, sym);
 			struct multijmp *jmp = alloc_multijmp(bb_computed, 1, 0);
 			add_multijmp(&goto_ins->multijmp_list, jmp);
-			add_bb(&bb_computed->parents, ep->active);
+			add_bb(&bb_computed->parents, active);
 			add_bb(&active->children, bb_computed);
 		} END_FOR_EACH_PTR(sym);
 
 		finish_block(ep);
+	out_goto:
 		break;
 	}
 
@@ -2613,6 +3065,8 @@ static struct entrypoint *linearize_fn(struct symbol *sym, struct symbol *base_t
 	if (!stmt || sym->bogus_linear)
 		return NULL;
 
+	prepare_cleanup_scopes(stmt, NULL);
+
 	ep = alloc_entrypoint();
 	ep->name = sym;
 	sym->ep = ep;
diff --git a/linearize.h b/linearize.h
index 3412494edbef..7b828859238e 100644
--- a/linearize.h
+++ b/linearize.h
@@ -321,6 +321,9 @@ static inline void replace_bb_in_list(struct basic_block_list **list,
 	replace_ptr_list_entry((struct ptr_list **)list, old, new, count);
 }
 
+struct cleanup_scope;
+DECLARE_ALLOCATOR(cleanup_scope);
+
 struct entrypoint {
 	struct symbol *name;
 	struct symbol_list *syms;
@@ -329,6 +332,7 @@ struct entrypoint {
 	struct basic_block *active;
 	struct instruction *entry;
 	unsigned int dom_levels;	/* max levels in the dom tree */
+	struct cleanup_scope *cleanup_scope;
 };
 
 extern void insert_select(struct basic_block *bb, struct instruction *br, struct instruction *phi, pseudo_t if_true, pseudo_t if_false);
diff --git a/parse.c b/parse.c
index 7010d412fdab..ae799370203a 100644
--- a/parse.c
+++ b/parse.c
@@ -1512,7 +1512,7 @@ static struct token *attribute_cleanup(struct token *token, struct symbol *attr,
 		else if (token_type(token) != TOKEN_IDENT)
 			sparse_error(token->pos, "argument is not an identifier");
 		token = primary_expression(token, &expr);
-		if (expr && expr->type == EXPR_SYMBOL)
+		if (expr && expr->type == EXPR_SYMBOL && match_op(token, ')'))
 			ctx->cleanup = expr;
 		return expect(token, ')', "after attribute's argument'");
 	}
@@ -2609,7 +2609,8 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 
 		decl->ctype = ctx.ctype;
 		decl->ctype.modifiers |= mod;
-		decl->cleanup = ctx.cleanup;
+		if (ctx.cleanup)
+			sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
 		decl->packed = ctx.packed;
 		decl->explicit_alignment = ctx.explicit_alignment;
 		apply_fn_ptr_modifiers(decl, ctx.f_modifiers);
@@ -2681,6 +2682,8 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	apply_fn_ptr_modifiers(sym, ctx.f_modifiers);
 	sym->endpos = token->pos;
 	sym->forced_arg = ctx.forced;
+	if (ctx.cleanup)
+		sparse_error(sym->pos, "cleanup attribute may only be applied to local variables");
 	if (ctx.has_auto || ctx.autotype) {
 		sparse_error(sym->pos,
 			     "parameter '%s' cannot be declared %s",
@@ -3845,6 +3848,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	struct symbol *decl;
 	struct decl_state ctx = { .ident = &ident };
 	struct ctype saved;
+	struct expression *saved_cleanup;
 	struct symbol *base_type;
 	unsigned long mod, saved_f_modifiers;
 	unsigned int saved_explicit_alignment, saved_td_align;
@@ -3889,6 +3893,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	saved_td_align = ctx.typedef_alignment;
 	saved_f_modifiers = ctx.f_modifiers;
 	saved = ctx.ctype;
+	saved_cleanup = ctx.cleanup;
 	token = declarator(token, &ctx);
 	token = handle_asm_name(token, &ctx);
 	token = handle_attributes(token, &ctx);
@@ -3945,6 +3950,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			}
 		}
 	} else if (base_type && base_type->type == SYM_FN) {
+		if (decl->cleanup) {
+			sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
+			decl->cleanup = NULL;
+		}
 		if (ctx.is_constexpr) {
 			sparse_error(
 				decl->pos,
@@ -4033,6 +4042,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 				add_symbol(list, decl);
 				fn_local_symbol(decl);
 			}
+		} else if (decl->cleanup) {
+			sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
+			decl->cleanup = NULL;
 		}
 		check_declaration(decl);
 		if (decl->same_symbol) {
@@ -4161,7 +4173,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		ctx.typedef_alignment = saved_td_align;
 		ctx.f_modifiers = saved_f_modifiers;
 		ctx.ctype = saved;
-		ctx.cleanup = NULL;
+		ctx.cleanup = saved_cleanup;
 		ctx.ident_modifiers = 0;
 		token = handle_attributes(token, &ctx);
 		token = declarator(token, &ctx);
@@ -4195,6 +4207,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		/* Function declarations are automatically extern unless specifically static */
 		base_type = decl->ctype.base_type;
 		if (!is_typedef && base_type && base_type->type == SYM_FN) {
+			if (decl->cleanup) {
+				sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
+				decl->cleanup = NULL;
+			}
 			decl->ctype.modifiers |= ctx.f_modifiers;
 			if (standard >= STANDARD_C23 && base_type->kr_args)
 				sparse_error(decl->pos,
diff --git a/parse.h b/parse.h
index 6a528bc8ca4d..83c39c3cecbc 100644
--- a/parse.h
+++ b/parse.h
@@ -44,9 +44,12 @@ enum statement_type {
 	STMT_RANGE,
 };
 
+struct cleanup_scope;
+
 struct statement {
 	enum statement_type type;
 	struct position pos;
+	struct cleanup_scope *cleanup_scope;
 	union {
 		struct /* declaration */ {
 			struct symbol_list *declaration;
@@ -138,6 +141,7 @@ extern struct symbol *ctype_integer(int size, int want_unsigned);
 
 extern int inline_function(struct expression *expr, struct symbol *sym);
 extern void uninline(struct symbol *sym);
+extern struct expression *copy_cleanup_expression(struct expression *expr);
 extern void init_parser(int);
 extern void init_parser_finalize(void);
 
diff --git a/symbol.h b/symbol.h
index cd9268703192..721b6c7d05a4 100644
--- a/symbol.h
+++ b/symbol.h
@@ -251,6 +251,7 @@ struct symbol {
 		};
 	};
 	pseudo_t pseudo;
+	struct cleanup_scope *cleanup_scope;
 };
 
 /* Modifiers */
diff --git a/validation/cleanup-context.c b/validation/cleanup-context.c
new file mode 100644
index 000000000000..d07936359466
--- /dev/null
+++ b/validation/cleanup-context.c
@@ -0,0 +1,161 @@
+#define __cleanup(f) __attribute__((cleanup(f)))
+#define __context_lock(x) __attribute__((context(0, 1)))
+#define __context_unlock(x) __attribute__((context(1, 0)))
+
+static void lock(int *x) __context_lock(x)
+{
+	__context__(1);
+}
+
+static void unlock(int **x) __context_unlock(x)
+{
+	__context__(-1);
+}
+
+static void test_normal_exit(int *m)
+{
+	int *guard __cleanup(unlock) = (lock(m), m);
+}
+
+static int test_return(int *m, int cond)
+{
+	int *guard __cleanup(unlock) = (lock(m), m);
+	if (cond)
+		return 1;
+	return 0;
+}
+
+static int test_nested_return(int *m1, int *m2, int cond)
+{
+	int *g1 __cleanup(unlock) = (lock(m1), m1);
+	{
+		int *g2 __cleanup(unlock) = (lock(m2), m2);
+		if (cond)
+			return -1;
+	}
+	return 0;
+}
+
+static void test_loop_break(int *m, int n)
+{
+	for (int i = 0; i < n; i++) {
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (i == 2)
+			break;
+	}
+}
+
+static void test_loop_continue(int *m, int n)
+{
+	for (int i = 0; i < n; i++) {
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (i == 2)
+			continue;
+	}
+}
+
+static void test_backward_goto(int *m, int cond)
+{
+again:
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (cond) {
+			cond = 0;
+			goto again;
+		}
+	}
+}
+
+static void test_forward_goto(int *m, int cond)
+{
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (cond)
+			goto out;
+	}
+out:
+	return;
+}
+
+static void test_loop_header_break(int *m, int n)
+{
+	for (int *guard __cleanup(unlock) = (lock(m), m); n > 0; n--) {
+		if (n == 2)
+			break;
+	}
+}
+
+static void test_switch_break(int *m, int val)
+{
+	switch (val) {
+	case 1: {
+		int *guard __cleanup(unlock) = (lock(m), m);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+static void test_switch_case_decl(int *m, int val)
+{
+	switch (val) {
+	default:
+		break;
+	case 1: ;
+		int *guard __cleanup(unlock) = (lock(m), m);
+		break;
+	}
+}
+
+static void test_computed_goto_const(int *m)
+{
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		goto *&&out;
+	}
+out:
+	return;
+}
+
+static void test_computed_goto_dyn(int *m, int cond)
+{
+	void *target = cond ? &&out1 : &&out2;
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		goto *target;
+	}
+out1:
+	return;
+out2:
+	return;
+}
+
+static void test_multi_declarator(int *m1, int *m2)
+{
+	int __cleanup(unlock) *g1 = (lock(m1), m1), *g2 = (lock(m2), m2);
+}
+
+static void test_stmt_expr_init_backward_goto(int *m, int cond)
+{
+again:
+	int *guard __cleanup(unlock) = ({ int *p = (lock(m), m); p; });
+	if (cond) {
+		cond = 0;
+		goto again;
+	}
+}
+
+static void test_warn_imbalance(int *m)
+{
+	lock(m);
+}
+
+/*
+ * check-name: cleanup attribute in context analysis
+ * check-command: sparse -Wcontext $file
+ *
+ * check-error-start
+cleanup-context.c:149:13: warning: context imbalance in 'test_warn_imbalance' - wrong count at exit
+ * check-error-end
+ */
diff --git a/validation/cleanup-errors.c b/validation/cleanup-errors.c
new file mode 100644
index 000000000000..0aed01fbbfde
--- /dev/null
+++ b/validation/cleanup-errors.c
@@ -0,0 +1,77 @@
+#define __cleanup(f) __attribute__((cleanup(f)))
+
+static void cl(int *p)
+{
+	(void)p;
+}
+
+static void lock(int *m) __attribute__((context(0, 1)))
+{
+	(void)m;
+	__context__(1);
+}
+
+static void unlock(int **m) __attribute__((context(1, 0)))
+{
+	(void)m;
+	__context__(-1);
+}
+
+static void bad_param(int x __cleanup(cl))
+{
+	(void)x;
+}
+
+struct bad_struct {
+	int member __cleanup(cl);
+};
+
+typedef int bad_typedef __cleanup(cl);
+
+static void bad_fn(void) __cleanup(cl);
+
+static void bad_forward_goto(int *m, int c)
+{
+	if (c)
+		goto inside;
+	int *guard __cleanup(unlock) = (lock(m), m);
+inside:
+	return;
+}
+
+static void bad_switch_bypass(int *m, int v)
+{
+	switch (v) {
+	case 1: ;
+		int *guard __cleanup(unlock) = (lock(m), m);
+		break;
+	case 2:
+		break;
+	}
+}
+
+static void bad_cgoto_into_scope(void *p)
+{
+	goto *p;
+	{
+		int x __cleanup(cl) = 1;
+	inner:
+		(void)&&inner;
+		(void)x;
+	}
+}
+
+/*
+ * check-name: cleanup attribute error diagnostics
+ * check-command: sparse -Wcontext $file
+ *
+ * check-error-start
+cleanup-errors.c:20:23: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:26:13: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:29:13: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:31:13: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:36:17: error: jump bypasses initialization of variable 'guard' with cleanup attribute
+cleanup-errors.c:48:9: error: jump bypasses initialization of variable 'guard' with cleanup attribute
+cleanup-errors.c:55:9: error: jump bypasses initialization of variable 'x' with cleanup attribute
+ * check-error-end
+ */
diff --git a/validation/cleanup-inline-loop.c b/validation/cleanup-inline-loop.c
new file mode 100644
index 000000000000..539cb39f8d98
--- /dev/null
+++ b/validation/cleanup-inline-loop.c
@@ -0,0 +1,31 @@
+#define __cleanup(f) __attribute__((cleanup(f)))
+
+static inline void cleanup_fn(int *p)
+{
+	int tmp = *p;
+	while (tmp > 0) {
+		tmp--;
+		if (tmp == 0)
+			goto done;
+	}
+done:
+	*p = tmp;
+}
+
+static int test_multiple_exits(int cond)
+{
+	int x __cleanup(cleanup_fn) = 2;
+	if (cond == 1)
+		return 1;
+	if (cond == 2)
+		return 2;
+	return 0;
+}
+
+/*
+ * check-name: inline cleanup with loop across multiple exits
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-excludes: VOID
+ */
diff --git a/validation/linear/cleanup-control-flow.c b/validation/linear/cleanup-control-flow.c
new file mode 100644
index 000000000000..269df60917e4
--- /dev/null
+++ b/validation/linear/cleanup-control-flow.c
@@ -0,0 +1,62 @@
+void cl1(int *p);
+void cl2(int *p);
+void cl3(int *p);
+
+/* 1. break and continue inside loop with cleanup variable */
+void loop_cleanup(int a, int b)
+{
+	while (a > 0) {
+		int x __attribute__((cleanup(cl1))) = a;
+		if (b == 1)
+			break;
+		if (b == 2)
+			continue;
+		a--;
+	}
+}
+
+/* 2. goto out of nested cleanup scopes */
+int goto_nested_cleanup(int c)
+{
+	int outer __attribute__((cleanup(cl1))) = 1;
+	{
+		int inner __attribute__((cleanup(cl2))) = 2;
+		if (c)
+			goto out;
+	}
+out:
+	return outer;
+}
+
+/* 3. Cleanup declaration inside unbraced switch case runs once, not twice */
+void switch_case_cleanup(int v)
+{
+	switch (v) {
+	default:
+		break;
+	case 1: ;
+		int s __attribute__((cleanup(cl3))) = v;
+		break;
+	}
+}
+
+/* 4. Multi-declarator cleanup and statement-expression initializer + backward goto */
+void multi_and_stmt_expr_cleanup(int c)
+{
+again:
+	int __attribute__((cleanup(cl3))) a = ({ int t = c; t; }), b = 2;
+	if (c) {
+		c = 0;
+		goto again;
+	}
+}
+
+/*
+ * check-name: cleanup-control-flow
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-pattern(4): call.*cl1
+ * check-output-pattern(2): call.*cl2
+ * check-output-pattern(5): call.*cl3
+ */
diff --git a/validation/linear/cleanup-stmt-expr-asm.c b/validation/linear/cleanup-stmt-expr-asm.c
new file mode 100644
index 000000000000..82c79c3fbee1
--- /dev/null
+++ b/validation/linear/cleanup-stmt-expr-asm.c
@@ -0,0 +1,48 @@
+/*
+ * Cleanup expressions are copied after expansion. expand_compound()
+ * collapses single-statement compound statements, so the statement of
+ * a statement expression is not necessarily a compound statement anymore
+ * when the cleanup expression is copied. Additionally, asm memory
+ * operands have already been converted into addresses and must keep
+ * their constraint flags when copied.
+ */
+struct s {
+	int a;
+	int counter;
+};
+
+static inline void release(struct s **p)
+{
+	struct s *v = *p;
+
+	({ asm volatile("inc %0" : "+m" (v->counter)); });
+	({ asm volatile("dec %0" : "=m" (v->a) : "m" (v->a)); });
+}
+
+int f(struct s *v)
+{
+	struct s *__attribute__((cleanup(release))) x = v;
+
+	return 0;
+}
+
+/*
+ * check-name: cleanup-stmt-expr-asm
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-start
+f:
+.L0:
+	<entry-point>
+	add.64      %r6 <- %arg1, $4
+	asm         "inc %0"
+		out: "+m" (%r6)
+	asm         "dec %0"
+		out: "=m" (%arg1)
+		in: "m" (%arg1)
+	# call      release, %r2
+	ret.32      $0
+
+
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0)
  2026-11-09 20:31 Bart Van Assche
                   ` (67 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
                   ` (3 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Do not evaluate __builtin_clz() and __builtin_ctz() at compile time when
the argument is zero. Return SIDE_EFFECTS instead so that the expression
is not constant-folded.

Avoid undefined behavior in the host compiler. Prevent crashes when
sparse is built with -fsanitize=undefined and run with
UBSAN_OPTIONS=halt_on_error=1, as passing zero to the host compiler
builtins invokes undefined behavior.

Add a regression test in validation/builtin-clz-ctz-zero.c to verify that
passing zero to __builtin_clz() and __builtin_ctz() does not cause sparse
to crash.

Fixes: 3df9233e87de ("expand more builtins like __builtin_ffs()")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 builtin.c                         | 16 +++++++++-------
 validation/builtin-clz-ctz-zero.c | 11 +++++++++++
 2 files changed, 20 insertions(+), 7 deletions(-)
 create mode 100644 validation/builtin-clz-ctz-zero.c

diff --git a/builtin.c b/builtin.c
index 34af6d82b846..93377718f1fc 100644
--- a/builtin.c
+++ b/builtin.c
@@ -246,7 +246,7 @@ static struct symbol_op bswap_op = {
 };
 
 
-#define EXPAND_FINDBIT(name)					\
+#define EXPAND_FINDBIT(name, zero_is_ub)			\
 static int expand_##name(struct expression *expr, int cost)	\
 {								\
 	struct expression *arg;					\
@@ -257,6 +257,8 @@ static int expand_##name(struct expression *expr, int cost)	\
 								\
 	arg = first_expression(expr->args);			\
 	val = get_expression_value_silent(arg);			\
+	if (zero_is_ub && !val)					\
+		return SIDE_EFFECTS;				\
 	switch (arg->ctype->bit_size) {				\
 	case sizeof(int) * 8:					\
 		val = __builtin_##name(val); break;		\
@@ -277,12 +279,12 @@ static struct symbol_op name##_op = {				\
 	.expand = expand_##name,				\
 }
 
-EXPAND_FINDBIT(clz);
-EXPAND_FINDBIT(ctz);
-EXPAND_FINDBIT(clrsb);
-EXPAND_FINDBIT(ffs);
-EXPAND_FINDBIT(parity);
-EXPAND_FINDBIT(popcount);
+EXPAND_FINDBIT(clz, 1);
+EXPAND_FINDBIT(ctz, 1);
+EXPAND_FINDBIT(clrsb, 0);
+EXPAND_FINDBIT(ffs, 0);
+EXPAND_FINDBIT(parity, 0);
+EXPAND_FINDBIT(popcount, 0);
 
 static int evaluate_fp_unop(struct expression *expr)
 {
diff --git a/validation/builtin-clz-ctz-zero.c b/validation/builtin-clz-ctz-zero.c
new file mode 100644
index 000000000000..eb49f0c175d9
--- /dev/null
+++ b/validation/builtin-clz-ctz-zero.c
@@ -0,0 +1,11 @@
+static int ctz_zero = __builtin_ctz(0);
+static int clz_zero = __builtin_clz(0);
+static int ctzll_zero = __builtin_ctzll(0);
+static int clzll_zero = __builtin_clzll(0);
+
+/*
+ * check-name: __builtin_clz and __builtin_ctz with zero
+ * check-command: sparse -Wno-decl $file
+ * check-error-start
+ * check-error-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 70/73] pre-process: Support long token and string expansions dynamically
  2026-11-09 20:31 Bart Van Assche
                   ` (68 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
                   ` (2 subsequent siblings)
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Dynamically resize token and string buffers in show_token_sequence(),
tokenize.c, and get_string_constant() in char.c, check allocation and size
bounds, increase MAX_STRING in token.h to 65535, and round oversized slab
allocations up to chunk multiples in allocate.c.

Prevent "too long token expansion" errors, 64 KB string literal truncation,
die("alloc too big") aborts, and fixed-buffer overflows when formatting,
stringifying, or concatenating deeply nested macro expansions such as those
found in modern Linux kernel headers.

Add a regression test in validation/long-string-expansion.c.

Fixes: fbc8230fa8c4 ("Larger buffer size for token concatenation")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 allocate.c                         | 27 ++++++++---
 allocate.h                         |  1 +
 char.c                             | 46 +++++++++++-------
 pre-process.c                      | 58 ++++++++++++++++------
 token.h                            |  2 +-
 tokenize.c                         | 78 +++++++++++++++++++++---------
 validation/long-string-expansion.c | 30 ++++++++++++
 7 files changed, 179 insertions(+), 63 deletions(-)
 create mode 100644 validation/long-string-expansion.c

diff --git a/allocate.c b/allocate.c
index 2aca1815e5f2..75cd8f3befef 100644
--- a/allocate.c
+++ b/allocate.c
@@ -30,6 +30,7 @@
  * efficient: allocate larger "blobs", and give out individual
  * small bits and pieces of it with no maintenance overhead.
  */
+#include <limits.h>
 #include <stdlib.h>
 #include <stddef.h>
 #include <stdio.h>
@@ -60,7 +61,7 @@ void drop_all_allocations(struct allocator_struct *desc)
 	desc->freelist = NULL;
 	while (blob) {
 		struct allocation_blob *next = blob->next;
-		blob_free(blob, desc->chunking);
+		blob_free(blob, blob->size);
 		blob = next;
 	}
 }
@@ -97,22 +98,32 @@ void *allocate(struct allocator_struct *desc, unsigned int size)
 	desc->useful_bytes += size;
 	size = (size + alignment - 1) & ~(alignment-1);
 	if (!blob || blob->left < size) {
-		unsigned int offset, chunking = desc->chunking;
+		unsigned long offset, chunking = desc->chunking;
 		struct allocation_blob *newblob;
 
 		offset = offsetof(struct allocation_blob, data);
 		offset = (offset + alignment - 1) & ~(alignment-1);
-		if (size > chunking - offset)
-			die("alloc too big");
+		if (size > chunking - offset) {
+			unsigned long need = (unsigned long)size + offset;
+			chunking = (need + chunking - 1) & ~(chunking - 1);
+			if (chunking < need || chunking - offset > UINT_MAX)
+				die("alloc too big");
+		}
 		newblob = blob_alloc(chunking);
 		if (!newblob)
 			die("out of memory");
 		desc->total_bytes += chunking;
-		newblob->next = blob;
+		newblob->size = chunking;
+		newblob->left = chunking - offset;
+		newblob->offset = offset - offsetof(struct allocation_blob, data);
+		if (blob && blob->left > newblob->left - size) {
+			newblob->next = blob->next;
+			blob->next = newblob;
+		} else {
+			newblob->next = blob;
+			desc->blobs = newblob;
+		}
 		blob = newblob;
-		desc->blobs = newblob;
-		blob->left = chunking - offset;
-		blob->offset = offset - offsetof(struct allocation_blob, data);
 	}
 	retval = blob->data + blob->offset;
 	blob->offset += size;
diff --git a/allocate.h b/allocate.h
index a6d305371c28..be99d1053608 100644
--- a/allocate.h
+++ b/allocate.h
@@ -5,6 +5,7 @@
 
 struct allocation_blob {
 	struct allocation_blob *next;
+	unsigned long size;
 	unsigned int left, offset;
 	unsigned char data[];
 };
diff --git a/char.c b/char.c
index a0776b560e29..5614a887f095 100644
--- a/char.c
+++ b/char.c
@@ -1,3 +1,6 @@
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
 #include <string.h>
 #include "target.h"
 #include "lib.h"
@@ -171,6 +174,22 @@ void get_char_constant(struct token *token, unsigned long long *val)
 	*val = v;
 }
 
+static void append_string_byte(char **buf, size_t *cap, size_t *len, char ch)
+{
+	if (*len >= INT_MAX - sizeof(struct string))
+		die("string too large");
+	if (*len + 1 > *cap) {
+		size_t new_cap = *cap ? *cap * 2 : 4096;
+		if (new_cap < *len + 1)
+			new_cap = *len + 1;
+		*buf = realloc(*buf, new_cap);
+		if (!*buf)
+			die("out of memory");
+		*cap = new_cap;
+	}
+	(*buf)[(*len)++] = ch;
+}
+
 struct token *get_string_constant(struct token *token, struct expression *expr)
 {
 	struct string *string = token->string;
@@ -178,8 +197,9 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 	int stringtype = token_type(token);
 	int is_wide = stringtype == TOKEN_WIDE_STRING;
 	int is_utf8 = stringtype == TOKEN_UTF8_STRING;
-	static char buffer[MAX_STRING];
-	int len = 0;
+	static char *buffer;
+	static size_t buf_size;
+	size_t len = 0;
 	int bits;
 	int esc_count = 0;
 
@@ -219,29 +239,21 @@ struct token *get_string_constant(struct token *token, struct expression *expr)
 			if (is_ucn && !is_wide) {
 				char utf8[4];
 				int i, n = encode_utf8(v, utf8);
-				for (i = 0; i < n; i++) {
-					if (len < MAX_STRING)
-						buffer[len] = utf8[i];
-					len++;
-				}
+				for (i = 0; i < n; i++)
+					append_string_byte(&buffer, &buf_size, &len, utf8[i]);
 			} else {
-				if (len < MAX_STRING)
-					buffer[len] = v;
-				len++;
+				append_string_byte(&buffer, &buf_size, &len, v);
 			}
 		}
 		token = token->next;
 	}
-	if (len > MAX_STRING) {
-		warning(token->pos, "trying to concatenate %d-character string (%d bytes max)", len, MAX_STRING);
-		len = MAX_STRING;
-	}
 
 	if (esc_count || len >= string->length) {
 		if (string->immutable || len >= string->length)	/* can't cannibalize */
-			string = __alloc_string(len+1);
-		string->length = len+1;
-		memcpy(string->data, buffer, len);
+			string = __alloc_string(len + 1);
+		string->length = len + 1;
+		if (len)
+			memcpy(string->data, buffer, len);
 		string->data[len] = '\0';
 	}
 	expr->string = string;
diff --git a/pre-process.c b/pre-process.c
index 8eb70ca7a1a4..1ea6c71b9a7e 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -34,6 +34,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <stdint.h>
 #include <time.h>
 #include <errno.h>
 #include <sys/stat.h>
@@ -420,38 +421,65 @@ static struct token *dup_list(struct token *list)
 
 static const char *show_token_sequence(struct token *token, int quote)
 {
-	static char buffer[MAX_STRING];
-	char *ptr = buffer;
-	int whitespace = 0;
+	static char *buffer;
+	static size_t buf_size;
+	size_t offset = 0;
+	size_t whitespace = 0;
 
 	if (!token && !quote)
 		return "<none>";
 	while (!eof_token(token)) {
 		const char *val = quote ? quote_token(token) : show_token(token);
-		int len = strlen(val);
-
-		if (ptr + whitespace + len >= buffer + sizeof(buffer)) {
-			sparse_error(token->pos, "too long token expansion");
-			break;
+		size_t len = strlen(val);
+		size_t needed;
+
+		if (len > SIZE_MAX - offset - whitespace - 1)
+			die("out of memory");
+		needed = offset + whitespace + len + 1;
+
+		if (needed > buf_size) {
+			size_t new_size = buf_size ? buf_size : 65536;
+			while (new_size < needed) {
+				if (new_size > SIZE_MAX / 2) {
+					new_size = needed;
+					break;
+				}
+				new_size *= 2;
+			}
+			buffer = realloc(buffer, new_size);
+			if (!buffer)
+				die("out of memory");
+			buf_size = new_size;
 		}
 
 		if (whitespace)
-			*ptr++ = ' ';
-		memcpy(ptr, val, len);
-		ptr += len;
+			buffer[offset++] = ' ';
+		memcpy(buffer + offset, val, len);
+		offset += len;
 		token = token->next;
 		whitespace = token->pos.whitespace;
 	}
-	*ptr = 0;
+	if (!buffer) {
+		buf_size = 64;
+		buffer = malloc(buf_size);
+		if (!buffer)
+			die("out of memory");
+	}
+	buffer[offset] = 0;
 	return buffer;
 }
 
 static struct token *stringify(struct token *arg)
 {
 	const char *s = show_token_sequence(arg, 1);
-	int size = strlen(s)+1;
-	struct token *token = __alloc_token(0);
-	struct string *string = __alloc_string(size);
+	size_t size = strlen(s) + 1;
+	struct token *token;
+	struct string *string;
+
+	if (size > INT_MAX - sizeof(struct string))
+		die("string too large");
+	token = __alloc_token(0);
+	string = __alloc_string(size);
 
 	memcpy(string->data, s, size);
 	string->length = size;
diff --git a/token.h b/token.h
index 998d83a123c9..e8276757ca5f 100644
--- a/token.h
+++ b/token.h
@@ -206,7 +206,7 @@ struct token {
 	};
 };
 
-#define MAX_STRING 8191
+#define MAX_STRING 65535
 
 static inline struct token *containing_token(struct token **p)
 {
diff --git a/tokenize.c b/tokenize.c
index 775c54667af7..95dad56a8096 100644
--- a/tokenize.c
+++ b/tokenize.c
@@ -30,6 +30,7 @@
 #include <string.h>
 #include <ctype.h>
 #include <unistd.h>
+#include <limits.h>
 #include <stdint.h>
 
 #include "lib.h"
@@ -110,6 +111,25 @@ const char *show_ident(const struct ident *ident)
 	return buffer;
 }
 
+static char *grow_buffer(char **buf, size_t *cap, size_t needed)
+{
+	if (needed > *cap) {
+		size_t new_cap = *cap ? *cap : 4096;
+		while (new_cap < needed) {
+			if (new_cap > SIZE_MAX / 2) {
+				new_cap = needed;
+				break;
+			}
+			new_cap *= 2;
+		}
+		*buf = realloc(*buf, new_cap);
+		if (!*buf)
+			die("out of memory");
+		*cap = new_cap;
+	}
+	return *buf;
+}
+
 static char *charstr(char *ptr, unsigned char c, unsigned char escape, unsigned char next)
 {
 	if (isprint(c)) {
@@ -135,13 +155,18 @@ static char *charstr(char *ptr, unsigned char c, unsigned char escape, unsigned
 
 const char *show_string(const struct string *string)
 {
-	static char buffer[4 * MAX_STRING + 3];
+	static char *buffer;
+	static size_t buf_size;
+	size_t len;
 	char *ptr;
 	int i;
 
 	if (!string || !string->length)
 		return "<bad_string>";
-	ptr = buffer;
+	len = string->length;
+	if (len > (SIZE_MAX - 3) / 4)
+		die("out of memory");
+	ptr = grow_buffer(&buffer, &buf_size, 4 * len + 3);
 	*ptr++ = '"';
 	for (i = 0; i < string->length-1; i++) {
 		const char *p = string->data + i;
@@ -155,10 +180,15 @@ const char *show_string(const struct string *string)
 static const char *show_char(const char *s, size_t len, const char *prefix,
 			     char delim)
 {
-	static char buffer[MAX_STRING + 8];
-	char *p = buffer;
-	if (prefix) {
-		size_t plen = strlen(prefix);
+	static char *buffer;
+	static size_t buf_size;
+	size_t plen = prefix ? strlen(prefix) : 0;
+	char *p;
+
+	if (len > SIZE_MAX - plen - 3)
+		die("out of memory");
+	p = grow_buffer(&buffer, &buf_size, plen + len + 3);
+	if (plen) {
 		memcpy(p, prefix, plen);
 		p += plen;
 	}
@@ -173,11 +203,16 @@ static const char *show_char(const char *s, size_t len, const char *prefix,
 static const char *quote_char(const char *s, size_t len, const char *prefix,
 			      char delim)
 {
-	static char buffer[2 * MAX_STRING + 8];
+	static char *buffer;
+	static size_t buf_size;
+	size_t plen = prefix ? strlen(prefix) : 0;
 	size_t i;
-	char *p = buffer;
-	if (prefix) {
-		size_t plen = strlen(prefix);
+	char *p;
+
+	if (len > (SIZE_MAX - plen - 6) / 2)
+		die("out of memory");
+	p = grow_buffer(&buffer, &buf_size, plen + 2 * len + 6);
+	if (plen) {
 		memcpy(p, prefix, plen);
 		p += plen;
 	}
@@ -661,18 +696,20 @@ static int get_one_number(int c, int next, stream_t *stream)
 
 static int eat_string(int next, stream_t *stream, enum token_type type)
 {
-	static char buffer[MAX_STRING];
+	static char *buffer;
+	static size_t buf_size;
 	struct string *string;
 	struct token *token = stream->token;
-	int len = 0;
+	size_t len = 0;
 	int escape;
 	int want_hex = 0;
 	char delim = type < TOKEN_STRING ? '\'' : '"';
 
 	for (escape = 0; escape || next != delim; next = nextchar(stream)) {
-		if (len < MAX_STRING)
-			buffer[len] = next;
-		len++;
+		if (len >= INT_MAX - sizeof(struct string))
+			die("string too large");
+		grow_buffer(&buffer, &buf_size, len + 4 < 64 ? 64 : len + 4);
+		buffer[len++] = next;
 		if (next == '\n') {
 			warning(stream_pos(stream),
 				"missing terminating %c character", delim);
@@ -698,20 +735,17 @@ static int eat_string(int next, stream_t *stream, enum token_type type)
 	if (want_hex)
 		warning(stream_pos(stream),
 			"\\x used with no following hex digits");
-	if (len > MAX_STRING) {
-		warning(stream_pos(stream), "string too long (%d bytes, %d bytes max)", len, MAX_STRING);
-		len = MAX_STRING;
-	}
 	if (delim == '\'' && len && len <= 4) {
 		token_type(token) = type + len;
 		memset(buffer + len, '\0', 4 - len);
 		memcpy(token->embedded, buffer, 4);
 	} else {
 		token_type(token) = type;
-		string = __alloc_string(len+1);
-		memcpy(string->data, buffer, len);
+		string = __alloc_string(len + 1);
+		if (len)
+			memcpy(string->data, buffer, len);
 		string->data[len] = '\0';
-		string->length = len+1;
+		string->length = len + 1;
 		token->string = string;
 	}
 
diff --git a/validation/long-string-expansion.c b/validation/long-string-expansion.c
new file mode 100644
index 000000000000..5523ae966c74
--- /dev/null
+++ b/validation/long-string-expansion.c
@@ -0,0 +1,30 @@
+#define S16 "0123456789abcdef"
+#define S64 S16 S16 S16 S16
+#define S256 S64 S64 S64 S64
+#define S1024 S256 S256 S256 S256
+#define S4096 S1024 S1024 S1024 S1024
+#define S16384 S4096 S4096 S4096 S4096
+#define S65536 S16384 S16384 S16384 S16384
+
+#define STR_INNER(x) #x
+#define STR(x) STR_INNER(x)
+
+#define T4 a+a+a+a
+#define T16 T4+T4+T4+T4
+#define T64 T16+T16+T16+T16
+#define T256 T64+T64+T64+T64
+#define T1024 T256+T256+T256+T256
+#define T4096 T1024+T1024+T1024+T1024
+#define T16384 T4096+T4096+T4096+T4096
+#define T65536 T16384+T16384+T16384+T16384
+
+/* Concatenated string literal > 65535 bytes */
+_Static_assert(sizeof(S65536 S16) == 65536 + 16 + 1, "concat > 64KB");
+
+/* Stringified macro expansion with escaped quotes > 65535 bytes */
+_Static_assert(sizeof(STR("q" T65536)) > 130000, "stringify > 64KB");
+
+/*
+ * check-name: long-string-expansion
+ * check-command: sparse $file
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags
  2026-11-09 20:31 Bart Van Assche
                   ` (69 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
  2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Emit #undef directives for conflicting x86 bitness macros in
handle_mbitness() in options.c at the command-line position of -m16,
-m32, -m64, or -mx32, while preserving explicit -D macro definitions
that appear later on the command line.

Prevent inconsistent word size errors and static assertion failures in
the Linux kernel when real-mode 16-bit code is compiled under an x86_64
kernel build that passes -D__x86_64__ before -m16 in CHECKFLAGS, and add
validation/arch/x86-bitness-undef.c and x86-bitness-override.c.

Fixes: efd9af566c05 ("arch: move target-specific predefines...")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 options.c                              | 58 +++++++++++++++++++++++---
 validation/arch/x86-bitness-override.c | 17 ++++++++
 validation/arch/x86-bitness-undef.c    | 16 +++++++
 3 files changed, 85 insertions(+), 6 deletions(-)
 create mode 100644 validation/arch/x86-bitness-override.c
 create mode 100644 validation/arch/x86-bitness-undef.c

diff --git a/options.c b/options.c
index 2de74afc5c35..2e6e2ce0c2ea 100644
--- a/options.c
+++ b/options.c
@@ -671,13 +671,59 @@ static char **handle_multiarch_dir(char *arg, char **next)
 	return next;
 }
 
+static int handle_mbitness(const char *arg, const char *opt, const struct flag *flag, int options)
+{
+	if (opt[0] != '\0' || (options & OPT_INVERSE))
+		return 0;
+	*flag->flag = flag->val;
+	if (arch_target->mach == MACH_I386 || arch_target->mach == MACH_X86_64) {
+		switch (flag->val) {
+		case ARCH_LP32:
+			add_pre_buffer("#undef __x86_64__\n"
+				       "#undef __x86_64\n"
+				       "#undef __amd64__\n"
+				       "#undef __amd64\n"
+				       "#ifndef __i386__\n"
+				       "#define __i386__ 1\n"
+				       "#endif\n"
+				       "#ifndef __i386\n"
+				       "#define __i386 1\n"
+				       "#endif\n"
+				       "#if !defined(__STRICT_ANSI__) && !defined(i386)\n"
+				       "#define i386 1\n"
+				       "#endif\n");
+			break;
+		case ARCH_LP64:
+		case ARCH_X32:
+		case ARCH_LLP64:
+			add_pre_buffer("#undef __i386__\n"
+				       "#undef __i386\n"
+				       "#undef i386\n"
+				       "#ifndef __x86_64__\n"
+				       "#define __x86_64__ 1\n"
+				       "#endif\n"
+				       "#ifndef __x86_64\n"
+				       "#define __x86_64 1\n"
+				       "#endif\n"
+				       "#ifndef __amd64__\n"
+				       "#define __amd64__ 1\n"
+				       "#endif\n"
+				       "#ifndef __amd64\n"
+				       "#define __amd64 1\n"
+				       "#endif\n");
+			break;
+		}
+	}
+	return 1;
+}
+
 static const struct flag mflags[] = {
-	{ "64", &arch_m64, NULL, OPT_VAL, ARCH_LP64 },
-	{ "32", &arch_m64, NULL, OPT_VAL, ARCH_LP32 },
-	{ "31", &arch_m64, NULL, OPT_VAL, ARCH_LP32 },
-	{ "16", &arch_m64, NULL, OPT_VAL, ARCH_LP32 },
-	{ "x32",&arch_m64, NULL, OPT_VAL, ARCH_X32 },
-	{ "size-llp64", &arch_m64, NULL, OPT_VAL, ARCH_LLP64 },
+	{ "64", &arch_m64, handle_mbitness, OPT_VAL, ARCH_LP64 },
+	{ "32", &arch_m64, handle_mbitness, OPT_VAL, ARCH_LP32 },
+	{ "31", &arch_m64, handle_mbitness, OPT_VAL, ARCH_LP32 },
+	{ "16", &arch_m64, handle_mbitness, OPT_VAL, ARCH_LP32 },
+	{ "x32",&arch_m64, handle_mbitness, OPT_VAL, ARCH_X32 },
+	{ "size-llp64", &arch_m64, handle_mbitness, OPT_VAL, ARCH_LLP64 },
 	{ "size-long", &arch_msize_long },
 	{ "arch=", NULL, handle_march },
 	{ "big-endian", &arch_big_endian, NULL },
diff --git a/validation/arch/x86-bitness-override.c b/validation/arch/x86-bitness-override.c
new file mode 100644
index 000000000000..ac51ecb244a8
--- /dev/null
+++ b/validation/arch/x86-bitness-override.c
@@ -0,0 +1,17 @@
+#ifdef __x86_64__
+defined_x86_64
+#endif
+#ifdef __i386__
+defined_i386
+#endif
+
+/*
+ * check-name: arch/x86-bitness-override
+ * check-command: sparse --arch=x86_64 -m16 -D__x86_64__ -E $file
+ *
+ * check-output-start
+
+defined_x86_64
+defined_i386
+ * check-output-end
+ */
diff --git a/validation/arch/x86-bitness-undef.c b/validation/arch/x86-bitness-undef.c
new file mode 100644
index 000000000000..ab2434c1173b
--- /dev/null
+++ b/validation/arch/x86-bitness-undef.c
@@ -0,0 +1,16 @@
+#ifdef __x86_64__
+defined_x86_64
+#endif
+#ifdef __i386__
+defined_i386
+#endif
+
+/*
+ * check-name: arch/x86-bitness-undef
+ * check-command: sparse --arch=x86_64 -D__x86_64__ -m16 -E $file
+ *
+ * check-output-start
+
+defined_i386
+ * check-output-end
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 72/73] evaluate: Accept conditional expressions with only one void side
  2026-11-09 20:31 Bart Van Assche
                   ` (70 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Accept conditional expressions in which exactly one of the second and
third operands has complete type void as a GNU extension matching GCC
and Clang: give the expression type void, evaluate the non-void operand
only for its side effects, warn only under -Wpedantic, and avoid
emitting zero-sized phi nodes in linearize_conditional().

Support Linux kernel macros such as STA_ASSIGN in net/mac80211/trace.h
and nfs_errorf() in fs/nfs/internal.h without false "incompatible types
in conditional expression" errors while continuing to diagnose uses of
the void result value.

Update validation/cond-err-expand.c and add validation/cond-one-void.c
and validation/cond-one-void-pedantic.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                          | 113 ++++++++++++++++++----------
 expand.c                            |  17 ++++-
 linearize.c                         |  19 +++++
 validation/cond-err-expand.c        |   9 ---
 validation/cond-one-void-pedantic.c |  39 ++++++++++
 validation/cond-one-void.c          |  47 ++++++++++++
 validation/linear/cond-one-void.c   |  24 ++++++
 7 files changed, 217 insertions(+), 51 deletions(-)
 create mode 100644 validation/cond-one-void-pedantic.c
 create mode 100644 validation/cond-one-void.c
 create mode 100644 validation/linear/cond-one-void.c

diff --git a/evaluate.c b/evaluate.c
index 6e1be32f929f..927c4f35d831 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -1421,6 +1421,37 @@ static struct symbol *evaluate_conditional_expression(struct expression *expr)
 
 	lclass = classify_type(ltype, &ltype);
 	rclass = classify_type(rtype, &rtype);
+
+	/*
+	 * GNU extension supported by gcc and clang: if exactly one of the
+	 * second and third operands has type void, the result has type void
+	 * and the other operand is only evaluated for its side effects.
+	 * ISO C forbids this [6.5.15(3)].
+	 */
+	if ((ltype == &void_ctype) != (rtype == &void_ctype)) {
+		struct symbol *other_type = (ltype == &void_ctype) ? rtype : ltype;
+
+		if (!valid_type(other_type)) {
+			expr->flags = CEF_NONE;
+			expr->ctype = &bad_ctype;
+			return NULL;
+		}
+		if (other_type == &incomplete_ctype || other_type->bit_size < 0 ||
+		    ((other_type->type == SYM_STRUCT || other_type->type == SYM_UNION) &&
+		     !other_type->defined)) {
+			expression_error(expr, "expression has incomplete type '%s'",
+					 show_typename(other_type));
+			return NULL;
+		}
+		if (Wpedantic)
+			warning(expr->pos, "ISO C forbids conditional expression with only one void side");
+		if (expr->cond_true)
+			*cond = cast_to(*cond, &void_ctype);
+		expr->cond_false = cast_to(expr->cond_false, &void_ctype);
+		ctype = &void_ctype;
+		goto out;
+	}
+
 	if (lclass & rclass & TYPE_NUM) {
 		ctype = usual_conversions('?', *cond, expr->cond_false,
 					  lclass, rclass, ltype, rtype);
@@ -3127,7 +3158,8 @@ static void check_constexpr_scalar_init(struct expression *orig,
 		}
 		tmp = *orig;
 		if (!is_null_pointer_constant(&tmp) &&
-		    !(((sclass & TYPE_PTR) || is_nullptr_type(stype)) && is_zero_constant(&tmp)))
+		    !(((sclass & TYPE_PTR) || is_nullptr_type(stype)) &&
+		      is_zero_constant(&tmp)))
 			sparse_error(orig->pos,
 				     "constexpr pointer initializer is not null");
 		return;
@@ -3309,7 +3341,6 @@ static int handle_initializer(struct expression **ep, int nested,
 		struct expression orig;
 
 		e = handle_scalar(e, nested, class, ctype);
-
 		if (!e)
 			return 0;
 		*ep = e;
@@ -3323,11 +3354,11 @@ static int handle_initializer(struct expression **ep, int nested,
 		 * shall be constant expressions or a string literal [6.7.8(4)].
 		 */
 		mods |= ctype->ctype.modifiers;
-		if (mods & MOD_CONSTEXPR)
+		if (mods & MOD_CONSTEXPR) {
 			check_constexpr_scalar_init(&orig, ctype);
-		else if ((mods & (MOD_TOPLEVEL | MOD_STATIC | MOD_TLS)) &&
-			 valid_type(ctype->ctype.base_type) &&
-			 !(e->flags & (CEF_ACE | CEF_ADDR))) {
+		} else if ((mods & (MOD_TOPLEVEL | MOD_STATIC | MOD_TLS)) &&
+			   valid_type(ctype->ctype.base_type) &&
+			   !(e->flags & (CEF_ACE | CEF_ADDR))) {
 			if (standard >= STANDARD_C23)
 				sparse_error(e->pos, "non-constant initializer for static object");
 			else if (Wconstexpr_not_const)
@@ -4187,6 +4218,41 @@ void check_duplicates(struct symbol *sym)
 	}
 }
 
+static void evaluate_cleanup(struct symbol *sym)
+{
+	struct expression *var_expr, *addr_expr, *fn_expr, *call;
+
+	if (!sym->cleanup || sym->cleanup->type != EXPR_SYMBOL)
+		return;
+
+	if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_STATIC | MOD_EXTERN)) {
+		sparse_error(sym->pos, "cleanup attribute may only be applied to local variables");
+		sym->cleanup = NULL;
+		return;
+	}
+
+	var_expr = alloc_expression(sym->pos, EXPR_SYMBOL);
+	var_expr->symbol = sym;
+	var_expr->symbol_name = sym->ident;
+	evaluate_expression(var_expr);
+
+	addr_expr = alloc_expression(sym->pos, EXPR_PREOP);
+	addr_expr->op = '&';
+	addr_expr->unop = var_expr;
+	evaluate_expression(addr_expr);
+
+	fn_expr = alloc_expression(sym->cleanup->pos, EXPR_SYMBOL);
+	fn_expr->symbol = sym->cleanup->symbol;
+	fn_expr->symbol_name = sym->cleanup->symbol_name;
+
+	call = alloc_expression(sym->pos, EXPR_CALL);
+	call->fn = fn_expr;
+	add_expression(&call->args, addr_expr);
+	evaluate_call(call);
+
+	sym->cleanup = call;
+}
+
 static bool check_constexpr_vm_type(struct symbol *type)
 {
 	while (type) {
@@ -4236,41 +4302,6 @@ static bool has_invalid_constexpr_qualifiers(struct symbol *type)
 	return false;
 }
 
-static void evaluate_cleanup(struct symbol *sym)
-{
-	struct expression *var_expr, *addr_expr, *fn_expr, *call;
-
-	if (!sym->cleanup || sym->cleanup->type != EXPR_SYMBOL)
-		return;
-
-	if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_STATIC | MOD_EXTERN)) {
-		sparse_error(sym->pos, "cleanup attribute may only be applied to local variables");
-		sym->cleanup = NULL;
-		return;
-	}
-
-	var_expr = alloc_expression(sym->pos, EXPR_SYMBOL);
-	var_expr->symbol = sym;
-	var_expr->symbol_name = sym->ident;
-	evaluate_expression(var_expr);
-
-	addr_expr = alloc_expression(sym->pos, EXPR_PREOP);
-	addr_expr->op = '&';
-	addr_expr->unop = var_expr;
-	evaluate_expression(addr_expr);
-
-	fn_expr = alloc_expression(sym->cleanup->pos, EXPR_SYMBOL);
-	fn_expr->symbol = sym->cleanup->symbol;
-	fn_expr->symbol_name = sym->cleanup->symbol_name;
-
-	call = alloc_expression(sym->pos, EXPR_CALL);
-	call->fn = fn_expr;
-	add_expression(&call->args, addr_expr);
-	evaluate_call(call);
-
-	sym->cleanup = call;
-}
-
 static struct symbol *evaluate_symbol(struct symbol *sym)
 {
 	struct symbol *base_type;
diff --git a/expand.c b/expand.c
index 5c3777ff3a23..1888effa4e37 100644
--- a/expand.c
+++ b/expand.c
@@ -850,6 +850,7 @@ static int expand_conditional(struct expression *expr)
 
 	cond_cost = expand_expression(cond);
 	if (cond->type == EXPR_VALUE) {
+		struct symbol *ctype = expr->ctype;
 		unsigned flags = expr->flags;
 		if (!cond->value && !cond->value_hi)
 			valt = valf;
@@ -860,13 +861,27 @@ static int expand_conditional(struct expression *expr)
 		expr->flags = flags;
 		if (expr->type == EXPR_VALUE)
 			expr->taint |= cond->taint;
+		if (ctype == &void_ctype && expr->ctype != &void_ctype) {
+			if (expr->type == EXPR_VALUE || expr->type == EXPR_FVALUE) {
+				expr->type = EXPR_VALUE;
+				expr->value = 0;
+				expr->ctype = &void_ctype;
+			} else {
+				struct expression *sub = alloc_expression(expr->pos, expr->type);
+				*sub = *expr;
+				expr->type = EXPR_CAST;
+				expr->ctype = &void_ctype;
+				expr->cast_type = &void_ctype;
+				expr->cast_expression = sub;
+			}
+		}
 		return cost;
 	}
 
 	cost = expand_expression(valt);
 	cost += expand_expression(valf);
 
-	if (cost < SELECT_COST) {
+	if (expr->ctype != &void_ctype && cost < SELECT_COST) {
 		expr->type = EXPR_SELECT;
 		cost -= BRANCH_COST - 1;
 	}
diff --git a/linearize.c b/linearize.c
index 9d36415551d6..9dc2fecfe731 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1699,6 +1699,14 @@ static pseudo_t linearize_short_conditional(struct entrypoint *ep, struct expres
 	bb_false = alloc_basic_block(ep, expr_false->pos);
 	merge = alloc_basic_block(ep, expr->pos);
 
+	if (expr->ctype == &void_ctype || type_size(expr->ctype) <= 0) {
+		linearize_cond_branch(ep, cond, merge, bb_false);
+		set_activeblock(ep, bb_false);
+		linearize_expression(ep, expr_false);
+		set_activeblock(ep, merge);
+		return VOID;
+	}
+
 	src1 = linearize_expression(ep, cond);
 	phi1 = alloc_phi(ep->active, src1, expr->ctype);
 	add_branch(ep, src1, merge, bb_false);
@@ -1728,6 +1736,17 @@ static pseudo_t linearize_conditional(struct entrypoint *ep, struct expression *
 
 	linearize_cond_branch(ep, cond, bb_true, bb_false);
 
+	if (expr->ctype == &void_ctype || type_size(expr->ctype) <= 0) {
+		set_activeblock(ep, bb_true);
+		linearize_expression(ep, expr_true);
+		add_goto(ep, merge);
+
+		set_activeblock(ep, bb_false);
+		linearize_expression(ep, expr_false);
+		set_activeblock(ep, merge);
+		return VOID;
+	}
+
 	set_activeblock(ep, bb_true);
 	src1 = linearize_expression(ep, expr_true);
 	phi1 = alloc_phi(ep->active, src1, expr->ctype);
diff --git a/validation/cond-err-expand.c b/validation/cond-err-expand.c
index b52624bc9de5..f24235a22e68 100644
--- a/validation/cond-err-expand.c
+++ b/validation/cond-err-expand.c
@@ -17,15 +17,6 @@ void bar(void)
  * check-name: cond-err-expand.c
  * check-command: test-linearize -Wno-decl $file
  *
- * check-error-start
-cond-err-expand.c:8:11: error: incompatible types in conditional expression (different base types):
-cond-err-expand.c:8:11:    int
-cond-err-expand.c:8:11:    void
-cond-err-expand.c:13:11: error: incompatible types in conditional expression (different base types):
-cond-err-expand.c:13:11:    void
-cond-err-expand.c:13:11:    int
- * check-error-end
- *
  * check-output-ignore
  * check-output-excludes: call.* __builtin_constant_p
  */
diff --git a/validation/cond-one-void-pedantic.c b/validation/cond-one-void-pedantic.c
new file mode 100644
index 000000000000..f91254f52c04
--- /dev/null
+++ b/validation/cond-one-void-pedantic.c
@@ -0,0 +1,39 @@
+void *f(void);
+void g(void);
+int h(void);
+extern void *p;
+
+static void test(int c)
+{
+	c ? f() : g();
+	c ? g() : h();
+	c ? 1 : g();
+	c ? p : g();
+	p ?: g();
+	c ? g() : (void)0;		// both sides void: always OK
+	(void)(c ? h() : g());
+}
+
+static int use_value(int c)
+{
+	int x = c ? h() : g();		// KO: void value used
+	return x;
+}
+
+/*
+ * check-name: cond-one-void-pedantic
+ * check-command: sparse -Wpedantic $file
+ *
+ * check-error-start
+cond-one-void-pedantic.c:8:11: warning: ISO C forbids conditional expression with only one void side
+cond-one-void-pedantic.c:9:11: warning: ISO C forbids conditional expression with only one void side
+cond-one-void-pedantic.c:10:11: warning: ISO C forbids conditional expression with only one void side
+cond-one-void-pedantic.c:11:11: warning: ISO C forbids conditional expression with only one void side
+cond-one-void-pedantic.c:12:11: warning: ISO C forbids conditional expression with only one void side
+cond-one-void-pedantic.c:14:18: warning: ISO C forbids conditional expression with only one void side
+cond-one-void-pedantic.c:19:19: warning: ISO C forbids conditional expression with only one void side
+cond-one-void-pedantic.c:19:19: warning: incorrect type in initializer (different base types)
+cond-one-void-pedantic.c:19:19:    expected int x
+cond-one-void-pedantic.c:19:19:    got void
+ * check-error-end
+ */
diff --git a/validation/cond-one-void.c b/validation/cond-one-void.c
new file mode 100644
index 000000000000..6034dbb218a9
--- /dev/null
+++ b/validation/cond-one-void.c
@@ -0,0 +1,47 @@
+void *f(void);
+void g(void);
+int h(void);
+extern void *p;
+
+static void test(int c)
+{
+	c ? f() : g();
+	c ? g() : h();
+	c ? 1 : g();
+	c ? p : g();
+	p ?: g();
+	1 ?: g();
+	0 ?: g();
+	c ? g() : (void)0;		// both sides void: always OK
+	(void)(c ? h() : g());
+}
+
+struct empty {};
+struct incomplete;
+extern struct empty emp;
+extern struct incomplete inc;
+
+static int use_value(int c)
+{
+	int x = c ? h() : g();		// KO: void value used
+	int y = 1 ?: g();		// KO: void value used
+	c ? (void)0 : emp;		// OK: complete empty struct
+	c ? (void)0 : inc;		// KO: incomplete type
+	return x + y;
+}
+
+/*
+ * check-name: cond-one-void
+ * check-description: Like gcc and clang, accept conditional expressions
+ *	with only one void side and give these type void.
+ *
+ * check-error-start
+cond-one-void.c:26:19: warning: incorrect type in initializer (different base types)
+cond-one-void.c:26:19:    expected int x
+cond-one-void.c:26:19:    got void
+cond-one-void.c:27:19: warning: incorrect type in initializer (different base types)
+cond-one-void.c:27:19:    expected int y
+cond-one-void.c:27:19:    got void
+cond-one-void.c:29:11: error: expression has incomplete type 'struct incomplete'
+ * check-error-end
+ */
diff --git a/validation/linear/cond-one-void.c b/validation/linear/cond-one-void.c
new file mode 100644
index 000000000000..6c962809736f
--- /dev/null
+++ b/validation/linear/cond-one-void.c
@@ -0,0 +1,24 @@
+void *f(void);
+void g(void);
+int h(void);
+
+void test_cond_void_ir(int c, void *p)
+{
+	c ? f() : g();
+	c ? g() : h();
+	c ? 1 : g();
+	p ?: g();
+	1 ?: g();
+	0 ?: g();
+}
+
+/*
+ * check-name: cond-one-void-linearize
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-excludes: VOID
+ * check-output-excludes: phi\\.
+ * check-output-excludes: phisrc\\.
+ * check-output-excludes: select\\.
+ */

^ permalink raw reply related	[flat|nested] 74+ messages in thread

* [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script
  2026-11-09 20:31 Bart Van Assche
                   ` (71 preceding siblings ...)
  2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
@ 2026-11-09 20:32 ` Bart Van Assche
  72 siblings, 0 replies; 74+ messages in thread
From: Bart Van Assche @ 2026-11-09 20:32 UTC (permalink / raw)
  To: linux-sparse; +Cc: Linus Torvalds, Bart Van Assche

Add a GitHub Actions workflow and helper script to build the Linux
kernel with sparse under AddressSanitizer and UndefinedBehaviorSanitizer
so regressions not covered by the validation suite are caught on a large
real-world C codebase:
- .github/workflows/build-linux-kernel-with-sparse.yml: Run on push,
  pull_request, daily schedule, and workflow_dispatch with read-only
  repository contents permissions.
- ci/build-linux-kernel-with-sparse.sh: Clean and build sparse in asan
  mode, clone ../linux-kernel or upstream torvalds/linux.git, neutralize
  non-core __CHECKER__ guards, and run a defconfig C=1 kernel check with
  detect_invalid_pointer_pairs=2 and halt_on_error=1.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 .../build-linux-kernel-with-sparse.yml        | 28 +++++++++++++++++++
 ci/build-linux-kernel-with-sparse.sh          | 23 +++++++++++++++
 2 files changed, 51 insertions(+)
 create mode 100644 .github/workflows/build-linux-kernel-with-sparse.yml
 create mode 100755 ci/build-linux-kernel-with-sparse.sh

diff --git a/.github/workflows/build-linux-kernel-with-sparse.yml b/.github/workflows/build-linux-kernel-with-sparse.yml
new file mode 100644
index 000000000000..a7566c4d8082
--- /dev/null
+++ b/.github/workflows/build-linux-kernel-with-sparse.yml
@@ -0,0 +1,28 @@
+# See also https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
+
+name: Build Linux kernel with sparse
+
+on:
+  push:
+  pull_request:
+  schedule:
+    - cron: '0 0 * * *'
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@main
+
+    - name: install
+      run: sudo ci/install.sh
+
+    - name: build Linux kernel with sparse
+      run: |
+        sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
+        ci/build-linux-kernel-with-sparse.sh
diff --git a/ci/build-linux-kernel-with-sparse.sh b/ci/build-linux-kernel-with-sparse.sh
new file mode 100755
index 000000000000..7e3505634d95
--- /dev/null
+++ b/ci/build-linux-kernel-with-sparse.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -euo pipefail
+
+make clean
+export MODE=asan
+ci/build.sh
+
+rm -rf linux-kernel
+if [ -e ../linux-kernel ]; then
+    git clone ../linux-kernel linux-kernel
+else
+    git clone --depth=3 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-kernel
+fi
+cd linux-kernel
+# Make most __CHECKER__ checks ineffective.
+{ git grep -lw __CHECKER__ || [ $? -eq 1 ]; } |
+    { grep -vE '^arch/|^include/linux/compiler|^include/uapi/linux/types.h|^include/linux/bug.h' || [ $? -eq 1 ]; } |
+    xargs -r -d '\n' sed -i 's/__CHECKER__/___CHECKER___/g'
+make defconfig
+export ASAN_OPTIONS=detect_invalid_pointer_pairs=2
+export UBSAN_OPTIONS=halt_on_error=1
+make -sj"$(nproc)" C=1 CHECK="${PWD}/../sparse"

^ permalink raw reply related	[flat|nested] 74+ messages in thread

end of thread, other threads:[~2026-11-09 20:34 UTC | newest]

Thread overview: 74+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).