https://github.com/balazske created 
https://github.com/llvm/llvm-project/pull/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.

From 70051e9fc1872c4c86019e805b200fce4d9ebcf0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <[email protected]>
Date: Mon, 29 Dec 2025 10:59:41 +0100
Subject: [PATCH] [clang][analyzer] Extend CallAndMessageChecker argument
 initializedness check

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.
---
 .../Checkers/CallAndMessageChecker.cpp        | 62 +++++++++++++++----
 ...ll-and-message-argpointeeinitializedness.c | 57 +++++++++++++++++
 2 files changed, 107 insertions(+), 12 deletions(-)
 create mode 100644 
clang/test/Analysis/call-and-message-argpointeeinitializedness.c

diff --git a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
index 97acc34644c9c..7ed0fc268dc53 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"
@@ -127,10 +128,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 +275,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 +292,18 @@ bool CallAndMessageChecker::uninitRefOrPointer(
   if (!ParamT->isPointerOrReferenceType())
     return false;
 
+  bool AllowPartialInitializedness = StructInitializednessComplete;
   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 +317,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 +326,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 +339,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 +348,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 +365,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()) {
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}}
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to