Skip to content

Commit 777eb4b

Browse files
author
MalavikaSamak
committed
[-Wunsafe-buffer-usage] Handle unevaluated contexts that contain unsafe buffer usages
This patch handles unevaluated contexts to ensure no warnings are produced by the machinery for buffer access made within an unevaluated contexts. However, such accesses must be considered by a FixableGadget and produce the necessary fixits. Reviewed by: NoQ, ziqingluo-90, jkorous Differential revision: https://reviews.llvm.org/D144905
1 parent 376402b commit 777eb4b

File tree

4 files changed

+198
-25
lines changed

4 files changed

+198
-25
lines changed

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ class MatchDescendantVisitor
3535
MatchDescendantVisitor(const internal::DynTypedMatcher *Matcher,
3636
internal::ASTMatchFinder *Finder,
3737
internal::BoundNodesTreeBuilder *Builder,
38-
internal::ASTMatchFinder::BindKind Bind)
38+
internal::ASTMatchFinder::BindKind Bind,
39+
const bool ignoreUnevaluatedContext)
3940
: Matcher(Matcher), Finder(Finder), Builder(Builder), Bind(Bind),
40-
Matches(false) {}
41+
Matches(false), ignoreUnevaluatedContext(ignoreUnevaluatedContext) {}
4142

4243
// Returns true if a match is found in a subtree of `DynNode`, which belongs
4344
// to the same callable of `DynNode`.
@@ -70,6 +71,48 @@ class MatchDescendantVisitor
7071
return VisitorBase::TraverseDecl(Node);
7172
}
7273

74+
bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
75+
// These are unevaluated, except the result expression.
76+
if(ignoreUnevaluatedContext)
77+
return TraverseStmt(Node->getResultExpr());
78+
return VisitorBase::TraverseGenericSelectionExpr(Node);
79+
}
80+
81+
bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
82+
// Unevaluated context.
83+
if(ignoreUnevaluatedContext)
84+
return true;
85+
return VisitorBase::TraverseUnaryExprOrTypeTraitExpr(Node);
86+
}
87+
88+
bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) {
89+
// Unevaluated context.
90+
if(ignoreUnevaluatedContext)
91+
return true;
92+
return VisitorBase::TraverseTypeOfExprTypeLoc(Node);
93+
}
94+
95+
bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) {
96+
// Unevaluated context.
97+
if(ignoreUnevaluatedContext)
98+
return true;
99+
return VisitorBase::TraverseDecltypeTypeLoc(Node);
100+
}
101+
102+
bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) {
103+
// Unevaluated context.
104+
if(ignoreUnevaluatedContext)
105+
return true;
106+
return VisitorBase::TraverseCXXNoexceptExpr(Node);
107+
}
108+
109+
bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) {
110+
// Unevaluated context.
111+
if(ignoreUnevaluatedContext)
112+
return true;
113+
return VisitorBase::TraverseCXXTypeidExpr(Node);
114+
}
115+
73116
bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) {
74117
if (!Node)
75118
return true;
@@ -111,6 +154,7 @@ class MatchDescendantVisitor
111154
internal::BoundNodesTreeBuilder ResultBindings;
112155
const internal::ASTMatchFinder::BindKind Bind;
113156
bool Matches;
157+
bool ignoreUnevaluatedContext;
114158
};
115159

116160
// Because we're dealing with raw pointers, let's define what we mean by that.
@@ -121,11 +165,18 @@ static auto hasPointerType() {
121165
static auto hasArrayType() {
122166
return hasType(hasCanonicalType(arrayType()));
123167
}
124-
125-
AST_MATCHER_P(Stmt, forEveryDescendant, internal::Matcher<Stmt>, innerMatcher) {
168+
169+
AST_MATCHER_P(Stmt, forEachDescendantEvaluatedStmt, internal::Matcher<Stmt>, innerMatcher) {
126170
const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);
127171

128-
MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All);
172+
MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All, true);
173+
return Visitor.findMatch(DynTypedNode::create(Node));
174+
}
175+
176+
AST_MATCHER_P(Stmt, forEachDescendantStmt, internal::Matcher<Stmt>, innerMatcher) {
177+
const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);
178+
179+
MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All, false);
129180
return Visitor.findMatch(DynTypedNode::create(Node));
130181
}
131182

@@ -870,32 +921,31 @@ findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler) {
870921

871922
// clang-format off
872923
M.addMatcher(
873-
stmt(forEveryDescendant(
874-
eachOf(
924+
stmt(eachOf(
875925
// A `FixableGadget` matcher and a `WarningGadget` matcher should not disable
876926
// each other (they could if they were put in the same `anyOf` group).
877927
// We also should make sure no two `FixableGadget` (resp. `WarningGadget`) matchers
878928
// match for the same node, so that we can group them
879929
// in one `anyOf` group (for better performance via short-circuiting).
880-
stmt(eachOf(
930+
forEachDescendantStmt(stmt(eachOf(
881931
#define FIXABLE_GADGET(x) \
882932
x ## Gadget::matcher().bind(#x),
883933
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
884-
// Also match DeclStmts because we'll need them when fixing
885-
// their underlying VarDecls that otherwise don't have
886-
// any backreferences to DeclStmts.
887-
declStmt().bind("any_ds")
888-
)),
889-
stmt(anyOf(
934+
// In parallel, match all DeclRefExprs so that to find out
935+
// whether there are any uncovered by gadgets.
936+
declRefExpr(anyOf(hasPointerType(), hasArrayType()), to(varDecl())).bind("any_dre")
937+
))),
938+
forEachDescendantEvaluatedStmt(stmt(anyOf(
890939
// Add Gadget::matcher() for every gadget in the registry.
891940
#define WARNING_GADGET(x) \
892941
allOf(x ## Gadget::matcher().bind(#x), notInSafeBufferOptOut(&Handler)),
893942
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
894-
// In parallel, match all DeclRefExprs so that to find out
895-
// whether there are any uncovered by gadgets.
896-
declRefExpr(anyOf(hasPointerType(), hasArrayType()), to(varDecl())).bind("any_dre")
897-
)))
898-
)),
943+
// Also match DeclStmts because we'll need them when fixing
944+
// their underlying VarDecls that otherwise don't have
945+
// any backreferences to DeclStmts.
946+
declStmt().bind("any_ds")
947+
))
948+
))),
899949
&CB
900950
);
901951
// clang-format on
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fdiagnostics-parseable-fixits -fsyntax-only %s 2>&1 | FileCheck %s
2+
3+
namespace std {
4+
class type_info;
5+
class bad_cast;
6+
class bad_typeid;
7+
}
8+
using size_t = __typeof(sizeof(int));
9+
void *malloc(size_t);
10+
11+
void foo(...);
12+
int bar(int *ptr);
13+
14+
void uneval_context_fix_pointer_dereference() {
15+
auto p = new int[10];
16+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> p"
17+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{"
18+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:23-[[@LINE-3]]:23}:", 10}"
19+
20+
int tmp = p[5];
21+
typeid(foo(*p));
22+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:14-[[@LINE-1]]:15}:""
23+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"[0]"
24+
_Generic(*p, int: 2, float: 3);
25+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:12-[[@LINE-1]]:13}:""
26+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:14-[[@LINE-2]]:14}:"[0]"
27+
}
28+
29+
void uneval_context_fix_pointer_array_access() {
30+
auto p = new int[10];
31+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> p"
32+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{"
33+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:23-[[@LINE-3]]:23}:", 10}"
34+
35+
int tmp = p[5];
36+
typeid(foo(p[5]));
37+
_Generic(p[2], int: 2, float: 3);
38+
}
39+
40+
void uneval_context_fix_pointer_reference() {
41+
auto p = new int[10];
42+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> p"
43+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{"
44+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:23-[[@LINE-3]]:23}:", 10}"
45+
46+
int tmp = p[5];
47+
typeid(bar(p));
48+
// CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:15-[[@LINE-1]]:15}:".data()"
49+
}
50+
51+
// The FixableGagdtes are not working in the following scenarios:
52+
// 1. sizeof(DRE)
53+
// 2. typeid(DRE)
54+
// 3. __typeof(DRE)
55+
// 4. _Generic(expr, type_1: DRE, type_2:)
56+
// 5. decltype(DRE) var = y;
57+
// 6. noexcept(DRE);
58+
// This is becauste the UPC and ULC context matchers do not handle these contexts
59+
// and almost all FixableGagdets currently depend on these matchers.
60+
61+
// FIXME: Emit fixits for each of the below use.
62+
void uneval_context_fix_pointer_dereference_not_handled() {
63+
auto p = new int[10];
64+
int tmp = p[5];
65+
66+
foo(sizeof(*p), sizeof(decltype(*p)));
67+
__typeof(*p) x;
68+
int *q = (int *)malloc(sizeof(*p));
69+
int y = sizeof(*p);
70+
__is_pod(__typeof(*p));
71+
__is_trivially_constructible(__typeof(*p), decltype(*p));
72+
_Generic(*p, int: 2, float: 3);
73+
_Generic(1, int: *p, float: 3);
74+
_Generic(1, int: 2, float: *p);
75+
decltype(*p) var = y;
76+
noexcept(*p);
77+
typeid(*p);
78+
}
79+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage -fblocks -include %s -verify %s
2+
3+
// RUN: %clang -x c++ -fsyntax-only -fblocks -include %s %s 2>&1 | FileCheck --allow-empty %s
4+
// RUN: %clang_cc1 -std=c++11 -fblocks -include %s %s 2>&1 | FileCheck --allow-empty %s
5+
// RUN: %clang_cc1 -std=c++20 -fblocks -include %s %s 2>&1 | FileCheck --allow-empty %s
6+
// CHECK-NOT: [-Wunsafe-buffer-usage]
7+
8+
#ifndef INCLUDED
9+
#define INCLUDED
10+
#pragma clang system_header
11+
12+
// no spanification warnings for system headers
13+
void foo(...); // let arguments of `foo` to hold testing expressions
14+
#else
15+
16+
namespace std {
17+
class type_info;
18+
class bad_cast;
19+
class bad_typeid;
20+
}
21+
using size_t = __typeof(sizeof(int));
22+
void *malloc(size_t);
23+
24+
void foo(int v) {
25+
}
26+
27+
void foo(int *p){}
28+
29+
void uneval_context_fix() {
30+
auto p = new int[10]; // expected-warning{{'p' is an unsafe pointer used for buffer access}}
31+
32+
// Warn on the following DREs
33+
_Generic(1, int: p[2], float: 3); // expected-note{{used in buffer access here}}
34+
35+
// Do not warn for following DREs
36+
auto q = new int[10];
37+
foo(sizeof(q[1]), // no-note
38+
sizeof(decltype(q[1]))); // no-note
39+
__typeof(q[5]) x; // no-note
40+
int *r = (int *)malloc(sizeof(q[5])); // no-note
41+
int y = sizeof(q[5]); // no-note
42+
__is_pod(__typeof(q[5])); // no-note
43+
__is_trivially_constructible(__typeof(q[5]), decltype(q[5])); // no-note
44+
_Generic(q[1], int: 2, float: 3); // no-note
45+
_Generic(1, int: 2, float: q[3]); // no-note
46+
decltype(q[2]) var = y; // no-note
47+
noexcept(q[2]); // no-note
48+
typeid(q[3]); // no-note
49+
}
50+
#endif

clang/test/SemaCXX/warn-unsafe-buffer-usage.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,6 @@ void testArraySubscriptsWithAuto(int *p, int **pp) {
102102
foo(ap4[1]); // expected-note{{used in buffer access here}}
103103
}
104104

105-
//TODO: do not warn for unevaluated context
106-
void testUnevaluatedContext(int * p) {// expected-warning{{'p' is an unsafe pointer used for buffer access}}
107-
foo(sizeof(p[1]), // expected-note{{used in buffer access here}}
108-
sizeof(decltype(p[1]))); // expected-note{{used in buffer access here}}
109-
}
110-
111105
void testQualifiedParameters(const int * p, const int * const q, const int a[10], const int b[10][10]) {
112106
// expected-warning@-1{{'p' is an unsafe pointer used for buffer access}}
113107
// expected-warning@-2{{'q' is an unsafe pointer used for buffer access}}

0 commit comments

Comments
 (0)