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.get.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(scope 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() scope @safe { 401 ++indent; 402 lastDecr.clear; 403 } 404 405 override void decr() scope @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(scope Cursor cursor, analyze.Location l) @trusted { 413 return blacklist.isBlacklist(cursor, l); 414 } 415 416 private void pushStack(scope 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 n.covBlacklist = n.blacklist || n.schemaBlacklist || n.covBlacklist; 421 if (!nstack.empty) 422 n.schemaBlacklist = n.schemaBlacklist || nstack[$ - 1].data.schemaBlacklist; 423 nstack.put(n, indent); 424 cstack.put(cKind, indent); 425 ast.get.put(n, l); 426 } 427 428 /// Returns: true if it is OK to modify the cursor 429 private void pushStack(AstT, ClangT)(AstT n, ClangT c) @trusted { 430 nstack.back.children ~= n; 431 static if (is(ClangT == Cursor)) { 432 auto loc = c.toLocation; 433 pushStack(c, n, loc, c.kind); 434 } else { 435 auto loc = c.cursor.toLocation; 436 pushStack(c.cursor, n, loc, c.cursor.kind); 437 } 438 } 439 440 override void visit(scope const TranslationUnit v) @trusted { 441 import clang.c.Index : CXLanguageKind; 442 443 mixin(mixinNodeLog!()); 444 445 blacklist = Blacklist(v.cursor); 446 447 ast.get.root = ast.get.make!(analyze.TranslationUnit); 448 auto loc = v.cursor.toLocation; 449 pushStack(v.cursor, ast.get.root, loc, v.cursor.kind); 450 451 // it is most often invalid 452 switch (v.cursor.language) { 453 case CXLanguageKind.c: 454 ast.get.lang = Language.c; 455 break; 456 case CXLanguageKind.cPlusPlus: 457 ast.get.lang = Language.cpp; 458 break; 459 default: 460 ast.get.lang = Language.assumeCpp; 461 } 462 463 v.accept(this); 464 } 465 466 override void visit(scope const Attribute v) { 467 mixin(mixinNodeLog!()); 468 v.accept(this); 469 } 470 471 override void visit(scope const Declaration v) { 472 mixin(mixinNodeLog!()); 473 v.accept(this); 474 } 475 476 override void visit(scope const VarDecl v) @trusted { 477 mixin(mixinNodeLog!()); 478 visitVar(v); 479 v.accept(this); 480 } 481 482 override void visit(scope const ParmDecl v) @trusted { 483 mixin(mixinNodeLog!()); 484 visitVar(v); 485 v.accept(this); 486 } 487 488 override void visit(scope const DeclStmt v) @trusted { 489 mixin(mixinNodeLog!()); 490 491 // this, in clang-11, is one of the patterns in the AST when struct 492 // binding is used: 493 // declStmt 494 // | unexposedDecl 495 // | unexposedDecl 496 // | unexposedDecl 497 // | unexposedDecl 498 // | unexposedDecl 499 // | unexposedDecl 500 // | callExpr 501 // it can't be assumed to be the only one. 502 // by injecting a Poision node for a DeclStmt it signal that there are 503 // hidden traps thus any mutation and schemata should be careful. 504 auto n = ast.get.make!(analyze.Poision); 505 pushStack(n, v); 506 507 v.accept(this); 508 } 509 510 // This note covers ClassTemplate, ClassTemplatePartialSpecialization and 511 // FunctionTemplate. 512 // NOTE: to future self. I tried to block scheman inside template 513 // classes but it turned out to be a bad idea. Most of the time they 514 // actually work OK. If it is turned modern c++ code takes a loooong 515 // time to run mutation testing on. There are some corner cases wherein 516 // an infinite recursion loop can occur which I haven't been able to 517 // figure out why. 518 // With the introduction of ML for schema generation the tool will adapt 519 // how scheman are composed based on the compilation failures. 520 521 override void visit(scope const ClassTemplate v) @trusted { 522 mixin(mixinNodeLog!()); 523 // by adding the node it is possible to search for it in cstack 524 auto n = ast.get.make!(analyze.Poision); 525 n.covBlacklist = true; 526 pushStack(n, v); 527 v.accept(this); 528 } 529 530 override void visit(scope const ClassTemplatePartialSpecialization v) @trusted { 531 mixin(mixinNodeLog!()); 532 // by adding the node it is possible to search for it in cstack 533 auto n = ast.get.make!(analyze.Poision); 534 n.covBlacklist = true; 535 pushStack(n, v); 536 v.accept(this); 537 } 538 539 override void visit(scope const FunctionTemplate v) @trusted { 540 mixin(mixinNodeLog!()); 541 auto n = ast.get.make!(analyze.Function); 542 n.covBlacklist = true; 543 pushStack(n, v); 544 v.accept(this); 545 } 546 547 override void visit(scope const TemplateTypeParameter v) { 548 mixin(mixinNodeLog!()); 549 // block mutants inside template parameters 550 } 551 552 override void visit(scope const TemplateTemplateParameter v) { 553 mixin(mixinNodeLog!()); 554 // block mutants inside template parameters 555 } 556 557 override void visit(scope const NonTypeTemplateParameter v) { 558 mixin(mixinNodeLog!()); 559 // block mutants inside template parameters 560 } 561 562 override void visit(scope const TypeAliasDecl v) { 563 mixin(mixinNodeLog!()); 564 // block mutants inside template parameters 565 } 566 567 override void visit(scope const CxxBaseSpecifier v) { 568 mixin(mixinNodeLog!()); 569 // block mutants inside template parameters. 570 // the only mutants that are inside an inheritance is template 571 // parameters and such. 572 } 573 574 private void visitVar(T)(T v) @trusted { 575 pushStack(ast.get.make!(analyze.VarDecl), v); 576 } 577 578 override void visit(scope const Directive v) { 579 mixin(mixinNodeLog!()); 580 v.accept(this); 581 } 582 583 override void visit(scope const InclusionDirective v) @trusted { 584 import std.file : exists; 585 import std.path : buildPath, dirName; 586 587 mixin(mixinNodeLog!()); 588 589 const spell = v.cursor.spelling; 590 const file = v.cursor.location.file.name; 591 const p = buildPath(file.dirName, spell); 592 if (exists(p)) 593 includes.put(Path(p)); 594 else 595 includes.put(Path(spell)); 596 597 v.accept(this); 598 } 599 600 override void visit(scope const Reference v) { 601 mixin(mixinNodeLog!()); 602 v.accept(this); 603 } 604 605 // TODO overlapping logic with Expression. deduplicate 606 override void visit(scope const DeclRefExpr v) @trusted { 607 import libclang_ast.ast : dispatch; 608 import clang.SourceRange : intersects; 609 610 mixin(mixinNodeLog!()); 611 612 if (v.cursor.toHash in isVisited) 613 return; 614 isVisited.add(v.cursor.toHash); 615 616 auto n = ast.get.make!(analyze.Expr); 617 n.schemaBlacklist = isParent(CXCursorKind.classTemplate, 618 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 619 620 auto ue = deriveCursorType(ast.get, v.cursor); 621 ue.put(ast.get); 622 if (ue.type !is null) { 623 ast.get.put(n, ue.id); 624 } 625 626 // only deref a node which is a self-reference 627 auto r = v.cursor.referenced; 628 if (r.isValid && r != v.cursor && intersects(v.cursor.extent, r.extent) 629 && r.toHash !in isVisited) { 630 isVisited.add(r.toHash); 631 pushStack(n, v); 632 633 incr; 634 scope (exit) 635 decr; 636 dispatch(r, this); 637 } else if (ue.expr.isValid && ue.expr != v.cursor && ue.expr.toHash !in isVisited) { 638 isVisited.add(ue.expr.toHash); 639 pushStack(n, ue.expr); 640 641 incr; 642 scope (exit) 643 decr; 644 dispatch(ue.expr, this); 645 } else { 646 pushStack(n, v); 647 v.accept(this); 648 } 649 } 650 651 override void visit(scope const Statement v) { 652 mixin(mixinNodeLog!()); 653 v.accept(this); 654 } 655 656 override void visit(scope const LabelRef v) { 657 mixin(mixinNodeLog!()); 658 // a label cannot be duplicated thus mutant scheman aren't possible to generate 659 blacklistFunc = true; 660 } 661 662 override void visit(scope const ArraySubscriptExpr v) { 663 mixin(mixinNodeLog!()); 664 // block schematan inside subscripts because some lead to compilation 665 // errors. Need to investigate more to understand why and how to avoid. 666 // For now they are blocked. 667 auto n = ast.get.make!(analyze.Poision); 668 n.schemaBlacklist = true; 669 pushStack(n, v); 670 v.accept(this); 671 } 672 673 override void visit(scope const Expression v) { 674 mixin(mixinNodeLog!()); 675 676 const h = v.cursor.toHash; 677 if (h in isVisited) 678 return; 679 isVisited.add(h); 680 681 auto n = ast.get.make!(analyze.Expr); 682 n.schemaBlacklist = isParent(CXCursorKind.classTemplate, 683 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 684 685 auto ue = deriveCursorType(ast.get, v.cursor); 686 ue.put(ast.get); 687 if (ue.type !is null) { 688 ast.get.put(n, ue.id); 689 } 690 691 pushStack(n, v); 692 v.accept(this); 693 } 694 695 override void visit(scope const Preprocessor v) { 696 mixin(mixinNodeLog!()); 697 698 const bool isCpp = v.cursor.spelling == "__cplusplus"; 699 700 if (isCpp) 701 ast.get.lang = Language.cpp; 702 else if (!isCpp && ast.get.lang != Language.cpp) 703 ast.get.lang = Language.c; 704 705 v.accept(this); 706 } 707 708 override void visit(scope const EnumDecl v) @trusted { 709 mixin(mixinNodeLog!()); 710 711 // extract the boundaries of the enum to update the type db. 712 scope vis = new EnumVisitor(ast.ptr, indent); 713 vis.visit(v); 714 ast.get.types.set(vis.id, vis.toType); 715 } 716 717 override void visit(scope const FunctionDecl v) @trusted { 718 mixin(mixinNodeLog!()); 719 visitFunc(v); 720 } 721 722 override void visit(scope const Constructor v) @trusted { 723 mixin(mixinNodeLog!()); 724 725 auto n = ast.get.make!(analyze.Poision); 726 n.schemaBlacklist = isConstExpr(v.cursor); 727 pushStack(n, v); 728 729 // skip all "= default" 730 if (!v.cursor.isDefaulted) 731 v.accept(this); 732 } 733 734 override void visit(scope const Destructor v) @trusted { 735 mixin(mixinNodeLog!()); 736 737 auto n = ast.get.make!(analyze.Poision); 738 n.schemaBlacklist = isConstExpr(v.cursor); 739 pushStack(n, v); 740 741 // skip all "= default" 742 if (!v.cursor.isDefaulted) 743 v.accept(this); 744 // TODO: no test covers this case where = default is used for a 745 // destructor. For some versions of clang a CompoundStmt is generated 746 } 747 748 override void visit(scope const CxxMethod v) { 749 mixin(mixinNodeLog!()); 750 751 // model C++ methods as functions. It should be enough to know that it 752 // is a function and the return type when generating mutants. 753 754 // skip all "= default" 755 if (!v.cursor.isDefaulted) 756 visitFunc(v); 757 } 758 759 override void visit(scope const BreakStmt v) { 760 mixin(mixinNodeLog!()); 761 v.accept(this); 762 } 763 764 override void visit(scope const BinaryOperator v) @trusted { 765 mixin(mixinNodeLog!()); 766 767 visitOp(v, v.cursor.kind); 768 } 769 770 override void visit(scope const UnaryOperator v) @trusted { 771 mixin(mixinNodeLog!()); 772 visitOp(v, v.cursor.kind); 773 } 774 775 override void visit(scope const CompoundAssignOperator v) { 776 mixin(mixinNodeLog!()); 777 // TODO: implement all aor assignment such as += 778 pushStack(ast.get.make!(analyze.OpAssign), v); 779 v.accept(this); 780 } 781 782 override void visit(scope const CallExpr v) { 783 mixin(mixinNodeLog!()); 784 785 if (!visitOp(v, v.cursor.kind)) { 786 visitCall(v); 787 } 788 } 789 790 override void visit(scope const CxxThrowExpr v) { 791 mixin(mixinNodeLog!()); 792 // model a C++ exception as a return expression because that is 793 // "basically" what happens. 794 auto n = ast.get.make!(analyze.Return); 795 n.blacklist = true; 796 pushStack(n, v); 797 v.accept(this); 798 } 799 800 override void visit(scope const InitListExpr v) { 801 mixin(mixinNodeLog!()); 802 pushStack(ast.get.make!(analyze.Constructor), v); 803 v.accept(this); 804 } 805 806 override void visit(scope const LambdaExpr v) { 807 mixin(mixinNodeLog!()); 808 809 // model C++ lambdas as functions. It should be enough to know that it 810 // is a function and the return type when generating mutants. 811 visitFunc(v); 812 } 813 814 override void visit(scope const ReturnStmt v) { 815 mixin(mixinNodeLog!()); 816 pushStack(ast.get.make!(analyze.Return), v); 817 v.accept(this); 818 } 819 820 override void visit(scope const CompoundStmt v) { 821 import std.algorithm : min; 822 823 mixin(mixinNodeLog!()); 824 825 static uint findBraketOffset(Blob file, const uint begin, const uint end, const ubyte letter) { 826 for (uint i = begin; i < end; ++i) { 827 if (file.content[i] == letter) { 828 return i; 829 } 830 } 831 return begin; 832 } 833 834 if (isDirectParent(CXCursorKind.switchStmt)) { 835 // the CompoundStmt statement {} directly inside a switch statement 836 // isn't useful to manipulate as a block. The useful part is the 837 // smaller blocks that the case and default break down the block 838 // into thus this avoid generating useless blocks that lead to 839 // equivalent or unproductive mutants. 840 } else 841 try { 842 auto loc = v.cursor.toLocation; 843 // there are unexposed nodes which has range [0,0] 844 if (loc.interval.begin < loc.interval.end) { 845 auto n = ast.get.make!(analyze.Block); 846 847 // important because it triggers an invalid path if the 848 // file shouldn't be manipulated 849 auto file = fio.makeInput(loc.file); 850 851 // if the loc is inside a macro it may be impossible to 852 // textually find matching brackets. 853 if (!isBlacklist(v.cursor, loc)) { 854 const maxEnd = file.content.length; 855 856 // The block that can be modified is the inside of it thus the 857 // a CompoundStmt that represent a "{..}" can for example be the 858 // body of a function or the block that a try statement encompase. 859 // done then a SDL can't be generated that delete the inside of 860 // e.g. void functions. 861 862 auto end = min(findBraketOffset(file, loc.interval.end == 0 863 ? loc.interval.end : loc.interval.end - 1, 864 cast(uint) maxEnd, cast(ubyte) '}'), maxEnd); 865 auto begin = findBraketOffset(file, loc.interval.begin, end, cast(ubyte) '{'); 866 867 if (begin < end) 868 begin = begin + 1; 869 870 // TODO: need to adjust sloc too 871 loc.interval = Interval(begin, end); 872 } 873 874 nstack.back.children ~= n; 875 pushStack(v.cursor, n, loc, v.cursor.kind); 876 } 877 } catch (InvalidPathException e) { 878 } catch (Exception e) { 879 log.trace(e.msg).collectException; 880 } 881 882 v.accept(this); 883 } 884 885 override void visit(scope const CaseStmt v) @trusted { 886 import libclang_ast.ast : dispatch; 887 import dextool.clang_extensions; 888 889 mixin(mixinNodeLog!()); 890 891 auto stmt = getCaseStmt(v.cursor); 892 if (stmt.isValid && stmt.subStmt.isValid) { 893 dispatch(stmt.subStmt, this); 894 } else { 895 v.accept(this); 896 } 897 } 898 899 override void visit(scope const DefaultStmt v) { 900 mixin(mixinNodeLog!()); 901 v.accept(this); 902 } 903 904 override void visit(scope const ForStmt v) { 905 mixin(mixinNodeLog!()); 906 pushStack(ast.get.make!(analyze.Loop), v); 907 908 scope visitor = new FindVisitor!CompoundStmt; 909 v.accept(visitor); 910 911 if (visitor.node !is null) { 912 this.visit(visitor.node); 913 } 914 } 915 916 override void visit(scope const CxxForRangeStmt v) { 917 mixin(mixinNodeLog!()); 918 pushStack(ast.get.make!(analyze.Loop), v); 919 920 scope visitor = new FindVisitor!CompoundStmt; 921 v.accept(visitor); 922 923 if (visitor.node !is null) { 924 this.visit(visitor.node); 925 } 926 } 927 928 override void visit(scope const WhileStmt v) { 929 mixin(mixinNodeLog!()); 930 pushStack(ast.get.make!(analyze.Loop), v); 931 v.accept(this); 932 } 933 934 override void visit(scope const DoStmt v) { 935 mixin(mixinNodeLog!()); 936 pushStack(ast.get.make!(analyze.Loop), v); 937 v.accept(this); 938 } 939 940 override void visit(scope const SwitchStmt v) { 941 mixin(mixinNodeLog!()); 942 auto n = ast.get.make!(analyze.BranchBundle); 943 pushStack(n, v); 944 v.accept(this); 945 946 scope caseVisitor = new FindVisitor!CaseStmt; 947 v.accept(caseVisitor); 948 949 if (caseVisitor.node is null) { 950 log.warning( 951 "switch without any case statements may result in a high number of mutant scheman that do not compile"); 952 } else { 953 incr; 954 scope (exit) 955 decr; 956 auto block = ast.get.make!(analyze.Block); 957 auto l = ast.get.location(n); 958 l.interval.end = l.interval.begin; 959 pushStack(v.cursor, block, l, CXCursorKind.unexposedDecl); 960 rewriteSwitch(ast.get, n, block, caseVisitor.node.cursor.toLocation); 961 } 962 } 963 964 override void visit(scope const ConditionalOperator v) { 965 mixin(mixinNodeLog!()); 966 // need to push a node because a ternery can contain function calls. 967 // Without a node for the op it seems like it is in the body, which it 968 // isn't, and then can be removed. 969 pushStack(ast.get.make!(analyze.Poision), v); 970 v.accept(this); 971 } 972 973 override void visit(scope const IfStmt v) @trusted { 974 mixin(mixinNodeLog!()); 975 // to avoid infinite recursion, which may occur in e.g. postgresql, block 976 // segfault on 300 977 if (indent > 200) { 978 throw new Exception("max analyze depth reached (200)"); 979 } 980 981 pushStack(ast.get.make!(analyze.BranchBundle), v); 982 dextool.plugin.mutate.backend.analyze.extensions.accept(v, this); 983 } 984 985 override void visit(scope const IfStmtInit v) @trusted { 986 mixin(mixinNodeLog!()); 987 auto n = ast.get.make!(analyze.Poision); 988 n.schemaBlacklist = true; 989 pushStack(n, v); 990 v.accept(this); 991 } 992 993 override void visit(scope const IfStmtCond v) { 994 mixin(mixinNodeLog!()); 995 996 auto n = ast.get.make!(analyze.Condition); 997 pushStack(n, v); 998 999 if (!visitOp(v, v.cursor.kind)) { 1000 v.accept(this); 1001 } 1002 1003 if (!n.children.empty) { 1004 auto tyId = ast.get.typeId(n.children[0]); 1005 if (tyId.hasValue) { 1006 ast.get.put(n, tyId.orElse(analyze.TypeId.init)); 1007 } 1008 } 1009 1010 rewriteCondition(ast.get, n); 1011 } 1012 1013 override void visit(scope const IfStmtCondVar v) { 1014 mixin(mixinNodeLog!()); 1015 auto n = ast.get.make!(analyze.Poision); 1016 n.schemaBlacklist = true; 1017 pushStack(n, v); 1018 } 1019 1020 override void visit(scope const IfStmtThen v) { 1021 mixin(mixinNodeLog!()); 1022 visitIfBranch(v); 1023 } 1024 1025 override void visit(scope const IfStmtElse v) { 1026 mixin(mixinNodeLog!()); 1027 visitIfBranch(v); 1028 } 1029 1030 private void visitIfBranch(T)(scope const T v) @trusted { 1031 pushStack(ast.get.make!(analyze.Branch), v); 1032 v.accept(this); 1033 } 1034 1035 private bool visitOp(T)(scope const T v, const CXCursorKind cKind) @trusted { 1036 auto op = operatorCursor(ast.get, v); 1037 if (op.isNull) { 1038 return false; 1039 } 1040 1041 if (visitBinaryOp(v.cursor, op.get, cKind)) 1042 return true; 1043 return visitUnaryOp(v.cursor, op.get, cKind); 1044 } 1045 1046 /// Returns: true if it added a binary operator, false otherwise. 1047 private bool visitBinaryOp(scope Cursor cursor, ref OperatorCursor op, const CXCursorKind cKind) @trusted { 1048 import libclang_ast.ast : dispatch; 1049 1050 auto astOp = cast(analyze.BinaryOp) op.astOp; 1051 if (astOp is null) 1052 return false; 1053 1054 const blockSchema = op.isOverload || isBlacklist(cursor, op.opLoc) || isParent(CXCursorKind.classTemplate, 1055 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 1056 1057 astOp.schemaBlacklist = blockSchema; 1058 astOp.operator = op.operator; 1059 astOp.operator.blacklist = isBlacklist(cursor, op.opLoc); 1060 astOp.operator.schemaBlacklist = blockSchema; 1061 1062 op.put(nstack.back, ast.get); 1063 pushStack(cursor, astOp, op.exprLoc, cKind); 1064 incr; 1065 scope (exit) 1066 decr; 1067 1068 if (op.lhs.isValid) { 1069 incr; 1070 scope (exit) 1071 decr; 1072 dispatch(op.lhs, this); 1073 auto b = () { 1074 if (!lastDecr.empty) 1075 return cast(analyze.Expr) lastDecr[$ - 1]; 1076 return null; 1077 }(); 1078 if (b !is null && b != astOp) { 1079 astOp.lhs = b; 1080 auto ty = deriveCursorType(ast.get, op.lhs); 1081 ty.put(ast.get); 1082 if (ty.type !is null) { 1083 ast.get.put(b, ty.id); 1084 } 1085 if (ty.symbol !is null) { 1086 ast.get.put(b, ty.symId); 1087 } 1088 } 1089 } 1090 if (op.rhs.isValid) { 1091 incr; 1092 scope (exit) 1093 decr; 1094 dispatch(op.rhs, this); 1095 auto b = () { 1096 if (!lastDecr.empty) 1097 return cast(analyze.Expr) lastDecr[$ - 1]; 1098 return null; 1099 }(); 1100 if (b !is null && b != astOp) { 1101 astOp.rhs = b; 1102 auto ty = deriveCursorType(ast.get, op.rhs); 1103 ty.put(ast.get); 1104 if (ty.type !is null) { 1105 ast.get.put(b, ty.id); 1106 } 1107 if (ty.symbol !is null) { 1108 ast.get.put(b, ty.symId); 1109 } 1110 } 1111 } 1112 1113 return true; 1114 } 1115 1116 /// Returns: true if it added a binary operator, false otherwise. 1117 private bool visitUnaryOp(Cursor cursor, ref OperatorCursor op, CXCursorKind cKind) @trusted { 1118 import libclang_ast.ast : dispatch; 1119 1120 auto astOp = cast(analyze.UnaryOp) op.astOp; 1121 if (astOp is null) 1122 return false; 1123 1124 const blockSchema = op.isOverload || isBlacklist(cursor, op.opLoc) || isParent(CXCursorKind.classTemplate, 1125 CXCursorKind.classTemplatePartialSpecialization, CXCursorKind.functionTemplate) != 0; 1126 1127 astOp.operator = op.operator; 1128 astOp.operator.blacklist = isBlacklist(cursor, op.opLoc); 1129 astOp.operator.schemaBlacklist = blockSchema; 1130 1131 op.put(nstack.back, ast.get); 1132 pushStack(cursor, astOp, op.exprLoc, cKind); 1133 incr; 1134 scope (exit) 1135 decr; 1136 1137 if (op.lhs.isValid) { 1138 incr; 1139 scope (exit) 1140 decr; 1141 dispatch(op.lhs, this); 1142 auto b = () { 1143 if (!lastDecr.empty) 1144 return cast(analyze.Expr) lastDecr[$ - 1]; 1145 return null; 1146 }(); 1147 if (b !is null && b != astOp) { 1148 astOp.expr = b; 1149 auto ty = deriveCursorType(ast.get, op.lhs); 1150 ty.put(ast.get); 1151 if (ty.type !is null) 1152 ast.get.put(b, ty.id); 1153 if (ty.symbol !is null) 1154 ast.get.put(b, ty.symId); 1155 } 1156 } 1157 if (op.rhs.isValid) { 1158 incr; 1159 scope (exit) 1160 decr; 1161 dispatch(op.rhs, this); 1162 auto b = () { 1163 if (!lastDecr.empty) 1164 return cast(analyze.Expr) lastDecr[$ - 1]; 1165 return null; 1166 }(); 1167 if (b !is null && b != astOp) { 1168 astOp.expr = b; 1169 auto ty = deriveCursorType(ast.get, op.rhs); 1170 ty.put(ast.get); 1171 if (ty.type !is null) 1172 ast.get.put(b, ty.id); 1173 if (ty.symbol !is null) 1174 ast.get.put(b, ty.symId); 1175 } 1176 } 1177 1178 return true; 1179 } 1180 1181 private void visitFunc(T)(scope const T v) @trusted { 1182 auto oldBlacklistFn = blacklistFunc; 1183 scope (exit) 1184 blacklistFunc = oldBlacklistFn; 1185 1186 auto loc = v.cursor.toLocation; 1187 auto n = ast.get.make!(analyze.Function); 1188 n.schemaBlacklist = isConstExpr(v.cursor); 1189 nstack.back.children ~= n; 1190 pushStack(v.cursor, n, loc, v.cursor.kind); 1191 1192 auto fRetval = ast.get.make!(analyze.Return); 1193 auto rty = deriveType(ast.get, v.cursor.func.resultType); 1194 rty.put(ast.get); 1195 if (rty.type !is null) { 1196 ast.get.put(fRetval, loc); 1197 n.return_ = fRetval; 1198 ast.get.put(fRetval, rty.id); 1199 } 1200 if (rty.symbol !is null) 1201 ast.get.put(fRetval, rty.symId); 1202 1203 v.accept(this); 1204 1205 if (blacklistFunc != 0) { 1206 foreach (c; BreathFirstRange(n)) 1207 c.schemaBlacklist = true; 1208 } 1209 } 1210 1211 private void visitCall(T)(scope const T v) @trusted { 1212 auto n = ast.get.make!(analyze.Call); 1213 pushStack(n, v); 1214 1215 auto ty = deriveType(ast.get, v.cursor.type); 1216 ty.put(ast.get); 1217 if (ty.type !is null) 1218 ast.get.put(n, ty.id); 1219 if (ty.symbol !is null) 1220 ast.get.put(n, ty.symId); 1221 1222 v.accept(this); 1223 } 1224 } 1225 1226 final class EnumVisitor : ExtendedVisitor { 1227 import libclang_ast.ast; 1228 1229 alias visit = ExtendedVisitor.visit; 1230 1231 mixin generateIndentIncrDecr; 1232 1233 analyze.Ast* ast; 1234 analyze.TypeId id; 1235 Nullable!long minValue; 1236 Nullable!long maxValue; 1237 1238 this(scope analyze.Ast* ast, const uint indent) @trusted { 1239 this.ast = ast; 1240 this.indent = indent; 1241 } 1242 1243 override void visit(scope const EnumDecl v) @trusted { 1244 mixin(mixinNodeLog!()); 1245 id = make!(analyze.TypeId)(v.cursor); 1246 v.accept(this); 1247 } 1248 1249 override void visit(scope const EnumConstantDecl v) @trusted { 1250 mixin(mixinNodeLog!()); 1251 1252 long value = v.cursor.enum_.signedValue; 1253 1254 if (minValue.isNull) { 1255 minValue = value; 1256 maxValue = value; 1257 } 1258 1259 if (value < minValue.get) 1260 minValue = value; 1261 if (value > maxValue.get) 1262 maxValue = value; 1263 1264 v.accept(this); 1265 } 1266 1267 analyze.Type toType() { 1268 auto l = () { 1269 if (minValue.isNull) 1270 return analyze.Value(analyze.Value.NegInf.init); 1271 return analyze.Value(analyze.Value.Int(minValue.get)); 1272 }(); 1273 auto u = () { 1274 if (maxValue.isNull) 1275 return analyze.Value(analyze.Value.PosInf.init); 1276 return analyze.Value(analyze.Value.Int(maxValue.get)); 1277 }(); 1278 1279 return ast.make!(analyze.DiscreteType)(analyze.Range(l, u)); 1280 } 1281 } 1282 1283 /// Find first occurences of the node type `T`. 1284 final class FindVisitor(T) : Visitor { 1285 import libclang_ast.ast; 1286 1287 alias visit = Visitor.visit; 1288 1289 const(T)[] nodes; 1290 1291 const(T) node() @safe pure nothrow const @nogc { 1292 if (nodes.empty) 1293 return null; 1294 return nodes[0]; 1295 } 1296 1297 //uint indent; 1298 //override void incr() @safe { 1299 // ++indent; 1300 //} 1301 // 1302 //override void decr() @safe { 1303 // --indent; 1304 //} 1305 1306 override void visit(scope const Statement v) { 1307 //mixin(mixinNodeLog!()); 1308 if (nodes.empty) 1309 v.accept(this); 1310 } 1311 1312 override void visit(scope const T v) @trusted { 1313 //mixin(mixinNodeLog!()); 1314 // nodes are scope allocated thus it needs to be duplicated. 1315 if (nodes.empty) 1316 nodes = [new T(v.cursor)]; 1317 } 1318 } 1319 1320 /** Rewrite the position of a condition to perfectly match the parenthesis. 1321 * 1322 * The source code: 1323 * ``` 1324 * if (int x = 42) y = 43; 1325 * ``` 1326 * 1327 * Results in something like this in the AST. 1328 * 1329 * `-Condition 1330 * `-Expr 1331 * `-Expr 1332 * `-VarDecl 1333 * `-OpGreater 1334 * `-Operator 1335 * `-Expr 1336 * `-Expr 1337 * 1338 * The problem is that the location of the Condition node will be OpGreater and 1339 * not the VarDecl. 1340 */ 1341 void rewriteCondition(ref analyze.Ast ast, analyze.Condition root) { 1342 import sumtype; 1343 import dextool.plugin.mutate.backend.analyze.ast : TypeId, VarDecl, Kind; 1344 1345 // set the type of the Condition to the first expression with a type. 1346 foreach (ty; BreathFirstRange(root).map!(a => ast.typeId(a)) 1347 .filter!(a => a.hasValue)) { 1348 sumtype.match!((Some!TypeId a) => ast.put(root, a), (None a) {})(ty); 1349 break; 1350 } 1351 } 1352 1353 void rewriteSwitch(ref analyze.Ast ast, analyze.BranchBundle root, 1354 analyze.Block block, Location firstCase) { 1355 import std.range : enumerate; 1356 import std.typecons : tuple; 1357 1358 Node[] beforeCase = root.children; 1359 Node[] rest; 1360 1361 foreach (n; root.children 1362 .map!(a => tuple!("node", "loc")(a, ast.location(a))) 1363 .enumerate 1364 .filter!(a => a.value.loc.interval.begin > firstCase.interval.begin)) { 1365 beforeCase = root.children[0 .. n.index]; 1366 rest = root.children[n.index .. $]; 1367 break; 1368 } 1369 1370 root.children = beforeCase ~ block; 1371 block.children = rest; 1372 } 1373 1374 enum discreteCategory = AliasSeq!(CXTypeKind.charU, CXTypeKind.uChar, CXTypeKind.char16, 1375 CXTypeKind.char32, CXTypeKind.uShort, CXTypeKind.uInt, CXTypeKind.uLong, CXTypeKind.uLongLong, 1376 CXTypeKind.uInt128, CXTypeKind.charS, CXTypeKind.sChar, CXTypeKind.wChar, CXTypeKind.short_, 1377 CXTypeKind.int_, CXTypeKind.long_, CXTypeKind.longLong, 1378 CXTypeKind.int128, CXTypeKind.enum_,); 1379 enum floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_, 1380 CXTypeKind.longDouble, CXTypeKind.float128, CXTypeKind.half, CXTypeKind.float16,); 1381 enum pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer, 1382 CXTypeKind.blockPointer, CXTypeKind.memberPointer, CXTypeKind.record); 1383 enum boolCategory = AliasSeq!(CXTypeKind.bool_); 1384 1385 enum voidCategory = AliasSeq!(CXTypeKind.void_); 1386 1387 struct DeriveTypeResult { 1388 analyze.TypeId id; 1389 analyze.Type type; 1390 analyze.SymbolId symId; 1391 analyze.Symbol symbol; 1392 1393 void put(ref analyze.Ast ast) @safe { 1394 if (type !is null) { 1395 ast.types.require(id, type); 1396 } 1397 if (symbol !is null) { 1398 ast.symbols.require(symId, symbol); 1399 } 1400 } 1401 } 1402 1403 DeriveTypeResult deriveType(ref Ast ast, Type cty) { 1404 DeriveTypeResult rval; 1405 1406 if (!cty.isValid) 1407 return rval; 1408 1409 auto ctydecl = cty.declaration; 1410 if (ctydecl.isValid) { 1411 rval.id = make!(analyze.TypeId)(ctydecl); 1412 } else { 1413 rval.id = make!(analyze.TypeId)(cty.cursor); 1414 } 1415 1416 if (cty.isEnum) { 1417 rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf); 1418 if (!cty.isSigned) { 1419 rval.type.range.low = analyze.Value(analyze.Value.Int(0)); 1420 } 1421 } else if (cty.kind.among(floatCategory)) { 1422 rval.type = ast.make!(analyze.ContinuesType)(analyze.Range.makeInf); 1423 } else if (cty.kind.among(pointerCategory)) { 1424 rval.type = ast.make!(analyze.UnorderedType)(analyze.Range.makeInf); 1425 } else if (cty.kind.among(boolCategory)) { 1426 rval.type = ast.make!(analyze.BooleanType)(analyze.Range.makeBoolean); 1427 } else if (cty.kind.among(discreteCategory)) { 1428 rval.type = ast.make!(analyze.DiscreteType)(analyze.Range.makeInf); 1429 if (!cty.isSigned) { 1430 rval.type.range.low = analyze.Value(analyze.Value.Int(0)); 1431 } 1432 } else if (cty.kind.among(voidCategory)) { 1433 rval.type = ast.make!(analyze.VoidType)(); 1434 } else { 1435 // unknown such as an elaborated 1436 rval.type = ast.make!(analyze.Type)(); 1437 } 1438 1439 return rval; 1440 } 1441 1442 struct DeriveCursorTypeResult { 1443 Cursor expr; 1444 DeriveTypeResult typeResult; 1445 alias typeResult this; 1446 } 1447 1448 /** Analyze a cursor to derive the type of it and if it has a concrete value 1449 * and what it is in that case. 1450 * 1451 * This is intended for expression nodes in the clang AST. 1452 */ 1453 DeriveCursorTypeResult deriveCursorType(ref Ast ast, scope const Cursor baseCursor) { 1454 auto c = Cursor(getUnderlyingExprNode(baseCursor)); 1455 if (!c.isValid) 1456 return DeriveCursorTypeResult.init; 1457 1458 auto rval = DeriveCursorTypeResult(c); 1459 auto cty = c.type.canonicalType; 1460 rval.typeResult = deriveType(ast, cty); 1461 1462 // evaluate the cursor to add a value for the symbol 1463 void eval(const ref Eval e) { 1464 if (!e.kind.among(CXEvalResultKind.int_)) 1465 return; 1466 1467 const long value = () { 1468 if (e.isUnsignedInt) { 1469 const v = e.asUnsigned; 1470 if (v < long.max) 1471 return cast(long) v; 1472 } 1473 return e.asLong; 1474 }(); 1475 1476 rval.symId = make!(analyze.SymbolId)(c); 1477 rval.symbol = ast.make!(analyze.DiscretSymbol)(analyze.Value(analyze.Value.Int(value))); 1478 } 1479 1480 if (cty.isEnum) { 1481 // TODO: check if c.eval give the same result. If so it may be easier 1482 // to remove this special case of an enum because it is covered by the 1483 // generic branch for discretes. 1484 1485 auto ctydecl = cty.declaration; 1486 if (!ctydecl.isValid) 1487 return rval; 1488 1489 const cref = c.referenced; 1490 if (!cref.isValid) 1491 return rval; 1492 1493 if (cref.kind == CXCursorKind.enumConstantDecl) { 1494 const long value = cref.enum_.signedValue; 1495 rval.symId = make!(analyze.SymbolId)(c); 1496 rval.symbol = ast.make!(analyze.DiscretSymbol)( 1497 analyze.Value(analyze.Value.Int(value))); 1498 } 1499 } else if (cty.kind.among(discreteCategory)) { 1500 // crashes in clang 7.x. Investigate why. 1501 //const e = c.eval; 1502 //if (e.isValid) 1503 // eval(e); 1504 } 1505 1506 return rval; 1507 } 1508 1509 auto make(T)(const Cursor c) if (is(T == analyze.TypeId) || is(T == analyze.SymbolId)) { 1510 const usr = c.usr; 1511 if (usr.empty) { 1512 return T(c.toHash); 1513 } 1514 return analyze.makeId!T(usr); 1515 } 1516 1517 /// trusted: trusting the impl in clang. 1518 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted { 1519 foreach (ref t; toks) { 1520 if (t.location.offset >= sr.end) { 1521 if (t.kind == kind) { 1522 return t.extent.end.offset; 1523 } 1524 break; 1525 } 1526 } 1527 1528 return sr.end; 1529 } 1530 1531 bool isConstExpr(const Cursor c) @trusted { 1532 import dextool.clang_extensions; 1533 1534 return dex_isPotentialConstExpr(c); 1535 } 1536 1537 /// Locations that should not be mutated with scheman 1538 struct Blacklist { 1539 import clang.TranslationUnit : clangTranslationUnit = TranslationUnit; 1540 import dextool.plugin.mutate.backend.analyze.utility : Index; 1541 1542 clangTranslationUnit rootTu; 1543 bool[size_t] cache_; 1544 Index!string macros; 1545 1546 this(const Cursor root) { 1547 rootTu = root.translationUnit; 1548 1549 Interval[][string] macros; 1550 1551 foreach (c, parent; root.all) { 1552 if (!c.kind.among(CXCursorKind.macroExpansion, 1553 CXCursorKind.macroDefinition) || c.isMacroBuiltin) 1554 continue; 1555 add(c, macros); 1556 } 1557 1558 foreach (k; macros.byKey) 1559 macros[k] = macros[k].sort.array; 1560 1561 this.macros = Index!string(macros); 1562 } 1563 1564 static void add(ref const Cursor c, ref Interval[][string] idx) { 1565 const file = c.location.path; 1566 if (file.empty) 1567 return; 1568 const e = c.extent; 1569 const interval = Interval(e.start.offset, e.end.offset); 1570 1571 auto absFile = AbsolutePath(file); 1572 if (auto v = absFile in idx) { 1573 (*v) ~= interval; 1574 } else { 1575 idx[absFile.toString] = [interval]; 1576 } 1577 } 1578 1579 bool isBlacklist(const Cursor cursor, const analyze.Location l) @trusted { 1580 import dextool.clang_extensions; 1581 import clang.c.Index; 1582 1583 bool clangPass() { 1584 if (!cursor.isValid) 1585 return false; 1586 1587 auto hb = l.file.toHash + l.interval.begin; 1588 if (auto v = hb in cache_) 1589 return *v; 1590 auto he = l.file.toHash + l.interval.end; 1591 if (auto v = he in cache_) 1592 return *v; 1593 1594 auto file = cursor.location.file; 1595 if (!file.isValid) 1596 return false; 1597 1598 auto cxLoc = clang_getLocationForOffset(rootTu, file, l.interval.begin); 1599 if (cxLoc is CXSourceLocation.init) 1600 return false; 1601 auto res = dex_isAnyMacro(cxLoc); 1602 cache_[hb] = res; 1603 if (res) 1604 return true; 1605 1606 cxLoc = clang_getLocationForOffset(rootTu, file, l.interval.end); 1607 if (cxLoc is CXSourceLocation.init) 1608 return false; 1609 res = dex_isAnyMacro(cxLoc); 1610 cache_[he] = res; 1611 1612 return res; 1613 } 1614 1615 return clangPass() || macros.overlap(l.file.toString, l.interval); 1616 } 1617 }