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