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