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