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