OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "FindBadConstructsConsumer.h" | 5 #include "FindBadConstructsConsumer.h" |
6 | 6 |
7 #include "clang/Frontend/CompilerInstance.h" | 7 #include "clang/Frontend/CompilerInstance.h" |
8 #include "clang/AST/Attr.h" | 8 #include "clang/AST/Attr.h" |
9 #include "clang/Lex/Lexer.h" | 9 #include "clang/Lex/Lexer.h" |
10 #include "llvm/Support/raw_ostream.h" | 10 #include "llvm/Support/raw_ostream.h" |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
71 } | 71 } |
72 | 72 |
73 // Generates a fixit hint to remove the 'virtual' keyword. | 73 // Generates a fixit hint to remove the 'virtual' keyword. |
74 // Unfortunately, there doesn't seem to be a good way to determine the source | 74 // Unfortunately, there doesn't seem to be a good way to determine the source |
75 // location of the 'virtual' keyword. It's available in Declarator, but that | 75 // location of the 'virtual' keyword. It's available in Declarator, but that |
76 // isn't accessible from the AST. So instead, make an educated guess that the | 76 // isn't accessible from the AST. So instead, make an educated guess that the |
77 // first token is probably the virtual keyword. Strictly speaking, this doesn't | 77 // first token is probably the virtual keyword. Strictly speaking, this doesn't |
78 // have to be true, but it probably will be. | 78 // have to be true, but it probably will be. |
79 // TODO(dcheng): Add a warning to force virtual to always appear first ;-) | 79 // TODO(dcheng): Add a warning to force virtual to always appear first ;-) |
80 FixItHint FixItRemovalForVirtual(const SourceManager& manager, | 80 FixItHint FixItRemovalForVirtual(const SourceManager& manager, |
| 81 const LangOptions& lang_opts, |
81 const CXXMethodDecl* method) { | 82 const CXXMethodDecl* method) { |
82 SourceRange range(method->getLocStart()); | 83 SourceRange range(method->getLocStart()); |
83 // Get the spelling loc just in case it was expanded from a macro. | 84 // Get the spelling loc just in case it was expanded from a macro. |
84 SourceRange spelling_range(manager.getSpellingLoc(range.getBegin())); | 85 SourceRange spelling_range(manager.getSpellingLoc(range.getBegin())); |
85 // Sanity check that the text looks like virtual. | 86 // Sanity check that the text looks like virtual. |
86 StringRef text = clang::Lexer::getSourceText( | 87 StringRef text = clang::Lexer::getSourceText( |
87 CharSourceRange::getTokenRange(spelling_range), manager, LangOptions()); | 88 CharSourceRange::getTokenRange(spelling_range), manager, lang_opts); |
88 if (text.trim() != "virtual") | 89 if (text.trim() != "virtual") |
89 return FixItHint(); | 90 return FixItHint(); |
90 return FixItHint::CreateRemoval(range); | 91 return FixItHint::CreateRemoval(range); |
91 } | 92 } |
92 | 93 |
93 bool IsPodOrTemplateType(const CXXRecordDecl& record) { | 94 bool IsPodOrTemplateType(const CXXRecordDecl& record) { |
94 return record.isPOD() || | 95 return record.isPOD() || |
95 record.getDescribedClassTemplate() || | 96 record.getDescribedClassTemplate() || |
96 record.getTemplateSpecializationKind() || | 97 record.getTemplateSpecializationKind() || |
97 record.isDependentType(); | 98 record.isDependentType(); |
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
283 // find one that tries to be inline. | 284 // find one that tries to be inline. |
284 for (CXXRecordDecl::ctor_iterator it = record->ctor_begin(); | 285 for (CXXRecordDecl::ctor_iterator it = record->ctor_begin(); |
285 it != record->ctor_end(); | 286 it != record->ctor_end(); |
286 ++it) { | 287 ++it) { |
287 // The current check is buggy. An implicit copy constructor does not | 288 // The current check is buggy. An implicit copy constructor does not |
288 // have an inline body, so this check never fires for classes with a | 289 // have an inline body, so this check never fires for classes with a |
289 // user-declared out-of-line constructor. | 290 // user-declared out-of-line constructor. |
290 if (it->hasInlineBody()) { | 291 if (it->hasInlineBody()) { |
291 if (it->isCopyConstructor() && | 292 if (it->isCopyConstructor() && |
292 !record->hasUserDeclaredCopyConstructor()) { | 293 !record->hasUserDeclaredCopyConstructor()) { |
293 emitWarning(record_location, | 294 // In general, implicit constructors are generated on demand. But |
294 "Complex class/struct needs an explicit out-of-line " | 295 // in the Windows component build, dllexport causes instantiation of |
295 "copy constructor."); | 296 // the copy constructor which means that this fires on many more |
| 297 // classes. For now, suppress this on dllexported classes. |
| 298 // (This does mean that windows component builds will not emit this |
| 299 // warning in some cases where it is emitted in other configs, but |
| 300 // that's the better tradeoff at this point). |
| 301 // TODO(dcheng): With the RecursiveASTVisitor, these warnings might |
| 302 // be emitted on other platforms too, reevaluate if we want to keep |
| 303 // surpressing this then http://crbug.com/467288 |
| 304 if (!record->hasAttr<DLLExportAttr>()) |
| 305 emitWarning(record_location, |
| 306 "Complex class/struct needs an explicit out-of-line " |
| 307 "copy constructor."); |
296 } else { | 308 } else { |
297 emitWarning(it->getInnerLocStart(), | 309 // See the comment in the previous branch about copy constructors. |
298 "Complex constructor has an inlined body."); | 310 // This does the same for implicit move constructors. |
| 311 bool is_likely_compiler_generated_dllexport_move_ctor = |
| 312 it->isMoveConstructor() && |
| 313 !record->hasUserDeclaredMoveConstructor() && |
| 314 record->hasAttr<DLLExportAttr>(); |
| 315 if (!is_likely_compiler_generated_dllexport_move_ctor) |
| 316 emitWarning(it->getInnerLocStart(), |
| 317 "Complex constructor has an inlined body."); |
299 } | 318 } |
300 } else if (it->isInlined() && !it->isInlineSpecified() && | 319 } else if (it->isInlined() && !it->isInlineSpecified() && |
301 !it->isDeleted() && (!it->isCopyOrMoveConstructor() || | 320 !it->isDeleted() && (!it->isCopyOrMoveConstructor() || |
302 it->isExplicitlyDefaulted())) { | 321 it->isExplicitlyDefaulted())) { |
303 // isInlined() is a more reliable check than hasInlineBody(), but | 322 // isInlined() is a more reliable check than hasInlineBody(), but |
304 // unfortunately, it results in warnings for implicit copy/move | 323 // unfortunately, it results in warnings for implicit copy/move |
305 // constructors in the previously mentioned situation. To preserve | 324 // constructors in the previously mentioned situation. To preserve |
306 // compatibility with existing Chromium code, only warn if it's an | 325 // compatibility with existing Chromium code, only warn if it's an |
307 // explicitly defaulted copy or move constructor. | 326 // explicitly defaulted copy or move constructor. |
308 emitWarning(it->getInnerLocStart(), | 327 emitWarning(it->getInnerLocStart(), |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
347 // incorrectly, but having the plugin enforce override is also nice. | 366 // incorrectly, but having the plugin enforce override is also nice. |
348 (InTestingNamespace(overridden) && | 367 (InTestingNamespace(overridden) && |
349 !IsGtestTestFixture(overridden->getParent()))) { | 368 !IsGtestTestFixture(overridden->getParent()))) { |
350 return true; | 369 return true; |
351 } | 370 } |
352 } | 371 } |
353 | 372 |
354 return false; | 373 return false; |
355 } | 374 } |
356 | 375 |
| 376 SuppressibleDiagnosticBuilder |
| 377 FindBadConstructsConsumer::ReportIfSpellingLocNotIgnored( |
| 378 SourceLocation loc, |
| 379 unsigned diagnostic_id) { |
| 380 return SuppressibleDiagnosticBuilder( |
| 381 &diagnostic(), loc, diagnostic_id, |
| 382 InBannedDirectory(instance().getSourceManager().getSpellingLoc(loc))); |
| 383 } |
| 384 |
357 // Checks that virtual methods are correctly annotated, and have no body in a | 385 // Checks that virtual methods are correctly annotated, and have no body in a |
358 // header file. | 386 // header file. |
359 void FindBadConstructsConsumer::CheckVirtualMethods( | 387 void FindBadConstructsConsumer::CheckVirtualMethods( |
360 SourceLocation record_location, | 388 SourceLocation record_location, |
361 CXXRecordDecl* record, | 389 CXXRecordDecl* record, |
362 bool warn_on_inline_bodies) { | 390 bool warn_on_inline_bodies) { |
363 // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a | 391 // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a |
364 // trick to get around that. If a class has member variables whose types are | 392 // trick to get around that. If a class has member variables whose types are |
365 // in the "testing" namespace (which is how gmock works behind the scenes), | 393 // in the "testing" namespace (which is how gmock works behind the scenes), |
366 // there's a really high chance we won't care about these errors | 394 // there's a really high chance we won't care about these errors |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
404 const CXXMethodDecl* method) { | 432 const CXXMethodDecl* method) { |
405 bool is_override = method->size_overridden_methods() > 0; | 433 bool is_override = method->size_overridden_methods() > 0; |
406 bool has_virtual = method->isVirtualAsWritten(); | 434 bool has_virtual = method->isVirtualAsWritten(); |
407 OverrideAttr* override_attr = method->getAttr<OverrideAttr>(); | 435 OverrideAttr* override_attr = method->getAttr<OverrideAttr>(); |
408 FinalAttr* final_attr = method->getAttr<FinalAttr>(); | 436 FinalAttr* final_attr = method->getAttr<FinalAttr>(); |
409 | 437 |
410 if (IsMethodInBannedOrTestingNamespace(method)) | 438 if (IsMethodInBannedOrTestingNamespace(method)) |
411 return; | 439 return; |
412 | 440 |
413 SourceManager& manager = instance().getSourceManager(); | 441 SourceManager& manager = instance().getSourceManager(); |
| 442 const LangOptions& lang_opts = instance().getLangOpts(); |
414 | 443 |
415 // Complain if a method is annotated virtual && (override || final). | 444 // Complain if a method is annotated virtual && (override || final). |
416 if (has_virtual && (override_attr || final_attr)) { | 445 if (has_virtual && (override_attr || final_attr)) { |
417 // ... but only if virtual does not originate in a macro from a banned file. | 446 // ... but only if virtual does not originate in a macro from a banned file. |
418 // Note this is just an educated guess: the assumption here is that any | 447 // Note this is just an educated guess: the assumption here is that any |
419 // macro for declaring methods will probably be at the start of the method's | 448 // macro for declaring methods will probably be at the start of the method's |
420 // source range. | 449 // source range. |
421 if (!InBannedDirectory(manager.getSpellingLoc(method->getLocStart()))) { | 450 ReportIfSpellingLocNotIgnored(method->getLocStart(), |
422 diagnostic().Report(method->getLocStart(), | 451 diag_redundant_virtual_specifier_) |
423 diag_redundant_virtual_specifier_) | 452 << "'virtual'" |
424 << "'virtual'" | 453 << (override_attr ? static_cast<Attr*>(override_attr) : final_attr) |
425 << (override_attr ? static_cast<Attr*>(override_attr) : final_attr) | 454 << FixItRemovalForVirtual(manager, lang_opts, method); |
426 << FixItRemovalForVirtual(manager, method); | |
427 } | |
428 } | 455 } |
429 | 456 |
430 // Complain if a method is an override and is not annotated with override or | 457 // Complain if a method is an override and is not annotated with override or |
431 // final. | 458 // final. |
432 if (is_override && !override_attr && !final_attr) { | 459 if (is_override && !override_attr && !final_attr) { |
433 SourceRange range = method->getSourceRange(); | 460 SourceRange range = method->getSourceRange(); |
434 SourceLocation loc; | 461 SourceLocation loc; |
435 if (method->hasInlineBody()) { | 462 if (method->hasInlineBody()) { |
436 loc = method->getBody()->getSourceRange().getBegin(); | 463 loc = method->getBody()->getSourceRange().getBegin(); |
437 } else { | 464 } else { |
438 // TODO(dcheng): We should probably use ASTContext's LangOptions here. | 465 loc = Lexer::getLocForEndOfToken(manager.getSpellingLoc(range.getEnd()), |
439 LangOptions lang_options; | 466 0, manager, lang_opts); |
440 loc = Lexer::getLocForEndOfToken( | |
441 manager.getSpellingLoc(range.getEnd()), 0, | |
442 manager, lang_options); | |
443 // The original code used the ending source loc of TypeSourceInfo's | 467 // The original code used the ending source loc of TypeSourceInfo's |
444 // TypeLoc. Unfortunately, this breaks down in the presence of attributes. | 468 // TypeLoc. Unfortunately, this breaks down in the presence of attributes. |
445 // Attributes often appear at the end of a TypeLoc, e.g. | 469 // Attributes often appear at the end of a TypeLoc, e.g. |
446 // virtual ULONG __stdcall AddRef() | 470 // virtual ULONG __stdcall AddRef() |
447 // has a TypeSourceInfo that looks something like: | 471 // has a TypeSourceInfo that looks something like: |
448 // ULONG AddRef() __attribute(stdcall) | 472 // ULONG AddRef() __attribute(stdcall) |
449 // so a fix-it insertion would be generated to insert 'override' after | 473 // so a fix-it insertion would be generated to insert 'override' after |
450 // __stdcall in the code as written. | 474 // __stdcall in the code as written. |
451 // While using the spelling loc of the CXXMethodDecl fixes attribute | 475 // While using the spelling loc of the CXXMethodDecl fixes attribute |
452 // handling, it breaks handling of "= 0" and similar constructs.. To work | 476 // handling, it breaks handling of "= 0" and similar constructs.. To work |
453 // around this, scan backwards in the source text for a '=' or ')' token | 477 // around this, scan backwards in the source text for a '=' or ')' token |
454 // and adjust the location as needed... | 478 // and adjust the location as needed... |
455 for (SourceLocation l = loc.getLocWithOffset(-1); | 479 for (SourceLocation l = loc.getLocWithOffset(-1); |
456 l != manager.getLocForStartOfFile(manager.getFileID(loc)); | 480 l != manager.getLocForStartOfFile(manager.getFileID(loc)); |
457 l = l.getLocWithOffset(-1)) { | 481 l = l.getLocWithOffset(-1)) { |
458 l = Lexer::GetBeginningOfToken(l, manager, lang_options); | 482 l = Lexer::GetBeginningOfToken(l, manager, lang_opts); |
459 Token token; | 483 Token token; |
460 // getRawToken() returns *true* on failure. In that case, just give up | 484 // getRawToken() returns *true* on failure. In that case, just give up |
461 // and don't bother generating a possibly incorrect fix-it. | 485 // and don't bother generating a possibly incorrect fix-it. |
462 if (Lexer::getRawToken(l, token, manager, lang_options, true)) { | 486 if (Lexer::getRawToken(l, token, manager, lang_opts, true)) { |
463 loc = SourceLocation(); | 487 loc = SourceLocation(); |
464 break; | 488 break; |
465 } | 489 } |
466 if (token.is(tok::r_paren)) { | 490 if (token.is(tok::r_paren)) { |
467 break; | 491 break; |
468 } else if (token.is(tok::equal)) { | 492 } else if (token.is(tok::equal)) { |
469 loc = l; | 493 loc = l; |
470 break; | 494 break; |
471 } | 495 } |
472 } | 496 } |
473 } | 497 } |
| 498 // Again, only emit the warning if it doesn't originate from a macro in |
| 499 // a system header. |
474 if (loc.isValid()) { | 500 if (loc.isValid()) { |
475 diagnostic().Report(loc, diag_method_requires_override_) | 501 ReportIfSpellingLocNotIgnored(loc, diag_method_requires_override_) |
476 << FixItHint::CreateInsertion(loc, " override"); | 502 << FixItHint::CreateInsertion(loc, " override"); |
477 } else { | 503 } else { |
478 diagnostic().Report(range.getBegin(), diag_method_requires_override_); | 504 ReportIfSpellingLocNotIgnored(range.getBegin(), |
| 505 diag_method_requires_override_); |
479 } | 506 } |
480 } | 507 } |
481 | 508 |
482 if (final_attr && override_attr) { | 509 if (final_attr && override_attr) { |
483 diagnostic().Report(override_attr->getLocation(), | 510 ReportIfSpellingLocNotIgnored(override_attr->getLocation(), |
484 diag_redundant_virtual_specifier_) | 511 diag_redundant_virtual_specifier_) |
485 << override_attr << final_attr | 512 << override_attr << final_attr |
486 << FixItHint::CreateRemoval(override_attr->getRange()); | 513 << FixItHint::CreateRemoval(override_attr->getRange()); |
487 } | 514 } |
488 | 515 |
489 if (final_attr && !is_override) { | 516 if (final_attr && !is_override) { |
490 diagnostic().Report(method->getLocStart(), | 517 ReportIfSpellingLocNotIgnored(method->getLocStart(), |
491 diag_base_method_virtual_and_final_) | 518 diag_base_method_virtual_and_final_) |
492 << FixItRemovalForVirtual(manager, method) | 519 << FixItRemovalForVirtual(manager, lang_opts, method) |
493 << FixItHint::CreateRemoval(final_attr->getRange()); | 520 << FixItHint::CreateRemoval(final_attr->getRange()); |
494 } | 521 } |
495 } | 522 } |
496 | 523 |
497 void FindBadConstructsConsumer::CheckVirtualBodies( | 524 void FindBadConstructsConsumer::CheckVirtualBodies( |
498 const CXXMethodDecl* method) { | 525 const CXXMethodDecl* method) { |
499 // Virtual methods should not have inline definitions beyond "{}". This | 526 // Virtual methods should not have inline definitions beyond "{}". This |
500 // only matters for header files. | 527 // only matters for header files. |
501 if (method->hasBody() && method->hasInlineBody()) { | 528 if (method->hasBody() && method->hasInlineBody()) { |
502 if (CompoundStmt* cs = dyn_cast<CompoundStmt>(method->getBody())) { | 529 if (CompoundStmt* cs = dyn_cast<CompoundStmt>(method->getBody())) { |
503 if (cs->size()) { | 530 if (cs->size()) { |
504 emitWarning(cs->getLBracLoc(), | 531 SourceLocation loc = cs->getLBracLoc(); |
505 "virtual methods with non-empty bodies shouldn't be " | 532 // CR_BEGIN_MSG_MAP_EX and BEGIN_SAFE_MSG_MAP_EX try to be compatible |
506 "declared inline."); | 533 // to BEGIN_MSG_MAP(_EX). So even though they are in chrome code, |
| 534 // we can't easily fix them, so explicitly whitelist them here. |
| 535 bool emit = true; |
| 536 if (loc.isMacroID()) { |
| 537 SourceManager& manager = instance().getSourceManager(); |
| 538 if (InBannedDirectory(manager.getSpellingLoc(loc))) |
| 539 emit = false; |
| 540 else { |
| 541 StringRef name = Lexer::getImmediateMacroName( |
| 542 loc, manager, instance().getLangOpts()); |
| 543 if (name == "CR_BEGIN_MSG_MAP_EX" || |
| 544 name == "BEGIN_SAFE_MSG_MAP_EX") |
| 545 emit = false; |
| 546 } |
| 547 } |
| 548 if (emit) |
| 549 emitWarning(loc, |
| 550 "virtual methods with non-empty bodies shouldn't be " |
| 551 "declared inline."); |
507 } | 552 } |
508 } | 553 } |
509 } | 554 } |
510 } | 555 } |
511 | 556 |
512 void FindBadConstructsConsumer::CountType(const Type* type, | 557 void FindBadConstructsConsumer::CountType(const Type* type, |
513 int* trivial_member, | 558 int* trivial_member, |
514 int* non_trivial_member, | 559 int* non_trivial_member, |
515 int* templated_non_trivial_member) { | 560 int* templated_non_trivial_member) { |
516 switch (type->getTypeClass()) { | 561 switch (type->getTypeClass()) { |
517 case Type::Record: { | 562 case Type::Record: { |
518 // Simplifying; the whole class isn't trivial if the dtor is, but | 563 // Simplifying; the whole class isn't trivial if the dtor is, but |
519 // we use this as a signal about complexity. | 564 // we use this as a signal about complexity. |
520 if (TypeHasNonTrivialDtor(type)) | 565 if (TypeHasNonTrivialDtor(type)) |
521 (*trivial_member)++; | 566 (*trivial_member)++; |
522 else | 567 else |
523 (*non_trivial_member)++; | 568 (*non_trivial_member)++; |
524 break; | 569 break; |
525 } | 570 } |
526 case Type::TemplateSpecialization: { | 571 case Type::TemplateSpecialization: { |
527 TemplateName name = | 572 TemplateName name = |
528 dyn_cast<TemplateSpecializationType>(type)->getTemplateName(); | 573 dyn_cast<TemplateSpecializationType>(type)->getTemplateName(); |
529 bool whitelisted_template = false; | 574 bool whitelisted_template = false; |
530 | 575 |
531 // HACK: I'm at a loss about how to get the syntax checker to get | 576 // HACK: I'm at a loss about how to get the syntax checker to get |
532 // whether a template is exterened or not. For the first pass here, | 577 // whether a template is externed or not. For the first pass here, |
533 // just do retarded string comparisons. | 578 // just do retarded string comparisons. |
534 if (TemplateDecl* decl = name.getAsTemplateDecl()) { | 579 if (TemplateDecl* decl = name.getAsTemplateDecl()) { |
535 std::string base_name = decl->getNameAsString(); | 580 std::string base_name = decl->getNameAsString(); |
536 if (base_name == "basic_string") | 581 if (base_name == "basic_string") |
537 whitelisted_template = true; | 582 whitelisted_template = true; |
538 } | 583 } |
539 | 584 |
540 if (whitelisted_template) | 585 if (whitelisted_template) |
541 (*non_trivial_member)++; | 586 (*non_trivial_member)++; |
542 else | 587 else |
(...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
817 // one of those, it means there is at least one member after a factory. | 862 // one of those, it means there is at least one member after a factory. |
818 if (weak_ptr_factory_location.isValid() && | 863 if (weak_ptr_factory_location.isValid() && |
819 !param_is_weak_ptr_factory_to_self) { | 864 !param_is_weak_ptr_factory_to_self) { |
820 diagnostic().Report(weak_ptr_factory_location, | 865 diagnostic().Report(weak_ptr_factory_location, |
821 diag_weak_ptr_factory_order_); | 866 diag_weak_ptr_factory_order_); |
822 } | 867 } |
823 } | 868 } |
824 } | 869 } |
825 | 870 |
826 } // namespace chrome_checker | 871 } // namespace chrome_checker |
OLD | NEW |