1 /** 2 Copyright: Copyright (c) 2017, 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.visitor; 11 12 @safe: 13 14 public import dextool.clang_extensions : ValueKind; 15 import logger = std.experimental.logger; 16 17 import cpptooling.analyzer.clang.ast : Visitor; 18 import dextool.type : AbsolutePath, Path, FileName; 19 20 // these imports are used in visitors. They are here to avoid cluttering the 21 // individual visitors with a wall of text of imports. 22 import clang.Cursor : Cursor; 23 import clang.SourceLocation : SourceLocation; 24 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 25 import dextool.plugin.mutate.backend.database : MutationPointEntry; 26 import dextool.plugin.mutate.backend.interface_ : ValidateLoc; 27 import dextool.plugin.mutate.backend.type : MutationPoint, SourceLoc, 28 OpTypeInfo; 29 30 /// Contain a visitor and the data. 31 struct VisitorResult { 32 const(MutationPointEntry[]) mutationPoints() { 33 return result.mutationPoints; 34 } 35 36 Path[] mutationPointFiles() @trusted { 37 return result.mutationPointFiles; 38 } 39 40 ExtendedVisitor visitor; 41 42 private: 43 ValidateLoc validateLoc; 44 AnalyzeResult result; 45 Transform transf; 46 EnumCache enum_cache; 47 } 48 49 /** Construct and configure a visitor to analyze a clang AST for mutations. 50 * 51 * Params: 52 * val_loc_ = queried by the visitor with paths for the AST nodes to determine 53 * if they should be analyzed. 54 */ 55 VisitorResult makeRootVisitor(ValidateLoc val_loc_) { 56 typeof(return) rval; 57 rval.validateLoc = val_loc_; 58 rval.result = new AnalyzeResult; 59 rval.transf = new Transform(rval.result, val_loc_); 60 rval.enum_cache = new EnumCache; 61 rval.visitor = new BaseVisitor(rval.transf, rval.enum_cache); 62 63 import dextool.clang_extensions : OpKind; 64 import dextool.plugin.mutate.backend.utility : stmtDelMutations, 65 absMutations, uoiLvalueMutations, uoiRvalueMutations, isDcc, 66 dccBranchMutations, dccCaseMutations, dcrCaseMutations; 67 68 //rval.transf.stmtCallback ~= () => stmtDelMutations; 69 rval.transf.funcCallCallback ~= () => stmtDelMutations; 70 71 rval.transf.unaryInjectCallback ~= (ValueKind k) => absMutations; 72 rval.transf.binaryOpExprCallback ~= (OpKind k) => absMutations; 73 74 rval.transf.unaryInjectCallback ~= (ValueKind k) => k == ValueKind.lvalue 75 ? uoiLvalueMutations : uoiRvalueMutations; 76 77 rval.transf.branchCondCallback ~= () => dccBranchMutations; 78 rval.transf.binaryOpExprCallback ~= (OpKind k) { 79 return k in isDcc ? dccBranchMutations : null; 80 }; 81 82 rval.transf.caseSubStmtCallback ~= () => dccCaseMutations; 83 rval.transf.caseStmtCallback ~= () => dcrCaseMutations; 84 85 import std.algorithm : map; 86 import std.array : array; 87 import dextool.plugin.mutate.backend.type : Mutation; 88 import dextool.plugin.mutate.backend.utility : isLcr, lcrMutations, isAor, 89 aorMutations, isAorAssign, aorAssignMutations, isRor, rorMutations, 90 isCor, corOpMutations, corExprMutations, isLcrb, isLcrbAssign, 91 lcrbMutations, lcrbAssignMutations; 92 93 // TODO refactor so array() can be removed. It is an unnecessary allocation 94 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 95 if (auto v = k in isLcr) 96 return lcrMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 97 else 98 return null; 99 }; 100 101 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 102 if (auto v = k in isLcrb) { 103 return lcrbMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 104 } else 105 return null; 106 }; 107 rval.transf.assignOpOpCallback ~= (OpKind k) { 108 if (auto v = k in isLcrbAssign) 109 return lcrbAssignMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 110 else 111 return null; 112 }; 113 114 rval.transf.assignOpOpCallback ~= (OpKind k) { 115 if (auto v = k in isAorAssign) 116 return aorAssignMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 117 else 118 return null; 119 }; 120 121 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 122 if (auto v = k in isAor) 123 return aorMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 124 else 125 return null; 126 }; 127 rval.transf.assignOpOpCallback ~= (OpKind k) { 128 if (auto v = k in isAorAssign) 129 return aorAssignMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 130 else 131 return null; 132 }; 133 134 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo tyi) { 135 if (k in isRor) 136 return rorMutations(k, tyi).op.map!(a => cast(Mutation.Kind) a).array(); 137 else 138 return null; 139 }; 140 rval.transf.binaryOpExprCallback ~= (OpKind k) { 141 if (k in isRor) 142 return rorMutations(k, OpTypeInfo.none).expr; 143 else 144 return null; 145 }; 146 147 //rval.transf.binaryOpLhsCallback ~= (OpKind k) => uoiLvalueMutations; 148 //rval.transf.binaryOpRhsCallback ~= (OpKind k) => uoiLvalueMutations; 149 150 rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor 151 ? [Mutation.Kind.corRhs] : null; 152 rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor 153 ? [Mutation.Kind.corLhs] : null; 154 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 155 if (auto v = k in isCor) 156 return corOpMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 157 else 158 return null; 159 }; 160 rval.transf.binaryOpExprCallback ~= (OpKind k) { 161 if (auto v = k in isCor) 162 return corExprMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 163 else 164 return null; 165 }; 166 167 return rval; 168 } 169 170 private: 171 172 /** Find all mutation points that affect a whole expression. 173 * 174 * TODO change the name of the class. It is more than just an expression 175 * visitor. 176 * 177 * # Usage of kind_stack 178 * All usage of the kind_stack shall be documented here. 179 * - track assignments to avoid generating unary insert operators for the LHS. 180 */ 181 class BaseVisitor : ExtendedVisitor { 182 import clang.c.Index : CXCursorKind; 183 import cpptooling.analyzer.clang.ast; 184 import dextool.clang_extensions : getExprOperator, OpKind; 185 186 alias visit = ExtendedVisitor.visit; 187 188 mixin generateIndentIncrDecr; 189 190 private Transform transf; 191 private EnumCache enum_cache; 192 193 /// Track the visited nodes 194 private Stack!CXCursorKind kind_stack; 195 196 /** 197 * Params: 198 * restrict = only analyze files starting with this path 199 */ 200 this(Transform transf, EnumCache ec, const uint indent = 0) nothrow { 201 this.transf = transf; 202 this.indent = indent; 203 this.enum_cache = ec; 204 } 205 206 override void visit(const(TranslationUnit) v) { 207 mixin(mixinNodeLog!()); 208 v.accept(this); 209 } 210 211 override void visit(const(Attribute) v) { 212 mixin(mixinNodeLog!()); 213 v.accept(this); 214 } 215 216 override void visit(const(Declaration) v) { 217 mixin(mixinNodeLog!()); 218 v.accept(this); 219 } 220 221 override void visit(const EnumDecl v) @trusted { 222 mixin(mixinNodeLog!()); 223 import std.typecons : scoped; 224 225 auto vis = scoped!EnumVisitor(indent); 226 v.accept(vis); 227 228 if (!vis.entry.isNull) { 229 enum_cache.put(EnumCache.USR(v.cursor.usr), vis.entry); 230 } 231 232 debug logger.tracef("%s", enum_cache); 233 } 234 235 override void visit(const(FunctionDecl) v) { 236 mixin(mixinNodeLog!()); 237 v.accept(this); 238 } 239 240 override void visit(const(Directive) v) { 241 mixin(mixinNodeLog!()); 242 v.accept(this); 243 } 244 245 override void visit(const(Expression) v) { 246 mixin(mixinNodeLog!()); 247 v.accept(this); 248 } 249 250 override void visit(const(DeclRefExpr) v) { 251 mixin(mixinNodeLog!()); 252 253 if (kind_stack.hasValue(CXCursorKind.compoundAssignOperator).isNull) 254 transf.unaryInject(v.cursor); 255 v.accept(this); 256 } 257 258 override void visit(const IntegerLiteral v) { 259 mixin(mixinNodeLog!()); 260 //transf.unaryInject(v.cursor); 261 v.accept(this); 262 } 263 264 override void visit(const(CallExpr) v) { 265 mixin(mixinNodeLog!()); 266 transf.binaryOp(v.cursor, enum_cache); 267 transf.funcCall(v.cursor); 268 v.accept(this); 269 } 270 271 override void visit(const(BreakStmt) v) { 272 mixin(mixinNodeLog!()); 273 v.accept(this); 274 } 275 276 override void visit(const(BinaryOperator) v) { 277 mixin(mixinNodeLog!()); 278 transf.binaryOp(v.cursor, enum_cache); 279 v.accept(this); 280 } 281 282 override void visit(const(CompoundAssignOperator) v) { 283 mixin(mixinNodeLog!()); 284 mixin(pushPopStack("kind_stack", "v.cursor.kind")); 285 transf.assignOp(v.cursor, enum_cache); 286 v.accept(this); 287 } 288 289 override void visit(const(Preprocessor) v) { 290 mixin(mixinNodeLog!()); 291 v.accept(this); 292 } 293 294 override void visit(const(Reference) v) { 295 mixin(mixinNodeLog!()); 296 v.accept(this); 297 } 298 299 override void visit(const(Statement) v) { 300 mixin(mixinNodeLog!()); 301 v.accept(this); 302 } 303 304 // trusted: the scope allocated visitor do not escape the method 305 override void visit(const IfStmt v) @trusted { 306 mixin(mixinNodeLog!()); 307 import std.typecons : scoped; 308 309 auto clause = scoped!IfStmtClauseVisitor(transf, enum_cache, indent); 310 auto ifstmt = scoped!IfStmtVisitor(transf, enum_cache, this, clause, indent); 311 accept(v, cast(IfStmtVisitor) ifstmt); 312 } 313 314 override void visit(const CaseStmt v) { 315 mixin(mixinNodeLog!()); 316 transf.caseStmt(v.cursor); 317 v.accept(this); 318 } 319 } 320 321 /** Inject code to validate and check the location of a cursor. 322 * 323 * Params: 324 * cursor = code snippet to get the cursor from a variable accessable in the method. 325 */ 326 string makeAndCheckLocation(string cursor) { 327 import std.format : format; 328 329 return format(q{auto loc = %s.location; 330 if (!val_loc.shouldAnalyze(loc.path)) { 331 return; 332 }}, cursor); 333 } 334 335 struct Stack(T) { 336 import std.typecons : Nullable; 337 import std.container : Array; 338 339 Array!T arr; 340 341 // trusted: as long as arr do not escape the instance 342 void put(T a) @trusted { 343 arr.insertBack(a); 344 } 345 346 // trusted: as long as arr do not escape the instance 347 void pop() @trusted { 348 arr.removeBack; 349 } 350 351 /** Check from the top of the stack if v is in the stack 352 * 353 * trusted: the slice never escape the method and v never affects the 354 * slicing thus the memory. 355 */ 356 Nullable!size_t hasValue(T v) @trusted { 357 import std.range : retro, enumerate; 358 359 foreach (idx, a; arr[].retro.enumerate) { 360 if (a == v) 361 return typeof(return)(idx); 362 } 363 364 return typeof(return)(); 365 } 366 } 367 368 /// A mixin string that pushes `value` to `instance` and pops on scope exit. 369 string pushPopStack(string instance, string value) { 370 import std.format : format; 371 372 return format(q{%s.put(%s); scope(exit) %s.pop;}, instance, value, instance); 373 } 374 375 /** Transform the AST to mutation poins and mutations. 376 * 377 * The intent is to decouple the AST visitor from the transformation logic. 378 * 379 * TODO reduce code duplication. Do it after the first batch of mutations are 380 * implemented. 381 */ 382 class Transform { 383 import std.algorithm : map; 384 import std.array : array; 385 import dextool.clang_extensions : OpKind; 386 import dextool.plugin.mutate.backend.type : Offset; 387 import dextool.plugin.mutate.backend.utility; 388 389 /// Any statement 390 alias StatementEvent = Mutation.Kind[]delegate(); 391 alias FunctionCallEvent = Mutation.Kind[]delegate(); 392 StatementEvent[] stmtCallback; 393 FunctionCallEvent[] funcCallCallback; 394 395 /// Any statement that should have a unary operator inserted before/after 396 alias UnaryInjectEvent = Mutation.Kind[]delegate(ValueKind); 397 UnaryInjectEvent[] unaryInjectCallback; 398 399 /// Any binary operator 400 alias BinaryOpEvent = Mutation.Kind[]delegate(OpKind kind, OpTypeInfo tyi); 401 alias BinaryExprEvent = Mutation.Kind[]delegate(OpKind kind); 402 BinaryOpEvent[] binaryOpOpCallback; 403 BinaryOpEvent[] binaryOpLhsCallback; 404 BinaryOpEvent[] binaryOpRhsCallback; 405 BinaryExprEvent[] binaryOpExprCallback; 406 407 /// Assignment operators 408 alias AssignOpKindEvent = Mutation.Kind[]delegate(OpKind kind); 409 alias AssignOpEvent = Mutation.Kind[]delegate(); 410 AssignOpKindEvent[] assignOpOpCallback; 411 AssignOpEvent[] assignOpLhsCallback; 412 AssignOpEvent[] assignOpRhsCallback; 413 414 /// Branch condition expression such as those in an if stmt 415 alias BranchEvent = Mutation.Kind[]delegate(); 416 BranchEvent[] branchCondCallback; 417 BranchEvent[] branchClauseCallback; 418 BranchEvent[] branchThenCallback; 419 BranchEvent[] branchElseCallback; 420 421 /// Switch condition 422 alias CaseEvent = Mutation.Kind[]delegate(); 423 /// the statement after the `case 2:` until the next one 424 CaseEvent[] caseStmtCallback; 425 CaseEvent[] caseSubStmtCallback; 426 427 private AnalyzeResult result; 428 private ValidateLoc val_loc; 429 430 this(AnalyzeResult res, ValidateLoc vloc) { 431 this.result = res; 432 this.val_loc = vloc; 433 } 434 435 private void noArgCallback(T)(const Cursor c, T callbacks) { 436 mixin(makeAndCheckLocation("c")); 437 mixin(mixinPath); 438 439 auto sr = c.extent; 440 auto offs = Offset(sr.start.offset, sr.end.offset); 441 442 auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line, loc.column)); 443 foreach (cb; callbacks) { 444 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 445 } 446 447 result.put(p); 448 } 449 450 void statement(T)(const(T) v) { 451 mixin(makeAndCheckLocation("v.cursor")); 452 mixin(mixinPath); 453 454 auto offs = calcOffset(v); 455 auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line, loc.column)); 456 foreach (cb; stmtCallback) { 457 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 458 } 459 460 result.put(p); 461 } 462 463 void funcCall(const Cursor c) { 464 noArgCallback(c, funcCallCallback); 465 } 466 467 void unaryInject(const(Cursor) c) { 468 import dextool.clang_extensions : exprValueKind, getUnderlyingExprNode; 469 470 // nodes from getOperator can be invalid. 471 if (!c.isValid) 472 return; 473 474 mixin(makeAndCheckLocation("c")); 475 mixin(mixinPath); 476 477 auto sr = c.extent; 478 auto offs = Offset(sr.start.offset, sr.end.offset); 479 480 const auto kind = exprValueKind(getUnderlyingExprNode(c)); 481 482 auto p = MutationPointEntry(MutationPoint(offs, null), path, 483 SourceLoc(loc.line, loc.column)); 484 foreach (cb; unaryInjectCallback) { 485 p.mp.mutations ~= cb(kind).map!(a => Mutation(a)).array(); 486 } 487 488 result.put(p); 489 } 490 491 void binaryOp(const(Cursor) c, const EnumCache ec) { 492 mixin(makeAndCheckLocation("c")); 493 mixin(mixinPath); 494 495 auto mp = getOperatorMP(c, ec); 496 if (!mp.isValid) 497 return; 498 499 binaryOpInternal(mp); 500 501 result.put(mp.lhs); 502 result.put(mp.rhs); 503 result.put(mp.op); 504 result.put(mp.expr); 505 } 506 507 private void binaryOpInternal(ref OperatorMP mp) { 508 foreach (cb; binaryOpOpCallback) 509 mp.op.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array(); 510 foreach (cb; binaryOpLhsCallback) 511 mp.lhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array(); 512 foreach (cb; binaryOpRhsCallback) 513 mp.rhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array(); 514 foreach (cb; binaryOpExprCallback) 515 mp.expr.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array(); 516 } 517 518 void assignOp(const Cursor c, const EnumCache ec) { 519 mixin(makeAndCheckLocation("c")); 520 mixin(mixinPath); 521 522 auto mp = getOperatorMP(c, ec); 523 if (!mp.isValid) 524 return; 525 526 foreach (cb; assignOpOpCallback) 527 mp.op.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array(); 528 529 foreach (cb; assignOpLhsCallback) 530 mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 531 532 foreach (cb; assignOpRhsCallback) 533 mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 534 535 result.put(mp.lhs); 536 result.put(mp.rhs); 537 result.put(mp.op); 538 } 539 540 /** Callback for the whole condition in a if statement. 541 */ 542 void branchCond(const Cursor c, const EnumCache ec) { 543 mixin(makeAndCheckLocation("c")); 544 mixin(mixinPath); 545 546 auto mp = getOperatorMP(c, ec); 547 if (mp.isValid) { 548 binaryOpInternal(mp); 549 550 foreach (cb; branchClauseCallback) { 551 mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 552 } 553 554 result.put(mp.lhs); 555 result.put(mp.rhs); 556 result.put(mp.op); 557 result.put(mp.expr); 558 } else { 559 auto sr = c.extent; 560 auto offs = Offset(sr.start.offset, sr.end.offset); 561 auto p = MutationPointEntry(MutationPoint(offs, null), path, 562 SourceLoc(loc.line, loc.column)); 563 564 foreach (cb; branchCondCallback) { 565 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 566 } 567 568 result.put(p); 569 } 570 } 571 572 /** Callback for the individual clauses in an if statement. 573 */ 574 void branchClause(const Cursor c, const EnumCache ec) { 575 mixin(makeAndCheckLocation("c")); 576 mixin(mixinPath); 577 578 auto mp = getOperatorMP(c, ec); 579 if (!mp.isValid) 580 return; 581 582 binaryOpInternal(mp); 583 584 foreach (cb; branchClauseCallback) { 585 mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 586 } 587 588 result.put(mp.lhs); 589 result.put(mp.rhs); 590 result.put(mp.op); 591 result.put(mp.expr); 592 } 593 594 void branchThen(const Cursor c) { 595 mixin(makeAndCheckLocation("c")); 596 mixin(mixinPath); 597 598 auto sr = c.extent; 599 auto offs = Offset(sr.start.offset, sr.end.offset); 600 601 auto p = MutationPointEntry(MutationPoint(offs, null), path, 602 SourceLoc(loc.line, loc.column)); 603 604 foreach (cb; branchThenCallback) { 605 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 606 } 607 608 result.put(p); 609 } 610 611 void branchElse(const Cursor c) { 612 mixin(makeAndCheckLocation("c")); 613 mixin(mixinPath); 614 615 auto sr = c.extent; 616 auto offs = Offset(sr.start.offset, sr.end.offset); 617 618 auto p = MutationPointEntry(MutationPoint(offs, null), path, 619 SourceLoc(loc.line, loc.column)); 620 621 foreach (cb; branchElseCallback) { 622 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 623 } 624 625 result.put(p); 626 } 627 628 void caseStmt(const Cursor c) { 629 import clang.c.Index : CXTokenKind, CXCursorKind; 630 import dextool.clang_extensions : getCaseStmt; 631 632 auto mp = getCaseStmt(c); 633 if (!mp.isValid) 634 return; 635 636 mixin(makeAndCheckLocation("c")); 637 mixin(mixinPath); 638 639 auto sr = mp.subStmt.extent; 640 auto offs = Offset(sr.start.offset, sr.end.offset); 641 if (mp.subStmt.kind == CXCursorKind.caseStmt) { 642 // a case statement with fallthrough. the only point to inject a bomb is directly efter the semicolon 643 offs.begin = mp.colonLocation.offset + 1; 644 offs.end = offs.begin; 645 } else if (mp.subStmt.kind != CXCursorKind.compoundStmt) { 646 offs.end = findTokenOffset(c.translationUnit.cursor.tokens, offs, 647 CXTokenKind.punctuation); 648 } 649 650 void subStmt() { 651 auto p = MutationPointEntry(MutationPoint(offs), path, 652 SourceLoc(loc.line, loc.column)); 653 654 foreach (cb; caseSubStmtCallback) { 655 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 656 } 657 658 result.put(p); 659 } 660 661 void stmt() { 662 auto stmt_sr = c.extent; 663 // reuse the end from offs because it also covers either only the fallthrough OR also the end semicolon 664 auto stmt_offs = Offset(stmt_sr.start.offset, offs.end); 665 666 auto p = MutationPointEntry(MutationPoint(stmt_offs), path, 667 SourceLoc(loc.line, loc.column)); 668 669 foreach (cb; caseStmtCallback) { 670 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 671 } 672 673 result.put(p); 674 } 675 676 stmt(); 677 subStmt(); 678 } 679 680 private static struct OperatorMP { 681 bool isValid; 682 Operator rawOp; 683 /// the left side INCLUDING the operator 684 MutationPointEntry lhs; 685 /// the right side INCLUDING the operator 686 MutationPointEntry rhs; 687 /// the operator 688 MutationPointEntry op; 689 /// the whole operator expression 690 MutationPointEntry expr; 691 /// Type information of the types on the sides of the operator 692 OpTypeInfo typeInfo; 693 } 694 695 OperatorMP getOperatorMP(const(Cursor) c, const EnumCache ec) { 696 import dextool.clang_extensions : getExprOperator; 697 698 typeof(return) rval; 699 700 auto op_ = getExprOperator(c); 701 if (!op_.isValid) 702 return rval; 703 704 rval.rawOp = op_; 705 706 SourceLocation loc = op_.cursor.location; 707 708 auto path = loc.path.Path; 709 if (path is null) 710 return rval; 711 result.put(path); 712 713 rval.isValid = op_.isValid; 714 715 void sidesPoint() { 716 auto sides = op_.sides; 717 auto opsr = op_.location.spelling; 718 auto sr = op_.cursor.extent; 719 720 if (sides.rhs.isValid) { 721 auto offs_rhs = Offset(opsr.offset, sr.end.offset); 722 rval.rhs = MutationPointEntry(MutationPoint(offs_rhs, null), path, 723 SourceLoc(sides.rhs.location.line, sides.rhs.location.column)); 724 } 725 726 if (sides.lhs.isValid) { 727 auto offs_lhs = Offset(sr.start.offset, cast(uint)(opsr.offset + op_.length)); 728 rval.lhs = MutationPointEntry(MutationPoint(offs_lhs, null), path, 729 SourceLoc(sides.lhs.location.line, sides.lhs.location.column)); 730 } 731 } 732 733 void opPoint() { 734 auto sr = op_.location.spelling; 735 auto offs = Offset(sr.offset, cast(uint)(sr.offset + op_.length)); 736 rval.op = MutationPointEntry(MutationPoint(offs, null), path, 737 SourceLoc(op_.location.line, op_.location.column)); 738 739 auto sides = op_.sides; 740 rval.typeInfo = deriveOpTypeInfo(sides.lhs, sides.rhs, ec); 741 } 742 743 void exprPoint() { 744 auto sloc = SourceLoc(loc.line, loc.column); 745 746 // TODO this gives a slightly different result from calling getUnderlyingExprNode on v.cursor. 747 // Investigate which one is the "correct" way. 748 auto sr = op_.cursor.extent; 749 auto offs_expr = Offset(sr.start.offset, sr.end.offset); 750 rval.expr = MutationPointEntry(MutationPoint(offs_expr, null), path, sloc); 751 } 752 753 sidesPoint(); 754 opPoint(); 755 exprPoint(); 756 757 return rval; 758 } 759 760 // expects a makeAndCheckLocation exists before. 761 private static string mixinPath() { 762 // a bug in getExprOperator makes the path for a ++ which is overloaded 763 // is null. 764 return q{ 765 auto path = loc.path.Path; 766 if (path is null) 767 return; 768 result.put(path); 769 }; 770 } 771 } 772 773 OpTypeInfo deriveOpTypeInfo(const Cursor lhs_, const Cursor rhs_, const EnumCache ec) @safe { 774 import std.meta : AliasSeq; 775 import std.algorithm : among; 776 import clang.c.Index : CXTypeKind, CXCursorKind; 777 import clang.Type : Type; 778 import dextool.clang_extensions : getUnderlyingExprNode; 779 780 auto lhs = Cursor(getUnderlyingExprNode(lhs_)); 781 auto rhs = Cursor(getUnderlyingExprNode(rhs_)); 782 783 if (!lhs.isValid || !rhs.isValid) 784 return OpTypeInfo.none; 785 786 auto lhs_ty = lhs.type.canonicalType; 787 auto rhs_ty = rhs.type.canonicalType; 788 789 if (!lhs_ty.isValid || !rhs_ty.isValid) 790 return OpTypeInfo.none; 791 792 auto floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_, CXTypeKind.longDouble); 793 auto pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer, 794 CXTypeKind.blockPointer, CXTypeKind.memberPointer); 795 auto boolCategory = AliasSeq!(CXTypeKind.bool_); 796 797 if (lhs_ty.isEnum && rhs_ty.isEnum) { 798 auto lhs_ref = lhs.referenced; 799 auto rhs_ref = rhs.referenced; 800 if (!lhs_ref.isValid || !rhs_ref.isValid) 801 return OpTypeInfo.none; 802 803 auto lhs_usr = lhs_ref.usr; 804 auto rhs_usr = rhs_ref.usr; 805 806 debug logger.tracef("lhs:%s:%s rhs:%s:%s", lhs_usr, lhs_ref.kind, rhs_usr, rhs_ref.kind); 807 808 if (lhs_usr == rhs_usr) { 809 return OpTypeInfo.enumLhsRhsIsSame; 810 } else if (lhs_ref.kind == CXCursorKind.enumConstantDecl) { 811 auto lhs_ty_decl = lhs_ty.declaration; 812 if (!lhs_ty_decl.isValid) 813 return OpTypeInfo.none; 814 815 auto p = ec.position(EnumCache.USR(lhs_ty_decl.usr), EnumCache.USR(lhs_usr)); 816 if (p == EnumCache.Query.isMin) 817 return OpTypeInfo.enumLhsIsMin; 818 else if (p == EnumCache.Query.isMax) 819 return OpTypeInfo.enumLhsIsMax; 820 return OpTypeInfo.none; 821 } else if (rhs_ref.kind == CXCursorKind.enumConstantDecl) { 822 auto rhs_ty_decl = rhs_ty.declaration; 823 if (!rhs_ty_decl.isValid) 824 return OpTypeInfo.none; 825 826 auto p = ec.position(EnumCache.USR(rhs_ty_decl.usr), EnumCache.USR(rhs_usr)); 827 if (p == EnumCache.Query.isMin) 828 return OpTypeInfo.enumRhsIsMin; 829 else if (p == EnumCache.Query.isMax) 830 return OpTypeInfo.enumRhsIsMax; 831 return OpTypeInfo.none; 832 } 833 834 return OpTypeInfo.none; 835 } else if (lhs_ty.kind.among(floatCategory) && rhs_ty.kind.among(floatCategory)) { 836 return OpTypeInfo.floatingPoint; 837 } else if (lhs_ty.kind.among(pointerCategory) || rhs_ty.kind.among(pointerCategory)) { 838 return OpTypeInfo.pointer; 839 } else if (lhs_ty.kind.among(boolCategory) && rhs_ty.kind.among(boolCategory)) { 840 return OpTypeInfo.boolean; 841 } 842 843 return OpTypeInfo.none; 844 } 845 846 import dextool.clang_extensions : Operator; 847 848 import clang.c.Index : CXTokenKind; 849 import dextool.plugin.mutate.backend.type : Offset; 850 851 class AnalyzeResult { 852 import std.array : Appender; 853 854 Appender!(MutationPointEntry[]) exprs; 855 bool[Path] files; 856 857 void put(MutationPointEntry a) { 858 exprs.put(a); 859 } 860 861 void put(Path a) { 862 files[a] = true; 863 } 864 865 const(MutationPointEntry[]) mutationPoints() { 866 return exprs.data; 867 } 868 869 Path[] mutationPointFiles() @trusted { 870 import std.array : array; 871 872 return files.byKey.array(); 873 } 874 } 875 876 // trusted: the tokens do not escape this function. 877 Offset calcOffset(T)(const(T) v) @trusted { 878 import clang.c.Index : CXTokenKind; 879 import cpptooling.analyzer.clang.ast; 880 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 881 882 Offset rval; 883 884 auto sr = v.cursor.extent; 885 rval = Offset(sr.start.offset, sr.end.offset); 886 887 static if (is(T == CallExpr) || is(T == BreakStmt)) { 888 import clang.Token; 889 890 // TODO inactivated because it leaks memory. Unable to run on sqlite3. 891 892 // TODO this is extremly inefficient. change to a more localized cursor 893 // or even better. Get the tokens at the end. 894 //auto arg = v.cursor.translationUnit.cursor; 895 // 896 //rval.end = findTokenOffset(arg.tokens, rval, CXTokenKind.punctuation, ";"); 897 } 898 899 return rval; 900 } 901 /// trusted: trusting the impl in clang. 902 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind, string spelling) @trusted { 903 foreach (ref t; toks) { 904 if (t.location.offset >= sr.end) { 905 if (t.kind == kind && t.spelling == spelling) { 906 return t.extent.end.offset; 907 } 908 break; 909 } 910 } 911 912 return sr.end; 913 } 914 915 /// trusted: trusting the impl in clang. 916 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted { 917 foreach (ref t; toks) { 918 if (t.location.offset >= sr.end) { 919 if (t.kind == kind) { 920 return t.extent.end.offset; 921 } 922 break; 923 } 924 } 925 926 return sr.end; 927 } 928 929 final class IfStmtVisitor : ExtendedVisitor { 930 import cpptooling.analyzer.clang.ast; 931 932 alias visit = ExtendedVisitor.visit; 933 934 mixin generateIndentIncrDecr; 935 936 private { 937 Transform transf; 938 EnumCache enum_cache; 939 ExtendedVisitor sub_visitor; 940 ExtendedVisitor cond_visitor; 941 } 942 943 /** 944 * Params: 945 * sub_visitor = visitor used for recursive analyze. 946 */ 947 this(Transform transf, EnumCache ec, ExtendedVisitor sub_visitor, 948 ExtendedVisitor cond_visitor, const uint indent) { 949 this.transf = transf; 950 this.enum_cache = ec; 951 this.sub_visitor = sub_visitor; 952 this.cond_visitor = cond_visitor; 953 this.indent = indent; 954 } 955 956 override void visit(const IfStmtCond v) { 957 mixin(mixinNodeLog!()); 958 transf.branchCond(v.cursor, enum_cache); 959 v.accept(cond_visitor); 960 } 961 962 override void visit(const IfStmtThen v) { 963 mixin(mixinNodeLog!()); 964 transf.branchThen(v.cursor); 965 v.accept(sub_visitor); 966 } 967 968 override void visit(const IfStmtElse v) { 969 mixin(mixinNodeLog!()); 970 transf.branchElse(v.cursor); 971 v.accept(sub_visitor); 972 } 973 } 974 975 /// Visit all clauses in the condition of a statement. 976 final class IfStmtClauseVisitor : BaseVisitor { 977 import cpptooling.analyzer.clang.ast; 978 979 alias visit = BaseVisitor.visit; 980 981 /** 982 * Params: 983 * transf = ? 984 * sub_visitor = visitor used for recursive analyze. 985 */ 986 this(Transform transf, EnumCache ec, const uint indent) { 987 super(transf, ec, indent); 988 } 989 990 override void visit(const(BinaryOperator) v) { 991 mixin(mixinNodeLog!()); 992 transf.branchClause(v.cursor, enum_cache); 993 v.accept(this); 994 } 995 } 996 997 /// Cache enums that are found in the AST for later lookup 998 class EnumCache { 999 static struct USR { 1000 string payload; 1001 alias payload this; 1002 } 1003 1004 static struct Entry { 1005 long minValue; 1006 USR[] minId; 1007 1008 long maxValue; 1009 USR[] maxId; 1010 } 1011 1012 enum Query { 1013 unknown, 1014 isMin, 1015 isMiddle, 1016 isMax, 1017 } 1018 1019 Entry[USR] cache; 1020 1021 void put(USR u, Entry e) { 1022 cache[u] = e; 1023 } 1024 1025 /// Check what position the enum const declaration have in enum declaration. 1026 Query position(USR enum_, USR enum_const_decl) const { 1027 import std.algorithm : canFind; 1028 1029 if (auto v = enum_ in cache) { 1030 if ((*v).minId.canFind(enum_const_decl)) 1031 return Query.isMin; 1032 else if ((*v).maxId.canFind(enum_const_decl)) 1033 return Query.isMax; 1034 return Query.isMiddle; 1035 } 1036 1037 return Query.unknown; 1038 } 1039 1040 import std.format : FormatSpec; 1041 1042 // trusted: remove when upgrading to dmd-FE 2.078.1 1043 void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) @trusted const { 1044 import std.format : formatValue, formattedWrite; 1045 import std.range.primitives : put; 1046 1047 foreach (kv; cache.byKeyValue) { 1048 formattedWrite(w, "enum:%s min:%s:%s max:%s:%s", kv.key, 1049 kv.value.minValue, kv.value.minId, kv.value.maxValue, kv.value.maxId); 1050 } 1051 } 1052 1053 // remove this function when upgrading to dmd-FE 2.078.1 1054 override string toString() @trusted pure const { 1055 import std.exception : assumeUnique; 1056 import std.format : FormatSpec; 1057 1058 char[] buf; 1059 buf.reserve(100); 1060 auto fmt = FormatSpec!char("%s"); 1061 toString((const(char)[] s) { buf ~= s; }, fmt); 1062 auto trustedUnique(T)(T t) @trusted { 1063 return assumeUnique(t); 1064 } 1065 1066 return trustedUnique(buf); 1067 } 1068 } 1069 1070 final class EnumVisitor : Visitor { 1071 import std.typecons : Nullable; 1072 import cpptooling.analyzer.clang.ast; 1073 1074 alias visit = Visitor.visit; 1075 1076 mixin generateIndentIncrDecr; 1077 1078 Nullable!(EnumCache.Entry) entry; 1079 1080 this(const uint indent) { 1081 this.indent = indent; 1082 } 1083 1084 override void visit(const EnumDecl v) { 1085 mixin(mixinNodeLog!()); 1086 v.accept(this); 1087 } 1088 1089 override void visit(const EnumConstantDecl v) @trusted { 1090 mixin(mixinNodeLog!()); 1091 1092 Cursor c = v.cursor; 1093 long value = c.enum_.signedValue; 1094 1095 if (entry.isNull) { 1096 entry = EnumCache.Entry(value, [EnumCache.USR(c.usr)], value, [EnumCache.USR(c.usr)]); 1097 } else if (value < entry.minValue) { 1098 entry.minValue = value; 1099 entry.minId = [EnumCache.USR(c.usr)]; 1100 } else if (value == entry.minValue) { 1101 entry.minId ~= EnumCache.USR(c.usr); 1102 } else if (value > entry.maxValue) { 1103 entry.maxValue = value; 1104 entry.maxId = [EnumCache.USR(c.usr)]; 1105 } else if (value == entry.maxValue) { 1106 entry.maxId ~= EnumCache.USR(c.usr); 1107 } 1108 1109 v.accept(this); 1110 } 1111 } 1112 1113 // Intende to move this code to clang_extensions if this approach to extending the clang AST works well. 1114 // --- BEGIN 1115 1116 static import dextool.clang_extensions; 1117 1118 static import cpptooling.analyzer.clang.ast; 1119 1120 class ExtendedVisitor : Visitor { 1121 import cpptooling.analyzer.clang.ast; 1122 import dextool.clang_extensions; 1123 1124 alias visit = Visitor.visit; 1125 1126 void visit(const(IfStmtInit) value) { 1127 visit(cast(const(Statement)) value); 1128 } 1129 1130 void visit(const(IfStmtCond) value) { 1131 visit(cast(const(Expression)) value); 1132 } 1133 1134 void visit(const(IfStmtThen) value) { 1135 visit(cast(const(Statement)) value); 1136 } 1137 1138 void visit(const(IfStmtElse) value) { 1139 visit(cast(const(Statement)) value); 1140 } 1141 } 1142 1143 final class IfStmtInit : cpptooling.analyzer.clang.ast.Statement { 1144 this(Cursor cursor) @safe { 1145 super(cursor); 1146 } 1147 1148 void accept(ExtendedVisitor v) @safe const { 1149 static import cpptooling.analyzer.clang.ast; 1150 1151 cpptooling.analyzer.clang.ast.accept(cursor, v); 1152 } 1153 } 1154 1155 final class IfStmtCond : cpptooling.analyzer.clang.ast.Expression { 1156 this(Cursor cursor) @safe { 1157 super(cursor); 1158 } 1159 1160 void accept(ExtendedVisitor v) @safe const { 1161 static import cpptooling.analyzer.clang.ast; 1162 1163 cpptooling.analyzer.clang.ast.accept(cursor, v); 1164 } 1165 } 1166 1167 final class IfStmtThen : cpptooling.analyzer.clang.ast.Statement { 1168 this(Cursor cursor) @safe { 1169 super(cursor); 1170 } 1171 1172 void accept(ExtendedVisitor v) @safe const { 1173 static import cpptooling.analyzer.clang.ast; 1174 1175 cpptooling.analyzer.clang.ast.accept(cursor, v); 1176 } 1177 } 1178 1179 final class IfStmtElse : cpptooling.analyzer.clang.ast.Statement { 1180 this(Cursor cursor) @safe { 1181 super(cursor); 1182 } 1183 1184 void accept(ExtendedVisitor v) @safe const { 1185 static import cpptooling.analyzer.clang.ast; 1186 1187 cpptooling.analyzer.clang.ast.accept(cursor, v); 1188 } 1189 } 1190 1191 void accept(T)(const(cpptooling.analyzer.clang.ast.IfStmt) n, T v) 1192 if (is(T : ExtendedVisitor)) { 1193 import dextool.clang_extensions; 1194 1195 auto stmt = getIfStmt(n.cursor); 1196 accept(stmt, v); 1197 } 1198 1199 void accept(T)(ref dextool.clang_extensions.IfStmt n, T v) 1200 if (is(T : ExtendedVisitor)) { 1201 import std.traits : hasMember; 1202 1203 static if (hasMember!(T, "incr")) 1204 v.incr; 1205 { 1206 if (n.init_.isValid) { 1207 auto sub = new IfStmtInit(n.init_); 1208 v.visit(sub); 1209 } 1210 } 1211 { 1212 if (n.cond.isValid) { 1213 auto sub = new IfStmtCond(n.cond); 1214 v.visit(sub); 1215 } 1216 } 1217 { 1218 if (n.then.isValid) { 1219 auto sub = new IfStmtThen(n.then); 1220 v.visit(sub); 1221 } 1222 } 1223 { 1224 if (n.else_.isValid) { 1225 auto sub = new IfStmtElse(n.else_); 1226 v.visit(sub); 1227 } 1228 } 1229 1230 static if (hasMember!(T, "decr")) 1231 v.decr; 1232 } 1233 1234 // --- END