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