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