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