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