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, CppVirtualMethod,
35     CppMethodName, CppClassName, CppNs, CppAccess, StorageClass, CFunctionName,
36     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,
198         logTypeResult;
199     import cpptooling.data : TypeResult, TypeKindAttr, CxParam, CFunctionName,
200         CxReturnType, CFunction, VariadicType, LocationTag, StorageClass;
201     import cpptooling.data.symbol : Container;
202 
203     // hint, start reading the function from the bottom up.
204     // design is pipe and data transformation
205 
206     Nullable!TypeResults extractAndStoreRawType(const(Cursor) c) @safe {
207         auto tr = () @trusted{ return retrieveType(c, container, indent); }();
208         if (tr.isNull) {
209             return tr;
210         }
211 
212         assert(tr.primary.type.kind.info.kind.among(TypeKind.Info.Kind.func,
213                 TypeKind.Info.Kind.typeRef, TypeKind.Info.Kind.simple));
214         put(tr, container, indent);
215 
216         return tr;
217     }
218 
219     Nullable!TypeResults lookupRefToConcreteType(Nullable!TypeResults tr) @safe {
220         if (tr.isNull) {
221             return tr;
222         }
223 
224         if (tr.primary.type.kind.info.kind == TypeKind.Info.Kind.typeRef) {
225             // replace typeRef kind with the func
226             auto kind = container.find!TypeKind(tr.primary.type.kind.info.canonicalRef).front;
227             tr.primary.type.kind = kind;
228         }
229 
230         logTypeResult(tr, indent);
231         assert(tr.primary.type.kind.info.kind == TypeKind.Info.Kind.func);
232 
233         return tr;
234     }
235 
236     static struct ComposeData {
237         TypeResults tr;
238         CFunctionName name;
239         LocationTag loc;
240         VariadicType isVariadic;
241         StorageClass storageClass;
242         Flag!"isDefinition" is_definition;
243         Language language;
244     }
245 
246     ComposeData getCursorData(TypeResults tr) @safe {
247         auto data = ComposeData(tr);
248 
249         data.name = CFunctionName(c_in.spelling);
250         data.loc = locToTag(c_in.location());
251         data.is_definition = cast(Flag!"isDefinition") c_in.isDefinition;
252         data.storageClass = c_in.storageClass().toStorageClass;
253         data.language = c_in.toLanguage;
254 
255         return data;
256     }
257 
258     FunctionDeclResult composeFunc(ComposeData data) @safe {
259         Nullable!CFunction rval;
260 
261         auto return_type = container.find!TypeKind(data.tr.primary.type.kind.info.return_);
262         if (return_type.length == 0) {
263             return FunctionDeclResult.init;
264         }
265 
266         auto params = toCxParam(data.tr.primary.type.kind, container);
267 
268         VariadicType is_variadic;
269         // according to C/C++ standard the last parameter is the only one
270         // that can be a variadic, therefor only needing to peek at that
271         // one.
272         if (params.length > 0) {
273             is_variadic = cast(VariadicType)() @trusted{
274                 return params[$ - 1].peek!VariadicType;
275             }();
276         }
277 
278         return FunctionDeclResult(Yes.isValid, data.tr.primary.type, data.name,
279                 TypeKindAttr(return_type.front, data.tr.primary.type.kind.info.returnAttr), is_variadic,
280                 data.storageClass, params, data.loc, data.is_definition, data.language);
281     }
282 
283     // dfmt off
284     auto rval = pipe!(extractAndStoreRawType,
285                       lookupRefToConcreteType,
286                       // either break early if null or continue composing a
287                       // function representation
288                       (Nullable!TypeResults tr) {
289                           if (tr.isNull) {
290                               return FunctionDeclResult.init;
291                           } else {
292                               return pipe!(getCursorData, composeFunc)(tr.get);
293                           }
294                       }
295                       )
296         (c_in);
297     // dfmt on
298 
299     return rval;
300 }
301 
302 struct VarDeclResult {
303     TypeKindAttr type;
304     CppVariable name;
305     LocationTag location;
306     USRType instanceUSR;
307     StorageClass storageClass;
308 }
309 
310 /// Analyze a variable declaration
311 VarDeclResult analyzeVarDecl(const(VarDecl) v, ref Container container, in uint indent) @safe {
312     return analyzeVarDecl(v.cursor, container, indent);
313 }
314 
315 /// ditto
316 VarDeclResult analyzeVarDecl(const(Cursor) v, ref Container container, in uint indent) @safe
317 in {
318     import clang.c.Index : CXCursorKind;
319 
320     assert(v.kind == CXCursorKind.varDecl);
321 }
322 body {
323     import clang.Cursor : Cursor;
324     import cpptooling.analyzer.clang.type : retrieveType;
325     import cpptooling.data : CppVariable;
326 
327     auto type = () @trusted{ return retrieveType(v, container, indent); }();
328     put(type, container, indent);
329 
330     auto name = CppVariable(v.spelling);
331     auto loc = locToTag(v.location());
332     auto instance_usr = USRType(v.usr);
333     // Assuming that all variable declarations have a USR
334     assert(instance_usr.length > 0);
335 
336     // store the location to enable creating relations to/from this instance
337     // USR.
338     container.put(loc, instance_usr, Yes.isDefinition);
339 
340     auto storage = () @trusted{ return v.storageClass.toStorageClass; }();
341 
342     return VarDeclResult(type.primary.type, name, loc, instance_usr, storage);
343 }
344 
345 struct ConstructorResult {
346     TypeKindAttr type;
347     CppMethodName name;
348     CxParam[] params;
349     LocationTag location;
350 }
351 
352 /** Analyze the node for actionable data.
353  * Params:
354  *   v = node
355  *   container = container to store the type in
356  *   indent = to use when logging
357  *
358  * Returns: analyzed data.
359  */
360 auto analyzeConstructor(const(Constructor) v, ref Container container, in uint indent) @safe {
361     auto type = () @trusted{ return retrieveType(v.cursor, container, indent); }();
362     put(type, container, indent);
363 
364     auto params = toCxParam(type.primary.type.kind, container);
365     auto name = CppMethodName(v.cursor.spelling);
366 
367     return ConstructorResult(type.primary.type, name, params, type.primary.location);
368 }
369 
370 struct DestructorResult {
371     TypeKindAttr type;
372     CppMethodName name;
373     CppVirtualMethod virtualKind;
374     LocationTag location;
375 }
376 
377 /// ditto
378 auto analyzeDestructor(const(Destructor) v, ref Container container, in uint indent) @safe {
379     auto type = () @trusted{ return retrieveType(v.cursor, container, indent); }();
380     put(type, container, indent);
381 
382     auto name = CppMethodName(v.cursor.spelling);
383     auto virtual_kind = classify(v.cursor);
384 
385     return DestructorResult(type.primary.type, name, virtual_kind, type.primary.location);
386 }
387 
388 struct CxxMethodResult {
389     TypeKindAttr type;
390     CppMethodName name;
391     CxParam[] params;
392     Flag!"isOperator" isOperator;
393     CxReturnType returnType;
394     CppVirtualMethod virtualKind;
395     Flag!"isConst" isConst;
396     LocationTag location;
397 }
398 
399 CxxMethodResult analyzeCxxMethod(const(CxxMethod) v, ref Container container, in uint indent) @safe {
400     return analyzeCxxMethod(v.cursor, container, indent);
401 }
402 
403 /// ditto
404 CxxMethodResult analyzeCxxMethod(const(Cursor) v, ref Container container, in uint indent) @safe {
405     auto type = () @trusted{ return retrieveType(v, container, indent); }();
406     assert(type.get.primary.type.kind.info.kind == TypeKind.Info.Kind.func);
407     put(type, container, indent);
408 
409     auto name = CppMethodName(v.spelling);
410     auto params = toCxParam(type.primary.type.kind, container);
411     auto return_type = CxReturnType(TypeKindAttr(container.find!TypeKind(
412             type.primary.type.kind.info.return_).front, type.primary.type.kind.info.returnAttr));
413     auto is_virtual = classify(v);
414 
415     return CxxMethodResult(type.primary.type, name, params,
416             cast(Flag!"isOperator") isOperator(name), return_type, is_virtual,
417             cast(Flag!"isConst") type.primary.type.attr.isConst, type.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.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.primary.type.toStringDecl);
480     auto access = CppAccess(toAccessType(() @trusted{ return v.cursor.access; }().accessSpecifier));
481     auto usr = type.primary.type.kind.usr;
482 
483     if (type.primary.type.kind.info.kind == TypeKind.Info.Kind.typeRef) {
484         usr = type.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.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.primary.type, name, type.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 }