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