1 /**
2  * Contains high-level interfaces for interacting with DMD as a library.
3  *
4  * Copyright:   Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/id.d, _id.d)
8  * Documentation:  https://dlang.org/phobos/dmd_frontend.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/frontend.d
10  */
11 module dmd.frontend;
12 
13 import dmd.astcodegen : ASTCodegen;
14 import dmd.dmodule : Module;
15 import dmd.globals : CHECKENABLE, Loc, DiagnosticReporting, Param;
16 import dmd.errors : DiagnosticHandler, diagnosticHandler, Classification;
17 import dmd.target : Target;
18 
19 import std.range.primitives : isInputRange, ElementType;
20 import std.traits : isNarrowString;
21 import std.typecons : Tuple;
22 import core.stdc.stdarg;
23 
24 version (Windows) private enum sep = ";", exe = ".exe";
25 version (Posix) private enum sep = ":", exe = "";
26 
27 /// Contains aggregated diagnostics information.
28 immutable struct Diagnostics
29 {
30     /// Number of errors diagnosed
31     uint errors;
32 
33     /// Number of warnings diagnosed
34     uint warnings;
35 
36     /// Returns: `true` if errors have been diagnosed
37     bool hasErrors()
38     {
39         return errors > 0;
40     }
41 
42     /// Returns: `true` if warnings have been diagnosed
43     bool hasWarnings()
44     {
45         return warnings > 0;
46     }
47 }
48 
49 /// Indicates the checking state of various contracts.
50 enum ContractChecking : CHECKENABLE
51 {
52     /// Initial value
53     default_ = CHECKENABLE._default,
54 
55     /// Never do checking
56     disabled = CHECKENABLE.off,
57 
58     /// Always do checking
59     enabled = CHECKENABLE.on,
60 
61     /// Only do checking in `@safe` functions
62     enabledInSafe = CHECKENABLE.safeonly
63 }
64 
65 unittest
66 {
67     static assert(
68         __traits(allMembers, ContractChecking).length ==
69         __traits(allMembers, CHECKENABLE).length
70     );
71 }
72 
73 /// Indicates which contracts should be checked or not.
74 struct ContractChecks
75 {
76     /// Precondition checks (in contract).
77     ContractChecking precondition = ContractChecking.enabled;
78 
79     /// Invariant checks.
80     ContractChecking invariant_ = ContractChecking.enabled;
81 
82     /// Postcondition checks (out contract).
83     ContractChecking postcondition = ContractChecking.enabled;
84 
85     /// Array bound checks.
86     ContractChecking arrayBounds = ContractChecking.enabled;
87 
88     /// Assert checks.
89     ContractChecking assert_ = ContractChecking.enabled;
90 
91     /// Switch error checks.
92     ContractChecking switchError = ContractChecking.enabled;
93 }
94 
95 /*
96 Initializes the global variables of the DMD compiler.
97 This needs to be done $(I before) calling any function.
98 
99 Params:
100     handler = a delegate to configure what to do with diagnostics (other than printing to console or stderr).
101     contractChecks = indicates which contracts should be enabled or not
102     versionIdentifiers = a list of version identifiers that should be enabled
103 */
104 void initDMD(
105     DiagnosticHandler handler = null,
106     const string[] versionIdentifiers = [],
107     ContractChecks contractChecks = ContractChecks(),
108     Target.Architecture architecture = Target.defaultArchitecture
109 )
110 {
111     import dmd.globals : global;
112 
113     diagnosticHandler = handler;
114 
115     global._init();
116     setTarget(global.params, architecture);
117     setContractChecks(contractChecks, global.params);
118     initializeStaticData(global.params);
119     setVersionIdentifiers(versionIdentifiers, global.params);
120 }
121 
122 void setContractChecks(ContractChecks contractChecks, ref Param params)
123 {
124     with (params)
125     {
126         useIn = contractChecks.precondition;
127         useInvariants = contractChecks.invariant_;
128         useOut = contractChecks.postcondition;
129         useArrayBounds = contractChecks.arrayBounds;
130         useAssert = contractChecks.assert_;
131         useSwitchError = contractChecks.switchError;
132     }
133 }
134 
135 void setTarget(ref Param params, Target.Architecture architecture)
136 {
137     import dmd.mars : setTarget, setTargetCPU;
138     import dmd.target : is64bit;
139 
140     params.isLP64 = params.is64bit = architecture.is64bit;
141     setTarget(params);
142     setTargetCPU(params);
143 }
144 
145 void setVersionIdentifiers(const string[] versionIdentifiers, const ref Param params)
146 {
147     import std.algorithm : each;
148 
149     import dmd.cond : VersionCondition;
150     import dmd.mars : addDefaultVersionIdentifiers;
151 
152     versionIdentifiers.each!(VersionCondition.addGlobalIdent);
153     addDefaultVersionIdentifiers(params);
154 }
155 
156 void initializeStaticData(ref Param params)
157 {
158     import dmd.root.ctfloat : CTFloat;
159 
160     version (CRuntime_Microsoft)
161         import dmd.root.longdouble : initFPU;
162 
163     import dmd.dmodule : Module;
164     import dmd.expression : Expression;
165     import dmd.filecache : FileCache;
166     import dmd.globals : global;
167     import dmd.id : Id;
168     import dmd.mtype : Type;
169     import dmd.objc : Objc;
170     import dmd.target : target;
171 
172     Type._init();
173     Id.initialize();
174     Module._init();
175     target._init(global.params);
176     Expression._init();
177     Objc._init();
178     FileCache._init();
179 
180     version (CRuntime_Microsoft)
181         initFPU();
182 
183     CTFloat.initialize();
184 }
185 
186 /**
187 Deinitializes the global variables of the DMD compiler.
188 
189 This can be used to restore the state set by `initDMD` to its original state.
190 Useful if there's a need for multiple sessions of the DMD compiler in the same
191 application.
192 */
193 void deinitializeDMD()
194 {
195     import dmd.dmodule : Module;
196     import dmd.expression : Expression;
197     import dmd.globals : global;
198     import dmd.id : Id;
199     import dmd.mtype : Type;
200     import dmd.objc : Objc;
201     import dmd.target : target;
202 
203     diagnosticHandler = null;
204 
205     global.deinitialize();
206 
207     Type.deinitialize();
208     Id.deinitialize();
209     Module.deinitialize();
210     target.deinitialize();
211     Expression.deinitialize();
212     Objc.deinitialize();
213 }
214 
215 /**
216 Add import path to the `global.path`.
217 Params:
218     path = import to add
219 */
220 void addImport(const(char)[] path)
221 {
222     import dmd.globals : global;
223     import dmd.arraytypes : Strings;
224     import std.string : toStringz;
225 
226     if (global.path is null)
227         global.path = new Strings();
228 
229     global.path.push(path.toStringz);
230 }
231 
232 /**
233 Add string import path to `global.filePath`.
234 Params:
235     path = string import to add
236 */
237 void addStringImport(const(char)[] path)
238 {
239     import std.string : toStringz;
240 
241     import dmd.globals : global;
242     import dmd.arraytypes : Strings;
243 
244     if (global.filePath is null)
245         global.filePath = new Strings();
246 
247     global.filePath.push(path.toStringz);
248 }
249 
250 /**
251 Searches for a `dmd.conf`.
252 
253 Params:
254     dmdFilePath = path to the current DMD executable
255 
256 Returns: full path to the found `dmd.conf`, `null` otherwise.
257 */
258 string findDMDConfig(const(char)[] dmdFilePath)
259 {
260     string findSnapConfig(string configPath)
261     {
262         import std.algorithm : filter, findSplitAfter, map, startsWith;
263         import std.process : execute;
264         import std.string : lineSplitter;
265 
266         if (!dmdFilePath.startsWith("/snap"))
267             return configPath;
268 
269         enum prefix = "Config file: ";
270 
271         auto result = execute([dmdFilePath, "-v"])
272             .output
273             .lineSplitter
274             .filter!(line => line.startsWith(prefix))
275             .map!(line => line.findSplitAfter(prefix)[1]);
276 
277         return result.empty ? configPath : result.front;
278     }
279 
280     import std.algorithm : filter, findSplitAfter, startsWith;
281     import std.file : exists;
282     import std.process : execute;
283     import std.string : lineSplitter;
284 
285     import dmd.dinifile : findConfFile;
286 
287     version (Windows)
288         enum configFile = "sc.ini";
289     else
290         enum configFile = "dmd.conf";
291 
292     const configPath = findConfFile(dmdFilePath, configFile).idup;
293 
294     return configPath.exists ? configPath : findSnapConfig(configPath);
295 }
296 
297 /**
298 Searches for a `ldc2.conf`.
299 
300 Params:
301     ldcFilePath = path to the current LDC executable
302 
303 Returns: full path to the found `ldc2.conf`, `null` otherwise.
304 */
305 string findLDCConfig(const(char)[] ldcFilePath)
306 {
307     string findSnapConfig()
308     {
309         import std.algorithm : filter, findSplitAfter, findSplitBefore, map, startsWith;
310         import std.process : execute;
311         import std.string : lineSplitter, strip;
312 
313         if (!ldcFilePath.startsWith("/snap"))
314             return null;
315 
316         enum prefix = "config";
317 
318         auto result = execute([ldcFilePath, "-v"])
319             .output
320             .lineSplitter
321             .filter!(line => line.startsWith(prefix))
322             .map!(line => line.findSplitAfter(prefix)[1])
323             .map!(line => line.findSplitBefore("(")[0])
324             .map!(line => line.strip);
325 
326         return result.empty ? null : result.front;
327     }
328 
329     import std.file : getcwd;
330     import std.path : buildPath, dirName;
331     import std.algorithm.iteration : filter;
332     import std.file : exists;
333 
334     auto execDir = ldcFilePath.dirName;
335 
336     enum ldcConfig = "ldc2.conf";
337     // https://wiki.dlang.org/Using_LDC
338     auto ldcConfigs = [
339         getcwd.buildPath(ldcConfig),
340         execDir.buildPath(ldcConfig),
341         execDir.dirName.buildPath("etc", ldcConfig),
342         "~/.ldc".buildPath(ldcConfig),
343         execDir.buildPath("etc", ldcConfig),
344         execDir.buildPath("etc", "ldc", ldcConfig),
345         "/etc".buildPath(ldcConfig),
346         "/etc/ldc".buildPath(ldcConfig),
347     ].filter!exists;
348 
349     return ldcConfigs.empty ? findSnapConfig() : ldcConfigs.front;
350 }
351 
352 /**
353 Detect the currently active compiler.
354 Returns: full path to the executable of the found compiler, `null` otherwise.
355 */
356 string determineDefaultCompiler()
357 {
358     import std.algorithm.iteration : filter, joiner, map, splitter;
359     import std.file : exists;
360     import std.path : buildPath;
361     import std.process : environment;
362     import std.range : front, empty, transposed;
363     // adapted from DUB: https://github.com/dlang/dub/blob/350a0315c38fab9d3d0c4c9d30ff6bb90efb54d6/source/dub/dub.d#L1183
364 
365     auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
366 
367     // Search the user's PATH for the compiler binary
368     if ("DMD" in environment)
369         compilers = environment.get("DMD") ~ compilers;
370     auto paths = environment.get("PATH", "").splitter(sep);
371     auto res = compilers.map!(c => paths.map!(p => p.buildPath(c~exe))).joiner.filter!exists;
372     return !res.empty ? res.front : null;
373 }
374 
375 /**
376 Parses a `dmd.conf` or `ldc2.conf` config file and returns defined import paths.
377 
378 Params:
379     iniFile = iniFile to parse imports from
380     execDir = the path `@P`/`ldcbinarypath` will expand to
381 
382 Returns: forward range of import paths found in `iniFile`
383 */
384 auto parseImportPathsFromConfig(const(char)[] iniFile, const(char)[] execDir)
385 {
386     import std.algorithm, std.range, std.regex;
387     import std.stdio : File;
388     import std.path : buildNormalizedPath;
389 
390     alias expandConfigVariables = a => a.drop(2) // -I
391                                 // "set" common config variables
392                                 .replace("%@P%", execDir)
393                                 .replace("%%ldcbinarypath%%", execDir);
394 
395     // search for all -I imports in this file
396     alias searchForImports = l => l.matchAll(`-I[^ "]+`.regex).joiner.map!expandConfigVariables;
397 
398     return File(iniFile, "r")
399         .byLineCopy
400         .map!searchForImports
401         .joiner
402         // remove duplicated imports paths
403         .array
404         .sort
405         .uniq
406         .map!buildNormalizedPath;
407 }
408 
409 /**
410 Finds a `dmd.conf` and parses it for import paths.
411 This depends on the `$DMD` environment variable.
412 If `$DMD` is set to `ldmd`, it will try to detect and parse a `ldc2.conf` instead.
413 
414 Params:
415     configFilename = the path to the config file. If this is `null` or empty,
416         it will automatically try to find the config file.
417 
418 Returns:
419     A forward range of normalized import paths.
420 
421 See_Also: $(LREF determineDefaultCompiler), $(LREF parseImportPathsFromConfig)
422 */
423 auto findImportPaths(string configFilename = null)
424 {
425     import std.path : dirName;
426 
427     immutable compilerPath = determineDefaultCompiler;
428 
429     if (configFilename.length == 0)
430         configFilename = findConfigFilename(compilerPath);
431 
432     return configFilename.parseImportPathsFromConfig(configFilename.dirName);
433 }
434 
435 string findConfigFilename(string compilerPath = determineDefaultCompiler)
436 in
437 {
438     assert(compilerPath !is null, "No D compiler found. `Use parseImportsFromConfig` manually.");
439 }
440 out(result)
441 {
442     import std.file : exists;
443     assert(result !is null && result.exists, "No valid config found.");
444 }
445 do
446 {
447     import std.algorithm.searching : endsWith;
448 
449     immutable isLDC = compilerPath.endsWith("ldc"~exe, "ldc2"~exe, "ldmd"~exe, "ldmd2"~exe);
450 
451     return isLDC ? findLDCConfig(compilerPath) : findDMDConfig(compilerPath);
452 }
453 
454 /**
455 Parse a module from a string.
456 
457 Params:
458     fileName = file to parse
459     code = text to use instead of opening the file
460 
461 Returns: the parsed module object
462 */
463 Tuple!(Module, "module_", Diagnostics, "diagnostics") parseModule(AST = ASTCodegen)(
464     const(char)[] fileName,
465     const(char)[] code = null)
466 {
467     import dmd.root.file : File, FileBuffer;
468 
469     import dmd.globals : Loc, global;
470     import dmd.parse : Parser;
471     import dmd.identifier : Identifier;
472     import dmd.tokens : TOK;
473 
474     import std.path : baseName, stripExtension;
475     import std.string : toStringz;
476     import std.typecons : tuple;
477 
478     auto id = Identifier.idPool(fileName.baseName.stripExtension);
479     auto m = new Module(fileName, id, 0, 0);
480 
481     if (code is null)
482         m.read(Loc.initial);
483     else
484     {
485         File.ReadResult readResult = {
486             success: true,
487             buffer: FileBuffer(cast(ubyte[]) code.dup ~ '\0')
488         };
489 
490         m.loadSourceBuffer(Loc.initial, readResult);
491     }
492 
493     m.parseModule!AST();
494 
495     Diagnostics diagnostics = {
496         errors: global.errors,
497         warnings: global.warnings
498     };
499 
500     return typeof(return)(m, diagnostics);
501 }
502 
503 /**
504 Run full semantic analysis on a module.
505 */
506 void fullSemantic(Module m)
507 {
508     import dmd.dsymbolsem : dsymbolSemantic;
509     import dmd.semantic2 : semantic2;
510     import dmd.semantic3 : semantic3;
511 
512     m.importedFrom = m;
513     m.importAll(null);
514 
515     m.dsymbolSemantic(null);
516     Module.dprogress = 1;
517     Module.runDeferredSemantic();
518 
519     m.semantic2(null);
520     Module.runDeferredSemantic2();
521 
522     m.semantic3(null);
523     Module.runDeferredSemantic3();
524 }
525 
526 /**
527 Pretty print a module.
528 
529 Returns:
530     Pretty printed module as string.
531 */
532 string prettyPrint(Module m)
533 {
534     import dmd.root.outbuffer: OutBuffer;
535     import dmd.hdrgen : HdrGenState, moduleToBuffer2;
536 
537     OutBuffer buf = { doindent: 1 };
538     HdrGenState hgs = { fullDump: 1 };
539     moduleToBuffer2(m, &buf, &hgs);
540 
541     import std.string : replace, fromStringz;
542     import std.exception : assumeUnique;
543 
544     auto generated = buf.extractSlice.replace("\t", "    ");
545     return generated.assumeUnique;
546 }
547 
548 /// Interface for diagnostic reporting.
549 abstract class DiagnosticReporter
550 {
551     import dmd.console : Color;
552 
553 nothrow:
554     DiagnosticHandler prevHandler;
555 
556     this()
557     {
558         prevHandler = diagnosticHandler;
559         diagnosticHandler = &diagHandler;
560     }
561 
562     ~this()
563     {
564         // assumed to be used scoped
565         diagnosticHandler = prevHandler;
566     }
567 
568     bool diagHandler(const ref Loc loc, Color headerColor, const(char)* header,
569                      const(char)* format, va_list ap, const(char)* p1, const(char)* p2)
570     {
571         import core.stdc.string;
572 
573         // recover type from header and color
574         if (strncmp (header, "Error:", 6) == 0)
575             return error(loc, format, ap, p1, p2);
576         if (strncmp (header, "Warning:", 8) == 0)
577             return warning(loc, format, ap, p1, p2);
578         if (strncmp (header, "Deprecation:", 12) == 0)
579             return deprecation(loc, format, ap, p1, p2);
580 
581         if (cast(Classification)headerColor == Classification.warning)
582             return warningSupplemental(loc, format, ap, p1, p2);
583         if (cast(Classification)headerColor == Classification.deprecation)
584             return deprecationSupplemental(loc, format, ap, p1, p2);
585 
586         return errorSupplemental(loc, format, ap, p1, p2);
587     }
588 
589     /// Returns: the number of errors that occurred during lexing or parsing.
590     abstract int errorCount();
591 
592     /// Returns: the number of warnings that occurred during lexing or parsing.
593     abstract int warningCount();
594 
595     /// Returns: the number of deprecations that occurred during lexing or parsing.
596     abstract int deprecationCount();
597 
598     /**
599     Reports an error message.
600 
601     Params:
602         loc = Location of error
603         format = format string for error
604         args = printf-style variadic arguments
605         p1 = additional message prefix
606         p2 = additional message prefix
607 
608     Returns: false if the message should also be printed to stderr, true otherwise
609     */
610     abstract bool error(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2);
611 
612     /**
613     Reports additional details about an error message.
614 
615     Params:
616         loc = Location of error
617         format = format string for supplemental message
618         args = printf-style variadic arguments
619         p1 = additional message prefix
620         p2 = additional message prefix
621 
622     Returns: false if the message should also be printed to stderr, true otherwise
623     */
624     abstract bool errorSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2);
625 
626     /**
627     Reports a warning message.
628 
629     Params:
630         loc = Location of warning
631         format = format string for warning
632         args = printf-style variadic arguments
633         p1 = additional message prefix
634         p2 = additional message prefix
635 
636     Returns: false if the message should also be printed to stderr, true otherwise
637     */
638     abstract bool warning(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2);
639 
640     /**
641     Reports additional details about a warning message.
642 
643     Params:
644         loc = Location of warning
645         format = format string for supplemental message
646         args = printf-style variadic arguments
647         p1 = additional message prefix
648         p2 = additional message prefix
649 
650     Returns: false if the message should also be printed to stderr, true otherwise
651     */
652     abstract bool warningSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2);
653 
654     /**
655     Reports a deprecation message.
656 
657     Params:
658         loc = Location of the deprecation
659         format = format string for the deprecation
660         args = printf-style variadic arguments
661         p1 = additional message prefix
662         p2 = additional message prefix
663 
664     Returns: false if the message should also be printed to stderr, true otherwise
665     */
666     abstract bool deprecation(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2);
667 
668     /**
669     Reports additional details about a deprecation message.
670 
671     Params:
672         loc = Location of deprecation
673         format = format string for supplemental message
674         args = printf-style variadic arguments
675         p1 = additional message prefix
676         p2 = additional message prefix
677 
678     Returns: false if the message should also be printed to stderr, true otherwise
679     */
680     abstract bool deprecationSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2);
681 }
682 
683 /**
684 Diagnostic reporter which prints the diagnostic messages to stderr.
685 
686 This is usually the default diagnostic reporter.
687 */
688 final class StderrDiagnosticReporter : DiagnosticReporter
689 {
690     private const DiagnosticReporting useDeprecated;
691 
692     private int errorCount_;
693     private int warningCount_;
694     private int deprecationCount_;
695 
696 nothrow:
697 
698     /**
699     Initializes this object.
700 
701     Params:
702         useDeprecated = indicates how deprecation diagnostics should be
703                         handled
704     */
705     this(DiagnosticReporting useDeprecated)
706     {
707         this.useDeprecated = useDeprecated;
708     }
709 
710     override int errorCount()
711     {
712         return errorCount_;
713     }
714 
715     override int warningCount()
716     {
717         return warningCount_;
718     }
719 
720     override int deprecationCount()
721     {
722         return deprecationCount_;
723     }
724 
725     override bool error(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2)
726     {
727         errorCount_++;
728         return false;
729     }
730 
731     override bool errorSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2)
732     {
733         return false;
734     }
735 
736     override bool warning(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2)
737     {
738         warningCount_++;
739         return false;
740     }
741 
742     override bool warningSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2)
743     {
744         return false;
745     }
746 
747     override bool deprecation(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2)
748     {
749         if (useDeprecated == DiagnosticReporting.error)
750             errorCount_++;
751         else
752             deprecationCount_++;
753         return false;
754     }
755 
756     override bool deprecationSupplemental(const ref Loc loc, const(char)* format, va_list args, const(char)* p1, const(char)* p2)
757     {
758         return false;
759     }
760 }
761