Skip to content

Commit a36c2dd

Browse files
committed
[clang][dataflow] Add modeling of Chromium's CHECK functionality
Chromium's implementation of assertions (`CHECK`, `DCHECK`, etc.) are not annotated with "noreturn", by default. This patch adds a model of the logical implications of successfully executing one of these assertions. Differential Revision: https://reviews.llvm.org/D121797
1 parent b58413d commit a36c2dd

File tree

5 files changed

+327
-0
lines changed

5 files changed

+327
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===-- ChromiumCheckModel.h ------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines a dataflow model for Chromium's family of CHECK functions.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
#ifndef CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_CHROMIUMCHECKMODEL_H
13+
#define CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_CHROMIUMCHECKMODEL_H
14+
15+
#include "clang/AST/DeclCXX.h"
16+
#include "clang/AST/Stmt.h"
17+
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
18+
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
19+
#include "llvm/ADT/DenseSet.h"
20+
21+
namespace clang {
22+
namespace dataflow {
23+
24+
/// Models the behavior of Chromium's CHECK, DCHECK, etc. macros, so that code
25+
/// after a call to `*CHECK` can rely on the condition being true.
26+
class ChromiumCheckModel : public DataflowModel {
27+
public:
28+
ChromiumCheckModel() = default;
29+
bool transfer(const Stmt *Stmt, Environment &Env) override;
30+
31+
private:
32+
/// Declarations for `::logging::CheckError::.*Check`, lazily initialized.
33+
llvm::SmallDenseSet<const CXXMethodDecl *> CheckDecls;
34+
};
35+
36+
} // namespace dataflow
37+
} // namespace clang
38+
39+
#endif // CLANG_ANALYSIS_FLOWSENSITIVE_MODELS_CHROMIUMCHECKMODEL_H

clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
add_clang_library(clangAnalysisFlowSensitiveModels
2+
ChromiumCheckModel.cpp
23
UncheckedOptionalAccessModel.cpp
34

45
LINK_LIBS
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//===-- ChromiumCheckModel.cpp ----------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "clang/Analysis/FlowSensitive/Models/ChromiumCheckModel.h"
10+
#include "clang/AST/Decl.h"
11+
#include "clang/AST/DeclCXX.h"
12+
#include "llvm/ADT/DenseSet.h"
13+
14+
namespace clang {
15+
namespace dataflow {
16+
17+
/// Determines whether `D` is one of the methods used to implement Chromium's
18+
/// `CHECK` macros. Populates `CheckDecls`, if empty.
19+
bool isCheckLikeMethod(llvm::SmallDenseSet<const CXXMethodDecl *> &CheckDecls,
20+
const CXXMethodDecl &D) {
21+
// All of the methods of interest are static, so avoid any lookup for
22+
// non-static methods (the common case).
23+
if (!D.isStatic())
24+
return false;
25+
26+
if (CheckDecls.empty()) {
27+
// Attempt to initialize `CheckDecls` with the methods in class
28+
// `CheckError`.
29+
const CXXRecordDecl *ParentClass = D.getParent();
30+
if (ParentClass == nullptr || !ParentClass->getDeclName().isIdentifier() ||
31+
ParentClass->getName() != "CheckError")
32+
return false;
33+
34+
// Check whether namespace is "logging".
35+
const auto *N =
36+
dyn_cast_or_null<NamespaceDecl>(ParentClass->getDeclContext());
37+
if (N == nullptr || !N->getDeclName().isIdentifier() ||
38+
N->getName() != "logging")
39+
return false;
40+
41+
// Check whether "logging" is a top-level namespace.
42+
if (N->getParent() == nullptr || !N->getParent()->isTranslationUnit())
43+
return false;
44+
45+
for (const CXXMethodDecl *M : ParentClass->methods())
46+
if (M->getDeclName().isIdentifier() && M->getName().endswith("Check"))
47+
CheckDecls.insert(M);
48+
}
49+
50+
return CheckDecls.contains(&D);
51+
}
52+
53+
bool ChromiumCheckModel::transfer(const Stmt *Stmt, Environment &Env) {
54+
if (const auto *Call = dyn_cast<CallExpr>(Stmt)) {
55+
if (const auto *M = dyn_cast<CXXMethodDecl>(Call->getDirectCallee())) {
56+
if (isCheckLikeMethod(CheckDecls, *M)) {
57+
// Mark this branch as unreachable.
58+
Env.addToFlowCondition(Env.getBoolLiteralValue(false));
59+
return true;
60+
}
61+
}
62+
}
63+
return false;
64+
}
65+
66+
} // namespace dataflow
67+
} // namespace clang

clang/unittests/Analysis/FlowSensitive/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
44
)
55

66
add_clang_unittest(ClangAnalysisFlowSensitiveTests
7+
ChromiumCheckModelTest.cpp
78
DataflowAnalysisContextTest.cpp
89
DataflowEnvironmentTest.cpp
910
MapLatticeTest.cpp
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//===- ChromiumCheckModelTest.cpp -----------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models.
9+
10+
#include "clang/Analysis/FlowSensitive/Models/ChromiumCheckModel.h"
11+
#include "NoopAnalysis.h"
12+
#include "TestingSupport.h"
13+
#include "clang/AST/ASTContext.h"
14+
#include "clang/ASTMatchers/ASTMatchers.h"
15+
#include "clang/Tooling/Tooling.h"
16+
#include "llvm/ADT/ArrayRef.h"
17+
#include "llvm/ADT/StringExtras.h"
18+
#include "llvm/Support/Error.h"
19+
#include "llvm/Testing/Support/Error.h"
20+
#include "gmock/gmock.h"
21+
#include "gtest/gtest.h"
22+
#include <string>
23+
24+
using namespace clang;
25+
using namespace dataflow;
26+
using namespace test;
27+
28+
namespace {
29+
using ::testing::_;
30+
using ::testing::ElementsAre;
31+
using ::testing::NotNull;
32+
using ::testing::Pair;
33+
34+
static constexpr char ChromiumCheckHeader[] = R"(
35+
namespace std {
36+
class ostream;
37+
} // namespace std
38+
39+
namespace logging {
40+
class VoidifyStream {
41+
public:
42+
VoidifyStream() = default;
43+
void operator&(std::ostream&) {}
44+
};
45+
46+
class CheckError {
47+
public:
48+
static CheckError Check(const char* file, int line, const char* condition);
49+
static CheckError DCheck(const char* file, int line, const char* condition);
50+
static CheckError PCheck(const char* file, int line, const char* condition);
51+
static CheckError PCheck(const char* file, int line);
52+
static CheckError DPCheck(const char* file, int line, const char* condition);
53+
54+
std::ostream& stream();
55+
56+
~CheckError();
57+
58+
CheckError(const CheckError& other) = delete;
59+
CheckError& operator=(const CheckError& other) = delete;
60+
CheckError(CheckError&& other) = default;
61+
CheckError& operator=(CheckError&& other) = default;
62+
};
63+
64+
} // namespace logging
65+
66+
#define LAZY_CHECK_STREAM(stream, condition) \
67+
!(condition) ? (void)0 : ::logging::VoidifyStream() & (stream)
68+
69+
#define CHECK(condition) \
70+
LAZY_CHECK_STREAM( \
71+
::logging::CheckError::Check(__FILE__, __LINE__, #condition).stream(), \
72+
!(condition))
73+
74+
#define PCHECK(condition) \
75+
LAZY_CHECK_STREAM( \
76+
::logging::CheckError::PCheck(__FILE__, __LINE__, #condition).stream(), \
77+
!(condition))
78+
79+
#define DCHECK(condition) \
80+
LAZY_CHECK_STREAM( \
81+
::logging::CheckError::DCheck(__FILE__, __LINE__, #condition).stream(), \
82+
!(condition))
83+
84+
#define DPCHECK(condition) \
85+
LAZY_CHECK_STREAM( \
86+
::logging::CheckError::DPCheck(__FILE__, __LINE__, #condition).stream(), \
87+
!(condition))
88+
)";
89+
90+
// A definition of the `CheckError` class that looks like the Chromium one, but
91+
// is actually something else.
92+
static constexpr char OtherCheckHeader[] = R"(
93+
namespace other {
94+
namespace logging {
95+
class CheckError {
96+
public:
97+
static CheckError Check(const char* file, int line, const char* condition);
98+
};
99+
} // namespace logging
100+
} // namespace other
101+
)";
102+
103+
/// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
104+
std::string ReplacePattern(std::string S, const std::string &Pattern,
105+
const std::string &Replacement) {
106+
size_t Pos = 0;
107+
Pos = S.find(Pattern, Pos);
108+
if (Pos != std::string::npos)
109+
S.replace(Pos, Pattern.size(), Replacement);
110+
return S;
111+
}
112+
113+
template <typename Model>
114+
class ModelAdaptorAnalysis
115+
: public DataflowAnalysis<ModelAdaptorAnalysis<Model>, NoopLattice> {
116+
public:
117+
explicit ModelAdaptorAnalysis(ASTContext &Context)
118+
: DataflowAnalysis<ModelAdaptorAnalysis, NoopLattice>(
119+
Context, /*ApplyBuiltinTransfer=*/true) {}
120+
121+
static NoopLattice initialElement() { return NoopLattice(); }
122+
123+
void transfer(const Stmt *S, NoopLattice &, Environment &Env) {
124+
M.transfer(S, Env);
125+
}
126+
127+
private:
128+
Model M;
129+
};
130+
131+
class ChromiumCheckModelTest : public ::testing::TestWithParam<std::string> {
132+
protected:
133+
template <typename Matcher>
134+
void runDataflow(llvm::StringRef Code, Matcher Match) {
135+
const tooling::FileContentMappings FileContents = {
136+
{"check.h", ChromiumCheckHeader}, {"othercheck.h", OtherCheckHeader}};
137+
138+
ASSERT_THAT_ERROR(
139+
test::checkDataflow<ModelAdaptorAnalysis<ChromiumCheckModel>>(
140+
Code, "target",
141+
[](ASTContext &C, Environment &) {
142+
return ModelAdaptorAnalysis<ChromiumCheckModel>(C);
143+
},
144+
[&Match](
145+
llvm::ArrayRef<
146+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
147+
Results,
148+
ASTContext &ASTCtx) { Match(Results, ASTCtx); },
149+
{"-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"},
150+
FileContents),
151+
llvm::Succeeded());
152+
}
153+
};
154+
155+
TEST_F(ChromiumCheckModelTest, CheckSuccessImpliesConditionHolds) {
156+
auto Expectations =
157+
[](llvm::ArrayRef<
158+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
159+
Results,
160+
ASTContext &ASTCtx) {
161+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
162+
const Environment &Env = Results[0].second.Env;
163+
164+
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
165+
ASSERT_THAT(FooDecl, NotNull());
166+
167+
auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
168+
169+
EXPECT_TRUE(Env.flowConditionImplies(*FooVal));
170+
};
171+
172+
std::string Code = R"(
173+
#include "check.h"
174+
175+
void target(bool Foo) {
176+
$check(Foo);
177+
bool X = true;
178+
(void)X;
179+
// [[p]]
180+
}
181+
)";
182+
runDataflow(ReplacePattern(Code, "$check", "CHECK"), Expectations);
183+
runDataflow(ReplacePattern(Code, "$check", "DCHECK"), Expectations);
184+
runDataflow(ReplacePattern(Code, "$check", "PCHECK"), Expectations);
185+
runDataflow(ReplacePattern(Code, "$check", "DPCHECK"), Expectations);
186+
}
187+
188+
TEST_F(ChromiumCheckModelTest, UnrelatedCheckIgnored) {
189+
auto Expectations =
190+
[](llvm::ArrayRef<
191+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
192+
Results,
193+
ASTContext &ASTCtx) {
194+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
195+
const Environment &Env = Results[0].second.Env;
196+
197+
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
198+
ASSERT_THAT(FooDecl, NotNull());
199+
200+
auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
201+
202+
EXPECT_FALSE(Env.flowConditionImplies(*FooVal));
203+
};
204+
205+
std::string Code = R"(
206+
#include "othercheck.h"
207+
208+
void target(bool Foo) {
209+
if (!Foo) {
210+
(void)other::logging::CheckError::Check(__FILE__, __LINE__, "Foo");
211+
}
212+
bool X = true;
213+
(void)X;
214+
// [[p]]
215+
}
216+
)";
217+
runDataflow(Code, Expectations);
218+
}
219+
} // namespace

0 commit comments

Comments
 (0)