[clang] de2ec22 - [analyzer] Fix crash when modelling 'getline' function in checkers (#145229)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 24 11:18:18 PDT 2025
Author: Baranov Victor
Date: 2025-06-24T20:18:15+02:00
New Revision: de2ec228c03c3072782b6fbcb43388a4556b90b2
URL: https://github.com/llvm/llvm-project/commit/de2ec228c03c3072782b6fbcb43388a4556b90b2
DIFF: https://github.com/llvm/llvm-project/commit/de2ec228c03c3072782b6fbcb43388a4556b90b2.diff
LOG: [analyzer] Fix crash when modelling 'getline' function in checkers (#145229)
Fixes #144884
Added:
clang/test/Analysis/getline-unixapi-invalid-signatures.c
Modified:
clang/docs/ReleaseNotes.rst
clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2b32f80df596b..e1fe22393eebb 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1071,6 +1071,9 @@ impact the linker behaviour like the other `-static-*` flags.
Crash and bug fixes
^^^^^^^^^^^^^^^^^^^
+- Fixed a crash in ``UnixAPIMisuseChecker`` and ``MallocChecker`` when analyzing
+ code with non-standard ``getline`` or ``getdelim`` function signatures. (#GH144884)
+
Improvements
^^^^^^^^^^^^
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 35e98a5e2719a..fa6e8e4146804 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -430,8 +430,8 @@ class MallocChecker
CHECK_FN(checkGMemdup)
CHECK_FN(checkGMallocN)
CHECK_FN(checkGMallocN0)
- CHECK_FN(preGetdelim)
- CHECK_FN(checkGetdelim)
+ CHECK_FN(preGetDelimOrGetLine)
+ CHECK_FN(checkGetDelimOrGetLine)
CHECK_FN(checkReallocN)
CHECK_FN(checkOwnershipAttr)
@@ -445,15 +445,16 @@ class MallocChecker
const CallDescriptionMap<CheckFn> PreFnMap{
// NOTE: the following CallDescription also matches the C++ standard
// library function std::getline(); the callback will filter it out.
- {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetdelim},
- {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetdelim},
+ {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetDelimOrGetLine},
+ {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetDelimOrGetLine},
};
const CallDescriptionMap<CheckFn> PostFnMap{
// NOTE: the following CallDescription also matches the C++ standard
// library function std::getline(); the callback will filter it out.
- {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetdelim},
- {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::checkGetdelim},
+ {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetDelimOrGetLine},
+ {{CDM::CLibrary, {"getdelim"}, 4},
+ &MallocChecker::checkGetDelimOrGetLine},
};
const CallDescriptionMap<CheckFn> FreeingMemFnMap{
@@ -1482,8 +1483,9 @@ static bool isFromStdNamespace(const CallEvent &Call) {
return FD->isInStdNamespace();
}
-void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call,
- CheckerContext &C) const {
+void MallocChecker::preGetDelimOrGetLine(ProgramStateRef State,
+ const CallEvent &Call,
+ CheckerContext &C) const {
// Discard calls to the C++ standard library function std::getline(), which
// is completely unrelated to the POSIX getline() that we're checking.
if (isFromStdNamespace(Call))
@@ -1505,8 +1507,9 @@ void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call,
C.addTransition(State);
}
-void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call,
- CheckerContext &C) const {
+void MallocChecker::checkGetDelimOrGetLine(ProgramStateRef State,
+ const CallEvent &Call,
+ CheckerContext &C) const {
// Discard calls to the C++ standard library function std::getline(), which
// is completely unrelated to the POSIX getline() that we're checking.
if (isFromStdNamespace(Call))
@@ -1518,14 +1521,19 @@ void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call,
if (!CE)
return;
- const auto LinePtr =
- getPointeeVal(Call.getArgSVal(0), State)->getAs<DefinedSVal>();
- const auto Size =
- getPointeeVal(Call.getArgSVal(1), State)->getAs<DefinedSVal>();
- if (!LinePtr || !Size || !LinePtr->getAsRegion())
+ const auto LinePtrOpt = getPointeeVal(Call.getArgSVal(0), State);
+ const auto SizeOpt = getPointeeVal(Call.getArgSVal(1), State);
+ if (!LinePtrOpt || !SizeOpt || LinePtrOpt->isUnknownOrUndef() ||
+ SizeOpt->isUnknownOrUndef())
+ return;
+
+ const auto LinePtr = LinePtrOpt->getAs<DefinedSVal>();
+ const auto Size = SizeOpt->getAs<DefinedSVal>();
+ const MemRegion *LinePtrReg = LinePtr->getAsRegion();
+ if (!LinePtrReg)
return;
- State = setDynamicExtent(State, LinePtr->getAsRegion(), *Size);
+ State = setDynamicExtent(State, LinePtrReg, *Size);
C.addTransition(MallocUpdateRefState(C, CE, State,
AllocationFamily(AF_Malloc), *LinePtr));
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
index 79d10d99e11d0..ce5887d56b181 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
@@ -83,7 +83,7 @@ class UnixAPIMisuseChecker : public Checker<check::PreCall> {
void CheckOpen(CheckerContext &C, const CallEvent &Call) const;
void CheckOpenAt(CheckerContext &C, const CallEvent &Call) const;
- void CheckGetDelim(CheckerContext &C, const CallEvent &Call) const;
+ void CheckGetDelimOrGetline(CheckerContext &C, const CallEvent &Call) const;
void CheckPthreadOnce(CheckerContext &C, const CallEvent &Call) const;
void CheckOpenVariant(CheckerContext &C, const CallEvent &Call,
@@ -128,7 +128,7 @@ ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull(
const StringRef PtrDescr,
std::optional<std::reference_wrapper<const BugType>> BT) const {
const auto Ptr = PtrVal.getAs<DefinedSVal>();
- if (!Ptr)
+ if (!Ptr || !PtrExpr->getType()->isPointerType())
return State;
const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);
@@ -177,7 +177,7 @@ void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call,
CheckPthreadOnce(C, Call);
else if (is_contained({"getdelim", "getline"}, FName))
- CheckGetDelim(C, Call);
+ CheckGetDelimOrGetline(C, Call);
}
void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C,
ProgramStateRef State,
@@ -332,8 +332,12 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
// We have a pointer to a pointer to the buffer, and a pointer to the size.
// We want what they point at.
- auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>();
- auto NSVal = getPointeeVal(SizePtrSVal, State);
+ const auto LinePtrValOpt = getPointeeVal(LinePtrPtrSVal, State);
+ if (!LinePtrValOpt)
+ return nullptr;
+
+ const auto LinePtrSVal = LinePtrValOpt->getAs<DefinedSVal>();
+ const auto NSVal = getPointeeVal(SizePtrSVal, State);
if (!LinePtrSVal || !NSVal || NSVal->isUnknown())
return nullptr;
@@ -350,9 +354,16 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
// If it is defined, and known, its size must be less than or equal to
// the buffer size.
auto NDefSVal = NSVal->getAs<DefinedSVal>();
+ if (!NDefSVal)
+ return LinePtrNotNull;
+
auto &SVB = C.getSValBuilder();
- auto LineBufSize =
- getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);
+
+ const MemRegion *LinePtrRegion = LinePtrSVal->getAsRegion();
+ if (!LinePtrRegion)
+ return LinePtrNotNull;
+
+ auto LineBufSize = getDynamicExtent(LinePtrNotNull, LinePtrRegion, SVB);
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
*NDefSVal, SVB.getConditionType())
.getAs<DefinedOrUnknownSVal>();
@@ -367,8 +378,11 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
return State;
}
-void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C,
- const CallEvent &Call) const {
+void UnixAPIMisuseChecker::CheckGetDelimOrGetline(CheckerContext &C,
+ const CallEvent &Call) const {
+ if (Call.getNumArgs() < 2)
+ return;
+
ProgramStateRef State = C.getState();
// The parameter `n` must not be NULL.
diff --git a/clang/test/Analysis/getline-unixapi-invalid-signatures.c b/clang/test/Analysis/getline-unixapi-invalid-signatures.c
new file mode 100644
index 0000000000000..c906625b47634
--- /dev/null
+++ b/clang/test/Analysis/getline-unixapi-invalid-signatures.c
@@ -0,0 +1,127 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_CORRECT
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_1
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_2
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_3
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_4
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_5
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_GH144884
+
+// emulator of "system-header-simulator.h" because of redefinition of 'getline' function
+typedef struct _FILE FILE;
+typedef __typeof(sizeof(int)) size_t;
+typedef long ssize_t;
+#define NULL 0
+
+int fclose(FILE *fp);
+FILE *tmpfile(void);
+
+#ifdef TEST_CORRECT
+ssize_t getline(char **lineptr, size_t *n, FILE *stream);
+ssize_t getdelim(char **lineptr, size_t *n, int delimiter, FILE *stream);
+
+void test_correct() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getline(&buffer, NULL, F1); // expected-warning {{Size pointer might be NULL}}
+ fclose(F1);
+}
+
+void test_delim_correct() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getdelim(&buffer, NULL, ',', F1); // expected-warning {{Size pointer might be NULL}}
+ fclose(F1);
+}
+#endif
+
+#ifdef TEST_GETLINE_1
+// expected-no-diagnostics
+ssize_t getline(int lineptr);
+
+void test() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ int buffer = 0;
+ getline(buffer);
+ fclose(F1);
+}
+#endif
+
+#ifdef TEST_GETLINE_2
+ssize_t getline(char **lineptr, size_t *n);
+
+void test() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getline(&buffer, NULL); // expected-warning {{Size pointer might be NULL}}
+ fclose(F1);
+}
+#endif
+
+#ifdef TEST_GETLINE_3
+// expected-no-diagnostics
+ssize_t getline(char **lineptr, size_t n, FILE *stream);
+
+void test() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getline(&buffer, 0, F1);
+ fclose(F1);
+}
+#endif
+
+#ifdef TEST_GETLINE_4
+ssize_t getline(char **lineptr, size_t *n, int stream);
+ssize_t getdelim(char **lineptr, size_t *n, int delimiter, int stream);
+
+void test() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getline(&buffer, NULL, 1); // expected-warning {{Size pointer might be NULL}}
+ fclose(F1);
+}
+
+void test_delim() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getdelim(&buffer, NULL, ',', 1); // expected-warning {{Size pointer might be NULL}}
+ fclose(F1);
+}
+#endif
+
+#ifdef TEST_GETLINE_5
+ssize_t getdelim(char **lineptr, size_t *n, const char* delimiter, FILE *stream);
+
+void test_delim() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getdelim(&buffer, NULL, ",", F1); // expected-warning {{Size pointer might be NULL}}
+ fclose(F1);
+}
+#endif
+
+#ifdef TEST_GETLINE_GH144884
+// expected-no-diagnostics
+struct AW_string {};
+void getline(int *, struct AW_string);
+void top() {
+ struct AW_string line;
+ int getline_file_info;
+ getline(&getline_file_info, line);
+}
+#endif
More information about the cfe-commits
mailing list