Skip to content

Commit 829bcb0

Browse files
committed
[-Wunsafe-buffer-usage] Add unsafe buffer checking opt-out pragmas
Add a pair of clang pragmas: - `#pragma clang unsafe_buffer_usage begin` and - `#pragma clang unsafe_buffer_usage end`, which specify the start and end of an (unsafe buffer checking) opt-out region, respectively. Behaviors of opt-out regions conform to the following rules: - No nested nor overlapped opt-out regions are allowed. One cannot start an opt-out region with `... unsafe_buffer_usage begin` but never close it with `... unsafe_buffer_usage end`. Mis-use of the pragmas will be warned. - Warnings raised from unsafe buffer operations inside such an opt-out region will always be suppressed. This behavior CANNOT be changed by `clang diagnostic` pragmas or command-line flags. - Warnings raised from unsafe operations outside of such opt-out regions may be reported on declarations inside opt-out regions. These warnings are NOT suppressed. - An un-suppressed unsafe operation warning may be attached with notes. These notes are NOT suppressed as well regardless of whether they are in opt-out regions. The implementation maintains a separate sequence of location pairs representing opt-out regions in `Preprocessor`. The `UnsafeBufferUsage` analyzer reads the region sequence to check if an unsafe operation is in an opt-out region. If it is, discard the warning raised from the operation immediately. This is a re-land after I reverting it at 9aa00c8. The compilation error should be resolved. Reviewed by: NoQ Differential revision: https://reviews.llvm.org/D140179
1 parent a150766 commit 829bcb0

12 files changed

+434
-3
lines changed

clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ class UnsafeBufferUsageHandler {
3838
virtual void handleFixableVariable(const VarDecl *Variable,
3939
FixItList &&List) = 0;
4040

41+
/// Returns a reference to the `Preprocessor`:
42+
virtual bool isSafeBufferOptOut(const SourceLocation &Loc) const = 0;
43+
4144
/// Returns the text indicating that the user needs to provide input there:
4245
virtual std::string
4346
getUserFillPlaceHolder(StringRef HintTextToUser = "placeholder") {

clang/include/clang/Basic/DiagnosticLexKinds.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,4 +945,15 @@ def err_dep_source_scanner_unexpected_tokens_at_import : Error<
945945

946946
}
947947

948+
def err_pp_double_begin_pragma_unsafe_buffer_usage :
949+
Error<"already inside '#pragma unsafe_buffer_usage'">;
950+
951+
def err_pp_unmatched_end_begin_pragma_unsafe_buffer_usage :
952+
Error<"not currently inside '#pragma unsafe_buffer_usage'">;
953+
954+
def err_pp_unclosed_pragma_unsafe_buffer_usage :
955+
Error<"'#pragma unsafe_buffer_usage' was not ended">;
956+
957+
def err_pp_pragma_unsafe_buffer_usage_syntax :
958+
Error<"Expected 'begin' or 'end'">;
948959
}

clang/include/clang/Lex/Preprocessor.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2688,6 +2688,51 @@ class Preprocessor {
26882688
void emitMacroDeprecationWarning(const Token &Identifier) const;
26892689
void emitRestrictExpansionWarning(const Token &Identifier) const;
26902690
void emitFinalMacroWarning(const Token &Identifier, bool IsUndef) const;
2691+
2692+
/// This boolean state keeps track if the current scanned token (by this PP)
2693+
/// is in an "-Wunsafe-buffer-usage" opt-out region. Assuming PP scans a
2694+
/// translation unit in a linear order.
2695+
bool InSafeBufferOptOutRegion = 0;
2696+
2697+
/// Hold the start location of the current "-Wunsafe-buffer-usage" opt-out
2698+
/// region if PP is currently in such a region. Hold undefined value
2699+
/// otherwise.
2700+
SourceLocation CurrentSafeBufferOptOutStart; // It is used to report the start location of an never-closed region.
2701+
2702+
// An ordered sequence of "-Wunsafe-buffer-usage" opt-out regions in one
2703+
// translation unit. Each region is represented by a pair of start and end
2704+
// locations. A region is "open" if its' start and end locations are
2705+
// identical.
2706+
SmallVector<std::pair<SourceLocation, SourceLocation>, 8> SafeBufferOptOutMap;
2707+
2708+
public:
2709+
/// \return true iff the given `Loc` is in a "-Wunsafe-buffer-usage" opt-out
2710+
/// region. This `Loc` must be a source location that has been pre-processed.
2711+
bool isSafeBufferOptOut(const SourceManager&SourceMgr, const SourceLocation &Loc) const;
2712+
2713+
/// Alter the state of whether this PP currently is in a
2714+
/// "-Wunsafe-buffer-usage" opt-out region.
2715+
///
2716+
/// \param isEnter: true if this PP is entering a region; otherwise, this PP
2717+
/// is exiting a region
2718+
/// \param Loc: the location of the entry or exit of a
2719+
/// region
2720+
/// \return true iff it is INVALID to enter or exit a region, i.e.,
2721+
/// attempt to enter a region before exiting a previous region, or exiting a
2722+
/// region that PP is not currently in.
2723+
bool enterOrExitSafeBufferOptOutRegion(bool isEnter,
2724+
const SourceLocation &Loc);
2725+
2726+
/// \return true iff this PP is currently in a "-Wunsafe-buffer-usage"
2727+
/// opt-out region
2728+
bool isPPInSafeBufferOptOutRegion();
2729+
2730+
/// \param StartLoc: output argument. It will be set to the start location of
2731+
/// the current "-Wunsafe-buffer-usage" opt-out region iff this function
2732+
/// returns true.
2733+
/// \return true iff this PP is currently in a "-Wunsafe-buffer-usage"
2734+
/// opt-out region
2735+
bool isPPInSafeBufferOptOutRegion(SourceLocation &StartLoc);
26912736
};
26922737

26932738
/// Abstract base class that describes a handler that will receive

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "clang/Analysis/Analyses/UnsafeBufferUsage.h"
1010
#include "clang/AST/RecursiveASTVisitor.h"
1111
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
#include "clang/Lex/Preprocessor.h"
1213
#include "clang/Lex/Lexer.h"
1314
#include "llvm/ADT/SmallVector.h"
1415
#include <memory>
@@ -117,6 +118,11 @@ AST_MATCHER_P(Stmt, forEveryDescendant, internal::Matcher<Stmt>, innerMatcher) {
117118
return Visitor.findMatch(DynTypedNode::create(Node));
118119
}
119120

121+
// Matches a `Stmt` node iff the node is in a safe-buffer opt-out region
122+
AST_MATCHER_P(Stmt, notInSafeBufferOptOut, const UnsafeBufferUsageHandler *, Handler) {
123+
return !Handler->isSafeBufferOptOut(Node.getBeginLoc());
124+
}
125+
120126
AST_MATCHER_P(CastExpr, castSubExpr, internal::Matcher<Expr>, innerMatcher) {
121127
return innerMatcher.matches(*Node.getSubExpr(), Finder, Builder);
122128
}
@@ -548,7 +554,8 @@ class Strategy {
548554
} // namespace
549555

550556
/// Scan the function and return a list of gadgets found with provided kits.
551-
static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker> findGadgets(const Decl *D) {
557+
static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>
558+
findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler) {
552559

553560
struct GadgetFinderCallback : MatchFinder::MatchCallback {
554561
FixableGadgetList FixableGadgets;
@@ -620,7 +627,7 @@ static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker> findGadg
620627
stmt(anyOf(
621628
// Add Gadget::matcher() for every gadget in the registry.
622629
#define WARNING_GADGET(x) \
623-
x ## Gadget::matcher().bind(#x),
630+
allOf(x ## Gadget::matcher().bind(#x), notInSafeBufferOptOut(&Handler)),
624631
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
625632
// In parallel, match all DeclRefExprs so that to find out
626633
// whether there are any uncovered by gadgets.
@@ -1009,7 +1016,7 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
10091016
DeclUseTracker Tracker;
10101017

10111018
{
1012-
auto [FixableGadgets, WarningGadgets, TrackerRes] = findGadgets(D);
1019+
auto [FixableGadgets, WarningGadgets, TrackerRes] = findGadgets(D, Handler);
10131020
UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
10141021
FixablesForUnsafeVars = groupFixablesByVar(std::move(FixableGadgets));
10151022
Tracker = std::move(TrackerRes);

clang/lib/Lex/PPLexerChange.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,15 @@ bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) {
333333
assert(!CurTokenLexer &&
334334
"Ending a file when currently in a macro!");
335335

336+
SourceLocation UnclosedSafeBufferOptOutLoc;
337+
338+
if (IncludeMacroStack.empty() &&
339+
isPPInSafeBufferOptOutRegion(UnclosedSafeBufferOptOutLoc)) {
340+
// To warn if a "-Wunsafe-buffer-usage" opt-out region is still open by the
341+
// end of a file.
342+
Diag(UnclosedSafeBufferOptOutLoc,
343+
diag::err_pp_unclosed_pragma_unsafe_buffer_usage);
344+
}
336345
// If we have an unclosed module region from a pragma at the end of a
337346
// module, complain and close it now.
338347
const bool LeavingSubmodule = CurLexer && CurLexerSubmodule;

clang/lib/Lex/Pragma.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,32 @@ struct PragmaDebugHandler : public PragmaHandler {
12431243
#endif
12441244
};
12451245

1246+
struct PragmaUnsafeBufferUsageHandler : public PragmaHandler {
1247+
PragmaUnsafeBufferUsageHandler() : PragmaHandler("unsafe_buffer_usage") {}
1248+
void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
1249+
Token &FirstToken) override {
1250+
Token Tok;
1251+
1252+
PP.LexUnexpandedToken(Tok);
1253+
if (Tok.isNot(tok::identifier)) {
1254+
PP.Diag(Tok, diag::err_pp_pragma_unsafe_buffer_usage_syntax);
1255+
return;
1256+
}
1257+
1258+
IdentifierInfo *II = Tok.getIdentifierInfo();
1259+
SourceLocation Loc = Tok.getLocation();
1260+
1261+
if (II->isStr("begin")) {
1262+
if (PP.enterOrExitSafeBufferOptOutRegion(true, Loc))
1263+
PP.Diag(Loc, diag::err_pp_double_begin_pragma_unsafe_buffer_usage);
1264+
} else if (II->isStr("end")) {
1265+
if (PP.enterOrExitSafeBufferOptOutRegion(false, Loc))
1266+
PP.Diag(Loc, diag::err_pp_unmatched_end_begin_pragma_unsafe_buffer_usage);
1267+
} else
1268+
PP.Diag(Tok, diag::err_pp_pragma_unsafe_buffer_usage_syntax);
1269+
}
1270+
};
1271+
12461272
/// PragmaDiagnosticHandler - e.g. '\#pragma GCC diagnostic ignored "-Wformat"'
12471273
struct PragmaDiagnosticHandler : public PragmaHandler {
12481274
private:
@@ -2128,6 +2154,9 @@ void Preprocessor::RegisterBuiltinPragmas() {
21282154
ModuleHandler->AddPragma(new PragmaModuleBuildHandler());
21292155
ModuleHandler->AddPragma(new PragmaModuleLoadHandler());
21302156

2157+
// Safe Buffers pragmas
2158+
AddPragmaHandler("clang", new PragmaUnsafeBufferUsageHandler);
2159+
21312160
// Add region pragmas.
21322161
AddPragmaHandler(new PragmaRegionHandler("region"));
21332162
AddPragmaHandler(new PragmaRegionHandler("endregion"));

clang/lib/Lex/Preprocessor.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,75 @@ void Preprocessor::emitFinalMacroWarning(const Token &Identifier,
14621462
Diag(*A.FinalAnnotationLoc, diag::note_pp_macro_annotation) << 2;
14631463
}
14641464

1465+
bool Preprocessor::isSafeBufferOptOut(const SourceManager &SourceMgr,
1466+
const SourceLocation &Loc) const {
1467+
// Try to find a region in `SafeBufferOptOutMap` where `Loc` is in:
1468+
auto FirstRegionEndingAfterLoc = llvm::partition_point(
1469+
SafeBufferOptOutMap,
1470+
[&SourceMgr,
1471+
&Loc](const std::pair<SourceLocation, SourceLocation> &Region) {
1472+
return SourceMgr.isBeforeInTranslationUnit(Region.second, Loc);
1473+
});
1474+
1475+
if (FirstRegionEndingAfterLoc != SafeBufferOptOutMap.end()) {
1476+
// To test if the start location of the found region precedes `Loc`:
1477+
return SourceMgr.isBeforeInTranslationUnit(FirstRegionEndingAfterLoc->first,
1478+
Loc);
1479+
}
1480+
// If we do not find a region whose end location passes `Loc`, we want to
1481+
// check if the current region is still open:
1482+
if (!SafeBufferOptOutMap.empty() &&
1483+
SafeBufferOptOutMap.back().first == SafeBufferOptOutMap.back().second)
1484+
return SourceMgr.isBeforeInTranslationUnit(SafeBufferOptOutMap.back().first,
1485+
Loc);
1486+
return false;
1487+
}
1488+
1489+
bool Preprocessor::enterOrExitSafeBufferOptOutRegion(
1490+
bool isEnter, const SourceLocation &Loc) {
1491+
if (isEnter) {
1492+
if (isPPInSafeBufferOptOutRegion())
1493+
return true; // invalid enter action
1494+
InSafeBufferOptOutRegion = true;
1495+
CurrentSafeBufferOptOutStart = Loc;
1496+
1497+
// To set the start location of a new region:
1498+
1499+
if (!SafeBufferOptOutMap.empty()) {
1500+
auto *PrevRegion = &SafeBufferOptOutMap.back();
1501+
assert(PrevRegion->first != PrevRegion->second &&
1502+
"Shall not begin a safe buffer opt-out region before closing the "
1503+
"previous one.");
1504+
}
1505+
// If the start location equals to the end location, we call the region a
1506+
// open region or a unclosed region (i.e., end location has not been set
1507+
// yet).
1508+
SafeBufferOptOutMap.emplace_back(Loc, Loc);
1509+
} else {
1510+
if (!isPPInSafeBufferOptOutRegion())
1511+
return true; // invalid enter action
1512+
InSafeBufferOptOutRegion = false;
1513+
1514+
// To set the end location of the current open region:
1515+
1516+
assert(!SafeBufferOptOutMap.empty() &&
1517+
"Misordered safe buffer opt-out regions");
1518+
auto *CurrRegion = &SafeBufferOptOutMap.back();
1519+
assert(CurrRegion->first == CurrRegion->second &&
1520+
"Set end location to a closed safe buffer opt-out region");
1521+
CurrRegion->second = Loc;
1522+
}
1523+
return false;
1524+
}
1525+
1526+
bool Preprocessor::isPPInSafeBufferOptOutRegion() {
1527+
return InSafeBufferOptOutRegion;
1528+
}
1529+
bool Preprocessor::isPPInSafeBufferOptOutRegion(SourceLocation &StartLoc) {
1530+
StartLoc = CurrentSafeBufferOptOutStart;
1531+
return InSafeBufferOptOutRegion;
1532+
}
1533+
14651534
ModuleLoader::~ModuleLoader() = default;
14661535

14671536
CommentHandler::~CommentHandler() = default;

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,6 +2213,10 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22132213
FD << F;
22142214
}
22152215
}
2216+
2217+
bool isSafeBufferOptOut(const SourceLocation &Loc) const override {
2218+
return S.PP.isSafeBufferOptOut(S.getSourceManager(), Loc);
2219+
}
22162220
};
22172221
} // namespace
22182222

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
2+
3+
void basic(int * x) {
4+
int tmp;
5+
int *p1 = new int[10]; // no fix
6+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
7+
int *p2 = new int[10];
8+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:12}:"std::span<int> p2"
9+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:13-[[@LINE-2]]:13}:"{"
10+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:24-[[@LINE-3]]:24}:", 10}"
11+
#pragma clang unsafe_buffer_usage begin
12+
tmp = p1[5];
13+
#pragma clang unsafe_buffer_usage end
14+
tmp = p2[5];
15+
}
16+
17+
void withDiagnosticWarning() {
18+
int tmp;
19+
int *p1 = new int[10]; // no fix
20+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
21+
int *p2 = new int[10];
22+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:12}:"std::span<int> p2"
23+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:13-[[@LINE-2]]:13}:"{"
24+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:24-[[@LINE-3]]:24}:", 10}"
25+
26+
// diagnostics in opt-out region
27+
#pragma clang unsafe_buffer_usage begin
28+
tmp = p1[5]; // not to warn
29+
tmp = p2[5]; // not to warn
30+
#pragma clang diagnostic push
31+
#pragma clang diagnostic warning "-Wunsafe-buffer-usage"
32+
tmp = p1[5]; // not to warn
33+
tmp = p2[5]; // not to warn
34+
#pragma clang diagnostic warning "-Weverything"
35+
tmp = p1[5]; // not to warn
36+
tmp = p2[5]; // not to warn
37+
#pragma clang diagnostic pop
38+
#pragma clang unsafe_buffer_usage end
39+
40+
// opt-out region under diagnostic warning
41+
#pragma clang diagnostic push
42+
#pragma clang diagnostic warning "-Wunsafe-buffer-usage"
43+
#pragma clang unsafe_buffer_usage begin
44+
tmp = p1[5]; // not to warn
45+
tmp = p2[5]; // not to warn
46+
#pragma clang unsafe_buffer_usage end
47+
#pragma clang diagnostic pop
48+
49+
tmp = p2[5];
50+
}
51+
52+
53+
void withDiagnosticIgnore() {
54+
int tmp;
55+
int *p1 = new int[10];
56+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
57+
int *p2 = new int[10];
58+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:12}:"std::span<int> p2"
59+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:13-[[@LINE-2]]:13}:"{"
60+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:24-[[@LINE-3]]:24}:", 10}"
61+
int *p3 = new int[10];
62+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:12}:"std::span<int> p3"
63+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:13-[[@LINE-2]]:13}:"{"
64+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:24-[[@LINE-3]]:24}:", 10}"
65+
66+
#pragma clang unsafe_buffer_usage begin
67+
tmp = p1[5]; // not to warn
68+
tmp = p2[5]; // not to warn
69+
#pragma clang diagnostic push
70+
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
71+
tmp = p1[5]; // not to warn
72+
tmp = p2[5]; // not to warn
73+
#pragma clang diagnostic ignored "-Weverything"
74+
tmp = p1[5]; // not to warn
75+
tmp = p2[5]; // not to warn
76+
#pragma clang diagnostic pop
77+
#pragma clang unsafe_buffer_usage end
78+
79+
#pragma clang diagnostic push
80+
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
81+
#pragma clang unsafe_buffer_usage begin
82+
tmp = p1[5]; // not to warn
83+
tmp = p2[5]; // not to warn
84+
#pragma clang unsafe_buffer_usage end
85+
#pragma clang diagnostic pop
86+
87+
tmp = p2[5];
88+
89+
#pragma clang diagnostic push
90+
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
91+
#pragma clang unsafe_buffer_usage begin
92+
tmp = p1[5]; // not to warn
93+
tmp = p2[5]; // not to warn
94+
#pragma clang unsafe_buffer_usage end
95+
tmp = p3[5]; // expected-note{{used in buffer access here}}
96+
#pragma clang diagnostic pop
97+
}
98+
99+
void noteGoesWithVarDeclWarning() {
100+
#pragma clang diagnostic push
101+
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
102+
int *p = new int[10]; // not to warn
103+
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:
104+
#pragma clang diagnostic pop
105+
106+
p[5]; // not to note since the associated warning is suppressed
107+
}

0 commit comments

Comments
 (0)