1 /** 2 Copyright: Copyright (c) 2016, 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 Design. 11 - Using named tuples as the result from analyze* to allow the tuples to add 12 more data in the future without breaking existing code. 13 */ 14 module cpptooling.analyzer.clang.analyze_helper; 15 16 import logger = std.experimental.logger; 17 18 import std.traits : Unqual; 19 import std.typecons : tuple, Flag, Yes, No, Nullable; 20 import std.meta : staticIndexOf; 21 22 import sumtype; 23 import my.sumtype; 24 25 import clang.c.Index : CX_CXXAccessSpecifier, CX_StorageClass, CXLanguageKind; 26 import clang.Cursor : Cursor; 27 import clang.SourceLocation : SourceLocation; 28 29 import libclang_ast.ast : ClassTemplate, ClassTemplatePartialSpecialization, 30 Constructor, CxxMethod, ClassDecl, CxxBaseSpecifier, 31 Destructor, FieldDecl, FunctionDecl, StructDecl, TranslationUnit, UnionDecl, VarDecl, Visitor; 32 33 import cpptooling.analyzer.clang.type : retrieveType, TypeKind, TypeKindAttr, 34 TypeResult, TypeResults, logTypeResult, TypeAttr; 35 import cpptooling.analyzer.clang.store : put; 36 import cpptooling.data : AccessType, VariadicType, CxParam, TypeKindVariable, 37 CppVariable, LocationTag, Location, CxReturnType, 38 CppVirtualMethod, CppMethodName, CppClassName, CppNs, CppAccess, 39 StorageClass, CFunctionName, Language, CFunction, CxGlobalVariable; 40 import cpptooling.data.symbol : Container, USRType; 41 42 /// Convert Cursor attributes to enum representation. 43 private CppVirtualMethod classify(T)(T c) @safe if (is(Unqual!T == Cursor)) { 44 import cpptooling.data.type : MemberVirtualType; 45 46 auto is_virtual = MemberVirtualType.Normal; 47 auto func = () @trusted { return c.func; }(); 48 49 if (!func.isValid) { 50 // do nothing 51 } else if (func.isPureVirtual) { 52 is_virtual = MemberVirtualType.Pure; 53 } else if (func.isVirtual) { 54 is_virtual = MemberVirtualType.Virtual; 55 } 56 57 return CppVirtualMethod(is_virtual); 58 } 59 60 /// Convert a clang access specifier to dextool representation. 61 AccessType toAccessType(CX_CXXAccessSpecifier accessSpec) @safe { 62 final switch (accessSpec) with (CX_CXXAccessSpecifier) { 63 case cxxInvalidAccessSpecifier: 64 return AccessType.Public; 65 case cxxPublic: 66 return AccessType.Public; 67 case cxxProtected: 68 return AccessType.Protected; 69 case cxxPrivate: 70 return AccessType.Private; 71 } 72 } 73 74 StorageClass toStorageClass(CX_StorageClass storageClass) @safe pure nothrow @nogc { 75 switch (storageClass) with (CX_StorageClass) { 76 case extern_: 77 return StorageClass.Extern; 78 case static_: 79 return StorageClass.Static; 80 default: 81 return StorageClass.None; 82 } 83 } 84 85 private CxParam[] toCxParam(ref TypeKind kind, ref Container container) @safe { 86 import std.array; 87 import std.algorithm : map; 88 import std.range : chain, zip, tee; 89 import std..string : strip; 90 import cpptooling.data.kind_type; 91 92 auto tr_params = kind.info.match!((a => a.params), _ => (FuncInfoParam[]).init); 93 94 // dfmt off 95 auto params = zip(// range 1 96 tr_params 97 // lookup the parameters by the usr 98 .map!(a => container.find!TypeKind(a.usr)) 99 // assuming none of the results to find failed 100 // merge the results to a range 101 .map!(a => a.front), 102 // range 2 103 tr_params) 104 .map!((a) { 105 if (a[1].isVariadic) { 106 return CxParam(VariadicType.yes); 107 } else if (a[1].id.strip.length == 0) { 108 //TODO fix the above workaround with strip by fixing type.d 109 return CxParam(TypeKindAttr(a[0].get, a[1].attr)); 110 } else { 111 return CxParam(TypeKindVariable(TypeKindAttr(a[0].get, a[1].attr), CppVariable(a[1].id))); 112 } 113 }); 114 // dfmt on 115 116 return () @trusted { return params.array(); }(); 117 } 118 119 private auto locToTag(SourceLocation c_loc) { 120 auto l = c_loc.expansion(); 121 auto into = LocationTag(Location(l.file.name(), l.line, l.column)); 122 123 return into; 124 } 125 126 private bool isOperator(CppMethodName name_) @safe { 127 import std.algorithm : among; 128 129 if (name_.length <= 8) { 130 // "operator" keyword is 8 char long, thus an optimization to first 131 // look at the length 132 return false; 133 } else if (name_[8 .. $].among("=", "==", "+=", "-=", "++", "--", "+", "-", 134 "*", ">", ">=", "<", "<=", ">>", "<<")) { 135 return true; 136 } 137 138 return false; 139 } 140 141 /** Correctly determine the language of a libclang Cursor. 142 * 143 * Combines an analysis of the name USR and a cursor query. 144 */ 145 Language toLanguage(const Cursor c) @safe 146 in { 147 assert(c.isValid); 148 } 149 do { 150 import std.algorithm : canFind; 151 152 // assuming that the C++ USR always contains a '#'. 153 if (c.usr.canFind('#')) { 154 return Language.cpp; 155 } 156 157 final switch (c.language) with (CXLanguageKind) { 158 case invalid: 159 return Language.unknown; 160 case c: 161 return Language.c; 162 case objC: 163 return Language.unknown; 164 case cPlusPlus: 165 return Language.cpp; 166 } 167 } 168 169 struct FunctionDeclResult { 170 Flag!"isValid" isValid; 171 TypeKindAttr type; 172 CFunctionName name; 173 TypeKindAttr returnType; 174 VariadicType isVariadic; 175 StorageClass storageClass; 176 CxParam[] params; 177 LocationTag location; 178 Flag!"isDefinition" isDefinition; 179 Language language; 180 } 181 182 FunctionDeclResult analyzeFunctionDecl(scope const FunctionDecl v, 183 ref Container container, in uint indent) @safe { 184 return analyzeFunctionDecl(v.cursor, container, indent); 185 } 186 187 // added trusted to get the compiler to shut up. the code works, just... needs scope. 188 FunctionDeclResult analyzeFunctionDecl(scope const Cursor c_in, ref Container container, 189 in uint indent) @trusted 190 in { 191 import clang.c.Index : CXCursorKind; 192 193 () @trusted { assert(c_in.kind == CXCursorKind.functionDecl); }(); 194 } 195 do { 196 import std.algorithm : among; 197 import std.functional : pipe; 198 199 import clang.Cursor : Cursor; 200 import cpptooling.analyzer.clang.type : TypeKind, retrieveType, logTypeResult; 201 import cpptooling.data : TypeResult, TypeKindAttr, CxParam, CFunctionName, 202 CxReturnType, CFunction, VariadicType, LocationTag, StorageClass; 203 import cpptooling.data.symbol : Container; 204 205 // hint, start reading the function from the bottom up. 206 // design is pipe and data transformation 207 208 Nullable!TypeResults extractAndStoreRawType(const Cursor c) @trusted { 209 auto tr = () @trusted { return retrieveType(c, container, indent); }(); 210 if (tr.isNull) { 211 return tr; 212 } 213 214 bool fail; 215 tr.get.primary.type.kind.info.match!(ignore!(TypeKind.FuncInfo), 216 ignore!(TypeKind.TypeRefInfo), ignore!(TypeKind.SimpleInfo), (_) { 217 fail = true; 218 }); 219 if (fail) 220 assert(0, "wrong type"); 221 222 put(tr, container, indent); 223 224 return tr; 225 } 226 227 Nullable!TypeResults lookupRefToConcreteType(const Cursor c, Nullable!TypeResults tr) @trusted { 228 if (tr.isNull) { 229 return tr; 230 } 231 232 tr.get.primary.type.kind.info.match!((TypeKind.TypeRefInfo t) { 233 // replace typeRef kind with the func 234 auto kind = container.find!TypeKind(t.canonicalRef).front; 235 tr.get.primary.type.kind = kind.get; 236 }, (_) {}); 237 238 logTypeResult(tr, indent); 239 tr.get.primary.type.kind.info.match!(ignore!(TypeKind.FuncInfo), (_) { 240 assert(0, "wrong type"); 241 }); 242 243 return tr; 244 } 245 246 static struct ComposeData { 247 TypeResults tr; 248 CFunctionName name; 249 LocationTag loc; 250 VariadicType isVariadic; 251 StorageClass storageClass; 252 Flag!"isDefinition" is_definition; 253 Language language; 254 } 255 256 static ComposeData getCursorData(const Cursor c, TypeResults tr) @safe { 257 auto data = ComposeData(tr); 258 259 data.name = CFunctionName(c.spelling); 260 data.loc = locToTag(c.location()); 261 data.is_definition = cast(Flag!"isDefinition") c.isDefinition; 262 data.storageClass = c.storageClass().toStorageClass; 263 data.language = c.toLanguage; 264 265 return data; 266 } 267 268 FunctionDeclResult composeFunc(ComposeData data) @safe { 269 auto return_type = container.find!TypeKind( 270 data.tr.primary.type.kind.info.match!(a => a.return_, _ => USRType.init)); 271 if (return_type.length == 0) { 272 return FunctionDeclResult.init; 273 } 274 275 auto params = toCxParam(data.tr.primary.type.kind, container); 276 277 VariadicType is_variadic; 278 // according to C/C++ standard the last parameter is the only one 279 // that can be a variadic, therefor only needing to peek at that 280 // one. 281 if (params.length > 0) { 282 is_variadic = cast(VariadicType)() @trusted { 283 return params[$ - 1].peek!VariadicType; 284 }(); 285 } 286 287 auto attrs = data.tr.primary.type.kind.info.match!(a => a.returnAttr, _ => TypeAttr.init); 288 return FunctionDeclResult(Yes.isValid, data.tr.primary.type, data.name, 289 TypeKindAttr(return_type.front.get, attrs), is_variadic, 290 data.storageClass, params, data.loc, data.is_definition, data.language); 291 } 292 293 FunctionDeclResult rval; 294 auto r0 = extractAndStoreRawType(c_in); 295 auto r1 = lookupRefToConcreteType(c_in, r0); 296 if (!r1.isNull) { 297 auto r2 = getCursorData(c_in, r1.get); 298 rval = composeFunc(r2); 299 } 300 301 return rval; 302 } 303 304 struct VarDeclResult { 305 TypeKindAttr type; 306 CppVariable name; 307 LocationTag location; 308 USRType instanceUSR; 309 StorageClass storageClass; 310 } 311 312 /// Analyze a variable declaration 313 VarDeclResult analyzeVarDecl(scope const VarDecl v, ref Container container, in uint indent) @safe { 314 return analyzeVarDecl(v.cursor, container, indent); 315 } 316 317 /// ditto 318 VarDeclResult analyzeVarDecl(scope const Cursor v, ref Container container, in uint indent) @safe 319 in { 320 import clang.c.Index : CXCursorKind; 321 322 assert(v.kind == CXCursorKind.varDecl); 323 } 324 do { 325 import clang.Cursor : Cursor; 326 import cpptooling.analyzer.clang.type : retrieveType; 327 import cpptooling.data : CppVariable; 328 329 auto type = () @trusted { return retrieveType(v, container, indent); }(); 330 put(type, container, indent); 331 332 auto name = CppVariable(v.spelling); 333 auto loc = locToTag(v.location()); 334 auto instance_usr = USRType(v.usr); 335 // Assuming that all variable declarations have a USR 336 assert(instance_usr.length > 0); 337 338 // store the location to enable creating relations to/from this instance 339 // USR. 340 container.put(loc, instance_usr, Yes.isDefinition); 341 342 auto storage = () @trusted { return v.storageClass.toStorageClass; }(); 343 344 return VarDeclResult(type.get.primary.type, name, loc, instance_usr, storage); 345 } 346 347 struct ConstructorResult { 348 TypeKindAttr type; 349 CppMethodName name; 350 CxParam[] params; 351 LocationTag location; 352 } 353 354 /** Analyze the node for actionable data. 355 * Params: 356 * v = node 357 * container = container to store the type in 358 * indent = to use when logging 359 * 360 * Returns: analyzed data. 361 */ 362 auto analyzeConstructor(scope const Constructor v, ref Container container, in uint indent) @safe { 363 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 364 put(type, container, indent); 365 366 auto params = toCxParam(type.get.primary.type.kind, container); 367 auto name = CppMethodName(v.cursor.spelling); 368 369 return ConstructorResult(type.get.primary.type, name, params, type.get.primary.location); 370 } 371 372 struct DestructorResult { 373 TypeKindAttr type; 374 CppMethodName name; 375 CppVirtualMethod virtualKind; 376 LocationTag location; 377 } 378 379 /// ditto 380 auto analyzeDestructor(scope const Destructor v, ref Container container, in uint indent) @safe { 381 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 382 put(type, container, indent); 383 384 auto name = CppMethodName(v.cursor.spelling); 385 auto virtual_kind = classify(v.cursor); 386 387 return DestructorResult(type.get.primary.type, name, virtual_kind, type.get.primary.location); 388 } 389 390 struct CxxMethodResult { 391 TypeKindAttr type; 392 CppMethodName name; 393 CxParam[] params; 394 Flag!"isOperator" isOperator; 395 CxReturnType returnType; 396 CppVirtualMethod virtualKind; 397 Flag!"isConst" isConst; 398 LocationTag location; 399 } 400 401 CxxMethodResult analyzeCxxMethod(scope const CxxMethod v, ref Container container, in uint indent) @safe { 402 return analyzeCxxMethod(v.cursor, container, indent); 403 } 404 405 /// ditto 406 CxxMethodResult analyzeCxxMethod(scope const Cursor v, ref Container container, in uint indent) @safe { 407 auto type = () @trusted { return retrieveType(v, container, indent); }(); 408 type.get.primary.type.kind.info.match!(ignore!(TypeKind.FuncInfo), (_) { 409 assert(0, "wrong type"); 410 }); 411 put(type, container, indent); 412 413 auto name = CppMethodName(v.spelling); 414 auto params = toCxParam(type.get.primary.type.kind, container); 415 auto return_type = CxReturnType(TypeKindAttr(container.find!TypeKind( 416 type.get.primary.type.kind.info.match!(a => a.return_, _ => USRType.init)).front.get, 417 type.get.primary.type.kind.info.match!(a => a.returnAttr, _ => TypeAttr.init))); 418 auto is_virtual = classify(v); 419 420 return CxxMethodResult(type.get.primary.type, name, params, 421 cast(Flag!"isOperator") isOperator(name), return_type, is_virtual, 422 cast(Flag!"isConst") type.get.primary.type.attr.isConst, type.get.primary.location); 423 } 424 425 struct FieldDeclResult { 426 TypeKindAttr type; 427 CppVariable name; 428 USRType instanceUSR; 429 LocationTag location; 430 } 431 432 /// ditto 433 auto analyzeFieldDecl(scope const FieldDecl v, ref Container container, in uint indent) @safe { 434 import cpptooling.analyzer.clang.type : makeEnsuredUSR; 435 436 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 437 put(type, container, indent); 438 439 auto name = CppVariable(v.cursor.spelling); 440 441 auto instance_usr = makeEnsuredUSR(v.cursor, indent + 1); 442 // Assuming that all field declarations have a USR 443 assert(instance_usr.length > 0); 444 445 auto loc = () @trusted { return locToTag(v.cursor.location()); }(); 446 // store the location to enable creating relations to/from this instance 447 // USR. 448 container.put(loc, instance_usr, Yes.isDefinition); 449 450 return FieldDeclResult(type.get.primary.type, name, instance_usr, loc); 451 } 452 453 struct CxxBaseSpecifierResult { 454 TypeKindAttr type; 455 CppClassName name; 456 CppNs[] reverseScope; 457 USRType canonicalUSR; 458 CppAccess access; 459 } 460 461 /** Analyze the node that represents a inheritance. 462 * 463 * reverseScope. 464 * scope the class reside in starting from the bottom. 465 * class A : public B {}; 466 * reverseScope is then [B, A]. 467 * 468 * canonicalUSR. 469 * The resolved USR. 470 * It is possible to inherit from for example a typedef. canonicalUSR would be 471 * the class the typedef refers. 472 */ 473 auto analyzeCxxBaseSpecified(scope const CxxBaseSpecifier v, ref Container container, in uint indent) @safe { 474 import clang.c.Index : CXCursorKind; 475 import std.array : array; 476 import std.algorithm : map; 477 import cpptooling.data.type : CppAccess; 478 import cpptooling.analyzer.clang.cursor_backtrack : backtrackScopeRange; 479 import cpptooling.data : toStringDecl; 480 481 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 482 put(type, container, indent); 483 484 auto name = CppClassName(type.get.primary.type.toStringDecl); 485 auto access = CppAccess(toAccessType(() @trusted { return v.cursor.access; }().accessSpecifier)); 486 auto usr = type.get.primary.type.kind.usr; 487 488 type.get.primary.type.kind.info.match!((TypeKind.TypeRefInfo t) { 489 usr = t.canonicalRef; 490 }, (_) {}); 491 492 CppNs[] namespace; 493 auto c_ref = v.cursor.referenced; 494 if (c_ref.kind == CXCursorKind.noDeclFound) { 495 namespace = backtrackScopeRange(c_ref).map!(a => CppNs(a.spelling)).array(); 496 } else { 497 namespace = backtrackScopeRange(v.cursor).map!(a => CppNs(a.spelling)).array(); 498 } 499 500 if (namespace.length > 0) { 501 // namespace has the class itself in the range so must remove 502 namespace = namespace[1 .. $]; 503 } 504 505 return CxxBaseSpecifierResult(type.get.primary.type, name, namespace, usr, access); 506 } 507 508 struct RecordResult { 509 TypeKindAttr type; 510 CppClassName name; 511 LocationTag location; 512 } 513 514 RecordResult analyzeRecord(T)(scope const T decl, ref Container container, in uint indent) 515 if (staticIndexOf!(T, ClassDecl, StructDecl, ClassTemplate, 516 ClassTemplatePartialSpecialization, UnionDecl) != -1) { 517 return analyzeRecord(decl.cursor, container, indent); 518 } 519 520 RecordResult analyzeRecord(scope const Cursor cursor, ref Container container, in uint indent) @safe { 521 auto type = () @trusted { return retrieveType(cursor, container, indent); }(); 522 put(type, container, indent); 523 524 auto name = CppClassName(cursor.spelling); 525 526 return RecordResult(type.get.primary.type, name, type.get.primary.location); 527 } 528 529 /// 530 struct TranslationUnitResult { 531 string fileName; 532 } 533 534 auto analyzeTranslationUnit(scope const TranslationUnit tu, ref Container container, in uint indent) { 535 auto fname = tu.cursor.spelling; 536 return TranslationUnitResult(fname); 537 } 538 539 /** Reconstruct the semantic clang AST with dextool data structures suitable 540 * for code generation. 541 * 542 * Note that it do NOT traverses the inheritance chain. 543 */ 544 final class ClassVisitor : Visitor { 545 import clang.Cursor : Cursor; 546 import libclang_ast.ast; 547 import cpptooling.data; 548 import cpptooling.data.symbol : Container; 549 import libclang_ast.cursor_logger : logNode, mixinNodeLog; 550 551 alias visit = Visitor.visit; 552 553 mixin generateIndentIncrDecr; 554 555 /// The reconstructed class. 556 CppClass root; 557 558 private { 559 Container* container; 560 CppAccess accessType; 561 } 562 563 this(T)(const T decl, CppNsStack reside_in_ns, RecordResult result, 564 ref Container container, const uint indent) 565 if (is(T == ClassDecl) || is(T == StructDecl)) { 566 this.container = &container; 567 this.indent = indent; 568 569 static if (is(T == StructDecl)) { 570 this.accessType = CppAccess(AccessType.Public); 571 } else { 572 this.accessType = CppAccess(AccessType.Private); 573 } 574 575 this.root = CppClass(result.name, CppInherit[].init, reside_in_ns); 576 this.root.usr = result.type.kind.usr; 577 } 578 579 override void visit(scope const(CxxBaseSpecifier) v) { 580 import std.range : retro; 581 import std.array : appender; 582 import clang.c.Index : CXCursorKind; 583 584 mixin(mixinNodeLog!()); 585 586 auto result = analyzeCxxBaseSpecified(v, *container, indent); 587 auto inherit = CppInherit(result.name, result.access); 588 inherit.usr = result.canonicalUSR; 589 590 foreach (a; retro(result.reverseScope)) { 591 inherit.put(a); 592 } 593 root.put(inherit); 594 } 595 596 override void visit(scope const(Constructor) v) { 597 mixin(mixinNodeLog!()); 598 599 auto result = analyzeConstructor(v, *container, indent); 600 auto tor = CppCtor(result.type.kind.usr, result.name, result.params, accessType); 601 root.put(tor); 602 603 debug logger.trace("ctor: ", tor.toString); 604 } 605 606 override void visit(scope const(Destructor) v) @trusted { 607 mixin(mixinNodeLog!()); 608 609 auto type = retrieveType(v.cursor, *container, indent); 610 .put(type, *container, indent); 611 612 auto result = analyzeDestructor(v, *container, indent); 613 auto tor = CppDtor(result.type.kind.usr, result.name, accessType, classify(v.cursor)); 614 root.put(tor); 615 616 debug logger.trace("dtor: ", tor.toString); 617 } 618 619 override void visit(scope const(CxxMethod) v) { 620 import cpptooling.data : CppMethodOp; 621 622 mixin(mixinNodeLog!()); 623 624 auto result = analyzeCxxMethod(v, *container, indent); 625 626 if (result.isOperator) { 627 auto op = CppMethodOp(result.type.kind.usr, result.name, result.params, 628 result.returnType, accessType, 629 CppConstMethod(result.isConst), result.virtualKind); 630 root.put(op); 631 debug logger.trace("operator: ", op.toString); 632 } else { 633 auto method = CppMethod(result.type.kind.usr, result.name, result.params, 634 result.returnType, accessType, 635 CppConstMethod(result.isConst), result.virtualKind); 636 root.put(method); 637 debug logger.trace("method: ", method.toString); 638 } 639 } 640 641 override void visit(scope const(CxxAccessSpecifier) v) { 642 mixin(mixinNodeLog!()); 643 644 accessType = CppAccess(toAccessType(v.cursor.access.accessSpecifier)); 645 } 646 647 override void visit(scope const(FieldDecl) v) { 648 import cpptooling.data : TypeKindVariable; 649 650 mixin(mixinNodeLog!()); 651 652 auto result = analyzeFieldDecl(v, *container, indent); 653 root.put(TypeKindVariable(result.type, result.name), accessType); 654 655 debug logger.trace("member: ", result.name); 656 } 657 }