[clang] [clang][analyzer] fix crash when modelling 'getline' function in checkers (PR #145229)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 22 04:30:25 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Baranov Victor (vbvictor)
<details>
<summary>Changes</summary>
Fixed crush caused by types mismatch of expected `getline` and `getdelim` signatures and actual user-defined signatures.
There are 2 ways to fix the issue:
1. Make sure that all parameters of `CallEvent` underlying `FunctionDecl` has the exact types of unix `getline` function. This way bloats the code by rigorous checking but may eliminate unnecessary transitions. I didn't find a way to eliminate code duplication, is there a `Utils` directory for CSA to place such utility functions?
2. Add more error-handling to `CheckGetDelim` methods to avoid crushes (as for now, it crushed trying to dereference an invalid `optional`). However, we will still try to model "incorrect" `getline` methods, wasting cpu.
I currently implemented 1st solution, but I'm happy to implement 2nd if it's more appropriate for CSA, WDYT?
I will create more PRs for other potential crushes of `unix`-function if we settle on the way of fixing it.
Addresses https://github.com/llvm/llvm-project/issues/144884.
---
Full diff: https://github.com/llvm/llvm-project/pull/145229.diff
4 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+3)
- (modified) clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp (+54-2)
- (modified) clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp (+50-1)
- (added) clang/test/Analysis/getline-unixapi-invalid-signatures.c (+130)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 12816eed2e8b5..b6f6dce29a87b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1060,6 +1060,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.
+
Improvements
^^^^^^^^^^^^
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 35e98a5e2719a..b3f0110539ffa 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -1482,11 +1482,63 @@ static bool isFromStdNamespace(const CallEvent &Call) {
return FD->isInStdNamespace();
}
+static bool IsGetDelim(const CallEvent &Call, CheckerContext &C) {
+ const FunctionDecl *FD = dyn_cast_if_present<FunctionDecl>(Call.getDecl());
+ if (!FD || FD->getKind() != Decl::Function)
+ return false;
+
+ const StringRef FName = C.getCalleeName(FD);
+ const bool IsDelim = FName == "getdelim";
+ const bool IsLine = FName == "getline";
+
+ unsigned ExpectedNumArgs = 0;
+ if (IsDelim)
+ ExpectedNumArgs = 4;
+ else if (IsLine)
+ ExpectedNumArgs = 3;
+ else
+ return false;
+
+ if (FD->getNumParams() != ExpectedNumArgs)
+ return false;
+
+ // Should be char**
+ const QualType CharPtrType = FD->getParamDecl(0)->getType()->getPointeeType();
+ if (CharPtrType.isNull())
+ return false;
+ const QualType CharType = CharPtrType->getPointeeType();
+ if (CharType.isNull() || !CharType->isCharType())
+ return false;
+
+ // Should be size_t*
+ const QualType SizePtrType = FD->getParamDecl(1)->getType();
+ if (!SizePtrType->isPointerType())
+ return false;
+ const QualType SizeArgType = SizePtrType->getPointeeType();
+ const ASTContext &Ctx = C.getASTContext();
+ if (SizeArgType.isNull() ||
+ !Ctx.hasSameUnqualifiedType(SizeArgType, Ctx.getSizeType()))
+ return false;
+
+ // For getdelim, should be int
+ if (IsDelim)
+ if (!FD->getParamDecl(2)->getType()->isIntegerType())
+ return false;
+
+ // Last parameter should be FILE*
+ const QualType Pointee =
+ FD->getParamDecl(IsDelim ? 3 : 2)->getType()->getPointeeType();
+ if (Pointee.isNull() || Pointee.getAsString() != "FILE")
+ return false;
+
+ return true;
+}
+
void MallocChecker::preGetdelim(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))
+ if (isFromStdNamespace(Call) || !IsGetDelim(Call, C))
return;
const auto LinePtr = getPointeeVal(Call.getArgSVal(0), State);
@@ -1509,7 +1561,7 @@ void MallocChecker::checkGetdelim(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))
+ if (isFromStdNamespace(Call) || !IsGetDelim(Call, C))
return;
// Handle the post-conditions of getline and getdelim:
diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
index 79d10d99e11d0..11d82f6de5e62 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
@@ -147,6 +147,55 @@ ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull(
return PtrNotNull;
}
+static bool IsGetDelim(const CallEvent &Call, const FunctionDecl *FD,
+ CheckerContext &C) {
+ const StringRef FName = C.getCalleeName(FD);
+ const bool IsDelim = FName == "getdelim";
+ const bool IsLine = FName == "getline";
+
+ unsigned ExpectedNumArgs = 0;
+ if (IsDelim)
+ ExpectedNumArgs = 4;
+ else if (IsLine)
+ ExpectedNumArgs = 3;
+ else
+ return false;
+
+ if (FD->getNumParams() != ExpectedNumArgs)
+ return false;
+
+ // Should be char**
+ const QualType CharPtrType = FD->getParamDecl(0)->getType()->getPointeeType();
+ if (CharPtrType.isNull())
+ return false;
+ const QualType CharType = CharPtrType->getPointeeType();
+ if (CharType.isNull() || !CharType->isCharType())
+ return false;
+
+ // Should be size_t*
+ const QualType SizePtrType = FD->getParamDecl(1)->getType();
+ if (!SizePtrType->isPointerType())
+ return false;
+ const QualType SizeArgType = SizePtrType->getPointeeType();
+ const ASTContext &Ctx = C.getASTContext();
+ if (SizeArgType.isNull() ||
+ !Ctx.hasSameUnqualifiedType(SizeArgType, Ctx.getSizeType()))
+ return false;
+
+ // For getdelim, should be int
+ if (IsDelim)
+ if (!FD->getParamDecl(2)->getType()->isIntegerType())
+ return false;
+
+ // Last parameter should be FILE*
+ const QualType Pointee =
+ FD->getParamDecl(IsDelim ? 3 : 2)->getType()->getPointeeType();
+ if (Pointee.isNull() || Pointee.getAsString() != "FILE")
+ return false;
+
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// "open" (man 2 open)
//===----------------------------------------------------------------------===/
@@ -176,7 +225,7 @@ void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call,
else if (FName == "pthread_once")
CheckPthreadOnce(C, Call);
- else if (is_contained({"getdelim", "getline"}, FName))
+ else if (IsGetDelim(Call, FD, C))
CheckGetDelim(C, Call);
}
void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C,
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..ad218937f1184
--- /dev/null
+++ b/clang/test/Analysis/getline-unixapi-invalid-signatures.c
@@ -0,0 +1,130 @@
+// 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
+// expected-no-diagnostics
+ssize_t getline(char **lineptr, size_t *n);
+
+void test() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getline(&buffer, 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
+// expected-no-diagnostics
+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);
+ fclose(F1);
+}
+
+void test_delim() {
+ FILE *F1 = tmpfile();
+ if (!F1)
+ return;
+ char *buffer = NULL;
+ getdelim(&buffer, NULL, ',', 1);
+ fclose(F1);
+}
+#endif
+
+#ifdef TEST_GETLINE_5
+// expected-no-diagnostics
+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);
+ 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
``````````
</details>
https://github.com/llvm/llvm-project/pull/145229
More information about the cfe-commits
mailing list