[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
https://github.com/alejandro-alvarez-sonarsource created https://github.com/llvm/llvm-project/pull/83138 `getdelim` and `getline` may free, allocate, or re-allocate the input buffer, ensuring its size is enough to hold the incoming line, the delimiter, and the null terminator. `*lineptr` must be a valid argument to `free`, which means it can be either 1. `NULL`, in which case these functions perform an allocation equivalent to a call to `malloc` even on failure. 2. A pointer returned by the `malloc` family of functions. Other pointers are UB (`alloca`, a pointer to a static, to a stack variable, etc.) From ad17cfb588500d095b4e402bd2f2197772f89b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= Date: Wed, 21 Feb 2024 16:08:04 +0100 Subject: [PATCH] [clang][analyzer] Model allocation behavior or getdelim/geline --- .../Core/PathSensitive/CheckerHelpers.h | 6 ++ .../StaticAnalyzer/Checkers/MallocChecker.cpp | 73 +-- .../StaticAnalyzer/Core/CheckerHelpers.cpp| 9 +++ .../system-header-simulator-for-malloc.h | 1 + clang/test/Analysis/getline-alloc.c | 71 ++ 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 clang/test/Analysis/getline-alloc.c diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h index 65982457ad8393..60421e5437d82f 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h @@ -13,6 +13,9 @@ #ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H +#include "ProgramState_Fwd.h" +#include "SVals.h" + #include "clang/AST/OperationKinds.h" #include "clang/AST/Stmt.h" #include "clang/Basic/OperatorKinds.h" @@ -110,6 +113,9 @@ class OperatorKind { OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK, bool IsBinary); +std::optional getPointeeDefVal(SVal PtrSVal, +ProgramStateRef State); + } // namespace ento } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 79ab05f2c7866a..c5c382634c981c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -382,6 +382,8 @@ class MallocChecker CHECK_FN(checkGMemdup) CHECK_FN(checkGMallocN) CHECK_FN(checkGMallocN0) + CHECK_FN(preGetdelim) + CHECK_FN(checkGetdelim) CHECK_FN(checkReallocN) CHECK_FN(checkOwnershipAttr) @@ -391,6 +393,11 @@ class MallocChecker using CheckFn = std::function; + const CallDescriptionMap PreFnMap{ + {{{"getline"}, 3}, &MallocChecker::preGetdelim}, + {{{"getdelim"}, 4}, &MallocChecker::preGetdelim}, + }; + const CallDescriptionMap FreeingMemFnMap{ {{{"free"}, 1}, &MallocChecker::checkFree}, {{{"if_freenameindex"}, 1}, &MallocChecker::checkIfFreeNameIndex}, @@ -439,6 +446,8 @@ class MallocChecker std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, {{{"g_realloc_n"}, 3}, &MallocChecker::checkReallocN}, {{{"g_try_realloc_n"}, 3}, &MallocChecker::checkReallocN}, + {{{"getline"}, 3}, &MallocChecker::checkGetdelim}, + {{{"getdelim"}, 4}, &MallocChecker::checkGetdelim}, }; bool isMemCall(const CallEvent &Call) const; @@ -588,11 +597,14 @@ class MallocChecker /// } /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function /// we're modeling returns with Null on failure. + /// \param [in] ArgValOpt Optional value to use for the argument instead of + /// the one obtained from ArgExpr. /// \returns The ProgramState right after deallocation. [[nodiscard]] ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call, ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, - AllocationFamily Family, bool ReturnsNullOnFailure = false) const; + AllocationFamily Family, bool ReturnsNullOnFailure = false, + std::optional ArgValOpt = {}) const; // TODO: Needs some refactoring, as all other deallocation modeling // functions are suffering from out parameters and messy code due to how @@ -1423,6 +1435,46 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call, C.addTransition(State); } +void MallocChecker::preGetdelim(const CallEvent &Call, +CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; + + ProgramStateRef State = C.getState(); + const auto LinePtr = getPointeeDefVal(Call.getArgSVal(0), State); + if (!LinePtr) +return; + + bool IsKnownToBeA
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
llvmbot wrote: @llvm/pr-subscribers-clang-static-analyzer-1 Author: Alejandro Álvarez Ayllón (alejandro-alvarez-sonarsource) Changes `getdelim` and `getline` may free, allocate, or re-allocate the input buffer, ensuring its size is enough to hold the incoming line, the delimiter, and the null terminator. `*lineptr` must be a valid argument to `free`, which means it can be either 1. `NULL`, in which case these functions perform an allocation equivalent to a call to `malloc` even on failure. 2. A pointer returned by the `malloc` family of functions. Other pointers are UB (`alloca`, a pointer to a static, to a stack variable, etc.) --- Full diff: https://github.com/llvm/llvm-project/pull/83138.diff 5 Files Affected: - (modified) clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h (+6) - (modified) clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp (+67-6) - (modified) clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp (+9) - (modified) clang/test/Analysis/Inputs/system-header-simulator-for-malloc.h (+1) - (added) clang/test/Analysis/getline-alloc.c (+71) ``diff diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h index 65982457ad8393..60421e5437d82f 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h @@ -13,6 +13,9 @@ #ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H +#include "ProgramState_Fwd.h" +#include "SVals.h" + #include "clang/AST/OperationKinds.h" #include "clang/AST/Stmt.h" #include "clang/Basic/OperatorKinds.h" @@ -110,6 +113,9 @@ class OperatorKind { OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK, bool IsBinary); +std::optional getPointeeDefVal(SVal PtrSVal, +ProgramStateRef State); + } // namespace ento } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 79ab05f2c7866a..c5c382634c981c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -382,6 +382,8 @@ class MallocChecker CHECK_FN(checkGMemdup) CHECK_FN(checkGMallocN) CHECK_FN(checkGMallocN0) + CHECK_FN(preGetdelim) + CHECK_FN(checkGetdelim) CHECK_FN(checkReallocN) CHECK_FN(checkOwnershipAttr) @@ -391,6 +393,11 @@ class MallocChecker using CheckFn = std::function; + const CallDescriptionMap PreFnMap{ + {{{"getline"}, 3}, &MallocChecker::preGetdelim}, + {{{"getdelim"}, 4}, &MallocChecker::preGetdelim}, + }; + const CallDescriptionMap FreeingMemFnMap{ {{{"free"}, 1}, &MallocChecker::checkFree}, {{{"if_freenameindex"}, 1}, &MallocChecker::checkIfFreeNameIndex}, @@ -439,6 +446,8 @@ class MallocChecker std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, {{{"g_realloc_n"}, 3}, &MallocChecker::checkReallocN}, {{{"g_try_realloc_n"}, 3}, &MallocChecker::checkReallocN}, + {{{"getline"}, 3}, &MallocChecker::checkGetdelim}, + {{{"getdelim"}, 4}, &MallocChecker::checkGetdelim}, }; bool isMemCall(const CallEvent &Call) const; @@ -588,11 +597,14 @@ class MallocChecker /// } /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function /// we're modeling returns with Null on failure. + /// \param [in] ArgValOpt Optional value to use for the argument instead of + /// the one obtained from ArgExpr. /// \returns The ProgramState right after deallocation. [[nodiscard]] ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call, ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, - AllocationFamily Family, bool ReturnsNullOnFailure = false) const; + AllocationFamily Family, bool ReturnsNullOnFailure = false, + std::optional ArgValOpt = {}) const; // TODO: Needs some refactoring, as all other deallocation modeling // functions are suffering from out parameters and messy code due to how @@ -1423,6 +1435,46 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call, C.addTransition(State); } +void MallocChecker::preGetdelim(const CallEvent &Call, +CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; + + ProgramStateRef State = C.getState(); + const auto LinePtr = getPointeeDefVal(Call.getArgSVal(0), State); + if (!LinePtr) +return; + + bool IsKnownToBeAllocated = false; + State = FreeMemAux(C, Call.getArgExpr(0), Call, State, false, + IsKnownToBeAllocated, AF_Malloc, false, LinePtr); + if
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
https://github.com/alejandro-alvarez-sonarsource updated https://github.com/llvm/llvm-project/pull/83138 From ad17cfb588500d095b4e402bd2f2197772f89b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= Date: Wed, 21 Feb 2024 16:08:04 +0100 Subject: [PATCH 1/2] [clang][analyzer] Model allocation behavior or getdelim/geline --- .../Core/PathSensitive/CheckerHelpers.h | 6 ++ .../StaticAnalyzer/Checkers/MallocChecker.cpp | 73 +-- .../StaticAnalyzer/Core/CheckerHelpers.cpp| 9 +++ .../system-header-simulator-for-malloc.h | 1 + clang/test/Analysis/getline-alloc.c | 71 ++ 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 clang/test/Analysis/getline-alloc.c diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h index 65982457ad8393..60421e5437d82f 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h @@ -13,6 +13,9 @@ #ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H +#include "ProgramState_Fwd.h" +#include "SVals.h" + #include "clang/AST/OperationKinds.h" #include "clang/AST/Stmt.h" #include "clang/Basic/OperatorKinds.h" @@ -110,6 +113,9 @@ class OperatorKind { OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK, bool IsBinary); +std::optional getPointeeDefVal(SVal PtrSVal, +ProgramStateRef State); + } // namespace ento } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 79ab05f2c7866a..c5c382634c981c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -382,6 +382,8 @@ class MallocChecker CHECK_FN(checkGMemdup) CHECK_FN(checkGMallocN) CHECK_FN(checkGMallocN0) + CHECK_FN(preGetdelim) + CHECK_FN(checkGetdelim) CHECK_FN(checkReallocN) CHECK_FN(checkOwnershipAttr) @@ -391,6 +393,11 @@ class MallocChecker using CheckFn = std::function; + const CallDescriptionMap PreFnMap{ + {{{"getline"}, 3}, &MallocChecker::preGetdelim}, + {{{"getdelim"}, 4}, &MallocChecker::preGetdelim}, + }; + const CallDescriptionMap FreeingMemFnMap{ {{{"free"}, 1}, &MallocChecker::checkFree}, {{{"if_freenameindex"}, 1}, &MallocChecker::checkIfFreeNameIndex}, @@ -439,6 +446,8 @@ class MallocChecker std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, {{{"g_realloc_n"}, 3}, &MallocChecker::checkReallocN}, {{{"g_try_realloc_n"}, 3}, &MallocChecker::checkReallocN}, + {{{"getline"}, 3}, &MallocChecker::checkGetdelim}, + {{{"getdelim"}, 4}, &MallocChecker::checkGetdelim}, }; bool isMemCall(const CallEvent &Call) const; @@ -588,11 +597,14 @@ class MallocChecker /// } /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function /// we're modeling returns with Null on failure. + /// \param [in] ArgValOpt Optional value to use for the argument instead of + /// the one obtained from ArgExpr. /// \returns The ProgramState right after deallocation. [[nodiscard]] ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call, ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, - AllocationFamily Family, bool ReturnsNullOnFailure = false) const; + AllocationFamily Family, bool ReturnsNullOnFailure = false, + std::optional ArgValOpt = {}) const; // TODO: Needs some refactoring, as all other deallocation modeling // functions are suffering from out parameters and messy code due to how @@ -1423,6 +1435,46 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call, C.addTransition(State); } +void MallocChecker::preGetdelim(const CallEvent &Call, +CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; + + ProgramStateRef State = C.getState(); + const auto LinePtr = getPointeeDefVal(Call.getArgSVal(0), State); + if (!LinePtr) +return; + + bool IsKnownToBeAllocated = false; + State = FreeMemAux(C, Call.getArgExpr(0), Call, State, false, + IsKnownToBeAllocated, AF_Malloc, false, LinePtr); + if (State) +C.addTransition(State); +} + +void MallocChecker::checkGetdelim(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; + + ProgramStateRef State = C.getState(); + // Handle the post-conditions of getline and getdelim: + // Register the new conjured value
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
Alejandro =?utf-8?q?=C3=81lvarez_Ayll=C3=B3n?= Message-ID: In-Reply-To: https://github.com/NagyDonat approved this pull request. Thanks, this seems to be a good improvement! I added some minor remarks, but I didn't notice any serious issues. https://github.com/llvm/llvm-project/pull/83138 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
Alejandro =?utf-8?q?Álvarez_Ayllón?Message-ID: In-Reply-To: https://github.com/NagyDonat edited https://github.com/llvm/llvm-project/pull/83138 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
Alejandro =?utf-8?q?Álvarez_Ayllón?Message-ID: In-Reply-To: @@ -1423,6 +1435,46 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call, C.addTransition(State); } +void MallocChecker::preGetdelim(const CallEvent &Call, +CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; NagyDonat wrote: I'm in the middle of refactoring `CallDescription::matches()` and after that there will be a cleaner alternative instead of this kind of late check. (No action needed from you, I'll update this code along with all the other checkers.) https://github.com/llvm/llvm-project/pull/83138 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
Alejandro =?utf-8?q?Álvarez_Ayllón?Message-ID: In-Reply-To: @@ -1423,6 +1435,46 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call, C.addTransition(State); } +void MallocChecker::preGetdelim(const CallEvent &Call, +CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; + + ProgramStateRef State = C.getState(); + const auto LinePtr = getPointeeDefVal(Call.getArgSVal(0), State); + if (!LinePtr) +return; + + bool IsKnownToBeAllocated = false; + State = FreeMemAux(C, Call.getArgExpr(0), Call, State, false, + IsKnownToBeAllocated, AF_Malloc, false, LinePtr); + if (State) +C.addTransition(State); +} + +void MallocChecker::checkGetdelim(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; + + ProgramStateRef State = C.getState(); + // Handle the post-conditions of getline and getdelim: + // Register the new conjured value as an allocated buffer. + const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) +return; + + SValBuilder &svalBuilder = C.getSValBuilder(); NagyDonat wrote: For `SValBuilder` objects I prefer the other commonly used name `SVB`, which follows the convention that variable names start with a capital letter. However, this is also OK if you wish to be consistent with other functions in this file. https://github.com/llvm/llvm-project/pull/83138 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
Alejandro =?utf-8?q?Álvarez_Ayllón?Message-ID: In-Reply-To: @@ -110,6 +113,9 @@ class OperatorKind { OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK, bool IsBinary); +std::optional getPointeeDefVal(SVal PtrSVal, +ProgramStateRef State); NagyDonat wrote: Perhaps consider turning this helper function into a method of `SVal`, because it's a fairly generic operation and it would be easier to find there. https://github.com/llvm/llvm-project/pull/83138 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
Alejandro =?utf-8?q?Álvarez_Ayllón?Message-ID: In-Reply-To: @@ -1423,6 +1435,46 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call, C.addTransition(State); } +void MallocChecker::preGetdelim(const CallEvent &Call, +CheckerContext &C) const { + if (!Call.isGlobalCFunction()) +return; + + ProgramStateRef State = C.getState(); + const auto LinePtr = getPointeeDefVal(Call.getArgSVal(0), State); + if (!LinePtr) +return; + + bool IsKnownToBeAllocated = false; NagyDonat wrote: So if I understand it correctly, you declare this `bool` because `FreeMemAux` needs a reference that'll be use as an out parameter; but you don't actually use the value that's returned in it. Consider adding a comment that explains this, because out-parameters are not that common and the fact that this acts as an out-parameter is not marked (unlike the older style when out-parameters are implemented with pointers and when `&TargetVariable` is passed to a function you can guess that it's an out-parameter). Originally I thought that this is an in-parameter and you put it into a named variable to explain its purpose (as an alternative to the comment `/*IsKnownToBeAllocated=*/`). https://github.com/llvm/llvm-project/pull/83138 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [clang][analyzer] Model allocation behavior or getdelim/geline (PR #83138)
Alejandro =?utf-8?q?Álvarez_Ayllón?=, Alejandro =?utf-8?q?Álvarez_Ayllón?=, Alejandro =?utf-8?q?Álvarez_Ayllón?Message-ID: In-Reply-To: https://github.com/steakhal closed https://github.com/llvm/llvm-project/pull/83138 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits