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