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