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