Skip to content

Commit 017675f

Browse files
authored
[attributes][analyzer] Generalize [[clang::suppress]] to declarations. (#80371)
The attribute is now allowed on an assortment of declarations, to suppress warnings related to declarations themselves, or all warnings in the lexical scope of the declaration. I don't necessarily see a reason to have a list at all, but it does look as if some of those more niche items aren't properly supported by the compiler itself so let's maintain a short safe list for now. The initial implementation raised a question whether the attribute should apply to lexical declaration context vs. "actual" declaration context. I'm using "lexical" here because it results in less warnings suppressed, which is the conservative behavior: we can always expand it later if we think this is wrong, without breaking any existing code. I also think that this is the correct behavior that we will probably never want to change, given that the user typically desires to keep the suppressions as localized as possible.
1 parent e06f352 commit 017675f

18 files changed

+251
-43
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2891,6 +2891,13 @@ def Suppress : DeclOrStmtAttr {
28912891
let Spellings = [CXX11<"gsl", "suppress">, Clang<"suppress">];
28922892
let Args = [VariadicStringArgument<"DiagnosticIdentifiers">];
28932893
let Accessors = [Accessor<"isGSL", [CXX11<"gsl", "suppress">]>];
2894+
// There's no fundamental reason why we can't simply accept all Decls
2895+
// but let's make a short list so that to avoid supporting something weird
2896+
// by accident. We can always expand the list later.
2897+
let Subjects = SubjectList<[
2898+
Stmt, Var, Field, ObjCProperty, Function, ObjCMethod, Record, ObjCInterface,
2899+
ObjCImplementation, Namespace, Empty
2900+
], ErrorDiag, "variables, functions, structs, interfaces, and namespaces">;
28942901
let Documentation = [SuppressDocs];
28952902
}
28962903

clang/include/clang/Basic/AttrDocs.td

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5321,6 +5321,29 @@ Putting the attribute on a compound statement suppresses all warnings in scope:
53215321
}
53225322
}
53235323

5324+
The attribute can also be placed on entire declarations of functions, classes,
5325+
variables, member variables, and so on, to suppress warnings related
5326+
to the declarations themselves. When used this way, the attribute additionally
5327+
suppresses all warnings in the lexical scope of the declaration:
5328+
5329+
.. code-block:: c++
5330+
5331+
class [[clang::suppress]] C {
5332+
int foo() {
5333+
int *x = nullptr;
5334+
...
5335+
return *x; // warnings suppressed in the entire class scope
5336+
}
5337+
5338+
int bar();
5339+
};
5340+
5341+
int C::bar() {
5342+
int *x = nullptr;
5343+
...
5344+
return *x; // warning NOT suppressed! - not lexically nested in 'class C{}'
5345+
}
5346+
53245347
Some static analysis warnings are accompanied by one or more notes, and the
53255348
line of code against which the warning is emitted isn't necessarily the best
53265349
for suppression purposes. In such cases the tools are allowed to implement

clang/lib/Sema/SemaDecl.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,9 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D,
29602960
S.mergeHLSLNumThreadsAttr(D, *NT, NT->getX(), NT->getY(), NT->getZ());
29612961
else if (const auto *SA = dyn_cast<HLSLShaderAttr>(Attr))
29622962
NewAttr = S.mergeHLSLShaderAttr(D, *SA, SA->getType());
2963+
else if (const auto *SupA = dyn_cast<SuppressAttr>(Attr))
2964+
// Do nothing. Each redeclaration should be suppressed separately.
2965+
NewAttr = nullptr;
29632966
else if (Attr->shouldInheritEvenIfAlreadyPresent() || !DeclHasAttr(D, Attr))
29642967
NewAttr = cast<InheritableAttr>(Attr->clone(S.Context));
29652968

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5260,11 +5260,6 @@ static void handleSuppressAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
52605260
// Suppression attribute with GSL spelling requires at least 1 argument.
52615261
if (!AL.checkAtLeastNumArgs(S, 1))
52625262
return;
5263-
} else if (!isa<VarDecl>(D)) {
5264-
// Analyzer suppression applies only to variables and statements.
5265-
S.Diag(AL.getLoc(), diag::err_attribute_wrong_decl_type_str)
5266-
<< AL << 0 << "variables and statements";
5267-
return;
52685263
}
52695264

52705265
std::vector<StringRef> DiagnosticIdentifiers;

clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ static void checkObjCUnusedIvar(const ObjCImplementationDecl *D,
161161

162162
PathDiagnosticLocation L =
163163
PathDiagnosticLocation::create(Ivar, BR.getSourceManager());
164-
BR.EmitBasicReport(D, Checker, "Unused instance variable", "Optimization",
165-
os.str(), L);
164+
BR.EmitBasicReport(ID, Checker, "Unused instance variable",
165+
"Optimization", os.str(), L);
166166
}
167167
}
168168

clang/lib/StaticAnalyzer/Core/BugSuppression.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ class CacheInitializer : public RecursiveASTVisitor<CacheInitializer> {
8282
CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
8383
}
8484

85-
bool VisitVarDecl(VarDecl *VD) {
85+
bool VisitDecl(Decl *D) {
8686
// Bug location could be somewhere in the init value of
8787
// a freshly declared variable. Even though it looks like the
8888
// user applied attribute to a statement, it will apply to a
8989
// variable declaration, and this is where we check for it.
90-
return VisitAttributedNode(VD);
90+
return VisitAttributedNode(D);
9191
}
9292

9393
bool VisitAttributedStmt(AttributedStmt *AS) {
@@ -147,6 +147,20 @@ bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
147147
// done as well as perform a lot of work we'll never need.
148148
// Gladly, none of our on-by-default checkers currently need it.
149149
DeclWithIssue = ACtx.getTranslationUnitDecl();
150+
} else {
151+
// This is the fast path. However, we should still consider the topmost
152+
// declaration that isn't TranslationUnitDecl, because we should respect
153+
// attributes on the entire declaration chain.
154+
while (true) {
155+
// Use the "lexical" parent. Eg., if the attribute is on a class, suppress
156+
// warnings in inline methods but not in out-of-line methods.
157+
const Decl *Parent =
158+
dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext());
159+
if (Parent == nullptr || isa<TranslationUnitDecl>(Parent))
160+
break;
161+
162+
DeclWithIssue = Parent;
163+
}
150164
}
151165

152166
// While some warnings are attached to AST nodes (mostly path-sensitive

clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ struct DerivedWithVirtualDtor : RefCntblBase {
1313
virtual ~DerivedWithVirtualDtor() {}
1414
};
1515

16+
// Confirm that the checker respects [[clang::suppress]]
17+
struct [[clang::suppress]] SuppressedDerived : RefCntblBase { };
18+
struct [[clang::suppress]] SuppressedDerivedWithVirtualDtor : RefCntblBase {
19+
virtual ~SuppressedDerivedWithVirtualDtor() {}
20+
};
1621

22+
// FIXME: Support attributes on base specifiers? Currently clang
23+
// doesn't support such attributes at all, even though it knows
24+
// how to parse them.
25+
//
26+
// struct SuppressedBaseSpecDerived : [[clang::suppress]] RefCntblBase { };
1727

1828
template<class T>
1929
struct DerivedClassTmpl : T { };

clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ void raw_ptr() {
1515
// CHECK-NEXT:{{^ | }} ^
1616
auto foo4 = [=](){ (void) ref_countable; };
1717
// CHECK: warning: Implicitly captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]
18+
19+
// Confirm that the checker respects [[clang::suppress]].
20+
RefCountable* suppressed_ref_countable = nullptr;
21+
[[clang::suppress]] auto foo5 = [suppressed_ref_countable](){};
22+
// CHECK-NOT: warning: Captured raw-pointer 'suppressed_ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]
1823
}
1924

2025
void references() {

clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class Foo {
6060
// expected-warning@-1{{Local variable 'baz' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
6161
auto *baz2 = this->provide_ref_ctnbl();
6262
// expected-warning@-1{{Local variable 'baz2' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
63+
[[clang::suppress]] auto *baz_suppressed = provide_ref_ctnbl(); // no-warning
6364
}
6465
};
6566
} // namespace auto_keyword

clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ namespace members {
88
RefCountable* a = nullptr;
99
// expected-warning@-1{{Member variable 'a' in 'members::Foo' is a raw pointer to ref-countable type 'RefCountable'}}
1010

11+
[[clang::suppress]]
12+
RefCountable* a_suppressed = nullptr;
13+
1114
protected:
1215
RefPtr<RefCountable> b;
1316

@@ -25,8 +28,14 @@ namespace members {
2528
};
2629

2730
void forceTmplToInstantiate(FooTmpl<RefCountable>) {}
31+
32+
struct [[clang::suppress]] FooSuppressed {
33+
private:
34+
RefCountable* a = nullptr;
35+
};
2836
}
2937

38+
3039
namespace ignore_unions {
3140
union Foo {
3241
RefCountable* a;

clang/test/Analysis/ObjCRetSigs.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,32 @@
44

55
@interface MyBase
66
-(long long)length;
7+
-(long long)suppressedLength;
78
@end
89

910
@interface MySub : MyBase{}
1011
-(double)length;
12+
-(double)suppressedLength;
1113
@end
1214

1315
@implementation MyBase
1416
-(long long)length{
1517
printf("Called MyBase -length;\n");
1618
return 3;
1719
}
20+
-(long long)suppressedLength{
21+
printf("Called MyBase -length;\n");
22+
return 3;
23+
}
1824
@end
1925

2026
@implementation MySub
2127
-(double)length{ // expected-warning{{types are incompatible}}
2228
printf("Called MySub -length;\n");
2329
return 3.3;
2430
}
31+
-(double)suppressedLength [[clang::suppress]]{ // no-warning
32+
printf("Called MySub -length;\n");
33+
return 3.3;
34+
}
2535
@end

clang/test/Analysis/objc_invalidation.m

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,17 @@ @interface MissingInvalidationMethod : Foo <FooBar_Protocol>
257257
@implementation MissingInvalidationMethod
258258
@end
259259

260+
@interface SuppressedMissingInvalidationMethod : Foo <FooBar_Protocol>
261+
@property (assign) [[clang::suppress]] SuppressedMissingInvalidationMethod *foobar16_warn;
262+
// FIXME: Suppression should have worked but decl-with-issue is the ivar, not the property.
263+
#if RUN_IVAR_INVALIDATION
264+
// expected-warning@-3 {{Property foobar16_warn needs to be invalidated; no invalidation method is defined in the @implementation for SuppressedMissingInvalidationMethod}}
265+
#endif
266+
267+
@end
268+
@implementation SuppressedMissingInvalidationMethod
269+
@end
270+
260271
@interface MissingInvalidationMethod2 : Foo <FooBar_Protocol> {
261272
Foo *Ivar1;
262273
#if RUN_IVAR_INVALIDATION
@@ -290,8 +301,10 @@ @implementation MissingInvalidationMethodDecl2
290301
@end
291302

292303
@interface InvalidatedInPartial : SomeInvalidationImplementingObject {
293-
SomeInvalidationImplementingObject *Ivar1;
294-
SomeInvalidationImplementingObject *Ivar2;
304+
SomeInvalidationImplementingObject *Ivar1;
305+
SomeInvalidationImplementingObject *Ivar2;
306+
[[clang::suppress]]
307+
SomeInvalidationImplementingObject *Ivar3; // no-warning
295308
}
296309
-(void)partialInvalidator __attribute__((annotate("objc_instance_variable_invalidator_partial")));
297310
@end

clang/test/Analysis/suppression-attr-doc.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,17 @@ int bar2(bool coin_flip) {
5252
__attribute__((suppress))
5353
return *result; // leak warning is suppressed only on this path
5454
}
55+
56+
class [[clang::suppress]] C {
57+
int foo() {
58+
int *x = nullptr;
59+
return *x; // warnings suppressed in the entire class
60+
}
61+
62+
int bar();
63+
};
64+
65+
int C::bar() {
66+
int *x = nullptr;
67+
return *x; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
68+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s
2+
3+
namespace [[clang::suppress]]
4+
suppressed_namespace {
5+
int foo() {
6+
int *x = 0;
7+
return *x;
8+
}
9+
10+
int foo_forward();
11+
}
12+
13+
int suppressed_namespace::foo_forward() {
14+
int *x = 0;
15+
return *x; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
16+
}
17+
18+
// Another instance of the same namespace.
19+
namespace suppressed_namespace {
20+
int bar() {
21+
int *x = 0;
22+
return *x; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
23+
}
24+
}
25+
26+
void lambda() {
27+
[[clang::suppress]] {
28+
auto lam = []() {
29+
int *x = 0;
30+
return *x;
31+
};
32+
}
33+
}
34+
35+
class [[clang::suppress]] SuppressedClass {
36+
int foo() {
37+
int *x = 0;
38+
return *x;
39+
}
40+
41+
int bar();
42+
};
43+
44+
int SuppressedClass::bar() {
45+
int *x = 0;
46+
return *x; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
47+
}
48+
49+
class SuppressedMethodClass {
50+
[[clang::suppress]] int foo() {
51+
int *x = 0;
52+
return *x;
53+
}
54+
55+
[[clang::suppress]] int bar1();
56+
int bar2();
57+
};
58+
59+
int SuppressedMethodClass::bar1() {
60+
int *x = 0;
61+
return *x; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
62+
}
63+
64+
[[clang::suppress]]
65+
int SuppressedMethodClass::bar2() {
66+
int *x = 0;
67+
return *x; // no-warning
68+
}

0 commit comments

Comments
 (0)