* [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow
@ 2026-10-19 17:56 Bart Van Assche
2026-10-19 17:56 ` [PATCH 01/20] doc: Update maintainer and mirror information Bart Van Assche
` (19 more replies)
0 siblings, 20 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Hi,
Please help with reviewing these sparse patches.
This patch series updates the Sparse maintainer and mirror documentation,
fixes undefined behavior and memory leaks reported by sanitizers, updates
the LLVM backend for recent LLVM releases, adds support for empty attribute
argument lists, and introduces a GitHub Actions CI workflow:
- Patch 1 updates the maintainer history in Documentation/index.rst from
the git changelog and points the GitHub mirror link to
https://github.com/bvanassche/sparse.
- Patches 2-14 fix memory leaks, out-of-bounds array reads, null member
accesses, inactive union member reads, zero-length VLAs, signed integer
overflows, and out-of-range bit shifts across ptrlist, bitmask helpers,
storage management, builtin declarations, c2xml, parsing, preprocessing,
target size conversions, evaluation, linearization, constant folding, and
IR simplification so Sparse runs cleanly under ASan, LSan, and UBSan.
- Patches 15-17 and 19 update sparsec and sparse-llvm for modern LLVM
versions and LeakSanitizer cleanliness. sparsec emits object files
directly via 'llc -filetype=obj' instead of piping assembly to 'as', and
sparse-llvm avoids deprecated LLVMConstGEP2() calls on LLVM 15+, uses an
explicit LLVMContextRef instead of the deprecated global context, and
disposes allocated arrays, IR builders, and LLVM modules on exit.
- Patch 18 teaches the parser to accept empty attribute argument lists such
as __attribute__((nonnull())), matching GCC and Clang and avoiding parse
errors on Linux kernel headers.
- Patch 20 adds a GitHub Actions workflow and CI scripts that build Sparse
and run 'make check' in both regular mode and ASan + UBSan mode to catch
regressions, memory errors, and undefined behavior automatically.
If nobody objects I plan to push these changes to the kernel.org sparse git
repository by the end of this week.
Thanks,
Bart.
Bart Van Assche (19):
doc: Update maintainer and mirror information
ptrlist: Guard against null list in ptrlist operations
bits: Guard against out-of-bounds bit shifts in bitmask helpers
storage: Free hash table entries when freeing storage
builtin: Bound argument array iteration in declare_builtin()
c2xml: Use an unsigned shift in examine_modifiers()
parse: Avoid incrementing rank on invalid long real specifiers
pre-process: Avoid zero-length VLAs for parameterless macros
pre-process: Avoid reading token->string on non-string tokens
target: Avoid signed overflow in bit and byte conversion helpers
evaluate: Use bitmask helpers in evaluate_ptr_add()
linearize: Use bits_mask() in linearize_bitfield_insert()
expand: Avoid undefined shifts and signed overflow in constant folding
simplify: Avoid undefined shifts and signed overflow in simplification
sparsec: Emit object files directly with llc -filetype=obj
llvm: Stop calling LLVMConstGEP2() for string literals
llvm: Free leaked allocations and LLVM objects
llvm: Use explicit LLVMContextRef instead of global context
ci: Add a GitHub Actions workflow and CI scripts
Dmitry Ilvokhin (1):
parse: Accept an empty attribute argument list
.github/workflows/build.yml | 36 +++++++++++
Documentation/index.rst | 11 ++--
bitmap.h | 2 +-
bits.h | 12 ++--
builtin.c | 2 +-
builtin.h | 1 -
c2xml.c | 2 +-
ci/build.sh | 22 +++++++
ci/install.sh | 24 +++++++
ci/test.sh | 3 +
evaluate.c | 6 +-
expand.c | 52 ++++++---------
linearize.c | 2 +-
parse.c | 34 ++++++----
pre-process.c | 10 +--
ptrlist.c | 47 ++++++++++----
ptrlist.h | 14 ++--
simplify.c | 53 ++++++++-------
sparse-llvm.c | 96 ++++++++++++++++++----------
sparsec | 2 +-
storage.c | 10 ++-
target.h | 6 +-
validation/attribute-nonnull-empty.c | 12 ++++
23 files changed, 310 insertions(+), 149 deletions(-)
create mode 100644 .github/workflows/build.yml
create mode 100755 ci/build.sh
create mode 100755 ci/install.sh
create mode 100755 ci/test.sh
create mode 100644 validation/attribute-nonnull-empty.c
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH 01/20] doc: Update maintainer and mirror information
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 02/20] ptrlist: Guard against null list in ptrlist operations Bart Van Assche
` (18 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Update the maintainer entry to list Bart Van Assche and replace the
unofficial GitHub mirror URL with https://github.com/bvanassche/sparse.
The maintainer information has been derived from the git changelog.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
Documentation/index.rst | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/index.rst b/Documentation/index.rst
index f483c081681d..112a3751cc44 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -22,10 +22,11 @@ as mixing pointers to user address space and pointers to kernel address space.
Maintainer
----------
-1. 2006 - 2009, Josh Triplett
-2. 2009 - 2018, Chris Li
-3. 2018 - 2025, Luc Van Oostenryck
-4. 2025 - present, Chris Li
+1. 2006/09 - 2008/08, Josh Triplett
+2. 2008/12 - 2017/02, Chris Li
+3. 2017/03 - 2024/02, Luc Van Oostenryck
+4. 2025/09 - 2025/10, Chris Li
+5. 2026/09 - present, Bart Van Assche
Getting Sparse
--------------
@@ -36,7 +37,7 @@ repository with the command::
git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git
You can also `browse the Git repository <https://git.kernel.org/pub/scm/devel/sparse/sparse.git>`_
-or use the mirror at https://github.com/lucvoo/sparse.
+or use the mirror at https://github.com/bvanassche/sparse.
The tarballs of released versions of Sparse and their signatures can be found at
https://www.kernel.org/pub/software/devel/sparse/dist/.
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 02/20] ptrlist: Guard against null list in ptrlist operations
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
2026-10-19 17:56 ` [PATCH 01/20] doc: Update maintainer and mirror information Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-21 13:02 ` Dmitry Ilvokhin
2026-10-19 17:56 ` [PATCH 03/20] bits: Guard against out-of-bounds bit shifts in bitmask helpers Bart Van Assche
` (17 subsequent siblings)
19 siblings, 1 reply; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Guard against null list pointers before accessing list->rm in ptrlist.h
and ptrlist.c to eliminate undefined behavior and UBSan null member
access traps.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
ptrlist.c | 47 +++++++++++++++++++++++++++++++++--------------
ptrlist.h | 14 ++++++++++----
2 files changed, 43 insertions(+), 18 deletions(-)
diff --git a/ptrlist.c b/ptrlist.c
index c5766002e7ef..29fea6a35c70 100644
--- a/ptrlist.c
+++ b/ptrlist.c
@@ -165,19 +165,21 @@ void *ptr_list_nth_entry(struct ptr_list *list, unsigned int idx)
int linearize_ptr_list(struct ptr_list *head, void **arr, int max)
{
int nr = 0;
- if (head && max > 0) {
+ if (head && max >= 0) {
struct ptr_list *list = head;
do {
- int i = list->nr;
- nr += i;
- if (max == 0)
- continue;
- if (i > max)
- i = max;
- memcpy(arr, list->list, i*sizeof(void *));
- arr += i;
- max -= i;
+ int i;
+ for (i = 0; i < list->nr; i++) {
+ void *ptr = list->list[i];
+ if (list->rm && !ptr)
+ continue;
+ nr++;
+ if (max > 0) {
+ *arr++ = ptr;
+ max--;
+ }
+ }
} while ((list = list->next) != head);
}
return nr;
@@ -247,6 +249,15 @@ void split_ptr_list_head(struct ptr_list *head)
newlist->nr = nr;
memcpy(newlist->list, head->list + old, nr * sizeof(void *));
memset(head->list + old, 0xf0, nr * sizeof(void *));
+ if (head->rm) {
+ int i, rm = 0;
+ for (i = 0; i < nr; i++) {
+ if (!newlist->list[i])
+ rm++;
+ }
+ newlist->rm = rm;
+ head->rm -= rm;
+ }
}
///
@@ -409,10 +420,18 @@ void * delete_ptr_list_last(struct ptr_list **head)
if (!first)
return NULL;
- last = first->prev;
- if (last->nr)
- ptr = last->list[--last->nr];
- if (last->nr <=0) {
+ while ((last = first->prev)->nr == 0) {
+ if (last == first) {
+ *head = NULL;
+ __free_ptrlist(last);
+ return NULL;
+ }
+ first->prev = last->prev;
+ last->prev->next = first;
+ __free_ptrlist(last);
+ }
+ ptr = last->list[--last->nr];
+ if (last->nr <= 0) {
first->prev = last->prev;
last->prev->next = first;
if (last == first)
diff --git a/ptrlist.h b/ptrlist.h
index 5a3dcbeb97ae..89f15d53fa13 100644
--- a/ptrlist.h
+++ b/ptrlist.h
@@ -11,7 +11,7 @@
*/
/* Silly type-safety check ;) */
-#define CHECK_TYPE(head,ptr) (void)(&(ptr) == &(head)->list[0])
+#define CHECK_TYPE(head,ptr) (void)sizeof(&(ptr) == &(head)->list[0])
#define PTRLIST_TYPE(head) __typeof__((head)->list[0])
#define VRFY_PTR_LIST(head) (void)(sizeof((head)->list[0]))
@@ -163,15 +163,19 @@ extern void __free_ptr_list(struct ptr_list **);
#define PTR_NEXT(ptr, __head, __list, __nr, PTR_ENTRY) \
- do { \
+ for (;;) { \
if (__nr < __list->nr) { \
ptr = PTR_ENTRY(__list,__nr); \
__nr++; \
+ if (__list->rm && !ptr) \
+ continue; \
break; \
} \
ptr = NULL; \
__nr = 0; \
- } while ((__list = __list->next) != __head) \
+ if ((__list = __list->next) == __head) \
+ break; \
+ } \
#define DO_PREPARE(head, ptr, __head, __list, __nr, PTR_ENTRY) \
do { \
@@ -224,7 +228,7 @@ extern void __free_ptr_list(struct ptr_list **);
__typeof__(head) __list = __head; \
__typeof__(head) __name = __head; \
int __nr; \
- if (!head) \
+ if (!__head) \
break; \
do { \
__list = __list->prev; \
@@ -255,6 +259,8 @@ extern void __free_ptr_list(struct ptr_list **);
__inside##new: \
while (--__newnr >= 0) { \
new = PTR_ENTRY(__newlist,__newnr); \
+ if (__newlist->rm && !new) \
+ continue; \
#define DO_THIS_ADDRESS(ptr, __head, __list, __nr) \
(&__list->list[__nr])
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 03/20] bits: Guard against out-of-bounds bit shifts in bitmask helpers
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
2026-10-19 17:56 ` [PATCH 01/20] doc: Update maintainer and mirror information Bart Van Assche
2026-10-19 17:56 ` [PATCH 02/20] ptrlist: Guard against null list in ptrlist operations Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-21 13:22 ` Dmitry Ilvokhin
2026-10-19 17:56 ` [PATCH 04/20] storage: Free hash table entries when freeing storage Bart Van Assche
` (16 subsequent siblings)
19 siblings, 1 reply; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Clamp bit widths to [0, 64] in bits.h and fix 64-bit shift calculations
in bitmap.h to avoid undefined shift behavior on 64-bit integers.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
bitmap.h | 2 +-
bits.h | 12 ++++++++----
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/bitmap.h b/bitmap.h
index 4d81ffc07538..1f5d2ce48b24 100644
--- a/bitmap.h
+++ b/bitmap.h
@@ -2,7 +2,7 @@
#define BITMAP_H
#define BITS_IN_LONG (sizeof(unsigned long)*8)
-#define LONGS(x) ((x + BITS_IN_LONG - 1) & -BITS_IN_LONG)
+#define LONGS(x) (((x) + BITS_IN_LONG - 1) / BITS_IN_LONG)
/* Every bitmap gets its own type */
#define DECLARE_BITMAP(name, x) unsigned long name[LONGS(x)]
diff --git a/bits.h b/bits.h
index 9908190d8c2f..23ee8805479c 100644
--- a/bits.h
+++ b/bits.h
@@ -12,19 +12,23 @@
static inline unsigned long long sign_bit(unsigned size)
{
+ if (!size)
+ return 0;
+ if (size > 64)
+ size = 64;
return 1ULL << (size - 1);
}
static inline unsigned long long sign_mask(unsigned size)
{
unsigned long long sbit = sign_bit(size);
- return sbit - 1;
+ return sbit ? sbit - 1 : 0;
}
static inline unsigned long long bits_mask(unsigned size)
{
unsigned long long sbit = sign_bit(size);
- return sbit | (sbit - 1);
+ return sbit | (sbit ? sbit - 1 : 0);
}
@@ -58,7 +62,7 @@ static inline long long bits_extend(long long val, unsigned size, int is_signed)
return val;
}
-static inline int is_power_of_2(long long val)
+static inline int is_power_of_2(unsigned long long val)
{
return val && !(val & (val - 1));
}
@@ -67,7 +71,7 @@ static inline int is_power_of_2(long long val)
// log base 2 of an exact power-of-2
static inline int log2_exact(unsigned long long val)
{
- return 8 * sizeof(val) - __builtin_clzl(val) - 1;
+ return 8 * sizeof(val) - __builtin_clzll(val) - 1;
}
#endif
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 04/20] storage: Free hash table entries when freeing storage
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (2 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 03/20] bits: Guard against out-of-bounds bit shifts in bitmask helpers Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-21 13:50 ` Dmitry Ilvokhin
2026-10-19 17:56 ` [PATCH 05/20] builtin: Bound argument array iteration in declare_builtin() Bart Van Assche
` (15 subsequent siblings)
19 siblings, 1 reply; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Free allocated hash table entries when freeing storage structures in
storage.c to prevent memory leaks during storage teardown.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
storage.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/storage.c b/storage.c
index 6fc6e3d7085f..160d424ee1f6 100644
--- a/storage.c
+++ b/storage.c
@@ -159,12 +159,18 @@ static void vrfy_storage(struct storage_hash_list **listp)
void free_storage(void)
{
+ struct storage_hash_list *all = NULL;
int i;
for (i = 0; i < MAX_STORAGE_HASH; i++) {
- vrfy_storage(storage_hash_table + i);
+ struct storage_hash *entry;
+ FOR_EACH_PTR(storage_hash_table[i], entry) {
+ add_ptr_list(&all, entry);
+ } END_FOR_EACH_PTR(entry);
free_ptr_list(storage_hash_table + i);
}
+ vrfy_storage(&all);
+ free_ptr_list(&all);
}
const char *show_storage(struct storage *s)
@@ -208,7 +214,7 @@ static struct storage * combine_storage(struct storage *src, struct storage *dst
/* Mark it unused */
src->type = REG_BAD;
- src->users = NULL;
+ free_ptr_list(&src->users);
return dst;
}
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 05/20] builtin: Bound argument array iteration in declare_builtin()
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (3 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 04/20] storage: Free hash table entries when freeing storage Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-21 13:54 ` Dmitry Ilvokhin
2026-10-19 17:56 ` [PATCH 06/20] c2xml: Use an unsigned shift in examine_modifiers() Bart Van Assche
` (14 subsequent siblings)
19 siblings, 1 reply; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Bound the loop over entry->args[] in declare_builtin() with
ARRAY_SIZE(entry->args) before reading entry->args[i], and remove the
sentinel member _args_null_tail from struct builtin_fn.
Prevent out-of-bounds array reads past the end of struct builtin_fn's
six-element args[] array when declaring six-argument builtins such as
__atomic_compare_exchange_n, avoiding AddressSanitizer bounds errors.
Fixes: 935cf032524f ("builtin: use a table for the builtins")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
builtin.c | 2 +-
builtin.h | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/builtin.c b/builtin.c
index a704c5e8447c..1f3568bbad47 100644
--- a/builtin.c
+++ b/builtin.c
@@ -658,7 +658,7 @@ static void declare_builtin(int stream, const struct builtin_fn *entry)
fun->ctype.base_type = get_ctype(entry->ret_type);
fun->variadic = entry->variadic;
- for (i = 0; (arg = entry->args[i]); i++) {
+ for (i = 0; i < ARRAY_SIZE(entry->args) && (arg = entry->args[i]); i++) {
struct symbol *anode = alloc_symbol(sym->pos, SYM_NODE);
anode->ctype.base_type = get_ctype(arg);
add_symbol(&fun->arguments, anode);
diff --git a/builtin.h b/builtin.h
index 5fe77c926244..8f82d138bec7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -8,7 +8,6 @@ struct builtin_fn {
struct symbol *ret_type;
unsigned int variadic:1;
struct symbol *args[6];
- struct symbol *_args_null_tail;
struct symbol_op *op;
};
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 06/20] c2xml: Use an unsigned shift in examine_modifiers()
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (4 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 05/20] builtin: Bound argument array iteration in declare_builtin() Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 07/20] parse: Avoid incrementing rank on invalid long real specifiers Bart Van Assche
` (13 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Replace the signed integer shift expression 1 << i with (1UL << i) in
examine_modifiers() when testing bits in sym->ctype.modifiers across
indices 0 through 31.
Avoid shifting a signed 32-bit int value into its sign bit when i is 31,
eliminating undefined signed left-shift behavior in C and matching the
unsigned long type of sym->ctype.modifiers.
Fixes: f744a1419d92 ("add c2xml program")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
c2xml.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/c2xml.c b/c2xml.c
index 1a24c3a8120a..519769f72a71 100644
--- a/c2xml.c
+++ b/c2xml.c
@@ -149,7 +149,7 @@ static void examine_modifiers(struct symbol *sym, xmlNodePtr node)
/*iterate over the 32 bit bitfield*/
for (i=0; i < 32; i++) {
- if ((sym->ctype.modifiers & 1<<i) && modifiers[i])
+ if ((sym->ctype.modifiers & (1UL << i)) && modifiers[i])
newProp(node, modifiers[i], "1");
}
}
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 07/20] parse: Avoid incrementing rank on invalid long real specifiers
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (5 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 06/20] c2xml: Use an unsigned shift in examine_modifiers() Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 08/20] pre-process: Avoid zero-length VLAs for parameterless macros Bart Van Assche
` (12 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Check whether rank is already non-zero before incrementing rank for a
Set_Long type specifier in declaration_specifiers(), and break out of the
loop before incrementing rank when class is CReal.
Prevent out-of-bounds reads past the end of real_types[] when indexing
types[class][rank] for invalid specifier combinations such as "long long
double", where incrementing rank to 2 before breaking exceeded the valid
[-1, 1] index range of real_types + 1.
Fixes: 319206abe1f2 ("Rewrite and fix specifiers handling")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
parse.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/parse.c b/parse.c
index 9389079eb173..0e3e384550c5 100644
--- a/parse.c
+++ b/parse.c
@@ -1595,14 +1595,17 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
rank = -2;
if (s->op->set & (Set_Short|Set_Float)) {
rank = -1;
- } else if (s->op->set & Set_Long && rank++) {
- if (class == CReal) {
- specifier_conflict(token->pos,
- Set_Vlong,
- &double_ident);
- break;
+ } else if (s->op->set & Set_Long) {
+ if (rank) {
+ if (class == CReal) {
+ specifier_conflict(token->pos,
+ Set_Vlong,
+ &double_ident);
+ break;
+ }
+ seen |= Set_Vlong;
}
- seen |= Set_Vlong;
+ rank++;
}
}
token = token->next;
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 08/20] pre-process: Avoid zero-length VLAs for parameterless macros
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (6 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 07/20] parse: Avoid incrementing rank on invalid long real specifiers Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 09/20] pre-process: Avoid reading token->string on non-string tokens Bart Van Assche
` (11 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Ensure at least one element when declaring the variable-length args[]
arrays in expand() and dump_macro() by using nargs ? nargs : 1 instead of
nargs.
Avoid undefined behavior in C from declaring zero-length variable-length
arrays on the stack when expanding or dumping function-like macros defined
with an empty parameter list (nargs == 0).
Fixes: 669244d22464 ("[PATCH] lazy-copy macro expansion in pre-processing")
Fixes: 9b4e30eb0baf ("teach sparse how to dump macro definitions")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
pre-process.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pre-process.c b/pre-process.c
index 4e322855d600..038e019b11c9 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -745,7 +745,7 @@ 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;
- struct arg args[nargs];
+ struct arg args[nargs ? nargs : 1];
if (expanding->tainted) {
token->pos.noexpand = 1;
@@ -2285,7 +2285,7 @@ static int is_VA_ARGS_token(struct token *token)
static void dump_macro(struct symbol *sym)
{
int nargs = sym->arglist ? sym->arglist->count.normal : 0;
- struct token *args[nargs];
+ struct token *args[nargs ? nargs : 1];
struct token *token;
printf("#define %s", show_ident(sym->ident));
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 09/20] pre-process: Avoid reading token->string on non-string tokens
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (7 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 08/20] pre-process: Avoid zero-length VLAs for parameterless macros Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 10/20] target: Avoid signed overflow in bit and byte conversion helpers Bart Van Assche
` (10 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Check whether token_type(token) equals TOKEN_STRING before accessing
token->string->data in token_name_sequence(), and call show_token(token)
in the else branch.
Avoid dereferencing an inactive union member of struct token when
processing non-string tokens such as identifiers or punctuation in
computed or angle-bracketed #include directives, preventing undefined
pointer formation on non-pointer union payloads.
Fixes: f9d206d60eec ("Properly expand tokens on #if/#elif/#include preprocessor lines")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
pre-process.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/pre-process.c b/pre-process.c
index 038e019b11c9..7b6555677570 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -787,8 +787,10 @@ static const char *token_name_sequence(struct token *token, int endop, struct to
while (!eof_token(token) && !match_op(token, endop)) {
int len;
- const char *val = token->string->data;
- if (token_type(token) != TOKEN_STRING)
+ const char *val;
+ if (token_type(token) == TOKEN_STRING)
+ val = token->string->data;
+ else
val = show_token(token);
len = strlen(val);
memcpy(ptr, val, len);
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 10/20] target: Avoid signed overflow in bit and byte conversion helpers
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (8 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 09/20] pre-process: Avoid reading token->string on non-string tokens Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 11/20] evaluate: Use bitmask helpers in evaluate_ptr_add() Bart Van Assche
` (9 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Cast bits to unsigned int before adding bits_in_char - 1 in
bits_to_bytes(), and widen bytes_to_bits() to accept and return long while
performing the multiplication by bits_in_char in unsigned long.
Prevent signed integer addition overflow in bits_to_bytes() near INT_MAX
and prevent 32-bit signed multiplication overflow and truncation in
bytes_to_bits() on large array offsets and sparse initializers.
Fixes: eb800bdc3791 ("Unhardcode byte size being 8 bits.")
Fixes: b3e9d87c6a98 ("sparse: make bits_to_bytes round up instead of down")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
target.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/target.h b/target.h
index 92b8af9169d7..0b21702c45f1 100644
--- a/target.h
+++ b/target.h
@@ -143,12 +143,12 @@ void target_init(void);
static inline int bits_to_bytes(int bits)
{
- return bits >= 0 ? (bits + bits_in_char - 1) / bits_in_char : -1;
+ return bits >= 0 ? ((unsigned int)bits + bits_in_char - 1) / bits_in_char : -1;
}
-static inline int bytes_to_bits(int bytes)
+static inline long bytes_to_bits(long bytes)
{
- return bytes * bits_in_char;
+ return (unsigned long)bytes * bits_in_char;
}
static inline unsigned long array_element_offset(unsigned long base_bits, int idx)
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 11/20] evaluate: Use bitmask helpers in evaluate_ptr_add()
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (9 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 10/20] target: Avoid signed overflow in bit and byte conversion helpers Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 12/20] linearize: Use bits_mask() in linearize_bitfield_insert() Bart Van Assche
` (8 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Include "bits.h" in evaluate.c and replace the open-coded 1ULL shifts in
evaluate_ptr_add() with sign_bit(itype->bit_size) and
bits_mask(bits_in_pointer).
Avoid undefined shift behavior when folding constant pointer offsets whose
integer index type has zero bit size (on invalid types) or exceeds 64 bits
(such as __int128), or when bits_in_pointer equals 64.
Fixes: fbab5f78de94 ("[PATCH] fix index conversions in evaluate_ptr_add()")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
evaluate.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/evaluate.c b/evaluate.c
index 85a6447ba3ce..988ace95abed 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -37,6 +37,7 @@
#include "evaluate.h"
#include "lib.h"
#include "allocate.h"
+#include "bits.h"
#include "parse.h"
#include "token.h"
#include "symbol.h"
@@ -601,14 +602,13 @@ static struct symbol *evaluate_ptr_add(struct expression *expr, struct symbol *i
if (index->type == EXPR_VALUE) {
struct expression *val = alloc_expression(expr->pos, EXPR_VALUE);
unsigned long long v = index->value, mask;
- mask = 1ULL << (itype->bit_size - 1);
+ mask = sign_bit(itype->bit_size);
if (v & mask)
v |= -mask;
else
v &= mask - 1;
v *= multiply;
- mask = 1ULL << (bits_in_pointer - 1);
- v &= mask | (mask - 1);
+ v &= bits_mask(bits_in_pointer);
val->value = v;
val->ctype = ssize_t_ctype;
expr->right = val;
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 12/20] linearize: Use bits_mask() in linearize_bitfield_insert()
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (10 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 11/20] evaluate: Use bitmask helpers in evaluate_ptr_add() Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 13/20] expand: Avoid undefined shifts and signed overflow in constant folding Bart Van Assche
` (7 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Replace the open-coded ((1ULL << size) - 1) bitmask expression in
linearize_bitfield_insert() with bits_mask(size).
Avoid undefined 64-bit shift behavior when inserting into a 64-bit
bitfield (size == 64), matching the existing bits_mask(btype->bit_size)
call in the same function.
Fixes: ff5cc9636a30 ("Add a semblance of sanity to structure member accesses.")
Fixes: a8e1df57346c ("bitfield: extract linearize_bitfield_insert()")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
linearize.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/linearize.c b/linearize.c
index 1db2d505a7d4..5bdd6119260f 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1003,7 +1003,7 @@ static pseudo_t linearize_bitfield_insert(struct entrypoint *ep,
{
unsigned int shift = ctype->bit_offset;
unsigned int size = ctype->bit_size;
- unsigned long long mask = ((1ULL << size) - 1);
+ unsigned long long mask = bits_mask(size);
unsigned long long smask= bits_mask(btype->bit_size);
val = add_cast(ep, btype, ctype, OP_ZEXT, val);
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 13/20] expand: Avoid undefined shifts and signed overflow in constant folding
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (11 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 12/20] linearize: Use bits_mask() in linearize_bitfield_insert() Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 14/20] simplify: Avoid undefined shifts and signed overflow in simplification Bart Van Assche
` (6 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Include "bits.h" in expand.c, rewrite get_longlong() using bits_extend(),
use unsigned long long bitmasks with sign_bit() and bits_mask() across
cast_value(), simplify_int_binop(), simplify_cmp_binop(),
simplify_preop(), and __get_expression_value(), evaluate signed
multiplication in simplify_int_binop() via unsigned arithmetic, and clamp
shift counts in simplify_int_binop() to valid 64-bit host shift ranges.
Eliminate undefined host C behavior from out-of-range shifts (when type
bit_size is 0 or 128 or when folding over-wide constant shift expressions)
and from signed 64-bit multiplication overflow when folding constant
integer multiplications.
Fixes: 8d121d51b6f8 ("Split tree evaluation into two phases: the first phase does type evaluation, the second one does value evaluation and inline expansion.")
Fixes: 1c8c7d7237cf ("simplify_int_binop: split signed and unsigned operations and handle signed division overflow.")
Fixes: 411aed021d36 ("big-shift: don't take the modulo at expand time")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
expand.c | 52 +++++++++++++++++++++-------------------------------
1 file changed, 21 insertions(+), 31 deletions(-)
diff --git a/expand.c b/expand.c
index f14e718187bc..bbb30ae605dc 100644
--- a/expand.c
+++ b/expand.c
@@ -36,6 +36,7 @@
#include "lib.h"
#include "allocate.h"
+#include "bits.h"
#include "parse.h"
#include "token.h"
#include "symbol.h"
@@ -80,18 +81,8 @@ static int expand_symbol_expression(struct expression *expr)
static long long get_longlong(struct expression *expr)
{
- int no_expand = expr->ctype->ctype.modifiers & MOD_UNSIGNED;
- long long mask = 1ULL << (expr->ctype->bit_size - 1);
- long long value = expr->value;
- long long ormask, andmask;
-
- if (!(value & mask))
- no_expand = 1;
- andmask = mask | (mask-1);
- ormask = ~andmask;
- if (no_expand)
- ormask = 0;
- return (value & andmask) | ormask;
+ int is_signed = !(expr->ctype->ctype.modifiers & MOD_UNSIGNED);
+ return bits_extend(expr->value, expr->ctype->bit_size, is_signed);
}
void cast_value(struct expression *expr, struct symbol *newtype, struct expression *old)
@@ -99,8 +90,9 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
struct symbol *oldtype = old->ctype;
int old_size = oldtype->bit_size;
int new_size = newtype->bit_size;
- long long value, mask, signmask;
- long long oldmask, oldsignmask, dropped;
+ long long value;
+ unsigned long long mask, signmask;
+ unsigned long long oldmask, oldsignmask, dropped;
if (is_float_type(newtype) || is_float_type(oldtype))
goto Float;
@@ -128,8 +120,8 @@ Int:
}
// Truncate it to the new size
- signmask = 1ULL << (new_size-1);
- mask = signmask | (signmask-1);
+ signmask = sign_bit(new_size);
+ mask = bits_mask(new_size);
expr->value = value & mask;
// Stop here unless checking for truncation
@@ -137,8 +129,8 @@ Int:
return;
// Check if we dropped any bits..
- oldsignmask = 1ULL << (old_size-1);
- oldmask = oldsignmask | (oldsignmask-1);
+ oldsignmask = sign_bit(old_size);
+ oldmask = bits_mask(old_size);
dropped = oldmask & ~mask;
// OK if the bits were (and still are) purely sign bits
@@ -210,7 +202,7 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
return 0;
l = left->value; r = right->value;
is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
- mask = 1ULL << (ctype->bit_size-1);
+ mask = sign_bit(ctype->bit_size);
sl = l; sr = r;
if (is_signed && (sl & mask))
sl |= ~(mask-1);
@@ -244,9 +236,6 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
break;
case SIGNED('*'):
- v = sl * sr;
- break;
-
case UNSIGNED('*'):
v = l * r;
break;
@@ -279,21 +268,21 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
case SIGNED(SPECIAL_LEFTSHIFT):
case UNSIGNED(SPECIAL_LEFTSHIFT):
- v = l << r;
+ v = r < 64 ? l << r : 0;
break;
case SIGNED(SPECIAL_RIGHTSHIFT):
- v = sl >> r;
+ v = sl >> (r < 63 ? r : 63);
break;
case UNSIGNED(SPECIAL_RIGHTSHIFT):
- v = l >> r;
+ v = r < 64 ? l >> r : 0;
break;
default:
return 0;
}
- mask = mask | (mask-1);
+ mask = bits_mask(ctype->bit_size);
expr->value = v & mask;
expr->type = EXPR_VALUE;
expr->taint = left->taint | right->taint;
@@ -317,7 +306,7 @@ static int simplify_cmp_binop(struct expression *expr, struct symbol *ctype)
if (left->type != EXPR_VALUE || right->type != EXPR_VALUE)
return 0;
l = left->value; r = right->value;
- mask = 1ULL << (ctype->bit_size-1);
+ mask = sign_bit(ctype->bit_size);
sl = l; sr = r;
if (sl & mask)
sl |= ~(mask-1);
@@ -773,7 +762,7 @@ static int simplify_preop(struct expression *expr)
if (op->type != EXPR_VALUE)
return 0;
- mask = 1ULL << (expr->ctype->bit_size-1);
+ mask = sign_bit(expr->ctype->bit_size);
v = op->value;
switch (expr->op) {
case '+': break;
@@ -786,7 +775,7 @@ static int simplify_preop(struct expression *expr)
case '~': v = ~v; break;
default: return 0;
}
- mask = mask | (mask-1);
+ mask = bits_mask(expr->ctype->bit_size);
expr->value = v & mask;
expr->type = EXPR_VALUE;
expr->taint = op->taint;
@@ -1377,7 +1366,8 @@ static inline int bad_integer_constant_expression(struct expression *expr)
static long long __get_expression_value(struct expression *expr, int strict)
{
- long long value, mask;
+ long long value;
+ unsigned long long mask;
struct symbol *ctype;
if (!expr)
@@ -1399,7 +1389,7 @@ static long long __get_expression_value(struct expression *expr, int strict)
}
value = expr->value;
- mask = 1ULL << (ctype->bit_size-1);
+ mask = sign_bit(ctype->bit_size);
if (value & mask) {
while (ctype->type != SYM_BASETYPE)
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 14/20] simplify: Avoid undefined shifts and signed overflow in simplification
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (12 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 13/20] expand: Avoid undefined shifts and signed overflow in constant folding Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 15/20] sparsec: Emit object files directly with llc -filetype=obj Bart Van Assche
` (5 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Use unsigned long long masks and arithmetic with sign_bit() and
bits_mask() in eval_op(), simplify_mul_div(), simplify_constant_mask(),
simplify_constant_rightside(), simplify_constant_unop(), and
simplify_cast(), guard shift counts in eval_op() against 64-bit overflow,
and skip 64-bit mask simplifications in simplify_shift() when insn->size
exceeds 64 bits.
Eliminate undefined host C behavior from signed 64-bit overflow during
constant negation, addition, and subtraction, and from out-of-range
shifts when simplifying 0-bit, 32-bit, 64-bit, or 128-bit integer
instructions.
Fixes: 29946c577c13 ("Do first-approximation constant binop simplification.")
Fixes: 08778dab2167 ("Simplify constant unops")
Fixes: 62cab21fe13b ("add support for __int128")
Fixes: 0b9963941148 ("simplify 'x | ~0' to '~0'")
Fixes: 97e6515a7944 ("unop: add helper eval_unop()")
Fixes: f1252a541ac8 ("simplify '(x * -1)' to '-x'")
Fixes: c68bfab0e487 ("simplify ((x & M) >> S) when (M >> S) == 0")
Fixes: cb4bed63e19e ("simplify ((x & M) << S) when (M << S) == 0")
Fixes: 3a729e93ba9f ("simplify ((x >> S) << S)")
Fixes: 1ed0dfb47362 ("cast: simplify TRUNC + ZEXT to AND")
Fixes: 5da4e09b4701 ("cast: simplify AND(ZEXT(x,M),N)")
Fixes: 780d796a478f ("cast: simplify [ZS]EXT(AND(x,M),N)")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
simplify.c | 53 ++++++++++++++++++++++++++---------------------------
1 file changed, 26 insertions(+), 27 deletions(-)
diff --git a/simplify.c b/simplify.c
index 68c5f9c74021..f20b6f1a9881 100644
--- a/simplify.c
+++ b/simplify.c
@@ -632,10 +632,11 @@ static pseudo_t eval_op(int op, unsigned size, pseudo_t src1, pseudo_t src2)
long long left = src1->value;
long long right = src2->value;
unsigned long long ul, ur;
- long long res, mask, bits;
+ long long res;
+ unsigned long long mask, bits;
- mask = 1ULL << (size-1);
- bits = mask | (mask-1);
+ mask = sign_bit(size);
+ bits = bits_mask(size);
if (left & mask)
left |= ~bits;
@@ -646,17 +647,17 @@ static pseudo_t eval_op(int op, unsigned size, pseudo_t src1, pseudo_t src2)
switch (op) {
case OP_NEG:
- res = -left;
+ res = -ul;
break;
case OP_NOT:
res = ~ul;
break;
case OP_ADD:
- res = left + right;
+ res = ul + ur;
break;
case OP_SUB:
- res = left - right;
+ res = ul - ur;
break;
case OP_MUL:
res = ul * ur;
@@ -688,17 +689,17 @@ static pseudo_t eval_op(int op, unsigned size, pseudo_t src1, pseudo_t src2)
case OP_SHL:
if (ur >= size)
goto undef;
- res = left << right;
+ res = ur < 64 ? ul << ur : 0;
break;
case OP_LSR:
if (ur >= size)
goto undef;
- res = ul >> ur;
+ res = ur < 64 ? ul >> ur : 0;
break;
case OP_ASR:
if (ur >= size)
goto undef;
- res = left >> right;
+ res = left >> (ur < 63 ? ur : 63);
break;
/* Logical */
case OP_AND:
@@ -967,7 +968,7 @@ static int simplify_shift(struct instruction *insn, pseudo_t pseudo, long long v
case OP_AND:
// replace (A & M) >> S
// by (A >> S) & (M >> S)
- if (!constant(def->src2))
+ if (!constant(def->src2) || insn->size > 64)
break;
mask = bits_mask(insn->size - value) << value;
omask = def->src2->value;
@@ -991,7 +992,7 @@ static int simplify_shift(struct instruction *insn, pseudo_t pseudo, long long v
case OP_SHL:
// replace ((x << S) >> S)
// by (x & (-1 >> S))
- if (def->src2 != insn->src2)
+ if (def->src2 != insn->src2 || insn->size > 64)
break;
mask = bits_mask(insn->size - value);
goto replace_mask;
@@ -1003,7 +1004,7 @@ static int simplify_shift(struct instruction *insn, pseudo_t pseudo, long long v
switch(DEF_OPCODE(def, pseudo)) {
case OP_AND:
// simplify (A & M) << S
- if (!constant(def->src2))
+ if (!constant(def->src2) || insn->size > 64)
break;
mask = bits_mask(insn->size) >> value;
omask = def->src2->value;
@@ -1017,7 +1018,7 @@ static int simplify_shift(struct instruction *insn, pseudo_t pseudo, long long v
case OP_LSR:
// replace ((x >> S) << S)
// by (x & (-1 << S))
- if (def->src2 != insn->src2)
+ if (def->src2 != insn->src2 || insn->size > 64)
break;
mask = bits_mask(insn->size - value) << value;
goto replace_mask;
@@ -1056,8 +1057,8 @@ replace_mask:
static int simplify_mul_div(struct instruction *insn, long long value)
{
- unsigned long long sbit = 1ULL << (insn->size - 1);
- unsigned long long bits = sbit | (sbit - 1);
+ unsigned long long sbit = sign_bit(insn->size);
+ unsigned long long bits = bits_mask(insn->size);
if (value == 1)
return replace_with_pseudo(insn, insn->src1);
@@ -1523,7 +1524,7 @@ static int simplify_constant_mask(struct instruction *insn, unsigned long long m
osize = def->orig_type->bit_size;
/* fall through */
oldsize:
- omask = (1ULL << osize) - 1;
+ omask = bits_mask(osize);
nmask = mask & omask;
if (nmask == omask)
// the AND mask is redundant
@@ -1559,8 +1560,7 @@ static int simplify_const_rightadd(struct instruction *def, struct instruction *
static int simplify_constant_rightside(struct instruction *insn)
{
long long value = insn->src2->value;
- long long sbit = 1ULL << (insn->size - 1);
- long long bits = sbit | (sbit - 1);
+ unsigned long long bits = bits_mask(insn->size);
int changed = 0;
switch (insn->opcode) {
@@ -2138,19 +2138,19 @@ static int simplify_xor(struct instruction *insn)
static int simplify_constant_unop(struct instruction *insn)
{
long long val = insn->src1->value;
- long long res, mask;
+ unsigned long long res, mask;
switch (insn->opcode) {
case OP_NOT:
res = ~val;
break;
case OP_NEG:
- res = -val;
+ res = -(unsigned long long)val;
break;
case OP_SEXT:
- mask = 1ULL << (insn->orig_type->bit_size-1);
+ mask = sign_bit(insn->orig_type->bit_size);
if (val & mask)
- val |= ~(mask | (mask-1));
+ val |= ~bits_mask(insn->orig_type->bit_size);
/* fall through */
case OP_ZEXT:
case OP_TRUNC:
@@ -2159,8 +2159,7 @@ static int simplify_constant_unop(struct instruction *insn)
default:
return 0;
}
- mask = 1ULL << (insn->size-1);
- res &= mask | (mask-1);
+ res &= bits_mask(insn->size);
return replace_with_value(insn, res);
}
@@ -2329,12 +2328,12 @@ static int simplify_cast(struct instruction *insn)
insn->opcode = OP_AND;
mask = val->value;
- mask &= (1ULL << size) - 1;
+ mask &= bits_mask(size);
insn->src2 = value_pseudo(mask);
return REPEAT_CSE;
case OP_SEXT:
- if (val->value & (1 << (def->size - 1)))
+ if (val->value & sign_bit(def->size))
break;
// OK, sign bit is 0
case OP_ZEXT:
@@ -2426,7 +2425,7 @@ static int simplify_cast(struct instruction *insn)
if (size != def->orig_type->bit_size)
break;
insn->opcode = OP_AND;
- insn->src2 = value_pseudo((1ULL << def->size) - 1);
+ insn->src2 = value_pseudo(bits_mask(def->size));
return replace_pseudo(insn, &insn->src1, def->src);
}
break;
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 15/20] sparsec: Emit object files directly with llc -filetype=obj
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (13 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 14/20] simplify: Avoid undefined shifts and signed overflow in simplification Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 16/20] llvm: Stop calling LLVMConstGEP2() for string literals Bart Van Assche
` (4 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Modern LLVM llc supports emitting object files directly via
'-filetype=obj'. Use 'llc -filetype=obj' instead of piping assembly
output into the system assembler ('as') to simplify the compilation
pipeline and fix test-suite failures when using LLVM 14+.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
sparsec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sparsec b/sparsec
index 3f9b60c67e0a..57a02d51cd48 100755
--- a/sparsec
+++ b/sparsec
@@ -45,7 +45,7 @@ case "$(uname -s)" in
;;
esac
-$DIRNAME/sparse-llvm $SPARSEOPTS | $LLC ${LLC_ARCH_OPTS} | as -o $TMPFILE
+$DIRNAME/sparse-llvm $SPARSEOPTS | $LLC ${LLC_ARCH_OPTS} -filetype=obj -o $TMPFILE
if [ $NEED_LINK -eq 1 ]; then
if [ -z $OUTFILE ]; then
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 16/20] llvm: Stop calling LLVMConstGEP2() for string literals
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (14 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 15/20] sparsec: Emit object files directly with llc -filetype=obj Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 17/20] llvm: Free leaked allocations and LLVM objects Bart Van Assche
` (3 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Return the string global variable directly in get_sym_value() when
compiling with LLVM 15 or later instead of passing it to
LLVMConstGEP2() with zero indices, and restrict the indices array
definition to LLVM 14 and earlier.
Avoid the LLVMConstGEP2() deprecation warning on LLVM 24 and omit the
redundant zero-offset constant GEP call because LLVM 15 and later use
opaque pointers (ptr) and fold zero-offset constant GEPs back to the
underlying global variable.
Fixes: 0544c547682b ("llvm: fix LLVM 15 deprecation warnings")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
sparse-llvm.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/sparse-llvm.c b/sparse-llvm.c
index 90a931b76a0e..1155c2341251 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -311,7 +311,9 @@ static LLVMValueRef get_sym_value(LLVMModuleRef module, struct symbol *sym)
case EXPR_STRING: {
const char *s = expr->string->data;
size_t len = expr->string->length;
+#if LLVM_VERSION_MAJOR <= 14
LLVMValueRef indices[] = { LLVMConstInt(LLVMInt64Type(), 0, 0), LLVMConstInt(LLVMInt64Type(), 0, 0) };
+#endif
LLVMValueRef data;
data = LLVMAddGlobal(module, LLVMArrayType(LLVMInt8Type(), len), ".str");
@@ -320,7 +322,7 @@ static LLVMValueRef get_sym_value(LLVMModuleRef module, struct symbol *sym)
LLVMSetInitializer(data, LLVMConstString(s, len, true));
#if LLVM_VERSION_MAJOR > 14
- result = LLVMConstGEP2(LLVMTypeOf(data), data, indices, ARRAY_SIZE(indices));
+ result = data;
#else
result = LLVMConstGEP(data, indices, ARRAY_SIZE(indices));
#endif
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 17/20] llvm: Free leaked allocations and LLVM objects
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (15 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 16/20] llvm: Stop calling LLVMConstGEP2() for string literals Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 18/20] parse: Accept an empty attribute argument list Bart Van Assche
` (2 subsequent siblings)
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Include <stdlib.h> in sparse-llvm.c, free arg_type after calling
LLVMFunctionType() in sym_func_type(), free args after emitting the call
instruction in output_op_call(), call LLVMDisposeBuilder() at the end of
output_fn(), and jump to a cleanup label when die_if_error is set in
main() so LLVMDisposeModule() executes before returning.
Prevent memory leaks of temporary argument arrays, per-function LLVM IR
builders, and the LLVM module during both normal compilation and error
exits under LeakSanitizer.
Fixes: 45a6fbff8250 ("sparse, llvm: Initial commit")
Fixes: 404705a0c86d ("sparse, llvm: implement OP_CALL")
Fixes: 064c4adfb5fd ("sparse, llvm: Function pointer code generation")
Fixes: 469e5d6ea335 ("sparse, llvm: die if error")
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
sparse-llvm.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/sparse-llvm.c b/sparse-llvm.c
index 1155c2341251..70e0ff6f58a4 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -10,6 +10,7 @@
#include <stdbool.h>
#include <stdio.h>
+#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
@@ -53,6 +54,7 @@ static LLVMTypeRef sym_func_type(struct symbol *sym)
int n_arg = symbol_list_size(sym->arguments);
LLVMTypeRef *arg_type = calloc(n_arg, sizeof(LLVMTypeRef));
LLVMTypeRef ret_type = func_return_type(sym);
+ LLVMTypeRef fn_type;
struct symbol *arg;
int idx = 0;
@@ -62,7 +64,9 @@ static LLVMTypeRef sym_func_type(struct symbol *sym)
arg_type[idx++] = symbol_type(arg_sym);
} END_FOR_EACH_PTR(arg);
- return LLVMFunctionType(ret_type, arg_type, n_arg, sym->variadic);
+ fn_type = LLVMFunctionType(ret_type, arg_type, n_arg, sym->variadic);
+ free(arg_type);
+ return fn_type;
}
static LLVMTypeRef sym_array_type(struct symbol *sym)
@@ -858,6 +862,7 @@ static void output_op_call(struct function *fn, struct instruction *insn)
(void) fntype;
target = LLVMBuildCall(fn->builder, func, args, n_arg, name);
#endif
+ free(args);
insn->target->priv = target;
}
@@ -1223,6 +1228,8 @@ static void output_fn(LLVMModuleRef module, struct entrypoint *ep)
} END_FOR_EACH_PTR(phi);
} END_FOR_EACH_PTR(insn);
} END_FOR_EACH_PTR(bb);
+
+ LLVMDisposeBuilder(function.builder);
}
static LLVMValueRef output_data(LLVMModuleRef module, struct symbol *sym)
@@ -1372,6 +1379,7 @@ int main(int argc, char **argv)
struct string_list *filelist = NULL;
struct symbol_list *symlist;
LLVMModuleRef module;
+ int ret = 0;
char *file;
symlist = sparse_initialize(argc, argv, &filelist);
@@ -1383,8 +1391,10 @@ int main(int argc, char **argv)
FOR_EACH_PTR(filelist, file) {
symlist = sparse(file);
- if (die_if_error)
- return 1;
+ if (die_if_error) {
+ ret = 1;
+ goto out;
+ }
compile(module, symlist);
} END_FOR_EACH_PTR(file);
@@ -1392,8 +1402,9 @@ int main(int argc, char **argv)
LLVMWriteBitcodeToFD(module, STDOUT_FILENO, 0, 0);
+out:
LLVMDisposeModule(module);
report_stats();
- return 0;
+ return ret;
}
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 18/20] parse: Accept an empty attribute argument list
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (16 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 17/20] llvm: Free leaked allocations and LLVM objects Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 19/20] llvm: Use explicit LLVMContextRef instead of global context Bart Van Assche
2026-10-19 17:56 ` [PATCH 20/20] ci: Add a GitHub Actions workflow and CI scripts Bart Van Assche
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche, Dmitry Ilvokhin, Dan Carpenter
From: Dmitry Ilvokhin <d@ilvokhin.com>
GCC and clang accept an empty argument list for attributes, e.g.
__attribute__((nonnull())), and treat it the same as the bare
__attribute__((nonnull)). sparse instead rejects it with
"an expression is expected before ')'".
ignore_attribute() and recover_unknown_attribute() consume the optional
argument list with parens_expression(), which requires a non-empty
expression, so an empty () is rejected. The bare form is fine because it
takes no parentheses and skips that path.
This turned up in the Linux kernel: the cleanup.h guard macros expand
__nonnull_args() to __attribute__((__nonnull__())).
https://lore.kernel.org/all/aiJi0WcYE8FZt-jO@stanley.mountain/
Reported-by: Dan Carpenter <error27@gmail.com>
Signed-off-by: Dmitry Ilvokhin <d@ilvokhin.com>
Tested-by: Dan Carpenter <error27@gmail.com>
[ bvanassche: added "parse" prefix to the patch subject ]
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
parse.c | 17 +++++++++++++----
validation/attribute-nonnull-empty.c | 12 ++++++++++++
2 files changed, 25 insertions(+), 4 deletions(-)
create mode 100644 validation/attribute-nonnull-empty.c
diff --git a/parse.c b/parse.c
index 0e3e384550c5..1a889ccb6a90 100644
--- a/parse.c
+++ b/parse.c
@@ -1106,8 +1106,13 @@ static struct token *autotype_specifier(struct token *token, struct symbol *sym,
static struct token *ignore_attribute(struct token *token, struct symbol *attr, struct decl_state *ctx)
{
struct expression *expr = NULL;
- if (match_op(token, '('))
- token = parens_expression(token, &expr, "in attribute");
+ if (match_op(token, '(')) {
+ /* Accept an empty argument list, e.g. __attribute__((nonnull())) */
+ if (match_op(token->next, ')'))
+ token = token->next->next;
+ else
+ token = parens_expression(token, &expr, "in attribute");
+ }
return token;
}
@@ -1362,8 +1367,12 @@ static struct token *recover_unknown_attribute(struct token *token)
if (Wunknown_attribute)
warning(token->pos, "unknown attribute '%s'", show_ident(token->ident));
token = token->next;
- if (match_op(token, '('))
- token = parens_expression(token, &expr, "in attribute");
+ if (match_op(token, '(')) {
+ if (match_op(token->next, ')'))
+ token = token->next->next;
+ else
+ token = parens_expression(token, &expr, "in attribute");
+ }
return token;
}
diff --git a/validation/attribute-nonnull-empty.c b/validation/attribute-nonnull-empty.c
new file mode 100644
index 000000000000..c4021f61db0e
--- /dev/null
+++ b/validation/attribute-nonnull-empty.c
@@ -0,0 +1,12 @@
+/*
+ * An empty attribute argument list, like __attribute__((nonnull())), is
+ * accepted by GCC and Clang (equivalent to the bare __attribute__((nonnull)))
+ * and must be parsed by sparse too.
+ */
+extern void *bare (void *d, const void *s) __attribute__((nonnull));
+extern void *empty(void *d, const void *s) __attribute__((nonnull()));
+extern void *args (void *d, const void *s) __attribute__((nonnull(1, 2)));
+
+/*
+ * check-name: attribute with empty argument list
+ */
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 19/20] llvm: Use explicit LLVMContextRef instead of global context
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (17 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 18/20] parse: Accept an empty attribute argument list Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 20/20] ci: Add a GitHub Actions workflow and CI scripts Bart Van Assche
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Create and dispose of an explicit LLVMContextRef using
LLVMContextCreate() and LLVMContextDispose(), and use the
LLVM*InContext() API variants for types and constants instead of relying
on the deprecated implicit global context.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
sparse-llvm.c | 73 ++++++++++++++++++++++++++++++---------------------
1 file changed, 43 insertions(+), 30 deletions(-)
diff --git a/sparse-llvm.c b/sparse-llvm.c
index 70e0ff6f58a4..facce6f745ff 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -26,6 +26,17 @@ struct function {
LLVMModuleRef module;
};
+static LLVMContextRef context;
+
+static inline LLVMValueRef const_string(const char *str, size_t len)
+{
+#if LLVM_VERSION_MAJOR > 18
+ return LLVMConstStringInContext2(context, str, len, true);
+#else
+ return LLVMConstStringInContext(context, str, len, true);
+#endif
+}
+
static LLVMTypeRef symbol_type(struct symbol *sym);
static LLVMTypeRef func_return_type(struct symbol *sym)
@@ -96,7 +107,7 @@ static LLVMTypeRef sym_struct_type(struct symbol *sym)
unsigned nr = 0;
snprintf(buffer, sizeof(buffer), "struct.%s", sym->ident ? sym->ident->name : "anno");
- ret = LLVMStructCreateNamed(LLVMGetGlobalContext(), buffer);
+ ret = LLVMStructCreateNamed(context, buffer);
/* set ->aux to avoid recursion */
sym->aux = ret;
@@ -126,9 +137,9 @@ static LLVMTypeRef sym_union_type(struct symbol *sym)
*/
union_size = sym->bit_size / 8;
- elements = LLVMArrayType(LLVMInt8Type(), union_size);
+ elements = LLVMArrayType(LLVMInt8TypeInContext(context), union_size);
- return LLVMStructType(&elements, 1, 0 /* packed? */);
+ return LLVMStructTypeInContext(context, &elements, 1, 0 /* packed? */);
}
static LLVMTypeRef sym_ptr_type(struct symbol *sym)
@@ -137,7 +148,7 @@ static LLVMTypeRef sym_ptr_type(struct symbol *sym)
/* 'void *' is treated like 'char *' */
if (is_void_type(sym->ctype.base_type))
- type = LLVMInt8Type();
+ type = LLVMInt8TypeInContext(context);
else
type = symbol_type(sym->ctype.base_type);
@@ -151,13 +162,13 @@ static LLVMTypeRef sym_basetype_type(struct symbol *sym)
if (is_float_type(sym)) {
switch (sym->bit_size) {
case 32:
- ret = LLVMFloatType();
+ ret = LLVMFloatTypeInContext(context);
break;
case 64:
- ret = LLVMDoubleType();
+ ret = LLVMDoubleTypeInContext(context);
break;
case 80:
- ret = LLVMX86FP80Type();
+ ret = LLVMX86FP80TypeInContext(context);
break;
default:
die("invalid bit size %d for type %d", sym->bit_size, sym->type);
@@ -166,22 +177,22 @@ static LLVMTypeRef sym_basetype_type(struct symbol *sym)
} else {
switch (sym->bit_size) {
case -1:
- ret = LLVMVoidType();
+ ret = LLVMVoidTypeInContext(context);
break;
case 1:
- ret = LLVMInt1Type();
+ ret = LLVMInt1TypeInContext(context);
break;
case 8:
- ret = LLVMInt8Type();
+ ret = LLVMInt8TypeInContext(context);
break;
case 16:
- ret = LLVMInt16Type();
+ ret = LLVMInt16TypeInContext(context);
break;
case 32:
- ret = LLVMInt32Type();
+ ret = LLVMInt32TypeInContext(context);
break;
case 64:
- ret = LLVMInt64Type();
+ ret = LLVMInt64TypeInContext(context);
break;
default:
die("invalid bit size %d for type %d", sym->bit_size, sym->type);
@@ -205,7 +216,7 @@ static LLVMTypeRef symbol_type(struct symbol *sym)
switch (sym->type) {
case SYM_BITFIELD:
- ret = LLVMIntType(sym->bit_size);
+ ret = LLVMIntTypeInContext(context, sym->bit_size);
break;
case SYM_RESTRICT:
case SYM_ENUM:
@@ -244,10 +255,10 @@ static LLVMTypeRef insn_symbol_type(struct instruction *insn)
return symbol_type(insn->type);
switch (insn->size) {
- case 8: return LLVMInt8Type();
- case 16: return LLVMInt16Type();
- case 32: return LLVMInt32Type();
- case 64: return LLVMInt64Type();
+ case 8: return LLVMInt8TypeInContext(context);
+ case 16: return LLVMInt16TypeInContext(context);
+ case 32: return LLVMInt32TypeInContext(context);
+ case 64: return LLVMInt64TypeInContext(context);
default:
die("invalid bit size %d", insn->size);
@@ -316,14 +327,14 @@ static LLVMValueRef get_sym_value(LLVMModuleRef module, struct symbol *sym)
const char *s = expr->string->data;
size_t len = expr->string->length;
#if LLVM_VERSION_MAJOR <= 14
- LLVMValueRef indices[] = { LLVMConstInt(LLVMInt64Type(), 0, 0), LLVMConstInt(LLVMInt64Type(), 0, 0) };
+ LLVMValueRef indices[] = { LLVMConstInt(LLVMInt64TypeInContext(context), 0, 0), LLVMConstInt(LLVMInt64TypeInContext(context), 0, 0) };
#endif
LLVMValueRef data;
- data = LLVMAddGlobal(module, LLVMArrayType(LLVMInt8Type(), len), ".str");
+ data = LLVMAddGlobal(module, LLVMArrayType(LLVMInt8TypeInContext(context), len), ".str");
LLVMSetLinkage(data, LLVMPrivateLinkage);
LLVMSetGlobalConstant(data, 1);
- LLVMSetInitializer(data, LLVMConstString(s, len, true));
+ LLVMSetInitializer(data, const_string(s, len));
#if LLVM_VERSION_MAJOR > 14
result = data;
@@ -357,7 +368,7 @@ static LLVMValueRef constant_value(unsigned long long val, LLVMTypeRef dtype)
switch (LLVMGetTypeKind(dtype)) {
case LLVMPointerTypeKind:
if (val != 0) { // for example: ... = (void*) 0x123;
- LLVMTypeRef itype = LLVMIntType(bits_in_pointer);
+ LLVMTypeRef itype = LLVMIntTypeInContext(context, bits_in_pointer);
result = LLVMConstInt(itype, val, 1);
result = LLVMConstIntToPtr(result, dtype);
} else {
@@ -443,7 +454,7 @@ static LLVMValueRef value_to_ivalue(struct function *fn, struct symbol *ctype, L
LLVMTypeRef dtype = symbol_type(ctype);
if (LLVMGetTypeKind(LLVMTypeOf(val)) == LLVMPointerTypeKind) {
- LLVMTypeRef dtype = LLVMIntType(bits_in_pointer);
+ LLVMTypeRef dtype = LLVMIntTypeInContext(context, bits_in_pointer);
val = LLVMBuildPtrToInt(fn->builder, val, dtype, name);
}
if (ctype && is_int_type(ctype)) {
@@ -505,7 +516,7 @@ static LLVMValueRef calc_gep(LLVMBuilderRef builder, LLVMValueRef base, LLVMValu
{
LLVMTypeRef type = LLVMTypeOf(base);
unsigned int as = LLVMGetPointerAddressSpace(type);
- LLVMTypeRef bytep = LLVMPointerType(LLVMInt8Type(), as);
+ LLVMTypeRef bytep = LLVMPointerType(LLVMInt8TypeInContext(context), as);
LLVMValueRef addr;
const char *name = LLVMGetValueName(off);
@@ -718,7 +729,7 @@ static LLVMValueRef calc_memop_addr(struct function *fn, struct instruction *ins
unsigned int as;
/* int type large enough to hold a pointer */
- int_type = LLVMIntType(bits_in_pointer);
+ int_type = LLVMIntTypeInContext(context, bits_in_pointer);
off = LLVMConstInt(int_type, insn->offset, 0);
/* convert src to the effective pointer type */
@@ -765,7 +776,7 @@ static void output_op_store(struct function *fn, struct instruction *insn)
static LLVMValueRef bool_value(struct function *fn, LLVMValueRef value)
{
- if (LLVMTypeOf(value) != LLVMInt1Type())
+ if (LLVMTypeOf(value) != LLVMInt1TypeInContext(context))
value = LLVMBuildIsNotNull(fn->builder, value, LLVMGetValueName(value));
return value;
@@ -1171,7 +1182,7 @@ static void output_fn(LLVMModuleRef module, struct entrypoint *ep)
LLVMSetFunctionCallConv(function.fn, LLVMCCallConv);
LLVMSetLinkage(function.fn, function_linkage(sym));
- function.builder = LLVMCreateBuilder();
+ function.builder = LLVMCreateBuilderInContext(context);
/* give a name to each argument */
nr_args = symbol_list_size(base_type->arguments);
@@ -1191,7 +1202,7 @@ static void output_fn(LLVMModuleRef module, struct entrypoint *ep)
char bbname[32];
sprintf(bbname, "L%d", nr_bb++);
- bbr = LLVMAppendBasicBlock(function.fn, bbname);
+ bbr = LLVMAppendBasicBlockInContext(context, function.fn, bbname);
bb->priv = bbr;
}
@@ -1259,7 +1270,7 @@ static LLVMValueRef output_data(LLVMModuleRef module, struct symbol *sym)
const char *s = initializer->string->data;
size_t len = initializer->string->length;
- initial_value = LLVMConstString(s, len, true);
+ initial_value = const_string(s, len);
break;
}
default:
@@ -1384,7 +1395,8 @@ int main(int argc, char **argv)
symlist = sparse_initialize(argc, argv, &filelist);
- module = LLVMModuleCreateWithName("sparse");
+ context = LLVMContextCreate();
+ module = LLVMModuleCreateWithNameInContext("sparse", context);
set_target(module);
compile(module, symlist);
@@ -1404,6 +1416,7 @@ int main(int argc, char **argv)
out:
LLVMDisposeModule(module);
+ LLVMContextDispose(context);
report_stats();
return ret;
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 20/20] ci: Add a GitHub Actions workflow and CI scripts
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
` (18 preceding siblings ...)
2026-10-19 17:56 ` [PATCH 19/20] llvm: Use explicit LLVMContextRef instead of global context Bart Van Assche
@ 2026-10-19 17:56 ` Bart Van Assche
19 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-19 17:56 UTC (permalink / raw)
To: linux-sparse; +Cc: Bart Van Assche
Add a GitHub Actions workflow and supporting scripts to build and test
sparse automatically. Catch regressions in sparse and use
AddressSanitizer and UndefinedBehaviorSanitizer to detect memory errors
and undefined behavior.
- .github/workflows/build.yml: Build and test sparse on ubuntu-latest in
regular and asan mode. Run on push and pull request events and allow
manual runs via workflow_dispatch.
- ci/install.sh: Install the packages required to build and test sparse.
- ci/build.sh: Build sparse with parallel make. If MODE=asan, pass ASan
and UBSan compiler and linker flags. Fail for unknown modes.
- ci/test.sh: Run the sparse test suite via 'make check'.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
.github/workflows/build.yml | 36 ++++++++++++++++++++++++++++++++++++
ci/build.sh | 22 ++++++++++++++++++++++
ci/install.sh | 24 ++++++++++++++++++++++++
ci/test.sh | 3 +++
4 files changed, 85 insertions(+)
create mode 100644 .github/workflows/build.yml
create mode 100755 ci/build.sh
create mode 100755 ci/install.sh
create mode 100755 ci/test.sh
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000000..62d9512fdf9e
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,36 @@
+# See also https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
+
+name: Build
+
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ mode: regular
+ - os: ubuntu-latest
+ mode: asan
+
+ env:
+ MODE: ${{ matrix.mode }}
+
+ steps:
+ - uses: actions/checkout@main
+
+ - name: install
+ run: sudo bash -c "MODE=$MODE ci/install.sh"
+
+ - name: build
+ run: ci/build.sh
+
+ - name: test
+ run: ci/test.sh
diff --git a/ci/build.sh b/ci/build.sh
new file mode 100755
index 000000000000..a155671dbb8b
--- /dev/null
+++ b/ci/build.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+set -eu
+
+scriptdir="$(dirname "$0")"
+
+echo "compiler path: $(type -p "${CC:-gcc}")"
+nproc=$(nproc)
+case "$MODE" in
+ asan)
+ ASAN_FLAGS="-fsanitize=address,pointer-compare,pointer-subtract,undefined"
+ CFLAGS="-O1 -g ${ASAN_FLAGS} -fno-omit-frame-pointer"
+ LDFLAGS="${ASAN_FLAGS}"
+ make -s -j"${nproc}" CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}"
+ ;;
+ regular)
+ make -s -j"${nproc}"
+ ;;
+ *)
+ exit 1
+ ;;
+esac
diff --git a/ci/install.sh b/ci/install.sh
new file mode 100755
index 000000000000..7024c3812fab
--- /dev/null
+++ b/ci/install.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -eu
+
+packages=(
+ bc
+ bison
+ build-essential
+ dwarves
+ flex
+ libelf-dev
+ libgtk-3-dev
+ libsqlite3-dev
+ libssl-dev
+ llvm-dev
+ make
+ pahole
+ perl
+ pkg-config
+)
+apt-get update
+for p in "${packages[@]}"; do
+ apt-get install -qq -o=Dpkg::Use-Pty=0 -y "$p"
+done
diff --git a/ci/test.sh b/ci/test.sh
new file mode 100755
index 000000000000..a057cba051d0
--- /dev/null
+++ b/ci/test.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+make check
^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH 02/20] ptrlist: Guard against null list in ptrlist operations
2026-10-19 17:56 ` [PATCH 02/20] ptrlist: Guard against null list in ptrlist operations Bart Van Assche
@ 2026-10-21 13:02 ` Dmitry Ilvokhin
0 siblings, 0 replies; 26+ messages in thread
From: Dmitry Ilvokhin @ 2026-10-21 13:02 UTC (permalink / raw)
To: Bart Van Assche; +Cc: linux-sparse
On Mon, Oct 19, 2026 at 10:56:15AM -0700, Bart Van Assche wrote:
> Guard against null list pointers before accessing list->rm in ptrlist.h
> and ptrlist.c to eliminate undefined behavior and UBSan null member
> access traps.
>
> Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 03/20] bits: Guard against out-of-bounds bit shifts in bitmask helpers
2026-10-19 17:56 ` [PATCH 03/20] bits: Guard against out-of-bounds bit shifts in bitmask helpers Bart Van Assche
@ 2026-10-21 13:22 ` Dmitry Ilvokhin
0 siblings, 0 replies; 26+ messages in thread
From: Dmitry Ilvokhin @ 2026-10-21 13:22 UTC (permalink / raw)
To: Bart Van Assche; +Cc: linux-sparse
On Mon, Oct 19, 2026 at 10:56:16AM -0700, Bart Van Assche wrote:
> Clamp bit widths to [0, 64] in bits.h and fix 64-bit shift calculations
> in bitmap.h to avoid undefined shift behavior on 64-bit integers.
>
> Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 04/20] storage: Free hash table entries when freeing storage
2026-10-19 17:56 ` [PATCH 04/20] storage: Free hash table entries when freeing storage Bart Van Assche
@ 2026-10-21 13:50 ` Dmitry Ilvokhin
2026-10-21 16:21 ` Bart Van Assche
0 siblings, 1 reply; 26+ messages in thread
From: Dmitry Ilvokhin @ 2026-10-21 13:50 UTC (permalink / raw)
To: Bart Van Assche; +Cc: linux-sparse
On Mon, Oct 19, 2026 at 10:56:17AM -0700, Bart Van Assche wrote:
> Free allocated hash table entries when freeing storage structures in
> storage.c to prevent memory leaks during storage teardown.
>
> Signed-off-by: Bart Van Assche <bvanassche@acm.org>
> ---
> storage.c | 10 ++++++++--
> 1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/storage.c b/storage.c
> index 6fc6e3d7085f..160d424ee1f6 100644
> --- a/storage.c
> +++ b/storage.c
> @@ -159,12 +159,18 @@ static void vrfy_storage(struct storage_hash_list **listp)
>
> void free_storage(void)
> {
> + struct storage_hash_list *all = NULL;
> int i;
>
> for (i = 0; i < MAX_STORAGE_HASH; i++) {
> - vrfy_storage(storage_hash_table + i);
> + struct storage_hash *entry;
> + FOR_EACH_PTR(storage_hash_table[i], entry) {
> + add_ptr_list(&all, entry);
> + } END_FOR_EACH_PTR(entry);
> free_ptr_list(storage_hash_table + i);
> }
> + vrfy_storage(&all);
> + free_ptr_list(&all);
> }
This seems like a behaviour change (a correct one, as far as I can
tell), that is not mentioned in the changelog. Before verification was
done for each bucket independently and could miss conflicts, while now
verification is done globally.
Does it worth to split this hunk it is own commit with the proper
description?
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 05/20] builtin: Bound argument array iteration in declare_builtin()
2026-10-19 17:56 ` [PATCH 05/20] builtin: Bound argument array iteration in declare_builtin() Bart Van Assche
@ 2026-10-21 13:54 ` Dmitry Ilvokhin
0 siblings, 0 replies; 26+ messages in thread
From: Dmitry Ilvokhin @ 2026-10-21 13:54 UTC (permalink / raw)
To: Bart Van Assche; +Cc: linux-sparse
On Mon, Oct 19, 2026 at 10:56:18AM -0700, Bart Van Assche wrote:
> Bound the loop over entry->args[] in declare_builtin() with
> ARRAY_SIZE(entry->args) before reading entry->args[i], and remove the
> sentinel member _args_null_tail from struct builtin_fn.
>
> Prevent out-of-bounds array reads past the end of struct builtin_fn's
> six-element args[] array when declaring six-argument builtins such as
> __atomic_compare_exchange_n, avoiding AddressSanitizer bounds errors.
>
> Fixes: 935cf032524f ("builtin: use a table for the builtins")
> Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 04/20] storage: Free hash table entries when freeing storage
2026-10-21 13:50 ` Dmitry Ilvokhin
@ 2026-10-21 16:21 ` Bart Van Assche
0 siblings, 0 replies; 26+ messages in thread
From: Bart Van Assche @ 2026-10-21 16:21 UTC (permalink / raw)
To: Dmitry Ilvokhin; +Cc: linux-sparse
On 10/21/26 6:50 AM, Dmitry Ilvokhin wrote:
> On Mon, Oct 19, 2026 at 10:56:17AM -0700, Bart Van Assche wrote:
>> Free allocated hash table entries when freeing storage structures in
>> storage.c to prevent memory leaks during storage teardown.
>>
>> Signed-off-by: Bart Van Assche <bvanassche@acm.org>
>> ---
>> storage.c | 10 ++++++++--
>> 1 file changed, 8 insertions(+), 2 deletions(-)
>>
>> diff --git a/storage.c b/storage.c
>> index 6fc6e3d7085f..160d424ee1f6 100644
>> --- a/storage.c
>> +++ b/storage.c
>> @@ -159,12 +159,18 @@ static void vrfy_storage(struct storage_hash_list **listp)
>>
>> void free_storage(void)
>> {
>> + struct storage_hash_list *all = NULL;
>> int i;
>>
>> for (i = 0; i < MAX_STORAGE_HASH; i++) {
>> - vrfy_storage(storage_hash_table + i);
>> + struct storage_hash *entry;
>> + FOR_EACH_PTR(storage_hash_table[i], entry) {
>> + add_ptr_list(&all, entry);
>> + } END_FOR_EACH_PTR(entry);
>> free_ptr_list(storage_hash_table + i);
>> }
>> + vrfy_storage(&all);
>> + free_ptr_list(&all);
>> }
>
> This seems like a behaviour change (a correct one, as far as I can
> tell), that is not mentioned in the changelog. Before verification was
> done for each bucket independently and could miss conflicts, while now
> verification is done globally.
>
> Does it worth to split this hunk it is own commit with the proper
> description?
Does replacing the above patch with the two patches below address your
feedback?
Thanks,
Bart.
----------------------------------------------------------------------
From: Bart Van Assche <bvanassche@acm.org>
Date: Wed, 21 Oct 2026 09:04:56 -0700
Subject: [PATCH 1/2] storage: Verify storage conflicts across all hash
buckets
The storage hash table is indexed by a hash of (bb, pseudo, inout).
vrfy_storage() reports a conflict if two different pseudos in the same
basic block and with the same direction have been assigned the same
storage. Since such entries have a different pseudo, they typically
end up in different hash buckets. Hence, calling vrfy_storage() for
each bucket separately misses most conflicts.
Collect all hash table entries into a single list before calling
vrfy_storage() such that conflicts are detected independent of the
hash bucket an entry has been stored in.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
storage.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/storage.c b/storage.c
index 6fc6e3d7085f..d99d3af2ec18 100644
--- a/storage.c
+++ b/storage.c
@@ -159,12 +159,18 @@ static void vrfy_storage(struct storage_hash_list
**listp)
void free_storage(void)
{
+ struct storage_hash_list *all = NULL;
int i;
for (i = 0; i < MAX_STORAGE_HASH; i++) {
- vrfy_storage(storage_hash_table + i);
+ struct storage_hash *entry;
+ FOR_EACH_PTR(storage_hash_table[i], entry) {
+ add_ptr_list(&all, entry);
+ } END_FOR_EACH_PTR(entry);
free_ptr_list(storage_hash_table + i);
}
+ vrfy_storage(&all);
+ free_ptr_list(&all);
}
const char *show_storage(struct storage *s)
----------------------------------------------------------------------
From: Bart Van Assche <bvanassche@acm.org>
Date: Wed, 21 Oct 2026 09:04:57 -0700
Subject: [PATCH 2/2] storage: Free the users list in combine_storage()
combine_storage() marks the source storage as unused by setting its
users list pointer to NULL. This leaks the memory allocated for that
list. Free the list instead of only clearing the pointer.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
storage.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/storage.c b/storage.c
index d99d3af2ec18..160d424ee1f6 100644
--- a/storage.c
+++ b/storage.c
@@ -214,7 +214,7 @@ static struct storage * combine_storage(struct
storage *src, struct storage *dst
/* Mark it unused */
src->type = REG_BAD;
- src->users = NULL;
+ free_ptr_list(&src->users);
return dst;
}
----------------------------------------------------------------------
^ permalink raw reply related [flat|nested] 26+ messages in thread
end of thread, other threads:[~2026-10-21 16:21 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19 17:56 [PATCH 00/20] Sanitizer fixes, LLVM updates, and CI workflow Bart Van Assche
2026-10-19 17:56 ` [PATCH 01/20] doc: Update maintainer and mirror information Bart Van Assche
2026-10-19 17:56 ` [PATCH 02/20] ptrlist: Guard against null list in ptrlist operations Bart Van Assche
2026-10-21 13:02 ` Dmitry Ilvokhin
2026-10-19 17:56 ` [PATCH 03/20] bits: Guard against out-of-bounds bit shifts in bitmask helpers Bart Van Assche
2026-10-21 13:22 ` Dmitry Ilvokhin
2026-10-19 17:56 ` [PATCH 04/20] storage: Free hash table entries when freeing storage Bart Van Assche
2026-10-21 13:50 ` Dmitry Ilvokhin
2026-10-21 16:21 ` Bart Van Assche
2026-10-19 17:56 ` [PATCH 05/20] builtin: Bound argument array iteration in declare_builtin() Bart Van Assche
2026-10-21 13:54 ` Dmitry Ilvokhin
2026-10-19 17:56 ` [PATCH 06/20] c2xml: Use an unsigned shift in examine_modifiers() Bart Van Assche
2026-10-19 17:56 ` [PATCH 07/20] parse: Avoid incrementing rank on invalid long real specifiers Bart Van Assche
2026-10-19 17:56 ` [PATCH 08/20] pre-process: Avoid zero-length VLAs for parameterless macros Bart Van Assche
2026-10-19 17:56 ` [PATCH 09/20] pre-process: Avoid reading token->string on non-string tokens Bart Van Assche
2026-10-19 17:56 ` [PATCH 10/20] target: Avoid signed overflow in bit and byte conversion helpers Bart Van Assche
2026-10-19 17:56 ` [PATCH 11/20] evaluate: Use bitmask helpers in evaluate_ptr_add() Bart Van Assche
2026-10-19 17:56 ` [PATCH 12/20] linearize: Use bits_mask() in linearize_bitfield_insert() Bart Van Assche
2026-10-19 17:56 ` [PATCH 13/20] expand: Avoid undefined shifts and signed overflow in constant folding Bart Van Assche
2026-10-19 17:56 ` [PATCH 14/20] simplify: Avoid undefined shifts and signed overflow in simplification Bart Van Assche
2026-10-19 17:56 ` [PATCH 15/20] sparsec: Emit object files directly with llc -filetype=obj Bart Van Assche
2026-10-19 17:56 ` [PATCH 16/20] llvm: Stop calling LLVMConstGEP2() for string literals Bart Van Assche
2026-10-19 17:56 ` [PATCH 17/20] llvm: Free leaked allocations and LLVM objects Bart Van Assche
2026-10-19 17:56 ` [PATCH 18/20] parse: Accept an empty attribute argument list Bart Van Assche
2026-10-19 17:56 ` [PATCH 19/20] llvm: Use explicit LLVMContextRef instead of global context Bart Van Assche
2026-10-19 17:56 ` [PATCH 20/20] ci: Add a GitHub Actions workflow and CI scripts 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