1 /**
2 Common error signaling facilities for the $(LINK2 ./package.html, `checkedint`) package.
3 
4 Copyright: Copyright Thomas Stuart Bockman 2015
5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 Authors: Thomas Stuart Bockman
7 
8 $(BIG $(B `IntFlagPolicy.throws`)) $(BR)
9 When the `throws` policy is set, errors are signalled by simply throwing a new
10 $(LINK2 ./_flags.html#CheckedIntException, `CheckedIntException`). This is the recommended policy because:
11 $(UL
12     $(LI The API user is not required to explicitly handle errors.)
13     $(LI Printing a precise stack trace is very helpful for debugging.)
14 )
15 However, this approach is not suitable in all cases. In particular:
16 $(UL
17     $(LI Obviously, it will not work in `nothrow` code.)
18     $(LI As of $(B D 2.071), exceptions are still not safe to use in `@nogc` code.)
19     $(LI Exceptions are too slow for code that is expected to signal many integer math errors in $(I normal) operation.)
20 )
21 
22 $(BIG $(B `IntFlagPolicy.asserts`)) $(BR)
23 When the `asserts` policy is set, errors trigger an assertion failure. The result depends upon whether assertions were
24 enabled at compiler time:
25 $(UL
26     $(LI With `version(assert)` - enabled by default in debug and unittest builds - a `core.exception.AssertError` will
27         be thrown. Its `msg` property will be set to the description of an `IntFlag` that was raised.)
28     $(LI Otherwise (in release mode), `assert(0);` will be used to halt the program immediately. Unfortunately, no
29         message or stack trace can be provided in this case. Use one of the other two error signalling policies if you
30         need detailed information in release mode.)
31 )
32 The `asserts` policy is the only one that is compatible with `pure nothrow @nogc` code.
33 
34 $(BIG $(B `IntFlagPolicy.noex`)) $(BR)
35 An alternative error signalling method may be selected using the `noex` policy:
36 $(OL
37     $(LI Whenever an integer math error occurs, a bit flag is raised in
38         $(LINK2 ./_flags.html#IntFlags.local, `IntFlags.local`), which is a TLS variable.)
39     $(LI The integer math operations in `checkedint` only set bit flags; they never clear them. Thus, any flag that is
40         raised because of an error will remain set until handled by the API user.)
41     $(LI The API user periodically checks whether any flags have been raised like so: `if (IntFlags.local)`)
42     $(LI `IntFlags.local` may be inspected to determine the general type of the error - for example, "divide by zero".)
43     $(LI Once the API user has handled the error somehow, `IntFlags.clear()` can be used to unset all bit flags before
44         continuing the program.)
45 )
46 The $(LINK2 ./_flags.html#IntFlags.pushPop, `IntFlags.pushPop`) mixin can be used to prevent a function from handling
47 or clearing flags that were set by the caller.
48 
49 Care must be taken when using the `noex` policy to insert sufficient `if (IntFlags.local)` checks; otherwise
50 `checkedint` will not provide much protection from integer math related bugs.
51 **/
52 module checkedint.flags;
53 
54 import future.bitop, std.algorithm, std.array, std.format, std.range/+.primitives+/;
55 
56 ///
57 enum IntFlagPolicy
58 {
59     none = 0,
60     noex = 1,
61     asserts = 2,
62     throws = 3
63 }
64 /// In mixed-policy `checkedint` operations, the higher ranking policy should be used.
65 unittest
66 {
67     assert(!IntFlagPolicy.none); // none == 0
68     assert(IntFlagPolicy.noex > IntFlagPolicy.none);
69     assert(IntFlagPolicy.asserts > IntFlagPolicy.noex);
70     assert(IntFlagPolicy.throws > IntFlagPolicy.asserts);
71 }
72 
73 /// Get the `IntFlagPolicy` associated with some type `T`.
74 template intFlagPolicyOf(T)
75 {
76     static if (is(typeof(T.policy) : IntFlagPolicy))
77         enum IntFlagPolicy intFlagPolicyOf = T.policy;
78     else
79         enum intFlagPolicyOf = IntFlagPolicy.none;
80 }
81 ///
82 unittest
83 {
84     import checkedint : SmartInt, SafeInt;
85 
86     alias IFP = IntFlagPolicy;
87     assert(intFlagPolicyOf!(SmartInt!(long, IFP.throws)) == IFP.throws);
88     assert(intFlagPolicyOf!(SafeInt!(uint, IFP.asserts)) == IFP.asserts);
89     assert(intFlagPolicyOf!(SmartInt!(ushort, IFP.noex)) == IFP.noex);
90 
91     // basic types implicitly have the `none` policy
92     assert(intFlagPolicyOf!(wchar) == IFP.none);
93     assert(intFlagPolicyOf!(int) == IFP.none);
94     assert(intFlagPolicyOf!(double) == IFP.none);
95     assert(intFlagPolicyOf!(bool) == IFP.none);
96 }
97 /// `intFlagPolicyOf` works with custom types, too:
98 unittest
99 {
100     alias IFP = IntFlagPolicy;
101     struct Foo
102     {
103         enum policy = IFP.asserts;
104     }
105     assert(intFlagPolicyOf!Foo == IFP.asserts);
106 
107     struct Bar
108     {
109         // This will be ignored by intFlagPolicyOf, because it has the wrong type:
110         enum policy = 2;
111     }
112     assert(intFlagPolicyOf!Bar == IFP.none);
113 }
114 
115 /**
116 Function used to signal a failure and its proximate cause from integer math code. Depending on the value of the
117 `policy` parameter, `raise()` will either:
118 $(UL
119     $(LI Throw a $(LINK2 ./_flags.html#CheckedIntException, `CheckedIntException`),)
120     $(LI Trigger an assertion failure, or)
121     $(LI Set a bit in $(LINK2 ./_flags.html#IntFlags.local, `IntFlags.local`) that can be checked by the caller
122         later.)
123 )
124 **/
125 template raise(IntFlagPolicy policy)
126 {
127     static if (policy == IntFlagPolicy.throws)
128     {
129         void raise(IntFlags flags) pure @safe
130         {
131             version(DigitalMars) pragma(inline, false); // DMD usually won't inline the caller without this.
132             if (flags) throw new CheckedIntException(flags);
133         }
134         void raise(IntFlag flag) pure @safe
135         {
136             version(DigitalMars) pragma(inline, false); // DMD usually won't inline the caller without this.
137             if (flag) throw new CheckedIntException(flag);
138         }
139     }
140     else static if (policy == IntFlagPolicy.asserts)
141     {
142         void raise(IntFlags flags) pure @safe nothrow @nogc
143         {
144             version(assert)
145             {
146                 version(DigitalMars) pragma(inline, false); // DMD usually won't inline the caller without this.
147                 assert(!flags, flags.front.desc); // throw AssertError
148             }
149             else
150             {
151                 if (flags) assert(0); // halt
152             }
153         }
154         void raise(IntFlag flag) pure @safe nothrow @nogc
155         {
156             version(assert)
157             {
158                 version(DigitalMars) pragma(inline, false); // DMD usually won't inline the caller without this.
159                 assert(!flag, flag.desc); // throw AssertError
160             }
161             else
162             {
163                 if (flag) assert(0); // halt
164             }
165         }
166     }
167     else static if (policy == IntFlagPolicy.noex)
168     {
169         void raise(IntFlags flags) @safe nothrow @nogc
170         {
171           /+pragma(inline, true);+/
172             IntFlags.local |= flags;
173         }
174         void raise(IntFlag flag) @safe nothrow @nogc
175         {
176           /+pragma(inline, true);+/
177             IntFlags.local |= flag;
178         }
179     }
180     else static if (policy == IntFlagPolicy.none)
181     {
182         void raise(IntFlags flags) pure @safe nothrow @nogc { /+pragma(inline, true);+/ }
183         void raise(IntFlag flag) pure @safe nothrow @nogc { /+pragma(inline, true);+/ }
184     } else
185         static assert(false);
186 }
187 ///
188 unittest
189 {
190     import checkedint.throws : raise; // set IntFlagPolicy.throws
191 
192     bool caught = false;
193     try
194     {
195         raise(IntFlag.div0);
196     }
197     catch (CheckedIntException e)
198     {
199         caught = (e.intFlags == IntFlag.div0);
200     }
201     assert(caught);
202 }
203 ///
204 @trusted unittest
205 {
206     import checkedint.asserts : raise; // set IntFlagPolicy.asserts
207 
208     bool caught = false;
209     try
210     {
211         raise(IntFlag.div0);
212     }
213     catch (Error e)
214     {
215         caught = (e.msg == "divide by zero");
216     }
217     assert(caught);
218 }
219 ///
220 unittest
221 {
222     import checkedint.noex : raise; // set IntFlagPolicy.noex
223 
224     raise(IntFlag.div0);
225     raise(IntFlag.posOver);
226 
227     assert(IntFlags.local.clear() == (IntFlag.div0 | IntFlag.posOver));
228 }
229 ///
230 unittest
231 {
232     // Multiple signaling strategies may be usefully mixed within the same program:
233     alias IFP = IntFlagPolicy;
234 
235     static void fails() @safe nothrow
236     {
237         raise!(IFP.noex)(IntFlag.negOver);
238         raise!(IFP.noex)(IntFlag.imag);
239     }
240 
241     bool caught = false;
242     try
243     {
244         fails();
245         // Flags that were raised by `nothrow` code can easily be turned into an exception by the caller.
246         raise!(IFP.throws)(IntFlags.local.clear());
247     }
248     catch (CheckedIntException e)
249     {
250         caught = (e.intFlags == (IntFlag.negOver | IntFlag.imag));
251     }
252     assert(caught);
253 }
254 
255 /// Represents a single cause of failure for an integer math operation.
256 struct IntFlag
257 {
258 /+pragma(inline, true):+/
259 private:
260     uint index;
261     this(uint index) pure @safe nothrow @nogc
262     {
263         assert(index < strs.length);
264         this.index = index;
265     }
266 
267     // Ordered from lowest to highest priority, because pure @nogc assert can only show one.
268     private static immutable strs = [
269         "{NULL}",
270         "{overflow}",
271         "{positive overflow}",
272         "{negative overflow}",
273         "{imaginary component}",
274         "{undefined result}",
275         "{divide by zero}"
276     ];
277 public:
278     static if (__VERSION__ >= 2067)
279     {
280         version(GNU) { static assert(false); }
281 
282         /// Overflow occured.
283         enum IntFlag over    = 1;
284         /// Overflow occured because a value was too large.
285         enum IntFlag posOver = 2;
286         /// Overflow occured because a value was too negative.
287         enum IntFlag negOver = 3;
288         /// The result is imaginary, and as such not representable by an integral type.
289         enum IntFlag imag    = 4;
290         /// The result of the operation is undefined mathematically, by the API, or both.
291         enum IntFlag undef   = 5;
292         /// A division by zero was attempted.
293         enum IntFlag div0    = 6;
294     }
295     else
296     {
297         static @property pure @safe nothrow @nogc
298         {
299             auto over()
300             {
301                 return IntFlag(1);
302             }
303             auto posOver()
304             {
305                 return IntFlag(2);
306             }
307             auto negOver()
308             {
309                 return IntFlag(3);
310             }
311             auto imag()
312             {
313                 return IntFlag(4);
314             }
315             auto undef()
316             {
317                 return IntFlag(5);
318             }
319             auto div0()
320             {
321                 return IntFlag(6);
322             }
323         }
324     }
325 
326     /// `false` if this `IntFlag` is set to one of the error signals listed above. Otherwise `true`.
327     @property bool isNull() const pure @safe nothrow @nogc
328     {
329         return index == 0;
330     }
331     ///
332     unittest
333     {
334         assert( IntFlag.init.isNull);
335         assert(!IntFlag.div0.isNull);
336     }
337 
338     /// An `IntFlag` value is implicitly convertible to an `IntFlags` with only the one flag raised.
339     @property IntFlags mask() const pure @safe nothrow @nogc
340     {
341         return IntFlags(1u << index);
342     }
343     /// ditto
344     alias mask this;
345     ///
346     unittest
347     {
348         IntFlags flags = IntFlag.over;
349         assert(flags == IntFlag.over);
350     }
351 
352     /// Get a description of this error flag.
353     @property string desc() const pure @safe nothrow @nogc
354     {
355         return strs[index][1 .. ($ - 1)];
356     }
357     ///
358     unittest
359     {
360         assert(IntFlag.over.desc == "overflow");
361         assert(IntFlag.init.desc == "NULL");
362     }
363 
364     /// Get a string representation of this `IntFlag`. The format is the same as that returned by `IntFlags.toString()`.
365     string toString() const pure @safe nothrow @nogc
366     {
367         return strs[index];
368     }
369     /// ditto
370     void toString(Writer, Char = char)(Writer sink, FormatSpec!Char fmt = (FormatSpec!Char).init) const
371     {
372         formatValue(sink, strs[index], fmt);
373     }
374     ///
375     unittest
376     {
377         assert(IntFlag.over.toString() == "{overflow}");
378         assert(IntFlag.over.toString() == IntFlag.over.mask.toString());
379     }
380 }
381 
382 /**
383 A bitset that can be used to track integer math failures.
384 
385 `IntFlags` is also a forward range which can be used to iterate over the set (raised)
386 $(LINK2 ./_flags.html#IntFlag, `IntFlag`) values. Fully consuming the range is equivalent to calling `clear()`;
387 iterate over a copy made with `save()`, instead, if this is not your intention.
388 **/
389 struct IntFlags
390 {
391 /+pragma(inline, true):+/
392 private:
393     uint _bits = 0;
394     @property uint bits() const pure @safe nothrow @nogc
395     {
396         return _bits;
397     }
398     @property void bits(uint bits) pure @safe nothrow @nogc
399     {
400         // filter out {NULL}
401         _bits = bits & ~1;
402     }
403 
404     this(uint bits) pure @safe nothrow @nogc
405     {
406         this.bits = bits;
407     }
408 
409 public:
410     /**
411     Assign the set of flags represented by `that` to this `IntFlags`. Note that $(LINK2 ./_flags.html#IntFlag, `IntFlag`)
412     values are accepted also, because `IntFlag` is implicitly convertible to `IntFlags`.
413     **/
414     this(IntFlags that) pure @safe nothrow @nogc
415     {
416         bits = that.bits;
417     }
418     ref IntFlags opAssign(IntFlags that) /+return+/ pure @safe nothrow @nogc
419     {
420         bits = that.bits;
421         return this;
422     }
423     ///
424     unittest
425     {
426         IntFlags flags = IntFlag.div0;
427         assert(flags == IntFlag.div0);
428         flags = IntFlag.negOver | IntFlag.imag;
429         assert(flags == (IntFlag.negOver | IntFlag.imag));
430 
431         IntFlags.local = flags;
432         assert(IntFlags.local.clear() == (IntFlag.negOver | IntFlag.imag));
433     }
434 
435     /**
436     Clear all flags, and return the set of flags that were previously set.
437 
438     `raise!(IntFlagPolicy.throws)(IntFlags.local.clear())` is a convenient way that the caller of a `nothrow`
439     function can convert any flags that were raised into an exception.
440     **/
441     IntFlags clear() pure @safe nothrow @nogc
442     {
443         IntFlags ret = this;
444         _bits = 0;
445         return ret;
446     }
447     ///
448     unittest
449     {
450         IntFlags.local = IntFlag.posOver | IntFlag.negOver;
451         assert(IntFlags.local.clear() == (IntFlag.posOver | IntFlag.negOver));
452         assert(!IntFlags.local);
453     }
454 
455     /// Test (`&`), set (`|`), or unset (`-`) individual flags.
456     IntFlags opBinary(string op)(IntFlags that) const @safe pure nothrow @nogc
457         if (op.among!("&", "|", "-"))
458     {
459         IntFlags ret = this;
460         return ret.opOpAssign!op(that);
461     }
462     /// ditto
463     ref IntFlags opOpAssign(string op)(IntFlags that) /+return+/ pure @safe nothrow @nogc
464         if (op.among!("&", "|", "-"))
465     {
466         static if (op == "&")
467             bits = this.bits & that.bits;
468         else static if (op == "|")
469             bits = this.bits | that.bits;
470         else static if (op == "-")
471             bits = this.bits & ~(that.bits);
472 
473         return this;
474     }
475     ///
476     unittest
477     {
478         IntFlags flags = IntFlag.undef | IntFlag.posOver | IntFlag.negOver;
479 
480         flags &= IntFlag.posOver | IntFlag.negOver;
481         assert(!(flags & IntFlag.undef));
482 
483         flags -= IntFlag.undef | IntFlag.negOver;
484         assert(  flags & IntFlag.posOver);
485         assert(!(flags & IntFlag.negOver));
486     }
487 
488     /**
489     `true` if any non-null flag is set, otherwise `false`.
490 
491     An `IntFlags` value is implicitly convertible to `bool` through `anySet`.
492     **/
493     @property bool anySet() const pure @safe nothrow @nogc
494     {
495         return bits != 0;
496     }
497     /// ditto
498     alias anySet this;
499     ///
500     unittest
501     {
502         IntFlags flags;
503         assert(!flags);
504         flags = IntFlag.imag | IntFlag.undef;
505         assert( flags);
506     }
507 
508     /// `true` if no non-null flags are set.
509     @property bool empty() const pure @safe nothrow @nogc
510     {
511         return bits == 0;
512     }
513     /// Get the first set `IntFlag`.
514     @property IntFlag front() const pure @safe nothrow @nogc
515     {
516         // bsr() is undefined for 0.
517         return IntFlag(bsr(bits | 1));
518     }
519     /// Clear the first set `IntFlag`. This is equivalent to `flags -= flags.front`.
520     ref IntFlags popFront() /+return+/ pure @safe nothrow @nogc
521     {
522         // bsr() is undefined for 0.
523         bits = bits & ~(1u << bsr(bits | 1));
524         return this;
525     }
526     /// Get a mutable copy of this `IntFlags` value, so as not to `clear()` the original by iterating through it.
527     @property IntFlags save() const pure @safe nothrow @nogc
528     {
529         return this;
530     }
531     /// Get the number of raised flags.
532     @property uint length() const pure @safe nothrow @nogc
533     {
534         return popcnt(bits);
535     }
536 
537     unittest
538     {
539         import std.range : isForwardRange, hasLength;
540         static assert(isForwardRange!IntFlags);
541         static assert(hasLength!IntFlags);
542     }
543 
544     /// The standard `IntFlags` set for the current thread. `raise!(IntFlagPolicy.noex)()` mutates this variable.
545     static IntFlags local;
546 
547     /**
548     A `mixin` string that may be used to (effectively) push a new `IntFlags.local` variable onto the stack at the
549     beginning of a scope, and restore the previous one at the end.
550 
551     Any flags raised during the scope must be manually checked, handled, and cleared before the end, otherwise a
552     debugging `assert` will be triggered to warn you that restoring the old `IntFlags.local` value would cause a
553     loss of information.
554     **/
555     enum string pushPop = r"
556 IntFlags outerIntFlags = IntFlags.local.clear();
557 scope(exit)
558 {
559     assert(IntFlags.local.empty);
560     IntFlags.local = outerIntFlags;
561 }";
562     ///
563     unittest
564     {
565         import checkedint.noex : raise; // set IntFlagPolicy.noex
566 
567         string[] log;
568 
569         void onlyZero(int x)
570         {
571             mixin(IntFlags.pushPop);
572 
573             if (x < 0)
574                 raise(IntFlag.negOver);
575             if (x > 0)
576                 raise(IntFlag.posOver);
577 
578             if (IntFlags.local)
579                 log ~= IntFlags.local.clear().toString();
580         }
581 
582         IntFlags.local = IntFlag.imag;
583         onlyZero(-50);
584         onlyZero(22);
585         onlyZero(0);
586         assert(IntFlags.local.clear() == IntFlag.imag);
587 
588         assert(log == ["{negative overflow}", "{positive overflow}"]);
589     }
590 
591 /+pragma(inline):+/
592     /// Get a string representation of the list of set flags.
593     string toString() const pure @safe
594     {
595         switch (length)
596         {
597         case 0:
598             return "{}";
599         case 1:
600             return front.toString();
601         default:
602             auto buff = appender!string();
603             toString(buff);
604             return cast(immutable)(buff.data);
605         }
606     }
607     /// ditto
608     void toString(Writer, Char = char)(Writer sink, FormatSpec!Char fmt = (FormatSpec!Char).init) const
609     {
610         put(sink, '{');
611 
612         bool first = true;
613         foreach (fd; this.save())
614         {
615             if (first)
616                 first = false;
617             else
618                 put(sink, ", ");
619             put(sink, fd.desc);
620         }
621 
622         put(sink, '}');
623     }
624     ///
625     unittest
626     {
627         IntFlags flags;
628         assert(flags.toString() == "{}");
629 
630         flags = IntFlag.undef;
631         assert((flags.toString() == "{undefined result}") && (flags.toString() == IntFlag.undef.toString()));
632 
633         flags |= IntFlag.imag;
634         assert(flags.toString() == "{undefined result, imaginary component}", flags.toString());
635     }
636 
637     /// An IntFlags value with all possible flags raised.
638     enum IntFlags all = IntFlags((~0u >>> (8*_bits.sizeof - IntFlag.strs.length)) ^ 0x1);
639     ///
640     unittest
641     {
642         assert(IntFlags.all.length == 6);
643     }
644 }
645 
646 /**
647 An `Exception` representing the cause of an integer math failure.
648 
649 A new instances may be created and thrown using `raise!(IntFlagPolicy.throws)()`.
650 **/
651 class CheckedIntException : Exception
652 {
653     /// An `IntFlags` bitset indicating the proximate cause(s) of the exception.
654     const IntFlags intFlags;
655 
656 private:
657     enum msg0 = "Integer math exception(s): ";
658     private this(IntFlag flag, string fn = __FILE__, size_t ln = __LINE__) pure @safe nothrow
659     {
660         intFlags = flag;
661         super(msg0 ~ flag.toString(), fn, ln);
662     }
663     private this(IntFlags flags, string fn = __FILE__, size_t ln = __LINE__) pure @safe nothrow
664     {
665         intFlags = flags;
666 
667         auto buff = appender(msg0);
668         flags.toString(buff);
669 
670         super(cast(immutable)(buff.data), fn, ln);
671     }
672 }