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 #SPC-plugin_mutate_analyzer 11 12 TODO cache the checksums. They are *heavy*. 13 */ 14 module dextool.plugin.mutate.backend.analyzer; 15 16 import logger = std.experimental.logger; 17 18 import dextool.plugin.mutate.backend.database : Database; 19 20 import dextool.type : ExitStatusType, AbsolutePath, Path, DirName; 21 import dextool.compilation_db : CompileCommandFilter, defaultCompilerFlagFilter, 22 CompileCommandDB; 23 import dextool.user_filerange; 24 25 import dextool.plugin.mutate.backend.interface_ : ValidateLoc, FilesysIO; 26 import dextool.plugin.mutate.backend.visitor : makeRootVisitor; 27 import dextool.plugin.mutate.backend.utility : checksum, trustedRelativePath; 28 29 /** Analyze the files in `frange` for mutations. 30 */ 31 ExitStatusType runAnalyzer(ref Database db, ref UserFileRange frange, 32 ValidateLoc val_loc, FilesysIO fio) @safe { 33 import std.algorithm : map; 34 import std.path : relativePath; 35 import std.typecons : Yes; 36 import cpptooling.analyzer.clang.context : ClangContext; 37 import cpptooling.utility.virtualfilesystem; 38 import dextool.clang : findFlags; 39 import dextool.type : FileName, Exists, makeExists; 40 import dextool.utility : analyzeFile; 41 42 // they are not by necessity the same. 43 // Input could be a file that is excluded via --restrict but pull in a 44 // header-only library that is allowed to be mutated. 45 bool[AbsolutePath] analyzed_files; 46 bool[AbsolutePath] files_with_mutations; 47 48 foreach (in_file; frange) { 49 // find the file and flags to analyze 50 51 Exists!AbsolutePath checked_in_file; 52 try { 53 checked_in_file = makeExists(in_file.absoluteFile); 54 } 55 catch (Exception e) { 56 logger.warning(e.msg); 57 continue; 58 } 59 60 if (checked_in_file in analyzed_files) { 61 continue; 62 } 63 64 analyzed_files[checked_in_file] = true; 65 66 // analyze the file 67 auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly); 68 auto root = makeRootVisitor(val_loc); 69 analyzeFile(checked_in_file, in_file.cflags, root.visitor, ctx); 70 71 foreach (a; root.mutationPointFiles.map!(a => FileName(a))) { 72 analyzed_files[AbsolutePath(a)] = true; 73 files_with_mutations[AbsolutePath(a)] = true; 74 75 auto relp = trustedRelativePath(a, fio.getOutputDir); 76 77 try { 78 auto f_status = isFileChanged(db, AbsolutePath(a, DirName(fio.getOutputDir)), fio); 79 if (f_status == FileStatus.changed) { 80 logger.infof("Updating analyze of '%s'", a); 81 db.removeFile(relp); 82 } 83 84 auto cs = checksum(ctx.virtualFileSystem.slice!(ubyte[])(a)); 85 db.put(Path(relp), cs); 86 } 87 catch (Exception e) { 88 logger.warning(e.msg); 89 } 90 } 91 92 db.put(root.mutationPoints, fio.getOutputDir); 93 } 94 95 prune(db, files_with_mutations, fio.getOutputDir); 96 97 return ExitStatusType.Ok; 98 } 99 100 private: 101 102 enum FileStatus { 103 noChange, 104 notInDatabase, 105 changed 106 } 107 108 /// Prune the database of files that has been removed since last analysis. 109 void prune(ref Database db, const bool[AbsolutePath] analyzed_files, const AbsolutePath root_dir) @safe { 110 import dextool.type : FileName; 111 112 foreach (const f; db.getFiles) { 113 auto abs_f = AbsolutePath(FileName(f), DirName(cast(string) root_dir)); 114 115 if (abs_f in analyzed_files) 116 continue; 117 118 logger.infof("Removed from files to mutate: '%s'", abs_f); 119 db.removeFile(f); 120 } 121 } 122 123 FileStatus isFileChanged(ref Database db, AbsolutePath p, FilesysIO fio) @safe { 124 auto relp = trustedRelativePath(p, fio.getOutputDir); 125 126 if (!db.isAnalyzed(relp)) 127 return FileStatus.notInDatabase; 128 129 auto db_checksum = db.getFileChecksum(relp); 130 auto f_checksum = checksum(fio.makeInput(p).read[]); 131 132 auto rval = (!db_checksum.isNull && db_checksum != f_checksum) ? FileStatus.changed 133 : FileStatus.noChange; 134 debug logger.trace(rval == FileStatus.changed, "db: ", db_checksum, " file: ", f_checksum); 135 136 return rval; 137 }