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 }