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; 15 import std.exception : collectException; 16 import std.format : formattedWrite; 17 import std.meta : AliasSeq; 18 import std.typecons : Nullable, scoped; 19 20 import automem : vector, Vector; 21 22 import clang.Cursor : Cursor; 23 import clang.Eval : Eval; 24 import clang.Type : Type; 25 import clang.c.Index : CXTypeKind, CXCursorKind, CXEvalResultKind, CXTokenKind; 26 27 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 28 29 import dextool.clang_extensions : getUnderlyingExprNode; 30 31 import dextool.type : Path, AbsolutePath; 32 33 import dextool.plugin.mutate.backend.analyze.ast : Interval, Location; 34 import dextool.plugin.mutate.backend.analyze.extensions; 35 import dextool.plugin.mutate.backend.analyze.utility; 36 import dextool.plugin.mutate.backend.interface_ : FilesysIO; 37 import dextool.plugin.mutate.backend.type : Language, SourceLoc, Offset, SourceLocRange; 38 39 import analyze = dextool.plugin.mutate.backend.analyze.ast; 40 41 alias accept = dextool.plugin.mutate.backend.analyze.extensions.accept; 42 43 /** Translate a clang AST to a mutation AST. 44 */ 45 analyze.Ast toMutateAst(const Cursor root, FilesysIO fio) @trusted { 46 import cpptooling.analyzer.clang.ast : ClangAST; 47 48 auto svisitor = scoped!BaseVisitor(fio); 49 auto visitor = cast(BaseVisitor) svisitor; 50 auto ast = ClangAST!BaseVisitor(root); 51 ast.accept(visitor); 52 return visitor.ast; 53 } 54 55 private: 56 57 struct OperatorCursor { 58 analyze.Expr astOp; 59 60 // the whole expression 61 analyze.Location exprLoc; 62 DeriveCursorTypeResult exprTy; 63 64 // the operator itself 65 analyze.Operator operator; 66 analyze.Location opLoc; 67 68 Cursor lhs; 69 Cursor rhs; 70 71 /// Add the result to the AST and astOp to the parent. 72 /// astOp is set to have two children, lhs and rhs. 73 void put(analyze.Node parent, ref analyze.Ast ast) @safe { 74 ast.put(astOp, exprLoc); 75 ast.put(operator, opLoc); 76 77 exprTy.put(ast); 78 79 if (exprTy.type !is null) 80 ast.put(astOp, exprTy.id); 81 82 if (exprTy.symbol !is null) 83 ast.put(astOp, exprTy.symId); 84 85 parent.children ~= astOp; 86 } 87 } 88 89 Nullable!OperatorCursor operatorCursor(T)(T node) { 90 import dextool.clang_extensions : getExprOperator, OpKind, ValueKind, getUnderlyingExprNode; 91 92 auto op = getExprOperator(node.cursor); 93 if (!op.isValid) 94 return typeof(return)(); 95 96 auto path = op.cursor.location.path.Path; 97 if (path.empty) 98 return typeof(return)(); 99 100 OperatorCursor res; 101 102 void sidesPoint() { 103 auto sides = op.sides; 104 if (sides.lhs.isValid) { 105 res.lhs = getUnderlyingExprNode(sides.lhs); 106 } 107 if (sides.rhs.isValid) { 108 res.rhs = getUnderlyingExprNode(sides.rhs); 109 } 110 } 111 112 // the operator itself 113 void opPoint() { 114 auto loc = op.location; 115 auto sr = loc.spelling; 116 res.operator = new analyze.Operator; 117 res.opLoc = new analyze.Location(path, Interval(sr.offset, 118 cast(uint)(sr.offset + op.length)), SourceLocRange(SourceLoc(loc.line, 119 loc.column), SourceLoc(loc.line, cast(uint)(loc.column + op.length)))); 120 } 121 122 // the arguments and the operator 123 void exprPoint() { 124 auto sr = op.cursor.extent; 125 res.exprLoc = new analyze.Location(path, Interval(sr.start.offset, 126 sr.end.offset), SourceLocRange(SourceLoc(sr.start.line, 127 sr.start.column), SourceLoc(sr.end.line, sr.end.column))); 128 res.exprTy = deriveCursorType(op.cursor); 129 switch (op.kind) with (OpKind) { 130 case OO_Star: // "*" 131 goto case; 132 case Mul: // "*" 133 res.astOp = new analyze.OpMul; 134 break; 135 case OO_Slash: // "/" 136 goto case; 137 case Div: // "/" 138 res.astOp = new analyze.OpDiv; 139 break; 140 case OO_Percent: // "%" 141 goto case; 142 case Rem: // "%" 143 res.astOp = new analyze.OpMod; 144 break; 145 case OO_Plus: // "+" 146 goto case; 147 case Add: // "+" 148 res.astOp = new analyze.OpAdd; 149 break; 150 case OO_Minus: // "-" 151 goto case; 152 case Sub: // "-" 153 res.astOp = new analyze.OpSub; 154 break; 155 case OO_Less: // "<" 156 goto case; 157 case LT: // "<" 158 res.astOp = new analyze.OpLess; 159 break; 160 case OO_Greater: // ">" 161 goto case; 162 case GT: // ">" 163 res.astOp = new analyze.OpGreater; 164 break; 165 case OO_LessEqual: // "<=" 166 goto case; 167 case LE: // "<=" 168 res.astOp = new analyze.OpLessEq; 169 break; 170 case OO_GreaterEqual: // ">=" 171 goto case; 172 case GE: // ">=" 173 res.astOp = new analyze.OpGreaterEq; 174 break; 175 case OO_EqualEqual: // "==" 176 goto case; 177 case EQ: // "==" 178 res.astOp = new analyze.OpEqual; 179 break; 180 case OO_Exclaim: // "!" 181 goto case; 182 case LNot: // "!" 183 res.astOp = new analyze.OpNegate; 184 break; 185 case OO_ExclaimEqual: // "!=" 186 goto case; 187 case NE: // "!=" 188 res.astOp = new analyze.OpNotEqual; 189 break; 190 case OO_AmpAmp: // "&&" 191 goto case; 192 case LAnd: // "&&" 193 res.astOp = new analyze.OpAnd; 194 break; 195 case OO_PipePipe: // "||" 196 goto case; 197 case LOr: // "||" 198 res.astOp = new analyze.OpOr; 199 break; 200 case OO_Amp: // "&" 201 goto case; 202 case And: // "&" 203 res.astOp = new analyze.OpAndBitwise; 204 break; 205 case OO_Pipe: // "|" 206 goto case; 207 case Or: // "|" 208 res.astOp = new analyze.OpOrBitwise; 209 break; 210 case OO_StarEqual: // "*=" 211 goto case; 212 case MulAssign: // "*=" 213 res.astOp = new analyze.OpAssignMul; 214 break; 215 case OO_SlashEqual: // "/=" 216 goto case; 217 case DivAssign: // "/=" 218 res.astOp = new analyze.OpAssignDiv; 219 break; 220 case OO_PercentEqual: // "%=" 221 goto case; 222 case RemAssign: // "%=" 223 res.astOp = new analyze.OpAssignMod; 224 break; 225 case OO_PlusEqual: // "+=" 226 goto case; 227 case AddAssign: // "+=" 228 res.astOp = new analyze.OpAssignAdd; 229 break; 230 case OO_MinusEqual: // "-=" 231 goto case; 232 case SubAssign: // "-=" 233 res.astOp = new analyze.OpAssignSub; 234 break; 235 case OO_AmpEqual: // "&=" 236 goto case; 237 case AndAssign: // "&=" 238 res.astOp = new analyze.OpAssignAndBitwise; 239 break; 240 case OrAssign: // "|=" 241 goto case; 242 case OO_PipeEqual: // "|=" 243 res.astOp = new analyze.OpAssignOrBitwise; 244 break; 245 case ShlAssign: // "<<=" 246 goto case; 247 case ShrAssign: // ">>=" 248 goto case; 249 case XorAssign: // "^=" 250 goto case; 251 case OO_CaretEqual: // "^=" 252 goto case; 253 case OO_Equal: // "=" 254 goto case; 255 case Assign: // "=" 256 res.astOp = new analyze.OpAssign; 257 break; 258 //case Xor: // "^" 259 //case OO_Caret: // "^" 260 //case OO_Tilde: // "~" 261 default: 262 res.astOp = new analyze.BinaryOp; 263 } 264 } 265 266 exprPoint; 267 opPoint; 268 sidesPoint; 269 return typeof(return)(res); 270 } 271 272 struct CaseStmtCursor { 273 analyze.Location branch; 274 analyze.Location insideBranch; 275 276 Cursor inner; 277 } 278 279 Nullable!CaseStmtCursor caseStmtCursor(T)(T node) { 280 import dextool.clang_extensions : getCaseStmt; 281 282 auto mp = getCaseStmt(node.cursor); 283 if (!mp.isValid) 284 return typeof(return)(); 285 286 auto path = node.cursor.location.path.Path; 287 if (path.empty) 288 return typeof(return)(); 289 290 auto extent = node.cursor.extent; 291 292 CaseStmtCursor res; 293 res.inner = mp.subStmt; 294 295 auto sr = res.inner.extent; 296 auto insideLoc = SourceLocRange(SourceLoc(sr.start.line, sr.start.column), 297 SourceLoc(sr.end.line, sr.end.column)); 298 auto offs = Interval(sr.start.offset, sr.end.offset); 299 if (res.inner.kind == CXCursorKind.caseStmt) { 300 auto colon = mp.colonLocation; 301 insideLoc.begin = SourceLoc(colon.line, colon.column + 1); 302 insideLoc.end = insideLoc.begin; 303 304 // a case statement with fallthrough. the only point to inject a bomb 305 // is directly after the semicolon 306 offs.begin = colon.offset + 1; 307 offs.end = offs.begin; 308 } else if (res.inner.kind != CXCursorKind.compoundStmt) { 309 offs.end = findTokenOffset(node.cursor.translationUnit.cursor.tokens, 310 offs, CXTokenKind.punctuation); 311 } 312 313 void subStmt() { 314 res.insideBranch = new analyze.Location(path, offs, insideLoc); 315 } 316 317 void stmt() { 318 auto loc = extent.start; 319 auto loc_end = extent.end; 320 // reuse the end from offs because it covers either only the 321 // fallthrough OR also the end semicolon 322 auto stmt_offs = Interval(extent.start.offset, offs.end); 323 res.branch = new analyze.Location(path, stmt_offs, 324 SourceLocRange(SourceLoc(loc.line, loc.column), 325 SourceLoc(loc_end.line, loc_end.column))); 326 } 327 328 stmt; 329 subStmt; 330 return typeof(return)(res); 331 } 332 333 @safe: 334 335 Location toLocation(ref const Cursor c) { 336 auto e = c.extent; 337 auto interval = Interval(e.start.offset, e.end.offset); 338 auto begin = e.start; 339 auto end = e.end; 340 return new Location(e.path.Path, interval, SourceLocRange(SourceLoc(begin.line, 341 begin.column), SourceLoc(end.line, end.column))); 342 } 343 344 /** Find all mutation points that affect a whole expression. 345 * 346 * TODO change the name of the class. It is more than just an expression 347 * visitor. 348 * 349 * # Usage of kind_stack 350 * All usage of the kind_stack shall be documented here. 351 * - track assignments to avoid generating unary insert operators for the LHS. 352 */ 353 final class BaseVisitor : ExtendedVisitor { 354 import clang.c.Index : CXCursorKind, CXTypeKind; 355 import cpptooling.analyzer.clang.ast; 356 import dextool.clang_extensions : getExprOperator, OpKind; 357 358 alias visit = ExtendedVisitor.visit; 359 360 // the depth the visitor is at. 361 uint indent; 362 // A stack of visited cursors up to the current one. 363 Stack!(analyze.Node) nstack; 364 365 /// The elements that where removed from the last decrement. 366 Vector!(analyze.Node) lastDecr; 367 368 /// List of macro locations which blacklist mutants that would be injected 369 /// in any of them. 370 BlackList blacklist; 371 372 analyze.Ast ast; 373 374 FilesysIO fio; 375 376 this(FilesysIO fio) nothrow { 377 this.fio = fio; 378 } 379 380 override void incr() @safe { 381 ++indent; 382 lastDecr.clear; 383 } 384 385 override void decr() @trusted { 386 --indent; 387 lastDecr = nstack.popUntil(indent); 388 } 389 390 private void pushStack(analyze.Node n, analyze.Location l) @trusted { 391 n.blacklist = blacklist.inside(l); 392 nstack.put(n, indent); 393 } 394 395 /// Returns: true if it is OK to modify the cursor 396 private void pushStack(AstT, ClangT)(AstT n, ClangT v) @trusted { 397 auto loc = v.cursor.toLocation; 398 ast.put(n, loc); 399 nstack.back.children ~= n; 400 pushStack(n, loc); 401 } 402 403 /// Returns: the depth (1+) if any of the parent nodes is `k`. 404 private uint isInside(analyze.Kind k) { 405 return nstack.isInside(k); 406 } 407 408 override void visit(const(TranslationUnit) v) { 409 import clang.c.Index : CXLanguageKind; 410 411 mixin(mixinNodeLog!()); 412 413 blacklist = BlackList(v.cursor); 414 415 ast.root = new analyze.TranslationUnit; 416 auto loc = v.cursor.toLocation; 417 ast.put(ast.root, loc); 418 pushStack(ast.root, loc); 419 420 // it is most often invalid 421 switch (v.cursor.language) { 422 case CXLanguageKind.c: 423 ast.lang = Language.c; 424 break; 425 case CXLanguageKind.cPlusPlus: 426 ast.lang = Language.cpp; 427 break; 428 default: 429 ast.lang = Language.assumeCpp; 430 } 431 432 v.accept(this); 433 } 434 435 override void visit(const(Attribute) v) { 436 mixin(mixinNodeLog!()); 437 v.accept(this); 438 } 439 440 override void visit(const(Declaration) v) { 441 mixin(mixinNodeLog!()); 442 v.accept(this); 443 } 444 445 override void visit(const VarDecl v) @trusted { 446 mixin(mixinNodeLog!()); 447 visitVar(v); 448 v.accept(this); 449 } 450 451 override void visit(const ParmDecl v) @trusted { 452 mixin(mixinNodeLog!()); 453 visitVar(v); 454 v.accept(this); 455 } 456 457 override void visit(const TemplateTypeParameter v) { 458 mixin(mixinNodeLog!()); 459 // block mutants inside template parameters 460 } 461 462 override void visit(const TemplateTemplateParameter v) { 463 mixin(mixinNodeLog!()); 464 // block mutants inside template parameters 465 } 466 467 override void visit(const NonTypeTemplateParameter v) { 468 mixin(mixinNodeLog!()); 469 // block mutants inside template parameters 470 } 471 472 override void visit(const TypeAliasDecl v) { 473 mixin(mixinNodeLog!()); 474 // block mutants inside template parameters 475 } 476 477 override void visit(const CxxBaseSpecifier v) { 478 mixin(mixinNodeLog!()); 479 // block mutants inside template parameters. 480 // the only mutants that are inside an inheritance is template 481 // parameters and such. 482 } 483 484 private void visitVar(T)(T v) @trusted { 485 auto n = new analyze.VarDecl; 486 487 auto ty = v.cursor.type; 488 if (ty.isValid) { 489 n.isConst = ty.isConst; 490 } 491 492 pushStack(n, v); 493 } 494 495 override void visit(const(Directive) v) { 496 mixin(mixinNodeLog!()); 497 v.accept(this); 498 } 499 500 override void visit(const(Reference) v) { 501 mixin(mixinNodeLog!()); 502 v.accept(this); 503 } 504 505 override void visit(const(Statement) v) { 506 mixin(mixinNodeLog!()); 507 v.accept(this); 508 } 509 510 override void visit(const(Expression) v) { 511 import cpptooling.analyzer.clang.ast : dispatch; 512 import dextool.clang_extensions : getUnderlyingExprNode; 513 514 mixin(mixinNodeLog!()); 515 516 auto ue = Cursor(getUnderlyingExprNode(v.cursor)); 517 if (ue.isValid && ue != v.cursor) { 518 incr; 519 scope (exit) 520 decr; 521 dispatch(ue, this); 522 } else { 523 pushStack(new analyze.Expr, v); 524 v.accept(this); 525 } 526 } 527 528 override void visit(const Preprocessor v) { 529 mixin(mixinNodeLog!()); 530 531 const bool isCpp = v.spelling == "__cplusplus"; 532 533 if (isCpp) 534 ast.lang = Language.cpp; 535 else if (!isCpp && ast.lang != Language.cpp) 536 ast.lang = Language.c; 537 538 v.accept(this); 539 } 540 541 override void visit(const(EnumDecl) v) @trusted { 542 mixin(mixinNodeLog!()); 543 544 import std.typecons : scoped; 545 546 // extract the boundaries of the enum to update the type db. 547 auto vis = scoped!EnumVisitor(indent); 548 vis.visit(v); 549 ast.types.set(vis.id, vis.toType); 550 } 551 552 override void visit(const(FunctionDecl) v) @trusted { 553 mixin(mixinNodeLog!()); 554 visitFunc(v); 555 } 556 557 override void visit(const(CxxMethod) v) { 558 mixin(mixinNodeLog!()); 559 560 // model C++ methods as functions. It should be enough to know that it 561 // is a function and the return type when generating mutants. 562 visitFunc(v); 563 } 564 565 override void visit(const(CallExpr) v) { 566 mixin(mixinNodeLog!()); 567 568 if (!visitOp(v)) { 569 pushStack(new analyze.Call, v); 570 v.accept(this); 571 } 572 } 573 574 override void visit(const(BreakStmt) v) { 575 mixin(mixinNodeLog!()); 576 v.accept(this); 577 } 578 579 override void visit(const BinaryOperator v) @trusted { 580 mixin(mixinNodeLog!()); 581 visitOp(v); 582 } 583 584 override void visit(const UnaryOperator v) @trusted { 585 mixin(mixinNodeLog!()); 586 visitOp(v); 587 } 588 589 override void visit(const CompoundAssignOperator v) { 590 mixin(mixinNodeLog!()); 591 // TODO: implement all aor assignment such as += 592 pushStack(new analyze.OpAssign, v); 593 v.accept(this); 594 } 595 596 override void visit(const CxxThrowExpr v) { 597 mixin(mixinNodeLog!()); 598 // model a C++ exception as a return expression because that is 599 // "basically" what happens. 600 pushStack(new analyze.Return, v); 601 v.accept(this); 602 } 603 604 override void visit(const(ReturnStmt) v) { 605 mixin(mixinNodeLog!()); 606 pushStack(new analyze.Return, v); 607 v.accept(this); 608 } 609 610 override void visit(const(CompoundStmt) v) { 611 mixin(mixinNodeLog!()); 612 613 try { 614 auto loc = v.cursor.toLocation; 615 auto fin = fio.makeInput(loc.file); 616 617 // a CompoundStmt that represent a "{..}" can for example be the 618 // body of a function or the block that a try statement encompase. 619 // The block that can be modified is the inside of it thus the 620 // location has to be the inside. If this modification to isn't 621 // done then a SDL can't be generated that delete the inside of 622 // e.g. void functions. 623 if (fin.content[loc.interval.begin .. loc.interval.begin + 1] == cast(const(ubyte)[]) "{") { 624 const begin = loc.interval.begin + 1; 625 const end = loc.interval.end - 1; 626 if (begin < end) { 627 loc.interval = Interval(begin, end); 628 } 629 630 auto n = new analyze.Block; 631 ast.put(n, loc); 632 nstack.back.children ~= n; 633 pushStack(n, loc); 634 } else { 635 pushStack(new analyze.Block, v); 636 } 637 } catch (Exception e) { 638 logger.trace(e.msg).collectException; 639 } 640 641 v.accept(this); 642 } 643 644 override void visit(const CaseStmt v) { 645 mixin(mixinNodeLog!()); 646 visitCaseStmt(v); 647 } 648 649 override void visit(const DefaultStmt v) { 650 mixin(mixinNodeLog!()); 651 visitCaseStmt(v); 652 } 653 654 override void visit(const IfStmt v) @trusted { 655 mixin(mixinNodeLog!()); 656 pushStack(new analyze.Block, v); 657 dextool.plugin.mutate.backend.analyze.extensions.accept(v, this); 658 } 659 660 override void visit(const IfStmtCond v) { 661 mixin(mixinNodeLog!()); 662 pushStack(new analyze.Condition, v); 663 664 incr; 665 scope (exit) 666 decr; 667 if (!visitOp(v)) { 668 v.accept(this); 669 } 670 } 671 672 override void visit(const IfStmtThen v) { 673 mixin(mixinNodeLog!()); 674 pushStack(new analyze.Branch, v); 675 v.accept(this); 676 } 677 678 override void visit(const IfStmtElse v) { 679 mixin(mixinNodeLog!()); 680 pushStack(new analyze.Branch, v); 681 v.accept(this); 682 } 683 684 private bool visitOp(T)(ref const T v) @trusted { 685 auto op = operatorCursor(v); 686 if (op.isNull) { 687 return false; 688 } 689 690 if (visitBinaryOp(op.get)) 691 return true; 692 return visitUnaryOp(op.get); 693 } 694 695 /// Returns: true if it added a binary operator, false otherwise. 696 private bool visitBinaryOp(ref OperatorCursor op) @trusted { 697 import cpptooling.analyzer.clang.ast : dispatch; 698 699 auto astOp = cast(analyze.BinaryOp) op.astOp; 700 if (astOp is null) 701 return false; 702 703 astOp.operator = op.operator; 704 astOp.operator.blacklist = blacklist.inside(op.opLoc); 705 706 op.put(nstack.back, ast); 707 pushStack(astOp, op.exprLoc); 708 incr; 709 scope (exit) 710 decr; 711 712 if (op.lhs.isValid) { 713 incr; 714 scope (exit) 715 decr; 716 dispatch(op.lhs, this); 717 auto b = () { 718 if (!lastDecr.empty) 719 return cast(analyze.Expr) lastDecr[$ - 1]; 720 return null; 721 }(); 722 if (b !is null && b != astOp) { 723 astOp.lhs = b; 724 auto ty = deriveCursorType(op.lhs); 725 ty.put(ast); 726 if (ty.type !is null) { 727 ast.put(b, ty.id); 728 } 729 if (ty.symbol !is null) { 730 ast.put(b, ty.symId); 731 } 732 } 733 } 734 if (op.rhs.isValid) { 735 incr; 736 scope (exit) 737 decr; 738 dispatch(op.rhs, this); 739 auto b = () { 740 if (!lastDecr.empty) 741 return cast(analyze.Expr) lastDecr[$ - 1]; 742 return null; 743 }(); 744 if (b !is null && b != astOp) { 745 astOp.rhs = b; 746 auto ty = deriveCursorType(op.rhs); 747 ty.put(ast); 748 if (ty.type !is null) { 749 ast.put(b, ty.id); 750 } 751 if (ty.symbol !is null) { 752 ast.put(b, ty.symId); 753 } 754 } 755 } 756 757 return true; 758 } 759 760 /// Returns: true if it added a binary operator, false otherwise. 761 private bool visitUnaryOp(ref OperatorCursor op) @trusted { 762 import cpptooling.analyzer.clang.ast : dispatch; 763 764 auto astOp = cast(analyze.UnaryOp) op.astOp; 765 if (astOp is null) 766 return false; 767 768 astOp.operator = op.operator; 769 astOp.operator.blacklist = blacklist.inside(op.opLoc); 770 771 op.put(nstack.back, ast); 772 pushStack(astOp, op.exprLoc); 773 incr; 774 scope (exit) 775 decr; 776 777 if (op.lhs.isValid) { 778 incr; 779 scope (exit) 780 decr; 781 dispatch(op.lhs, this); 782 auto b = () { 783 if (!lastDecr.empty) 784 return cast(analyze.Expr) lastDecr[$ - 1]; 785 return null; 786 }(); 787 if (b !is null && b != astOp) { 788 astOp.expr = b; 789 auto ty = deriveCursorType(op.lhs); 790 ty.put(ast); 791 if (ty.type !is null) { 792 ast.put(b, ty.id); 793 } 794 if (ty.symbol !is null) { 795 ast.put(b, ty.symId); 796 } 797 } 798 } else if (op.rhs.isValid) { 799 incr; 800 scope (exit) 801 decr; 802 dispatch(op.rhs, this); 803 auto b = () { 804 if (!lastDecr.empty) 805 return cast(analyze.Expr) lastDecr[$ - 1]; 806 return null; 807 }(); 808 if (b !is null && b != astOp) { 809 astOp.expr = b; 810 auto ty = deriveCursorType(op.rhs); 811 ty.put(ast); 812 if (ty.type !is null) { 813 ast.put(b, ty.id); 814 } 815 if (ty.symbol !is null) { 816 ast.put(b, ty.symId); 817 } 818 } 819 } 820 821 return true; 822 } 823 824 private void visitFunc(T)(ref const T v) @trusted { 825 auto loc = v.cursor.toLocation; 826 auto n = new analyze.Function; 827 ast.put(n, loc); 828 nstack.back.children ~= n; 829 pushStack(n, loc); 830 831 auto fRetval = new analyze.Return; 832 auto rty = deriveType(v.cursor.func.resultType); 833 rty.put(ast); 834 if (rty.type !is null) { 835 ast.put(fRetval, loc); 836 n.return_ = fRetval; 837 ast.put(fRetval, rty.id); 838 } 839 if (rty.symbol !is null) { 840 ast.put(fRetval, rty.symId); 841 } 842 843 v.accept(this); 844 } 845 846 private void visitCaseStmt(T)(ref const T v) @trusted { 847 auto res = caseStmtCursor(v); 848 if (res.isNull) { 849 pushStack(new analyze.Block, v); 850 v.accept(this); 851 return; 852 } 853 854 auto branch = new analyze.Branch; 855 ast.put(branch, res.get.branch); 856 nstack.back.children ~= branch; 857 pushStack(branch, res.get.branch); 858 859 // create an node depth that diverge from the clang AST wherein the 860 // inside of a case stmt is modelled as a block. 861 incr; 862 scope (exit) 863 decr; 864 auto inner = new analyze.Block; 865 ast.put(inner, res.get.insideBranch); 866 branch.children ~= inner; 867 branch.inside = inner; 868 pushStack(inner, res.get.insideBranch); 869 870 dispatch(res.get.inner, this); 871 } 872 } 873 874 final class EnumVisitor : ExtendedVisitor { 875 import std.typecons : Nullable; 876 import cpptooling.analyzer.clang.ast; 877 878 alias visit = ExtendedVisitor.visit; 879 880 mixin generateIndentIncrDecr; 881 882 analyze.TypeId id; 883 Nullable!long minValue; 884 Nullable!long maxValue; 885 886 this(const uint indent) { 887 this.indent = indent; 888 } 889 890 override void visit(const EnumDecl v) @trusted { 891 mixin(mixinNodeLog!()); 892 id = make!(analyze.TypeId)(v.cursor); 893 v.accept(this); 894 } 895 896 override void visit(const EnumConstantDecl v) @trusted { 897 mixin(mixinNodeLog!()); 898 899 long value = v.cursor.enum_.signedValue; 900 901 if (minValue.isNull) { 902 minValue = value; 903 maxValue = value; 904 } 905 906 if (value < minValue.get) 907 minValue = value; 908 if (value > maxValue.get) 909 maxValue = value; 910 911 v.accept(this); 912 } 913 914 analyze.Type toType() const { 915 auto l = () { 916 if (minValue.isNull) 917 return analyze.Value(analyze.Value.NegInf.init); 918 return analyze.Value(analyze.Value.Int(minValue.get)); 919 }(); 920 auto u = () { 921 if (maxValue.isNull) 922 return analyze.Value(analyze.Value.PosInf.init); 923 return analyze.Value(analyze.Value.Int(maxValue.get)); 924 }(); 925 926 return new analyze.DiscreteType(analyze.Range(l, u)); 927 } 928 } 929 930 enum discreteCategory = AliasSeq!(CXTypeKind.charU, CXTypeKind.uChar, CXTypeKind.char16, 931 CXTypeKind.char32, CXTypeKind.uShort, CXTypeKind.uInt, CXTypeKind.uLong, CXTypeKind.uLongLong, 932 CXTypeKind.uInt128, CXTypeKind.charS, CXTypeKind.sChar, CXTypeKind.wChar, CXTypeKind.short_, 933 CXTypeKind.int_, CXTypeKind.long_, CXTypeKind.longLong, 934 CXTypeKind.int128, CXTypeKind.enum_,); 935 enum floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_, 936 CXTypeKind.longDouble, CXTypeKind.float128, CXTypeKind.half, CXTypeKind.float16,); 937 enum pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer, 938 CXTypeKind.blockPointer, CXTypeKind.memberPointer); 939 enum boolCategory = AliasSeq!(CXTypeKind.bool_); 940 941 struct DeriveTypeResult { 942 analyze.TypeId id; 943 analyze.Type type; 944 analyze.SymbolId symId; 945 analyze.Symbol symbol; 946 947 void put(ref analyze.Ast ast) @safe { 948 if (type !is null) { 949 ast.types.require(id, type); 950 } 951 if (symbol !is null) { 952 ast.symbols.require(symId, symbol); 953 } 954 } 955 } 956 957 DeriveTypeResult deriveType(Type cty) { 958 DeriveTypeResult rval; 959 960 auto ctydecl = cty.declaration; 961 if (ctydecl.isValid) { 962 rval.id = make!(analyze.TypeId)(ctydecl); 963 } else { 964 rval.id = make!(analyze.TypeId)(cty.cursor); 965 } 966 967 if (cty.isEnum) { 968 rval.type = new analyze.DiscreteType(analyze.Range.makeInf); 969 if (!cty.isSigned) { 970 rval.type.range.low = analyze.Value(analyze.Value.Int(0)); 971 } 972 } else if (cty.kind.among(floatCategory)) { 973 rval.type = new analyze.ContinuesType(analyze.Range.makeInf); 974 } else if (cty.kind.among(pointerCategory)) { 975 rval.type = new analyze.UnorderedType(analyze.Range.makeInf); 976 } else if (cty.kind.among(boolCategory)) { 977 rval.type = new analyze.BooleanType(analyze.Range.makeBoolean); 978 } else if (cty.kind.among(discreteCategory)) { 979 rval.type = new analyze.DiscreteType(analyze.Range.makeInf); 980 if (!cty.isSigned) { 981 rval.type.range.low = analyze.Value(analyze.Value.Int(0)); 982 } 983 } 984 985 return rval; 986 } 987 988 struct DeriveCursorTypeResult { 989 Cursor expr; 990 DeriveTypeResult typeResult; 991 alias typeResult this; 992 } 993 994 /** Analyze a cursor to derive the type of it and if it has a concrete value 995 * and what it is in that case. 996 * 997 * This is intended for expression nodes in the clang AST. 998 */ 999 DeriveCursorTypeResult deriveCursorType(const Cursor baseCursor) { 1000 auto c = Cursor(getUnderlyingExprNode(baseCursor)); 1001 if (!c.isValid) 1002 return DeriveCursorTypeResult.init; 1003 1004 auto rval = DeriveCursorTypeResult(c); 1005 auto cty = c.type.canonicalType; 1006 rval.typeResult = deriveType(cty); 1007 1008 // evaluate the cursor to add a value for the symbol 1009 void eval(const ref Eval e) { 1010 if (!e.kind.among(CXEvalResultKind.int_)) 1011 return; 1012 1013 const long value = () { 1014 if (e.isUnsignedInt) { 1015 const v = e.asUnsigned; 1016 if (v < long.max) 1017 return cast(long) v; 1018 } 1019 return e.asLong; 1020 }(); 1021 1022 rval.symId = make!(analyze.SymbolId)(c); 1023 rval.symbol = new analyze.DiscretSymbol(analyze.Value(analyze.Value.Int(value))); 1024 } 1025 1026 if (cty.isEnum) { 1027 // TODO: check if c.eval give the same result. If so it may be easier 1028 // to remove this special case of an enum because it is covered by the 1029 // generic branch for discretes. 1030 1031 auto ctydecl = cty.declaration; 1032 if (!ctydecl.isValid) 1033 return rval; 1034 1035 const cref = c.referenced; 1036 if (!cref.isValid) 1037 return rval; 1038 1039 if (cref.kind == CXCursorKind.enumConstantDecl) { 1040 const long value = cref.enum_.signedValue; 1041 rval.symId = make!(analyze.SymbolId)(c); 1042 rval.symbol = new analyze.DiscretSymbol(analyze.Value(analyze.Value.Int(value))); 1043 } 1044 } else if (cty.kind.among(discreteCategory)) { 1045 // crashes in clang 7.x. Investigate why. 1046 //const e = c.eval; 1047 //if (e.isValid) 1048 // eval(e); 1049 } 1050 1051 return rval; 1052 } 1053 1054 auto make(T)(const Cursor c) if (is(T == analyze.TypeId) || is(T == analyze.SymbolId)) { 1055 const usr = c.usr; 1056 if (usr.empty) { 1057 return T(c.toHash); 1058 } 1059 return analyze.makeId!T(usr); 1060 } 1061 1062 /// trusted: trusting the impl in clang. 1063 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted { 1064 foreach (ref t; toks) { 1065 if (t.location.offset >= sr.end) { 1066 if (t.kind == kind) { 1067 return t.extent.end.offset; 1068 } 1069 break; 1070 } 1071 } 1072 1073 return sr.end; 1074 } 1075 1076 /** Create an index of all macros that then can be queried to see if a Cursor 1077 * or Interval overlap a macro. 1078 */ 1079 struct BlackList { 1080 import dextool.plugin.mutate.backend.analyze.utility : Index; 1081 1082 Index!string macros; 1083 1084 this(const Cursor root) { 1085 Interval[][string] macros; 1086 1087 foreach (c, parent; root.all) { 1088 if (c.kind != CXCursorKind.macroExpansion || c.isMacroBuiltin) 1089 continue; 1090 1091 auto spelling = c.spelling; 1092 // C code almost always implement these as macros. They should not 1093 // be blocked from being mutated. 1094 if (spelling.among("bool", "TRUE", "FALSE")) 1095 continue; 1096 1097 const file = c.location.path; 1098 const e = c.extent; 1099 const interval = Interval(e.start.offset, e.end.offset); 1100 if (auto v = file in macros) { 1101 (*v) ~= interval; 1102 } else { 1103 macros[file] = [interval]; 1104 } 1105 } 1106 1107 foreach (k; macros.byKey) { 1108 macros[k] = macros[k].sort.array; 1109 } 1110 1111 this.macros = Index!string(macros); 1112 } 1113 1114 bool inside(const Cursor c) { 1115 const file = c.location.path.Path; 1116 const e = c.extent; 1117 const interval = Interval(e.start.offset, e.end.offset); 1118 return inside(file, interval); 1119 } 1120 1121 bool inside(analyze.Location l) { 1122 return inside(l.file, l.interval); 1123 } 1124 1125 /** 1126 * assuming that an invalid mutant is always inside a macro thus only 1127 * checking if the `i` is inside. Removing a "block" of code that happens 1128 * to contain a macro is totally "ok". It doesn't create any problem. 1129 * 1130 * Returns: true if `i` is inside a macro interval. 1131 */ 1132 bool inside(const Path file, const Interval i) { 1133 return macros.inside(file, i); 1134 } 1135 }