Skip to content

Commit e1d4ddb

Browse files
NagyDonatsteakhal
andauthored
Reapply "[analyzer] Accept C library functions from the std namespace" again (#85791)
This reapplies 80ab823 again, after fixing a name collision warning in the unit tests (see the revert commit 13ccaf9 for details). In addition to the previously applied changes, this commit also clarifies the code in MallocChecker that distinguishes POSIX "getline()" and C++ standard library "std::getline()" (which are two completely different functions). Note that "std::getline()" was (accidentally) handled correctly even without this clarification; but it's better to explicitly handle and test this corner case. --------- Co-authored-by: Balazs Benics <[email protected]>
1 parent 1081d3a commit e1d4ddb

File tree

8 files changed

+139
-16
lines changed

8 files changed

+139
-16
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,8 @@ class CallDescription {
4141
/// - We also accept calls where the number of arguments or parameters is
4242
/// greater than the specified value.
4343
/// For the exact heuristics, see CheckerContext::isCLibraryFunction().
44-
/// Note that functions whose declaration context is not a TU (e.g.
45-
/// methods, functions in namespaces) are not accepted as C library
46-
/// functions.
47-
/// FIXME: If I understand it correctly, this discards calls where C++ code
48-
/// refers a C library function through the namespace `std::` via headers
49-
/// like <cstdlib>.
44+
/// (This mode only matches functions that are declared either directly
45+
/// within a TU or in the namespace `std`.)
5046
CLibrary,
5147

5248
/// Matches "simple" functions that are not methods. (Static methods are

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,10 @@ class MallocChecker
394394
const CallEvent &Call, CheckerContext &C)>;
395395

396396
const CallDescriptionMap<CheckFn> PreFnMap{
397-
{{{"getline"}, 3}, &MallocChecker::preGetdelim},
398-
{{{"getdelim"}, 4}, &MallocChecker::preGetdelim},
397+
// NOTE: the following CallDescription also matches the C++ standard
398+
// library function std::getline(); the callback will filter it out.
399+
{{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetdelim},
400+
{{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetdelim},
399401
};
400402

401403
const CallDescriptionMap<CheckFn> FreeingMemFnMap{
@@ -446,8 +448,11 @@ class MallocChecker
446448
std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)},
447449
{{{"g_realloc_n"}, 3}, &MallocChecker::checkReallocN},
448450
{{{"g_try_realloc_n"}, 3}, &MallocChecker::checkReallocN},
449-
{{{"getline"}, 3}, &MallocChecker::checkGetdelim},
450-
{{{"getdelim"}, 4}, &MallocChecker::checkGetdelim},
451+
452+
// NOTE: the following CallDescription also matches the C++ standard
453+
// library function std::getline(); the callback will filter it out.
454+
{{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetdelim},
455+
{{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::checkGetdelim},
451456
};
452457

453458
bool isMemCall(const CallEvent &Call) const;
@@ -1435,9 +1440,17 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call,
14351440
C.addTransition(State);
14361441
}
14371442

1443+
static bool isFromStdNamespace(const CallEvent &Call) {
1444+
const Decl *FD = Call.getDecl();
1445+
assert(FD && "a CallDescription cannot match a call without a Decl");
1446+
return FD->isInStdNamespace();
1447+
}
1448+
14381449
void MallocChecker::preGetdelim(const CallEvent &Call,
14391450
CheckerContext &C) const {
1440-
if (!Call.isGlobalCFunction())
1451+
// Discard calls to the C++ standard library function std::getline(), which
1452+
// is completely unrelated to the POSIX getline() that we're checking.
1453+
if (isFromStdNamespace(Call))
14411454
return;
14421455

14431456
ProgramStateRef State = C.getState();
@@ -1458,7 +1471,9 @@ void MallocChecker::preGetdelim(const CallEvent &Call,
14581471

14591472
void MallocChecker::checkGetdelim(const CallEvent &Call,
14601473
CheckerContext &C) const {
1461-
if (!Call.isGlobalCFunction())
1474+
// Discard calls to the C++ standard library function std::getline(), which
1475+
// is completely unrelated to the POSIX getline() that we're checking.
1476+
if (isFromStdNamespace(Call))
14621477
return;
14631478

14641479
ProgramStateRef State = C.getState();

clang/lib/StaticAnalyzer/Core/CheckerContext.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ bool CheckerContext::isCLibraryFunction(const FunctionDecl *FD,
8787
if (!II)
8888
return false;
8989

90-
// Look through 'extern "C"' and anything similar invented in the future.
91-
// If this function is not in TU directly, it is not a C library function.
92-
if (!FD->getDeclContext()->getRedeclContext()->isTranslationUnit())
90+
// C library functions are either declared directly within a TU (the common
91+
// case) or they are accessed through the namespace `std` (when they are used
92+
// in C++ via headers like <cstdlib>).
93+
const DeclContext *DC = FD->getDeclContext()->getRedeclContext();
94+
if (!(DC->isTranslationUnit() || DC->isStdNamespace()))
9395
return false;
9496

9597
// If this function is not externally visible, it is not a C library function.

clang/test/Analysis/Inputs/system-header-simulator-cxx.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1106,11 +1106,20 @@ using ostream = basic_ostream<char>;
11061106
extern std::ostream cout;
11071107

11081108
ostream &operator<<(ostream &, const string &);
1109-
11101109
#if __cplusplus >= 202002L
11111110
template <class T>
11121111
ostream &operator<<(ostream &, const std::unique_ptr<T> &);
11131112
#endif
1113+
1114+
template <class CharT>
1115+
class basic_istream;
1116+
1117+
using istream = basic_istream<char>;
1118+
1119+
extern std::istream cin;
1120+
1121+
istream &getline(istream &, string &, char);
1122+
istream &getline(istream &, string &);
11141123
} // namespace std
11151124

11161125
#ifdef TEST_INLINABLE_ALLOCATORS

clang/test/Analysis/getline-cpp.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix,debug.ExprInspection -verify %s
2+
3+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix,alpha.unix,debug.ExprInspection -verify %s
4+
//
5+
// expected-no-diagnostics
6+
7+
#include "Inputs/system-header-simulator-cxx.h"
8+
9+
void test_std_getline() {
10+
std::string userid, comment;
11+
// MallocChecker should not confuse the POSIX function getline() and the
12+
// unrelated C++ standard library function std::getline.
13+
std::getline(std::cin, userid, ' '); // no-crash
14+
std::getline(std::cin, comment); // no-crash
15+
}

clang/unittests/StaticAnalyzer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_clang_unittest(StaticAnalysisTests
1111
CallEventTest.cpp
1212
ConflictingEvalCallsTest.cpp
1313
FalsePositiveRefutationBRVisitorTest.cpp
14+
IsCLibraryFunctionTest.cpp
1415
MemRegionDescriptiveNameTest.cpp
1516
NoStateChangeFuncVisitorTest.cpp
1617
ParamRegionTest.cpp
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include "clang/ASTMatchers/ASTMatchFinder.h"
2+
#include "clang/ASTMatchers/ASTMatchers.h"
3+
#include "clang/Analysis/AnalysisDeclContext.h"
4+
#include "clang/Frontend/ASTUnit.h"
5+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
6+
#include "clang/Tooling/Tooling.h"
7+
#include "gtest/gtest.h"
8+
9+
#include <memory>
10+
11+
using namespace clang;
12+
using namespace ento;
13+
using namespace ast_matchers;
14+
15+
class IsCLibraryFunctionTest : public testing::Test {
16+
std::unique_ptr<ASTUnit> ASTUnitP;
17+
const FunctionDecl *Result = nullptr;
18+
19+
public:
20+
const FunctionDecl *getFunctionDecl() const { return Result; }
21+
22+
testing::AssertionResult buildAST(StringRef Code) {
23+
ASTUnitP = tooling::buildASTFromCode(Code);
24+
if (!ASTUnitP)
25+
return testing::AssertionFailure() << "AST construction failed";
26+
27+
ASTContext &Context = ASTUnitP->getASTContext();
28+
if (Context.getDiagnostics().hasErrorOccurred())
29+
return testing::AssertionFailure() << "Compilation error";
30+
31+
auto Matches = ast_matchers::match(functionDecl().bind("fn"), Context);
32+
if (Matches.empty())
33+
return testing::AssertionFailure() << "No function declaration found";
34+
35+
if (Matches.size() > 1)
36+
return testing::AssertionFailure()
37+
<< "Multiple function declarations found";
38+
39+
Result = Matches[0].getNodeAs<FunctionDecl>("fn");
40+
return testing::AssertionSuccess();
41+
}
42+
};
43+
44+
TEST_F(IsCLibraryFunctionTest, AcceptsGlobal) {
45+
ASSERT_TRUE(buildAST(R"cpp(void fun();)cpp"));
46+
EXPECT_TRUE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
47+
}
48+
49+
TEST_F(IsCLibraryFunctionTest, AcceptsExternCGlobal) {
50+
ASSERT_TRUE(buildAST(R"cpp(extern "C" { void fun(); })cpp"));
51+
EXPECT_TRUE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
52+
}
53+
54+
TEST_F(IsCLibraryFunctionTest, RejectsNoInlineNoExternalLinkage) {
55+
// Functions that are neither inlined nor externally visible cannot be C
56+
// library functions.
57+
ASSERT_TRUE(buildAST(R"cpp(static void fun();)cpp"));
58+
EXPECT_FALSE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
59+
}
60+
61+
TEST_F(IsCLibraryFunctionTest, RejectsAnonymousNamespace) {
62+
ASSERT_TRUE(buildAST(R"cpp(namespace { void fun(); })cpp"));
63+
EXPECT_FALSE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
64+
}
65+
66+
TEST_F(IsCLibraryFunctionTest, AcceptsStdNamespace) {
67+
ASSERT_TRUE(buildAST(R"cpp(namespace std { void fun(); })cpp"));
68+
EXPECT_TRUE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
69+
}
70+
71+
TEST_F(IsCLibraryFunctionTest, RejectsOtherNamespaces) {
72+
ASSERT_TRUE(buildAST(R"cpp(namespace stdx { void fun(); })cpp"));
73+
EXPECT_FALSE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
74+
}
75+
76+
TEST_F(IsCLibraryFunctionTest, RejectsClassStatic) {
77+
ASSERT_TRUE(buildAST(R"cpp(class A { static void fun(); };)cpp"));
78+
EXPECT_FALSE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
79+
}
80+
81+
TEST_F(IsCLibraryFunctionTest, RejectsClassMember) {
82+
ASSERT_TRUE(buildAST(R"cpp(class A { void fun(); };)cpp"));
83+
EXPECT_FALSE(CheckerContext::isCLibraryFunction(getFunctionDecl()));
84+
}

llvm/utils/gn/secondary/clang/unittests/StaticAnalyzer/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ unittest("StaticAnalysisTests") {
1919
"CallEventTest.cpp",
2020
"ConflictingEvalCallsTest.cpp",
2121
"FalsePositiveRefutationBRVisitorTest.cpp",
22+
"IsCLibraryFunctionTest.cpp",
2223
"MemRegionDescriptiveNameTest.cpp",
2324
"NoStateChangeFuncVisitorTest.cpp",
2425
"ParamRegionTest.cpp",

0 commit comments

Comments
 (0)