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