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