1 /**
2  * Functions for raising errors.
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/errors.d, _errors.d)
8  * Documentation:  https://dlang.org/phobos/dmd_errors.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/errors.d
10  */
11 
12 module dmd.errors;
13 
14 import core.stdc.stdarg;
15 import core.stdc.stdio;
16 import core.stdc.stdlib;
17 import core.stdc..string;
18 import dmd.globals;
19 import dmd.root.outbuffer;
20 import dmd.root.rmem;
21 import dmd.root..string;
22 import dmd.console;
23 
24 nothrow:
25 
26 /**
27  * Color highlighting to classify messages
28  */
29 enum Classification
30 {
31     error = Color.brightRed,          /// for errors
32     gagged = Color.brightBlue,        /// for gagged errors
33     warning = Color.brightYellow,     /// for warnings
34     deprecation = Color.brightCyan,   /// for deprecations
35     tip = Color.brightGreen,          /// for tip messages
36 }
37 
38 /**
39  * Print an error message, increasing the global error count.
40  * Params:
41  *      loc    = location of error
42  *      format = printf-style format specification
43  *      ...    = printf-style variadic arguments
44  */
45 extern (C++) void error(const ref Loc loc, const(char)* format, ...)
46 {
47     va_list ap;
48     va_start(ap, format);
49     verror(loc, format, ap);
50     va_end(ap);
51 }
52 
53 /**
54  * Same as above, but allows Loc() literals to be passed.
55  * Params:
56  *      loc    = location of error
57  *      format = printf-style format specification
58  *      ...    = printf-style variadic arguments
59  */
60 extern (D) void error(Loc loc, const(char)* format, ...)
61 {
62     va_list ap;
63     va_start(ap, format);
64     verror(loc, format, ap);
65     va_end(ap);
66 }
67 
68 /**
69  * Same as above, but takes a filename and line information arguments as separate parameters.
70  * Params:
71  *      filename = source file of error
72  *      linnum   = line in the source file
73  *      charnum  = column number on the line
74  *      format   = printf-style format specification
75  *      ...      = printf-style variadic arguments
76  */
77 extern (C++) void error(const(char)* filename, uint linnum, uint charnum, const(char)* format, ...)
78 {
79     const loc = Loc(filename, linnum, charnum);
80     va_list ap;
81     va_start(ap, format);
82     verror(loc, format, ap);
83     va_end(ap);
84 }
85 
86 /**
87  * Print additional details about an error message.
88  * Doesn't increase the error count or print an additional error prefix.
89  * Params:
90  *      loc    = location of error
91  *      format = printf-style format specification
92  *      ...    = printf-style variadic arguments
93  */
94 extern (C++) void errorSupplemental(const ref Loc loc, const(char)* format, ...)
95 {
96     va_list ap;
97     va_start(ap, format);
98     verrorSupplemental(loc, format, ap);
99     va_end(ap);
100 }
101 
102 /**
103  * Print a warning message, increasing the global warning count.
104  * Params:
105  *      loc    = location of warning
106  *      format = printf-style format specification
107  *      ...    = printf-style variadic arguments
108  */
109 extern (C++) void warning(const ref Loc loc, const(char)* format, ...)
110 {
111     va_list ap;
112     va_start(ap, format);
113     vwarning(loc, format, ap);
114     va_end(ap);
115 }
116 
117 /**
118  * Print additional details about a warning message.
119  * Doesn't increase the warning count or print an additional warning prefix.
120  * Params:
121  *      loc    = location of warning
122  *      format = printf-style format specification
123  *      ...    = printf-style variadic arguments
124  */
125 extern (C++) void warningSupplemental(const ref Loc loc, const(char)* format, ...)
126 {
127     va_list ap;
128     va_start(ap, format);
129     vwarningSupplemental(loc, format, ap);
130     va_end(ap);
131 }
132 
133 /**
134  * Print a deprecation message, may increase the global warning or error count
135  * depending on whether deprecations are ignored.
136  * Params:
137  *      loc    = location of deprecation
138  *      format = printf-style format specification
139  *      ...    = printf-style variadic arguments
140  */
141 extern (C++) void deprecation(const ref Loc loc, const(char)* format, ...)
142 {
143     va_list ap;
144     va_start(ap, format);
145     vdeprecation(loc, format, ap);
146     va_end(ap);
147 }
148 
149 /**
150  * Print additional details about a deprecation message.
151  * Doesn't increase the error count, or print an additional deprecation prefix.
152  * Params:
153  *      loc    = location of deprecation
154  *      format = printf-style format specification
155  *      ...    = printf-style variadic arguments
156  */
157 extern (C++) void deprecationSupplemental(const ref Loc loc, const(char)* format, ...)
158 {
159     va_list ap;
160     va_start(ap, format);
161     vdeprecationSupplemental(loc, format, ap);
162     va_end(ap);
163 }
164 
165 /**
166  * Print a verbose message.
167  * Doesn't prefix or highlight messages.
168  * Params:
169  *      loc    = location of message
170  *      format = printf-style format specification
171  *      ...    = printf-style variadic arguments
172  */
173 extern (C++) void message(const ref Loc loc, const(char)* format, ...)
174 {
175     va_list ap;
176     va_start(ap, format);
177     vmessage(loc, format, ap);
178     va_end(ap);
179 }
180 
181 /**
182  * Same as above, but doesn't take a location argument.
183  * Params:
184  *      format = printf-style format specification
185  *      ...    = printf-style variadic arguments
186  */
187 extern (C++) void message(const(char)* format, ...)
188 {
189     va_list ap;
190     va_start(ap, format);
191     vmessage(Loc.initial, format, ap);
192     va_end(ap);
193 }
194 
195 /**
196  * The type of the diagnostic handler
197  * see verrorPrint for arguments
198  * Returns: true if error handling is done, false to continue printing to stderr
199  */
200 alias DiagnosticHandler = bool delegate(const ref Loc location, Color headerColor, const(char)* header, const(char)* messageFormat, va_list args, const(char)* prefix1, const(char)* prefix2);
201 
202 /**
203  * The diagnostic handler.
204  * If non-null it will be called for every diagnostic message issued by the compiler.
205  * If it returns false, the message will be printed to stderr as usual.
206  */
207 __gshared DiagnosticHandler diagnosticHandler;
208 
209 /**
210  * Print a tip message with the prefix and highlighting.
211  * Params:
212  *      format = printf-style format specification
213  *      ...    = printf-style variadic arguments
214  */
215 extern (C++) void tip(const(char)* format, ...)
216 {
217     va_list ap;
218     va_start(ap, format);
219     vtip(format, ap);
220     va_end(ap);
221 }
222 
223 /**
224  * Just print to stderr, doesn't care about gagging.
225  * (format,ap) text within backticks gets syntax highlighted.
226  * Params:
227  *      loc         = location of error
228  *      headerColor = color to set `header` output to
229  *      header      = title of error message
230  *      format      = printf-style format specification
231  *      ap          = printf-style variadic arguments
232  *      p1          = additional message prefix
233  *      p2          = additional message prefix
234  */
235 private void verrorPrint(const ref Loc loc, Color headerColor, const(char)* header,
236         const(char)* format, va_list ap, const(char)* p1 = null, const(char)* p2 = null)
237 {
238     if (diagnosticHandler && diagnosticHandler(loc, headerColor, header, format, ap, p1, p2))
239         return;
240 
241     if (global.params.showGaggedErrors && global.gag)
242         fprintf(stderr, "(spec:%d) ", global.gag);
243     Console* con = cast(Console*)global.console;
244     const p = loc.toChars();
245     if (con)
246         con.setColorBright(true);
247     if (*p)
248     {
249         fprintf(stderr, "%s: ", p);
250         mem.xfree(cast(void*)p);
251     }
252     if (con)
253         con.setColor(headerColor);
254     fputs(header, stderr);
255     if (con)
256         con.resetColor();
257     OutBuffer tmp;
258     if (p1)
259     {
260         tmp.writestring(p1);
261         tmp.writestring(" ");
262     }
263     if (p2)
264     {
265         tmp.writestring(p2);
266         tmp.writestring(" ");
267     }
268     tmp.vprintf(format, ap);
269 
270     if (con && strchr(tmp.peekChars(), '`'))
271     {
272         colorSyntaxHighlight(tmp);
273         writeHighlights(con, tmp);
274     }
275     else
276         fputs(tmp.peekChars(), stderr);
277     fputc('\n', stderr);
278 
279     if (global.params.printErrorContext &&
280         // ignore invalid files
281         loc != Loc.initial &&
282         // ignore mixins for now
283         !loc.filename.strstr(".d-mixin-") &&
284         !global.params.mixinOut)
285     {
286         import dmd.filecache : FileCache;
287         auto fllines = FileCache.fileCache.addOrGetFile(loc.filename.toDString());
288 
289         if (loc.linnum - 1 < fllines.lines.length)
290         {
291             auto line = fllines.lines[loc.linnum - 1];
292             if (loc.charnum < line.length)
293             {
294                 fprintf(stderr, "%.*s\n", cast(int)line.length, line.ptr);
295                 foreach (_; 1 .. loc.charnum)
296                     fputc(' ', stderr);
297 
298                 fputc('^', stderr);
299                 fputc('\n', stderr);
300             }
301         }
302     }
303     fflush(stderr);     // ensure it gets written out in case of compiler aborts
304 }
305 
306 /**
307  * Same as $(D error), but takes a va_list parameter, and optionally additional message prefixes.
308  * Params:
309  *      loc    = location of error
310  *      format = printf-style format specification
311  *      ap     = printf-style variadic arguments
312  *      p1     = additional message prefix
313  *      p2     = additional message prefix
314  *      header = title of error message
315  */
316 extern (C++) void verror(const ref Loc loc, const(char)* format, va_list ap, const(char)* p1 = null, const(char)* p2 = null, const(char)* header = "Error: ")
317 {
318     global.errors++;
319     if (!global.gag)
320     {
321         verrorPrint(loc, Classification.error, header, format, ap, p1, p2);
322         if (global.params.errorLimit && global.errors >= global.params.errorLimit)
323             fatal(); // moderate blizzard of cascading messages
324     }
325     else
326     {
327         if (global.params.showGaggedErrors)
328             verrorPrint(loc, Classification.gagged, header, format, ap, p1, p2);
329         global.gaggedErrors++;
330     }
331 }
332 
333 /**
334  * Same as $(D errorSupplemental), but takes a va_list parameter.
335  * Params:
336  *      loc    = location of error
337  *      format = printf-style format specification
338  *      ap     = printf-style variadic arguments
339  */
340 extern (C++) void verrorSupplemental(const ref Loc loc, const(char)* format, va_list ap)
341 {
342     Color color;
343     if (global.gag)
344     {
345         if (!global.params.showGaggedErrors)
346             return;
347         color = Classification.gagged;
348     }
349     else
350         color = Classification.error;
351     verrorPrint(loc, color, "       ", format, ap);
352 }
353 
354 /**
355  * Same as $(D warning), but takes a va_list parameter.
356  * Params:
357  *      loc    = location of warning
358  *      format = printf-style format specification
359  *      ap     = printf-style variadic arguments
360  */
361 extern (C++) void vwarning(const ref Loc loc, const(char)* format, va_list ap)
362 {
363     if (global.params.warnings != DiagnosticReporting.off)
364     {
365         if (!global.gag)
366         {
367             verrorPrint(loc, Classification.warning, "Warning: ", format, ap);
368             if (global.params.warnings == DiagnosticReporting.error)
369                 global.warnings++;
370         }
371         else
372         {
373             global.gaggedWarnings++;
374         }
375     }
376 }
377 
378 /**
379  * Same as $(D warningSupplemental), but takes a va_list parameter.
380  * Params:
381  *      loc    = location of warning
382  *      format = printf-style format specification
383  *      ap     = printf-style variadic arguments
384  */
385 extern (C++) void vwarningSupplemental(const ref Loc loc, const(char)* format, va_list ap)
386 {
387     if (global.params.warnings != DiagnosticReporting.off && !global.gag)
388         verrorPrint(loc, Classification.warning, "       ", format, ap);
389 }
390 
391 /**
392  * Same as $(D deprecation), but takes a va_list parameter, and optionally additional message prefixes.
393  * Params:
394  *      loc    = location of deprecation
395  *      format = printf-style format specification
396  *      ap     = printf-style variadic arguments
397  *      p1     = additional message prefix
398  *      p2     = additional message prefix
399  */
400 extern (C++) void vdeprecation(const ref Loc loc, const(char)* format, va_list ap, const(char)* p1 = null, const(char)* p2 = null)
401 {
402     __gshared const(char)* header = "Deprecation: ";
403     if (global.params.useDeprecated == DiagnosticReporting.error)
404         verror(loc, format, ap, p1, p2, header);
405     else if (global.params.useDeprecated == DiagnosticReporting.inform)
406     {
407         if (!global.gag)
408         {
409             verrorPrint(loc, Classification.deprecation, header, format, ap, p1, p2);
410         }
411         else
412         {
413             global.gaggedWarnings++;
414         }
415     }
416 }
417 
418 /**
419  * Same as $(D message), but takes a va_list parameter.
420  * Params:
421  *      loc       = location of message
422  *      format    = printf-style format specification
423  *      ap        = printf-style variadic arguments
424  */
425 extern (C++) void vmessage(const ref Loc loc, const(char)* format, va_list ap)
426 {
427     const p = loc.toChars();
428     if (*p)
429     {
430         fprintf(stdout, "%s: ", p);
431         mem.xfree(cast(void*)p);
432     }
433     OutBuffer tmp;
434     tmp.vprintf(format, ap);
435     fputs(tmp.peekChars(), stdout);
436     fputc('\n', stdout);
437     fflush(stdout);     // ensure it gets written out in case of compiler aborts
438 }
439 
440 /**
441  * Same as $(D tip), but takes a va_list parameter.
442  * Params:
443  *      format    = printf-style format specification
444  *      ap        = printf-style variadic arguments
445  */
446 extern (C++) void vtip(const(char)* format, va_list ap)
447 {
448     if (!global.gag)
449     {
450         Loc loc = Loc.init;
451         verrorPrint(loc, Classification.tip, "  Tip: ", format, ap);
452     }
453 }
454 
455 /**
456  * Same as $(D deprecationSupplemental), but takes a va_list parameter.
457  * Params:
458  *      loc    = location of deprecation
459  *      format = printf-style format specification
460  *      ap     = printf-style variadic arguments
461  */
462 extern (C++) void vdeprecationSupplemental(const ref Loc loc, const(char)* format, va_list ap)
463 {
464     if (global.params.useDeprecated == DiagnosticReporting.error)
465         verrorSupplemental(loc, format, ap);
466     else if (global.params.useDeprecated == DiagnosticReporting.inform && !global.gag)
467         verrorPrint(loc, Classification.deprecation, "       ", format, ap);
468 }
469 
470 /**
471  * Call this after printing out fatal error messages to clean up and exit
472  * the compiler.
473  */
474 extern (C++) void fatal()
475 {
476     version (none)
477     {
478         halt();
479     }
480     exit(EXIT_FAILURE);
481 }
482 
483 /**
484  * Try to stop forgetting to remove the breakpoints from
485  * release builds.
486  */
487 extern (C++) void halt()
488 {
489     assert(0);
490 }
491 
492 /**
493  * Scan characters in `buf`. Assume text enclosed by `...`
494  * is D source code, and color syntax highlight it.
495  * Modify contents of `buf` with highlighted result.
496  * Many parallels to ddoc.highlightText().
497  * Params:
498  *      buf = text containing `...` code to highlight
499  */
500 private void colorSyntaxHighlight(ref OutBuffer buf)
501 {
502     //printf("colorSyntaxHighlight('%.*s')\n", cast(int)buf.length, buf.data);
503     bool inBacktick = false;
504     size_t iCodeStart = 0;
505     size_t offset = 0;
506     for (size_t i = offset; i < buf.length; ++i)
507     {
508         char c = buf[i];
509         switch (c)
510         {
511             case '`':
512                 if (inBacktick)
513                 {
514                     inBacktick = false;
515                     OutBuffer codebuf;
516                     codebuf.write(buf[iCodeStart + 1 .. i]);
517                     codebuf.writeByte(0);
518                     // escape the contents, but do not perform highlighting except for DDOC_PSYMBOL
519                     colorHighlightCode(codebuf);
520                     buf.remove(iCodeStart, i - iCodeStart + 1); // also trimming off the current `
521                     immutable pre = "";
522                     i = buf.insert(iCodeStart, pre);
523                     i = buf.insert(i, codebuf[]);
524                     i--; // point to the ending ) so when the for loop does i++, it will see the next character
525                     break;
526                 }
527                 inBacktick = true;
528                 iCodeStart = i;
529                 break;
530 
531             default:
532                 break;
533         }
534     }
535 }
536 
537 
538 /**
539  * Embed these highlighting commands in the text stream.
540  * HIGHLIGHT.Escape indicates a Color follows.
541  */
542 enum HIGHLIGHT : ubyte
543 {
544     Default    = Color.black,           // back to whatever the console is set at
545     Escape     = '\xFF',                // highlight Color follows
546     Identifier = Color.white,
547     Keyword    = Color.white,
548     Literal    = Color.white,
549     Comment    = Color.darkGray,
550     Other      = Color.cyan,           // other tokens
551 }
552 
553 /**
554  * Highlight code for CODE section.
555  * Rewrite the contents of `buf` with embedded highlights.
556  * Analogous to doc.highlightCode2()
557  */
558 
559 private void colorHighlightCode(ref OutBuffer buf)
560 {
561     import dmd.lexer;
562     import dmd.tokens;
563     import dmd.diagnostic : DefaultDiagnosticHandler;
564 
565     __gshared int nested;
566     if (nested)
567     {
568         // Should never happen, but don't infinitely recurse if it does
569         --nested;
570         return;
571     }
572     ++nested;
573 
574     auto gaggedErrorsSave = global.startGagging();
575     auto diagnosticHandler = DefaultDiagnosticHandler();
576     scope Lexer lex = new Lexer(null, cast(char*)buf[].ptr, 0, buf.length - 1, 0, 1, diagnosticHandler.diagnosticHandler);
577     OutBuffer res;
578     const(char)* lastp = cast(char*)buf[].ptr;
579     //printf("colorHighlightCode('%.*s')\n", cast(int)(buf.length - 1), buf.data);
580     res.reserve(buf.length);
581     res.writeByte(HIGHLIGHT.Escape);
582     res.writeByte(HIGHLIGHT.Other);
583     while (1)
584     {
585         Token tok;
586         lex.scan(&tok);
587         diagnosticHandler.report();
588         res.writestring(lastp[0 .. tok.ptr - lastp]);
589         HIGHLIGHT highlight;
590         switch (tok.value)
591         {
592         case TOK.identifier:
593             highlight = HIGHLIGHT.Identifier;
594             break;
595         case TOK.comment:
596             highlight = HIGHLIGHT.Comment;
597             break;
598         case TOK.int32Literal:
599             ..
600         case TOK.dcharLiteral:
601         case TOK.string_:
602             highlight = HIGHLIGHT.Literal;
603             break;
604         default:
605             if (tok.isKeyword())
606                 highlight = HIGHLIGHT.Keyword;
607             break;
608         }
609         if (highlight != HIGHLIGHT.Default)
610         {
611             res.writeByte(HIGHLIGHT.Escape);
612             res.writeByte(highlight);
613             res.writestring(tok.ptr[0 .. lex.p - tok.ptr]);
614             res.writeByte(HIGHLIGHT.Escape);
615             res.writeByte(HIGHLIGHT.Other);
616         }
617         else
618             res.writestring(tok.ptr[0 .. lex.p - tok.ptr]);
619         if (tok.value == TOK.endOfFile)
620             break;
621         lastp = lex.p;
622     }
623     res.writeByte(HIGHLIGHT.Escape);
624     res.writeByte(HIGHLIGHT.Default);
625     //printf("res = '%.*s'\n", cast(int)buf.length, buf.data);
626     buf.setsize(0);
627     buf.write(&res);
628     global.endGagging(gaggedErrorsSave);
629     --nested;
630 }
631 
632 /**
633  * Write the buffer contents with embedded highlights to stderr.
634  * Params:
635  *      buf = highlighted text
636  */
637 private void writeHighlights(Console* con, ref const OutBuffer buf)
638 {
639     bool colors;
640     scope (exit)
641     {
642         /* Do not mess up console if highlighting aborts
643          */
644         if (colors)
645             con.resetColor();
646     }
647 
648     for (size_t i = 0; i < buf.length; ++i)
649     {
650         const c = buf[i];
651         if (c == HIGHLIGHT.Escape)
652         {
653             const color = buf[++i];
654             if (color == HIGHLIGHT.Default)
655             {
656                 con.resetColor();
657                 colors = false;
658             }
659             else
660             if (color == Color.white)
661             {
662                 con.resetColor();
663                 con.setColorBright(true);
664                 colors = true;
665             }
666             else
667             {
668                 con.setColor(cast(Color)color);
669                 colors = true;
670             }
671         }
672         else
673             fputc(c, con.fp);
674     }
675 }