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, CppVirtualMethod, 35 CppMethodName, CppClassName, CppNs, CppAccess, StorageClass, CFunctionName, 36 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, 198 logTypeResult; 199 import cpptooling.data : TypeResult, TypeKindAttr, CxParam, CFunctionName, 200 CxReturnType, CFunction, VariadicType, LocationTag, StorageClass; 201 import cpptooling.data.symbol : Container; 202 203 // hint, start reading the function from the bottom up. 204 // design is pipe and data transformation 205 206 Nullable!TypeResults extractAndStoreRawType(const(Cursor) c) @safe { 207 auto tr = () @trusted{ return retrieveType(c, container, indent); }(); 208 if (tr.isNull) { 209 return tr; 210 } 211 212 assert(tr.primary.type.kind.info.kind.among(TypeKind.Info.Kind.func, 213 TypeKind.Info.Kind.typeRef, TypeKind.Info.Kind.simple)); 214 put(tr, container, indent); 215 216 return tr; 217 } 218 219 Nullable!TypeResults lookupRefToConcreteType(Nullable!TypeResults tr) @safe { 220 if (tr.isNull) { 221 return tr; 222 } 223 224 if (tr.primary.type.kind.info.kind == TypeKind.Info.Kind.typeRef) { 225 // replace typeRef kind with the func 226 auto kind = container.find!TypeKind(tr.primary.type.kind.info.canonicalRef).front; 227 tr.primary.type.kind = kind; 228 } 229 230 logTypeResult(tr, indent); 231 assert(tr.primary.type.kind.info.kind == TypeKind.Info.Kind.func); 232 233 return tr; 234 } 235 236 static struct ComposeData { 237 TypeResults tr; 238 CFunctionName name; 239 LocationTag loc; 240 VariadicType isVariadic; 241 StorageClass storageClass; 242 Flag!"isDefinition" is_definition; 243 Language language; 244 } 245 246 ComposeData getCursorData(TypeResults tr) @safe { 247 auto data = ComposeData(tr); 248 249 data.name = CFunctionName(c_in.spelling); 250 data.loc = locToTag(c_in.location()); 251 data.is_definition = cast(Flag!"isDefinition") c_in.isDefinition; 252 data.storageClass = c_in.storageClass().toStorageClass; 253 data.language = c_in.toLanguage; 254 255 return data; 256 } 257 258 FunctionDeclResult composeFunc(ComposeData data) @safe { 259 Nullable!CFunction rval; 260 261 auto return_type = container.find!TypeKind(data.tr.primary.type.kind.info.return_); 262 if (return_type.length == 0) { 263 return FunctionDeclResult.init; 264 } 265 266 auto params = toCxParam(data.tr.primary.type.kind, container); 267 268 VariadicType is_variadic; 269 // according to C/C++ standard the last parameter is the only one 270 // that can be a variadic, therefor only needing to peek at that 271 // one. 272 if (params.length > 0) { 273 is_variadic = cast(VariadicType)() @trusted{ 274 return params[$ - 1].peek!VariadicType; 275 }(); 276 } 277 278 return FunctionDeclResult(Yes.isValid, data.tr.primary.type, data.name, 279 TypeKindAttr(return_type.front, data.tr.primary.type.kind.info.returnAttr), is_variadic, 280 data.storageClass, params, data.loc, data.is_definition, data.language); 281 } 282 283 // dfmt off 284 auto rval = pipe!(extractAndStoreRawType, 285 lookupRefToConcreteType, 286 // either break early if null or continue composing a 287 // function representation 288 (Nullable!TypeResults tr) { 289 if (tr.isNull) { 290 return FunctionDeclResult.init; 291 } else { 292 return pipe!(getCursorData, composeFunc)(tr.get); 293 } 294 } 295 ) 296 (c_in); 297 // dfmt on 298 299 return rval; 300 } 301 302 struct VarDeclResult { 303 TypeKindAttr type; 304 CppVariable name; 305 LocationTag location; 306 USRType instanceUSR; 307 StorageClass storageClass; 308 } 309 310 /// Analyze a variable declaration 311 VarDeclResult analyzeVarDecl(const(VarDecl) v, ref Container container, in uint indent) @safe { 312 return analyzeVarDecl(v.cursor, container, indent); 313 } 314 315 /// ditto 316 VarDeclResult analyzeVarDecl(const(Cursor) v, ref Container container, in uint indent) @safe 317 in { 318 import clang.c.Index : CXCursorKind; 319 320 assert(v.kind == CXCursorKind.varDecl); 321 } 322 body { 323 import clang.Cursor : Cursor; 324 import cpptooling.analyzer.clang.type : retrieveType; 325 import cpptooling.data : CppVariable; 326 327 auto type = () @trusted{ return retrieveType(v, container, indent); }(); 328 put(type, container, indent); 329 330 auto name = CppVariable(v.spelling); 331 auto loc = locToTag(v.location()); 332 auto instance_usr = USRType(v.usr); 333 // Assuming that all variable declarations have a USR 334 assert(instance_usr.length > 0); 335 336 // store the location to enable creating relations to/from this instance 337 // USR. 338 container.put(loc, instance_usr, Yes.isDefinition); 339 340 auto storage = () @trusted{ return v.storageClass.toStorageClass; }(); 341 342 return VarDeclResult(type.primary.type, name, loc, instance_usr, storage); 343 } 344 345 struct ConstructorResult { 346 TypeKindAttr type; 347 CppMethodName name; 348 CxParam[] params; 349 LocationTag location; 350 } 351 352 /** Analyze the node for actionable data. 353 * Params: 354 * v = node 355 * container = container to store the type in 356 * indent = to use when logging 357 * 358 * Returns: analyzed data. 359 */ 360 auto analyzeConstructor(const(Constructor) v, ref Container container, in uint indent) @safe { 361 auto type = () @trusted{ return retrieveType(v.cursor, container, indent); }(); 362 put(type, container, indent); 363 364 auto params = toCxParam(type.primary.type.kind, container); 365 auto name = CppMethodName(v.cursor.spelling); 366 367 return ConstructorResult(type.primary.type, name, params, type.primary.location); 368 } 369 370 struct DestructorResult { 371 TypeKindAttr type; 372 CppMethodName name; 373 CppVirtualMethod virtualKind; 374 LocationTag location; 375 } 376 377 /// ditto 378 auto analyzeDestructor(const(Destructor) v, ref Container container, in uint indent) @safe { 379 auto type = () @trusted{ return retrieveType(v.cursor, container, indent); }(); 380 put(type, container, indent); 381 382 auto name = CppMethodName(v.cursor.spelling); 383 auto virtual_kind = classify(v.cursor); 384 385 return DestructorResult(type.primary.type, name, virtual_kind, type.primary.location); 386 } 387 388 struct CxxMethodResult { 389 TypeKindAttr type; 390 CppMethodName name; 391 CxParam[] params; 392 Flag!"isOperator" isOperator; 393 CxReturnType returnType; 394 CppVirtualMethod virtualKind; 395 Flag!"isConst" isConst; 396 LocationTag location; 397 } 398 399 CxxMethodResult analyzeCxxMethod(const(CxxMethod) v, ref Container container, in uint indent) @safe { 400 return analyzeCxxMethod(v.cursor, container, indent); 401 } 402 403 /// ditto 404 CxxMethodResult analyzeCxxMethod(const(Cursor) v, ref Container container, in uint indent) @safe { 405 auto type = () @trusted{ return retrieveType(v, container, indent); }(); 406 assert(type.get.primary.type.kind.info.kind == TypeKind.Info.Kind.func); 407 put(type, container, indent); 408 409 auto name = CppMethodName(v.spelling); 410 auto params = toCxParam(type.primary.type.kind, container); 411 auto return_type = CxReturnType(TypeKindAttr(container.find!TypeKind( 412 type.primary.type.kind.info.return_).front, type.primary.type.kind.info.returnAttr)); 413 auto is_virtual = classify(v); 414 415 return CxxMethodResult(type.primary.type, name, params, 416 cast(Flag!"isOperator") isOperator(name), return_type, is_virtual, 417 cast(Flag!"isConst") type.primary.type.attr.isConst, type.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.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.primary.type.toStringDecl); 480 auto access = CppAccess(toAccessType(() @trusted{ return v.cursor.access; }().accessSpecifier)); 481 auto usr = type.primary.type.kind.usr; 482 483 if (type.primary.type.kind.info.kind == TypeKind.Info.Kind.typeRef) { 484 usr = type.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.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.primary.type, name, type.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 }