@@ -152,6 +152,7 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
152
152
clang::ASTContext &context_;
153
153
clang::SourceManager &source_manager_;
154
154
std::optional<unsigned > id_unexported_;
155
+ std::optional<unsigned > id_improper_;
155
156
std::optional<unsigned > id_exported_;
156
157
PPCallbacks::FileIncludes &file_includes_;
157
158
@@ -228,6 +229,18 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
228
229
return diagnostics_engine.Report (location, *id_unexported_);
229
230
}
230
231
232
+ clang::DiagnosticBuilder
233
+ improperly_exported_interface (clang::SourceLocation location) {
234
+ clang::DiagnosticsEngine &diagnostics_engine = context_.getDiagnostics ();
235
+
236
+ if (!id_improper_)
237
+ id_improper_ = diagnostics_engine.getCustomDiagID (
238
+ clang::DiagnosticsEngine::Remark,
239
+ " improperly exported symbol %0: %1" );
240
+
241
+ return diagnostics_engine.Report (location, *id_improper_);
242
+ }
243
+
231
244
clang::DiagnosticBuilder
232
245
exported_private_interface (clang::SourceLocation location) {
233
246
clang::DiagnosticsEngine &diagnostics_engine = context_.getDiagnostics ();
@@ -280,9 +293,11 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
280
293
if (const auto *VA = D->template getAttr <clang::VisibilityAttr>())
281
294
return VA->getVisibility () == clang::VisibilityAttr::VisibilityType::Default;
282
295
283
- if (llvm::isa<clang::RecordDecl>(D))
284
- return false ;
296
+ return false ;
297
+ }
285
298
299
+ template <typename Decl_>
300
+ bool is_containing_record_exported (const Decl_ *D) const {
286
301
// For non-record declarations, the DeclContext is the containing record.
287
302
for (const clang::DeclContext *DC = D->getDeclContext (); DC; DC = DC->getParent ())
288
303
if (const auto *RD = llvm::dyn_cast<clang::RecordDecl>(DC))
@@ -291,13 +306,39 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
291
306
return false ;
292
307
}
293
308
309
+ // Emit a FixIt if a symbol is annotated with a default visibility or DLL
310
+ // export/import annotation. The FixIt will remove the annotation
311
+ template <typename Decl_>
312
+ void check_symbol_not_exported (const Decl_ *D, const std::string &message) {
313
+ clang::SourceLocation SLoc;
314
+ if (const auto *A = D->template getAttr <clang::DLLExportAttr>())
315
+ if (!A->isInherited ())
316
+ SLoc = A->getLocation ();
317
+
318
+ if (const auto *A = D->template getAttr <clang::DLLImportAttr>())
319
+ if (!A->isInherited ())
320
+ SLoc = A->getLocation ();
321
+
322
+ if (const auto *A = D->template getAttr <clang::VisibilityAttr>())
323
+ if (!A->isInherited () &&
324
+ A->getVisibility () == clang::VisibilityAttr::VisibilityType::Default)
325
+ SLoc = A->getLocation ();
326
+
327
+ if (SLoc.isInvalid ())
328
+ return ;
329
+
330
+ if (SLoc.isMacroID ())
331
+ SLoc = source_manager_.getExpansionLoc (SLoc);
332
+
333
+ clang::CharSourceRange range =
334
+ clang::CharSourceRange::getTokenRange (SLoc, SLoc);
335
+ improperly_exported_interface (SLoc)
336
+ << D << message << clang::FixItHint::CreateRemoval (range);
337
+ }
338
+
294
339
// Determine if a function needs exporting and add the export annotation as
295
340
// required.
296
341
void export_function_if_needed (const clang::FunctionDecl *FD) {
297
- // Check if the symbol is already exported.
298
- if (is_symbol_exported (FD))
299
- return ;
300
-
301
342
// Ignore declarations from the system.
302
343
if (is_in_system_header (FD))
303
344
return ;
@@ -306,41 +347,63 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
306
347
if (!is_in_header (FD))
307
348
return ;
308
349
309
- // We are only interested in non-dependent types .
310
- if (FD->isDependentContext () )
350
+ // Ignore friend declarations .
351
+ if (FD->getFriendObjectKind () != clang::Decl::FOK_None )
311
352
return ;
312
353
313
- // If the function has a body, it can be materialized by the user.
314
- if (FD->hasBody ( ))
354
+ // Ignore known forward declarations (builtins)
355
+ if (contains ( kIgnoredBuiltins , FD->getNameAsString () ))
315
356
return ;
316
357
317
- // Skip methods in template declarations.
318
- if (FD->getTemplateInstantiationPattern ( ))
358
+ // TODO(compnerd) replace with std::set::contains in C++20
359
+ if (contains ( get_ignored_symbols (), FD->getNameAsString () ))
319
360
return ;
320
361
321
- // Ignore friend declarations.
322
- if (FD->getFriendObjectKind () != clang::Decl::FOK_None)
362
+ // Skip functions contained in classes that are already exported.
363
+ if (is_containing_record_exported (FD)) {
364
+ // Exporting a symbol contained in an already exported class/struct will
365
+ // fail compilation on Windows.
366
+ check_symbol_not_exported (FD, " containing class is exported" );
367
+ return ;
368
+ }
369
+
370
+ // Skip any function defined inline, it can be materialized by the user.
371
+ if (FD->isThisDeclarationADefinition ()) {
372
+ check_symbol_not_exported (FD, " function is defined inline" );
323
373
return ;
374
+ }
375
+
376
+ // Pure virtual methods cannot be exported.
377
+ if (const auto *MD = llvm::dyn_cast<clang::CXXMethodDecl>(FD))
378
+ if (MD->isPureVirtual ()) {
379
+ check_symbol_not_exported (FD, " pure virtual method" );
380
+ return ;
381
+ }
324
382
325
383
// Ignore deleted and defaulted functions (e.g. operators).
326
384
if (FD->isDeleted () || FD->isDefaulted ())
327
385
return ;
328
386
387
+ // We are only interested in non-dependent types.
388
+ if (FD->isDependentContext ())
389
+ return ;
390
+
391
+ // Skip methods in template declarations.
392
+ if (FD->getTemplateInstantiationPattern ())
393
+ return ;
394
+
329
395
// Skip template class template argument deductions.
330
396
if (llvm::isa<clang::CXXDeductionGuideDecl>(FD))
331
397
return ;
332
398
333
- // Pure virtual methods cannot be exported.
334
- if (const auto *MD = llvm::dyn_cast<clang::CXXMethodDecl>(FD))
335
- if (MD->isPureVirtual ())
336
- return ;
337
-
338
- // Ignore known forward declarations (builtins)
339
- if (contains (kIgnoredBuiltins , FD->getNameAsString ()))
399
+ // If the function has a body, it can be materialized by the user. This
400
+ // check is distinct from isThisDeclarationADefinition() because it will
401
+ // return true if the function has a body anywhere in the translation unit.
402
+ if (FD->hasBody ())
340
403
return ;
341
404
342
- // TODO(compnerd) replace with std::set::contains in C++20
343
- if (contains ( get_ignored_symbols (), FD-> getNameAsString () ))
405
+ // Check if the symbol is already exported.
406
+ if (is_symbol_exported (FD ))
344
407
return ;
345
408
346
409
// Use the inner start location so that the annotation comes after
@@ -360,10 +423,6 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
360
423
// Determine if a variable needs exporting and add the export annotation as
361
424
// required. This only applies to extern globals and static member fields.
362
425
void export_variable_if_needed (const clang::VarDecl *VD) {
363
- // Check if the symbol is already exported.
364
- if (is_symbol_exported (VD))
365
- return ;
366
-
367
426
// Ignore declarations from the system.
368
427
if (is_in_system_header (VD))
369
428
return ;
@@ -376,18 +435,28 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
376
435
if (VD->getParentFunctionOrMethod ())
377
436
return ;
378
437
379
- // Skip static fields that have initializers.
380
- if (VD->hasInit ())
438
+ // TODO(compnerd) replace with std::set::contains in C++20
439
+ if (contains (get_ignored_symbols (), VD->getNameAsString ()))
440
+ return ;
441
+
442
+ // Skip variables that have initializers.
443
+ if (VD->hasInit ()) {
444
+ check_symbol_not_exported (VD, " variable initialized at declaration" );
381
445
return ;
446
+ }
382
447
383
448
// Skip all other local and global variables unless they are extern.
384
449
if (!(VD->isStaticDataMember () ||
385
- VD->getStorageClass () == clang::StorageClass::SC_Extern))
450
+ VD->getStorageClass () == clang::StorageClass::SC_Extern)) {
451
+ check_symbol_not_exported (VD, " variable not static or extern" );
386
452
return ;
453
+ }
387
454
388
- // Skip fields in template declarations.
389
- if (VD->getTemplateInstantiationPattern () != nullptr )
455
+ // Skip variables contained in classes that are already exported.
456
+ if (is_containing_record_exported (VD)) {
457
+ check_symbol_not_exported (VD, " containing class is exported" );
390
458
return ;
459
+ }
391
460
392
461
// Skip static variables declared in template class unless the template is
393
462
// fully specialized.
@@ -400,8 +469,12 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
400
469
return ;
401
470
}
402
471
403
- // TODO(compnerd) replace with std::set::contains in C++20
404
- if (contains (get_ignored_symbols (), VD->getNameAsString ()))
472
+ // Skip fields in template declarations.
473
+ if (VD->getTemplateInstantiationPattern () != nullptr )
474
+ return ;
475
+
476
+ // Check if the symbol is already exported.
477
+ if (is_symbol_exported (VD))
405
478
return ;
406
479
407
480
clang::SourceLocation SLoc = VD->getBeginLoc ();
0 commit comments