OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 // This clang plugin checks various invariants of the Blink garbage | 5 // This clang plugin checks various invariants of the Blink garbage |
6 // collection infrastructure. | 6 // collection infrastructure. |
7 // | 7 // |
8 // Errors are described at: | 8 // Errors are described at: |
9 // http://www.chromium.org/developers/blink-gc-plugin-errors | 9 // http://www.chromium.org/developers/blink-gc-plugin-errors |
10 | 10 |
11 #include "Config.h" | 11 #include "Config.h" |
| 12 #include "JsonWriter.h" |
12 #include "RecordInfo.h" | 13 #include "RecordInfo.h" |
13 | 14 |
14 #include "clang/AST/AST.h" | 15 #include "clang/AST/AST.h" |
15 #include "clang/AST/ASTConsumer.h" | 16 #include "clang/AST/ASTConsumer.h" |
16 #include "clang/AST/RecursiveASTVisitor.h" | 17 #include "clang/AST/RecursiveASTVisitor.h" |
17 #include "clang/Frontend/CompilerInstance.h" | 18 #include "clang/Frontend/CompilerInstance.h" |
18 #include "clang/Frontend/FrontendPluginRegistry.h" | 19 #include "clang/Frontend/FrontendPluginRegistry.h" |
19 | 20 |
20 using namespace clang; | 21 using namespace clang; |
21 using std::string; | 22 using std::string; |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
103 "[blink-gc] Field %0 requiring finalization declared here:"; | 104 "[blink-gc] Field %0 requiring finalization declared here:"; |
104 | 105 |
105 const char kManualDispatchMethodNote[] = | 106 const char kManualDispatchMethodNote[] = |
106 "[blink-gc] Manual dispatch %0 declared here:"; | 107 "[blink-gc] Manual dispatch %0 declared here:"; |
107 | 108 |
108 const char kDerivesNonStackAllocated[] = | 109 const char kDerivesNonStackAllocated[] = |
109 "[blink-gc] Stack-allocated class %0 derives class %1" | 110 "[blink-gc] Stack-allocated class %0 derives class %1" |
110 " which is not stack allocated."; | 111 " which is not stack allocated."; |
111 | 112 |
112 struct BlinkGCPluginOptions { | 113 struct BlinkGCPluginOptions { |
113 BlinkGCPluginOptions() : enable_oilpan(false) {} | 114 BlinkGCPluginOptions() : enable_oilpan(false), detect_cycles(false) {} |
114 bool enable_oilpan; | 115 bool enable_oilpan; |
| 116 bool detect_cycles; |
115 std::set<std::string> ignored_classes; | 117 std::set<std::string> ignored_classes; |
116 std::set<std::string> checked_namespaces; | 118 std::set<std::string> checked_namespaces; |
117 std::vector<std::string> ignored_directories; | 119 std::vector<std::string> ignored_directories; |
118 }; | 120 }; |
119 | 121 |
120 typedef std::vector<CXXRecordDecl*> RecordVector; | 122 typedef std::vector<CXXRecordDecl*> RecordVector; |
121 typedef std::vector<CXXMethodDecl*> MethodVector; | 123 typedef std::vector<CXXMethodDecl*> MethodVector; |
122 | 124 |
123 // Test if a template specialization is an instantiation. | 125 // Test if a template specialization is an instantiation. |
124 static bool IsTemplateInstantiation(CXXRecordDecl* record) { | 126 static bool IsTemplateInstantiation(CXXRecordDecl* record) { |
(...skipping 353 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
478 }; | 480 }; |
479 | 481 |
480 // Main class containing checks for various invariants of the Blink | 482 // Main class containing checks for various invariants of the Blink |
481 // garbage collection infrastructure. | 483 // garbage collection infrastructure. |
482 class BlinkGCPluginConsumer : public ASTConsumer { | 484 class BlinkGCPluginConsumer : public ASTConsumer { |
483 public: | 485 public: |
484 BlinkGCPluginConsumer(CompilerInstance& instance, | 486 BlinkGCPluginConsumer(CompilerInstance& instance, |
485 const BlinkGCPluginOptions& options) | 487 const BlinkGCPluginOptions& options) |
486 : instance_(instance), | 488 : instance_(instance), |
487 diagnostic_(instance.getDiagnostics()), | 489 diagnostic_(instance.getDiagnostics()), |
488 options_(options) { | 490 options_(options), |
| 491 json_(0) { |
489 | 492 |
490 // Only check structures in the blink, WebCore and WebKit namespaces. | 493 // Only check structures in the blink, WebCore and WebKit namespaces. |
491 options_.checked_namespaces.insert("blink"); | 494 options_.checked_namespaces.insert("blink"); |
492 options_.checked_namespaces.insert("WebCore"); | 495 options_.checked_namespaces.insert("WebCore"); |
493 options_.checked_namespaces.insert("WebKit"); | 496 options_.checked_namespaces.insert("WebKit"); |
494 | 497 |
495 // Ignore GC implementation files. | 498 // Ignore GC implementation files. |
496 options_.ignored_directories.push_back("/heap/"); | 499 options_.ignored_directories.push_back("/heap/"); |
497 | 500 |
498 // Register warning/error messages. | 501 // Register warning/error messages. |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
554 diag_overridden_non_virtual_trace_note_ = diagnostic_.getCustomDiagID( | 557 diag_overridden_non_virtual_trace_note_ = diagnostic_.getCustomDiagID( |
555 DiagnosticsEngine::Note, kOverriddenNonVirtualTraceNote); | 558 DiagnosticsEngine::Note, kOverriddenNonVirtualTraceNote); |
556 diag_manual_dispatch_method_note_ = diagnostic_.getCustomDiagID( | 559 diag_manual_dispatch_method_note_ = diagnostic_.getCustomDiagID( |
557 DiagnosticsEngine::Note, kManualDispatchMethodNote); | 560 DiagnosticsEngine::Note, kManualDispatchMethodNote); |
558 } | 561 } |
559 | 562 |
560 virtual void HandleTranslationUnit(ASTContext& context) { | 563 virtual void HandleTranslationUnit(ASTContext& context) { |
561 CollectVisitor visitor; | 564 CollectVisitor visitor; |
562 visitor.TraverseDecl(context.getTranslationUnitDecl()); | 565 visitor.TraverseDecl(context.getTranslationUnitDecl()); |
563 | 566 |
| 567 if (options_.detect_cycles) { |
| 568 string err; |
| 569 // TODO: Make createDefaultOutputFile or a shorter createOutputFile work. |
| 570 json_ = JsonWriter::from(instance_.createOutputFile( |
| 571 "", // OutputPath |
| 572 err, // Errors |
| 573 true, // Binary |
| 574 true, // RemoveFileOnSignal |
| 575 instance_.getFrontendOpts().OutputFile, // BaseInput |
| 576 "graph.json", // Extension |
| 577 false, // UseTemporary |
| 578 false, // CreateMissingDirectories |
| 579 0, // ResultPathName |
| 580 0)); // TempPathName |
| 581 if (err.empty() && json_) { |
| 582 json_->OpenList(); |
| 583 } else { |
| 584 json_ = 0; |
| 585 llvm::errs() |
| 586 << "[blink-gc] " |
| 587 << "Failed to create an output file for the object graph.\n"; |
| 588 } |
| 589 } |
| 590 |
564 for (RecordVector::iterator it = visitor.record_decls().begin(); | 591 for (RecordVector::iterator it = visitor.record_decls().begin(); |
565 it != visitor.record_decls().end(); | 592 it != visitor.record_decls().end(); |
566 ++it) { | 593 ++it) { |
567 CheckRecord(cache_.Lookup(*it)); | 594 CheckRecord(cache_.Lookup(*it)); |
568 } | 595 } |
569 | 596 |
570 for (MethodVector::iterator it = visitor.trace_decls().begin(); | 597 for (MethodVector::iterator it = visitor.trace_decls().begin(); |
571 it != visitor.trace_decls().end(); | 598 it != visitor.trace_decls().end(); |
572 ++it) { | 599 ++it) { |
573 CheckTracingMethod(*it); | 600 CheckTracingMethod(*it); |
574 } | 601 } |
| 602 |
| 603 if (json_) { |
| 604 json_->CloseList(); |
| 605 delete json_; |
| 606 json_ = 0; |
| 607 } |
575 } | 608 } |
576 | 609 |
577 // Main entry for checking a record declaration. | 610 // Main entry for checking a record declaration. |
578 void CheckRecord(RecordInfo* info) { | 611 void CheckRecord(RecordInfo* info) { |
579 if (IsIgnored(info)) | 612 if (IsIgnored(info)) |
580 return; | 613 return; |
581 | 614 |
582 CXXRecordDecl* record = info->record(); | 615 CXXRecordDecl* record = info->record(); |
583 | 616 |
584 // TODO: what should we do to check unions? | 617 // TODO: what should we do to check unions? |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
627 } | 660 } |
628 | 661 |
629 if (info->IsGCDerived()) { | 662 if (info->IsGCDerived()) { |
630 CheckGCRootsVisitor visitor; | 663 CheckGCRootsVisitor visitor; |
631 if (visitor.ContainsGCRoots(info)) | 664 if (visitor.ContainsGCRoots(info)) |
632 ReportClassContainsGCRoots(info, &visitor.gc_roots()); | 665 ReportClassContainsGCRoots(info, &visitor.gc_roots()); |
633 | 666 |
634 if (info->NeedsFinalization()) | 667 if (info->NeedsFinalization()) |
635 CheckFinalization(info); | 668 CheckFinalization(info); |
636 } | 669 } |
| 670 |
| 671 DumpClass(info); |
637 } | 672 } |
638 | 673 |
639 void CheckDispatch(RecordInfo* info) { | 674 void CheckDispatch(RecordInfo* info) { |
640 bool finalized = info->IsGCFinalized(); | 675 bool finalized = info->IsGCFinalized(); |
641 CXXMethodDecl* trace_dispatch = info->GetTraceDispatchMethod(); | 676 CXXMethodDecl* trace_dispatch = info->GetTraceDispatchMethod(); |
642 CXXMethodDecl* finalize_dispatch = info->GetFinalizeDispatchMethod(); | 677 CXXMethodDecl* finalize_dispatch = info->GetFinalizeDispatchMethod(); |
643 if (!trace_dispatch && !finalize_dispatch) | 678 if (!trace_dispatch && !finalize_dispatch) |
644 return; | 679 return; |
645 | 680 |
646 CXXRecordDecl* base = trace_dispatch ? | 681 CXXRecordDecl* base = trace_dispatch ? |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
797 it != parent->GetFields().end(); | 832 it != parent->GetFields().end(); |
798 ++it) { | 833 ++it) { |
799 if (!it->second.IsProperlyTraced()) { | 834 if (!it->second.IsProperlyTraced()) { |
800 // Discontinue once an untraced-field error is found. | 835 // Discontinue once an untraced-field error is found. |
801 ReportFieldsRequireTracing(parent, trace); | 836 ReportFieldsRequireTracing(parent, trace); |
802 break; | 837 break; |
803 } | 838 } |
804 } | 839 } |
805 } | 840 } |
806 | 841 |
| 842 void DumpClass(RecordInfo* info) { |
| 843 if (!json_) return; |
| 844 |
| 845 json_->OpenObject(); |
| 846 json_->Write("name", info->record()->getQualifiedNameAsString()); |
| 847 json_->Write("loc", GetLocString(info->record()->getLocStart())); |
| 848 json_->CloseObject(); |
| 849 |
| 850 class DumpEdgeVisitor : public RecursiveEdgeVisitor { |
| 851 public: |
| 852 DumpEdgeVisitor(JsonWriter* json) : json_(json) { } |
| 853 void DumpEdge(RecordInfo* src, |
| 854 RecordInfo* dst, |
| 855 const string& lbl, |
| 856 const Edge::LivenessKind& kind, |
| 857 const string& loc) { |
| 858 json_->OpenObject(); |
| 859 json_->Write("src", src->record()->getQualifiedNameAsString()); |
| 860 json_->Write("dst", dst->record()->getQualifiedNameAsString()); |
| 861 json_->Write("lbl", lbl); |
| 862 json_->Write("kind", kind); |
| 863 json_->Write("loc", loc); |
| 864 json_->CloseObject(); |
| 865 } |
| 866 |
| 867 void DumpField(RecordInfo* src, FieldPoint* point, const string& loc) { |
| 868 src_ = src; |
| 869 point_ = point; |
| 870 loc_ = loc; |
| 871 point_->edge()->Accept(this); |
| 872 } |
| 873 |
| 874 void AtValue(Value* e) { |
| 875 // The liveness kind of a path from the point to this value |
| 876 // is given by the innermost place that is non-strong. |
| 877 Edge::LivenessKind kind = Edge::kStrong; |
| 878 if (Config::IsIgnoreCycleAnnotated(point_->field())) { |
| 879 kind = Edge::kWeak; |
| 880 } else { |
| 881 for (Context::iterator it = context().begin(); |
| 882 it != context().end(); |
| 883 ++it) { |
| 884 Edge::LivenessKind pointer_kind = (*it)->Kind(); |
| 885 if (pointer_kind != Edge::kStrong) { |
| 886 kind = pointer_kind; |
| 887 break; |
| 888 } |
| 889 } |
| 890 } |
| 891 DumpEdge(src_, |
| 892 e->value(), |
| 893 point_->field()->getNameAsString(), |
| 894 kind, |
| 895 loc_); |
| 896 } |
| 897 |
| 898 private: |
| 899 JsonWriter* json_; |
| 900 RecordInfo* src_; |
| 901 FieldPoint* point_; |
| 902 string loc_; |
| 903 }; |
| 904 |
| 905 DumpEdgeVisitor visitor(json_); |
| 906 |
| 907 RecordInfo::Bases& bases = info->GetBases(); |
| 908 for (RecordInfo::Bases::iterator it = bases.begin(); |
| 909 it != bases.end(); |
| 910 ++it) { |
| 911 visitor.DumpEdge(info, |
| 912 it->second.info(), |
| 913 "<super>", |
| 914 Edge::kStrong, |
| 915 GetLocString(it->second.spec().getLocStart())); |
| 916 } |
| 917 |
| 918 RecordInfo::Fields& fields = info->GetFields(); |
| 919 for (RecordInfo::Fields::iterator it = fields.begin(); |
| 920 it != fields.end(); |
| 921 ++it) { |
| 922 visitor.DumpField(info, |
| 923 &it->second, |
| 924 GetLocString(it->second.field()->getLocStart())); |
| 925 } |
| 926 } |
| 927 |
807 // Adds either a warning or error, based on the current handling of -Werror. | 928 // Adds either a warning or error, based on the current handling of -Werror. |
808 DiagnosticsEngine::Level getErrorLevel() { | 929 DiagnosticsEngine::Level getErrorLevel() { |
809 return diagnostic_.getWarningsAsErrors() ? DiagnosticsEngine::Error | 930 return diagnostic_.getWarningsAsErrors() ? DiagnosticsEngine::Error |
810 : DiagnosticsEngine::Warning; | 931 : DiagnosticsEngine::Warning; |
811 } | 932 } |
812 | 933 |
| 934 const string GetLocString(SourceLocation loc) { |
| 935 const SourceManager& source_manager = instance_.getSourceManager(); |
| 936 PresumedLoc ploc = source_manager.getPresumedLoc(loc); |
| 937 if (ploc.isInvalid()) |
| 938 return ""; |
| 939 string loc_str; |
| 940 llvm::raw_string_ostream OS(loc_str); |
| 941 OS << ploc.getFilename() |
| 942 << ":" << ploc.getLine() |
| 943 << ":" << ploc.getColumn(); |
| 944 return OS.str(); |
| 945 } |
| 946 |
813 bool IsIgnored(RecordInfo* record) { | 947 bool IsIgnored(RecordInfo* record) { |
814 return !record || | 948 return !record || |
815 !InCheckedNamespace(record) || | 949 !InCheckedNamespace(record) || |
816 IsIgnoredClass(record) || | 950 IsIgnoredClass(record) || |
817 InIgnoredDirectory(record); | 951 InIgnoredDirectory(record); |
818 } | 952 } |
819 | 953 |
820 bool IsIgnoredClass(RecordInfo* info) { | 954 bool IsIgnoredClass(RecordInfo* info) { |
821 // Ignore any class prefixed by SameSizeAs. These are used in | 955 // Ignore any class prefixed by SameSizeAs. These are used in |
822 // Blink to verify class sizes and don't need checking. | 956 // Blink to verify class sizes and don't need checking. |
(...skipping 302 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1125 unsigned diag_user_declared_finalizer_note_; | 1259 unsigned diag_user_declared_finalizer_note_; |
1126 unsigned diag_base_requires_finalization_note_; | 1260 unsigned diag_base_requires_finalization_note_; |
1127 unsigned diag_field_requires_finalization_note_; | 1261 unsigned diag_field_requires_finalization_note_; |
1128 unsigned diag_overridden_non_virtual_trace_note_; | 1262 unsigned diag_overridden_non_virtual_trace_note_; |
1129 unsigned diag_manual_dispatch_method_note_; | 1263 unsigned diag_manual_dispatch_method_note_; |
1130 | 1264 |
1131 CompilerInstance& instance_; | 1265 CompilerInstance& instance_; |
1132 DiagnosticsEngine& diagnostic_; | 1266 DiagnosticsEngine& diagnostic_; |
1133 BlinkGCPluginOptions options_; | 1267 BlinkGCPluginOptions options_; |
1134 RecordCache cache_; | 1268 RecordCache cache_; |
| 1269 JsonWriter* json_; |
1135 }; | 1270 }; |
1136 | 1271 |
1137 class BlinkGCPluginAction : public PluginASTAction { | 1272 class BlinkGCPluginAction : public PluginASTAction { |
1138 public: | 1273 public: |
1139 BlinkGCPluginAction() {} | 1274 BlinkGCPluginAction() {} |
1140 | 1275 |
1141 protected: | 1276 protected: |
1142 // Overridden from PluginASTAction: | 1277 // Overridden from PluginASTAction: |
1143 virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance, | 1278 virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance, |
1144 llvm::StringRef ref) { | 1279 llvm::StringRef ref) { |
1145 return new BlinkGCPluginConsumer(instance, options_); | 1280 return new BlinkGCPluginConsumer(instance, options_); |
1146 } | 1281 } |
1147 | 1282 |
1148 virtual bool ParseArgs(const CompilerInstance& instance, | 1283 virtual bool ParseArgs(const CompilerInstance& instance, |
1149 const std::vector<string>& args) { | 1284 const std::vector<string>& args) { |
1150 bool parsed = true; | 1285 bool parsed = true; |
1151 | 1286 |
1152 for (size_t i = 0; i < args.size() && parsed; ++i) { | 1287 for (size_t i = 0; i < args.size() && parsed; ++i) { |
1153 if (args[i] == "enable-oilpan") { | 1288 if (args[i] == "enable-oilpan") { |
1154 options_.enable_oilpan = true; | 1289 options_.enable_oilpan = true; |
| 1290 } else if (args[i] == "detect-cycles") { |
| 1291 options_.detect_cycles = true; |
1155 } else { | 1292 } else { |
1156 parsed = false; | 1293 parsed = false; |
1157 llvm::errs() << "Unknown blink-gc-plugin argument: " << args[i] << "\n"; | 1294 llvm::errs() << "Unknown blink-gc-plugin argument: " << args[i] << "\n"; |
1158 } | 1295 } |
1159 } | 1296 } |
1160 | 1297 |
1161 return parsed; | 1298 return parsed; |
1162 } | 1299 } |
1163 | 1300 |
1164 private: | 1301 private: |
1165 BlinkGCPluginOptions options_; | 1302 BlinkGCPluginOptions options_; |
1166 }; | 1303 }; |
1167 | 1304 |
1168 } // namespace | 1305 } // namespace |
1169 | 1306 |
1170 static FrontendPluginRegistry::Add<BlinkGCPluginAction> X( | 1307 static FrontendPluginRegistry::Add<BlinkGCPluginAction> X( |
1171 "blink-gc-plugin", | 1308 "blink-gc-plugin", |
1172 "Check Blink GC invariants"); | 1309 "Check Blink GC invariants"); |
OLD | NEW |