[clang] 29a6e47 - [clang][analyzer] Extend CallAndMessageChecker argument initializedness check (#173854)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jan 7 04:02:22 PST 2026
Author: Balázs Kéri
Date: 2026-01-07T13:02:17+01:00
New Revision: 29a6e47ca3644e330d744364a65a25b273f3eb30
URL: https://github.com/llvm/llvm-project/commit/29a6e47ca3644e330d744364a65a25b273f3eb30
DIFF: https://github.com/llvm/llvm-project/commit/29a6e47ca3644e330d744364a65a25b273f3eb30.diff
LOG: [clang][analyzer] Extend CallAndMessageChecker argument initializedness check (#173854)
Add extra check to `CallAndMessageChecker` to find uninitialized
non-const values passed through parameters. This check is used only at a
specific list of C library functions which have non-const pointer
parameters and the value should be initialized before the call.
Added:
clang/test/Analysis/call-and-message-argpointeeinitializedness.c
Modified:
clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
Removed:
################################################################################
diff --git a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
index 97acc34644c9c..c864af820b966 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
@@ -18,6 +18,7 @@
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "llvm/ADT/STLExtras.h"
@@ -79,9 +80,11 @@ class CallAndMessageChecker
bool ChecksEnabled[CK_NumCheckKinds] = {false};
- /// When checking a struct value for uninitialized data, should all the fields
- /// be un-initialized or only find one uninitialized field.
- bool StructInitializednessComplete = true;
+ /// When checking a struct value for uninitialized data and this setting is
+ /// true, all members should be completely uninitialized to get a checker
+ /// warning. When the value is false, the warning is emitted for partially
+ // initialized structures too.
+ bool ArgPointeeInitializednessComplete = true;
void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
@@ -127,10 +130,35 @@ class CallAndMessageChecker
ProgramStateRef state,
const ObjCMethodCall &msg) const;
- bool uninitRefOrPointer(CheckerContext &C, SVal V, SourceRange ArgRange,
- const Expr *ArgEx, const BugType &BT,
- const ParmVarDecl *ParamDecl,
+ bool uninitRefOrPointer(CheckerContext &C, SVal V, const CallEvent &Call,
+ const BugType &BT, const ParmVarDecl *ParamDecl,
int ArgumentNumber) const;
+
+ // C library functions which have a pointer-to-struct parameter that should be
+ // initialized (at least partially) before the call. The 'uninitRefOrPointer'
+ // check uses this data.
+ CallDescriptionMap<int> FunctionsWithInOutPtrParam = {
+ {{CDM::CLibrary, {"mbrlen"}, 3}, 2},
+ {{CDM::CLibrary, {"mbrtowc"}, 4}, 3},
+ {{CDM::CLibrary, {"wcrtomb"}, 3}, 2},
+ {{CDM::CLibrary, {"mbsrtowcs"}, 4}, 3},
+ {{CDM::CLibrary, {"wcsrtombs"}, 4}, 3},
+ {{CDM::CLibrary, {"mbsnrtowcs"}, 5}, 4},
+ {{CDM::CLibrary, {"wcsnrtombs"}, 5}, 4},
+ {{CDM::CLibrary, {"wcrtomb_s"}, 5}, 4},
+ {{CDM::CLibrary, {"mbsrtowcs_s"}, 6}, 5},
+ {{CDM::CLibrary, {"wcsrtombs_s"}, 6}, 5},
+
+ {{CDM::CLibrary, {"mbrtoc8"}, 4}, 3},
+ {{CDM::CLibrary, {"c8rtomb"}, 3}, 2},
+ {{CDM::CLibrary, {"mbrtoc16"}, 4}, 3},
+ {{CDM::CLibrary, {"c16rtomb"}, 3}, 2},
+ {{CDM::CLibrary, {"mbrtoc32"}, 4}, 3},
+ {{CDM::CLibrary, {"c32rtomb"}, 3}, 2},
+
+ {{CDM::CLibrary, {"mktime"}, 1}, 0},
+ {{CDM::CLibrary, {"timegm"}, 1}, 0},
+ };
};
} // end anonymous namespace
@@ -249,9 +277,11 @@ template <> struct format_provider<FindUninitializedField::FieldChainTy> {
};
} // namespace llvm
-bool CallAndMessageChecker::uninitRefOrPointer(
- CheckerContext &C, SVal V, SourceRange ArgRange, const Expr *ArgEx,
- const BugType &BT, const ParmVarDecl *ParamDecl, int ArgumentNumber) const {
+bool CallAndMessageChecker::uninitRefOrPointer(CheckerContext &C, SVal V,
+ const CallEvent &Call,
+ const BugType &BT,
+ const ParmVarDecl *ParamDecl,
+ int ArgumentNumber) const {
if (!ChecksEnabled[CK_ArgPointeeInitializedness])
return false;
@@ -264,9 +294,18 @@ bool CallAndMessageChecker::uninitRefOrPointer(
if (!ParamT->isPointerOrReferenceType())
return false;
+ bool AllowPartialInitializedness = ArgPointeeInitializednessComplete;
QualType PointeeT = ParamT->getPointeeType();
- if (!PointeeT.isConstQualified())
- return false;
+ if (!PointeeT.isConstQualified()) {
+ if (const int *PI = FunctionsWithInOutPtrParam.lookup(Call)) {
+ if (*PI != ArgumentNumber)
+ return false;
+ // At these functions always allow partial argument initializedness.
+ AllowPartialInitializedness = true;
+ } else {
+ return false;
+ }
+ }
const MemRegion *SValMemRegion = V.getAsRegion();
if (!SValMemRegion)
@@ -280,6 +319,7 @@ bool CallAndMessageChecker::uninitRefOrPointer(
if (PointeeT->isVoidType())
PointeeT = C.getASTContext().CharTy;
const SVal PointeeV = State->getSVal(SValMemRegion, PointeeT);
+ const Expr *ArgEx = Call.getArgExpr(ArgumentNumber);
if (PointeeV.isUndef()) {
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -288,7 +328,7 @@ bool CallAndMessageChecker::uninitRefOrPointer(
ArgumentNumber + 1, llvm::getOrdinalSuffix(ArgumentNumber + 1),
ParamT->isPointerType() ? "a pointer to" : "an");
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
- R->addRange(ArgRange);
+ R->addRange(Call.getArgSourceRange(ArgumentNumber));
if (ArgEx)
bugreporter::trackExpressionValue(N, ArgEx, *R);
@@ -301,7 +341,7 @@ bool CallAndMessageChecker::uninitRefOrPointer(
const LazyCompoundValData *D = LV->getCVData();
FindUninitializedField F(C.getState()->getStateManager().getStoreManager(),
C.getSValBuilder().getRegionManager(),
- D->getStore(), StructInitializednessComplete);
+ D->getStore(), AllowPartialInitializedness);
if (F.Find(D->getRegion())) {
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -310,7 +350,7 @@ bool CallAndMessageChecker::uninitRefOrPointer(
(ArgumentNumber + 1), llvm::getOrdinalSuffix(ArgumentNumber + 1),
ParamT->isPointerType() ? "points to" : "references", F.FieldChain);
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
- R->addRange(ArgRange);
+ R->addRange(Call.getArgSourceRange(ArgumentNumber));
if (ArgEx)
bugreporter::trackExpressionValue(N, ArgEx, *R);
@@ -327,7 +367,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(
CheckerContext &C, SVal V, SourceRange ArgRange, const Expr *ArgEx,
int ArgumentNumber, bool CheckUninitFields, const CallEvent &Call,
const BugType &BT, const ParmVarDecl *ParamDecl) const {
- if (uninitRefOrPointer(C, V, ArgRange, ArgEx, BT, ParamDecl, ArgumentNumber))
+ if (uninitRefOrPointer(C, V, Call, BT, ParamDecl, ArgumentNumber))
return true;
if (V.isUndef()) {
@@ -727,7 +767,7 @@ void ento::registerCallAndMessageChecker(CheckerManager &Mgr) {
QUERY_CHECKER_OPTION(NilReceiver)
QUERY_CHECKER_OPTION(UndefReceiver)
- Chk->StructInitializednessComplete =
+ Chk->ArgPointeeInitializednessComplete =
Mgr.getAnalyzerOptions().getCheckerBooleanOption(
Mgr.getCurrentCheckerName(), "ArgPointeeInitializednessComplete");
}
diff --git a/clang/test/Analysis/call-and-message-argpointeeinitializedness.c b/clang/test/Analysis/call-and-message-argpointeeinitializedness.c
new file mode 100644
index 0000000000000..2fc4b9f9f523e
--- /dev/null
+++ b/clang/test/Analysis/call-and-message-argpointeeinitializedness.c
@@ -0,0 +1,57 @@
+// RUN: %clang_analyze_cc1 -verify %s \
+// RUN: -analyzer-checker=core \
+// RUN: -analyzer-config core.CallAndMessage:ArgPointeeInitializedness=true \
+// RUN: -analyzer-config core.CallAndMessage:ArgPointeeInitializednessComplete=true \
+// RUN: -analyzer-config core.CallAndMessage:ArgInitializedness=false
+
+// RUN: %clang_analyze_cc1 -verify %s \
+// RUN: -analyzer-checker=core \
+// RUN: -analyzer-config core.CallAndMessage:ArgPointeeInitializedness=true \
+// RUN: -analyzer-config core.CallAndMessage:ArgInitializedness=false
+
+typedef __typeof(sizeof(int)) size_t;
+typedef __WCHAR_TYPE__ wchar_t;
+typedef __CHAR16_TYPE__ char16_t;
+typedef long time_t;
+typedef struct {
+ int x;
+ int y;
+} mbstate_t;
+struct tm {
+ int x;
+ int y;
+};
+extern size_t mbrlen(const char *restrict, size_t, mbstate_t *restrict);
+extern size_t wcsnrtombs(char *restrict dst, const wchar_t **restrict src,
+ size_t nwc, size_t len, mbstate_t *restrict ps);
+extern size_t mbrtoc16(char16_t *restrict pc16, const char *restrict s,
+ size_t n, mbstate_t *restrict ps);
+extern time_t mktime(struct tm *timeptr);
+
+void uninit_mbrlen(const char *mbs) {
+ mbstate_t state;
+ mbrlen(mbs, 1, &state); // expected-warning{{3rd function call argument points to an uninitialized value}}
+}
+
+void init_mbrlen(const char *mbs) {
+ mbstate_t state;
+ state.x = 0;
+ mbrlen(mbs, 1, &state);
+}
+
+void uninit_wcsnrtombs(const wchar_t *src) {
+ char dst[10];
+ mbstate_t state;
+ wcsnrtombs(dst, &src, 1, 2, &state); // expected-warning{{5th function call argument points to an uninitialized value}}
+}
+
+void uninit_mbrtoc16(const char *s) {
+ char16_t pc16[10];
+ mbstate_t state;
+ mbrtoc16(pc16, s, 1, &state); // expected-warning{{4th function call argument points to an uninitialized value}}
+}
+
+void uninit_mktime() {
+ struct tm time;
+ mktime(&time); // expected-warning{{1st function call argument points to an uninitialized value}}
+}
More information about the cfe-commits
mailing list