1 /**
2  * Copyright: Copyright (c) 2017 Wojciech Szęszoł. All rights reserved.
3  * Authors: Wojciech Szęszoł
4  * Version: Initial created: Jan 21, 2017
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
6  */
7 
8 module dstep.translator.HeaderIndex;
9 
10 import clang.c.Index;
11 import clang.Cursor;
12 import clang.Index;
13 import clang.Util;
14 import clang.TranslationUnit;
15 
16 class IncludeGraph
17 {
18     import std.typecons;
19 
20     struct Inclusion
21     {
22         size_t included;
23         size_t including;
24     }
25 
26     private size_t[string] headers_;
27     private string[size_t] reverse_;
28     private Set!Inclusion directInclusions;
29     private Set!Inclusion indirectInclusions;
30     private string internalPrefix;
31 
32     public this(TranslationUnit translationUnit)
33     {
34         import std.algorithm;
35 
36         auto directives = translationUnit.cursor.children
37             .filter!(cursor => cursor.kind == CXCursorKind.inclusionDirective);
38 
39         this(directives);
40     }
41 
42     public this(T)(T directives)
43     {
44         import std.random;
45         import std.range;
46 
47         internalPrefix = generate(() => uniform!("[]")('a', 'z'))
48             .take(32).array.idup;
49 
50         foreach (directive; directives)
51         {
52             auto path = directive.file.absolutePath;
53             auto includedPath = directive.includedFile.absolutePath;
54 
55             if (path != "" && includedPath != "")
56             {
57                 if (path !in headers_)
58                 {
59                     reverse_[headers_.length] = path;
60                     headers_[path] = headers_.length;
61                 }
62 
63                 if (includedPath !in headers_)
64                 {
65                     reverse_[headers_.length] = includedPath;
66                     headers_[includedPath] = headers_.length;
67                 }
68 
69                 auto pair = Inclusion(headers_[includedPath], headers_[path]);
70                 directInclusions.add(pair);
71             }
72         }
73 
74         removeCycles(directInclusions);
75 
76         foreach (header, closure; transitiveClosure())
77         {
78             foreach (closureHeader; closure.byKey)
79                 indirectInclusions.add(Inclusion(header, closureHeader));
80         }
81     }
82 
83     auto headers()
84     {
85         return headers_.byKey();
86     }
87 
88     bool isIncludedBy(string header, string by)
89     {
90         auto inclusion = Inclusion(
91             headers_[header.asAbsNormPath],
92             headers_[by.asAbsNormPath]);
93 
94         return directInclusions.contains(inclusion);
95     }
96 
97     bool isReachableBy(string header, string by)
98     {
99         auto inclusion = Inclusion(
100             headers_[header.asAbsNormPath],
101             headers_[by.asAbsNormPath]);
102 
103         return indirectInclusions.contains(inclusion);
104     }
105 
106     bool isReachableThrough(string header, string via, string by)
107     {
108         return isReachableBy(header, via) && isReachableBy(via, by);
109     }
110 
111     void removeCycles(Set!Inclusion inclusions)
112     {
113         enum Status
114         {
115             pristine,
116             visiting,
117             visited
118         }
119 
120         auto statuses = new Status[headers_.length];
121         auto includedBy = new size_t[][headers_.length];
122 
123         foreach (inclusion; inclusions.byKey)
124             includedBy[inclusion.included] ~= inclusion.including;
125 
126         void visit(size_t header, size_t includedHeader)
127         {
128             if (statuses[header] == Status.visited)
129                 return;
130 
131             if (statuses[header] == Status.visiting)
132             {
133                 inclusions.remove(Inclusion(includedHeader, header));
134             }
135             else
136             {
137                 statuses[header] = Status.visiting;
138                 foreach (includingHeader; includedBy[header])
139                     visit(includingHeader, header);
140                 statuses[header] = Status.visited;
141             }
142         }
143 
144         foreach (header; headers_.byValue)
145         {
146             if (statuses[header] == Status.pristine)
147                 visit(header, size_t.max);
148         }
149     }
150 
151     private size_t[] topologicalSort()
152     {
153         import std.range;
154 
155         enum Status
156         {
157             pristine,
158             visiting,
159             visited
160         }
161 
162         auto sorted = new size_t[0];
163         auto statuses = new Status[headers_.length];
164         auto includedBy = new size_t[][headers_.length];
165 
166         foreach (inclusion; directInclusions.byKey)
167             includedBy[inclusion.included] ~= inclusion.including;
168 
169         void visit(size_t header)
170         {
171             if (statuses[header] == Status.visited)
172                 return;
173 
174             if (statuses[header] == Status.visiting)
175                 throw new Exception("The include graph isn't a DAG.");
176 
177             statuses[header] = Status.visiting;
178 
179             foreach (includedHeader; includedBy[header])
180                 visit(includedHeader);
181 
182             statuses[header] = Status.visited;
183             sorted ~= header;
184         }
185 
186         foreach (header; headers_.byValue)
187         {
188             if (statuses[header] == Status.pristine)
189                 visit(header);
190         }
191 
192         return sorted;
193     }
194 
195     private Set!size_t[] transitiveClosure()
196     {
197         import std.range;
198 
199         auto sorted = topologicalSort();
200         auto closure = new Set!size_t[sorted.length];
201 
202         auto includedTo = new size_t[][headers_.length];
203 
204         foreach (inclusion; directInclusions.byKey)
205             includedTo[inclusion.including] ~= inclusion.included;
206 
207         foreach (header; sorted)
208         {
209             closure[header].add(header);
210 
211             foreach (transitiveHeader; closure[header].byKey)
212             {
213                 foreach (includedHeader; includedTo[header]) {
214                     closure[includedHeader].add(transitiveHeader);
215                 }
216             }
217         }
218 
219         return closure;
220     }
221 }
222 
223 class HeaderIndex
224 {
225     private IncludeGraph includeGraph_;
226     private string[string] stdLibPaths;
227     private string[string] knownModules;
228     private string mainFilePath;
229 
230     public this(TranslationUnit translationUnit)
231     {
232         immutable string[string] standardModuleMapping = [
233             // standard c library
234             "assert.h" : "core.stdc.assert_",
235             "ctype.h" : "core.stdc.ctype",
236             "errno.h" : "core.stdc.errno",
237             "float.h" : "core.stdc.float_",
238             "limits.h" : "core.stdc.limits",
239             "locale.h" : "core.stdc.locale",
240             "math.h" : "core.stdc.math",
241             "signal.h" : "core.stdc.signal",
242             "stdarg.h" : "core.stdc.stdarg",
243             "stddef.h" : "core.stdc.stddef",
244             "stdio.h" : "core.stdc.stdio",
245             "stdlib.h" : "core.stdc.stdlib",
246             "string.h" : "core.stdc.string",
247             "time.h" : "core.stdc.time",
248             "wctype.h" : "core.stdc.wctype",
249             "wchar.h" : "core.stdc.wchar_",
250             "complex.h" : "core.stdc.complex",
251             "fenv.h" : "core.stdc.fenv",
252             "inttypes.h" : "core.stdc.inttypes",
253             "tgmath.h" : "core.stdc.tgmath",
254             "stdint.h" : "core.stdc.stdint",
255             // posix library
256             "dirent.h" : "core.sys.posix.dirent",
257             "dlfcn.h" : "core.sys.posix.dlfcn",
258             "fcntl.h" : "core.sys.posix.fcntl",
259             "netdb.h" : "core.sys.posix.netdb",
260             "poll.h" : "core.sys.posix.poll",
261             "pthread.h" : "core.sys.posix.pthread",
262             "pwd.h" : "core.sys.posix.pwd",
263             "sched.h" : "core.sys.posix.sched",
264             "semaphore.h" : "core.sys.posix.semaphore",
265             "setjmp.h" : "core.sys.posix.setjmp",
266             "signal.h" : "core.sys.posix.signal",
267             "termios.h" : "core.sys.posix.termios",
268             "ucontext.h" : "core.sys.posix.ucontext",
269             "unistd.h" : "core.sys.posix.unistd",
270             "utime.h" : "core.sys.posix.utime",
271             "arpa/inet.h" : "core.sys.posix.arpa.inet",
272             "net/if.h" : "core.sys.posix.net.if_",
273             "netinet/in.h" : "core.sys.posix.netinet.in_",
274             "netinet/tcp.h" : "core.sys.posix.netinet.tcp",
275             "sys/ipc.h" : "core.sys.posix.sys.ipc",
276             "sys/mman.h" : "core.sys.posix.sys.mman",
277             "sys/select.h" : "core.sys.posix.sys.select",
278             "sys/shm.h" : "core.sys.posix.sys.shm",
279             "sys/socket.h" : "core.sys.posix.sys.socket",
280             "sys/stat.h" : "core.sys.posix.sys.stat",
281             "sys/time.h" : "core.sys.posix.sys.time",
282             "sys/types.h" : "core.sys.posix.sys.types",
283             "sys/_types.h" : "core.sys.posix.sys.types",
284             "sys/uio.h" : "core.sys.posix.sys.uio",
285             "sys/un.h" : "core.sys.posix.sys.un",
286             "sys/utsname.h" : "core.sys.posix.sys.utsname",
287             "sys/wait.h" : "core.sys.posix.sys.wait",
288             "sys/ioctl.h" : "core.sys.posix.sys.ioctl",
289             // windows library
290             "windows" : "core.sys.windows.windows"
291         ];
292 
293         this (translationUnit, standardModuleMapping);
294     }
295 
296     public this(
297         TranslationUnit translationUnit,
298         const string[string] moduleMapping)
299     {
300         import std.algorithm;
301 
302         if (!translationUnit.isCompiled())
303         {
304             throw new Exception(
305                 "The translation unit has to be compiled without errors.");
306         }
307 
308         alias predicate =
309             cursor => cursor.kind == CXCursorKind.inclusionDirective;
310 
311         auto directives = translationUnit.cursor.children.filter!predicate;
312 
313         includeGraph_ = new IncludeGraph(directives);
314 
315         mainFilePath = translationUnit.spelling.asAbsNormPath;
316         this(directives, moduleMapping);
317     }
318 
319     private this(T)(T directives, const string[string] moduleMapping)
320     {
321         knownModules = resolveKnownModules(
322             directives,
323             resolveKnownModulePaths(
324                 directives,
325                 moduleMapping));
326     }
327 
328     string searchKnownModules(Cursor cursor)
329     {
330         auto knownModule = cursor.file.name.asAbsNormPath in knownModules;
331         return knownModule !is null ? *knownModule : null;
332     }
333 
334     IncludeGraph includeGraph()
335     {
336         return includeGraph_;
337     }
338 
339     private struct KnownModule
340     {
341         string includePath;
342         string moduleName;
343     }
344 
345     private KnownModule[] resolveKnownModulePaths(T)(
346         T directives,
347         const string[string] moduleMapping) const
348     {
349         import std.algorithm;
350         import std.array;
351 
352         Set!KnownModule knownModules;
353 
354         foreach (directive; directives)
355         {
356             auto moduleName = directive.spelling in moduleMapping;
357 
358             if (moduleName !is null)
359             {
360                 auto absolutePath = directive.includedFile.absolutePath;
361                 knownModules.add(KnownModule(absolutePath, *moduleName));
362             }
363         }
364 
365         return knownModules.byKey.array;
366     }
367 
368     private string[string] resolveKnownModules(T)(
369         T directives,
370         KnownModule[] moduleDescs)
371     {
372         string[string] knownModules;
373 
374         foreach (directive; directives)
375         {
376             auto absolutePath = directive.includedFile.absolutePath;
377 
378             foreach (desc; moduleDescs)
379             {
380                 bool reachable = includeGraph.isReachableBy(
381                     absolutePath,
382                     desc.includePath);
383 
384                 if (reachable)
385                 {
386                     knownModules[absolutePath] = desc.moduleName;
387                     break;
388                 }
389             }
390         }
391 
392         return knownModules;
393     }
394 }