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 }