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