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 }