Skip to content

Commit ecdd6e5

Browse files
ids: check for unannotated static fields and extern global variables
Add scanning of variable declarations in headers. This allows us to catch instances of data symbols including static members who are external which we would not previously flag. Co-authored-by: Saleem Abdulrasool <compnerd@compnerd.org>
1 parent cd98319 commit ecdd6e5

3 files changed

Lines changed: 133 additions & 8 deletions

File tree

Sources/idt/idt.cc

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,20 @@ inplace("inplace", llvm::cl::init(false),
4444
llvm::cl::cat(idt::category));
4545

4646
llvm::cl::list<std::string>
47-
ignored_functions("ignore",
48-
llvm::cl::desc("Ignore one or more functions"),
49-
llvm::cl::value_desc("function-name[,function-name...]"),
50-
llvm::cl::CommaSeparated,
51-
llvm::cl::cat(idt::category));
47+
ignored_symbols("ignore",
48+
llvm::cl::desc("Ignore one or more functions"),
49+
llvm::cl::value_desc("function-name[,function-name...]"),
50+
llvm::cl::CommaSeparated,
51+
llvm::cl::cat(idt::category));
5252

5353
template <typename Key, typename Compare, typename Allocator>
5454
bool contains(const std::set<Key, Compare, Allocator>& set, const Key& key) {
5555
return set.find(key) != set.end();
5656
}
5757

58-
const std::set<std::string> &get_ignored_functions() {
58+
const std::set<std::string> &get_ignored_symbols() {
5959
static auto kIgnoredFunctions = [&]() -> std::set<std::string> {
60-
return { ignored_functions.begin(), ignored_functions.end() };
60+
return { ignored_symbols.begin(), ignored_symbols.end() };
6161
}();
6262

6363
return kIgnoredFunctions;
@@ -97,6 +97,19 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
9797
return context_.getFullLoc(TD->getBeginLoc()).getExpansionLoc();
9898
}
9999

100+
template <typename Decl_>
101+
bool is_in_header(const Decl_ *D) const {
102+
const clang::FullSourceLoc location = get_location(D);
103+
const clang::FileID id = source_manager_.getFileID(location);
104+
if (const auto entry = source_manager_.getFileEntryRefForID(id)) {
105+
const llvm::StringRef name = entry->getName();
106+
for (const auto &extension : { ".h", ".hh", ".hpp", ".hxx" })
107+
if (name.ends_with(extension))
108+
return true;
109+
}
110+
return false;
111+
}
112+
100113
public:
101114
explicit visitor(clang::ASTContext &context)
102115
: context_(context), source_manager_(context.getSourceManager()) {}
@@ -150,7 +163,7 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
150163
return true;
151164

152165
// TODO(compnerd) replace with std::set::contains in C++20
153-
if (contains(get_ignored_functions(), FD->getNameAsString()))
166+
if (contains(get_ignored_symbols(), FD->getNameAsString()))
154167
return true;
155168

156169
clang::SourceLocation insertion_point =
@@ -163,6 +176,46 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
163176
export_macro + " ");
164177
return true;
165178
}
179+
180+
// VisitVarDecl will visit all variable declarations as well as static fields
181+
// in classes and structs. Non-static fields are not visited by this method.
182+
bool VisitVarDecl(clang::VarDecl *VD) {
183+
if (VD->hasAttr<clang::DLLExportAttr>() ||
184+
VD->hasAttr<clang::DLLImportAttr>())
185+
return true;
186+
187+
if (VD->hasInit())
188+
return true;
189+
190+
// Skip local variables.
191+
if (VD->getParentFunctionOrMethod())
192+
return true;
193+
194+
// Skip all variable declarations not in header files.
195+
if (!is_in_header(VD))
196+
return true;
197+
198+
// Skip private static members.
199+
if (VD->getAccess() == clang::AccessSpecifier::AS_private)
200+
return true;
201+
202+
// Skip all other local and global variables unless they are extern.
203+
if (!VD->isStaticDataMember() &&
204+
VD->getStorageClass() != clang::StorageClass::SC_Extern)
205+
return true;
206+
207+
// TODO(compnerd) replace with std::set::contains in C++20
208+
if (contains(get_ignored_symbols(), VD->getNameAsString()))
209+
return true;
210+
211+
clang::FullSourceLoc location = get_location(VD);
212+
clang::SourceLocation insertion_point = VD->getBeginLoc();
213+
unexported_public_interface(location)
214+
<< VD
215+
<< clang::FixItHint::CreateInsertion(insertion_point,
216+
export_macro + " ");
217+
return true;
218+
}
166219
};
167220

168221
class consumer : public clang::ASTConsumer {

Tests/Variables.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// RUN: %idt -export-macro IDT_TEST_ABI %s 2>&1 | %FileCheck %s
2+
3+
extern int extern_variable;
4+
// CHECK-NOT: Variables.cc:[[@LINE-1]]:{{.*}}
5+
6+
extern const int extern_const_variable;
7+
// CHECK-NOT: Variables.cc:[[@LINE-1]]:{{.*}}
8+

Tests/Variables.hh

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// RUN: %idt -ignore ignored_extern_variable -export-macro IDT_TEST_ABI %s 2>&1 | %FileCheck %s
2+
3+
class ClassWithFields {
4+
public:
5+
int public_class_field;
6+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
7+
8+
static int public_static_class_field;
9+
// CHECK: Variables.hh:[[@LINE-1]]:3: remark: unexported public interface 'public_static_class_field'
10+
11+
static const int public_static_const_class_field;
12+
// CHECK: Variables.hh:[[@LINE-1]]:3: remark: unexported public interface 'public_static_const_class_field'
13+
14+
static const int public_static_const_init_class_field = 0;
15+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
16+
17+
static constexpr int public_static_constexpr_init_class_field = 0;
18+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
19+
20+
private:
21+
static int private_static_class_field;
22+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
23+
};
24+
25+
struct StructWithFields {
26+
int public_struct_field;
27+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
28+
29+
static int public_static_struct_field;
30+
// CHECK: Variables.hh:[[@LINE-1]]:3: remark: unexported public interface 'public_static_struct_field'
31+
32+
static const int public_static_const_struct_field;
33+
// CHECK: Variables.hh:[[@LINE-1]]:3: remark: unexported public interface 'public_static_const_struct_field'
34+
35+
static const int public_static_const_init_struct_field = 0;
36+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
37+
38+
static constexpr int public_static_constexpr_init_struct_field = 0;
39+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
40+
41+
private:
42+
static int private_static_struct_field;
43+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
44+
};
45+
46+
extern int extern_variable;
47+
// CHECK: Variables.hh:[[@LINE-1]]:1: remark: unexported public interface 'extern_variable'
48+
49+
extern const int extern_const_variable;
50+
// CHECK: Variables.hh:[[@LINE-1]]:1: remark: unexported public interface 'extern_const_variable'
51+
52+
extern int ignored_extern_variable;
53+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
54+
55+
int global_variable;
56+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
57+
58+
void function() {
59+
extern int extern_local_variable;
60+
// CHECK: Variables.hh:[[@LINE-1]]:3: remark: unexported public interface 'extern_local_variable'
61+
62+
int local_variable;
63+
// CHECK-NOT: Variables.hh:[[@LINE-1]]:{{.*}}
64+
}

0 commit comments

Comments
 (0)