1 /** 2 Copyright: Copyright (c) 2016-2017, 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 module dextool.plugin.graphml.backend.base; 11 12 import std.format : FormatSpec; 13 import std.range : isOutputRange; 14 import std.traits : isSomeString, Unqual; 15 import std.typecons : scoped, Nullable, Flag, Yes; 16 import logger = std.experimental.logger; 17 18 import cpptooling.analyzer.clang.analyze_helper : VarDeclResult, 19 FieldDeclResult; 20 import cpptooling.analyzer.clang.ast : Visitor; 21 import cpptooling.data : resolveCanonicalType, resolvePointeeType, TypeKindAttr, 22 TypeKind, TypeAttr, toStringDecl, CppAccess, LocationTag, Location, USRType, 23 AccessType; 24 import cpptooling.data.symbol : Container; 25 26 import dextool.plugin.graphml.backend.xml; 27 import dextool.plugin.graphml.backend.interface_; 28 29 version (unittest) { 30 import unit_threaded; 31 import std.array : appender; 32 33 private struct DummyRecv { 34 import std.array : Appender; 35 36 Appender!(string)* buf; 37 38 void put(const(char)[] s) { 39 buf.put(s); 40 } 41 } 42 } 43 44 static import cpptooling.data.class_classification; 45 46 final class GraphMLAnalyzer(ReceiveT) : Visitor { 47 import cpptooling.analyzer.clang.ast : TranslationUnit, ClassDecl, VarDecl, 48 FunctionDecl, Namespace, UnexposedDecl, StructDecl, CompoundStmt, 49 Constructor, Destructor, CxxMethod, Declaration, ClassTemplate, 50 ClassTemplatePartialSpecialization, FunctionTemplate, UnionDecl; 51 import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr; 52 import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, 53 analyzeVarDecl, analyzeRecord, analyzeTranslationUnit; 54 import cpptooling.data : CppRoot, CppNs, CFunction, CxReturnType, 55 LocationTag, Location; 56 import cpptooling.data.symbol : Container; 57 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 58 59 alias visit = Visitor.visit; 60 mixin generateIndentIncrDecr; 61 62 private { 63 ReceiveT recv; 64 Controller ctrl; 65 Parameters params; 66 Products prod; 67 Container* container; 68 69 CppNs[] scope_stack; 70 } 71 72 this(ReceiveT recv, Controller ctrl, Parameters params, Products prod, ref Container container) { 73 this.recv = recv; 74 this.ctrl = ctrl; 75 this.params = params; 76 this.prod = prod; 77 this.container = &container; 78 } 79 80 void toString(Writer, Char)(scope Writer w, FormatSpec!Char formatSpec) const { 81 container.toString(w, formatSpec); 82 } 83 84 override string toString() @safe const { 85 import std.exception : assumeUnique; 86 87 char[] buf; 88 buf.reserve(100); 89 auto fmt = FormatSpec!char("%s"); 90 toString((const(char)[] s) { buf ~= s; }, fmt); 91 auto trustedUnique(T)(T t) @trusted { 92 return assumeUnique(t); 93 } 94 95 return trustedUnique(buf); 96 } 97 98 override void visit(const(TranslationUnit) v) { 99 mixin(mixinNodeLog!()); 100 101 if (!ctrl.doFile(v.cursor.spelling)) 102 return; 103 104 auto result = analyzeTranslationUnit(v, *container, indent); 105 recv.put(result); 106 107 v.accept(this); 108 } 109 110 override void visit(const(Declaration) v) { 111 mixin(mixinNodeLog!()); 112 import cpptooling.analyzer.clang.type : retrieveType; 113 import cpptooling.analyzer.clang.store : put; 114 115 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 116 return; 117 118 auto type = () @trusted{ 119 return retrieveType(v.cursor, *container, indent); 120 }(); 121 put(type, *container, indent); 122 123 if (!type.isNull) { 124 recv.put(type.get.primary.type); 125 } 126 127 v.accept(this); 128 } 129 130 override void visit(const(UnexposedDecl) v) { 131 mixin(mixinNodeLog!()); 132 133 // An unexposed may be: 134 135 // extern "C" void func_c_linkage(); 136 // UnexposedDecl "" extern "C" {... 137 // FunctionDecl "fun_c_linkage" void func_c_linkage 138 v.accept(this); 139 } 140 141 override void visit(const(VarDecl) v) { 142 mixin(mixinNodeLog!()); 143 144 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 145 return; 146 147 auto result = analyzeVarDecl(v, *container, indent); 148 149 if (scope_stack.length == 0) { 150 recv.put(result); 151 } else { 152 recv.put(result, scope_stack); 153 } 154 } 155 156 override void visit(const(FunctionDecl) v) { 157 mixin(mixinNodeLog!()); 158 159 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 160 return; 161 162 auto result = analyzeFunctionDecl(v, *container, indent); 163 recv.put(result); 164 assert(result.isValid); 165 166 () @trusted{ 167 auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl, 168 recv, *container, indent + 1); 169 v.accept(visitor); 170 }(); 171 } 172 173 override void visit(const(Namespace) v) { 174 mixin(mixinNodeLog!()); 175 176 () @trusted{ scope_stack ~= CppNs(v.cursor.spelling); }(); 177 // pop the stack when done 178 scope (exit) 179 scope_stack = scope_stack[0 .. $ - 1]; 180 181 // fill the namespace with content from the analyse 182 v.accept(this); 183 } 184 185 // === Class and Struct === 186 override void visit(const(FunctionTemplate) v) { 187 } 188 189 /** Implicit promise that THIS method will output the class node after the 190 * class has been classified. 191 */ 192 override void visit(const(ClassDecl) v) { 193 mixin(mixinNodeLog!()); 194 195 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 196 return; 197 198 auto result = analyzeRecord(v, *container, indent + 1); 199 auto node = visitRecord(v, result); 200 recv.put(result, scope_stack, node); 201 } 202 203 override void visit(const(ClassTemplate) v) { 204 mixin(mixinNodeLog!()); 205 206 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 207 return; 208 209 auto result = analyzeRecord(v, *container, indent + 1); 210 auto node = visitRecord(v, result); 211 recv.put(result, scope_stack, node); 212 } 213 214 override void visit(const(ClassTemplatePartialSpecialization) v) { 215 mixin(mixinNodeLog!()); 216 217 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 218 return; 219 220 auto result = analyzeRecord(v, *container, indent + 1); 221 auto node = visitRecord(v, result); 222 recv.put(result, scope_stack, node); 223 } 224 225 override void visit(const(StructDecl) v) { 226 mixin(mixinNodeLog!()); 227 228 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 229 return; 230 231 auto result = analyzeRecord(v, *container, indent + 1); 232 auto node = visitRecord(v, result); 233 recv.put(result, scope_stack, node); 234 } 235 236 override void visit(const(UnionDecl) v) { 237 mixin(mixinNodeLog!()); 238 239 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 240 return; 241 242 auto result = analyzeRecord(v, *container, indent + 1); 243 auto node = visitRecord(v, result); 244 recv.put(result, scope_stack, node); 245 } 246 247 private auto visitRecord(T, ResultT)(const(T) v, ref ResultT result) @trusted { 248 import std.meta : staticIndexOf; 249 import cpptooling.data.type : AccessType; 250 251 static if (staticIndexOf!(Unqual!T, ClassDecl, ClassTemplate, 252 ClassTemplatePartialSpecialization) != -1) { 253 auto access_init = AccessType.Private; 254 } else { 255 auto access_init = AccessType.Public; 256 } 257 258 auto visitor = scoped!(ClassVisitor!(ReceiveT))(result.type, scope_stack, 259 access_init, result.name, ctrl, recv, *container, indent + 1); 260 v.accept(visitor); 261 262 return visitor.node; 263 } 264 265 // BEGIN. Needed for when the methods are defined outside of the class declaration. 266 // These functions may ONLY ever create relations. Never new nodes. 267 override void visit(const(Constructor) v) { 268 mixin(mixinNodeLog!()); 269 270 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 271 return; 272 273 visitClassStructMethod(v); 274 } 275 276 override void visit(const(Destructor) v) { 277 mixin(mixinNodeLog!()); 278 279 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 280 return; 281 282 visitClassStructMethod(v); 283 } 284 285 override void visit(const(CxxMethod) v) { 286 mixin(mixinNodeLog!()); 287 288 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 289 return; 290 291 visitClassStructMethod(v); 292 } 293 294 private auto visitClassStructMethod(T)(const(T) v) { 295 import std.algorithm : among; 296 import clang.c.Index : CXCursorKind; 297 298 auto parent = v.cursor.semanticParent; 299 300 // can't handle ClassTemplates etc yet 301 if (!parent.kind.among(CXCursorKind.classDecl, CXCursorKind.structDecl)) { 302 return; 303 } 304 305 auto result = analyzeRecord(parent, *container, indent); 306 307 () @trusted{ 308 auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl, 309 recv, *container, indent + 1); 310 v.accept(visitor); 311 }(); 312 } 313 // END. 314 } 315 316 /** 317 * 318 * The $(D ClassVisitor) do not know when the analyze is finished. 319 * Therefore from the viewpoint of $(D ClassVisitor) classification is an 320 * ongoing process. It is the responsibility of the caller of $(D 321 * ClassVisitor) to use the final result of the classification together with 322 * the style. 323 */ 324 private final class ClassVisitor(ReceiveT) : Visitor { 325 import std.algorithm : map, copy, each, joiner; 326 import std.array : Appender; 327 import std.conv : to; 328 import std.typecons : scoped, TypedefType, NullableRef; 329 330 import cpptooling.analyzer.clang.ast : ClassDecl, ClassTemplate, 331 ClassTemplatePartialSpecialization, StructDecl, CxxBaseSpecifier, 332 Constructor, Destructor, CxxMethod, FieldDecl, CxxAccessSpecifier, 333 TypedefDecl, UnionDecl; 334 import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr; 335 import cpptooling.analyzer.clang.analyze_helper : analyzeRecord, 336 analyzeConstructor, analyzeDestructor, analyzeCxxMethod, 337 analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType; 338 import cpptooling.data : CppNsStack, CppNs, AccessType, CppAccess, CppDtor, 339 CppCtor, CppMethod, CppClassName, MemberVirtualType; 340 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 341 342 import cpptooling.data.class_classification : ClassificationState = State; 343 import cpptooling.data.class_classification : classifyClass, MethodKind; 344 345 alias visit = Visitor.visit; 346 347 mixin generateIndentIncrDecr; 348 349 /** Type representation of this class. 350 * Used as the source of the outgoing relations from this class. 351 */ 352 TypeKindAttr this_; 353 354 NodeRecord node; 355 356 private { 357 Controller ctrl; 358 NullableRef!ReceiveT recv; 359 360 Container* container; 361 CppNsStack scope_stack; 362 CppAccess access; 363 364 /// If the class has any members. 365 Flag!"hasMember" hasMember; 366 367 /** Classification of the class. 368 * Affected by methods. 369 */ 370 ClassificationState classification; 371 } 372 373 this(ref const(TypeKindAttr) this_, const(CppNs)[] reside_in_ns, AccessType init_access, 374 CppClassName name, Controller ctrl, ref ReceiveT recv, 375 ref Container container, in uint indent) { 376 this.ctrl = ctrl; 377 this.recv = &recv; 378 this.container = &container; 379 this.indent = indent; 380 this.scope_stack = CppNsStack(reside_in_ns.dup); 381 382 this.access = CppAccess(init_access); 383 this.classification = ClassificationState.Unknown; 384 385 this.this_ = this_; 386 387 node.usr = this_.kind.usr; 388 node.identifier = name; 389 } 390 391 /** 392 * Has hidden data dependencies on: 393 * - hasMember. 394 * - current state of classification. 395 * 396 * Will update: 397 * - the internal state classification 398 * - the style stereotype 399 */ 400 private void updateClassification(MethodKind kind, MemberVirtualType virtual_kind) { 401 this.classification = classifyClass(this.classification, kind, 402 virtual_kind, this.hasMember); 403 this.node.stereotype = this.classification.toInternal!StereoType; 404 } 405 406 /// Nested class definitions. 407 override void visit(const(ClassDecl) v) { 408 mixin(mixinNodeLog!()); 409 410 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 411 return; 412 413 auto result = analyzeRecord(v, *container, indent + 1); 414 415 auto node = visitRecord(v, result); 416 recv.put(result, scope_stack, node); 417 } 418 419 override void visit(const(ClassTemplate) v) { 420 mixin(mixinNodeLog!()); 421 422 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 423 return; 424 425 auto result = analyzeRecord(v, *container, indent + 1); 426 427 auto node = visitRecord(v, result); 428 recv.put(result, scope_stack, node); 429 } 430 431 override void visit(const(ClassTemplatePartialSpecialization) v) { 432 mixin(mixinNodeLog!()); 433 434 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 435 return; 436 437 auto result = analyzeRecord(v, *container, indent + 1); 438 439 auto node = visitRecord(v, result); 440 recv.put(result, scope_stack, node); 441 } 442 443 override void visit(const(StructDecl) v) { 444 mixin(mixinNodeLog!()); 445 446 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 447 return; 448 449 auto result = analyzeRecord(v, *container, indent + 1); 450 451 auto node = visitRecord(v, result); 452 recv.put(result, scope_stack, node); 453 } 454 455 override void visit(const(UnionDecl) v) { 456 mixin(mixinNodeLog!()); 457 458 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 459 return; 460 461 auto result = analyzeRecord(v, *container, indent + 1); 462 463 auto node = visitRecord(v, result); 464 recv.put(result, scope_stack, node); 465 } 466 467 private auto visitRecord(T, ResultT)(const(T) v, ref ResultT result) @trusted { 468 import std.meta : staticIndexOf; 469 import cpptooling.data.type : AccessType; 470 471 scope_stack ~= CppNs(cast(string) result.name); 472 scope (exit) 473 scope_stack = scope_stack[0 .. $ - 1]; 474 475 static if (staticIndexOf!(Unqual!T, ClassDecl, ClassTemplate, 476 ClassTemplatePartialSpecialization) != -1) { 477 auto access_init = AccessType.Private; 478 } else { 479 auto access_init = AccessType.Public; 480 } 481 482 auto visitor = scoped!(ClassVisitor!(ReceiveT))(result.type, scope_stack, 483 access_init, result.name, ctrl, recv, *container, indent + 1); 484 v.accept(visitor); 485 486 return visitor.node; 487 } 488 489 /// Analyze the inheritance(s). 490 override void visit(const(CxxBaseSpecifier) v) { 491 mixin(mixinNodeLog!()); 492 493 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 494 return; 495 496 auto result = analyzeCxxBaseSpecified(v, *container, indent); 497 498 recv.put(this_, result); 499 } 500 501 override void visit(const(Constructor) v) { 502 mixin(mixinNodeLog!()); 503 504 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 505 return; 506 507 auto result = analyzeConstructor(v, *container, indent); 508 updateClassification(MethodKind.Ctor, MemberVirtualType.Unknown); 509 510 auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access); 511 auto func = NodeFunction(result.type.kind.usr, tor.toString, result.name, result.location); 512 node.methods.put(func); 513 514 recv.put(this_, result, access); 515 516 () @trusted{ 517 auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl, 518 recv, *container, indent + 1); 519 v.accept(visitor); 520 }(); 521 } 522 523 override void visit(const(Destructor) v) { 524 mixin(mixinNodeLog!()); 525 526 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 527 return; 528 529 auto result = analyzeDestructor(v, *container, indent); 530 updateClassification(MethodKind.Dtor, cast(MemberVirtualType) result.virtualKind); 531 532 auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind); 533 auto func = NodeFunction(result.type.kind.usr, tor.toString, result.name, result.location); 534 node.methods.put(func); 535 536 recv.put(this_, result, access); 537 538 () @trusted{ 539 auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl, 540 recv, *container, indent + 1); 541 v.accept(visitor); 542 }(); 543 } 544 545 override void visit(const(CxxMethod) v) { 546 mixin(mixinNodeLog!()); 547 import cpptooling.data : CppMethod, CppConstMethod; 548 549 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 550 return; 551 552 auto result = analyzeCxxMethod(v, *container, indent); 553 updateClassification(MethodKind.Method, cast(MemberVirtualType) result.virtualKind); 554 555 auto method = CppMethod(result.type.kind.usr, result.name, result.params, 556 result.returnType, access, CppConstMethod(result.isConst), result.virtualKind); 557 auto func = NodeFunction(result.type.kind.usr, method.toString, 558 result.name, result.location); 559 node.methods.put(func); 560 561 recv.put(this_, result, access); 562 563 () @trusted{ 564 auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl, 565 recv, *container, indent + 1); 566 v.accept(visitor); 567 }(); 568 } 569 570 override void visit(const(FieldDecl) v) { 571 mixin(mixinNodeLog!()); 572 573 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 574 return; 575 576 auto result = analyzeFieldDecl(v, *container, indent); 577 578 auto field = NodeField(result.instanceUSR, result.name, result.type, 579 access, decideColor(result), result.location); 580 node.attributes.put(field); 581 582 // TODO probably not necessary for classification to store it as a 583 // member. Instead extend MethodKind to having a "Member". 584 hasMember = Yes.hasMember; 585 updateClassification(MethodKind.Unknown, MemberVirtualType.Unknown); 586 587 recv.put(this_, result, access); 588 } 589 590 override void visit(const(TypedefDecl) v) { 591 mixin(mixinNodeLog!()); 592 import cpptooling.analyzer.clang.type : retrieveType; 593 import cpptooling.analyzer.clang.store : put; 594 595 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 596 return; 597 598 auto result = () @trusted{ 599 return retrieveType(v.cursor, *container, indent + 1); 600 }(); 601 put(result, *container, indent + 1); 602 603 if (!result.isNull) { 604 auto tnode = NodeType(result.primary.type); 605 node.types.put(tnode); 606 } 607 } 608 609 override void visit(const(CxxAccessSpecifier) v) @trusted { 610 mixin(mixinNodeLog!()); 611 access = CppAccess(toAccessType(v.cursor.access.accessSpecifier)); 612 } 613 } 614 615 /** Visit a function or method body. 616 * 617 */ 618 private final class BodyVisitor(ReceiveT) : Visitor { 619 import std.algorithm; 620 import std.array; 621 import std.conv; 622 import std.typecons; 623 624 import cpptooling.analyzer.clang.ast; 625 import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr; 626 import cpptooling.analyzer.clang.analyze_helper; 627 import cpptooling.data.representation; 628 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 629 630 alias visit = Visitor.visit; 631 632 mixin generateIndentIncrDecr; 633 634 /** Type representation of parent. 635 * Used as the source of the outgoing relations from this class. 636 */ 637 TypeKindAttr parent; 638 639 private { 640 Controller ctrl; 641 NullableRef!ReceiveT recv; 642 643 Container* container; 644 } 645 646 this(const(TypeKindAttr) parent, Controller ctrl, ref ReceiveT recv, 647 ref Container container, const uint indent) { 648 this.parent = parent; 649 this.ctrl = ctrl; 650 this.recv = &recv; 651 this.container = &container; 652 this.indent = indent; 653 } 654 655 override void visit(const(Declaration) v) { 656 mixin(mixinNodeLog!()); 657 658 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 659 return; 660 661 v.accept(this); 662 } 663 664 override void visit(const(Expression) v) { 665 mixin(mixinNodeLog!()); 666 667 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 668 return; 669 670 v.accept(this); 671 } 672 673 override void visit(const(Statement) v) { 674 mixin(mixinNodeLog!()); 675 676 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 677 return; 678 679 v.accept(this); 680 } 681 682 override void visit(const(VarDecl) v) { 683 mixin(mixinNodeLog!()); 684 import cpptooling.analyzer.clang.cursor_backtrack : isGlobalOrNamespaceScope; 685 686 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 687 return; 688 689 // accessing a global 690 if (v.cursor.isGlobalOrNamespaceScope) { 691 auto result = analyzeVarDecl(v, *container, indent); 692 recv.put(parent, result.type); 693 } 694 695 v.accept(this); 696 } 697 698 override void visit(const(CallExpr) v) @trusted { 699 mixin(mixinNodeLog!()); 700 // assuming the needed reference information of the node is found by traversing the AST 701 702 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 703 return; 704 705 auto visitor = scoped!(RefVisitor)(ctrl, *container, indent + 1); 706 v.accept(visitor); 707 708 processRef(visitor); 709 } 710 711 override void visit(const(DeclRefExpr) v) @trusted { 712 mixin(mixinNodeLog!()); 713 714 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 715 return; 716 717 visitRef(v); 718 } 719 720 override void visit(const(MemberRefExpr) v) @trusted { 721 mixin(mixinNodeLog!()); 722 723 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 724 return; 725 726 visitRef(v); 727 } 728 729 private: 730 void visitRef(T)(const(T) v) @trusted { 731 // assuming there are no sub nodes so therefor not calling v.accept(visitor) 732 733 auto visitor = scoped!(RefVisitor)(ctrl, *container, indent + 1); 734 visitor.visitReferenced(v.cursor); 735 processRef(visitor); 736 } 737 738 void processRef(T)(ref const(T) r) { 739 foreach (usr; r.destinations.data) { 740 recv.put(parent.kind.usr, usr); 741 } 742 743 foreach (type; r.extraTypes.data) { 744 recv.putBodyNode(type); 745 } 746 } 747 } 748 749 /** Analyze a reference node. 750 * The data gathered is: 751 * - types 752 * - accessing global variables 753 * - function calls 754 * The gathered targets are stored in $(D destinations). 755 */ 756 private final class RefVisitor : Visitor { 757 import std.algorithm; 758 import std.array; 759 import std.conv; 760 import std.typecons; 761 762 import clang.Cursor : Cursor; 763 import clang.c.Index : CXCursorKind; 764 765 import cpptooling.analyzer.clang.ast; 766 import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr; 767 import cpptooling.analyzer.clang.analyze_helper; 768 import cpptooling.data.representation; 769 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 770 771 alias visit = Visitor.visit; 772 773 mixin generateIndentIncrDecr; 774 775 // It is assumed that the nodes the edges connect are analysed and 776 // retrieved from elsewhere. 777 Appender!(USRType[]) destinations; 778 779 // The assumtion fall apart for functions. 780 // For example __builtins will trigger cases where an edge is created but no node. 781 // To handle this extra dummy "type node" are created for functions 782 Appender!(TypeKindAttr[]) extraTypes; 783 784 private { 785 Controller ctrl; 786 Container* container; 787 bool[USRType] visited; 788 } 789 790 this(Controller ctrl, ref Container container, const uint indent) { 791 this.ctrl = ctrl; 792 this.container = &container; 793 this.indent = indent; 794 } 795 796 void visitReferenced(const(Cursor) cursor) @trusted { 797 import cpptooling.analyzer.clang.type : makeEnsuredUSR; 798 799 auto ref_ = cursor.referenced; 800 801 // avoid cycles 802 auto usr = makeEnsuredUSR(ref_, indent + 1); 803 if (usr in visited) { 804 return; 805 } 806 visited[usr] = true; 807 808 logNode(ref_, indent); 809 810 import cpptooling.analyzer.clang.ast.tree : dispatch; 811 812 dispatch(ref_, this); 813 } 814 815 // Begin: Referencing 816 override void visit(const(MemberRefExpr) v) { 817 mixin(mixinNodeLog!()); 818 819 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 820 return; 821 822 visitReferenced(v.cursor); 823 v.accept(this); 824 } 825 826 override void visit(const(DeclRefExpr) v) { 827 mixin(mixinNodeLog!()); 828 829 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 830 return; 831 832 visitReferenced(v.cursor); 833 v.accept(this); 834 } 835 // End: Referencing 836 837 override void visit(const(FunctionDecl) v) { 838 mixin(mixinNodeLog!()); 839 840 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 841 return; 842 843 auto result = analyzeFunctionDecl(v, *container, indent); 844 if (result.isValid) { 845 destinations.put(result.type.kind.usr); 846 extraTypes.put(result.type); 847 } 848 } 849 850 override void visit(const(VarDecl) v) { 851 mixin(mixinNodeLog!()); 852 import cpptooling.analyzer.clang.cursor_backtrack : isGlobalOrNamespaceScope; 853 854 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 855 return; 856 857 // the root node for the visitor is a reference. 858 // it may therefor be an access to a global variable. 859 860 // accessing a global 861 if (v.cursor.isGlobalOrNamespaceScope) { 862 auto result = analyzeVarDecl(v, *container, indent); 863 destinations.put(result.instanceUSR); 864 } 865 } 866 867 // Begin: Class struct 868 override void visit(const(FieldDecl) v) { 869 mixin(mixinNodeLog!()); 870 871 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 872 return; 873 874 auto result = analyzeFieldDecl(v, *container, indent); 875 destinations.put(result.instanceUSR); 876 // a template may result in extra nodes. e.g std::string's .c_str() 877 extraTypes.put(result.type); 878 } 879 880 override void visit(const(CxxMethod) v) { 881 mixin(mixinNodeLog!()); 882 883 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 884 return; 885 886 auto result = analyzeCxxMethod(v, *container, indent); 887 destinations.put(result.type.kind.usr); 888 // a template may result in extra nodes. e.g std::string's .c_str() 889 extraTypes.put(result.type); 890 } 891 892 override void visit(const(Constructor) v) { 893 mixin(mixinNodeLog!()); 894 895 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 896 return; 897 898 auto result = analyzeConstructor(v, *container, indent); 899 destinations.put(result.type.kind.usr); 900 // a template may result in extra nodes. e.g std::string's .c_str() 901 extraTypes.put(result.type); 902 } 903 904 override void visit(const(Destructor) v) { 905 mixin(mixinNodeLog!()); 906 907 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 908 return; 909 910 auto result = analyzeDestructor(v, *container, indent); 911 destinations.put(result.type.kind.usr); 912 // a template may result in extra nodes. e.g std::string's .c_str() 913 extraTypes.put(result.type); 914 } 915 // End: Class struct 916 917 // Begin: Generic 918 override void visit(const(Declaration) v) { 919 mixin(mixinNodeLog!()); 920 921 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 922 return; 923 924 v.accept(this); 925 } 926 927 override void visit(const(Expression) v) { 928 mixin(mixinNodeLog!()); 929 930 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 931 return; 932 933 v.accept(this); 934 } 935 936 override void visit(const(Statement) v) { 937 mixin(mixinNodeLog!()); 938 939 if (!ctrl.doFile(v.cursor.location.spelling.file.name)) 940 return; 941 942 v.accept(this); 943 } 944 // End: Generic 945 } 946 947 private T toInternal(T, S)(S value) @safe pure nothrow @nogc 948 if (isSomeString!T && (is(S == CppAccess) || is(S == AccessType))) { 949 import cpptooling.data : AccessType; 950 951 final switch (value) { 952 case AccessType.Private: 953 return "-"; 954 case AccessType.Protected: 955 return "#"; 956 case AccessType.Public: 957 return "+"; 958 } 959 } 960 961 private T toInternal(T, S)(S value) @safe pure nothrow @nogc 962 if (is(T == StereoType) && is(S == cpptooling.data.class_classification.State)) { 963 final switch (value) with (cpptooling.data.class_classification.State) { 964 case Unknown: 965 case Normal: 966 case Virtual: 967 return StereoType.None; 968 case Abstract: 969 return StereoType.Abstract; 970 case VirtualDtor: // only one method, a d'tor and it is virtual 971 case Pure: 972 return StereoType.Interface; 973 } 974 } 975 976 /** Deduct if the type is primitive from the point of view of TransformToXmlStream. 977 * 978 * Filtering out arrays or ptrs of primitive types as to not result in too much 979 * noise. 980 */ 981 private bool isPrimitive(T, LookupT)(const T data, LookupT lookup) @safe nothrow { 982 static if (is(T == TypeKind)) { 983 switch (data.info.kind) with (TypeKind.Info) { 984 case Kind.primitive: 985 return true; 986 case Kind.array: 987 foreach (ele; lookup.kind(data.info.element)) { 988 return ele.info.kind == Kind.primitive; 989 } 990 return false; 991 case Kind.pointer: 992 foreach (ele; lookup.kind(data.info.pointee)) { 993 return ele.info.kind == Kind.primitive; 994 } 995 return false; 996 default: 997 return false; 998 } 999 } else static if (is(T == NodeData)) { 1000 switch (data.tag.kind) with (NodeData.Tag) { 1001 case Kind.type: 1002 auto node = cast(NodeType) data.tag; 1003 return node.type.kind.isPrimitive(lookup); 1004 default: 1005 return false; 1006 } 1007 } else { 1008 static assert(0, "unsupported type: " ~ T.stringof); 1009 } 1010 } 1011 1012 package struct NodeData { 1013 import taggedalgebraic : TaggedAlgebraic; 1014 1015 alias Tag = TaggedAlgebraic!TagUnion; 1016 1017 Tag tag; 1018 alias tag this; 1019 1020 static union TagUnion { 1021 typeof(null) null_; 1022 NodeFunction func; 1023 NodeType type; 1024 NodeVariable variable; 1025 NodeRecord record; 1026 NodeFile file; 1027 NodeNamespace namespace; 1028 NodeField field; 1029 NodeFallback fallback; 1030 } 1031 1032 void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) { 1033 static import std.format; 1034 import std.traits : FieldNameTuple; 1035 1036 enum case_ = "case Tag.Kind.%s: auto n = cast(%s) tag; nodeToXml(n, w); break;\n"; 1037 1038 final switch (tag.kind) { 1039 foreach (tag_field; FieldNameTuple!TagUnion) { 1040 static if (tag_field == "null_") { 1041 mixin("case Kind.null_: break;\n"); 1042 } else { 1043 alias tag_field_t = typeof(__traits(getMember, TagUnion, tag_field)); 1044 mixin(std.format.format(case_, tag_field, tag_field_t.stringof)); 1045 } 1046 } 1047 } 1048 } 1049 } 1050 1051 private ColorKind decideColor(ref const(VarDeclResult) result) @safe pure nothrow @nogc { 1052 import cpptooling.data.type : StorageClass; 1053 1054 auto color = ColorKind.global; 1055 if (result.type.attr.isConst) { 1056 color = ColorKind.globalConst; 1057 } else if (result.storageClass == StorageClass.Static) { 1058 color = ColorKind.globalStatic; 1059 } 1060 1061 return color; 1062 } 1063 1064 private ColorKind decideColor(ref const(FieldDeclResult) result) @safe pure nothrow @nogc { 1065 // TODO extend to differentiate between const and mutable fields. 1066 return ColorKind.field; 1067 } 1068 1069 /** Transform analyze data to a xml stream. 1070 * 1071 * XML nodes must never be duplicated. 1072 * An edge source or target must be to nodes that exist. 1073 * 1074 * # Strategy `class` 1075 * The generation of a `node` is delayed as long as possible for a class 1076 * declaration in the hope of finding the definition. 1077 * The delay is implemented with a cache. 1078 * When finalize is called the cache is forcefully transformed to `nodes`. 1079 * Even those symbols that only have a declaration as location. 1080 */ 1081 class TransformToXmlStream(RecvXmlT, LookupT) if (isOutputRange!(RecvXmlT, char)) { 1082 import std.range : only; 1083 import std.typecons : NullableRef; 1084 1085 import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult, 1086 RecordResult, FieldDeclResult, CxxMethodResult, ConstructorResult, 1087 DestructorResult, VarDeclResult, FunctionDeclResult, 1088 TranslationUnitResult; 1089 import cpptooling.data.type : USRType, LocationTag, Location, CppNs, 1090 TypeKindAttr, TypeKind, TypeAttr, toStringDecl; 1091 import dextool.plugin.utility : MarkArray; 1092 1093 private { 1094 /// Nodes cached during an analyze phase 1095 MarkArray!NodeData node_cache; 1096 1097 /// nodes may never be duplicated. If they are it is a violation of the 1098 /// data format. 1099 bool[USRType] streamed_nodes; 1100 /// Ensure that there are only ever one relation between two entities. 1101 /// It avoids the scenario (which is common) of thick patches of 1102 /// relations to common nodes. 1103 bool[USRType] streamed_edges; 1104 1105 NullableRef!RecvXmlT recv; 1106 LookupT lookup; 1107 } 1108 1109 this(ref RecvXmlT recv, LookupT lookup) { 1110 this.recv = &recv; 1111 this.lookup = lookup; 1112 } 1113 1114 @safe: 1115 1116 /// 1117 void finalize() { 1118 if (node_cache.data.length == 0) { 1119 return; 1120 } 1121 1122 debug { 1123 import std.range : enumerate; 1124 1125 logger.tracef("%d nodes left in cache", node_cache.data.length); 1126 foreach (idx, ref n; node_cache.data.enumerate) { 1127 logger.tracef(" %d: %s", idx + 1, n.usr); 1128 } 1129 } 1130 1131 void anyLocation(ref const(NodeData) type, ref const(LocationTag) loc) { 1132 if (type.isPrimitive(lookup)) { 1133 return; 1134 } 1135 1136 import std.conv : to; 1137 1138 debug logger.tracef("creating node %s (%s)", cast(string) type.usr, 1139 type.tag.kind.to!string()); 1140 1141 NodeData node = type; 1142 node.location = loc; 1143 nodeIfMissing(streamed_nodes, recv, type.usr, node); 1144 } 1145 1146 LocationCallback cb; 1147 cb.unknown = &anyLocation; 1148 cb.declaration = &anyLocation; 1149 cb.definition = &anyLocation; 1150 1151 resolveLocation(cb, node_cache.data, lookup); 1152 node_cache.clear; 1153 } 1154 1155 /// 1156 void put(ref const(TranslationUnitResult) result) { 1157 xmlComment(recv, result.fileName); 1158 1159 if (node_cache.data.length == 0) { 1160 return; 1161 } 1162 1163 // empty the cache if anything is left in it 1164 1165 debug logger.tracef("%d nodes left in cache", node_cache.data.length); 1166 1167 // ugly hack. 1168 // used by putDefinition 1169 // incremented in the foreach 1170 size_t idx = 0; 1171 1172 void putDeclaration(ref const(NodeData) type, ref const(LocationTag) loc) { 1173 // hoping for the best that a definition is found later on. 1174 if (type.isPrimitive(lookup)) { 1175 node_cache.markForRemoval(idx); 1176 } 1177 } 1178 1179 void putDefinition(ref const(NodeData) type, ref const(LocationTag) loc) { 1180 import std.algorithm : among; 1181 1182 if (type.tag.kind.among(NodeData.Tag.Kind.record)) { 1183 // do nothing. delaying until finalize. 1184 // a struct/class (record) bypasses the cache. 1185 } else if (!type.isPrimitive(lookup)) { 1186 NodeData node = type; 1187 node.location = loc; 1188 nodeIfMissing(streamed_nodes, recv, type.usr, node); 1189 } 1190 1191 node_cache.markForRemoval(idx); 1192 } 1193 1194 LocationCallback cb; 1195 cb.unknown = &putDeclaration; 1196 cb.declaration = &putDeclaration; 1197 cb.definition = &putDefinition; 1198 1199 foreach (ref item; node_cache.data) { 1200 if (item.tag.kind == NodeData.Tag.Kind.fallback) { 1201 // a fallback node is most likely replaced by the correct node 1202 if (item.tag.usr in streamed_nodes) { 1203 node_cache.markForRemoval(idx); 1204 } 1205 } else { 1206 resolveLocation(cb, only(item), lookup); 1207 } 1208 1209 ++idx; 1210 } 1211 1212 debug logger.tracef("%d nodes left in cache", node_cache.data.length); 1213 1214 node_cache.doRemoval; 1215 } 1216 1217 /** Create a raw relation between two identifiers. 1218 */ 1219 void put(const(USRType) src, const(USRType) dst) { 1220 addEdge(streamed_nodes, node_cache, streamed_edges, recv, src, dst); 1221 } 1222 1223 /** Create a raw relation between two types. 1224 */ 1225 void put(const(TypeKindAttr) src, const(TypeKindAttr) dst) { 1226 edgeIfNotPrimitive(streamed_nodes, node_cache, streamed_edges, recv, src, dst, lookup); 1227 } 1228 1229 /** Create a raw node for a type. 1230 */ 1231 void put(const(TypeKindAttr) type) { 1232 if (!type.kind.isPrimitive(lookup)) { 1233 auto node = NodeType(type, LocationTag(null)); 1234 nodeIfMissing(streamed_nodes, recv, type.kind.usr, NodeData(NodeData.Tag(node))); 1235 } 1236 } 1237 1238 /** Create a _possible_ node from a body inspection. 1239 * It delays the creation of the node until the translation unit is fully 1240 * analyzed. 1241 */ 1242 void putBodyNode(const(TypeKindAttr) type) { 1243 if (!type.kind.isPrimitive(lookup)) { 1244 auto node = NodeType(type, LocationTag(null)); 1245 putToCache(NodeData(NodeData.Tag(node))); 1246 } 1247 } 1248 1249 /** A free variable declaration. 1250 * 1251 * This method do NOT handle those inside a function/method/namespace. 1252 */ 1253 void put(ref const(VarDeclResult) result) { 1254 Nullable!USRType file_usr = addFileNode(streamed_nodes, recv, result.location); 1255 addVarDecl(file_usr, result); 1256 } 1257 1258 /** A free variable declaration in a namespace. 1259 * 1260 * TODO maybe connect the namespace to the file? 1261 */ 1262 void put(ref const(VarDeclResult) result, CppNs[] ns) 1263 in { 1264 assert(ns.length > 0); 1265 } 1266 body { 1267 auto ns_usr = addNamespaceNode(streamed_nodes, recv, ns); 1268 addVarDecl(ns_usr, result); 1269 } 1270 1271 private void addVarDecl(Nullable!USRType parent, ref const(VarDeclResult) result) { 1272 { // instance node 1273 auto node = NodeVariable(result.instanceUSR, result.name, 1274 result.type, decideColor(result), result.location); 1275 nodeIfMissing(streamed_nodes, recv, result.instanceUSR, NodeData(NodeData.Tag(node))); 1276 } 1277 1278 if (!parent.isNull) { 1279 // connect namespace to instance 1280 addEdge(streamed_nodes, node_cache, streamed_edges, recv, parent, result.instanceUSR); 1281 } 1282 1283 // type node 1284 if (!result.type.kind.isPrimitive(lookup)) { 1285 auto node = NodeType(result.type, result.location); 1286 putToCache(NodeData(NodeData.Tag(node))); 1287 addEdge(streamed_nodes, node_cache, streamed_edges, recv, 1288 result.instanceUSR, result.type.kind.usr); 1289 } 1290 } 1291 1292 /** Accessing a global. 1293 * 1294 * Assuming that src is already put in the cache. 1295 * Assuming that target is already in cache or will be in the future when 1296 * traversing the AST. 1297 * */ 1298 void put(ref const(TypeKindAttr) src, ref const(VarDeclResult) result) { 1299 addEdge(streamed_nodes, node_cache, streamed_edges, recv, 1300 src.kind.usr, result.instanceUSR); 1301 } 1302 1303 /// 1304 void put(ref const(FunctionDeclResult) result) { 1305 import std.algorithm : map, filter, joiner; 1306 import std.range : only, chain; 1307 import cpptooling.data : unpackParam; 1308 1309 auto src = result.type; 1310 1311 { 1312 auto node = NodeFunction(src.kind.usr, 1313 result.type.toStringDecl(result.name), result.name, result.location); 1314 putToCache(NodeData(NodeData.Tag(node))); 1315 } 1316 1317 // dfmt off 1318 foreach (target; chain(only(cast(TypeKindAttr) result.returnType), 1319 result.params 1320 .map!(a => a.unpackParam) 1321 .filter!(a => !a.isVariadic) 1322 .map!(a => a.type) 1323 .map!(a => resolvePointeeType(a.kind, a.attr, lookup)) 1324 .joiner 1325 .map!(a => TypeKindAttr(a.kind, TypeAttr.init)))) { 1326 putToCache(NodeData(NodeData.Tag(NodeType(target)))); 1327 edgeIfNotPrimitive(streamed_nodes, node_cache, streamed_edges, recv, src, target, lookup); 1328 } 1329 // dfmt on 1330 } 1331 1332 /** Calls from src to result. 1333 * 1334 * Assuming that src is already put in the cache. 1335 * 1336 * Only interested in the relation from src to the function. 1337 */ 1338 void put(ref const(TypeKindAttr) src, ref const(FunctionDeclResult) result) { 1339 // TODO investigate if the resolve is needed. I don't think so. 1340 auto target = resolveCanonicalType(result.type.kind, result.type.attr, lookup).front; 1341 1342 auto node = NodeFunction(result.type.kind.usr, 1343 result.type.toStringDecl(result.name), result.name, result.location); 1344 putToCache(NodeData(NodeData.Tag(node))); 1345 1346 edgeIfNotPrimitive(streamed_nodes, node_cache, streamed_edges, recv, src, target, lookup); 1347 } 1348 1349 /** The node_cache may contain class/struct that have been put there by a parameter item. 1350 * Therefor bypass the cache when a definition is found. 1351 */ 1352 void put(ref const(RecordResult) result, CppNs[] ns, NodeRecord in_node) { 1353 auto node = in_node; 1354 1355 if (node.identifier.length == 0) { 1356 // a C typedef. 1357 // e.g. typedef struct { .. } Foo; 1358 auto type = resolveCanonicalType(result.type.kind, result.type.attr, lookup).front; 1359 node.identifier = type.kind.toStringDecl(TypeAttr.init); 1360 } 1361 1362 if (result.type.attr.isDefinition) { 1363 node.location = result.location; 1364 nodeIfMissing(streamed_nodes, recv, result.type.kind.usr, 1365 NodeData(NodeData.Tag(node))); 1366 } else { 1367 putToCache(NodeData(NodeData.Tag(node))); 1368 } 1369 1370 auto ns_usr = addNamespaceNode(streamed_nodes, recv, ns); 1371 if (!ns_usr.isNull) { 1372 addEdge(streamed_nodes, node_cache, streamed_edges, recv, ns_usr, result.type); 1373 } 1374 } 1375 1376 /// Create relations to the parameters of a constructor. 1377 void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) { 1378 import std.algorithm : map, filter, joiner; 1379 import cpptooling.data : unpackParam; 1380 1381 // dfmt off 1382 foreach (target; result.params 1383 .map!(a => a.unpackParam) 1384 .filter!(a => !a.isVariadic) 1385 .map!(a => resolvePointeeType(a.type.kind, a.type.attr, lookup)) 1386 .joiner 1387 .map!(a => TypeKindAttr(a.kind, TypeAttr.init))) { 1388 if (!target.kind.isPrimitive(lookup)) { 1389 putToCache(NodeData(NodeData.Tag(NodeType(target)))); 1390 addEdge(streamed_nodes, node_cache, streamed_edges, recv, result.type.kind.usr, target.kind.usr); 1391 } 1392 } 1393 // dfmt on 1394 } 1395 1396 /// No parameters in a destructor so skipping. 1397 void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) { 1398 // do nothing 1399 } 1400 1401 /// Create relations to the parameters of a method. 1402 void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) { 1403 import std.algorithm : map, filter, joiner; 1404 import std.range : only, chain; 1405 import cpptooling.data : unpackParam; 1406 1407 // dfmt off 1408 foreach (target; chain(only(cast(TypeKindAttr) result.returnType), 1409 result.params 1410 .map!(a => a.unpackParam) 1411 .filter!(a => !a.isVariadic) 1412 .map!(a => resolvePointeeType(a.type.kind, a.type.attr, lookup)) 1413 .joiner) 1414 .map!(a => TypeKindAttr(a.kind, TypeAttr.init))) { 1415 if (!target.kind.isPrimitive(lookup)) { 1416 putToCache(NodeData(NodeData.Tag(NodeType(target)))); 1417 addEdge(streamed_nodes, node_cache, streamed_edges, recv, result.type.kind.usr, target.kind.usr); 1418 } 1419 } 1420 // dfmt on 1421 } 1422 1423 /** Relation of a class/struct's field to the it is an instance of type. 1424 * 1425 * The field node is aggregated, and thus handled, in the NodeRecord. 1426 */ 1427 void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) { 1428 if (result.type.kind.isPrimitive(lookup)) { 1429 return; 1430 } 1431 1432 auto target = resolvePointeeType(result.type.kind, result.type.attr, lookup).front; 1433 1434 if (!target.kind.isPrimitive(lookup)) { 1435 putToCache(NodeData(NodeData.Tag(NodeType(target)))); 1436 addEdge(streamed_nodes, node_cache, streamed_edges, recv, 1437 result.instanceUSR, target.kind.usr); 1438 } 1439 } 1440 1441 /// Avoid code duplication by creating nodes via the node_cache. 1442 void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) { 1443 putToCache(NodeData(NodeData.Tag(NodeType(result.type)))); 1444 // by definition it can never be a primitive type so no check needed. 1445 addEdge(streamed_nodes, node_cache, streamed_edges, recv, src.kind.usr, 1446 result.canonicalUSR, EdgeKind.Generalization); 1447 } 1448 1449 private: 1450 1451 import std.range : ElementType; 1452 1453 /** Used for callback to distinguish the type of location that has been 1454 * resolved. 1455 */ 1456 struct LocationCallback { 1457 void delegate(ref const(NodeData) type, ref const(LocationTag) loc) @safe unknown; 1458 void delegate(ref const(NodeData) type, ref const(LocationTag) loc) @safe declaration; 1459 void delegate(ref const(NodeData) type, ref const(LocationTag) loc) @safe definition; 1460 } 1461 1462 /** Resolve a type and its location. 1463 * 1464 * Performs a callback to either: 1465 * - callback_def with the resolved type:s TypeKindAttr for the type and 1466 * location of the definition. 1467 * - callback_decl with the resolved type:s TypeKindAttr. 1468 * */ 1469 static void resolveLocation(Range, LookupT)(LocationCallback callback, 1470 Range range, LookupT lookup) 1471 if (is(Unqual!(ElementType!Range) == NodeData) && __traits(hasMember, 1472 LookupT, "kind") && __traits(hasMember, LookupT, "location")) { 1473 import std.algorithm : map; 1474 import std.typecons : tuple; 1475 1476 // dfmt off 1477 foreach (ref a; range 1478 // a tuple of (NodeData, DeclLocation) 1479 .map!(a => tuple(a, lookup.location(a.usr)))) { 1480 // no location? 1481 if (a[1].length == 0) { 1482 LocationTag noloc; 1483 callback.unknown(a[0], noloc); 1484 } 1485 1486 auto loc = a[1].front; 1487 1488 if (loc.hasDefinition) { 1489 callback.definition(a[0], loc.definition); 1490 } else if (loc.hasDeclaration) { 1491 callback.declaration(a[0], loc.declaration); 1492 } else { 1493 // no location? 1494 LocationTag noloc; 1495 callback.unknown(a[0], noloc); 1496 } 1497 } 1498 // dfmt on 1499 } 1500 1501 static auto toRelativePath(const LocationTag loc) { 1502 import std.path : relativePath; 1503 1504 if (loc.kind == LocationTag.Kind.noloc) { 1505 return loc; 1506 } 1507 1508 string rel; 1509 () @trusted{ rel = relativePath(loc.file); }(); 1510 1511 return LocationTag(Location(rel, loc.line, loc.column)); 1512 } 1513 1514 void putToCache(const NodeData data) { 1515 // lower the number of allocations by checking in the hash table. 1516 if (data.usr in streamed_nodes) { 1517 return; 1518 } 1519 1520 node_cache.put(data); 1521 } 1522 1523 // The following functions result in xml data being written. 1524 1525 static Nullable!USRType addNamespaceNode(NodeStoreT, RecvT)(ref NodeStoreT nodes, 1526 ref RecvT recv, CppNs[] ns) { 1527 if (ns.length == 0) { 1528 return Nullable!USRType(); 1529 } 1530 1531 import cpptooling.data.type : toStringNs; 1532 1533 auto ns_usr = USRType(ns.toStringNs); 1534 auto node = NodeData(NodeData.Tag(NodeNamespace(ns_usr))); 1535 nodeIfMissing(nodes, recv, ns_usr, node); 1536 1537 return Nullable!USRType(ns_usr); 1538 } 1539 1540 static USRType addFileNode(NodeStoreT, RecvT, LocationT)(ref NodeStoreT nodes, 1541 ref RecvT recv, LocationT location) { 1542 auto file_usr = cast(USRType) location.file; 1543 1544 if (file_usr !in nodes) { 1545 auto node = NodeData(NodeData.Tag(NodeFile(file_usr))); 1546 nodeIfMissing(nodes, recv, file_usr, node); 1547 } 1548 1549 return file_usr; 1550 } 1551 1552 /** 1553 * Params: 1554 * nodes = a AA with USRType as key 1555 * recv = the receiver of the xml data 1556 * node_usr = the unique USR for the node 1557 * node = either the TypeKindAttr of the node or a type supporting 1558 * `.toString` taking a generic writer as argument. 1559 */ 1560 static void nodeIfMissing(NodeStoreT, RecvT, NodeT)(ref NodeStoreT nodes, 1561 ref RecvT recv, USRType node_usr, NodeT node) { 1562 if (node_usr in nodes) { 1563 return; 1564 } 1565 1566 node.toString(recv, FormatSpec!char("%s")); 1567 nodes[node_usr] = true; 1568 } 1569 1570 static bool isEitherPrimitive(LookupT)(TypeKindAttr t0, TypeKindAttr t1, LookupT lookup) { 1571 if (t0.kind.info.kind == TypeKind.Info.Kind.null_ 1572 || t1.kind.info.kind == TypeKind.Info.Kind.null_ 1573 || t0.kind.isPrimitive(lookup) || t1.kind.isPrimitive(lookup)) { 1574 return true; 1575 } 1576 1577 return false; 1578 } 1579 1580 static void edgeIfNotPrimitive(NoDupNodesT, NodeStoreT, EdgeStoreT, RecvT, LookupT)( 1581 ref NoDupNodesT nodup_nodes, ref NodeStoreT node_store, 1582 ref EdgeStoreT edges, ref RecvT recv, TypeKindAttr src, 1583 TypeKindAttr target, LookupT lookup) { 1584 if (isEitherPrimitive(src, target, lookup)) { 1585 return; 1586 } 1587 1588 addEdge(nodup_nodes, node_store, edges, recv, src, target); 1589 } 1590 1591 static void addEdge(NoDupNodesT, NodeStoreT, EdgeStoreT, RecvT, SrcT, TargetT)( 1592 ref NoDupNodesT nodup_nodes, ref NodeStoreT node_store, 1593 ref EdgeStoreT edges, ref RecvT recv, SrcT src, TargetT target, 1594 EdgeKind kind = EdgeKind.Directed) { 1595 string target_usr; 1596 static if (is(Unqual!TargetT == TypeKindAttr)) { 1597 if (target.kind.info.kind == TypeKind.Info.Kind.null_) { 1598 return; 1599 } 1600 1601 target_usr = cast(string) target.kind.usr; 1602 } else { 1603 target_usr = cast(string) target; 1604 } 1605 1606 string src_usr; 1607 static if (is(Unqual!SrcT == TypeKindAttr)) { 1608 src_usr = cast(string) src.kind.usr; 1609 } else { 1610 src_usr = cast(string) src; 1611 } 1612 1613 // skip self edges 1614 if (target_usr == src_usr) { 1615 return; 1616 } 1617 1618 // naive approach 1619 USRType edge_key = USRType(src_usr ~ "_" ~ target_usr); 1620 if (edge_key in edges) { 1621 return; 1622 } 1623 1624 xmlEdge(recv, src_usr, target_usr, kind); 1625 edges[edge_key] = true; 1626 addFallbackNodes(nodup_nodes, node_store, [USRType(src_usr), USRType(target_usr)]); 1627 } 1628 1629 static void addFallbackNodes(NoDupNodesT, NodeStoreT, NodeT)( 1630 ref NoDupNodesT nodup_nodes, ref NodeStoreT node_store, NodeT[] nodes) { 1631 foreach (ref n; nodes) { 1632 NodeData data; 1633 1634 static if (is(Unqual!NodeT == TypeKindAttr)) { 1635 if (n.kind.info.kind == TypeKind.Info.Kind.null_) { 1636 return; 1637 } 1638 1639 data = NodeData(NodeData.Tag(NodeFallback(n.kind.usr))); 1640 } else { 1641 data = NodeData(NodeData.Tag(NodeFallback(n))); 1642 } 1643 1644 if (data.usr in nodup_nodes) { 1645 continue; 1646 } 1647 1648 node_store.put(data); 1649 } 1650 } 1651 } 1652 1653 private mixin template NodeLocationMixin() { 1654 LocationTag location; 1655 1656 @Attr(IdT.url) void url(scope StreamChar stream) { 1657 if (location.kind == LocationTag.Kind.loc) { 1658 ccdataWrap(stream, location.file); 1659 } 1660 } 1661 1662 @Attr(IdT.position) void position(scope StreamChar stream) { 1663 import std.conv : to; 1664 1665 if (location.kind == LocationTag.Kind.loc) { 1666 ccdataWrap(stream, "Line:", location.line.to!string, " Column:", 1667 location.column.to!string); 1668 } 1669 } 1670 } 1671 1672 /// Helper to generate a unique ID for the node. 1673 private mixin template NodeIdMixin() { 1674 @NodeId void putId(scope StreamChar stream) { 1675 auto id = ValidNodeId(usr); 1676 id.toString(stream, FormatSpec!char("%s")); 1677 } 1678 1679 debug { 1680 @NodeExtra void putIdDebug(scope StreamChar stream) { 1681 import std.format : formattedWrite; 1682 import std..string : replace; 1683 1684 // printing the raw identifiers to make it easier to debug 1685 formattedWrite(stream, "<!-- id: %s -->", usr.replace("-", "_")); 1686 } 1687 } 1688 } 1689 1690 /// A node for a free function or a class/struct method. 1691 private @safe struct NodeFunction { 1692 import std.array : Appender; 1693 1694 USRType usr; 1695 @Attr(IdT.signature) string signature; 1696 string identifier; 1697 1698 mixin NodeLocationMixin; 1699 1700 @Attr(IdT.kind) enum kind = "function"; 1701 1702 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1703 auto style = makeShapeNode(identifier, ColorKind.func); 1704 style.toString(stream, FormatSpec!char("%s")); 1705 } 1706 1707 mixin NodeIdMixin; 1708 } 1709 1710 @("Should be a xml node of a function") 1711 unittest { 1712 auto func = NodeFunction(USRType("123"), "void foo(int)", "foo", LocationTag("fun.h", 1, 2)); 1713 1714 auto buf = appender!string(); 1715 auto recv = DummyRecv(&buf); 1716 1717 nodeToXml(func, recv); 1718 buf.data.shouldEqual(`<node id="18446744072944306312"><data key="d11"><![CDATA[void foo(int)]]></data><data key="d3"><![CDATA[fun.h]]></data><data key="d8"><![CDATA[Line:1 Column:2]]></data><data key="d9"><![CDATA[function]]></data><data key="d5"><y:ShapeNode><y:Geometry height="20" width="140"/><y:Fill color="#FF6600" transparent="false"/><y:NodeLabel autoSizePolicy="node_size" configuration="CroppingLabel"><![CDATA[foo]]></y:NodeLabel></y:ShapeNode></data><!-- id: 123 --></node> 1719 `); 1720 } 1721 1722 /// Represents either a class or struct. 1723 private @safe struct NodeRecord { 1724 import std.array : Appender; 1725 1726 USRType usr; 1727 string identifier; 1728 StereoType stereotype; 1729 Appender!(NodeField[]) attributes; 1730 Appender!(NodeFunction[]) methods; 1731 Appender!(NodeType[]) types; 1732 1733 mixin NodeLocationMixin; 1734 1735 @Attr(IdT.kind) enum kind = "record"; 1736 1737 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1738 //TODO express stereotype in some way 1739 auto folder = FolderNode(identifier); 1740 folder.toString(stream, FormatSpec!char("%s")); 1741 } 1742 1743 @NodeAttribute string yfile = `yfiles.foldertype="folder"`; 1744 1745 @NodeExtra void graph(scope StreamChar stream) { 1746 import std.format : formattedWrite; 1747 import std.range.primitives : put; 1748 1749 formattedWrite(stream, "\n" ~ `<graph edgedefault="directed" id="G%s:">` ~ "\n", 1750 nextGraphId); 1751 1752 foreach (type; types.data) { 1753 nodeToXml(type, stream); 1754 } 1755 1756 foreach (attr; attributes.data) { 1757 nodeToXml(attr, stream); 1758 } 1759 1760 foreach (func; methods.data) { 1761 nodeToXml(func, stream); 1762 } 1763 1764 put(stream, `</graph>`); 1765 } 1766 1767 mixin NodeIdMixin; 1768 } 1769 1770 /// Node for a C++ type that has no other suitable node to represent it. 1771 private @safe struct NodeType { 1772 USRType usr; 1773 TypeKindAttr type; 1774 1775 mixin NodeLocationMixin; 1776 1777 this(TypeKindAttr type, LocationTag location) { 1778 this.type = type; 1779 this.location = location; 1780 1781 this.usr = type.kind.usr; 1782 } 1783 1784 this(TypeKindAttr type) { 1785 this(type, LocationTag(null)); 1786 } 1787 1788 @Attr(IdT.kind) enum kind = "type"; 1789 1790 @Attr(IdT.typeAttr) void typeAttr(scope StreamChar stream) { 1791 ccdataWrap(stream, type.attr); 1792 } 1793 1794 @Attr(IdT.signature) void signature(scope StreamChar stream) { 1795 ccdataWrap(stream, type.toStringDecl); 1796 } 1797 1798 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1799 auto style = makeShapeNode(type.kind.toStringDecl(TypeAttr.init)); 1800 style.toString(stream, FormatSpec!char("%s")); 1801 } 1802 1803 @NodeId void putId(scope StreamChar stream) { 1804 auto id = ValidNodeId(type.kind.usr); 1805 id.toString(stream, FormatSpec!char("%s")); 1806 } 1807 } 1808 1809 /// A variable definition. 1810 private @safe struct NodeVariable { 1811 USRType usr; 1812 string identifier; 1813 TypeKindAttr type; 1814 ColorKind color; 1815 1816 mixin NodeLocationMixin; 1817 1818 @Attr(IdT.kind) enum kind = "variable"; 1819 1820 @Attr(IdT.signature) void signature(scope StreamChar stream) { 1821 ccdataWrap(stream, type.toStringDecl(identifier)); 1822 } 1823 1824 @Attr(IdT.typeAttr) void typeAttr(scope StreamChar stream) { 1825 import std.algorithm : joiner, filter, copy; 1826 import std.range : chain, only; 1827 import std.array : appender; 1828 1829 auto app = appender!string(); 1830 1831 // dfmt off 1832 chain(type.attr.stringRange, only(color == ColorKind.globalStatic ? "static" : null)) 1833 .filter!(a => a !is null) 1834 .joiner(";") 1835 .copy(app); 1836 // dfmt on 1837 1838 ccdataWrap(stream, app.data); 1839 } 1840 1841 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1842 auto style = makeShapeNode(identifier, color); 1843 style.toString(stream, FormatSpec!char("%s")); 1844 } 1845 1846 mixin NodeIdMixin; 1847 } 1848 1849 @("Static attribute is derived from the color and represented in the typeAttr field") 1850 unittest { 1851 import std.array : appender; 1852 import cpptooling.data : makeSimple; 1853 import unit_threaded : shouldEqual; 1854 1855 auto tk = makeSimple("int"); 1856 auto node = NodeVariable(USRType("usr"), "x", tk, ColorKind.globalStatic); 1857 auto app = appender!string(); 1858 1859 node.typeAttr((const(char)[] s) { app.put(s); }); 1860 1861 app.data.shouldEqual("static"); 1862 } 1863 1864 /// A node for a field of a class/struct. 1865 private @safe struct NodeField { 1866 import cpptooling.data.type : AccessType; 1867 1868 USRType usr; 1869 string identifier; 1870 TypeKindAttr type; 1871 AccessType access; 1872 ColorKind color; 1873 1874 mixin NodeLocationMixin; 1875 1876 @Attr(IdT.kind) enum kind = "field"; 1877 1878 @Attr(IdT.signature) void signature(scope StreamChar stream) { 1879 ccdataWrap(stream, type.toStringDecl(identifier)); 1880 } 1881 1882 @Attr(IdT.typeAttr) void typeAttr(scope StreamChar stream) { 1883 ccdataWrap(stream, type.attr); 1884 } 1885 1886 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1887 auto style = makeShapeNode(access.toInternal!string ~ identifier, color); 1888 style.toString(stream, FormatSpec!char("%s")); 1889 } 1890 1891 mixin NodeIdMixin; 1892 } 1893 1894 /// A node for a file. 1895 private @safe struct NodeFile { 1896 USRType usr; 1897 1898 @Attr(IdT.kind) enum kind = "file"; 1899 1900 @Attr(IdT.url) void url(scope StreamChar stream) { 1901 ccdataWrap(stream, cast(string) usr); 1902 } 1903 1904 @Attr(IdT.signature) void signature(scope StreamChar stream) { 1905 ccdataWrap(stream, cast(string) usr); 1906 } 1907 1908 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1909 import std.path : baseName; 1910 1911 auto style = makeShapeNode((cast(string) usr).baseName, ColorKind.file); 1912 style.toString(stream, FormatSpec!char("%s")); 1913 } 1914 1915 mixin NodeIdMixin; 1916 } 1917 1918 /** A node for a namespace. 1919 * 1920 * Intended to enable edges of types and globals to relate to the namespace 1921 * that contain them. 1922 * 1923 * It is not intended to "contain" anything, which would be hard in for a 1924 * language as C++. Hard because any translation unit can add anything to a 1925 * namespace. 1926 */ 1927 private @safe struct NodeNamespace { 1928 USRType usr; 1929 1930 @Attr(IdT.kind) enum kind = "namespace"; 1931 1932 @Attr(IdT.signature) void signature(scope StreamChar stream) { 1933 ccdataWrap(stream, cast(string) usr); 1934 } 1935 1936 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1937 auto style = makeShapeNode(cast(string) usr, ColorKind.namespace); 1938 style.toString(stream, FormatSpec!char("%s")); 1939 } 1940 1941 mixin NodeIdMixin; 1942 } 1943 1944 private @safe struct NodeFallback { 1945 USRType usr; 1946 1947 mixin NodeLocationMixin; 1948 1949 @Attr(IdT.kind) enum kind = "fallback"; 1950 1951 @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) { 1952 auto style = makeShapeNode(usr, ColorKind.fallback); 1953 style.toString(stream, FormatSpec!char("%s")); 1954 } 1955 1956 mixin NodeIdMixin; 1957 }