1 // Copyright Brian Schott (Hackerpilot) 2014. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module dscanner.analysis.run; 7 8 import core.memory : GC; 9 10 import std.stdio; 11 import std.array; 12 import std.conv; 13 import std.algorithm; 14 import std.range; 15 import std.array; 16 import std.functional : toDelegate; 17 import dparse.lexer; 18 import dparse.parser; 19 import dparse.ast; 20 import dparse.rollback_allocator; 21 import std.typecons : scoped; 22 23 import stdx.allocator : CAllocatorImpl; 24 import stdx.allocator.mallocator : Mallocator; 25 import stdx.allocator.building_blocks.region : Region; 26 import stdx.allocator.building_blocks.allocator_list : AllocatorList; 27 28 import dscanner.analysis.config; 29 import dscanner.analysis.base; 30 import dscanner.analysis.style; 31 import dscanner.analysis.enumarrayliteral; 32 import dscanner.analysis.pokemon; 33 import dscanner.analysis.del; 34 import dscanner.analysis.fish; 35 import dscanner.analysis.numbers; 36 import dscanner.analysis.objectconst; 37 import dscanner.analysis.range; 38 import dscanner.analysis.ifelsesame; 39 import dscanner.analysis.constructors; 40 import dscanner.analysis.unused_variable; 41 import dscanner.analysis.unused_label; 42 import dscanner.analysis.unused_parameter; 43 import dscanner.analysis.duplicate_attribute; 44 import dscanner.analysis.opequals_without_tohash; 45 import dscanner.analysis.length_subtraction; 46 import dscanner.analysis.builtin_property_names; 47 import dscanner.analysis.asm_style; 48 import dscanner.analysis.logic_precedence; 49 import dscanner.analysis.stats_collector; 50 import dscanner.analysis.undocumented; 51 import dscanner.analysis.comma_expression; 52 import dscanner.analysis.function_attributes; 53 import dscanner.analysis.local_imports; 54 import dscanner.analysis.unmodified; 55 import dscanner.analysis.if_statements; 56 import dscanner.analysis.redundant_parens; 57 import dscanner.analysis.mismatched_args; 58 import dscanner.analysis.label_var_same_name_check; 59 import dscanner.analysis.line_length; 60 import dscanner.analysis.auto_ref_assignment; 61 import dscanner.analysis.incorrect_infinite_range; 62 import dscanner.analysis.useless_assert; 63 import dscanner.analysis.alias_syntax_check; 64 import dscanner.analysis.static_if_else; 65 import dscanner.analysis.lambda_return_check; 66 import dscanner.analysis.auto_function; 67 import dscanner.analysis.imports_sortedness; 68 import dscanner.analysis.explicitly_annotated_unittests; 69 import dscanner.analysis.properly_documented_public_functions; 70 import dscanner.analysis.final_attribute; 71 import dscanner.analysis.vcall_in_ctor; 72 import dscanner.analysis.useless_initializer; 73 import dscanner.analysis.allman; 74 import dscanner.analysis.redundant_attributes; 75 import dscanner.analysis.has_public_example; 76 import dscanner.analysis.assert_without_msg; 77 import dscanner.analysis.if_constraints_indent; 78 import dscanner.analysis.trust_too_much; 79 import dscanner.analysis.redundant_storage_class; 80 81 import dsymbol.string_interning : internString; 82 import dsymbol.scope_; 83 import dsymbol.semantic; 84 import dsymbol.conversion; 85 import dsymbol.conversion.first; 86 import dsymbol.conversion.second; 87 import dsymbol.modulecache : ModuleCache; 88 89 import dscanner.utils; 90 91 bool first = true; 92 93 private alias ASTAllocator = CAllocatorImpl!( 94 AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator)); 95 96 immutable string defaultErrorFormat = "{filepath}({line}:{column})[{type}]: {message}"; 97 98 void messageFunctionFormat(string format, Message message, bool isError) 99 { 100 auto s = format; 101 102 s = s.replace("{filepath}", message.fileName); 103 s = s.replace("{line}", to!string(message.line)); 104 s = s.replace("{column}", to!string(message.column)); 105 s = s.replace("{type}", isError ? "error" : "warn"); 106 s = s.replace("{message}", message.message); 107 108 writefln("%s", s); 109 } 110 111 void messageFunction(Message message, bool isError) 112 { 113 messageFunctionFormat(defaultErrorFormat, message, isError); 114 } 115 116 void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool) 117 { 118 writeJSON(Message(fileName, line, column, "dscanner.syntax", message)); 119 } 120 121 void writeJSON(Message message) 122 { 123 if (!first) 124 writeln(","); 125 else 126 first = false; 127 writeln(" {"); 128 writeln(` "key": "`, message.key, `",`); 129 if (message.checkName !is null) 130 { 131 writeln(` "name": "`, message.checkName, `",`); 132 } 133 writeln(` "fileName": "`, message.fileName.replace("\\", "\\\\").replace(`"`, `\"`), `",`); 134 writeln(` "line": `, message.line, `,`); 135 writeln(` "column": `, message.column, `,`); 136 writeln(` "message": "`, message.message.replace("\\", "\\\\").replace(`"`, `\"`), `"`); 137 write(" }"); 138 } 139 140 bool syntaxCheck(string[] fileNames, string errorFormat, ref StringCache stringCache, ref ModuleCache moduleCache) 141 { 142 StaticAnalysisConfig config = defaultStaticAnalysisConfig(); 143 return analyze(fileNames, config, errorFormat, stringCache, moduleCache, false); 144 } 145 146 void generateReport(string[] fileNames, const StaticAnalysisConfig config, 147 ref StringCache cache, ref ModuleCache moduleCache) 148 { 149 writeln("{"); 150 writeln(` "issues": [`); 151 first = true; 152 StatsCollector stats = new StatsCollector(""); 153 ulong lineOfCodeCount; 154 foreach (fileName; fileNames) 155 { 156 auto code = readFile(fileName); 157 // Skip files that could not be read and continue with the rest 158 if (code.length == 0) 159 continue; 160 RollbackAllocator r; 161 const(Token)[] tokens; 162 const Module m = parseModule(fileName, code, &r, defaultErrorFormat, cache, true, tokens, &lineOfCodeCount); 163 stats.visit(m); 164 MessageSet results = analyze(fileName, m, config, moduleCache, tokens, true); 165 foreach (result; results[]) 166 { 167 writeJSON(result); 168 } 169 } 170 writeln(); 171 writeln(" ],"); 172 writefln(` "interfaceCount": %d,`, stats.interfaceCount); 173 writefln(` "classCount": %d,`, stats.classCount); 174 writefln(` "functionCount": %d,`, stats.functionCount); 175 writefln(` "templateCount": %d,`, stats.templateCount); 176 writefln(` "structCount": %d,`, stats.structCount); 177 writefln(` "statementCount": %d,`, stats.statementCount); 178 writefln(` "lineOfCodeCount": %d,`, lineOfCodeCount); 179 writefln(` "undocumentedPublicSymbols": %d`, stats.undocumentedPublicSymbols); 180 writeln("}"); 181 } 182 183 /** 184 * For multiple files 185 * 186 * Returns: true if there were errors or if there were warnings and `staticAnalyze` was true. 187 */ 188 bool analyze(string[] fileNames, const StaticAnalysisConfig config, string errorFormat, 189 ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true) 190 { 191 bool hasErrors; 192 foreach (fileName; fileNames) 193 { 194 auto code = readFile(fileName); 195 // Skip files that could not be read and continue with the rest 196 if (code.length == 0) 197 continue; 198 RollbackAllocator r; 199 uint errorCount; 200 uint warningCount; 201 const(Token)[] tokens; 202 const Module m = parseModule(fileName, code, &r, errorFormat, cache, false, tokens, 203 null, &errorCount, &warningCount); 204 assert(m); 205 if (errorCount > 0 || (staticAnalyze && warningCount > 0)) 206 hasErrors = true; 207 MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze); 208 if (results is null) 209 continue; 210 foreach (result; results[]) 211 { 212 hasErrors = true; 213 messageFunctionFormat(errorFormat, result, false); 214 } 215 } 216 return hasErrors; 217 } 218 219 const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p, 220 string errorFormat, ref StringCache cache, bool report, ref const(Token)[] tokens, 221 ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null) 222 { 223 import dscanner.stats : isLineOfCode; 224 225 auto writeMessages = delegate(string fileName, size_t line, size_t column, string message, bool isError){ 226 return messageFunctionFormat(errorFormat, Message(fileName, line, column, message), isError); 227 }; 228 229 LexerConfig config; 230 config.fileName = fileName; 231 config.stringBehavior = StringBehavior.source; 232 tokens = getTokensForParser(code, config, &cache); 233 if (linesOfCode !is null) 234 (*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens); 235 return dparse.parser.parseModule(tokens, fileName, p, 236 report ? toDelegate(&messageFunctionJSON) : writeMessages, 237 errorCount, warningCount); 238 } 239 240 /** 241 Checks whether a module is part of a user-specified include/exclude list. 242 The user can specify a comma-separated list of filters, everyone needs to start with 243 either a '+' (inclusion) or '-' (exclusion). 244 If no includes are specified, all modules are included. 245 */ 246 bool shouldRun(check : BaseAnalyzer)(string moduleName, const ref StaticAnalysisConfig config) 247 { 248 enum string a = check.name; 249 250 if (mixin("config." ~ a) == Check.disabled) 251 return false; 252 253 // By default, run the check 254 if (!moduleName.length) 255 return true; 256 257 auto filters = mixin("config.filters." ~ a); 258 259 // Check if there are filters are defined 260 // filters starting with a comma are invalid 261 if (filters.length == 0 || filters[0].length == 0) 262 return true; 263 264 auto includers = filters.filter!(f => f[0] == '+').map!(f => f[1..$]); 265 auto excluders = filters.filter!(f => f[0] == '-').map!(f => f[1..$]); 266 267 // exclusion has preference over inclusion 268 if (!excluders.empty && excluders.any!(s => moduleName.canFind(s))) 269 return false; 270 271 if (!includers.empty) 272 return includers.any!(s => moduleName.canFind(s)); 273 274 // by default: include all modules 275 return true; 276 } 277 278 /// 279 unittest 280 { 281 bool test(string moduleName, string filters) 282 { 283 StaticAnalysisConfig config; 284 // it doesn't matter which check we test here 285 config.asm_style_check = Check.enabled; 286 // this is done automatically by inifiled 287 config.filters.asm_style_check = filters.split(","); 288 return shouldRun!AsmStyleCheck(moduleName, config); 289 } 290 291 // test inclusion 292 assert(test("std.foo", "+std.")); 293 // partial matches are ok 294 assert(test("std.foo", "+bar,+foo")); 295 // full as well 296 assert(test("std.foo", "+bar,+std.foo,+foo")); 297 // mismatch 298 assert(!test("std.foo", "+bar,+banana")); 299 300 // test exclusion 301 assert(!test("std.foo", "-std.")); 302 assert(!test("std.foo", "-bar,-std.foo")); 303 assert(!test("std.foo", "-bar,-foo")); 304 // mismatch 305 assert(test("std.foo", "-bar,-banana")); 306 307 // test combination (exclusion has precedence) 308 assert(!test("std.foo", "+foo,-foo")); 309 assert(test("std.foo", "+foo,-bar")); 310 assert(test("std.bar.foo", "-barr,+bar")); 311 } 312 313 MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig analysisConfig, 314 ref ModuleCache moduleCache, const(Token)[] tokens, bool staticAnalyze = true) 315 { 316 import dsymbol.symbol : DSymbol; 317 318 if (!staticAnalyze) 319 return null; 320 321 auto symbolAllocator = scoped!ASTAllocator(); 322 version (unittest) 323 enum ut = true; 324 else 325 enum ut = false; 326 327 string moduleName; 328 if (m !is null && m.moduleDeclaration !is null && 329 m.moduleDeclaration.moduleName !is null && 330 m.moduleDeclaration.moduleName.identifiers !is null) 331 moduleName = m.moduleDeclaration.moduleName.identifiers.map!(e => e.text).join("."); 332 333 auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator, 334 symbolAllocator, true, &moduleCache, null); 335 first.run(); 336 337 secondPass(first.rootSymbol, first.moduleScope, moduleCache); 338 auto moduleScope = first.moduleScope; 339 scope(exit) typeid(DSymbol).destroy(first.rootSymbol.acSymbol); 340 scope(exit) typeid(SemanticSymbol).destroy(first.rootSymbol); 341 scope(exit) typeid(Scope).destroy(first.moduleScope); 342 BaseAnalyzer[] checks; 343 344 GC.disable; 345 346 if (moduleName.shouldRun!AsmStyleCheck(analysisConfig)) 347 checks ~= new AsmStyleCheck(fileName, moduleScope, 348 analysisConfig.asm_style_check == Check.skipTests && !ut); 349 350 if (moduleName.shouldRun!BackwardsRangeCheck(analysisConfig)) 351 checks ~= new BackwardsRangeCheck(fileName, moduleScope, 352 analysisConfig.backwards_range_check == Check.skipTests && !ut); 353 354 if (moduleName.shouldRun!BuiltinPropertyNameCheck(analysisConfig)) 355 checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope, 356 analysisConfig.builtin_property_names_check == Check.skipTests && !ut); 357 358 if (moduleName.shouldRun!CommaExpressionCheck(analysisConfig)) 359 checks ~= new CommaExpressionCheck(fileName, moduleScope, 360 analysisConfig.comma_expression_check == Check.skipTests && !ut); 361 362 if (moduleName.shouldRun!ConstructorCheck(analysisConfig)) 363 checks ~= new ConstructorCheck(fileName, moduleScope, 364 analysisConfig.constructor_check == Check.skipTests && !ut); 365 366 if (moduleName.shouldRun!UnmodifiedFinder(analysisConfig)) 367 checks ~= new UnmodifiedFinder(fileName, moduleScope, 368 analysisConfig.could_be_immutable_check == Check.skipTests && !ut); 369 370 if (moduleName.shouldRun!DeleteCheck(analysisConfig)) 371 checks ~= new DeleteCheck(fileName, moduleScope, 372 analysisConfig.delete_check == Check.skipTests && !ut); 373 374 if (moduleName.shouldRun!DuplicateAttributeCheck(analysisConfig)) 375 checks ~= new DuplicateAttributeCheck(fileName, moduleScope, 376 analysisConfig.duplicate_attribute == Check.skipTests && !ut); 377 378 if (moduleName.shouldRun!EnumArrayLiteralCheck(analysisConfig)) 379 checks ~= new EnumArrayLiteralCheck(fileName, moduleScope, 380 analysisConfig.enum_array_literal_check == Check.skipTests && !ut); 381 382 if (moduleName.shouldRun!PokemonExceptionCheck(analysisConfig)) 383 checks ~= new PokemonExceptionCheck(fileName, moduleScope, 384 analysisConfig.exception_check == Check.skipTests && !ut); 385 386 if (moduleName.shouldRun!FloatOperatorCheck(analysisConfig)) 387 checks ~= new FloatOperatorCheck(fileName, moduleScope, 388 analysisConfig.float_operator_check == Check.skipTests && !ut); 389 390 if (moduleName.shouldRun!FunctionAttributeCheck(analysisConfig)) 391 checks ~= new FunctionAttributeCheck(fileName, moduleScope, 392 analysisConfig.function_attribute_check == Check.skipTests && !ut); 393 394 if (moduleName.shouldRun!IfElseSameCheck(analysisConfig)) 395 checks ~= new IfElseSameCheck(fileName, moduleScope, 396 analysisConfig.if_else_same_check == Check.skipTests&& !ut); 397 398 if (moduleName.shouldRun!LabelVarNameCheck(analysisConfig)) 399 checks ~= new LabelVarNameCheck(fileName, moduleScope, 400 analysisConfig.label_var_same_name_check == Check.skipTests && !ut); 401 402 if (moduleName.shouldRun!LengthSubtractionCheck(analysisConfig)) 403 checks ~= new LengthSubtractionCheck(fileName, moduleScope, 404 analysisConfig.length_subtraction_check == Check.skipTests && !ut); 405 406 if (moduleName.shouldRun!LocalImportCheck(analysisConfig)) 407 checks ~= new LocalImportCheck(fileName, moduleScope, 408 analysisConfig.local_import_check == Check.skipTests && !ut); 409 410 if (moduleName.shouldRun!LogicPrecedenceCheck(analysisConfig)) 411 checks ~= new LogicPrecedenceCheck(fileName, moduleScope, 412 analysisConfig.logical_precedence_check == Check.skipTests && !ut); 413 414 if (moduleName.shouldRun!MismatchedArgumentCheck(analysisConfig)) 415 checks ~= new MismatchedArgumentCheck(fileName, moduleScope, 416 analysisConfig.mismatched_args_check == Check.skipTests && !ut); 417 418 if (moduleName.shouldRun!NumberStyleCheck(analysisConfig)) 419 checks ~= new NumberStyleCheck(fileName, moduleScope, 420 analysisConfig.number_style_check == Check.skipTests && !ut); 421 422 if (moduleName.shouldRun!ObjectConstCheck(analysisConfig)) 423 checks ~= new ObjectConstCheck(fileName, moduleScope, 424 analysisConfig.object_const_check == Check.skipTests && !ut); 425 426 if (moduleName.shouldRun!OpEqualsWithoutToHashCheck(analysisConfig)) 427 checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope, 428 analysisConfig.opequals_tohash_check == Check.skipTests && !ut); 429 430 if (moduleName.shouldRun!RedundantParenCheck(analysisConfig)) 431 checks ~= new RedundantParenCheck(fileName, moduleScope, 432 analysisConfig.redundant_parens_check == Check.skipTests && !ut); 433 434 if (moduleName.shouldRun!StyleChecker(analysisConfig)) 435 checks ~= new StyleChecker(fileName, moduleScope, 436 analysisConfig.style_check == Check.skipTests && !ut); 437 438 if (moduleName.shouldRun!UndocumentedDeclarationCheck(analysisConfig)) 439 checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope, 440 analysisConfig.undocumented_declaration_check == Check.skipTests && !ut); 441 442 if (moduleName.shouldRun!UnusedLabelCheck(analysisConfig)) 443 checks ~= new UnusedLabelCheck(fileName, moduleScope, 444 analysisConfig.unused_label_check == Check.skipTests && !ut); 445 446 if (moduleName.shouldRun!UnusedVariableCheck(analysisConfig)) 447 checks ~= new UnusedVariableCheck(fileName, moduleScope, 448 analysisConfig.unused_variable_check == Check.skipTests && !ut); 449 450 if (moduleName.shouldRun!UnusedParameterCheck(analysisConfig)) 451 checks ~= new UnusedParameterCheck(fileName, moduleScope, 452 analysisConfig.unused_parameter_check == Check.skipTests && !ut); 453 454 if (moduleName.shouldRun!LineLengthCheck(analysisConfig)) 455 checks ~= new LineLengthCheck(fileName, tokens, 456 analysisConfig.long_line_check == Check.skipTests && !ut); 457 458 if (moduleName.shouldRun!AutoRefAssignmentCheck(analysisConfig)) 459 checks ~= new AutoRefAssignmentCheck(fileName, 460 analysisConfig.auto_ref_assignment_check == Check.skipTests && !ut); 461 462 if (moduleName.shouldRun!IncorrectInfiniteRangeCheck(analysisConfig)) 463 checks ~= new IncorrectInfiniteRangeCheck(fileName, 464 analysisConfig.incorrect_infinite_range_check == Check.skipTests && !ut); 465 466 if (moduleName.shouldRun!UselessAssertCheck(analysisConfig)) 467 checks ~= new UselessAssertCheck(fileName, 468 analysisConfig.useless_assert_check == Check.skipTests && !ut); 469 470 if (moduleName.shouldRun!AliasSyntaxCheck(analysisConfig)) 471 checks ~= new AliasSyntaxCheck(fileName, 472 analysisConfig.alias_syntax_check == Check.skipTests && !ut); 473 474 if (moduleName.shouldRun!StaticIfElse(analysisConfig)) 475 checks ~= new StaticIfElse(fileName, 476 analysisConfig.static_if_else_check == Check.skipTests && !ut); 477 478 if (moduleName.shouldRun!LambdaReturnCheck(analysisConfig)) 479 checks ~= new LambdaReturnCheck(fileName, 480 analysisConfig.lambda_return_check == Check.skipTests && !ut); 481 482 if (moduleName.shouldRun!AutoFunctionChecker(analysisConfig)) 483 checks ~= new AutoFunctionChecker(fileName, 484 analysisConfig.auto_function_check == Check.skipTests && !ut); 485 486 if (moduleName.shouldRun!ImportSortednessCheck(analysisConfig)) 487 checks ~= new ImportSortednessCheck(fileName, 488 analysisConfig.imports_sortedness == Check.skipTests && !ut); 489 490 if (moduleName.shouldRun!ExplicitlyAnnotatedUnittestCheck(analysisConfig)) 491 checks ~= new ExplicitlyAnnotatedUnittestCheck(fileName, 492 analysisConfig.explicitly_annotated_unittests == Check.skipTests && !ut); 493 494 if (moduleName.shouldRun!ProperlyDocumentedPublicFunctions(analysisConfig)) 495 checks ~= new ProperlyDocumentedPublicFunctions(fileName, 496 analysisConfig.properly_documented_public_functions == Check.skipTests && !ut); 497 498 if (moduleName.shouldRun!FinalAttributeChecker(analysisConfig)) 499 checks ~= new FinalAttributeChecker(fileName, 500 analysisConfig.final_attribute_check == Check.skipTests && !ut); 501 502 if (moduleName.shouldRun!VcallCtorChecker(analysisConfig)) 503 checks ~= new VcallCtorChecker(fileName, 504 analysisConfig.vcall_in_ctor == Check.skipTests && !ut); 505 506 if (moduleName.shouldRun!UselessInitializerChecker(analysisConfig)) 507 checks ~= new UselessInitializerChecker(fileName, 508 analysisConfig.useless_initializer == Check.skipTests && !ut); 509 510 if (moduleName.shouldRun!AllManCheck(analysisConfig)) 511 checks ~= new AllManCheck(fileName, tokens, 512 analysisConfig.allman_braces_check == Check.skipTests && !ut); 513 514 if (moduleName.shouldRun!RedundantAttributesCheck(analysisConfig)) 515 checks ~= new RedundantAttributesCheck(fileName, moduleScope, 516 analysisConfig.redundant_attributes_check == Check.skipTests && !ut); 517 518 if (moduleName.shouldRun!HasPublicExampleCheck(analysisConfig)) 519 checks ~= new HasPublicExampleCheck(fileName, moduleScope, 520 analysisConfig.has_public_example == Check.skipTests && !ut); 521 522 if (moduleName.shouldRun!AssertWithoutMessageCheck(analysisConfig)) 523 checks ~= new AssertWithoutMessageCheck(fileName, moduleScope, 524 analysisConfig.assert_without_msg == Check.skipTests && !ut); 525 526 if (moduleName.shouldRun!IfConstraintsIndentCheck(analysisConfig)) 527 checks ~= new IfConstraintsIndentCheck(fileName, tokens, 528 analysisConfig.if_constraints_indent == Check.skipTests && !ut); 529 530 if (moduleName.shouldRun!TrustTooMuchCheck(analysisConfig)) 531 checks ~= new TrustTooMuchCheck(fileName, 532 analysisConfig.trust_too_much == Check.skipTests && !ut); 533 534 if (moduleName.shouldRun!RedundantStorageClassCheck(analysisConfig)) 535 checks ~= new RedundantStorageClassCheck(fileName, 536 analysisConfig.redundant_storage_classes == Check.skipTests && !ut); 537 538 version (none) 539 if (moduleName.shouldRun!IfStatementCheck(analysisConfig)) 540 checks ~= new IfStatementCheck(fileName, moduleScope, 541 analysisConfig.redundant_if_check == Check.skipTests && !ut); 542 543 MessageSet set = new MessageSet; 544 foreach (check; checks) 545 { 546 check.visit(m); 547 foreach (message; check.messages) 548 set.insert(message); 549 } 550 551 GC.enable; 552 553 return set; 554 } 555