xref: /netbsd-src/external/gpl3/gcc/dist/libphobos/libdruntime/core/internal/dassert.d (revision 0a3071956a3a9fdebdbf7f338cf2d439b45fc728)
1 /*
2  * Support for rich error messages generation with `assert`
3  *
4  * This module provides the `_d_assert_fail` hooks which are instantiated
5  * by the compiler whenever `-checkaction=context` is used.
6  * There are two hooks, one for unary expressions, and one for binary.
7  * When used, the compiler will rewrite `assert(a >= b)` as
8  * `assert(a >= b, _d_assert_fail!(typeof(a))(">=", a, b))`.
9  * Temporaries will be created to avoid side effects if deemed necessary
10  * by the compiler.
11  *
12  * For more information, refer to the implementation in DMD frontend
13  * for `AssertExpression`'s semantic analysis.
14  *
15  * Copyright: D Language Foundation 2018 - 2020
16  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
17  * Source:    $(LINK2 https://github.com/dlang/druntime/blob/master/src/core/internal/dassert.d, _dassert.d)
18  * Documentation: https://dlang.org/phobos/core_internal_dassert.html
19  */
20 module core.internal.dassert;
21 
22 /**
23  * Generates rich assert error messages for unary expressions
24  *
25  * The unary expression `assert(!una)` will be turned into
26  * `assert(!una, _d_assert_fail("!", una))`.
27  * This routine simply acts as if the user wrote `assert(una == false)`.
28  *
29  * Params:
30  *   op = Operator that was used in the expression, currently only "!"
31  *        is supported.
32  *   a  = Result of the expression that was used in `assert` before
33  *        its implicit conversion to `bool`.
34  *
35  * Returns:
36  *   A string such as "$a != true" or "$a == true".
37  */
_d_assert_fail(A)38 string _d_assert_fail(A)(const scope string op, auto ref const scope A a)
39 {
40     // Prevent InvalidMemoryOperationError when triggered from a finalizer
41     if (inFinalizer())
42         return "Assertion failed (rich formatting is disabled in finalizers)";
43 
44     string[2] vals = [ miniFormatFakeAttributes(a), "true" ];
45     immutable token = op == "!" ? "==" : "!=";
46     return combine(vals[0 .. 1], token, vals[1 .. $]);
47 }
48 
49 /**
50  * Generates rich assert error messages for binary expressions
51  *
52  * The binary expression `assert(x == y)` will be turned into
53  * `assert(x == y, _d_assert_fail!(typeof(x))("==", x, y))`.
54  *
55  * Params:
56  *   comp = Comparison operator that was used in the expression.
57  *   a  = Left hand side operand (can be a tuple).
58  *   b  = Right hand side operand (can be a tuple).
59  *
60  * Returns:
61  *   A string such as "$a $comp $b".
62  */
_d_assert_fail(A...)63 template _d_assert_fail(A...)
64 {
65     string _d_assert_fail(B...)(
66         const scope string comp, auto ref const scope A a, auto ref const scope B b)
67     if (B.length != 0 || A.length != 1) // Resolve ambiguity with unary overload
68     {
69         // Prevent InvalidMemoryOperationError when triggered from a finalizer
70         if (inFinalizer())
71             return "Assertion failed (rich formatting is disabled in finalizers)";
72 
73         string[A.length + B.length] vals;
74         static foreach (idx; 0 .. A.length)
75             vals[idx] = miniFormatFakeAttributes(a[idx]);
76         static foreach (idx; 0 .. B.length)
77             vals[A.length + idx] = miniFormatFakeAttributes(b[idx]);
78         immutable token = invertCompToken(comp);
79         return combine(vals[0 .. A.length], token, vals[A.length .. $]);
80     }
81 }
82 
83 /// Combines the supplied arguments into one string `"valA token valB"`
combine(const scope string[]valA,const scope string token,const scope string[]valB)84 private string combine(const scope string[] valA, const scope string token,
85     const scope string[] valB) pure nothrow @nogc @safe
86 {
87     // Each separator is 2 chars (", "), plus the two spaces around the token.
88     size_t totalLen = (valA.length - 1) * 2 +
89         (valB.length - 1) * 2 + 2 + token.length;
90 
91     // Empty arrays are printed as ()
92     if (valA.length == 0) totalLen += 2;
93     if (valB.length == 0) totalLen += 2;
94 
95     foreach (v; valA) totalLen += v.length;
96     foreach (v; valB) totalLen += v.length;
97 
98     // Include braces when printing tuples
99     const printBraces = (valA.length + valB.length) != 2;
100     if (printBraces) totalLen += 4; // '(', ')' for both tuples
101 
102     char[] buffer = cast(char[]) pureAlloc(totalLen)[0 .. totalLen];
103     // @nogc-concat of "<valA> <comp> <valB>"
104     static void formatTuple (scope char[] buffer, ref size_t n, in string[] vals, in bool printBraces)
105     {
106         if (printBraces) buffer[n++] = '(';
107         foreach (idx, v; vals)
108         {
109             if (idx)
110             {
111                 buffer[n++] = ',';
112                 buffer[n++] = ' ';
113             }
114             buffer[n .. n + v.length] = v;
115             n += v.length;
116         }
117         if (printBraces) buffer[n++] = ')';
118     }
119 
120     size_t n;
121     formatTuple(buffer, n, valA, printBraces);
122     buffer[n++] = ' ';
123     buffer[n .. n + token.length] = token;
124     n += token.length;
125     buffer[n++] = ' ';
126     formatTuple(buffer, n, valB, printBraces);
127     return (() @trusted => cast(string) buffer)();
128 }
129 
130 /// Yields the appropriate `printf` format token for a type `T`
getPrintfFormat(T)131 private template getPrintfFormat(T)
132 {
133     static if (is(T == long))
134     {
135         enum getPrintfFormat = "%lld";
136     }
137     else static if (is(T == ulong))
138     {
139         enum getPrintfFormat = "%llu";
140     }
141     else static if (__traits(isIntegral, T))
142     {
143         static if (__traits(isUnsigned, T))
144         {
145             enum getPrintfFormat = "%u";
146         }
147         else
148         {
149             enum getPrintfFormat = "%d";
150         }
151     }
152     else
153     {
154         static assert(0, "Unknown format");
155     }
156 }
157 
158 /**
159  * Generates a textual representation of `v` without relying on Phobos.
160  * The value is formatted as follows:
161  *
162  *  - primitive types and arrays yield their respective literals
163  *  - pointers are printed as hexadecimal numbers
164  *  - enum members are represented by their name
165  *  - user-defined types are formatted by either calling `toString`
166  *    if defined or printing all members, e.g. `S(1, 2)`
167  *
168  * Note that unions are rejected because this method cannot determine which
169  * member is valid when calling this method.
170  *
171  * Params:
172  *   v = the value to print
173  *
174  * Returns: a string respresenting `v` or `V.stringof` if `V` is not supported
175  */
miniFormat(V)176 private string miniFormat(V)(const scope ref V v)
177 {
178     import core.internal.traits: isAggregateType;
179 
180     /// `shared` values are formatted as their base type
181     static if (is(V == shared T, T))
182     {
183         // Use atomics to avoid race conditions whenever possible
184         static if (__traits(compiles, atomicLoad(v)))
185         {
186             if (!__ctfe)
187             {
188                 T tmp = cast(T) atomicLoad(v);
189                 return miniFormat(tmp);
190             }
191         }
192 
193         // Fall back to a simple cast - we're violating the type system anyways
194         return miniFormat(*cast(const T*) &v);
195     }
196     // Format enum members using their name
197     else static if (is(V BaseType == enum))
198     {
199         // Always generate repeated if's instead of switch to skip the detection
200         // of non-integral enums. This method doesn't need to be fast.
201         static foreach (mem; __traits(allMembers, V))
202         {
203             if (v == __traits(getMember, V, mem))
204                 return mem;
205         }
206 
207         // Format invalid enum values as their base type
208         enum cast_ = "cast(" ~ V.stringof ~ ")";
209         const val = miniFormat(__ctfe ? cast(const BaseType) v : *cast(const BaseType*) &v);
210         return combine([ cast_ ], "", [ val ]);
211     }
212     else static if (is(V == bool))
213     {
214         return v ? "true" : "false";
215     }
216     // Detect vectors which match isIntegral / isFloating
217     else static if (is(V == __vector(ET[N]), ET, size_t N))
218     {
219         string msg = "[";
220         foreach (i; 0 .. N)
221         {
222             if (i > 0)
223                 msg ~= ", ";
224 
225             msg ~= miniFormat(v[i]);
226         }
227         msg ~= "]";
228         return msg;
229     }
230     else static if (__traits(isIntegral, V))
231     {
232         static if (is(V == char))
233         {
234             // Avoid invalid code points
235             if (v < 0x7F)
236                 return ['\'', v, '\''];
237 
238             uint tmp = v;
239             return "cast(char) " ~ miniFormat(tmp);
240         }
241         else static if (is(V == wchar) || is(V == dchar))
242         {
243             import core.internal.utf: isValidDchar, toUTF8;
244 
245             // Avoid invalid code points
246             if (isValidDchar(v))
247                 return toUTF8(['\'', v, '\'']);
248 
249             uint tmp = v;
250             return "cast(" ~ V.stringof ~ ") " ~ miniFormat(tmp);
251         }
252         else
253         {
254             import core.internal.string;
255             static if (__traits(isUnsigned, V))
256                 const val = unsignedToTempString(v);
257             else
258                 const val = signedToTempString(v);
259             return val.get().idup();
260         }
261     }
262     else static if (__traits(isFloating, V))
263     {
264         import core.stdc.stdio : sprintf;
265         import core.stdc.config : LD = c_long_double;
266 
267         // No suitable replacement for sprintf in druntime ATM
268         if (__ctfe)
269             return '<' ~ V.stringof ~ " not supported>";
270 
271         // Workaround for https://issues.dlang.org/show_bug.cgi?id=20759
272         static if (is(LD == real))
273             enum realFmt = "%Lg";
274         else
275             enum realFmt = "%g";
276 
277         char[60] val;
278         int len;
279         static if (is(V == float) || is(V == double))
280             len = sprintf(&val[0], "%g", v);
281         else static if (is(V == real))
282             len = sprintf(&val[0], realFmt, cast(LD) v);
283         else static if (is(V == cfloat) || is(V == cdouble))
284             len = sprintf(&val[0], "%g + %gi", v.re, v.im);
285         else static if (is(V == creal))
286             len = sprintf(&val[0], realFmt ~ " + " ~ realFmt ~ 'i', cast(LD) v.re, cast(LD) v.im);
287         else static if (is(V == ifloat) || is(V == idouble))
288             len = sprintf(&val[0], "%gi", v);
289         else // ireal
290         {
291             static assert(is(V == ireal));
292             static if (is(LD == real))
293                 alias R = ireal;
294             else
295                 alias R = idouble;
296             len = sprintf(&val[0], realFmt ~ 'i', cast(R) v);
297         }
298         return val.idup[0 .. len];
299     }
300     // special-handling for void-arrays
301     else static if (is(V == typeof(null)))
302     {
303         return "`null`";
304     }
305     else static if (is(V == U*, U))
306     {
307         // Format as ulong and prepend a 0x for pointers
308         import core.internal.string;
309         return cast(immutable) ("0x" ~ unsignedToTempString!16(cast(ulong) v));
310     }
311     // toString() isn't always const, e.g. classes inheriting from Object
312     else static if (__traits(compiles, { string s = V.init.toString(); }))
313     {
314         // Object references / struct pointers may be null
315         static if (is(V == class) || is(V == interface))
316         {
317             if (v is null)
318                 return "`null`";
319         }
320 
321         try
322         {
323             // Prefer const overload of toString
324             static if (__traits(compiles, { string s = v.toString(); }))
325                 return v.toString();
326             else
327                 return (cast() v).toString();
328         }
329         catch (Exception e)
330         {
331             return `<toString() failed: "` ~ e.msg ~ `", called on ` ~ formatMembers(v) ~`>`;
332         }
333     }
334     // Static arrays or slices (but not aggregates with `alias this`)
335     else static if (is(V : U[], U) && !isAggregateType!V)
336     {
337         import core.internal.traits: Unqual;
338         alias E = Unqual!U;
339 
340         // special-handling for void-arrays
341         static if (is(E == void))
342         {
343             if (__ctfe)
344                 return "<void[] not supported>";
345 
346             const bytes = cast(byte[]) v;
347             return miniFormat(bytes);
348         }
349         // anything string-like
350         else static if (is(E == char) || is(E == dchar) || is(E == wchar))
351         {
352             const s = `"` ~ v ~ `"`;
353 
354             // v could be a char[], dchar[] or wchar[]
355             static if (is(typeof(s) : const char[]))
356                 return cast(immutable) s;
357             else
358             {
359                 import core.internal.utf: toUTF8;
360                 return toUTF8(s);
361             }
362         }
363         else
364         {
365             string msg = "[";
366             foreach (i, ref el; v)
367             {
368                 if (i > 0)
369                     msg ~= ", ";
370 
371                 // don't fully print big arrays
372                 if (i >= 30)
373                 {
374                     msg ~= "...";
375                     break;
376                 }
377                 msg ~= miniFormat(el);
378             }
379             msg ~= "]";
380             return msg;
381         }
382     }
383     else static if (is(V : Val[K], K, Val))
384     {
385         size_t i;
386         string msg = "[";
387         foreach (ref k, ref val; v)
388         {
389             if (i > 0)
390                 msg ~= ", ";
391             // don't fully print big AAs
392             if (i++ >= 30)
393             {
394                 msg ~= "...";
395                 break;
396             }
397             msg ~= miniFormat(k) ~ ": " ~ miniFormat(val);
398         }
399         msg ~= "]";
400         return msg;
401     }
402     else static if (is(V == struct))
403     {
404         return formatMembers(v);
405     }
406     // Extern C++ classes don't have a toString by default
407     else static if (is(V == class) || is(V == interface))
408     {
409         if (v is null)
410             return "null";
411 
412         // Extern classes might be opaque
413         static if (is(typeof(v.tupleof)))
414             return formatMembers(v);
415         else
416             return '<' ~ V.stringof ~ '>';
417     }
418     else
419     {
420         return V.stringof;
421     }
422 }
423 
424 /// Formats `v`'s members as `V(<member 1>, <member 2>, ...)`
formatMembers(V)425 private string formatMembers(V)(const scope ref V v)
426 {
427     enum ctxPtr = __traits(isNested, V);
428     enum isOverlapped = calcFieldOverlap([ v.tupleof.offsetof ]);
429 
430     string msg = V.stringof ~ "(";
431     foreach (i, ref field; v.tupleof)
432     {
433         if (i > 0)
434             msg ~= ", ";
435 
436         static if (isOverlapped[i])
437         {
438             msg ~= "<overlapped field>";
439         }
440         else
441         {
442             // Mark context pointer
443             static if (ctxPtr && i == v.tupleof.length - 1)
444                 msg ~= "<context>: ";
445 
446             msg ~= miniFormat(field);
447         }
448     }
449     msg ~= ")";
450     return msg;
451 }
452 
453 /**
454  * Calculates whether fields are overlapped based on the passed offsets.
455  *
456  * Params:
457  *   offsets = offsets of all fields matching the order of `.tupleof`
458  *
459  * Returns: an array such that arr[n] = true indicates that the n'th field
460  *          overlaps with an adjacent field
461  **/
calcFieldOverlap(const scope size_t[]offsets)462 private bool[] calcFieldOverlap(const scope size_t[] offsets)
463 {
464     bool[] overlaps = new bool[](offsets.length);
465 
466     foreach (const idx; 1 .. overlaps.length)
467     {
468         if (offsets[idx - 1] == offsets[idx])
469             overlaps[idx - 1] = overlaps[idx] = true;
470     }
471 
472     return overlaps;
473 }
474 
475 // This should be a local import in miniFormat but fails with a cyclic dependency error
476 // core.thread.osthread -> core.time -> object -> core.internal.array.capacity
477 // -> core.atomic -> core.thread -> core.thread.osthread
478 import core.atomic : atomicLoad;
479 
480 /// Negates a comparison token, e.g. `==` is mapped to `!=`
invertCompToken(scope string comp)481 private string invertCompToken(scope string comp) pure nothrow @nogc @safe
482 {
483     switch (comp)
484     {
485         case "==":
486             return "!=";
487         case "!=":
488             return "==";
489         case "<":
490             return ">=";
491         case "<=":
492             return ">";
493         case ">":
494             return "<=";
495         case ">=":
496             return "<";
497         case "is":
498             return "!is";
499         case "!is":
500             return "is";
501         case "in":
502             return "!in";
503         case "!in":
504             return "in";
505         default:
506             assert(0, combine(["Invalid comparison operator '"], comp, ["'"]));
507     }
508 }
509 
510 /// Casts the function pointer to include `@safe`, `@nogc`, ...
assumeFakeAttributes(T)511 private auto assumeFakeAttributes(T)(T t) @trusted
512 {
513     import core.internal.traits : Parameters, ReturnType;
514     alias RT = ReturnType!T;
515     alias P = Parameters!T;
516     alias type = RT function(P) nothrow @nogc @safe pure;
517     return cast(type) t;
518 }
519 
520 /// Wrapper for `miniFormat` which assumes that the implementation is `@safe`, `@nogc`, ...
521 /// s.t. it does not violate the constraints of the the function containing the `assert`.
miniFormatFakeAttributes(T)522 private string miniFormatFakeAttributes(T)(const scope ref T t)
523 {
524     alias miniT = miniFormat!T;
525     return assumeFakeAttributes(&miniT)(t);
526 }
527 
528 /// Allocates an array of `t` bytes while pretending to be `@safe`, `@nogc`, ...
pureAlloc(size_t t)529 private auto pureAlloc(size_t t)
530 {
531     static auto alloc(size_t len)
532     {
533         return new ubyte[len];
534     }
535     return assumeFakeAttributes(&alloc)(t);
536 }
537 
538 /// Wrapper for GC.inFinalizer that fakes purity
inFinalizer()539 private bool inFinalizer()() pure nothrow @nogc @safe
540 {
541     // CTFE doesn't trigger InvalidMemoryErrors
542     import core.memory : GC;
543     return !__ctfe && assumeFakeAttributes(&GC.inFinalizer)();
544 }
545 
546 // https://issues.dlang.org/show_bug.cgi?id=21544
547 unittest
548 {
549     // Normal enum values
550     enum E { A, BCDE }
551     E e = E.A;
552     assert(miniFormat(e) == "A");
553     e = E.BCDE;
554     assert(miniFormat(e) == "BCDE");
555 
556     // Invalid enum value is printed as their implicit base type (int)
557     e = cast(E) 3;
558     assert(miniFormat(e) == "cast(E)  3");
559 
560     // Non-integral enums work as well
561     static struct S
562     {
563         int a;
564         string str;
565     }
566 
567     enum E2 : S { a2 = S(1, "Hello") }
568     E2 es = E2.a2;
569     assert(miniFormat(es) == `a2`);
570 
571     // Even invalid values
572     es = cast(E2) S(2, "World");
573     assert(miniFormat(es) == `cast(E2)  S(2, "World")`);
574 }
575 
576 // vectors
577 unittest
578 {
579     static if (is(__vector(float[4])))
580     {
581         __vector(float[4]) f = [-1.5f, 0.5f, 1.0f, 0.125f];
582         assert(miniFormat(f) == "[-1.5, 0.5, 1, 0.125]");
583     }
584 
585     static if (is(__vector(int[4])))
586     {
587         __vector(int[4]) i = [-1, 0, 1, 3];
588         assert(miniFormat(i) == "[-1, 0, 1, 3]");
589     }
590 }
591