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 279 // according to C/C++ standard the last parameter is the only one 280 // that can be a variadic, therefor only needing to peek at that 281 // one. 282 if (params.length > 0) { 283 is_variadic = params[$ - 1].match!((VariadicType a) => a, _ => VariadicType.init); 284 } 285 286 auto attrs = data.tr.primary.type.kind.info.match!(a => a.returnAttr, _ => TypeAttr.init); 287 return FunctionDeclResult(Yes.isValid, data.tr.primary.type, data.name, 288 TypeKindAttr(return_type.front.get, attrs), is_variadic, 289 data.storageClass, params, data.loc, data.is_definition, data.language); 290 } 291 292 FunctionDeclResult rval; 293 auto r0 = extractAndStoreRawType(c_in); 294 auto r1 = lookupRefToConcreteType(c_in, r0); 295 if (!r1.isNull) { 296 auto r2 = getCursorData(c_in, r1.get); 297 rval = composeFunc(r2); 298 } 299 300 return rval; 301 } 302 303 struct VarDeclResult { 304 TypeKindAttr type; 305 CppVariable name; 306 LocationTag location; 307 USRType instanceUSR; 308 StorageClass storageClass; 309 } 310 311 /// Analyze a variable declaration 312 VarDeclResult analyzeVarDecl(scope const VarDecl v, ref Container container, in uint indent) @safe { 313 return analyzeVarDecl(v.cursor, container, indent); 314 } 315 316 /// ditto 317 VarDeclResult analyzeVarDecl(scope const Cursor v, ref Container container, in uint indent) @safe 318 in { 319 import clang.c.Index : CXCursorKind; 320 321 assert(v.kind == CXCursorKind.varDecl); 322 } 323 do { 324 import clang.Cursor : Cursor; 325 import cpptooling.analyzer.clang.type : retrieveType; 326 import cpptooling.data : CppVariable; 327 328 auto type = () @trusted { return retrieveType(v, container, indent); }(); 329 put(type, container, indent); 330 331 auto name = CppVariable(v.spelling); 332 auto loc = locToTag(v.location()); 333 auto instance_usr = USRType(v.usr); 334 // Assuming that all variable declarations have a USR 335 assert(instance_usr.length > 0); 336 337 // store the location to enable creating relations to/from this instance 338 // USR. 339 container.put(loc, instance_usr, Yes.isDefinition); 340 341 auto storage = () @trusted { return v.storageClass.toStorageClass; }(); 342 343 return VarDeclResult(type.get.primary.type, name, loc, instance_usr, storage); 344 } 345 346 struct ConstructorResult { 347 TypeKindAttr type; 348 CppMethodName name; 349 CxParam[] params; 350 LocationTag location; 351 } 352 353 /** Analyze the node for actionable data. 354 * Params: 355 * v = node 356 * container = container to store the type in 357 * indent = to use when logging 358 * 359 * Returns: analyzed data. 360 */ 361 auto analyzeConstructor(scope const Constructor v, ref Container container, in uint indent) @safe { 362 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 363 put(type, container, indent); 364 365 auto params = toCxParam(type.get.primary.type.kind, container); 366 auto name = CppMethodName(v.cursor.spelling); 367 368 return ConstructorResult(type.get.primary.type, name, params, type.get.primary.location); 369 } 370 371 struct DestructorResult { 372 TypeKindAttr type; 373 CppMethodName name; 374 CppVirtualMethod virtualKind; 375 LocationTag location; 376 } 377 378 /// ditto 379 auto analyzeDestructor(scope const Destructor v, ref Container container, in uint indent) @safe { 380 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 381 put(type, container, indent); 382 383 auto name = CppMethodName(v.cursor.spelling); 384 auto virtual_kind = classify(v.cursor); 385 386 return DestructorResult(type.get.primary.type, name, virtual_kind, type.get.primary.location); 387 } 388 389 struct CxxMethodResult { 390 TypeKindAttr type; 391 CppMethodName name; 392 CxParam[] params; 393 Flag!"isOperator" isOperator; 394 CxReturnType returnType; 395 CppVirtualMethod virtualKind; 396 Flag!"isConst" isConst; 397 LocationTag location; 398 } 399 400 CxxMethodResult analyzeCxxMethod(scope const CxxMethod v, ref Container container, in uint indent) @safe { 401 return analyzeCxxMethod(v.cursor, container, indent); 402 } 403 404 /// ditto 405 CxxMethodResult analyzeCxxMethod(scope const Cursor v, ref Container container, in uint indent) @safe { 406 auto type = () @trusted { return retrieveType(v, container, indent); }(); 407 type.get.primary.type.kind.info.match!(ignore!(TypeKind.FuncInfo), (_) { 408 assert(0, "wrong type"); 409 }); 410 put(type, container, indent); 411 412 auto name = CppMethodName(v.spelling); 413 auto params = toCxParam(type.get.primary.type.kind, container); 414 auto return_type = CxReturnType(TypeKindAttr(container.find!TypeKind( 415 type.get.primary.type.kind.info.match!(a => a.return_, _ => USRType.init)).front.get, 416 type.get.primary.type.kind.info.match!(a => a.returnAttr, _ => TypeAttr.init))); 417 auto is_virtual = classify(v); 418 419 return CxxMethodResult(type.get.primary.type, name, params, 420 cast(Flag!"isOperator") isOperator(name), return_type, is_virtual, 421 cast(Flag!"isConst") type.get.primary.type.attr.isConst, type.get.primary.location); 422 } 423 424 struct FieldDeclResult { 425 TypeKindAttr type; 426 CppVariable name; 427 USRType instanceUSR; 428 LocationTag location; 429 } 430 431 /// ditto 432 auto analyzeFieldDecl(scope const FieldDecl v, ref Container container, in uint indent) @safe { 433 import cpptooling.analyzer.clang.type : makeEnsuredUSR; 434 435 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 436 put(type, container, indent); 437 438 auto name = CppVariable(v.cursor.spelling); 439 440 auto instance_usr = makeEnsuredUSR(v.cursor, indent + 1); 441 // Assuming that all field declarations have a USR 442 assert(instance_usr.length > 0); 443 444 auto loc = () @trusted { return locToTag(v.cursor.location()); }(); 445 // store the location to enable creating relations to/from this instance 446 // USR. 447 container.put(loc, instance_usr, Yes.isDefinition); 448 449 return FieldDeclResult(type.get.primary.type, name, instance_usr, loc); 450 } 451 452 struct CxxBaseSpecifierResult { 453 TypeKindAttr type; 454 CppClassName name; 455 CppNs[] reverseScope; 456 USRType canonicalUSR; 457 CppAccess access; 458 } 459 460 /** Analyze the node that represents a inheritance. 461 * 462 * reverseScope. 463 * scope the class reside in starting from the bottom. 464 * class A : public B {}; 465 * reverseScope is then [B, A]. 466 * 467 * canonicalUSR. 468 * The resolved USR. 469 * It is possible to inherit from for example a typedef. canonicalUSR would be 470 * the class the typedef refers. 471 */ 472 auto analyzeCxxBaseSpecified(scope const CxxBaseSpecifier v, ref Container container, in uint indent) @safe { 473 import clang.c.Index : CXCursorKind; 474 import std.array : array; 475 import std.algorithm : map; 476 import cpptooling.data.type : CppAccess; 477 import cpptooling.analyzer.clang.cursor_backtrack : backtrackScopeRange; 478 import cpptooling.data : toStringDecl; 479 480 auto type = () @trusted { return retrieveType(v.cursor, container, indent); }(); 481 put(type, container, indent); 482 483 auto name = CppClassName(type.get.primary.type.toStringDecl); 484 auto access = CppAccess(toAccessType(() @trusted { return v.cursor.access; }().accessSpecifier)); 485 auto usr = type.get.primary.type.kind.usr; 486 487 type.get.primary.type.kind.info.match!((TypeKind.TypeRefInfo t) { 488 usr = t.canonicalRef; 489 }, (_) {}); 490 491 CppNs[] namespace; 492 auto c_ref = v.cursor.referenced; 493 if (c_ref.kind == CXCursorKind.noDeclFound) { 494 namespace = backtrackScopeRange(c_ref).map!(a => CppNs(a.spelling)).array(); 495 } else { 496 // TODO: remove this workaround. 497 () @trusted { 498 namespace = backtrackScopeRange(v.cursor).map!(a => CppNs(a.spelling)).array(); 499 }(); 500 } 501 502 if (namespace.length > 0) { 503 // namespace has the class itself in the range so must remove 504 namespace = namespace[1 .. $]; 505 } 506 507 return CxxBaseSpecifierResult(type.get.primary.type, name, namespace, usr, access); 508 } 509 510 struct RecordResult { 511 TypeKindAttr type; 512 CppClassName name; 513 LocationTag location; 514 } 515 516 RecordResult analyzeRecord(T)(scope const T decl, ref Container container, in uint indent) 517 if (staticIndexOf!(T, ClassDecl, StructDecl, ClassTemplate, 518 ClassTemplatePartialSpecialization, UnionDecl) != -1) { 519 return analyzeRecord(decl.cursor, container, indent); 520 } 521 522 RecordResult analyzeRecord(scope const Cursor cursor, ref Container container, in uint indent) @safe { 523 auto type = () @trusted { return retrieveType(cursor, container, indent); }(); 524 put(type, container, indent); 525 526 auto name = CppClassName(cursor.spelling); 527 528 return RecordResult(type.get.primary.type, name, type.get.primary.location); 529 } 530 531 /// 532 struct TranslationUnitResult { 533 string fileName; 534 } 535 536 auto analyzeTranslationUnit(scope const TranslationUnit tu, ref Container container, in uint indent) { 537 auto fname = tu.cursor.spelling; 538 return TranslationUnitResult(fname); 539 } 540 541 /** Reconstruct the semantic clang AST with dextool data structures suitable 542 * for code generation. 543 * 544 * Note that it do NOT traverses the inheritance chain. 545 */ 546 final class ClassVisitor : Visitor { 547 import clang.Cursor : Cursor; 548 import libclang_ast.ast; 549 import cpptooling.data; 550 import cpptooling.data.symbol : Container; 551 import libclang_ast.cursor_logger : logNode, mixinNodeLog; 552 553 alias visit = Visitor.visit; 554 555 mixin generateIndentIncrDecr; 556 557 /// The reconstructed class. 558 CppClass root; 559 560 private { 561 Container* container; 562 CppAccess accessType; 563 } 564 565 this(T)(const T decl, CppNsStack reside_in_ns, RecordResult result, 566 ref Container container, const uint indent) 567 if (is(T == ClassDecl) || is(T == StructDecl)) { 568 this.container = &container; 569 this.indent = indent; 570 571 static if (is(T == StructDecl)) { 572 this.accessType = CppAccess(AccessType.Public); 573 } else { 574 this.accessType = CppAccess(AccessType.Private); 575 } 576 577 this.root = CppClass(result.name, CppInherit[].init, reside_in_ns); 578 this.root.usr = result.type.kind.usr; 579 } 580 581 override void visit(scope const(CxxBaseSpecifier) v) { 582 import std.range : retro; 583 import std.array : appender; 584 import clang.c.Index : CXCursorKind; 585 586 mixin(mixinNodeLog!()); 587 588 auto result = analyzeCxxBaseSpecified(v, *container, indent); 589 auto inherit = CppInherit(result.name, result.access); 590 inherit.usr = result.canonicalUSR; 591 592 foreach (a; retro(result.reverseScope)) { 593 inherit.put(a); 594 } 595 root.put(inherit); 596 } 597 598 override void visit(scope const(Constructor) v) { 599 mixin(mixinNodeLog!()); 600 601 auto result = analyzeConstructor(v, *container, indent); 602 auto tor = CppCtor(result.type.kind.usr, result.name, result.params, accessType); 603 root.put(tor); 604 605 debug logger.trace("ctor: ", tor.toString); 606 } 607 608 override void visit(scope const(Destructor) v) @trusted { 609 mixin(mixinNodeLog!()); 610 611 auto type = retrieveType(v.cursor, *container, indent); 612 .put(type, *container, indent); 613 614 auto result = analyzeDestructor(v, *container, indent); 615 auto tor = CppDtor(result.type.kind.usr, result.name, accessType, classify(v.cursor)); 616 root.put(tor); 617 618 debug logger.trace("dtor: ", tor.toString); 619 } 620 621 override void visit(scope const(CxxMethod) v) { 622 import cpptooling.data : CppMethodOp; 623 624 mixin(mixinNodeLog!()); 625 626 auto result = analyzeCxxMethod(v, *container, indent); 627 628 if (result.isOperator) { 629 auto op = CppMethodOp(result.type.kind.usr, result.name, result.params, 630 result.returnType, accessType, 631 CppConstMethod(result.isConst), result.virtualKind); 632 root.put(op); 633 debug logger.trace("operator: ", op.toString); 634 } else { 635 auto method = CppMethod(result.type.kind.usr, result.name, result.params, 636 result.returnType, accessType, 637 CppConstMethod(result.isConst), result.virtualKind); 638 root.put(method); 639 debug logger.trace("method: ", method.toString); 640 } 641 } 642 643 override void visit(scope const(CxxAccessSpecifier) v) { 644 mixin(mixinNodeLog!()); 645 646 accessType = CppAccess(toAccessType(v.cursor.access.accessSpecifier)); 647 } 648 649 override void visit(scope const(FieldDecl) v) { 650 import cpptooling.data : TypeKindVariable; 651 652 mixin(mixinNodeLog!()); 653 654 auto result = analyzeFieldDecl(v, *container, indent); 655 root.put(TypeKindVariable(result.type, result.name), accessType); 656 657 debug logger.trace("member: ", result.name); 658 } 659 }