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)
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 size_t[][] transitiveClosure()
196     {
197         import std.range;
198 
199         auto sorted = topologicalSort();
200         auto closure = new 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] ~= header;
210 
211             foreach (transitiveHeader; closure[header])
212             {
213                 foreach (includedHeader; includedTo[header])
214                     closure[includedHeader] ~= transitiveHeader;
215             }
216         }
217 
218         return closure;
219     }
220 }
221 
222 class HeaderIndex
223 {
224     private IncludeGraph includeGraph_;
225     private string[string] stdLibPaths;
226     private string[string] knownModules;
227     private string mainFilePath;
228 
229     public this(TranslationUnit translationUnit)
230     {
231         immutable string[string] standardModuleMapping = [
232             // standard c library
233             "assert.h" : "core.stdc.assert_",
234             "ctype.h" : "core.stdc.ctype",
235             "errno.h" : "core.stdc.errno",
236             "float.h" : "core.stdc.float_",
237             "limits.h" : "core.stdc.limits",
238             "locale.h" : "core.stdc.locale",
239             "math.h" : "core.stdc.math",
240             "signal.h" : "core.stdc.signal",
241             "stdarg.h" : "core.stdc.stdarg",
242             "stddef.h" : "core.stdc.stddef",
243             "stdio.h" : "core.stdc.stdio",
244             "stdlib.h" : "core.stdc.stdlib",
245             "string.h" : "core.stdc.string",
246             "time.h" : "core.stdc.time",
247             "wctype.h" : "core.stdc.wctype",
248             "wchar.h" : "core.stdc.wchar_",
249             "complex.h" : "core.stdc.complex",
250             "fenv.h" : "core.stdc.fenv",
251             "inttypes.h" : "core.stdc.inttypes",
252             "tgmath.h" : "core.stdc.tgmath",
253             "stdint.h" : "core.stdc.stdint",
254             // posix library
255             "dirent.h" : "core.sys.posix.dirent",
256             "dlfcn.h" : "core.sys.posix.dlfcn",
257             "fcntl.h" : "core.sys.posix.fcntl",
258             "netdb.h" : "core.sys.posix.netdb",
259             "poll.h" : "core.sys.posix.poll",
260             "pthread.h" : "core.sys.posix.pthread",
261             "pwd.h" : "core.sys.posix.pwd",
262             "sched.h" : "core.sys.posix.sched",
263             "semaphore.h" : "core.sys.posix.semaphore",
264             "setjmp.h" : "core.sys.posix.setjmp",
265             "signal.h" : "core.sys.posix.signal",
266             "termios.h" : "core.sys.posix.termios",
267             "ucontext.h" : "core.sys.posix.ucontext",
268             "unistd.h" : "core.sys.posix.unistd",
269             "utime.h" : "core.sys.posix.utime",
270             "arpa/inet.h" : "core.sys.posix.arpa.inet",
271             "net/if.h" : "core.sys.posix.net.if_",
272             "netinet/in.h" : "core.sys.posix.netinet.in_",
273             "netinet/tcp.h" : "core.sys.posix.netinet.tcp",
274             "sys/ipc.h" : "core.sys.posix.sys.ipc",
275             "sys/mman.h" : "core.sys.posix.sys.mman",
276             "sys/select.h" : "core.sys.posix.sys.select",
277             "sys/shm.h" : "core.sys.posix.sys.shm",
278             "sys/socket.h" : "core.sys.posix.sys.socket",
279             "sys/stat.h" : "core.sys.posix.sys.stat",
280             "sys/time.h" : "core.sys.posix.sys.time",
281             "sys/types.h" : "core.sys.posix.sys.types",
282             "sys/_types.h" : "core.sys.posix.sys.types",
283             "sys/uio.h" : "core.sys.posix.sys.uio",
284             "sys/un.h" : "core.sys.posix.sys.un",
285             "sys/utsname.h" : "core.sys.posix.sys.utsname",
286             "sys/wait.h" : "core.sys.posix.sys.wait",
287             "sys/ioctl.h" : "core.sys.posix.sys.ioctl",
288             // windows library
289             "windows" : "core.sys.windows.windows"
290         ];
291 
292         this (translationUnit, standardModuleMapping);
293     }
294 
295     public this(
296         TranslationUnit translationUnit,
297         const string[string] moduleMapping)
298     {
299         import std.algorithm;
300 
301         if (!translationUnit.isCompiled())
302         {
303             throw new Exception(
304                 "The translation unit has to be compiled without errors.");
305         }
306 
307         alias predicate =
308             cursor => cursor.kind == CXCursorKind.inclusionDirective;
309 
310         auto directives = translationUnit.cursor.children.filter!predicate;
311 
312         includeGraph_ = new IncludeGraph(directives);
313         mainFilePath = translationUnit.spelling.asAbsNormPath;
314         this(directives, moduleMapping);
315     }
316 
317     private this(T)(T directives, const string[string] moduleMapping)
318     {
319         knownModules = resolveKnownModules(
320             directives,
321             resolveKnownModulePaths(
322                 directives,
323                 moduleMapping));
324     }
325 
326     string searchKnownModules(Cursor cursor)
327     {
328         auto knownModule = cursor.file.name.asAbsNormPath in knownModules;
329         return knownModule !is null ? *knownModule : null;
330     }
331 
332     IncludeGraph includeGraph()
333     {
334         return includeGraph_;
335     }
336 
337     private struct KnownModule
338     {
339         string includePath;
340         string moduleName;
341     }
342 
343     private KnownModule[] resolveKnownModulePaths(T)(
344         T directives,
345         const string[string] moduleMapping) const
346     {
347         import std.algorithm;
348         import std.array;
349 
350         Set!KnownModule knownModules;
351 
352         foreach (directive; directives)
353         {
354             auto moduleName = directive.spelling in moduleMapping;
355 
356             if (moduleName !is null)
357             {
358                 auto absolutePath = directive.includedFile.absolutePath;
359                 knownModules.add(KnownModule(absolutePath, *moduleName));
360             }
361         }
362 
363         return knownModules.byKey.array;
364     }
365 
366     private string[string] resolveKnownModules(T)(
367         T directives,
368         KnownModule[] moduleDescs)
369     {
370         string[string] knownModules;
371 
372         foreach (directive; directives)
373         {
374             auto absolutePath = directive.includedFile.absolutePath;
375 
376             foreach (desc; moduleDescs)
377             {
378                 bool reachable = includeGraph.isReachableBy(
379                     absolutePath,
380                     desc.includePath);
381 
382                 if (reachable)
383                 {
384                     knownModules[absolutePath] = desc.moduleName;
385                     break;
386                 }
387             }
388         }
389 
390         return knownModules;
391     }
392 }