[clang] 2dfdd09 - [clang][StaticAnalyzer] Reduce MallocSizeofChecker false positives for layout-compatible types (#200253)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 16 12:54:20 PDT 2026
Author: Rashmi Mudduluru
Date: 2026-06-16T12:54:15-07:00
New Revision: 2dfdd094d084e258934cc1e89be8a09fb59cdfa1
URL: https://github.com/llvm/llvm-project/commit/2dfdd094d084e258934cc1e89be8a09fb59cdfa1
DIFF: https://github.com/llvm/llvm-project/commit/2dfdd094d084e258934cc1e89be8a09fb59cdfa1.diff
LOG: [clang][StaticAnalyzer] Reduce MallocSizeofChecker false positives for layout-compatible types (#200253)
When one operand is a record type and the other is a non-record type,
treat them as compatible if they share the same size and the record's
alignment satisfies the scalar's alignment. This suppresses warnings for
patterns like `malloc(sizeof(std::atomic<int32_t>))` assigned to an
`int32_t *` (or a wrapper struct with an identical layout), while still
flagging genuinely mismatched types such as `long` vs `double` or
unrelated struct pairs.
rdar://177553628
---------
Co-authored-by: Claude Sonnet 4.6 <noreply at anthropic.com>
Added:
clang/test/Analysis/malloc-sizeof-fp.cpp
Modified:
clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
Removed:
################################################################################
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
index f84d45214f6fe..5d4f15246c3fb 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
@@ -149,12 +149,39 @@ static bool typesCompatible(ASTContext &C, QualType A, QualType B) {
if (A.getTypePtr() == B.getTypePtr())
return true;
- if (const PointerType *ptrA = A->getAs<PointerType>())
- if (const PointerType *ptrB = B->getAs<PointerType>()) {
- A = ptrA->getPointeeType();
- B = ptrB->getPointeeType();
- continue;
+ const PointerType *ptrA = A->getAs<PointerType>();
+ const PointerType *ptrB = B->getAs<PointerType>();
+
+ // When neither type is a pointer and exactly one is a record type, check
+ // target-specific size and alignment. This avoids false positives for
+ // types that wrap another type with the same layout (e.g.
+ // std::atomic<int32_t> vs int32_t, or struct{int32_t x;} vs int32_t),
+ // while preserving warnings for unrelated types that happen to share a
+ // size (e.g. long vs double, struct A vs struct B).
+ if (!ptrA && !ptrB && (A->isRecordType() != B->isRecordType()) &&
+ !A->isIncompleteType() && !B->isIncompleteType()) {
+ const RecordType *RecTy = A->getAs<RecordType>();
+ QualType Scalar = B;
+ if (!RecTy) {
+ RecTy = B->getAs<RecordType>();
+ Scalar = A;
+ }
+ if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(RecTy->getDecl())) {
+ if (RD->isStandardLayout()) {
+ const CXXRecordDecl *Base = RD->getStandardLayoutBaseWithFields();
+ auto F = Base->field_begin();
+ if (F != Base->field_end() && std::next(F) == Base->field_end() &&
+ C.getCanonicalType((*F)->getType()) == Scalar)
+ return true;
+ }
}
+ }
+
+ if (ptrA && ptrB) {
+ A = ptrA->getPointeeType();
+ B = ptrB->getPointeeType();
+ continue;
+ }
break;
}
diff --git a/clang/test/Analysis/malloc-sizeof-fp.cpp b/clang/test/Analysis/malloc-sizeof-fp.cpp
new file mode 100644
index 0000000000000..64590796de4ce
--- /dev/null
+++ b/clang/test/Analysis/malloc-sizeof-fp.cpp
@@ -0,0 +1,61 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=unix.MallocSizeof -std=c++11 -verify %s
+
+// Verify no false positives for layout-compatible types: a record type that
+// wraps a scalar with identical size and alignment (e.g. std::atomic<int>
+// wrapping int, or struct { int c; } vs int).
+
+typedef int int32_t;
+using size_t = unsigned long long;
+void *malloc(size_t size);
+void free(void *ptr);
+
+namespace std {
+// Minimal atomic stub with the same size/alignment as T.
+template <typename T>
+struct atomic {
+ T _value;
+};
+} // namespace std
+
+typedef std::atomic<int32_t> u_atomic_int32_t;
+
+struct s_int {
+ int32_t c;
+};
+
+template <typename T>
+void work() {
+ u_atomic_int32_t *p1 = (u_atomic_int32_t *)malloc(sizeof(int32_t));
+ free(p1);
+
+ T *p2 = (T *)malloc(sizeof(int32_t));
+ free(p2);
+}
+
+int main() {
+ work<u_atomic_int32_t>();
+ work<int>();
+ work<s_int>();
+ return 0;
+}
+
+void test_no_false_negatives() {
+ // Unrelated struct with the same size
+ struct Color { float r; };
+ int *p = (int *)malloc(sizeof(Color));
+ // expected-warning at -1{{Result of 'malloc' is converted to a pointer of type 'int', which is incompatible with sizeof operand type 'Color'}}
+
+ // Multi-field struct with the same size and alignment as the scalar
+ struct Pair { int a; int b; }; // 8 bytes, align 4
+ Pair *pr = (Pair *)malloc(sizeof(long));
+ // expected-warning at -1{{Result of 'malloc' is converted to a pointer of type 'Pair', which is incompatible with sizeof operand type 'long'}}
+
+ struct Status { int code; };
+ float *f = (float *)malloc(sizeof(Status));
+ // expected-warning at -1{{Result of 'malloc' is converted to a pointer of type 'float', which is incompatible with sizeof operand type 'Status'}}
+
+ // Pointer-sized struct vs. pointer-sized scalar
+ struct Handle { long opaque; };
+ double *d = (double *)malloc(sizeof(Handle));
+ // expected-warning at -1{{Result of 'malloc' is converted to a pointer of type 'double', which is incompatible with sizeof operand type 'Handle'}}
+}
More information about the cfe-commits
mailing list