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 sumtype;
23 import my.sumtype;
24 
25 import clang.c.Index : CX_CXXAccessSpecifier, CX_StorageClass, CXLanguageKind;
26 import clang.Cursor : Cursor;
27 import clang.SourceLocation : SourceLocation;
28 
29 import libclang_ast.ast : ClassTemplate, ClassTemplatePartialSpecialization,
30     Constructor, CxxMethod, ClassDecl, CxxBaseSpecifier,
31     Destructor, FieldDecl, FunctionDecl, StructDecl, TranslationUnit, UnionDecl, VarDecl, Visitor;
32 
33 import cpptooling.analyzer.clang.type : retrieveType, TypeKind, TypeKindAttr,
34     TypeResult, TypeResults, logTypeResult, TypeAttr;
35 import cpptooling.analyzer.clang.store : put;
36 import cpptooling.data : AccessType, VariadicType, CxParam, TypeKindVariable,
37     CppVariable, LocationTag, Location, CxReturnType,
38     CppVirtualMethod, CppMethodName, CppClassName, CppNs, CppAccess,
39     StorageClass, CFunctionName, Language, CFunction, CxGlobalVariable;
40 import cpptooling.data.symbol : Container, USRType;
41 
42 /// Convert Cursor attributes to enum representation.
43 private CppVirtualMethod classify(T)(T c) @safe if (is(Unqual!T == Cursor)) {
44     import cpptooling.data.type : MemberVirtualType;
45 
46     auto is_virtual = MemberVirtualType.Normal;
47     auto func = () @trusted { return c.func; }();
48 
49     if (!func.isValid) {
50         // do nothing
51     } else if (func.isPureVirtual) {
52         is_virtual = MemberVirtualType.Pure;
53     } else if (func.isVirtual) {
54         is_virtual = MemberVirtualType.Virtual;
55     }
56 
57     return CppVirtualMethod(is_virtual);
58 }
59 
60 /// Convert a clang access specifier to dextool representation.
61 AccessType toAccessType(CX_CXXAccessSpecifier accessSpec) @safe {
62     final switch (accessSpec) with (CX_CXXAccessSpecifier) {
63     case cxxInvalidAccessSpecifier:
64         return AccessType.Public;
65     case cxxPublic:
66         return AccessType.Public;
67     case cxxProtected:
68         return AccessType.Protected;
69     case cxxPrivate:
70         return AccessType.Private;
71     }
72 }
73 
74 StorageClass toStorageClass(CX_StorageClass storageClass) @safe pure nothrow @nogc {
75     switch (storageClass) with (CX_StorageClass) {
76     case extern_:
77         return StorageClass.Extern;
78     case static_:
79         return StorageClass.Static;
80     default:
81         return StorageClass.None;
82     }
83 }
84 
85 private CxParam[] toCxParam(ref TypeKind kind, ref Container container) @safe {
86     import std.array;
87     import std.algorithm : map;
88     import std.range : chain, zip, tee;
89     import std..string : strip;
90     import cpptooling.data.kind_type;
91 
92     auto tr_params = kind.info.match!((a => a.params), _ => (FuncInfoParam[]).init);
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].get, a[1].attr));
110               } else {
111                   return CxParam(TypeKindVariable(TypeKindAttr(a[0].get, 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 do {
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 do {
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         bool fail;
212         tr.get.primary.type.kind.info.match!(ignore!(TypeKind.FuncInfo),
213                 ignore!(TypeKind.TypeRefInfo), ignore!(TypeKind.SimpleInfo), (_) {
214             fail = true;
215         });
216         if (fail)
217             assert(0, "wrong type");
218 
219         put(tr, container, indent);
220 
221         return tr;
222     }
223 
224     Nullable!TypeResults lookupRefToConcreteType(Nullable!TypeResults tr) @trusted {
225         if (tr.isNull) {
226             return tr;
227         }
228 
229         tr.get.primary.type.kind.info.match!((TypeKind.TypeRefInfo t) {
230             // replace typeRef kind with the func
231             auto kind = container.find!TypeKind(t.canonicalRef).front;
232             tr.get.primary.type.kind = kind.get;
233         }, (_) {});
234 
235         logTypeResult(tr, indent);
236         tr.get.primary.type.kind.info.match!(ignore!(TypeKind.FuncInfo), (_) {
237             assert(0, "wrong type");
238         });
239 
240         return tr;
241     }
242 
243     static struct ComposeData {
244         TypeResults tr;
245         CFunctionName name;
246         LocationTag loc;
247         VariadicType isVariadic;
248         StorageClass storageClass;
249         Flag!"isDefinition" is_definition;
250         Language language;
251     }
252 
253     ComposeData getCursorData(TypeResults tr) @safe {
254         auto data = ComposeData(tr);
255 
256         data.name = CFunctionName(c_in.spelling);
257         data.loc = locToTag(c_in.location());
258         data.is_definition = cast(Flag!"isDefinition") c_in.isDefinition;
259         data.storageClass = c_in.storageClass().toStorageClass;
260         data.language = c_in.toLanguage;
261 
262         return data;
263     }
264 
265     FunctionDeclResult composeFunc(ComposeData data) @safe {
266         auto return_type = container.find!TypeKind(
267                 data.tr.primary.type.kind.info.match!(a => a.return_, _ => USRType.init));
268         if (return_type.length == 0) {
269             return FunctionDeclResult.init;
270         }
271 
272         auto params = toCxParam(data.tr.primary.type.kind, container);
273 
274         VariadicType is_variadic;
275         // according to C/C++ standard the last parameter is the only one
276         // that can be a variadic, therefor only needing to peek at that
277         // one.
278         if (params.length > 0) {
279             is_variadic = cast(VariadicType)() @trusted {
280                 return params[$ - 1].peek!VariadicType;
281             }();
282         }
283 
284         auto attrs = data.tr.primary.type.kind.info.match!(a => a.returnAttr, _ => TypeAttr.init);
285         return FunctionDeclResult(Yes.isValid, data.tr.primary.type, data.name,
286                 TypeKindAttr(return_type.front.get, attrs), is_variadic,
287                 data.storageClass, params, data.loc, data.is_definition, data.language);
288     }
289 
290     // dfmt off
291     auto rval = pipe!(extractAndStoreRawType,
292                       lookupRefToConcreteType,
293                       // either break early if null or continue composing a
294                       // function representation
295                       (Nullable!TypeResults tr) {
296                           if (tr.isNull) {
297                               return FunctionDeclResult.init;
298                           } else {
299                               return pipe!(getCursorData, composeFunc)(tr.get);
300                           }
301                       }
302                       )
303         (c_in);
304     // dfmt on
305 
306     return rval;
307 }
308 
309 struct VarDeclResult {
310     TypeKindAttr type;
311     CppVariable name;
312     LocationTag location;
313     USRType instanceUSR;
314     StorageClass storageClass;
315 }
316 
317 /// Analyze a variable declaration
318 VarDeclResult analyzeVarDecl(const VarDecl v, ref Container container, in uint indent) @safe {
319     return analyzeVarDecl(v.cursor, container, indent);
320 }
321 
322 /// ditto
323 VarDeclResult analyzeVarDecl(const Cursor v, ref Container container, in uint indent) @safe
324 in {
325     import clang.c.Index : CXCursorKind;
326 
327     assert(v.kind == CXCursorKind.varDecl);
328 }
329 do {
330     import clang.Cursor : Cursor;
331     import cpptooling.analyzer.clang.type : retrieveType;
332     import cpptooling.data : CppVariable;
333 
334     auto type = () @trusted { return retrieveType(v, container, indent); }();
335     put(type, container, indent);
336 
337     auto name = CppVariable(v.spelling);
338     auto loc = locToTag(v.location());
339     auto instance_usr = USRType(v.usr);
340     // Assuming that all variable declarations have a USR
341     assert(instance_usr.length > 0);
342 
343     // store the location to enable creating relations to/from this instance
344     // USR.
345     container.put(loc, instance_usr, Yes.isDefinition);
346 
347     auto storage = () @trusted { return v.storageClass.toStorageClass; }();
348 
349     return VarDeclResult(type.get.primary.type, name, loc, instance_usr, storage);
350 }
351 
352 struct ConstructorResult {
353     TypeKindAttr type;
354     CppMethodName name;
355     CxParam[] params;
356     LocationTag location;
357 }
358 
359 /** Analyze the node for actionable data.
360  * Params:
361  *   v = node
362  *   container = container to store the type in
363  *   indent = to use when logging
364  *
365  * Returns: analyzed data.
366  */
367 auto analyzeConstructor(const(Constructor) v, ref Container container, in uint indent) @safe {
368     auto type = () @trusted { return retrieveType(v.cursor, container, indent); }();
369     put(type, container, indent);
370 
371     auto params = toCxParam(type.get.primary.type.kind, container);
372     auto name = CppMethodName(v.cursor.spelling);
373 
374     return ConstructorResult(type.get.primary.type, name, params, type.get.primary.location);
375 }
376 
377 struct DestructorResult {
378     TypeKindAttr type;
379     CppMethodName name;
380     CppVirtualMethod virtualKind;
381     LocationTag location;
382 }
383 
384 /// ditto
385 auto analyzeDestructor(const(Destructor) v, ref Container container, in uint indent) @safe {
386     auto type = () @trusted { return retrieveType(v.cursor, container, indent); }();
387     put(type, container, indent);
388 
389     auto name = CppMethodName(v.cursor.spelling);
390     auto virtual_kind = classify(v.cursor);
391 
392     return DestructorResult(type.get.primary.type, name, virtual_kind, type.get.primary.location);
393 }
394 
395 struct CxxMethodResult {
396     TypeKindAttr type;
397     CppMethodName name;
398     CxParam[] params;
399     Flag!"isOperator" isOperator;
400     CxReturnType returnType;
401     CppVirtualMethod virtualKind;
402     Flag!"isConst" isConst;
403     LocationTag location;
404 }
405 
406 CxxMethodResult analyzeCxxMethod(const(CxxMethod) v, ref Container container, in uint indent) @safe {
407     return analyzeCxxMethod(v.cursor, container, indent);
408 }
409 
410 /// ditto
411 CxxMethodResult analyzeCxxMethod(const(Cursor) v, ref Container container, in uint indent) @safe {
412     auto type = () @trusted { return retrieveType(v, container, indent); }();
413     type.get.primary.type.kind.info.match!(ignore!(TypeKind.FuncInfo), (_) {
414         assert(0, "wrong type");
415     });
416     put(type, container, indent);
417 
418     auto name = CppMethodName(v.spelling);
419     auto params = toCxParam(type.get.primary.type.kind, container);
420     auto return_type = CxReturnType(TypeKindAttr(container.find!TypeKind(
421             type.get.primary.type.kind.info.match!(a => a.return_, _ => USRType.init)).front.get,
422             type.get.primary.type.kind.info.match!(a => a.returnAttr, _ => TypeAttr.init)));
423     auto is_virtual = classify(v);
424 
425     return CxxMethodResult(type.get.primary.type, name, params,
426             cast(Flag!"isOperator") isOperator(name), return_type, is_virtual,
427             cast(Flag!"isConst") type.get.primary.type.attr.isConst, type.get.primary.location);
428 }
429 
430 struct FieldDeclResult {
431     TypeKindAttr type;
432     CppVariable name;
433     USRType instanceUSR;
434     LocationTag location;
435 }
436 
437 /// ditto
438 auto analyzeFieldDecl(const(FieldDecl) v, ref Container container, in uint indent) @safe {
439     import cpptooling.analyzer.clang.type : makeEnsuredUSR;
440 
441     auto type = () @trusted { return retrieveType(v.cursor, container, indent); }();
442     put(type, container, indent);
443 
444     auto name = CppVariable(v.cursor.spelling);
445 
446     auto instance_usr = makeEnsuredUSR(v.cursor, indent + 1);
447     // Assuming that all field declarations have a USR
448     assert(instance_usr.length > 0);
449 
450     auto loc = () @trusted { return locToTag(v.cursor.location()); }();
451     // store the location to enable creating relations to/from this instance
452     // USR.
453     container.put(loc, instance_usr, Yes.isDefinition);
454 
455     return FieldDeclResult(type.get.primary.type, name, instance_usr, loc);
456 }
457 
458 struct CxxBaseSpecifierResult {
459     TypeKindAttr type;
460     CppClassName name;
461     CppNs[] reverseScope;
462     USRType canonicalUSR;
463     CppAccess access;
464 }
465 
466 /** Analyze the node that represents a inheritance.
467  *
468  * reverseScope.
469  *  scope the class reside in starting from the bottom.
470  *  class A : public B {};
471  *  reverseScope is then [B, A].
472  *
473  * canonicalUSR.
474  * The resolved USR.
475  * It is possible to inherit from for example a typedef. canonicalUSR would be
476  * the class the typedef refers.
477  */
478 auto analyzeCxxBaseSpecified(const(CxxBaseSpecifier) v, ref Container container, in uint indent) @safe {
479     import clang.c.Index : CXCursorKind;
480     import std.array : array;
481     import std.algorithm : map;
482     import cpptooling.data.type : CppAccess;
483     import cpptooling.analyzer.clang.cursor_backtrack : backtrackScopeRange;
484     import cpptooling.data : toStringDecl;
485 
486     auto type = () @trusted { return retrieveType(v.cursor, container, indent); }();
487     put(type, container, indent);
488 
489     auto name = CppClassName(type.get.primary.type.toStringDecl);
490     auto access = CppAccess(toAccessType(() @trusted { return v.cursor.access; }().accessSpecifier));
491     auto usr = type.get.primary.type.kind.usr;
492 
493     type.get.primary.type.kind.info.match!((TypeKind.TypeRefInfo t) {
494         usr = t.canonicalRef;
495     }, (_) {});
496 
497     CppNs[] namespace;
498     auto c_ref = v.cursor.referenced;
499     if (c_ref.kind == CXCursorKind.noDeclFound) {
500         namespace = backtrackScopeRange(c_ref).map!(a => CppNs(a.spelling)).array();
501     } else {
502         namespace = backtrackScopeRange(v.cursor).map!(a => CppNs(a.spelling)).array();
503     }
504 
505     if (namespace.length > 0) {
506         // namespace has the class itself in the range so must remove
507         namespace = namespace[1 .. $];
508     }
509 
510     return CxxBaseSpecifierResult(type.get.primary.type, name, namespace, usr, access);
511 }
512 
513 struct RecordResult {
514     TypeKindAttr type;
515     CppClassName name;
516     LocationTag location;
517 }
518 
519 RecordResult analyzeRecord(T)(const(T) decl, ref Container container, in uint indent)
520         if (staticIndexOf!(T, ClassDecl, StructDecl, ClassTemplate,
521             ClassTemplatePartialSpecialization, UnionDecl) != -1) {
522     return analyzeRecord(decl.cursor, container, indent);
523 }
524 
525 RecordResult analyzeRecord(const(Cursor) cursor, ref Container container, in uint indent) @safe {
526     auto type = () @trusted { return retrieveType(cursor, container, indent); }();
527     put(type, container, indent);
528 
529     auto name = CppClassName(cursor.spelling);
530 
531     return RecordResult(type.get.primary.type, name, type.get.primary.location);
532 }
533 
534 ///
535 struct TranslationUnitResult {
536     string fileName;
537 }
538 
539 auto analyzeTranslationUnit(const(TranslationUnit) tu, ref Container container, in uint indent) {
540     auto fname = tu.spelling;
541     return TranslationUnitResult(fname);
542 }
543 
544 /** Reconstruct the semantic clang AST with dextool data structures suitable
545  * for code generation.
546  *
547  * Note that it do NOT traverses the inheritance chain.
548  */
549 final class ClassVisitor : Visitor {
550     import clang.Cursor : Cursor;
551     import libclang_ast.ast;
552     import cpptooling.data;
553     import cpptooling.data.symbol : Container;
554     import libclang_ast.cursor_logger : logNode, mixinNodeLog;
555 
556     alias visit = Visitor.visit;
557 
558     mixin generateIndentIncrDecr;
559 
560     /// The reconstructed class.
561     CppClass root;
562 
563     private {
564         Container* container;
565         CppAccess accessType;
566     }
567 
568     this(T)(const T decl, CppNsStack reside_in_ns, RecordResult result,
569             ref Container container, const uint indent)
570             if (is(T == ClassDecl) || is(T == StructDecl)) {
571         this.container = &container;
572         this.indent = indent;
573 
574         static if (is(T == StructDecl)) {
575             this.accessType = CppAccess(AccessType.Public);
576         } else {
577             this.accessType = CppAccess(AccessType.Private);
578         }
579 
580         this.root = CppClass(result.name, CppInherit[].init, reside_in_ns);
581         this.root.usr = result.type.kind.usr;
582     }
583 
584     override void visit(const(CxxBaseSpecifier) v) {
585         import std.range : retro;
586         import std.array : appender;
587         import clang.c.Index : CXCursorKind;
588 
589         mixin(mixinNodeLog!());
590 
591         auto result = analyzeCxxBaseSpecified(v, *container, indent);
592         auto inherit = CppInherit(result.name, result.access);
593         inherit.usr = result.canonicalUSR;
594 
595         foreach (a; retro(result.reverseScope)) {
596             inherit.put(a);
597         }
598         root.put(inherit);
599     }
600 
601     override void visit(const(Constructor) v) @trusted {
602         mixin(mixinNodeLog!());
603 
604         auto result = analyzeConstructor(v, *container, indent);
605         auto tor = CppCtor(result.type.kind.usr, result.name, result.params, accessType);
606         root.put(tor);
607 
608         debug logger.trace("ctor: ", tor.toString);
609     }
610 
611     override void visit(const(Destructor) v) @trusted {
612         mixin(mixinNodeLog!());
613 
614         auto type = retrieveType(v.cursor, *container, indent);
615         .put(type, *container, indent);
616 
617         auto result = analyzeDestructor(v, *container, indent);
618         auto tor = CppDtor(result.type.kind.usr, result.name, accessType, classify(v.cursor));
619         root.put(tor);
620 
621         debug logger.trace("dtor: ", tor.toString);
622     }
623 
624     override void visit(const(CxxMethod) v) @trusted {
625         import cpptooling.data : CppMethodOp;
626 
627         mixin(mixinNodeLog!());
628 
629         auto result = analyzeCxxMethod(v, *container, indent);
630 
631         if (result.isOperator) {
632             auto op = CppMethodOp(result.type.kind.usr, result.name, result.params,
633                     result.returnType, accessType,
634                     CppConstMethod(result.isConst), result.virtualKind);
635             root.put(op);
636             debug logger.trace("operator: ", op.toString);
637         } else {
638             auto method = CppMethod(result.type.kind.usr, result.name, result.params,
639                     result.returnType, accessType,
640                     CppConstMethod(result.isConst), result.virtualKind);
641             root.put(method);
642             debug logger.trace("method: ", method.toString);
643         }
644     }
645 
646     override void visit(const(CxxAccessSpecifier) v) @trusted {
647         mixin(mixinNodeLog!());
648 
649         accessType = CppAccess(toAccessType(v.cursor.access.accessSpecifier));
650     }
651 
652     override void visit(const(FieldDecl) v) @trusted {
653         import cpptooling.data : TypeKindVariable;
654 
655         mixin(mixinNodeLog!());
656 
657         auto result = analyzeFieldDecl(v, *container, indent);
658         root.put(TypeKindVariable(result.type, result.name), accessType);
659 
660         debug logger.trace("member: ", result.name);
661     }
662 }