https://github.com/gamesh411 updated https://github.com/llvm/llvm-project/pull/188709
From fe7730879969ebd71ea569605a81cc97ffab1f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <[email protected]> Date: Tue, 24 Mar 2026 12:05:52 +0100 Subject: [PATCH 1/2] [analyzer][NFC] Reorganize bstring.cpp tests This change eliminates preprocessor-based suppression of test cases, and moves special cases to separate test files. --- .../test/Analysis/bstring-oob-suppressed.cpp | 92 ++++++++++++++++ clang/test/Analysis/bstring-uninit-only.cpp | 23 ++++ clang/test/Analysis/bstring.cpp | 100 +----------------- 3 files changed, 120 insertions(+), 95 deletions(-) create mode 100644 clang/test/Analysis/bstring-oob-suppressed.cpp create mode 100644 clang/test/Analysis/bstring-uninit-only.cpp diff --git a/clang/test/Analysis/bstring-oob-suppressed.cpp b/clang/test/Analysis/bstring-oob-suppressed.cpp new file mode 100644 index 0000000000000..8eb9ad7a08dff --- /dev/null +++ b/clang/test/Analysis/bstring-oob-suppressed.cpp @@ -0,0 +1,92 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=unix.cstring \ +// RUN: -analyzer-checker=unix.Malloc \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap \ +// RUN: -analyzer-checker=unix.cstring.NotNullTerminated \ +// RUN: -verify %s + +// These tests exercise memset calls that go out-of-bounds. They are separated +// from bstring.cpp because they require OutOfBounds to be disabled: with +// OutOfBounds enabled, the analysis sinks at the OOB memset and the subsequent +// clang_analyzer_eval calls are not reached. +// +// FIXME: These tests document the analyzer's current behavior when OutOfBounds +// is disabled and OOB memset calls don't terminate the path. Once the +// OutOfBounds checker is stable enough to always terminate execution at OOB +// errors (regardless of whether the checker frontend is enabled), these tests +// should be revisited: the clang_analyzer_eval calls after the OOB memset +// would become unreachable. + +#include "Inputs/system-header-simulator-cxx.h" + +void clang_analyzer_eval(int); +void *memset(void *dest, int ch, std::size_t count); + +namespace memset_non_pod { +class Base { +public: + int b_mem; + Base() : b_mem(1) {} +}; + +class Derived : public Base { +public: + int d_mem; + Derived() : d_mem(2) {} +}; + +void memset2_inheritance_field() { + Derived d; + // FIXME: OOB memset on a derived field with sizeof(Derived). + // Current behavior: the not-set part is treated as UNKNOWN. + memset(&d.d_mem, 0, sizeof(Derived)); + clang_analyzer_eval(d.b_mem == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(d.d_mem == 0); // expected-warning{{UNKNOWN}} +} + +void memset3_inheritance_field() { + Derived d; + // FIXME: memset on the base field with sizeof(Derived). This doesn't + // actually write past the object's extent, but it's UB because the memset + // accesses the object through a pointer to a member, violating aliasing + // rules. Current behavior: the field is treated as correctly set to 0. + memset(&d.b_mem, 0, sizeof(Derived)); + clang_analyzer_eval(d.b_mem == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(d.d_mem == 0); // expected-warning{{TRUE}} +} + +class BaseVirtual { +public: + int b_mem; + virtual int get() { return 1; } +}; + +class DerivedVirtual : public BaseVirtual { +public: + int d_mem; +}; + +void memset8_virtual_inheritance_field() { + DerivedVirtual d; + // FIXME: Same as memset3, but the base has a virtual function. In typical + // implementations &d.b_mem differs from &d because the vtable pointer + // precedes the first member, so this may also write past the object's + // extent. + memset(&d.b_mem, 0, sizeof(Derived)); + clang_analyzer_eval(d.b_mem == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(d.d_mem == 0); // expected-warning{{UNKNOWN}} +} +} // namespace memset_non_pod + +void memset1_new_array() { + int *array = new int[10]; + memset(array, 0, 10 * sizeof(int)); + clang_analyzer_eval(array[2] == 0); // expected-warning{{TRUE}} + // FIXME: OOB memset on a heap array. The analysis continues past it. + memset(array + 1, 'a', 10 * sizeof(9)); + clang_analyzer_eval(array[2] == 0); // expected-warning{{UNKNOWN}} + delete[] array; +} diff --git a/clang/test/Analysis/bstring-uninit-only.cpp b/clang/test/Analysis/bstring-uninit-only.cpp new file mode 100644 index 0000000000000..fa78ccc2f4c26 --- /dev/null +++ b/clang/test/Analysis/bstring-uninit-only.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=unix.cstring \ +// RUN: -analyzer-checker=unix.Malloc \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-checker=alpha.unix.cstring.UninitializedRead \ +// RUN: -verify %s + +// This test verifies that UninitializedRead produces warnings even when +// OutOfBounds is disabled. Previously, CheckBufferAccess would early-return +// before reaching checkInit() when OutOfBounds was disabled, suppressing +// UninitializedRead as a side effect. + +#include "Inputs/system-header-simulator-cxx.h" +#include "Inputs/system-header-simulator-for-malloc.h" + +void memmove_uninit_without_outofbound() { + int src[4]; + int dst[4]; + memmove(dst, src, sizeof(src)); // expected-warning{{The first element of the 2nd argument is undefined}} + // expected-note@-1{{Other elements might also be undefined}} +} diff --git a/clang/test/Analysis/bstring.cpp b/clang/test/Analysis/bstring.cpp index 732b36a92ac5a..dabd53d48cb4b 100644 --- a/clang/test/Analysis/bstring.cpp +++ b/clang/test/Analysis/bstring.cpp @@ -3,28 +3,14 @@ // DEFINE: -analyzer-checker=unix.cstring \ // DEFINE: -analyzer-checker=unix.Malloc \ // DEFINE: -analyzer-checker=debug.ExprInspection \ +// DEFINE: -analyzer-checker=alpha.unix.cstring \ // DEFINE: -analyzer-config eagerly-assume=false \ // DEFINE: -verify %s -// RUN: %{analyzer} \ -// RUN: -analyzer-checker=alpha.unix.cstring - -// RUN: %{analyzer} -DUSE_BUILTINS \ -// RUN: -analyzer-checker=alpha.unix.cstring - -// RUN: %{analyzer} -DVARIANT \ -// RUN: -analyzer-checker=alpha.unix.cstring - -// RUN: %{analyzer} -DUSE_BUILTINS -DVARIANT \ -// RUN: -analyzer-checker=alpha.unix.cstring - -// RUN: %{analyzer} -DSUPPRESS_OUT_OF_BOUND \ -// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap \ -// RUN: -analyzer-checker=unix.cstring.NotNullTerminated - -// RUN: %{analyzer} \ -// RUN: -DUNINIT_WITHOUT_OUTOFBOUND \ -// RUN: -analyzer-checker=alpha.unix.cstring.UninitializedRead +// RUN: %{analyzer} +// RUN: %{analyzer} -DUSE_BUILTINS +// RUN: %{analyzer} -DVARIANT +// RUN: %{analyzer} -DUSE_BUILTINS -DVARIANT #include "Inputs/system-header-simulator-cxx.h" #include "Inputs/system-header-simulator-for-malloc.h" @@ -122,33 +108,6 @@ void memset1_inheritance() { clang_analyzer_eval(d.d_mem == 0); // expected-warning{{TRUE}} } -#ifdef SUPPRESS_OUT_OF_BOUND -void memset2_inheritance_field() { - Derived d; - // FIXME: This example wrongly calls `memset` on the derived field, with the - // size parameter that has the size of the whole derived class. The analysis - // should stop at that point as this is UB. - // This test asserts the current behavior of treating the not set part as - // UNKNOWN. - memset(&d.d_mem, 0, sizeof(Derived)); - clang_analyzer_eval(d.b_mem == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(d.d_mem == 0); // expected-warning{{UNKNOWN}} -} - -void memset3_inheritance_field() { - Derived d; - // FIXME: Here we are setting the field of the base with the size of the - // Derived class. By the letter of the standard this is UB, but practically - // this only touches memory it is supposed to with the above class - // definitions. If we were to be strict the analysis should stop here. - // This test asserts the current behavior of nevertheless treating the - // wrongly set field as correctly set to 0. - memset(&d.b_mem, 0, sizeof(Derived)); - clang_analyzer_eval(d.b_mem == 0); // expected-warning{{TRUE}} - clang_analyzer_eval(d.d_mem == 0); // expected-warning{{TRUE}} -} -#endif - void memset4_array_nonpod_object() { Derived array[10]; clang_analyzer_eval(array[1].b_mem == 1); // expected-warning{{UNKNOWN}} @@ -195,53 +154,4 @@ void memset7_placement_new() { clang_analyzer_eval(d->d_mem == 0); // expected-warning{{TRUE}} } -class BaseVirtual { -public: - int b_mem; - virtual int get() { return 1; } -}; - -class DerivedVirtual : public BaseVirtual { -public: - int d_mem; -}; - -#ifdef SUPPRESS_OUT_OF_BOUND -void memset8_virtual_inheritance_field() { - DerivedVirtual d; - // FIXME: This example wrongly calls `memset` on the derived field, with the - // size parameter that has the size of the whole derived class. The analysis - // should stop at that point as this is UB. The situation is further - // complicated by the fact the base base a virtual function. - // This test asserts the current behavior of treating the not set part as - // UNKNOWN. - memset(&d.b_mem, 0, sizeof(Derived)); - clang_analyzer_eval(d.b_mem == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(d.d_mem == 0); // expected-warning{{UNKNOWN}} -} -#endif } // namespace memset_non_pod - -#ifdef SUPPRESS_OUT_OF_BOUND -void memset1_new_array() { - int *array = new int[10]; - memset(array, 0, 10 * sizeof(int)); - clang_analyzer_eval(array[2] == 0); // expected-warning{{TRUE}} - // FIXME: The analyzer should stop analysis after memset. Maybe the intent of - // this test was to test for this as a desired behaviour, but it shouldn't be. - // Going out-of-bounds with memset is a fatal error, even if we decide not to - // report it. - memset(array + 1, 'a', 10 * sizeof(9)); - clang_analyzer_eval(array[2] == 0); // expected-warning{{UNKNOWN}} - delete[] array; -} -#endif - -#ifdef UNINIT_WITHOUT_OUTOFBOUND -void memmove_uninit_without_outofbound() { - int src[4]; - int dst[4]; - memmove(dst, src, sizeof(src)); // expected-warning{{The first element of the 2nd argument is undefined}} - // expected-note@-1{{Other elements might also be undefined}} -} -#endif From fea51fb0fc88e4242cf16b64dc0bbfb234ec5321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <[email protected]> Date: Mon, 30 Mar 2026 16:14:23 +0200 Subject: [PATCH 2/2] use multi-prefix verify instead of file splitting This eliminates all preprocessor-based test suppression (SUPPRESS_OUT_OF_BOUND and UNINIT_WITHOUT_OUTOFBOUND) without introducing new files. Three verify prefix groups are used: - 'oob': expectations only checked when OutOfBounds is enabled (sinks) - 'no-oob': expectations only checked when OutOfBounds is disabled - 'uninit': expectations only checked when UninitializedRead is enabled --- .../test/Analysis/bstring-oob-suppressed.cpp | 92 --------------- clang/test/Analysis/bstring-uninit-only.cpp | 23 ---- clang/test/Analysis/bstring.cpp | 109 ++++++++++++++++-- 3 files changed, 101 insertions(+), 123 deletions(-) delete mode 100644 clang/test/Analysis/bstring-oob-suppressed.cpp delete mode 100644 clang/test/Analysis/bstring-uninit-only.cpp diff --git a/clang/test/Analysis/bstring-oob-suppressed.cpp b/clang/test/Analysis/bstring-oob-suppressed.cpp deleted file mode 100644 index 8eb9ad7a08dff..0000000000000 --- a/clang/test/Analysis/bstring-oob-suppressed.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core \ -// RUN: -analyzer-checker=unix.cstring \ -// RUN: -analyzer-checker=unix.Malloc \ -// RUN: -analyzer-checker=debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false \ -// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap \ -// RUN: -analyzer-checker=unix.cstring.NotNullTerminated \ -// RUN: -verify %s - -// These tests exercise memset calls that go out-of-bounds. They are separated -// from bstring.cpp because they require OutOfBounds to be disabled: with -// OutOfBounds enabled, the analysis sinks at the OOB memset and the subsequent -// clang_analyzer_eval calls are not reached. -// -// FIXME: These tests document the analyzer's current behavior when OutOfBounds -// is disabled and OOB memset calls don't terminate the path. Once the -// OutOfBounds checker is stable enough to always terminate execution at OOB -// errors (regardless of whether the checker frontend is enabled), these tests -// should be revisited: the clang_analyzer_eval calls after the OOB memset -// would become unreachable. - -#include "Inputs/system-header-simulator-cxx.h" - -void clang_analyzer_eval(int); -void *memset(void *dest, int ch, std::size_t count); - -namespace memset_non_pod { -class Base { -public: - int b_mem; - Base() : b_mem(1) {} -}; - -class Derived : public Base { -public: - int d_mem; - Derived() : d_mem(2) {} -}; - -void memset2_inheritance_field() { - Derived d; - // FIXME: OOB memset on a derived field with sizeof(Derived). - // Current behavior: the not-set part is treated as UNKNOWN. - memset(&d.d_mem, 0, sizeof(Derived)); - clang_analyzer_eval(d.b_mem == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(d.d_mem == 0); // expected-warning{{UNKNOWN}} -} - -void memset3_inheritance_field() { - Derived d; - // FIXME: memset on the base field with sizeof(Derived). This doesn't - // actually write past the object's extent, but it's UB because the memset - // accesses the object through a pointer to a member, violating aliasing - // rules. Current behavior: the field is treated as correctly set to 0. - memset(&d.b_mem, 0, sizeof(Derived)); - clang_analyzer_eval(d.b_mem == 0); // expected-warning{{TRUE}} - clang_analyzer_eval(d.d_mem == 0); // expected-warning{{TRUE}} -} - -class BaseVirtual { -public: - int b_mem; - virtual int get() { return 1; } -}; - -class DerivedVirtual : public BaseVirtual { -public: - int d_mem; -}; - -void memset8_virtual_inheritance_field() { - DerivedVirtual d; - // FIXME: Same as memset3, but the base has a virtual function. In typical - // implementations &d.b_mem differs from &d because the vtable pointer - // precedes the first member, so this may also write past the object's - // extent. - memset(&d.b_mem, 0, sizeof(Derived)); - clang_analyzer_eval(d.b_mem == 0); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(d.d_mem == 0); // expected-warning{{UNKNOWN}} -} -} // namespace memset_non_pod - -void memset1_new_array() { - int *array = new int[10]; - memset(array, 0, 10 * sizeof(int)); - clang_analyzer_eval(array[2] == 0); // expected-warning{{TRUE}} - // FIXME: OOB memset on a heap array. The analysis continues past it. - memset(array + 1, 'a', 10 * sizeof(9)); - clang_analyzer_eval(array[2] == 0); // expected-warning{{UNKNOWN}} - delete[] array; -} diff --git a/clang/test/Analysis/bstring-uninit-only.cpp b/clang/test/Analysis/bstring-uninit-only.cpp deleted file mode 100644 index fa78ccc2f4c26..0000000000000 --- a/clang/test/Analysis/bstring-uninit-only.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core \ -// RUN: -analyzer-checker=unix.cstring \ -// RUN: -analyzer-checker=unix.Malloc \ -// RUN: -analyzer-checker=debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false \ -// RUN: -analyzer-checker=alpha.unix.cstring.UninitializedRead \ -// RUN: -verify %s - -// This test verifies that UninitializedRead produces warnings even when -// OutOfBounds is disabled. Previously, CheckBufferAccess would early-return -// before reaching checkInit() when OutOfBounds was disabled, suppressing -// UninitializedRead as a side effect. - -#include "Inputs/system-header-simulator-cxx.h" -#include "Inputs/system-header-simulator-for-malloc.h" - -void memmove_uninit_without_outofbound() { - int src[4]; - int dst[4]; - memmove(dst, src, sizeof(src)); // expected-warning{{The first element of the 2nd argument is undefined}} - // expected-note@-1{{Other elements might also be undefined}} -} diff --git a/clang/test/Analysis/bstring.cpp b/clang/test/Analysis/bstring.cpp index dabd53d48cb4b..9f044c6453739 100644 --- a/clang/test/Analysis/bstring.cpp +++ b/clang/test/Analysis/bstring.cpp @@ -3,14 +3,29 @@ // DEFINE: -analyzer-checker=unix.cstring \ // DEFINE: -analyzer-checker=unix.Malloc \ // DEFINE: -analyzer-checker=debug.ExprInspection \ -// DEFINE: -analyzer-checker=alpha.unix.cstring \ -// DEFINE: -analyzer-config eagerly-assume=false \ -// DEFINE: -verify %s - -// RUN: %{analyzer} -// RUN: %{analyzer} -DUSE_BUILTINS -// RUN: %{analyzer} -DVARIANT -// RUN: %{analyzer} -DUSE_BUILTINS -DVARIANT +// DEFINE: -analyzer-config eagerly-assume=false + +// All alpha.unix.cstring subcheckers enabled (OutOfBounds sinks at OOB memset). +// RUN: %{analyzer} -analyzer-checker=alpha.unix.cstring \ +// RUN: -verify=expected,oob,uninit %s +// RUN: %{analyzer} -analyzer-checker=alpha.unix.cstring \ +// RUN: -DUSE_BUILTINS -verify=expected,oob,uninit %s +// RUN: %{analyzer} -analyzer-checker=alpha.unix.cstring \ +// RUN: -DVARIANT -verify=expected,oob,uninit %s +// RUN: %{analyzer} -analyzer-checker=alpha.unix.cstring \ +// RUN: -DUSE_BUILTINS -DVARIANT -verify=expected,oob,uninit %s + +// OutOfBounds disabled: OOB memset doesn't sink, analysis continues. +// RUN: %{analyzer} \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap \ +// RUN: -analyzer-checker=unix.cstring.NotNullTerminated \ +// RUN: -verify=expected,no-oob %s + +// UninitializedRead enabled without OutOfBounds: verifies that +// UninitializedRead works independently of OutOfBounds. +// RUN: %{analyzer} \ +// RUN: -analyzer-checker=alpha.unix.cstring.UninitializedRead \ +// RUN: -verify=expected,no-oob,uninit %s #include "Inputs/system-header-simulator-cxx.h" #include "Inputs/system-header-simulator-for-malloc.h" @@ -108,6 +123,33 @@ void memset1_inheritance() { clang_analyzer_eval(d.d_mem == 0); // expected-warning{{TRUE}} } +void memset2_inheritance_field() { + Derived d; + // FIXME: OOB memset on a derived field with sizeof(Derived). + // Current behavior: with 'oob' the analysis sinks; with 'no-oob' it + // continues and the evals produce UNKNOWN. + // Expected behavior: the OOB error is fatal regardless of whether the + // OutOfBounds checker frontend is enabled, so the evals should be unreachable + // in all configurations. The 'no-oob' expectations below should be removed. + memset(&d.d_mem, 0, sizeof(Derived)); // oob-warning{{overflows the destination buffer}} + clang_analyzer_eval(d.b_mem == 0); // no-oob-warning{{UNKNOWN}} + clang_analyzer_eval(d.d_mem == 0); // no-oob-warning{{UNKNOWN}} +} + +void memset3_inheritance_field() { + Derived d; + // FIXME: memset on the base field with sizeof(Derived). By the letter of + // the standard this is UB, but practically this only touches memory it is + // supposed to with the above class definitions. + // Current behavior: with 'oob' the analysis sinks. With 'no-oob' the fields are + // treated as correctly set to 0. + // Expected behavior: same as memset2. The OOB error should be fatal in all + // configurations and the 'no-oob' expectations should be removed. + memset(&d.b_mem, 0, sizeof(Derived)); // oob-warning{{overflows the destination buffer}} + clang_analyzer_eval(d.b_mem == 0); // no-oob-warning{{TRUE}} + clang_analyzer_eval(d.d_mem == 0); // no-oob-warning{{TRUE}} +} + void memset4_array_nonpod_object() { Derived array[10]; clang_analyzer_eval(array[1].b_mem == 1); // expected-warning{{UNKNOWN}} @@ -154,4 +196,55 @@ void memset7_placement_new() { clang_analyzer_eval(d->d_mem == 0); // expected-warning{{TRUE}} } +class BaseVirtual { +public: + int b_mem; + virtual int get() { return 1; } +}; + +class DerivedVirtual : public BaseVirtual { +public: + int d_mem; +}; + +void memset8_virtual_inheritance_field() { + DerivedVirtual d; + // FIXME: Same as memset3, but the base has a virtual function. In typical + // implementations &d.b_mem differs from &d because the vtable pointer + // precedes the first member, so this may also write past the object's + // extent. + // Current behavior: with 'oob' the analysis sinks. With 'no-oob' the fields + // are treated as UNKNOWN. + // Expected behavior: same as memset2. The OOB error should be fatal in all + // configurations and the 'no-oob' expectations should be removed. + memset(&d.b_mem, 0, sizeof(Derived)); // oob-warning{{overflows the destination buffer}} + clang_analyzer_eval(d.b_mem == 0); // no-oob-warning{{UNKNOWN}} + clang_analyzer_eval(d.d_mem == 0); // no-oob-warning{{UNKNOWN}} +} + } // namespace memset_non_pod + +void memset1_new_array() { + int *array = new int[10]; + memset(array, 0, 10 * sizeof(int)); + clang_analyzer_eval(array[2] == 0); // expected-warning{{TRUE}} + // FIXME: OOB memset on a heap array. + // Current behavior: with 'oob' the analysis sinks. With 'no-oob' it + // continues and the eval produces UNKNOWN. + // Expected behavior: same as memset2. The OOB error should be fatal in all + // configurations and the 'no-oob' expectation should be removed. + memset(array + 1, 'a', 10 * sizeof(9)); // oob-warning{{overflows the destination buffer}} + clang_analyzer_eval(array[2] == 0); // no-oob-warning{{UNKNOWN}} + delete[] array; +} + +void memmove_uninit_without_outofbound() { + int src[4]; + int dst[4]; + // This test verifies that UninitializedRead produces warnings even when + // OutOfBounds is disabled. Previously, CheckBufferAccess would early-return + // before reaching checkInit() when OutOfBounds was disabled, suppressing + // UninitializedRead as a side effect. + memmove(dst, src, sizeof(src)); // uninit-warning{{The first element of the 2nd argument is undefined}} + // uninit-note@-1{{Other elements might also be undefined}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
