1 /**
2 Date: 2015-2017, Joakim Brännström
3 License: MPL-2, Mozilla Public License 2.0
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 Generate a C test double implementation from data about the structural
7 representation.
8 */
9 module dextool.plugin.ctestdouble.backend.cvariant;
10 
11 import std.typecons : Flag, Yes;
12 import logger = std.experimental.logger;
13 
14 import dsrcgen.cpp : CppModule, CppHModule;
15 import my.sumtype;
16 
17 import dextool.type : Path, DextoolVersion;
18 import cpptooling.data.symbol;
19 import libclang_ast.ast : Visitor;
20 import cpptooling.testdouble.header_filter : LocationType;
21 import cpptooling.type : MainName, StubPrefix, CustomHeader, MainNs, MainInterface;
22 
23 /// Control various aspects of the analyze and generation like what nodes to
24 /// process.
25 @safe interface Controller {
26     /// Query the controller with the filename of the AST node for a decision
27     /// if it shall be processed.
28     bool doFile(in string filename, in string info);
29 
30     /** Query the controller for a decision if it shall be processed. */
31     bool doSymbol(string symbol);
32 
33     /** A list of includes for the test double header.
34      *
35      * Part of the controller because they are dynamic, may change depending on
36      * for example calls to doFile.
37      */
38     Path[] getIncludes();
39 
40     /// Controls generation of google mock.
41     bool doGoogleMock();
42 
43     /// Generate a pre_include header file from internal template?
44     bool doPreIncludes();
45 
46     /// Generate a #include of the pre include header
47     bool doIncludeOfPreIncludes();
48 
49     /// Generate a post_include header file from internal template?
50     bool doPostIncludes();
51 
52     /// Generate a #include of the post include header
53     bool doIncludeOfPostIncludes();
54 
55     /// Generate location of symbols as comments
56     bool doLocationAsComment();
57 }
58 
59 /// Parameters used during generation.
60 /// Important aspect that they do NOT change, therefore it is pure.
61 @safe pure interface Parameters {
62     static struct Files {
63         Path hdr;
64         Path impl;
65         Path globals;
66         Path gmock;
67         Path pre_incl;
68         Path post_incl;
69     }
70 
71     /// Source files used to generate the test double.
72     Path[] getIncludes();
73 
74     /// Output directory to store files in.
75     Path getOutputDirectory();
76 
77     /// Files to write generated test double data to.
78     Files getFiles();
79 
80     /// Name affecting interface, namespace and output file.
81     MainName getMainName();
82 
83     /** Namespace for the generated test double.
84      *
85      * Contains the adapter, C++ interface, gmock etc.
86      */
87     MainNs getMainNs();
88 
89     /** Name of the interface of the test double.
90      *
91      * Used in Adapter.
92      */
93     MainInterface getMainInterface();
94 
95     /** Prefix to use for the generated files.
96      *
97      * Affects both the filename and the preprocessor #include.
98      */
99     StubPrefix getFilePrefix();
100 
101     /// Prefix used for test artifacts.
102     StubPrefix getArtifactPrefix();
103 
104     /// Dextool Tool version.
105     DextoolVersion getToolVersion();
106 
107     /// Custom header to prepend generated files with.
108     CustomHeader getCustomHeader();
109 
110     /** If an implementation of the interface for globals that zeroes them
111      * shall be generated.
112      */
113     Flag!"generateZeroGlobals" generateZeroGlobals();
114 }
115 
116 /// Data produced by the generator like files.
117 @safe interface Products {
118     /** Data pushed from the test double generator to be written to files.
119      *
120      * The put value is the code generation tree. It allows the caller of
121      * Generator to inject more data in the tree before writing. For
122      * example a custom header.
123      *
124      * Params:
125      *   fname = file the content is intended to be written to.
126      *   hdr_data = data to write to the file.
127      */
128     void putFile(Path fname, CppHModule hdr_data);
129 
130     /// ditto.
131     void putFile(Path fname, CppModule impl_data);
132 
133     /** During the translation phase the location of symbols that aren't
134      * filtered out are pushed to the variant.
135      *
136      * It is intended that the variant control the #include directive strategy.
137      * Just the files that was input?
138      * Deduplicated list of files where the symbols was found?
139      */
140     void putLocation(Path loc, LocationType type);
141 }
142 
143 /** Generator of test doubles for C code.
144  */
145 struct Generator {
146     import cpptooling.data : CppRoot;
147 
148     private static struct Modules {
149         static Modules make() @safe {
150             return Modules(new CppModule, new CppModule, new CppModule, new CppModule);
151         }
152 
153         CppModule hdr;
154         CppModule impl;
155         CppModule globals;
156         CppModule gmock;
157     }
158 
159     ///
160     this(Controller ctrl, Parameters params, Products products) {
161         this.ctrl = ctrl;
162         this.params = params;
163         this.products = products;
164         this.filtered = CppRoot.make;
165     }
166 
167     /** Filter and aggregate data for future processing.
168      */
169     void aggregate(ref CppRoot root, ref Container container) {
170         import cpptooling.data.symbol.types : USRType;
171 
172         rawFilter(root, ctrl, products, filtered, (USRType usr) => container.find!LocationTag(usr));
173     }
174 
175     /** Process structural data to a test double.
176      *
177      * aggregated -> translate -> code generation.
178      *
179      * Translate analyzes what is left after filtering.
180      * On demand extra data is created. An example of on demand is --gmock.
181      *
182      * Code generation is a straight up translation.
183      * Logical decisions should have been handled in earlier stages.
184      */
185     void process(ref Container container) {
186         logger.tracef("Filtered:\n%s\n", filtered.toString());
187 
188         auto implementation = makeImplementation(filtered, ctrl, params, container);
189         logger.trace("Post processed:\n", implementation.toString());
190         logger.tracef("kind: %s\nglobals: %s\nadapterKind: %s\n",
191                 implementation.kind, implementation.globals, implementation.adapterKind);
192 
193         auto m = Modules.make();
194         generate(implementation, ctrl, params, container, m.hdr, m.impl, m.globals, m.gmock);
195 
196         postProcess(m, ctrl, params, products);
197     }
198 
199 private:
200     CppRoot filtered;
201     Controller ctrl;
202     Parameters params;
203     Products products;
204 
205     static void postProcess(Modules modules, Controller ctrl, Parameters params, Products prod) {
206         import cpptooling.generator.includes : convToIncludeGuard,
207             generatePreInclude, generatePostInclude, makeHeader;
208 
209         /** Generate the C++ header file of the test double.
210          * Params:
211          *  fname = intended output filename, used for ifndef guard.
212          */
213         static auto outputHdr(CppModule hdr, Path fname, DextoolVersion ver,
214                 CustomHeader custom_hdr) {
215             auto o = CppHModule(convToIncludeGuard(fname));
216             o.header.append(makeHeader(fname, ver, custom_hdr));
217             o.content.append(hdr);
218 
219             return o;
220         }
221 
222         static auto output(CppModule code, Path incl_fname, Path dest,
223                 DextoolVersion ver, CustomHeader custom_hdr) {
224             import std.path : baseName;
225 
226             auto o = new CppModule;
227             o.suppressIndent(1);
228             o.append(makeHeader(dest, ver, custom_hdr));
229             o.include(incl_fname.baseName);
230             o.sep(2);
231             o.append(code);
232 
233             return o;
234         }
235 
236         prod.putFile(params.getFiles.hdr, outputHdr(modules.hdr,
237                 params.getFiles.hdr, params.getToolVersion, params.getCustomHeader));
238         prod.putFile(params.getFiles.impl, output(modules.impl,
239                 params.getFiles.hdr, params.getFiles.impl,
240                 params.getToolVersion, params.getCustomHeader));
241         prod.putFile(params.getFiles.globals, output(modules.globals,
242                 params.getFiles.hdr, params.getFiles.globals,
243                 params.getToolVersion, params.getCustomHeader));
244 
245         if (ctrl.doPreIncludes) {
246             prod.putFile(params.getFiles.pre_incl, generatePreInclude(params.getFiles.pre_incl));
247         }
248         if (ctrl.doPostIncludes) {
249             prod.putFile(params.getFiles.post_incl, generatePostInclude(params.getFiles.post_incl));
250         }
251 
252         //TODO refactor. should never reach this stage.
253         if (ctrl.doGoogleMock) {
254             import cpptooling.generator.gmock : generateGmockHdr;
255 
256             prod.putFile(params.getFiles.gmock, generateGmockHdr(params.getFiles.hdr,
257                     params.getFiles.gmock, params.getToolVersion,
258                     params.getCustomHeader, modules.gmock));
259         }
260     }
261 }
262 
263 final class CVisitor : Visitor {
264     import std.typecons : scoped;
265 
266     import libclang_ast.ast : VarDecl, FunctionDecl, TranslationUnit, generateIndentIncrDecr;
267     import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, analyzeVarDecl;
268     import cpptooling.data : CppRoot;
269     import cpptooling.data.symbol : Container;
270     import libclang_ast.cursor_logger : logNode, mixinNodeLog;
271 
272     alias visit = Visitor.visit;
273     mixin generateIndentIncrDecr;
274 
275     CppRoot root;
276     Container container;
277 
278     private {
279         Controller ctrl;
280         Products prod;
281     }
282 
283     this(Controller ctrl, Products prod) {
284         this.ctrl = ctrl;
285         this.prod = prod;
286         this.root = CppRoot.make;
287     }
288 
289     void clearRoot() @safe {
290         this.root = CppRoot.make;
291     }
292 
293     override void visit(const VarDecl v) @trusted {
294         import cpptooling.data : TypeKindVariable;
295         import clang.c.Index : CX_StorageClass;
296 
297         mixin(mixinNodeLog!());
298 
299         //TODO ugly hack. Move this information to the representation. But for
300         //now skipping all definitions
301         if (v.cursor.storageClass() == CX_StorageClass.extern_) {
302             auto result = analyzeVarDecl(v, container, indent);
303             auto var = CxGlobalVariable(result.instanceUSR,
304                     TypeKindVariable(result.type, result.name));
305             root.put(var);
306         }
307     }
308 
309     override void visit(const FunctionDecl v) {
310         import cpptooling.data.type : CxReturnType;
311 
312         mixin(mixinNodeLog!());
313 
314         auto result = analyzeFunctionDecl(v, container, indent);
315         if (result.isValid) {
316             auto func = CFunction(result.type.kind.usr, result.name, result.params,
317                     CxReturnType(result.returnType), result.isVariadic, result.storageClass);
318             root.put(func);
319         }
320     }
321 
322     override void visit(const TranslationUnit v) {
323         import cpptooling.analyzer.clang.type : makeLocation;
324 
325         mixin(mixinNodeLog!());
326 
327         LocationTag tu_loc;
328         () @trusted { tu_loc = LocationTag(Location(v.cursor.spelling, 0, 0)); }();
329 
330         if (tu_loc.kind != LocationTag.Kind.noloc && ctrl.doFile(tu_loc.file,
331                 "root " ~ tu_loc.toString)) {
332             prod.putLocation(Path(tu_loc.file), LocationType.Root);
333         }
334 
335         v.accept(this);
336     }
337 
338     void toString(Writer)(scope Writer w) @safe {
339         import std.format : FormatSpec;
340         import std.range.primitives : put;
341 
342         auto fmt = FormatSpec!char("%u");
343         fmt.writeUpToNextSpec(w);
344 
345         root.toString(w, fmt);
346         put(w, "\n");
347         container.toString(w, FormatSpec!char("%s"));
348     }
349 
350     override string toString() {
351         import std.exception : assumeUnique;
352 
353         char[] buf;
354         buf.reserve(100);
355         toString((const(char)[] s) { buf ~= s; });
356         auto trustedUnique(T)(T t) @trusted {
357             return assumeUnique(t);
358         }
359 
360         return trustedUnique(buf);
361     }
362 }
363 
364 private:
365 @safe:
366 
367 import cpptooling.data : CppRoot, CppClass, CppMethod, CppCtor, CppDtor,
368     CFunction, CppNamespace, CxGlobalVariable, USRType, LocationTag, Location;
369 import dsrcgen.cpp : E, noIndent;
370 
371 /** Contain data for code generation.
372  */
373 struct ImplData {
374     import cpptooling.data.type : CppMethodName;
375     import dextool.plugin.ctestdouble.backend.adapter : AdapterKind;
376     import dextool.plugin.ctestdouble.backend.global : MutableGlobal;
377 
378     CppRoot root;
379     alias root this;
380 
381     /// Tagging of nodes in the root
382     Kind[size_t] kind;
383     /// Global, mutable variables
384     MutableGlobal[] globals;
385     /// Constructor kinds for ctors in an adapter
386     AdapterKind[USRType] adapterKind;
387 
388     static auto make() {
389         return ImplData(CppRoot.make);
390     }
391 
392     void tag(size_t id, Kind kind_) {
393         kind[id] = kind_;
394     }
395 
396     Kind lookup(size_t id) {
397         if (auto k = id in kind) {
398             return *k;
399         }
400 
401         return Kind.none;
402     }
403 
404     MutableGlobal lookupGlobal(CppMethodName name) {
405         foreach (item; globals) {
406             if (item.name == name) {
407                 return item;
408             }
409         }
410 
411         // Methods shall always be 1:1 mapped with the globals list.
412         assert(0);
413     }
414 }
415 
416 enum Kind {
417     none,
418     /// Adapter class
419     adapter,
420     /// gmock class
421     gmock,
422     /// interface for globals
423     initGlobalInterface,
424     initGlobalsToZero,
425     testDoubleNamespace,
426     testDoubleSingleton,
427     testDoubleInterface,
428 }
429 
430 /** Structurally transformed the input to a test double implementation.
431  *
432  * This stage:
433  *  - removes C++ code.
434  *  - removes according to directives via ctrl.
435  *
436  * Params:
437  *  input = structural representation of the source code
438  *  ctrl = controll what nodes to keep
439  *  prod = push location data of the nodes that are kept
440  *  filtered = output structural representation
441  *  lookup = callback function supporting lookup of locations
442  */
443 void rawFilter(LookupT)(ref CppRoot input, Controller ctrl, Products prod,
444         ref CppRoot filtered, LookupT lookup) {
445     import std.algorithm : filter, each;
446     import std.range : tee;
447     import cpptooling.data : StorageClass;
448     import cpptooling.generator.utility : filterAnyLocation;
449 
450     // dfmt off
451     input.funcRange
452         // by definition static functions can't be replaced by test doubles
453         .filter!(a => a.payload.storageClass != StorageClass.Static)
454         // ask controller if the user wants to generate a test double function for the symbol.
455         // note: using the fact that C do NOT have name mangling.
456         .filter!(a => ctrl.doSymbol(a.payload.name))
457         // ask controller if to generate a test double for the function
458         .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup)
459         // pass on location as a product to be used to calculate #include
460         .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf))
461         .each!(a => filtered.put(a.value));
462 
463     input.globalRange()
464         // ask controller if the user wants to generate a global for the symbol.
465         // note: using the fact that C do NOT have name mangling.
466         .filter!(a => ctrl.doSymbol(a.name))
467         // ask controller if to generate a test double for the function
468         .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup)
469         // pass on location as a product to be used to calculate #include
470         .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf))
471         .each!(a => filtered.put(a.value));
472     // dfmt on
473 }
474 
475 /** Transform the content of the root to a test double implementation root.
476  *
477  * Make an adapter.
478  * Make a namespace holding the test double.
479  * Make an interface for initialization of globals.
480  * Make a google mock if asked by the user.
481  */
482 auto makeImplementation(ref CppRoot root, Controller ctrl, Parameters params,
483         ref Container container) @trusted {
484     import std.algorithm : filter;
485     import std.array : array;
486     import cpptooling.data : CppNamespace, CppNs, CppClassName, CppInherit,
487         CppAccess, AccessType, makeUniqueUSR, nextUniqueID, MergeMode;
488     import cpptooling.generator.func : makeFuncInterface;
489     import cpptooling.generator.gmock : makeGmock;
490     import dextool.plugin.ctestdouble.backend.adapter : makeSingleton, makeAdapter;
491     import dextool.plugin.ctestdouble.backend.global : makeGlobalInterface,
492         makeZeroGlobal, filterMutable;
493 
494     ImplData impl = ImplData.make;
495     impl.root.merge(root, MergeMode.shallow);
496 
497     impl.globals = impl.globalRange.filterMutable(container).array;
498 
499     const has_mutable_globals = impl.globals.length != 0;
500     const has_functions = !root.funcRange.empty;
501 
502     if (!has_functions && !has_mutable_globals) {
503         return impl;
504     }
505 
506     auto test_double_ns = CppNamespace.make(CppNs(params.getMainNs));
507 
508     if (has_functions) {
509         auto singleton = makeSingleton(CppNs(params.getMainNs),
510                 CppClassName(params.getMainInterface), "test_double_inst");
511         impl.kind[singleton.id] = Kind.testDoubleSingleton;
512         impl.put(singleton); // (1)
513 
514         auto c_if = makeFuncInterface(impl.funcRange, CppClassName(params.getMainInterface));
515         impl.tag(c_if.id, Kind.testDoubleInterface);
516         test_double_ns.put(c_if);
517 
518         if (ctrl.doGoogleMock) {
519             auto mock = makeGmock(c_if);
520             impl.tag(mock.id, Kind.gmock);
521             test_double_ns.put(mock);
522         }
523     }
524 
525     if (has_mutable_globals) {
526         auto if_name = CppClassName(params.getMainInterface ~ "_InitGlobals");
527         auto global_if = makeGlobalInterface(impl.globals[], if_name);
528         impl.tag(global_if.id, Kind.initGlobalInterface);
529         test_double_ns.put(global_if);
530 
531         if (params.generateZeroGlobals) {
532             auto global_init_zero = makeZeroGlobal(impl.globals[],
533                     CppClassName(params.getArtifactPrefix ~ "ZeroGlobals"),
534                     params.getArtifactPrefix, CppInherit(if_name, CppAccess(AccessType.Public)));
535             impl.tag(global_init_zero.id, Kind.initGlobalsToZero);
536             test_double_ns.put(global_init_zero);
537         }
538     }
539 
540     // MUST be added after the singleton (1)
541     impl.tag(test_double_ns.id, Kind.testDoubleNamespace);
542 
543     {
544         // dfmt off
545         auto adapter = makeAdapter(params.getMainInterface)
546             .makeTestDouble(has_functions)
547             .makeInitGlobals(has_mutable_globals)
548             .makeZeroGlobals(params.generateZeroGlobals)
549             .finalize(impl);
550         impl.tag(adapter.id, Kind.adapter);
551         test_double_ns.put(adapter);
552         // dfmt on
553     }
554 
555     impl.put(test_double_ns);
556     return impl;
557 }
558 
559 void generate(ref ImplData data, Controller ctrl, Parameters params, ref Container container,
560         CppModule hdr, CppModule impl, CppModule globals, CppModule gmock) {
561     import cpptooling.data.symbol.types : USRType;
562     import cpptooling.generator.func : generateFuncImpl;
563     import cpptooling.generator.includes : generateWrapIncludeInExternC;
564     import dextool.plugin.ctestdouble.backend.adapter : generateSingleton;
565 
566     generateWrapIncludeInExternC(ctrl, params, hdr);
567     generateGlobal(data.globalRange, ctrl, params, container, globals);
568 
569     auto mutable_extern_hook = impl.base;
570     mutable_extern_hook.suppressIndent(1);
571 
572     foreach (ns; data.namespaceRange) {
573         switch (data.lookup(ns.id)) {
574         case Kind.testDoubleSingleton:
575             generateSingleton(ns, impl);
576             break;
577         case Kind.testDoubleNamespace:
578             generateNsTestDoubleHdr(ns,
579                     cast(Flag!"locationAsComment") ctrl.doLocationAsComment, params, hdr, gmock,
580                     (USRType usr) => container.find!LocationTag(usr), (size_t id) => data.lookup(
581                         id));
582             generateNsTestDoubleImpl(ns, impl, mutable_extern_hook, data,
583                     params.getArtifactPrefix, container);
584             break;
585 
586         default:
587         }
588     }
589 
590     // The generated functions must be extern C declared.
591     auto extern_c = impl.suite("extern \"C\"");
592     extern_c.suppressIndent(1);
593     foreach (a; data.funcRange) {
594         generateFuncImpl(a, extern_c);
595     }
596 }
597 
598 /// Generate the global definitions and macros for initialization.
599 void generateGlobal(RangeT)(RangeT r, Controller ctrl, Parameters params,
600         ref Container container, CppModule globals) {
601     import cpptooling.data : TypeKind, Void;
602 
603     void generateDefinitions(ref CxGlobalVariable global, Flag!"locationAsComment" loc_as_comment,
604             string prefix, ref Container container, CppModule code)
605     in {
606         global.type.kind.info.match!(restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (_) {
607                 assert(0, "wrong type");
608             }), (_) {});
609     }
610     do {
611         import std.algorithm : map, joiner;
612         import std.format : format;
613         import std.string : toUpper;
614 
615         string d_name = (prefix ~ "Init_").toUpper ~ global.name;
616 
617         if (loc_as_comment) {
618             // dfmt off
619             foreach (loc; container.find!LocationTag(global.usr.get)
620                 // both declaration and definition is OK
621                 .map!(a => a.any)
622                 .joiner) {
623                 code.comment("Origin " ~ loc.toString)[$.begin = "/// "];
624             }
625             // dfmt on
626         }
627         code.stmt(d_name);
628     }
629 
630     void generatePreProcessor(ref CxGlobalVariable global, string prefix, CppModule code) {
631         import std.string : toUpper;
632         import cpptooling.data : toStringDecl;
633 
634         auto d_name = E((prefix ~ "Init_").toUpper ~ global.name);
635         auto ifndef = code.IFNDEF(d_name);
636 
637         void handler() {
638             ifndef.define(d_name ~ E(global.type.toStringDecl(global.name)));
639         }
640 
641         // example: #define TEST_INIT_extern_a int extern_a[4]
642         global.type.kind.info.match!(restrictTo!(TypeKind.ArrayInfo, TypeKind.FuncInfo,
643                 TypeKind.FuncPtrInfo, TypeKind.FuncSignatureInfo, TypeKind.PointerInfo, TypeKind.PrimitiveInfo,
644                 TypeKind.RecordInfo, TypeKind.SimpleInfo, TypeKind.TypeRefInfo, (a) {
645                 handler;
646             }), restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (a) {
647                 assert(0, "unexpected c++ code in preprocessor macros");
648             }), (Void a) {
649             logger.error("Type of global definition is null. Identifier ", global.name);
650         });
651     }
652 
653     auto global_macros = globals.base;
654     global_macros.suppressIndent(1);
655     globals.sep;
656     auto global_definitions = globals.base;
657     global_definitions.suppressIndent(1);
658 
659     foreach (a; r) {
660         generatePreProcessor(a, params.getArtifactPrefix, global_macros);
661         generateDefinitions(a, cast(Flag!"locationAsComment") ctrl.doLocationAsComment,
662                 params.getArtifactPrefix, container, global_definitions);
663     }
664 }
665 
666 void generateNsTestDoubleHdr(LookupT, KindLookupT)(ref CppNamespace ns, Flag!"locationAsComment" loc_as_comment,
667         Parameters params, CppModule hdr, CppModule gmock, LookupT lookup, KindLookupT kind_lookup) {
668     import cpptooling.generator.classes : generateHdr;
669     import cpptooling.generator.gmock : generateGmock;
670 
671     auto test_double_ns = hdr.namespace(ns.name);
672     test_double_ns.suppressIndent(1);
673     hdr.sep(2);
674 
675     foreach (class_; ns.classRange()) {
676         switch (kind_lookup(class_.id)) {
677         case Kind.none:
678         case Kind.initGlobalsToZero:
679         case Kind.adapter:
680             generateHdr(class_, test_double_ns, loc_as_comment, lookup);
681             break;
682         case Kind.initGlobalInterface:
683         case Kind.testDoubleInterface:
684             generateHdr(class_, test_double_ns, loc_as_comment, lookup, Yes.inlineDtor);
685             break;
686         case Kind.gmock:
687             auto mock_ns = gmock.namespace(params.getMainNs).noIndent;
688             generateGmock(class_, mock_ns);
689             break;
690         default:
691         }
692     }
693 }
694 
695 void generateNsTestDoubleImpl(ref CppNamespace ns, CppModule impl,
696         CppModule mutable_extern_hook, ref ImplData data, StubPrefix prefix, ref Container container) {
697     import dextool.plugin.ctestdouble.backend.global : generateGlobalExterns,
698         generateInitGlobalsToZero;
699     import dextool.plugin.ctestdouble.backend.adapter : generateClassImplAdapter = generateImpl;
700 
701     auto test_double_ns = impl.namespace(ns.name);
702     test_double_ns.suppressIndent(1);
703     impl.sep(2);
704 
705     auto lookup(USRType usr) {
706         return usr in data.adapterKind;
707     }
708 
709     foreach (class_; ns.classRange) {
710         switch (data.lookup(class_.id)) {
711         case Kind.adapter:
712             generateClassImplAdapter(class_, data.globals,
713                     prefix, test_double_ns, &lookup);
714             break;
715 
716         case Kind.initGlobalsToZero:
717             generateGlobalExterns(data.globals[],
718                     mutable_extern_hook, container);
719             generateInitGlobalsToZero(class_, test_double_ns, prefix, &data.lookupGlobal);
720             break;
721 
722         default:
723         }
724     }
725 }