1 /** 2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.backend.analyze.pass_clang; 11 12 import logger = std.experimental.logger; 13 import std.algorithm : among, map, sort, filter; 14 import std.array : empty, array, appender, Appender; 15 import std.exception : collectException; 16 import std.format : formattedWrite; 17 import std.meta : AliasSeq; 18 import std.typecons : Nullable; 19 20 import blob_model : Blob; 21 import my.container.vector : vector, Vector; 22 import my.gc.refc : RefCounted; 23 import my.optional; 24 25 import clang.Cursor : Cursor; 26 import clang.Eval : Eval; 27 import clang.Type : Type; 28 import clang.c.Index : CXTypeKind, CXCursorKind, CXEvalResultKind, CXTokenKind; 29 30 import libclang_ast.ast : Visitor; 31 import libclang_ast.cursor_logger : logNode, mixinNodeLog; 32 33 import dextool.clang_extensions : getUnderlyingExprNode; 34 35 import dextool.type : Path, AbsolutePath; 36 37 import dextool.plugin.mutate.backend.analyze.ast : Interval, Location, TypeKind, 38 Node, Ast, BreathFirstRange; 39 import dextool.plugin.mutate.backend.analyze.extensions; 40 import dextool.plugin.mutate.backend.analyze.utility; 41 import dextool.plugin.mutate.backend.interface_ : FilesysIO, InvalidPathException; 42 import dextool.plugin.mutate.backend.type : Language, SourceLoc, Offset, SourceLocRange; 43 44 import analyze = dextool.plugin.mutate.backend.analyze.ast; 45 46 alias accept = dextool.plugin.mutate.backend.analyze.extensions.accept; 47 48 /** Translate a clang AST to a mutation AST. 49 */ 50 ClangResult toMutateAst(const Cursor root, FilesysIO fio) @safe { 51 import libclang_ast.ast; 52 53 auto visitor = new BaseVisitor(fio); 54 scope (exit) 55 visitor.dispose; 56 auto ast = ClangAST!BaseVisitor(root); 57 ast.accept(visitor); 58 visitor.ast.releaseCache; 59 60 auto rval = ClangResult(visitor.ast, visitor.includes.data); 61 return rval; 62 } 63 64 struct ClangResult { 65 RefCounted!(analyze.Ast) ast; 66 67 /// All dependencies that the root has. 68 Path[] dependencies; 69 } 70 71 private: 72 73 struct OperatorCursor { 74 analyze.Expr astOp; 75 76 // true if the operator is overloaded. 77 bool isOverload; 78 79 // the whole expression 80 analyze.Location exprLoc; 81 DeriveCursorTypeResult exprTy; 82 83 // the operator itself 84 analyze.Operator operator; 85 analyze.Location opLoc; 86 87 Cursor lhs; 88 Cursor rhs; 89 90 /// Add the result to the AST and astOp to the parent. 91 /// astOp is set to have two children, lhs and rhs. 92 void put(analyze.Node parent, ref analyze.Ast ast) @safe { 93 ast.put(astOp, exprLoc); 94 ast.put(operator, opLoc); 95 96 exprTy.put(ast); 97 98 if (exprTy.type !is null) 99 ast.put(astOp, exprTy.id); 100 101 if (exprTy.symbol !is null) 102 ast.put(astOp, exprTy.symId); 103 104 parent.children ~= astOp; 105 } 106 } 107 108 Nullable!OperatorCursor operatorCursor(T)(ref Ast ast, T node) { 109 import dextool.clang_extensions : getExprOperator, OpKind, ValueKind, getUnderlyingExprNode; 110 111 auto op = getExprOperator(node.cursor); 112 if (!op.isValid) 113 return typeof(return)(); 114 115 auto path = op.cursor.location.path.Path; 116 if (path.empty) 117 return typeof(return)(); 118 119 OperatorCursor res; 120 121 void sidesPoint() { 122 auto sides = op.sides; 123 if (sides.lhs.isValid) { 124 res.lhs = getUnderlyingExprNode(sides.lhs); 125 } 126 if (sides.rhs.isValid) { 127 res.rhs = getUnderlyingExprNode(sides.rhs); 128 } 129 } 130 131 // the operator itself 132 void opPoint() { 133 auto loc = op.location; 134 auto sr = loc.spelling; 135 res.operator = ast.make!(analyze.Operator); 136 res.opLoc = analyze.Location(path, Interval(sr.offset, 137 cast(uint)(sr.offset + op.length)), SourceLocRange(SourceLoc(loc.line, 138 loc.column), SourceLoc(loc.line, cast(uint)(loc.column + op.length)))); 139 } 140 141 // the arguments and the operator 142 void exprPoint() { 143 auto sr = op.cursor.extent; 144 res.exprLoc = analyze.Location(path, Interval(sr.start.offset, 145 sr.end.offset), SourceLocRange(SourceLoc(sr.start.line, 146 sr.start.column), SourceLoc(sr.end.line, sr.end.column))); 147 res.exprTy = deriveCursorType(ast, op.cursor); 148 switch (op.kind) with (OpKind) { 149 case OO_Star: // "*" 150 res.isOverload = true; 151 goto case; 152 case Mul: // "*" 153 res.astOp = ast.make!(analyze.OpMul); 154 break; 155 case OO_Slash: // "/" 156 res.isOverload = true; 157 goto case; 158 case Div: // "/" 159 res.astOp = ast.make!(analyze.OpDiv); 160 break; 161 case OO_Percent: // "%" 162 res.isOverload = true; 163 goto case; 164 case Rem: // "%" 165 res.astOp = ast.make!(analyze.OpMod); 166 break; 167 case OO_Plus: // "+" 168 res.isOverload = true; 169 goto case; 170 case Add: // "+" 171 res.astOp = ast.make!(analyze.OpAdd); 172 break; 173 case OO_Minus: // "-" 174 res.isOverload = true; 175 goto case; 176 case Sub: // "-" 177 res.astOp = ast.make!(analyze.OpSub); 178 break; 179 case OO_Less: // "<" 180 res.isOverload = true; 181 goto case; 182 case LT: // "<" 183 res.astOp = ast.make!(analyze.OpLess); 184 break; 185 case OO_Greater: // ">" 186 res.isOverload = true; 187 goto case; 188 case GT: // ">" 189 res.astOp = ast.make!(analyze.OpGreater); 190 break; 191 case OO_LessEqual: // "<=" 192 res.isOverload = true; 193 goto case; 194 case LE: // "<=" 195 res.astOp = ast.make!(analyze.OpLessEq); 196 break; 197 case OO_GreaterEqual: // ">=" 198 res.isOverload = true; 199 goto case; 200 case GE: // ">=" 201 res.astOp = ast.make!(analyze.OpGreaterEq); 202 break; 203 case OO_EqualEqual: // "==" 204 res.isOverload = true; 205 goto case; 206 case EQ: // "==" 207 res.astOp = ast.make!(analyze.OpEqual); 208 break; 209 case OO_Exclaim: // "!" 210 res.isOverload = true; 211 goto case; 212 case LNot: // "!" 213 res.astOp = ast.make!(analyze.OpNegate); 214 break; 215 case OO_ExclaimEqual: // "!=" 216 res.isOverload = true; 217 goto case; 218 case NE: // "!=" 219 res.astOp = ast.make!(analyze.OpNotEqual); 220 break; 221 case OO_AmpAmp: // "&&" 222 res.isOverload = true; 223 goto case; 224 case LAnd: // "&&" 225 res.astOp = ast.make!(analyze.OpAnd); 226 break; 227 case OO_PipePipe: // "||" 228 res.isOverload = true; 229 goto case; 230 case LOr: // "||" 231 res.astOp = ast.make!(analyze.OpOr); 232 break; 233 case OO_Amp: // "&" 234 res.isOverload = true; 235 goto case; 236 case And: // "&" 237 res.astOp = ast.make!(analyze.OpAndBitwise); 238 break; 239 case OO_Pipe: // "|" 240 res.isOverload = true; 241 goto case; 242 case Or: // "|" 243 res.astOp = ast.make!(analyze.OpOrBitwise); 244 break; 245 case OO_StarEqual: // "*=" 246 res.isOverload = true; 247 goto case; 248 case MulAssign: // "*=" 249 res.astOp = ast.make!(analyze.OpAssignMul); 250 break; 251 case OO_SlashEqual: // "/=" 252 res.isOverload = true; 253 goto case; 254 case DivAssign: // "/=" 255 res.astOp = ast.make!(analyze.OpAssignDiv); 256 break; 257 case OO_PercentEqual: // "%=" 258 res.isOverload = true; 259 goto case; 260 case RemAssign: // "%=" 261 res.astOp = ast.make!(analyze.OpAssignMod); 262 break; 263 case OO_PlusEqual: // "+=" 264 res.isOverload = true; 265 goto case; 266 case AddAssign: // "+=" 267 res.astOp = ast.make!(analyze.OpAssignAdd); 268 break; 269 case OO_MinusEqual: // "-=" 270 res.isOverload = true; 271 goto case; 272 case SubAssign: // "-=" 273 res.astOp = ast.make!(analyze.OpAssignSub); 274 break; 275 case OO_AmpEqual: // "&=" 276 res.isOverload = true; 277 goto case; 278 case AndAssign: // "&=" 279 res.astOp = ast.make!(analyze.OpAssignAndBitwise); 280 break; 281 case OO_PipeEqual: // "|=" 282 res.isOverload = true; 283 goto case; 284 case OrAssign: // "|=" 285 res.astOp = ast.make!(analyze.OpAssignOrBitwise); 286 break; 287 case OO_CaretEqual: // "^=" 288 res.isOverload = true; 289 goto case; 290 case OO_Equal: // "=" 291 goto case; 292 case ShlAssign: // "<<=" 293 goto case; 294 case ShrAssign: // ">>=" 295 goto case; 296 case XorAssign: // "^=" 297 goto case; 298 case Assign: // "=" 299 res.astOp = ast.make!(analyze.OpAssign); 300 break; 301 //case Xor: // "^" 302 //case OO_Caret: // "^" 303 //case OO_Tilde: // "~" 304 default: 305 res.astOp = ast.make!(analyze.BinaryOp); 306 } 307 } 308 309 exprPoint; 310 opPoint; 311 sidesPoint; 312 return typeof(return)(res); 313 } 314 315 @safe: 316 317 Location toLocation(ref const Cursor c) { 318 auto e = c.extent; 319 auto interval = Interval(e.start.offset, e.end.offset); 320 auto begin = e.start; 321 auto end = e.end; 322 return Location(e.path.Path, interval, SourceLocRange(SourceLoc(begin.line, 323 begin.column), SourceLoc(end.line, end.column))); 324 } 325 326 /** Find all mutation points that affect a whole expression. 327 * 328 * TODO change the name of the class. It is more than just an expression 329 * visitor. 330 * 331 * # Usage of kind_stack 332 * All usage of the kind_stack shall be documented here. 333 * - track assignments to avoid generating unary insert operators for the LHS. 334 */ 335 final class BaseVisitor : ExtendedVisitor { 336 import clang.c.Index : CXCursorKind, CXTypeKind; 337 import libclang_ast.ast; 338 import dextool.clang_extensions : getExprOperator, OpKind; 339 import my.set; 340 341 alias visit = ExtendedVisitor.visit; 342 343 // the depth the visitor is at. 344 uint indent; 345 // A stack of the nodes that are generated up to the current one. 346 Stack!(analyze.Node) nstack; 347 348 // A stack of visited cursors up to the current one. 349 Stack!(CXCursorKind) cstack; 350 351 /// The elements that where removed from the last decrement. 352 Vector!(analyze.Node) lastDecr; 353 354 /// List of macro locations which blacklist mutants that would be injected 355 /// in any of them. 356 BlackList blacklist; 357 358 RefCounted!(analyze.Ast) ast; 359 Appender!(Path[]) includes; 360 361 /// Keep track of visited nodes to avoid circulare references. 362 Set!size_t isVisited; 363 364 FilesysIO fio; 365 366 this(FilesysIO fio) nothrow { 367 this.fio = fio; 368 this.ast = analyze.Ast.init; 369 } 370 371 void dispose() { 372 ast.release; 373 } 374 375 /// Returns: the depth (1+) if any of the parent nodes is `k`. 376 uint isParent(K...)(auto ref K k) { 377 return cstack.isParent(k); 378 } 379 380 /// Returns: if the previous nodes is a CXCursorKind `k`. 381 bool isDirectParent(CXCursorKind k) { 382 if (cstack.empty) 383 return false; 384 return cstack[$ - 1].data == k; 385 } 386 387 override void incr() @safe { 388 ++indent; 389 lastDecr.clear; 390 } 391 392 override void decr() @trusted { 393 --indent; 394 lastDecr = nstack.popUntil(indent); 395 cstack.popUntil(indent); 396 } 397 398 private void pushStack(analyze.Node n, analyze.Location l, const CXCursorKind cKind) @trusted { 399 n.blacklist = n.blacklist || blacklist.inside(l); 400 n.schemaBlacklist = n.blacklist || n.schemaBlacklist; 401 if (!nstack.empty) 402 n.schemaBlacklist = n.schemaBlacklist || nstack[$ - 1].data.schemaBlacklist; 403 nstack.put(n, indent); 404 cstack.put(cKind, indent); 405 ast.put(n, l); 406 } 407 408 /// Returns: true if it is OK to modify the cursor 409 private void pushStack(AstT, ClangT)(AstT n, ClangT c) @trusted { 410 static if (is(ClangT == Cursor)) 411 auto loc = c.toLocation; 412 else 413 auto loc = c.cursor.toLocation; 414 nstack.back.children ~= n; 415 pushStack(n, loc, c.kind); 416 } 417 418 override void visit(const TranslationUnit v) { 419 import clang.c.Index : CXLanguageKind; 420 421 mixin(mixinNodeLog!()); 422 423 ast.root = ast.make!(analyze.TranslationUnit); 424 auto loc = v.cursor.toLocation; 425 pushStack(ast.root, loc, v.cursor.kind); 426 427 blacklist = BlackList(v.cursor); 428 429 // it is most often invalid 430 switch (v.cursor.language) { 431 case CXLanguageKind.c: 432 ast.lang = Language.c; 433 break; 434 case CXLanguageKind.cPlusPlus: 435 ast.lang = Language.cpp; 436 break; 437 default: 438 ast.lang = Language.assumeCpp; 439 } 440 441 v.accept(this); 442 } 443 444 override void visit(const Attribute v) { 445 mixin(mixinNodeLog!()); 446 v.accept(this); 447 } 448 449 override void visit(const Declaration v) { 450 mixin(mixinNodeLog!()); 451 v.accept(this); 452 } 453 454 override void visit(const VarDecl v) @trusted { 455 mixin(mixinNodeLog!()); 456 visitVar(v); 457 v.accept(this); 458 } 459 460 override void visit(const ParmDecl v) @trusted { 461 mixin(mixinNodeLog!()); 462 visitVar(v); 463 v.accept(this); 464 } 465 466 override void visit(const DeclStmt v) { 467 mixin(mixinNodeLog!()); 468 469 // this, in clang-11, is one of the patterns in the AST when struct 470 // binding is used: 471 // declStmt 472 // | unexposedDecl 473 // | unexposedDecl 474 // | unexposedDecl 475 // | unexposedDecl 476 // | unexposedDecl 477 // | unexposedDecl 478 // | callExpr 479 // it can't be assumed to be the only one. 480 // by injecting a Poision node for a DeclStmt it signal that there are 481 // hidden traps thus any mutation and schemata should be careful. 482 auto n = ast.make!(analyze.Poision); 483 pushStack(n, v); 484 485 v.accept(this); 486 } 487 488 override void visit(const ClassTemplate v) { 489 mixin(mixinNodeLog!()); 490 // by adding the node it is possible to search for it in cstack 491 auto n = ast.make!(analyze.Poision); 492 pushStack(n, v); 493 v.accept(this); 494 } 495 496 override void visit(const ClassTemplatePartialSpecialization v) { 497 mixin(mixinNodeLog!()); 498 // by adding the node it is possible to search for it in cstack 499 auto n = ast.make!(analyze.Poision); 500 pushStack(n, v); 501 v.accept(this); 502 } 503 504 override void visit(const FunctionTemplate v) { 505 mixin(mixinNodeLog!()); 506 auto n = ast.make!(analyze.Function); 507 // it is too uncertain to inject mutant schematan inside a template 508 // because the types are not known which lead to a high probability 509 // that the schemata code will fail to compile. 510 n.schemaBlacklist = true; 511 pushStack(n, v); 512 513 v.accept(this); 514 } 515 516 override void visit(const TemplateTypeParameter v) { 517 mixin(mixinNodeLog!()); 518 // block mutants inside template parameters 519 } 520 521 override void visit(const TemplateTemplateParameter v) { 522 mixin(mixinNodeLog!()); 523 // block mutants inside template parameters 524 } 525 526 override void visit(const NonTypeTemplateParameter v) { 527 mixin(mixinNodeLog!()); 528 // block mutants inside template parameters 529 } 530 531 override void visit(const TypeAliasDecl v) { 532 mixin(mixinNodeLog!()); 533 // block mutants inside template parameters 534 } 535 536 override void visit(const CxxBaseSpecifier v) { 537 mixin(mixinNodeLog!()); 538 // block mutants inside template parameters. 539 // the only mutants that are inside an inheritance is template 540 // parameters and such. 541 } 542 543 private void visitVar(T)(T v) @trusted { 544 pushStack(ast.make!(analyze.VarDecl), v); 545 } 546 547 override void visit(const Directive v) { 548 mixin(mixinNodeLog!()); 549 v.accept(this); 550 } 551 552 override void visit(const InclusionDirective v) @trusted { 553 import std.file : exists; 554 import std.path : buildPath, dirName; 555 556 mixin(mixinNodeLog!()); 557 558 const spell = v.spelling; 559 const file = v.cursor.location.file.name; 560 const p = buildPath(file.dirName, spell); 561 if (exists(p)) 562 includes.put(Path(p)); 563 else 564 includes.put(Path(spell)); 565 566 v.accept(this); 567 } 568 569 override void visit(const Reference v) { 570 mixin(mixinNodeLog!()); 571 v.accept(this); 572 } 573 574 // TODO overlapping logic with Expression. deduplicate 575 override void visit(const DeclRefExpr v) @trusted { 576 import libclang_ast.ast : dispatch; 577 import clang.SourceRange : intersects; 578 579 mixin(mixinNodeLog!()); 580 581 if (v.cursor.toHash in isVisited) 582 return; 583 isVisited.add(v.cursor.toHash); 584 585 auto n = ast.make!(analyze.Expr); 586 n.schemaBlacklist = isParent(CXCursorKind.classTemplate, 587 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 588 589 auto ue = deriveCursorType(ast, v.cursor); 590 ue.put(ast); 591 if (ue.type !is null) { 592 ast.put(n, ue.id); 593 } 594 595 // only deref a node which is a self-reference 596 auto r = v.cursor.referenced; 597 if (r.isValid && r != v.cursor && intersects(v.cursor.extent, r.extent) 598 && r.toHash !in isVisited) { 599 isVisited.add(r.toHash); 600 pushStack(n, v); 601 602 incr; 603 scope (exit) 604 decr; 605 dispatch(r, this); 606 } else if (ue.expr.isValid && ue.expr != v.cursor && ue.expr.toHash !in isVisited) { 607 isVisited.add(ue.expr.toHash); 608 pushStack(n, ue.expr); 609 610 incr; 611 scope (exit) 612 decr; 613 dispatch(ue.expr, this); 614 } else { 615 pushStack(n, v); 616 v.accept(this); 617 } 618 } 619 620 override void visit(const Statement v) { 621 mixin(mixinNodeLog!()); 622 v.accept(this); 623 } 624 625 override void visit(const ArraySubscriptExpr v) { 626 mixin(mixinNodeLog!()); 627 // block schematan inside subscripts because some lead to compilation 628 // errors. Need to investigate more to understand why and how to avoid. 629 // For now they are blocked. 630 auto n = ast.make!(analyze.Poision); 631 n.schemaBlacklist = true; 632 pushStack(n, v); 633 v.accept(this); 634 } 635 636 override void visit(const Expression v) { 637 mixin(mixinNodeLog!()); 638 639 if (v.cursor.toHash in isVisited) 640 return; 641 isVisited.add(v.cursor.toHash); 642 643 auto n = ast.make!(analyze.Expr); 644 n.schemaBlacklist = isParent(CXCursorKind.classTemplate, 645 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 646 647 auto ue = deriveCursorType(ast, v.cursor); 648 ue.put(ast); 649 if (ue.type !is null) { 650 ast.put(n, ue.id); 651 } 652 653 pushStack(n, v); 654 v.accept(this); 655 } 656 657 override void visit(const Preprocessor v) { 658 mixin(mixinNodeLog!()); 659 660 const bool isCpp = v.spelling == "__cplusplus"; 661 662 if (isCpp) 663 ast.lang = Language.cpp; 664 else if (!isCpp && ast.lang != Language.cpp) 665 ast.lang = Language.c; 666 667 v.accept(this); 668 } 669 670 override void visit(const EnumDecl v) @trusted { 671 mixin(mixinNodeLog!()); 672 673 // extract the boundaries of the enum to update the type db. 674 scope vis = new EnumVisitor(ast.get, indent); 675 vis.visit(v); 676 ast.types.set(vis.id, vis.toType); 677 } 678 679 override void visit(const FunctionDecl v) @trusted { 680 mixin(mixinNodeLog!()); 681 visitFunc(v); 682 } 683 684 override void visit(const Constructor v) @trusted { 685 mixin(mixinNodeLog!()); 686 687 auto n = ast.make!(analyze.Poision); 688 n.schemaBlacklist = isConstExpr(v.cursor); 689 pushStack(n, v); 690 691 // skip all "= default" 692 if (!v.cursor.isDefaulted) 693 v.accept(this); 694 } 695 696 override void visit(const Destructor v) @trusted { 697 mixin(mixinNodeLog!()); 698 699 auto n = ast.make!(analyze.Poision); 700 n.schemaBlacklist = isConstExpr(v.cursor); 701 pushStack(n, v); 702 703 // skip all "= default" 704 if (!v.cursor.isDefaulted) 705 v.accept(this); 706 // TODO: no test covers this case where = default is used for a 707 // destructor. For some versions of clang a CompoundStmt is generated 708 } 709 710 override void visit(const CxxMethod v) { 711 mixin(mixinNodeLog!()); 712 713 // model C++ methods as functions. It should be enough to know that it 714 // is a function and the return type when generating mutants. 715 716 // skip all "= default" 717 if (!v.cursor.isDefaulted) 718 visitFunc(v); 719 } 720 721 override void visit(const BreakStmt v) { 722 mixin(mixinNodeLog!()); 723 v.accept(this); 724 } 725 726 override void visit(const BinaryOperator v) @trusted { 727 mixin(mixinNodeLog!()); 728 729 visitOp(v, v.cursor.kind); 730 } 731 732 override void visit(const UnaryOperator v) @trusted { 733 mixin(mixinNodeLog!()); 734 visitOp(v, v.cursor.kind); 735 } 736 737 override void visit(const CompoundAssignOperator v) { 738 mixin(mixinNodeLog!()); 739 // TODO: implement all aor assignment such as += 740 pushStack(ast.make!(analyze.OpAssign), v); 741 v.accept(this); 742 } 743 744 override void visit(const CallExpr v) { 745 mixin(mixinNodeLog!()); 746 747 if (!visitOp(v, v.cursor.kind)) { 748 visitCall(v); 749 } 750 } 751 752 override void visit(const CxxThrowExpr v) { 753 mixin(mixinNodeLog!()); 754 // model a C++ exception as a return expression because that is 755 // "basically" what happens. 756 auto n = ast.make!(analyze.Return); 757 n.blacklist = true; 758 pushStack(n, v); 759 v.accept(this); 760 } 761 762 override void visit(const InitListExpr v) { 763 mixin(mixinNodeLog!()); 764 pushStack(ast.make!(analyze.Constructor), v); 765 v.accept(this); 766 } 767 768 override void visit(const LambdaExpr v) { 769 mixin(mixinNodeLog!()); 770 771 // model C++ lambdas as functions. It should be enough to know that it 772 // is a function and the return type when generating mutants. 773 visitFunc(v); 774 } 775 776 override void visit(const ReturnStmt v) { 777 mixin(mixinNodeLog!()); 778 pushStack(ast.make!(analyze.Return), v); 779 v.accept(this); 780 } 781 782 override void visit(const CompoundStmt v) { 783 import std.algorithm : min; 784 785 mixin(mixinNodeLog!()); 786 787 static uint findBraketOffset(Blob file, const uint begin, const uint end, const ubyte letter) { 788 for (uint i = begin; i < end; ++i) { 789 if (file.content[i] == letter) { 790 return i; 791 } 792 } 793 return begin; 794 } 795 796 if (isDirectParent(CXCursorKind.switchStmt)) { 797 // the CompoundStmt statement {} directly inside a switch statement 798 // isn't useful to manipulate as a block. The useful part is the 799 // smaller blocks that the case and default break down the block 800 // into thus this avoid generating useless blocks that lead to 801 // equivalent or unproductive mutants. 802 } else 803 try { 804 auto loc = v.cursor.toLocation; 805 auto file = fio.makeInput(loc.file); 806 const maxEnd = file.content.length; 807 808 // The block that can be modified is the inside of it thus the 809 // a CompoundStmt that represent a "{..}" can for example be the 810 // body of a function or the block that a try statement encompase. 811 // done then a SDL can't be generated that delete the inside of 812 // e.g. void functions. 813 814 auto end = min(findBraketOffset(file, loc.interval.end == 0 815 ? loc.interval.end : loc.interval.end - 1, cast(uint) maxEnd, 816 cast(ubyte) '}'), maxEnd); 817 auto begin = findBraketOffset(file, loc.interval.begin, end, cast(ubyte) '{'); 818 819 if (begin < end) 820 begin = begin + 1; 821 822 // TODO: need to adjust sloc too 823 loc.interval = Interval(begin, end); 824 825 auto n = ast.make!(analyze.Block); 826 nstack.back.children ~= n; 827 pushStack(n, loc, v.cursor.kind); 828 } catch (InvalidPathException e) { 829 } catch (Exception e) { 830 logger.trace(e.msg).collectException; 831 } 832 833 v.accept(this); 834 } 835 836 override void visit(const CaseStmt v) { 837 mixin(mixinNodeLog!()); 838 v.accept(this); 839 } 840 841 override void visit(const DefaultStmt v) { 842 mixin(mixinNodeLog!()); 843 v.accept(this); 844 } 845 846 override void visit(const ForStmt v) { 847 mixin(mixinNodeLog!()); 848 pushStack(ast.make!(analyze.Loop), v); 849 850 auto visitor = new FindVisitor!CompoundStmt; 851 v.accept(visitor); 852 853 if (visitor.node !is null) { 854 this.visit(visitor.node); 855 } 856 } 857 858 override void visit(const CxxForRangeStmt v) { 859 mixin(mixinNodeLog!()); 860 pushStack(ast.make!(analyze.Loop), v); 861 862 auto visitor = new FindVisitor!CompoundStmt; 863 v.accept(visitor); 864 865 if (visitor.node !is null) { 866 this.visit(visitor.node); 867 } 868 } 869 870 override void visit(const WhileStmt v) { 871 mixin(mixinNodeLog!()); 872 pushStack(ast.make!(analyze.Loop), v); 873 v.accept(this); 874 } 875 876 override void visit(const DoStmt v) { 877 mixin(mixinNodeLog!()); 878 pushStack(ast.make!(analyze.Loop), v); 879 v.accept(this); 880 } 881 882 override void visit(const SwitchStmt v) { 883 mixin(mixinNodeLog!()); 884 auto n = ast.make!(analyze.BranchBundle); 885 pushStack(n, v); 886 v.accept(this); 887 888 auto caseVisitor = new FindVisitor!CaseStmt; 889 v.accept(caseVisitor); 890 891 if (caseVisitor.node is null) { 892 logger.warning( 893 "switch without any case statements may result in a high number of mutant scheman that do not compile"); 894 } else { 895 incr; 896 scope (exit) 897 decr; 898 auto block = ast.make!(analyze.Block); 899 auto l = ast.location(n); 900 l.interval.end = l.interval.begin; 901 pushStack(block, l, CXCursorKind.unexposedDecl); 902 rewriteSwitch(ast, n, block, caseVisitor.node.cursor.toLocation); 903 } 904 } 905 906 override void visit(const ConditionalOperator v) { 907 mixin(mixinNodeLog!()); 908 // need to push a node because a ternery can contain function calls. 909 // Without a node for the op it seems like it is in the body, which it 910 // isn't, and then can be removed. 911 pushStack(ast.make!(analyze.Poision), v); 912 v.accept(this); 913 } 914 915 override void visit(const IfStmt v) @trusted { 916 mixin(mixinNodeLog!()); 917 pushStack(ast.make!(analyze.BranchBundle), v); 918 dextool.plugin.mutate.backend.analyze.extensions.accept(v, this); 919 } 920 921 override void visit(const IfStmtCond v) { 922 mixin(mixinNodeLog!()); 923 924 auto n = ast.make!(analyze.Condition); 925 pushStack(n, v); 926 927 if (!visitOp(v, v.cursor.kind)) { 928 v.accept(this); 929 } 930 931 if (!n.children.empty) { 932 auto tyId = ast.typeId(n.children[0]); 933 if (tyId.hasValue) { 934 ast.put(n, tyId.orElse(analyze.TypeId.init)); 935 } 936 } 937 938 rewriteCondition(ast, n); 939 } 940 941 override void visit(const IfStmtThen v) { 942 mixin(mixinNodeLog!()); 943 visitIfBranch(v); 944 } 945 946 override void visit(const IfStmtElse v) { 947 mixin(mixinNodeLog!()); 948 visitIfBranch(v); 949 } 950 951 private void visitIfBranch(T)(ref const T v) @trusted { 952 pushStack(ast.make!(analyze.Branch), v); 953 v.accept(this); 954 } 955 956 private bool visitOp(T)(ref const T v, const CXCursorKind cKind) @trusted { 957 auto op = operatorCursor(ast.get, v); 958 if (op.isNull) { 959 return false; 960 } 961 962 if (visitBinaryOp(op.get, cKind)) 963 return true; 964 return visitUnaryOp(op.get, cKind); 965 } 966 967 /// Returns: true if it added a binary operator, false otherwise. 968 private bool visitBinaryOp(ref OperatorCursor op, const CXCursorKind cKind) @trusted { 969 import libclang_ast.ast : dispatch; 970 971 auto astOp = cast(analyze.BinaryOp) op.astOp; 972 if (astOp is null) 973 return false; 974 975 const blockSchema = op.isOverload || blacklist.blockSchema(op.opLoc) || isParent(CXCursorKind.classTemplate, 976 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 977 978 astOp.schemaBlacklist = blockSchema; 979 astOp.operator = op.operator; 980 astOp.operator.blacklist = blacklist.inside(op.opLoc); 981 astOp.operator.schemaBlacklist = blockSchema; 982 983 op.put(nstack.back, ast); 984 pushStack(astOp, op.exprLoc, cKind); 985 incr; 986 scope (exit) 987 decr; 988 989 if (op.lhs.isValid) { 990 incr; 991 scope (exit) 992 decr; 993 dispatch(op.lhs, this); 994 auto b = () { 995 if (!lastDecr.empty) 996 return cast(analyze.Expr) lastDecr[$ - 1]; 997 return null; 998 }(); 999 if (b !is null && b != astOp) { 1000 astOp.lhs = b; 1001 auto ty = deriveCursorType(ast, op.lhs); 1002 ty.put(ast); 1003 if (ty.type !is null) { 1004 ast.put(b, ty.id); 1005 } 1006 if (ty.symbol !is null) { 1007 ast.put(b, ty.symId); 1008 } 1009 } 1010 } 1011 if (op.rhs.isValid) { 1012 incr; 1013 scope (exit) 1014 decr; 1015 dispatch(op.rhs, this); 1016 auto b = () { 1017 if (!lastDecr.empty) 1018 return cast(analyze.Expr) lastDecr[$ - 1]; 1019 return null; 1020 }(); 1021 if (b !is null && b != astOp) { 1022 astOp.rhs = b; 1023 auto ty = deriveCursorType(ast, op.rhs); 1024 ty.put(ast); 1025 if (ty.type !is null) { 1026 ast.put(b, ty.id); 1027 } 1028 if (ty.symbol !is null) { 1029 ast.put(b, ty.symId); 1030 } 1031 } 1032 } 1033 1034 // TODO: this is crude and shouldn't be here as a check but we must 1035 // block aor/rorp schematan when the type is a pointer. 1036 foreach (_; getChildrenTypes(ast, astOp).filter!(a => a.among(TypeKind.unordered, 1037 TypeKind.bottom))) { 1038 foreach (c; BreathFirstRange(astOp)) 1039 c.schemaBlacklist = true; 1040 break; 1041 } 1042 1043 return true; 1044 } 1045 1046 /// Returns: true if it added a binary operator, false otherwise. 1047 private bool visitUnaryOp(ref OperatorCursor op, CXCursorKind cKind) @trusted { 1048 import libclang_ast.ast : dispatch; 1049 1050 auto astOp = cast(analyze.UnaryOp) op.astOp; 1051 if (astOp is null) 1052 return false; 1053 1054 const blockSchema = op.isOverload || blacklist.blockSchema(op.opLoc) || isParent(CXCursorKind.classTemplate, 1055 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 1056 1057 astOp.operator = op.operator; 1058 astOp.operator.blacklist = blacklist.inside(op.opLoc); 1059 astOp.operator.schemaBlacklist = blockSchema; 1060 1061 op.put(nstack.back, ast); 1062 pushStack(astOp, op.exprLoc, cKind); 1063 incr; 1064 scope (exit) 1065 decr; 1066 1067 if (op.lhs.isValid) { 1068 incr; 1069 scope (exit) 1070 decr; 1071 dispatch(op.lhs, this); 1072 auto b = () { 1073 if (!lastDecr.empty) 1074 return cast(analyze.Expr) lastDecr[$ - 1]; 1075 return null; 1076 }(); 1077 if (b !is null && b != astOp) { 1078 astOp.expr = b; 1079 auto ty = deriveCursorType(ast, op.lhs); 1080 ty.put(ast); 1081 if (ty.type !is null) 1082 ast.put(b, ty.id); 1083 if (ty.symbol !is null) 1084 ast.put(b, ty.symId); 1085 } 1086 } 1087 if (op.rhs.isValid) { 1088 incr; 1089 scope (exit) 1090 decr; 1091 dispatch(op.rhs, this); 1092 auto b = () { 1093 if (!lastDecr.empty) 1094 return cast(analyze.Expr) lastDecr[$ - 1]; 1095 return null; 1096 }(); 1097 if (b !is null && b != astOp) { 1098 astOp.expr = b; 1099 auto ty = deriveCursorType(ast, op.rhs); 1100 ty.put(ast); 1101 if (ty.type !is null) 1102 ast.put(b, ty.id); 1103 if (ty.symbol !is null) 1104 ast.put(b, ty.symId); 1105 } 1106 } 1107 1108 return true; 1109 } 1110 1111 private void visitFunc(T)(ref const T v) @trusted { 1112 auto loc = v.cursor.toLocation; 1113 auto n = ast.make!(analyze.Function); 1114 n.schemaBlacklist = isConstExpr(v.cursor); 1115 nstack.back.children ~= n; 1116 pushStack(n, loc, v.cursor.kind); 1117 1118 auto fRetval = ast.make!(analyze.Return); 1119 auto rty = deriveType(ast.get, v.cursor.func.resultType); 1120 rty.put(ast); 1121 if (rty.type !is null) { 1122 ast.put(fRetval, loc); 1123 n.return_ = fRetval; 1124 ast.put(fRetval, rty.id); 1125 } 1126 if (rty.symbol !is null) 1127 ast.put(fRetval, rty.symId); 1128 1129 v.accept(this); 1130 } 1131 1132 private void visitCall(T)(ref const T v) @trusted { 1133 auto n = ast.make!(analyze.Call); 1134 pushStack(n, v); 1135 1136 auto ty = deriveType(ast.get, v.cursor.type); 1137 ty.put(ast); 1138 if (ty.type !is null) 1139 ast.put(n, ty.id); 1140 if (ty.symbol !is null) 1141 ast.put(n, ty.symId); 1142 1143 v.accept(this); 1144 } 1145 } 1146 1147 final class EnumVisitor : ExtendedVisitor { 1148 import libclang_ast.ast; 1149 1150 alias visit = ExtendedVisitor.visit; 1151 1152 mixin generateIndentIncrDecr; 1153 1154 analyze.Ast* ast; 1155 analyze.TypeId id; 1156 Nullable!long minValue; 1157 Nullable!long maxValue; 1158 1159 this(ref analyze.Ast ast, const uint indent) @trusted { 1160 this.ast = * 1161 this.indent = indent; 1162 } 1163 1164 override void visit(const EnumDecl v) @trusted { 1165 mixin(mixinNodeLog!()); 1166 id = make!(analyze.TypeId)(v.cursor); 1167 v.accept(this); 1168 } 1169 1170 override void visit(const EnumConstantDecl v) @trusted { 1171 mixin(mixinNodeLog!()); 1172 1173 long value = v.cursor.enum_.signedValue; 1174 1175 if (minValue.isNull) { 1176 minValue = value; 1177 maxValue = value; 1178 } 1179 1180 if (value < minValue.get) 1181 minValue = value; 1182 if (value > maxValue.get) 1183 maxValue = value; 1184 1185 v.accept(this); 1186 } 1187 1188 analyze.Type toType() { 1189 auto l = () { 1190 if (minValue.isNull) 1191 return analyze.Value(analyze.Value.NegInf.init); 1192 return analyze.Value(analyze.Value.Int(minValue.get)); 1193 }(); 1194 auto u = () { 1195 if (maxValue.isNull) 1196 return analyze.Value(analyze.Value.PosInf.init); 1197 return analyze.Value(analyze.Value.Int(maxValue.get)); 1198 }(); 1199 1200 return ast.make!(analyze.DiscreteType)(analyze.Range(l, u)); 1201 } 1202 } 1203 1204 /// Find first occurences of the node type `T`. 1205 final class FindVisitor(T) : Visitor { 1206 import libclang_ast.ast; 1207 1208 alias visit = Visitor.visit; 1209 1210 const(T)[] nodes; 1211 1212 const(T) node() @safe pure nothrow const @nogc { 1213 if (nodes.empty) 1214 return null; 1215 return nodes[0]; 1216 } 1217 1218 //uint indent; 1219 //override void incr() @safe { 1220 // ++indent; 1221 //} 1222 // 1223 //override void decr() @safe { 1224 // --indent; 1225 //} 1226 1227 override void visit(const Statement v) { 1228 //mixin(mixinNodeLog!()); 1229 if (nodes.empty) 1230 v.accept(this); 1231 } 1232 1233 override void visit(const T v) @trusted { 1234 //mixin(mixinNodeLog!()); 1235 if (nodes.empty) 1236 nodes = [v]; 1237 } 1238 } 1239 1240 /** Rewrite the position of a condition to perfectly match the parenthesis. 1241 * 1242 * The source code: 1243 * ``` 1244 * if (int x = 42) y = 43; 1245 * ``` 1246 * 1247 * Results in something like this in the AST. 1248 * 1249 * `-Condition 1250 * `-Expr 1251 * `-Expr 1252 * `-VarDecl 1253 * `-OpGreater 1254 * `-Operator 1255 * `-Expr 1256 * `-Expr 1257 * 1258 * The problem is that the location of the Condition node will be OpGreater and 1259 * not the VarDecl. 1260 */ 1261 void rewriteCondition(ref analyze.Ast ast, analyze.Condition root) { 1262 import sumtype; 1263 import dextool.plugin.mutate.backend.analyze.ast : TypeId, VarDecl, Kind; 1264 1265 // set the type of the Condition to the first expression with a type. 1266 foreach (ty; BreathFirstRange(root).map!(a => ast.typeId(a)) 1267 .filter!(a => a.hasValue)) { 1268 sumtype.match!((Some!TypeId a) => ast.put(root, a), (None a) {})(ty); 1269 break; 1270 } 1271 1272 foreach (a; BreathFirstRange(root).filter!(a => a.kind == Kind.VarDecl)) { 1273 ast.put(root, ast.location(a)); 1274 // can't delete the VarDecl explicitly because the code will not compile 1275 a.schemaBlacklist = true; 1276 break; 1277 } 1278 } 1279 1280 void rewriteSwitch(ref analyze.Ast ast, analyze.BranchBundle root, 1281 analyze.Block block, Location firstCase) { 1282 import std.range : enumerate; 1283 import std.typecons : tuple; 1284 1285 Node[] beforeCase = root.children; 1286 Node[] rest; 1287 1288 foreach (n; root.children 1289 .map!(a => tuple!("node", "loc")(a, ast.location(a))) 1290 .enumerate 1291 .filter!(a => a.value.loc.interval.begin > firstCase.interval.begin)) { 1292 beforeCase = root.children[0 .. n.index]; 1293 rest = root.children[n.index .. $]; 1294 break; 1295 } 1296 1297 root.children = beforeCase ~ block; 1298 block.children = rest; 1299 } 1300 1301 enum discreteCategory = AliasSeq!(CXTypeKind.charU, CXTypeKind.uChar, CXTypeKind.char16, 1302 CXTypeKind.char32, CXTypeKind.uShort, CXTypeKind.uInt, CXTypeKind.uLong, CXTypeKind.uLongLong, 1303 CXTypeKind.uInt128, CXTypeKind.charS, CXTypeKind.sChar, CXTypeKind.wChar, CXTypeKind.short_, 1304 CXTypeKind.int_, CXTypeKind.long_, CXTypeKind.longLong, 1305 CXTypeKind.int128, CXTypeKind.enum_,); 1306 enum floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_, 1307 CXTypeKind.longDouble, CXTypeKind.float128, CXTypeKind.half, CXTypeKind.float16,); 1308 enum pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer, 1309 CXTypeKind.blockPointer, CXTypeKind.memberPointer, CXTypeKind.record); 1310 enum boolCategory = AliasSeq!(CXTypeKind.bool_); 1311 1312 enum voidCategory = AliasSeq!(CXTypeKind.void_); 1313 1314 struct DeriveTypeResult { 1315 analyze.TypeId id; 1316 analyze.Type type; 1317 analyze.SymbolId symId; 1318 analyze.Symbol symbol; 1319 1320 void put(ref analyze.Ast ast) @safe { 1321 if (type !is null) { 1322 ast.types.require(id, type); 1323 } 1324 if (symbol !is null) { 1325 ast.symbols.require(symId, symbol); 1326 } 1327 } 1328 } 1329 1330 DeriveTypeResult deriveType(ref Ast ast, Type cty) { 1331 DeriveTypeResult rval; 1332 1333 if (!cty.isValid) 1334 return rval; 1335 1336 auto ctydecl = cty.declaration; 1337 if (ctydecl.isValid) { 1338 rval.id = make!(analyze.TypeId)(ctydecl); 1339 } else { 1340 rval.id = make!(analyze.TypeId)(cty.cursor); 1341 } 1342 1343 if (cty.isEnum) { 1344 rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf); 1345 if (!cty.isSigned) { 1346 rval.type.range.low = analyze.Value(analyze.Value.Int(0)); 1347 } 1348 } else if (cty.kind.among(floatCategory)) { 1349 rval.type = ast.make!(analyze.ContinuesType)(analyze.Range.makeInf); 1350 } else if (cty.kind.among(pointerCategory)) { 1351 rval.type = ast.make!(analyze.UnorderedType)(analyze.Range.makeInf); 1352 } else if (cty.kind.among(boolCategory)) { 1353 rval.type = ast.make!(analyze.BooleanType)(analyze.Range.makeBoolean); 1354 } else if (cty.kind.among(discreteCategory)) { 1355 rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf); 1356 if (!cty.isSigned) { 1357 rval.type.range.low = analyze.Value(analyze.Value.Int(0)); 1358 } 1359 } else if (cty.kind.among(voidCategory)) { 1360 rval.type = ast.make!(analyze.VoidType)(); 1361 } else { 1362 // unknown such as an elaborated 1363 rval.type = ast.make!(analyze.Type)(); 1364 } 1365 1366 return rval; 1367 } 1368 1369 struct DeriveCursorTypeResult { 1370 Cursor expr; 1371 DeriveTypeResult typeResult; 1372 alias typeResult this; 1373 } 1374 1375 /** Analyze a cursor to derive the type of it and if it has a concrete value 1376 * and what it is in that case. 1377 * 1378 * This is intended for expression nodes in the clang AST. 1379 */ 1380 DeriveCursorTypeResult deriveCursorType(ref Ast ast, const Cursor baseCursor) { 1381 auto c = Cursor(getUnderlyingExprNode(baseCursor)); 1382 if (!c.isValid) 1383 return DeriveCursorTypeResult.init; 1384 1385 auto rval = DeriveCursorTypeResult(c); 1386 auto cty = c.type.canonicalType; 1387 rval.typeResult = deriveType(ast, cty); 1388 1389 // evaluate the cursor to add a value for the symbol 1390 void eval(const ref Eval e) { 1391 if (!e.kind.among(CXEvalResultKind.int_)) 1392 return; 1393 1394 const long value = () { 1395 if (e.isUnsignedInt) { 1396 const v = e.asUnsigned; 1397 if (v < long.max) 1398 return cast(long) v; 1399 } 1400 return e.asLong; 1401 }(); 1402 1403 rval.symId = make!(analyze.SymbolId)(c); 1404 rval.symbol = ast.make!(analyze.DiscretSymbol)(analyze.Value(analyze.Value.Int(value))); 1405 } 1406 1407 if (cty.isEnum) { 1408 // TODO: check if c.eval give the same result. If so it may be easier 1409 // to remove this special case of an enum because it is covered by the 1410 // generic branch for discretes. 1411 1412 auto ctydecl = cty.declaration; 1413 if (!ctydecl.isValid) 1414 return rval; 1415 1416 const cref = c.referenced; 1417 if (!cref.isValid) 1418 return rval; 1419 1420 if (cref.kind == CXCursorKind.enumConstantDecl) { 1421 const long value = cref.enum_.signedValue; 1422 rval.symId = make!(analyze.SymbolId)(c); 1423 rval.symbol = ast.make!(analyze.DiscretSymbol)( 1424 analyze.Value(analyze.Value.Int(value))); 1425 } 1426 } else if (cty.kind.among(discreteCategory)) { 1427 // crashes in clang 7.x. Investigate why. 1428 //const e = c.eval; 1429 //if (e.isValid) 1430 // eval(e); 1431 } 1432 1433 return rval; 1434 } 1435 1436 auto make(T)(const Cursor c) if (is(T == analyze.TypeId) || is(T == analyze.SymbolId)) { 1437 const usr = c.usr; 1438 if (usr.empty) { 1439 return T(c.toHash); 1440 } 1441 return analyze.makeId!T(usr); 1442 } 1443 1444 /// trusted: trusting the impl in clang. 1445 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted { 1446 foreach (ref t; toks) { 1447 if (t.location.offset >= sr.end) { 1448 if (t.kind == kind) { 1449 return t.extent.end.offset; 1450 } 1451 break; 1452 } 1453 } 1454 1455 return sr.end; 1456 } 1457 1458 /** Check if a function has the constexpr keyword. 1459 * 1460 * The implementation opt for higher precision than efficiency which is why it 1461 * looks at the tokens. That should eliminate such factors as "whitespace". 1462 */ 1463 bool isConstExpr(const Cursor c) @trusted { 1464 bool helper(T)(ref T toks) { 1465 foreach (ref t; toks.filter!(a => a.kind.among(CXTokenKind.keyword, 1466 CXTokenKind.identifier))) { 1467 if (t.spelling == "constexpr") { 1468 return true; 1469 } 1470 } 1471 return false; 1472 } 1473 1474 auto toks = c.tokens; 1475 return helper(toks); 1476 } 1477 1478 /** Create an index of all macros that then can be queried to see if a Cursor 1479 * or Interval overlap a macro. 1480 */ 1481 struct BlackList { 1482 import dextool.plugin.mutate.backend.analyze.utility : Index; 1483 1484 Index!string macros; 1485 /// schemas are blacklisted for these 1486 Index!string schemas; 1487 1488 this(const Cursor root) { 1489 Interval[][string] macros; 1490 Interval[][string] schemas; 1491 1492 foreach (c, parent; root.all) { 1493 if (!c.kind.among(CXCursorKind.macroExpansion, 1494 CXCursorKind.macroDefinition) || c.isMacroBuiltin) 1495 continue; 1496 1497 auto spelling = c.spelling; 1498 // C code almost always implement these as macros. They should not 1499 // be blocked from being mutated. 1500 if (spelling.among("bool", "TRUE", "FALSE")) { 1501 add(c, schemas); 1502 } else { 1503 add(c, macros); 1504 } 1505 } 1506 1507 foreach (k; macros.byKey) { 1508 macros[k] = macros[k].sort.array; 1509 } 1510 foreach (k; schemas.byKey) { 1511 schemas[k] = schemas[k].sort.array; 1512 } 1513 1514 this.macros = Index!string(macros); 1515 this.schemas = Index!string(schemas); 1516 } 1517 1518 static void add(const Cursor c, ref Interval[][string] idx) { 1519 const file = c.location.path; 1520 if (file.empty) 1521 return; 1522 const e = c.extent; 1523 const interval = Interval(e.start.offset, e.end.offset); 1524 if (auto v = file in idx) { 1525 (*v) ~= interval; 1526 } else { 1527 idx[file] = [interval]; 1528 } 1529 } 1530 1531 bool blockSchema(analyze.Location l) { 1532 return schemas.intersect(l.file, l.interval); 1533 } 1534 1535 bool inside(const Cursor c) { 1536 const file = c.location.path.Path; 1537 const e = c.extent; 1538 const interval = Interval(e.start.offset, e.end.offset); 1539 return inside(file, interval); 1540 } 1541 1542 bool inside(analyze.Location l) { 1543 return inside(l.file, l.interval); 1544 } 1545 1546 /** 1547 * assuming that an invalid mutant is always inside a macro thus only 1548 * checking if the `i` is inside. Removing a "block" of code that happens 1549 * to contain a macro is totally "ok". It doesn't create any problem. 1550 * 1551 * Returns: true if `i` is inside a macro interval. 1552 */ 1553 bool inside(const Path file, const Interval i) { 1554 return macros.overlap(file, i); 1555 } 1556 } 1557 1558 /// Returns: the types of the children 1559 auto getChildrenTypes(ref Ast ast, Node parent) { 1560 return BreathFirstRange(parent).map!(a => ast.type(a)) 1561 .filter!(a => a !is null) 1562 .map!(a => a.kind); 1563 }