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