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