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}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
