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 The flow of information is: 11 * Visitor. Visit the AST 12 * Transform. Gets data from the AST and transforms them into an abstraction. 13 The abstractions are common points where a mutation schema would want to create mutants. 14 The registered callbacks creates mutants. 15 * AnalyzeResult. Gets the mutants and files that contains mutants. 16 * MutantResult. Takes a AnalyzeResult and transform into code mutations. 17 */ 18 module dextool.plugin.mutate.backend.analyze.visitor; 19 20 @safe: 21 22 import logger = std.experimental.logger; 23 24 public import dextool.clang_extensions : ValueKind; 25 26 import cpptooling.analyzer.clang.ast : Visitor; 27 import dextool.type : AbsolutePath, Path, FileName, DirName; 28 29 // these imports are used in visitors. They are here to avoid cluttering the 30 // individual visitors with a wall of text of imports. 31 import clang.Cursor : Cursor; 32 import clang.SourceLocation : SourceLocation; 33 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 34 import dextool.plugin.mutate.backend.analyze.internal; 35 import dextool.plugin.mutate.backend.database : MutationPointEntry, MutationPointEntry2; 36 import dextool.plugin.mutate.backend.interface_ : ValidateLoc, FilesysIO; 37 import dextool.plugin.mutate.backend.type : Language; 38 import dextool.plugin.mutate.backend.type : MutationPoint, SourceLoc, OpTypeInfo; 39 40 /// Contain a visitor and the data. 41 struct VisitorResult { 42 MutationPointEntry2[] mutationPoints() { 43 return result.entries.data; 44 } 45 46 auto mutationPointFiles() @trusted { 47 return result.files.data; 48 } 49 50 ExtendedVisitor visitor; 51 52 private: 53 ValidateLoc validateLoc; 54 AnalyzeResult result; 55 Transform transf; 56 EnumCache enum_cache; 57 } 58 59 /** Construct and configure a visitor to analyze a clang AST for mutations. 60 * 61 * Params: 62 * val_loc_ = queried by the visitor with paths for the AST nodes to determine 63 * if they should be analyzed. 64 */ 65 VisitorResult makeRootVisitor(FilesysIO fio, ValidateLoc val_loc_, TokenStream tstream, Cache cache) { 66 typeof(return) rval; 67 rval.validateLoc = val_loc_; 68 rval.result = new AnalyzeResult(fio, tstream, cache); 69 rval.transf = new Transform(rval.result, val_loc_); 70 rval.enum_cache = new EnumCache; 71 rval.visitor = new BaseVisitor(rval.transf, rval.enum_cache); 72 73 import dextool.clang_extensions : OpKind; 74 import dextool.plugin.mutate.backend.mutation_type; 75 76 //rval.transf.stmtCallback ~= () => stmtDelMutations; 77 rval.transf.assignStmtCallback ~= () => stmtDelMutations; 78 rval.transf.funcCallCallback ~= () => stmtDelMutations; 79 rval.transf.voidFuncBodyCallback ~= () => stmtDelMutations; 80 rval.transf.throwStmtCallback ~= () => stmtDelMutations; 81 82 rval.transf.unaryInjectCallback ~= (ValueKind k) => absMutations; 83 rval.transf.binaryOpExprCallback ~= (OpKind k) => absMutations; 84 85 rval.transf.unaryInjectCallback ~= (ValueKind k) => k == ValueKind.lvalue 86 ? uoiLvalueMutations : uoiRvalueMutations; 87 88 rval.transf.branchClauseCallback ~= () => dccBranchMutations; 89 rval.transf.branchCondCallback ~= () => dccBranchMutations; 90 rval.transf.binaryOpExprCallback ~= (OpKind k) { 91 return k in isDcc ? dccBranchMutations : null; 92 }; 93 rval.transf.returnBoolFuncCallback ~= () => dccBranchMutations; 94 95 rval.transf.caseSubStmtCallback ~= () => dccCaseMutations; 96 rval.transf.caseStmtCallback ~= () => dcrCaseMutations; 97 98 import std.algorithm : map; 99 import std.array : array; 100 import dextool.plugin.mutate.backend.type : Mutation; 101 102 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 103 return lcrMutations(k).op; 104 }; 105 rval.transf.binaryOpExprCallback ~= (OpKind k) { return lcrMutations(k).expr; }; 106 rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => lcrRhsMutations(k); 107 rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => lcrLhsMutations(k); 108 109 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 110 return lcrbMutations(k); 111 }; 112 rval.transf.assignOpOpCallback ~= (OpKind k) { return lcrbAssignMutations(k); }; 113 rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => lcrbRhsMutations(k); 114 rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => lcrbLhsMutations(k); 115 116 rval.transf.assignOpOpCallback ~= (OpKind k) { return aorAssignMutations(k); }; 117 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 118 return aorMutations(k); 119 }; 120 rval.transf.assignOpOpCallback ~= (OpKind k) { return aorAssignMutations(k); }; 121 rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => aorRhsMutations(k); 122 rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => aorLhsMutations(k); 123 124 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo tyi) { 125 if (k in isRor) 126 return rorMutations(k, tyi).op.map!(a => cast(Mutation.Kind) a).array(); 127 else 128 return null; 129 }; 130 rval.transf.binaryOpExprCallback ~= (OpKind k) { 131 if (k in isRor) 132 return rorMutations(k, OpTypeInfo.none).expr; 133 else 134 return null; 135 }; 136 137 //rval.transf.binaryOpLhsCallback ~= (OpKind k) => uoiLvalueMutations; 138 //rval.transf.binaryOpRhsCallback ~= (OpKind k) => uoiLvalueMutations; 139 140 rval.transf.binaryOpLhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor 141 ? [Mutation.Kind.corRhs] : null; 142 rval.transf.binaryOpRhsCallback ~= (OpKind k, OpTypeInfo) => k in isCor 143 ? [Mutation.Kind.corLhs] : null; 144 rval.transf.binaryOpOpCallback ~= (OpKind k, OpTypeInfo) { 145 if (auto v = k in isCor) 146 return corOpMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 147 else 148 return null; 149 }; 150 rval.transf.binaryOpExprCallback ~= (OpKind k) { 151 if (auto v = k in isCor) 152 return corExprMutations(*v).map!(a => cast(Mutation.Kind) a).array(); 153 else 154 return null; 155 }; 156 157 return rval; 158 } 159 160 private: 161 162 /** Find all mutation points that affect a whole expression. 163 * 164 * TODO change the name of the class. It is more than just an expression 165 * visitor. 166 * 167 * # Usage of kind_stack 168 * All usage of the kind_stack shall be documented here. 169 * - track assignments to avoid generating unary insert operators for the LHS. 170 */ 171 class BaseVisitor : ExtendedVisitor { 172 import clang.c.Index : CXCursorKind, CXTypeKind; 173 import cpptooling.analyzer.clang.ast; 174 import dextool.clang_extensions : getExprOperator, OpKind; 175 176 alias visit = ExtendedVisitor.visit; 177 178 mixin generateIndentIncrDecr; 179 180 private Transform transf; 181 private EnumCache enum_cache; 182 183 /// Track the visited nodes. 184 private Stack!CXCursorKind kind_stack; 185 /// Track the return type of the function. 186 private Stack!CXTypeKind return_type_func; 187 188 /** 189 * Params: 190 * restrict = only analyze files starting with this path 191 */ 192 this(Transform transf, EnumCache ec, const uint indent = 0) nothrow { 193 this.transf = transf; 194 this.indent = indent; 195 this.enum_cache = ec; 196 } 197 198 override void visit(const(TranslationUnit) v) { 199 mixin(mixinNodeLog!()); 200 v.accept(this); 201 } 202 203 override void visit(const(Attribute) v) { 204 mixin(mixinNodeLog!()); 205 v.accept(this); 206 } 207 208 override void visit(const(Declaration) v) { 209 mixin(mixinNodeLog!()); 210 v.accept(this); 211 } 212 213 override void visit(const(EnumDecl) v) @trusted { 214 mixin(mixinNodeLog!()); 215 import std.typecons : scoped; 216 217 auto vis = scoped!EnumVisitor(indent); 218 v.accept(vis); 219 220 if (!vis.entry.isNull) { 221 enum_cache.put(EnumCache.USR(v.cursor.usr), vis.entry); 222 } 223 224 debug logger.tracef("%s", enum_cache); 225 } 226 227 override void visit(const(FunctionDecl) v) { 228 mixin(mixinNodeLog!()); 229 230 auto rtype = v.cursor.func.resultType; 231 if (rtype.isValid) { 232 mixin(pushPopStack("kind_stack", "v.cursor.kind")); 233 mixin(pushPopStack("return_type_func", "rtype.kind")); 234 v.accept(this); 235 } else { 236 v.accept(this); 237 } 238 } 239 240 override void visit(const(CxxMethod) v) { 241 mixin(mixinNodeLog!()); 242 // same logic in FunctionDecl 243 244 auto rtype = v.cursor.func.resultType; 245 if (rtype.isValid) { 246 mixin(pushPopStack("kind_stack", "v.cursor.kind")); 247 mixin(pushPopStack("return_type_func", "rtype.kind")); 248 v.accept(this); 249 } else { 250 v.accept(this); 251 } 252 } 253 254 override void visit(const(Directive) v) { 255 mixin(mixinNodeLog!()); 256 v.accept(this); 257 } 258 259 override void visit(const(Expression) v) { 260 mixin(mixinNodeLog!()); 261 v.accept(this); 262 } 263 264 override void visit(const(DeclRefExpr) v) { 265 mixin(mixinNodeLog!()); 266 267 // block UOI injection when inside an assignment 268 if (!kind_stack.hasValue(CXCursorKind.compoundAssignOperator)) 269 transf.unaryInject(v.cursor); 270 v.accept(this); 271 } 272 273 override void visit(const(IntegerLiteral) v) { 274 mixin(mixinNodeLog!()); 275 //transf.unaryInject(v.cursor); 276 v.accept(this); 277 } 278 279 override void visit(const(CxxThrowExpr) v) { 280 mixin(mixinNodeLog!()); 281 mixin(pushPopStack("kind_stack", "v.cursor.kind")); 282 transf.throwStmt(v.cursor); 283 v.accept(this); 284 } 285 286 override void visit(const(CallExpr) v) { 287 mixin(mixinNodeLog!()); 288 289 const is_func = kind_stack.hasValue(CXCursorKind.functionDecl) 290 || kind_stack.hasValue(CXCursorKind.cxxMethod); 291 const is_bool_func = is_func && return_type_func.hasValue(CXTypeKind.bool_); 292 const is_return_stmt = kind_stack.hasValue(CXCursorKind.returnStmt); 293 294 if (is_bool_func && is_return_stmt) { 295 transf.returnBoolFunc(v.cursor); 296 } else if (is_return_stmt) { 297 // a function that should return a value but is mutated to exit 298 // without a "return" statement introduce UB. 299 } else if (kind_stack.hasValue(CXCursorKind.cxxThrowExpr)) { 300 // mutating from "throw exception();" to "throw ;" is just stupid. 301 } else { 302 transf.binaryOp(v.cursor, enum_cache); 303 transf.funcCall(v.cursor); 304 } 305 306 v.accept(this); 307 } 308 309 override void visit(const(BreakStmt) v) { 310 mixin(mixinNodeLog!()); 311 v.accept(this); 312 } 313 314 override void visit(const(BinaryOperator) v) { 315 mixin(mixinNodeLog!()); 316 transf.binaryOp(v.cursor, enum_cache); 317 318 // by only removing when inside a {} it should limit generation of 319 // useless mutants. 320 // TODO: would it be useful to remove initialization of globals? 321 if (kind_stack.hasValue(CXCursorKind.compoundStmt)) 322 transf.assignStmt(v.cursor); 323 324 v.accept(this); 325 } 326 327 override void visit(const(CompoundAssignOperator) v) { 328 mixin(mixinNodeLog!()); 329 mixin(pushPopStack("kind_stack", "v.cursor.kind")); 330 transf.assignOp(v.cursor, enum_cache); 331 v.accept(this); 332 } 333 334 override void visit(const(Preprocessor) v) { 335 mixin(mixinNodeLog!()); 336 337 const bool is_cpp = v.spelling == "__cplusplus"; 338 339 if (is_cpp) 340 transf.lang = Language.cpp; 341 else if (!is_cpp && transf.lang != Language.cpp) 342 transf.lang = Language.c; 343 344 v.accept(this); 345 } 346 347 override void visit(const(Reference) v) { 348 mixin(mixinNodeLog!()); 349 v.accept(this); 350 } 351 352 override void visit(const(Statement) v) { 353 mixin(mixinNodeLog!()); 354 v.accept(this); 355 } 356 357 override void visit(const(ReturnStmt) v) { 358 mixin(mixinNodeLog!()); 359 mixin(pushPopStack("kind_stack", "v.cursor.kind")); 360 361 v.accept(this); 362 } 363 364 override void visit(const(CompoundStmt) v) { 365 mixin(mixinNodeLog!()); 366 mixin(pushPopStack("kind_stack", "v.cursor.kind")); 367 368 const is_inside_func = kind_stack.hasValue(CXCursorKind.functionDecl) 369 || kind_stack.hasValue(CXCursorKind.cxxMethod); 370 371 // a function/method body start at the first compound statement {...} 372 if (is_inside_func && return_type_func.hasValue(CXTypeKind.void_)) { 373 transf.voidFuncBody(v.cursor); 374 } 375 376 v.accept(this); 377 } 378 379 // trusted: the scope allocated visitor do not escape the method 380 override void visit(const(IfStmt) v) @trusted { 381 mixin(mixinNodeLog!()); 382 import std.typecons : scoped; 383 384 auto clause = scoped!IfStmtClauseVisitor(transf, enum_cache, indent); 385 auto ifstmt = scoped!IfStmtVisitor(transf, enum_cache, this, clause, indent); 386 accept(v, cast(IfStmtVisitor) ifstmt); 387 } 388 389 override void visit(const(CaseStmt) v) { 390 mixin(mixinNodeLog!()); 391 transf.caseStmt(v.cursor); 392 v.accept(this); 393 } 394 } 395 396 /** Inject code to validate and check the location of a cursor. 397 * 398 * Params: 399 * cursor = code snippet to get the cursor from a variable accessable in the method. 400 */ 401 string makeAndCheckLocation(string cursor) { 402 import std.format : format; 403 404 return format(q{ 405 auto extent = %s.extent; 406 auto loc = extent.start; 407 auto loc_end = extent.end; 408 if (!val_loc.shouldAnalyze(loc.path)) { 409 return; 410 }}, cursor); 411 } 412 413 struct Stack(T) { 414 import std.typecons : Nullable; 415 import std.container : Array; 416 417 Array!T arr; 418 alias arr this; 419 420 // trusted: as long as arr do not escape the instance 421 void put(T a) @trusted { 422 arr.insertBack(a); 423 } 424 425 // trusted: as long as arr do not escape the instance 426 void pop() @trusted { 427 arr.removeBack; 428 } 429 430 /** Check from the top of the stack if v is in the stack 431 * 432 * trusted: the slice never escape the method and v never affects the 433 * slicing thus the memory. 434 */ 435 bool hasValue(T v) @trusted { 436 foreach (a; arr[]) { 437 if (a == v) 438 return true; 439 } 440 441 return false; 442 } 443 } 444 445 /// A mixin string that pushes `value` to `instance` and pops on scope exit. 446 string pushPopStack(string instance, string value) { 447 import std.format : format; 448 449 return format(q{%s.put(%s); scope(exit) %s.pop;}, instance, value, instance); 450 } 451 452 /** Transform the AST to mutation poins and mutations. 453 * 454 * The intent is to decouple the AST visitor from the transformation logic. 455 * 456 * TODO reduce code duplication. Do it after the first batch of mutations are 457 * implemented. 458 */ 459 class Transform { 460 import std.algorithm : map; 461 import std.array : array; 462 import dextool.clang_extensions : OpKind; 463 import dextool.plugin.mutate.backend.type : Offset; 464 import dextool.plugin.mutate.backend.utility; 465 466 /// Any statement 467 alias StatementEvent = Mutation.Kind[]delegate(); 468 StatementEvent[] stmtCallback; 469 StatementEvent[] funcCallCallback; 470 StatementEvent[] voidFuncBodyCallback; 471 StatementEvent[] returnBoolFuncCallback; 472 StatementEvent[] assignStmtCallback; 473 StatementEvent[] throwStmtCallback; 474 475 /// Any statement that should have a unary operator inserted before/after 476 alias UnaryInjectEvent = Mutation.Kind[]delegate(ValueKind); 477 UnaryInjectEvent[] unaryInjectCallback; 478 479 /// Any binary operator 480 alias BinaryOpEvent = Mutation.Kind[]delegate(OpKind kind, OpTypeInfo tyi); 481 alias BinaryExprEvent = Mutation.Kind[]delegate(OpKind kind); 482 BinaryOpEvent[] binaryOpOpCallback; 483 BinaryOpEvent[] binaryOpLhsCallback; 484 BinaryOpEvent[] binaryOpRhsCallback; 485 BinaryExprEvent[] binaryOpExprCallback; 486 487 /// Assignment operators 488 alias AssignOpKindEvent = Mutation.Kind[]delegate(OpKind kind); 489 alias AssignOpEvent = Mutation.Kind[]delegate(); 490 AssignOpKindEvent[] assignOpOpCallback; 491 AssignOpEvent[] assignOpLhsCallback; 492 AssignOpEvent[] assignOpRhsCallback; 493 494 /// Branch condition expression such as those in an if stmt 495 alias BranchEvent = Mutation.Kind[]delegate(); 496 BranchEvent[] branchCondCallback; 497 BranchEvent[] branchClauseCallback; 498 BranchEvent[] branchThenCallback; 499 BranchEvent[] branchElseCallback; 500 501 /// Switch condition 502 alias CaseEvent = Mutation.Kind[]delegate(); 503 /// the statement after the `case 2:` until the next one 504 CaseEvent[] caseStmtCallback; 505 CaseEvent[] caseSubStmtCallback; 506 507 private AnalyzeResult result; 508 private ValidateLoc val_loc; 509 510 // it is assumed that the language is kept constant for one translation unit 511 private Language lang; 512 513 this(AnalyzeResult res, ValidateLoc vloc) { 514 this.result = res; 515 this.val_loc = vloc; 516 } 517 518 /// Call the callback without arguments. 519 private void noArgCallback(T)(const Cursor c, T callbacks) { 520 mixin(makeAndCheckLocation("c")); 521 mixin(mixinPath); 522 523 auto sr = c.extent; 524 auto offs = Offset(sr.start.offset, sr.end.offset); 525 526 auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line, 527 loc.column), SourceLoc(loc_end.line, loc_end.column)); 528 foreach (cb; callbacks) { 529 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 530 } 531 532 result.put(p); 533 } 534 535 void statement(T)(const(T) v) { 536 mixin(makeAndCheckLocation("v.cursor")); 537 mixin(mixinPath); 538 539 auto offs = calcOffset(v); 540 auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line, 541 loc.column), SourceLoc(loc_end.line, loc_end.column)); 542 foreach (cb; stmtCallback) { 543 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 544 } 545 546 result.put(p); 547 } 548 549 void funcCall(const Cursor c) { 550 noArgCallback(c, funcCallCallback); 551 } 552 553 void returnBoolFunc(const Cursor c) { 554 noArgCallback(c, returnBoolFuncCallback); 555 } 556 557 void throwStmt(const Cursor c) { 558 noArgCallback(c, throwStmtCallback); 559 } 560 561 void voidFuncBody(const Cursor c) { 562 funcBody(c, voidFuncBodyCallback); 563 } 564 565 private void funcBody(T)(const Cursor c, T[] callbacks) { 566 if (!c.isValid) 567 return; 568 569 mixin(makeAndCheckLocation("c")); 570 mixin(mixinPath); 571 572 auto sr = c.extent; 573 // because of how a compound statement for a function body is it has to 574 // be adjusted with +1 and -1 to "exclude" the {} 575 auto offs = Offset(sr.start.offset + 1, sr.end.offset - 1); 576 577 // sanity check. This shouldn't happen but I do not fully trust the extends from the clang AST. 578 if (offs.begin >= offs.end) 579 return; 580 581 auto p = MutationPointEntry(MutationPoint(offs, null), path, 582 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column)); 583 foreach (cb; callbacks) 584 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 585 586 result.put(p); 587 } 588 589 void unaryInject(const(Cursor) c) { 590 import dextool.clang_extensions : exprValueKind, getUnderlyingExprNode; 591 592 // nodes from getOperator can be invalid. 593 if (!c.isValid) 594 return; 595 596 mixin(makeAndCheckLocation("c")); 597 mixin(mixinPath); 598 599 auto sr = c.extent; 600 auto offs = Offset(sr.start.offset, sr.end.offset); 601 602 const auto kind = exprValueKind(getUnderlyingExprNode(c)); 603 604 auto p = MutationPointEntry(MutationPoint(offs, null), path, 605 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column)); 606 foreach (cb; unaryInjectCallback) { 607 p.mp.mutations ~= cb(kind).map!(a => Mutation(a)).array(); 608 } 609 610 result.put(p); 611 } 612 613 void binaryOp(const(Cursor) c, const EnumCache ec) { 614 mixin(makeAndCheckLocation("c")); 615 mixin(mixinPath); 616 617 auto mp = getOperatorMP(c, ec); 618 if (!mp.isValid) 619 return; 620 621 binaryOpInternal(mp); 622 623 result.put(mp.lhs); 624 result.put(mp.rhs); 625 result.put(mp.op); 626 result.put(mp.expr); 627 } 628 629 private void binaryOpInternal(ref OperatorMP mp) { 630 foreach (cb; binaryOpOpCallback) 631 mp.op.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array(); 632 foreach (cb; binaryOpLhsCallback) 633 mp.lhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array(); 634 foreach (cb; binaryOpRhsCallback) 635 mp.rhs.mp.mutations ~= cb(mp.rawOp.kind, mp.typeInfo).map!(a => Mutation(a)).array(); 636 foreach (cb; binaryOpExprCallback) 637 mp.expr.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array(); 638 } 639 640 void assignStmt(const Cursor c) { 641 noArgCallback(c, assignStmtCallback); 642 } 643 644 void assignOp(const Cursor c, const EnumCache ec) { 645 mixin(makeAndCheckLocation("c")); 646 mixin(mixinPath); 647 648 auto mp = getOperatorMP(c, ec); 649 if (!mp.isValid) 650 return; 651 652 foreach (cb; assignOpOpCallback) 653 mp.op.mp.mutations ~= cb(mp.rawOp.kind).map!(a => Mutation(a)).array(); 654 655 foreach (cb; assignOpLhsCallback) 656 mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 657 658 foreach (cb; assignOpRhsCallback) 659 mp.lhs.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 660 661 result.put(mp.lhs); 662 result.put(mp.rhs); 663 result.put(mp.op); 664 } 665 666 /** Callback for the whole condition in a if statement. 667 */ 668 void branchCond(const Cursor c, const EnumCache ec) { 669 mixin(makeAndCheckLocation("c")); 670 mixin(mixinPath); 671 672 auto mp = getOperatorMP(c, ec); 673 if (mp.isValid) { 674 binaryOpInternal(mp); 675 676 foreach (cb; branchCondCallback) { 677 mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 678 } 679 680 result.put(mp.lhs); 681 result.put(mp.rhs); 682 result.put(mp.op); 683 result.put(mp.expr); 684 } else { 685 auto sr = c.extent; 686 auto offs = Offset(sr.start.offset, sr.end.offset); 687 auto p = MutationPointEntry(MutationPoint(offs, null), path, 688 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column)); 689 690 foreach (cb; branchCondCallback) { 691 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 692 } 693 694 result.put(p); 695 } 696 } 697 698 /** Callback for the individual clauses in an if statement. 699 */ 700 void branchClause(const Cursor c, const EnumCache ec) { 701 mixin(makeAndCheckLocation("c")); 702 mixin(mixinPath); 703 704 auto mp = getOperatorMP(c, ec); 705 if (!mp.isValid) 706 return; 707 708 binaryOpInternal(mp); 709 710 foreach (cb; branchClauseCallback) { 711 mp.expr.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 712 } 713 714 result.put(mp.lhs); 715 result.put(mp.rhs); 716 result.put(mp.op); 717 result.put(mp.expr); 718 } 719 720 void branchThen(const Cursor c) { 721 mixin(makeAndCheckLocation("c")); 722 mixin(mixinPath); 723 724 auto sr = c.extent; 725 auto offs = Offset(sr.start.offset, sr.end.offset); 726 727 auto p = MutationPointEntry(MutationPoint(offs, null), path, 728 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column)); 729 730 foreach (cb; branchThenCallback) { 731 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 732 } 733 734 result.put(p); 735 } 736 737 void branchElse(const Cursor c) { 738 mixin(makeAndCheckLocation("c")); 739 mixin(mixinPath); 740 741 auto sr = c.extent; 742 auto offs = Offset(sr.start.offset, sr.end.offset); 743 744 auto p = MutationPointEntry(MutationPoint(offs, null), path, 745 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column)); 746 747 foreach (cb; branchElseCallback) { 748 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 749 } 750 751 result.put(p); 752 } 753 754 void caseStmt(const Cursor c) { 755 import clang.c.Index : CXTokenKind, CXCursorKind; 756 import dextool.clang_extensions : getCaseStmt; 757 758 auto mp = getCaseStmt(c); 759 if (!mp.isValid) 760 return; 761 762 mixin(makeAndCheckLocation("c")); 763 mixin(mixinPath); 764 765 auto sr = mp.subStmt.extent; 766 auto offs = Offset(sr.start.offset, sr.end.offset); 767 if (mp.subStmt.kind == CXCursorKind.caseStmt) { 768 // a case statement with fallthrough. the only point to inject a bomb is directly efter the semicolon 769 offs.begin = mp.colonLocation.offset + 1; 770 offs.end = offs.begin; 771 } else if (mp.subStmt.kind != CXCursorKind.compoundStmt) { 772 offs.end = findTokenOffset(c.translationUnit.cursor.tokens, offs, 773 CXTokenKind.punctuation); 774 } 775 776 void subStmt() { 777 auto p = MutationPointEntry(MutationPoint(offs), path, SourceLoc(loc.line, 778 loc.column), SourceLoc(loc_end.line, loc_end.column)); 779 780 foreach (cb; caseSubStmtCallback) { 781 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 782 } 783 784 result.put(p); 785 } 786 787 void stmt() { 788 auto stmt_sr = c.extent; 789 // reuse the end from offs because it also covers either only the fallthrough OR also the end semicolon 790 auto stmt_offs = Offset(stmt_sr.start.offset, offs.end); 791 792 auto p = MutationPointEntry(MutationPoint(stmt_offs), path, 793 SourceLoc(loc.line, loc.column), SourceLoc(loc_end.line, loc_end.column)); 794 795 foreach (cb; caseStmtCallback) { 796 p.mp.mutations ~= cb().map!(a => Mutation(a)).array(); 797 } 798 799 result.put(p); 800 } 801 802 stmt(); 803 subStmt(); 804 } 805 806 private static struct OperatorMP { 807 bool isValid; 808 Operator rawOp; 809 /// the left side INCLUDING the operator 810 MutationPointEntry lhs; 811 /// the right side INCLUDING the operator 812 MutationPointEntry rhs; 813 /// the operator 814 MutationPointEntry op; 815 /// the whole operator expression 816 MutationPointEntry expr; 817 /// Type information of the types on the sides of the operator 818 OpTypeInfo typeInfo; 819 } 820 821 OperatorMP getOperatorMP(const(Cursor) c, const EnumCache ec) { 822 import dextool.clang_extensions : getExprOperator; 823 824 typeof(return) rval; 825 826 auto op_ = getExprOperator(c); 827 if (!op_.isValid) 828 return rval; 829 830 rval.rawOp = op_; 831 832 SourceLocation loc = op_.cursor.location; 833 834 auto path = loc.path.Path; 835 if (path is null) 836 return rval; 837 result.put(path, lang); 838 839 rval.isValid = op_.isValid; 840 841 void sidesPoint() { 842 auto sides = op_.sides; 843 auto opsr = op_.location.spelling; 844 auto sr = op_.cursor.extent; 845 846 if (sides.rhs.isValid) { 847 auto offs_rhs = Offset(opsr.offset, sr.end.offset); 848 rval.rhs = MutationPointEntry(MutationPoint(offs_rhs, null), path, 849 SourceLoc(sides.rhs.extent.start.line, sides.rhs.extent.start.column), 850 SourceLoc(sides.rhs.extent.end.line, sides.rhs.extent.end.column)); 851 } 852 853 if (sides.lhs.isValid) { 854 auto offs_lhs = Offset(sr.start.offset, cast(uint)(opsr.offset + op_.length)); 855 rval.lhs = MutationPointEntry(MutationPoint(offs_lhs, null), path, 856 SourceLoc(sides.lhs.location.line, sides.lhs.location.column), 857 SourceLoc(sides.lhs.extent.end.line, sides.lhs.extent.end.column)); 858 } 859 } 860 861 void opPoint() { 862 auto sr = op_.location.spelling; 863 auto offs = Offset(sr.offset, cast(uint)(sr.offset + op_.length)); 864 rval.op = MutationPointEntry(MutationPoint(offs, null), path, 865 SourceLoc(op_.location.line, op_.location.column), 866 SourceLoc(op_.location.line, cast(uint)(op_.location.column + op_.length))); 867 868 auto sides = op_.sides; 869 rval.typeInfo = deriveOpTypeInfo(sides.lhs, sides.rhs, ec); 870 } 871 872 void exprPoint() { 873 // TODO this gives a slightly different result from calling getUnderlyingExprNode on v.cursor. 874 // Investigate which one is the "correct" way. 875 auto sr = op_.cursor.extent; 876 auto offs_expr = Offset(sr.start.offset, sr.end.offset); 877 rval.expr = MutationPointEntry(MutationPoint(offs_expr, null), path, 878 SourceLoc(sr.start.line, sr.start.column), 879 SourceLoc(sr.end.line, sr.end.column)); 880 } 881 882 sidesPoint(); 883 opPoint(); 884 exprPoint(); 885 886 return rval; 887 } 888 889 // expects a makeAndCheckLocation exists before. 890 private static string mixinPath() { 891 // a bug in getExprOperator makes the path for a ++ which is overloaded 892 // is null. 893 return q{ 894 auto path = loc.path.Path; 895 if (path is null) 896 return; 897 result.put(path, lang); 898 }; 899 } 900 } 901 902 OpTypeInfo deriveOpTypeInfo(const Cursor lhs_, const Cursor rhs_, const EnumCache ec) @safe { 903 import std.meta : AliasSeq; 904 import std.algorithm : among; 905 import clang.c.Index : CXTypeKind, CXCursorKind; 906 import clang.Type : Type; 907 import dextool.clang_extensions : getUnderlyingExprNode; 908 909 auto lhs = Cursor(getUnderlyingExprNode(lhs_)); 910 auto rhs = Cursor(getUnderlyingExprNode(rhs_)); 911 912 if (!lhs.isValid || !rhs.isValid) 913 return OpTypeInfo.none; 914 915 auto lhs_ty = lhs.type.canonicalType; 916 auto rhs_ty = rhs.type.canonicalType; 917 918 if (!lhs_ty.isValid || !rhs_ty.isValid) 919 return OpTypeInfo.none; 920 921 auto floatCategory = AliasSeq!(CXTypeKind.float_, CXTypeKind.double_, CXTypeKind.longDouble); 922 auto pointerCategory = AliasSeq!(CXTypeKind.nullPtr, CXTypeKind.pointer, 923 CXTypeKind.blockPointer, CXTypeKind.memberPointer); 924 auto boolCategory = AliasSeq!(CXTypeKind.bool_); 925 926 if (lhs_ty.isEnum && rhs_ty.isEnum) { 927 auto lhs_ref = lhs.referenced; 928 auto rhs_ref = rhs.referenced; 929 if (!lhs_ref.isValid || !rhs_ref.isValid) 930 return OpTypeInfo.none; 931 932 auto lhs_usr = lhs_ref.usr; 933 auto rhs_usr = rhs_ref.usr; 934 935 debug logger.tracef("lhs:%s:%s rhs:%s:%s", lhs_usr, lhs_ref.kind, rhs_usr, rhs_ref.kind); 936 937 if (lhs_usr == rhs_usr) { 938 return OpTypeInfo.enumLhsRhsIsSame; 939 } else if (lhs_ref.kind == CXCursorKind.enumConstantDecl) { 940 auto lhs_ty_decl = lhs_ty.declaration; 941 if (!lhs_ty_decl.isValid) 942 return OpTypeInfo.none; 943 944 auto p = ec.position(EnumCache.USR(lhs_ty_decl.usr), EnumCache.USR(lhs_usr)); 945 if (p == EnumCache.Query.isMin) 946 return OpTypeInfo.enumLhsIsMin; 947 else if (p == EnumCache.Query.isMax) 948 return OpTypeInfo.enumLhsIsMax; 949 return OpTypeInfo.none; 950 } else if (rhs_ref.kind == CXCursorKind.enumConstantDecl) { 951 auto rhs_ty_decl = rhs_ty.declaration; 952 if (!rhs_ty_decl.isValid) 953 return OpTypeInfo.none; 954 955 auto p = ec.position(EnumCache.USR(rhs_ty_decl.usr), EnumCache.USR(rhs_usr)); 956 if (p == EnumCache.Query.isMin) 957 return OpTypeInfo.enumRhsIsMin; 958 else if (p == EnumCache.Query.isMax) 959 return OpTypeInfo.enumRhsIsMax; 960 return OpTypeInfo.none; 961 } 962 963 return OpTypeInfo.none; 964 } else if (lhs_ty.kind.among(floatCategory) && rhs_ty.kind.among(floatCategory)) { 965 return OpTypeInfo.floatingPoint; 966 } else if (lhs_ty.kind.among(pointerCategory) || rhs_ty.kind.among(pointerCategory)) { 967 return OpTypeInfo.pointer; 968 } else if (lhs_ty.kind.among(boolCategory) && rhs_ty.kind.among(boolCategory)) { 969 return OpTypeInfo.boolean; 970 } 971 972 return OpTypeInfo.none; 973 } 974 975 import clang.c.Index : CXTokenKind; 976 import dextool.clang_extensions : Operator; 977 import dextool.plugin.mutate.backend.type : Offset; 978 979 /// Holds the resulting mutants. 980 class AnalyzeResult { 981 import std.array : Appender; 982 import dextool.plugin.mutate.backend.type : Checksum; 983 import dextool.set; 984 985 static struct FileResult { 986 import std.range : isOutputRange; 987 988 Path path; 989 Checksum cs; 990 Language lang; 991 992 void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, char)) { 993 import std.format : formattedWrite; 994 995 formattedWrite(w, "%s (%s)", path, lang); 996 } 997 } 998 999 FilesysIO fio; 1000 TokenStream tstream; 1001 Cache cache; 1002 1003 Appender!(MutationPointEntry2[]) entries; 1004 1005 /// Files that has been analyzed. 1006 Appender!(FileResult[]) files; 1007 /// Ensure that `files` do not contain duplicates. 1008 Set!Path file_index; 1009 1010 /// The source code language of the current file that is producing mutants. 1011 Language lang; 1012 1013 /// The factory used for IDs. Re-initialized for each file. 1014 MutationIdFactory id_factory; 1015 1016 this(FilesysIO fio, TokenStream tstream, Cache cache) { 1017 this.fio = fio; 1018 this.tstream = tstream; 1019 this.cache = cache; 1020 } 1021 1022 /// Returns: a tuple of two elements. The tokens before and after the mutation point. 1023 private static auto splitByMutationPoint(Token[] toks, MutationPoint mp) { 1024 import std.algorithm : countUntil; 1025 import std.typecons : Tuple; 1026 1027 Tuple!(size_t, "pre", size_t, "post") rval; 1028 1029 const pre_idx = toks.countUntil!((a, b) => a.offset.begin > b.offset.begin)(mp); 1030 if (pre_idx == -1) { 1031 rval.pre = toks.length; 1032 return rval; 1033 } 1034 1035 rval.pre = pre_idx; 1036 toks = toks[pre_idx .. $]; 1037 1038 const post_idx = toks.countUntil!((a, b) => a.offset.end > b.offset.end)(mp); 1039 if (post_idx != -1) { 1040 rval.post = toks.length - post_idx; 1041 } 1042 1043 return rval; 1044 } 1045 1046 void put(MutationPointEntry a) { 1047 import std.path : buildNormalizedPath; 1048 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText; 1049 1050 if (a.file.length == 0) { 1051 // TODO: this is a workaround. There should never be mutation points without a valid path. 1052 return; 1053 } 1054 1055 auto toks = cache.getFilteredTokens(AbsolutePath(a.file), tstream); 1056 const file_name = fio.toRelativeRoot(a.file).buildNormalizedPath.Path; 1057 1058 if (file_name != id_factory.fileName) { 1059 id_factory = MutationIdFactory(file_name, cache.getPathChecksum(file_name), toks); 1060 } 1061 1062 auto split = splitByMutationPoint(toks, a.mp); 1063 // these generate too much debug info to be active all the time 1064 //debug logger.trace("mutation point: ", a.mp); 1065 //debug logger.trace("pre_tokens: ", split.pre); 1066 //debug logger.trace("post_tokens: ", split.post); 1067 1068 id_factory.updatePosition(split.pre, split.post); 1069 1070 auto mpe = MutationPointEntry2(file_name, a.mp.offset, a.sloc, a.slocEnd); 1071 1072 auto p = AbsolutePath(a.file, DirName(fio.getOutputDir)); 1073 // the file should already be in the store of files to save to the DB. 1074 assert(p in file_index); 1075 auto fin = fio.makeInput(p); 1076 foreach (m; a.mp.mutations) { 1077 auto txt = makeMutationText(fin, mpe.offset, m.kind, lang); 1078 auto cm = id_factory.makeMutant(m, txt.rawMutation); 1079 mpe.put(cm); 1080 } 1081 1082 entries.put(mpe); 1083 } 1084 1085 void put(Path a, Language lang) { 1086 this.lang = lang; 1087 1088 if (file_index.contains(a)) 1089 return; 1090 1091 auto p = AbsolutePath(a, DirName(fio.getOutputDir)); 1092 auto fin = fio.makeInput(p); 1093 auto cs = cache.getFileChecksum(p, fin.content); 1094 1095 file_index.add(a); 1096 files.put(FileResult(a, cs, lang)); 1097 } 1098 } 1099 1100 // trusted: the tokens do not escape this function. 1101 Offset calcOffset(T)(const(T) v) @trusted { 1102 import clang.c.Index : CXTokenKind; 1103 import cpptooling.analyzer.clang.ast; 1104 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 1105 1106 Offset rval; 1107 1108 auto sr = v.cursor.extent; 1109 rval = Offset(sr.start.offset, sr.end.offset); 1110 1111 static if (is(T == CallExpr) || is(T == BreakStmt)) { 1112 import clang.Token; 1113 1114 // TODO inactivated because it leaks memory. Unable to run on sqlite3. 1115 1116 // TODO this is extremly inefficient. change to a more localized cursor 1117 // or even better. Get the tokens at the end. 1118 //auto arg = v.cursor.translationUnit.cursor; 1119 // 1120 //rval.end = findTokenOffset(arg.tokens, rval, CXTokenKind.punctuation, ";"); 1121 } 1122 1123 return rval; 1124 } 1125 /// trusted: trusting the impl in clang. 1126 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind, string spelling) @trusted { 1127 foreach (ref t; toks) { 1128 if (t.location.offset >= sr.end) { 1129 if (t.kind == kind && t.spelling == spelling) { 1130 return t.extent.end.offset; 1131 } 1132 break; 1133 } 1134 } 1135 1136 return sr.end; 1137 } 1138 1139 /// trusted: trusting the impl in clang. 1140 uint findTokenOffset(T)(T toks, Offset sr, CXTokenKind kind) @trusted { 1141 foreach (ref t; toks) { 1142 if (t.location.offset >= sr.end) { 1143 if (t.kind == kind) { 1144 return t.extent.end.offset; 1145 } 1146 break; 1147 } 1148 } 1149 1150 return sr.end; 1151 } 1152 1153 final class IfStmtVisitor : ExtendedVisitor { 1154 import cpptooling.analyzer.clang.ast; 1155 1156 alias visit = ExtendedVisitor.visit; 1157 1158 mixin generateIndentIncrDecr; 1159 1160 private { 1161 Transform transf; 1162 EnumCache enum_cache; 1163 ExtendedVisitor sub_visitor; 1164 ExtendedVisitor cond_visitor; 1165 } 1166 1167 /** 1168 * Params: 1169 * sub_visitor = visitor used for recursive analyze. 1170 */ 1171 this(Transform transf, EnumCache ec, ExtendedVisitor sub_visitor, 1172 ExtendedVisitor cond_visitor, const uint indent) { 1173 this.transf = transf; 1174 this.enum_cache = ec; 1175 this.sub_visitor = sub_visitor; 1176 this.cond_visitor = cond_visitor; 1177 this.indent = indent; 1178 } 1179 1180 override void visit(const IfStmtCond v) { 1181 mixin(mixinNodeLog!()); 1182 transf.branchCond(v.cursor, enum_cache); 1183 v.accept(cond_visitor); 1184 } 1185 1186 override void visit(const IfStmtThen v) { 1187 mixin(mixinNodeLog!()); 1188 transf.branchThen(v.cursor); 1189 v.accept(sub_visitor); 1190 } 1191 1192 override void visit(const IfStmtElse v) { 1193 mixin(mixinNodeLog!()); 1194 transf.branchElse(v.cursor); 1195 v.accept(sub_visitor); 1196 } 1197 } 1198 1199 /// Visit all clauses in the condition of a statement. 1200 final class IfStmtClauseVisitor : BaseVisitor { 1201 import cpptooling.analyzer.clang.ast; 1202 1203 alias visit = BaseVisitor.visit; 1204 1205 /** 1206 * Params: 1207 * transf = ? 1208 * sub_visitor = visitor used for recursive analyze. 1209 */ 1210 this(Transform transf, EnumCache ec, const uint indent) { 1211 super(transf, ec, indent); 1212 } 1213 1214 override void visit(const(BinaryOperator) v) { 1215 mixin(mixinNodeLog!()); 1216 transf.branchClause(v.cursor, enum_cache); 1217 v.accept(this); 1218 } 1219 } 1220 1221 /// Cache enums that are found in the AST for later lookup 1222 class EnumCache { 1223 static struct USR { 1224 string payload; 1225 alias payload this; 1226 } 1227 1228 static struct Entry { 1229 long minValue; 1230 USR[] minId; 1231 1232 long maxValue; 1233 USR[] maxId; 1234 } 1235 1236 enum Query { 1237 unknown, 1238 isMin, 1239 isMiddle, 1240 isMax, 1241 } 1242 1243 Entry[USR] cache; 1244 1245 void put(USR u, Entry e) { 1246 cache[u] = e; 1247 } 1248 1249 /// Check what position the enum const declaration have in enum declaration. 1250 Query position(USR enum_, USR enum_const_decl) const { 1251 import std.algorithm : canFind; 1252 1253 if (auto v = enum_ in cache) { 1254 if ((*v).minId.canFind(enum_const_decl)) 1255 return Query.isMin; 1256 else if ((*v).maxId.canFind(enum_const_decl)) 1257 return Query.isMax; 1258 return Query.isMiddle; 1259 } 1260 1261 return Query.unknown; 1262 } 1263 1264 import std.format : FormatSpec; 1265 1266 // trusted: remove when upgrading to dmd-FE 2.078.1 1267 void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) @trusted const { 1268 import std.format : formatValue, formattedWrite; 1269 import std.range.primitives : put; 1270 1271 foreach (kv; cache.byKeyValue) { 1272 formattedWrite(w, "enum:%s min:%s:%s max:%s:%s", kv.key, 1273 kv.value.minValue, kv.value.minId, kv.value.maxValue, kv.value.maxId); 1274 } 1275 } 1276 1277 // remove this function when upgrading to dmd-FE 2.078.1 1278 override string toString() @trusted pure const { 1279 import std.exception : assumeUnique; 1280 import std.format : FormatSpec; 1281 1282 char[] buf; 1283 buf.reserve(100); 1284 auto fmt = FormatSpec!char("%s"); 1285 toString((const(char)[] s) { buf ~= s; }, fmt); 1286 auto trustedUnique(T)(T t) @trusted { 1287 return assumeUnique(t); 1288 } 1289 1290 return trustedUnique(buf); 1291 } 1292 } 1293 1294 final class EnumVisitor : Visitor { 1295 import std.typecons : Nullable; 1296 import cpptooling.analyzer.clang.ast; 1297 1298 alias visit = Visitor.visit; 1299 1300 mixin generateIndentIncrDecr; 1301 1302 Nullable!(EnumCache.Entry) entry; 1303 1304 this(const uint indent) { 1305 this.indent = indent; 1306 } 1307 1308 override void visit(const EnumDecl v) { 1309 mixin(mixinNodeLog!()); 1310 v.accept(this); 1311 } 1312 1313 override void visit(const EnumConstantDecl v) @trusted { 1314 mixin(mixinNodeLog!()); 1315 1316 Cursor c = v.cursor; 1317 long value = c.enum_.signedValue; 1318 1319 if (entry.isNull) { 1320 entry = EnumCache.Entry(value, [EnumCache.USR(c.usr)], value, [ 1321 EnumCache.USR(c.usr) 1322 ]); 1323 } else if (value < entry.minValue) { 1324 entry.minValue = value; 1325 entry.minId = [EnumCache.USR(c.usr)]; 1326 } else if (value == entry.minValue) { 1327 entry.minId ~= EnumCache.USR(c.usr); 1328 } else if (value > entry.maxValue) { 1329 entry.maxValue = value; 1330 entry.maxId = [EnumCache.USR(c.usr)]; 1331 } else if (value == entry.maxValue) { 1332 entry.maxId ~= EnumCache.USR(c.usr); 1333 } 1334 1335 v.accept(this); 1336 } 1337 } 1338 1339 /// Create mutation ID's from source code mutations. 1340 struct MutationIdFactory { 1341 import dextool.hash : Checksum128, BuildChecksum128, toBytes, toChecksum128; 1342 import dextool.plugin.mutate.backend.type : CodeMutant, CodeChecksum, Mutation, Checksum; 1343 import dextool.type : Path; 1344 1345 /// An instance is related to a filename. 1346 Path fileName; 1347 1348 /// Checksum of the filename containing the mutants. 1349 Checksum file; 1350 /// Checksum of all tokens content. 1351 Checksum content; 1352 1353 private { 1354 /// Where in the token stream the preMutant calculation is. 1355 size_t preIdx; 1356 Checksum preMutant; 1357 /// Where in the post tokens the postMutant is. 1358 size_t postIdx; 1359 Checksum postMutant; 1360 } 1361 1362 /** 1363 * Params: 1364 * filename = the file that the factory is for 1365 * file = checksum of the filename. 1366 * tokens = all tokens from the file. 1367 */ 1368 this(Path fileName, Checksum file, Token[] tokens) { 1369 this.fileName = fileName; 1370 this.file = file; 1371 1372 BuildChecksum128 bc; 1373 foreach (t; tokens) { 1374 bc.put(cast(const(ubyte)[]) t.spelling); 1375 } 1376 this.content = toChecksum128(bc); 1377 } 1378 1379 /// Update the number of tokens that are before and after the mutant. 1380 void updatePosition(const size_t preCnt, const size_t postCnt) { 1381 // only do it if the position changes 1382 if (preCnt == preIdx && postCnt == postIdx) 1383 return; 1384 1385 preIdx = preCnt; 1386 postIdx = postCnt; 1387 1388 { 1389 BuildChecksum128 bc; 1390 bc.put(preIdx.toBytes); 1391 preMutant = toChecksum128(bc); 1392 } 1393 { 1394 BuildChecksum128 bc; 1395 bc.put(postIdx.toBytes); 1396 postMutant = toChecksum128(bc); 1397 } 1398 } 1399 1400 /// Calculate the unique ID for a specific mutation at this point. 1401 Checksum128 makeId(const(ubyte)[] mut) @safe pure nothrow const @nogc scope { 1402 // # SPC-analyzer-checksum 1403 BuildChecksum128 h; 1404 h.put(file.c0.toBytes); 1405 h.put(file.c1.toBytes); 1406 1407 h.put(content.c0.toBytes); 1408 h.put(content.c1.toBytes); 1409 1410 h.put(preMutant.c0.toBytes); 1411 h.put(preMutant.c1.toBytes); 1412 1413 h.put(mut); 1414 1415 h.put(postMutant.c0.toBytes); 1416 h.put(postMutant.c1.toBytes); 1417 return toChecksum128(h); 1418 } 1419 1420 /// Create a mutant at this mutation point. 1421 CodeMutant makeMutant(Mutation m, const(ubyte)[] mut) @safe pure nothrow const @nogc scope { 1422 auto id = makeId(mut); 1423 return CodeMutant(CodeChecksum(id), m); 1424 } 1425 } 1426 1427 // Intende to move this code to clang_extensions if this approach to extending the clang AST works well. 1428 // --- BEGIN 1429 1430 static import dextool.clang_extensions; 1431 1432 static import cpptooling.analyzer.clang.ast; 1433 1434 class ExtendedVisitor : Visitor { 1435 import cpptooling.analyzer.clang.ast; 1436 import dextool.clang_extensions; 1437 1438 alias visit = Visitor.visit; 1439 1440 void visit(const(IfStmtInit) value) { 1441 visit(cast(const(Statement)) value); 1442 } 1443 1444 void visit(const(IfStmtCond) value) { 1445 visit(cast(const(Expression)) value); 1446 } 1447 1448 void visit(const(IfStmtThen) value) { 1449 visit(cast(const(Statement)) value); 1450 } 1451 1452 void visit(const(IfStmtElse) value) { 1453 visit(cast(const(Statement)) value); 1454 } 1455 } 1456 1457 final class IfStmtInit : cpptooling.analyzer.clang.ast.Statement { 1458 this(Cursor cursor) @safe { 1459 super(cursor); 1460 } 1461 1462 void accept(ExtendedVisitor v) @safe const { 1463 static import cpptooling.analyzer.clang.ast; 1464 1465 cpptooling.analyzer.clang.ast.accept(cursor, v); 1466 } 1467 } 1468 1469 final class IfStmtCond : cpptooling.analyzer.clang.ast.Expression { 1470 this(Cursor cursor) @safe { 1471 super(cursor); 1472 } 1473 1474 void accept(ExtendedVisitor v) @safe const { 1475 static import cpptooling.analyzer.clang.ast; 1476 1477 cpptooling.analyzer.clang.ast.accept(cursor, v); 1478 } 1479 } 1480 1481 final class IfStmtThen : cpptooling.analyzer.clang.ast.Statement { 1482 this(Cursor cursor) @safe { 1483 super(cursor); 1484 } 1485 1486 void accept(ExtendedVisitor v) @safe const { 1487 static import cpptooling.analyzer.clang.ast; 1488 1489 cpptooling.analyzer.clang.ast.accept(cursor, v); 1490 } 1491 } 1492 1493 final class IfStmtElse : cpptooling.analyzer.clang.ast.Statement { 1494 this(Cursor cursor) @safe { 1495 super(cursor); 1496 } 1497 1498 void accept(ExtendedVisitor v) @safe const { 1499 static import cpptooling.analyzer.clang.ast; 1500 1501 cpptooling.analyzer.clang.ast.accept(cursor, v); 1502 } 1503 } 1504 1505 void accept(T)(const(cpptooling.analyzer.clang.ast.IfStmt) n, T v) 1506 if (is(T : ExtendedVisitor)) { 1507 import dextool.clang_extensions; 1508 1509 auto stmt = getIfStmt(n.cursor); 1510 accept(stmt, v); 1511 } 1512 1513 void accept(T)(ref dextool.clang_extensions.IfStmt n, T v) 1514 if (is(T : ExtendedVisitor)) { 1515 import std.traits : hasMember; 1516 1517 static if (hasMember!(T, "incr")) 1518 v.incr; 1519 { 1520 if (n.init_.isValid) { 1521 auto sub = new IfStmtInit(n.init_); 1522 v.visit(sub); 1523 } 1524 } 1525 { 1526 if (n.cond.isValid) { 1527 auto sub = new IfStmtCond(n.cond); 1528 v.visit(sub); 1529 } 1530 } 1531 { 1532 if (n.then.isValid) { 1533 auto sub = new IfStmtThen(n.then); 1534 v.visit(sub); 1535 } 1536 } 1537 { 1538 if (n.else_.isValid) { 1539 auto sub = new IfStmtElse(n.else_); 1540 v.visit(sub); 1541 } 1542 } 1543 1544 static if (hasMember!(T, "decr")) 1545 v.decr; 1546 } 1547 1548 // --- END