1 /**
2 Common error signaling facilities for the $(LINK2 ./package.html, `checkedint`) package.
3 
4 $(BIG $(B `IntFlagPolicy.throws`)) $(BR)
5 When the `throws` policy is set, errors are signalled by simply throwing a new
6 $(LINK2 ./_flags.html#CheckedIntException, `CheckedIntException`). This is the recommended policy because:
7 $(UL
8     $(LI The API user is not required to explicitly handle errors.)
9     $(LI Printing a precise stack trace is very helpful for debugging.)
10 )
11 However, this approach is not suitable in all cases. In particular:
12 $(UL
13     $(LI Obviously, it will not work in `nothrow` code.)
14     $(LI As of $(B D 2.071), exceptions are still not safe to use in `@nogc` code.)
15     $(LI Exceptions are too slow for code that is expected to signal many integer math errors in $(I normal) operation.)
16 )
17 
18 $(BIG $(B `IntFlagPolicy.asserts`)) $(BR)
19 When the `asserts` policy is set, errors trigger an assertion failure. The result depends upon whether assertions were
20 enabled at compiler time:
21 $(UL
22     $(LI With `version(assert)` - enabled by default in debug and unittest builds - a `core.exception.AssertError` will
23         be thrown. Its `msg` property will be set to the description of an `IntFlag` that was raised.)
24     $(LI Otherwise (in release mode), `assert(0);` will be used to halt the program immediately. Unfortunately, no
25         message or stack trace can be provided in this case. Use one of the other two error signalling policies if
26         detailed information is needed in release mode.)
27 )
28 The `asserts` policy is the only one that is compatible with `pure nothrow @nogc` code.
29 
30 $(BIG $(B `IntFlagPolicy.sticky`)) $(BR)
31 An alternative error signalling method may be selected using the `sticky` policy:
32 $(OL
33     $(LI Whenever an integer math error occurs, a sticky bit flag is raised in
34         $(LINK2 ./_flags.html#IntFlags.local, `IntFlags.local`), which is a TLS variable.)
35     $(LI The integer math operations in `checkedint` only set bit flags; they never clear them. Thus, any flag that is
36         raised because of an error will remain set until handled by the API user.)
37     $(LI The API user periodically checks whether any flags have been raised like so: `if (IntFlags.local)`)
38     $(LI `IntFlags.local` may be inspected to determine the general type of the error - for example, "divide by zero".)
39     $(LI Once the API user has handled the error somehow, `IntFlags.clear()` can be used to unset all bit flags before
40         continuing the program.)
41 )
42 The $(LINK2 ./_flags.html#IntFlags.pushPop, `IntFlags.pushPop`) mixin can be used to prevent a function from handling
43 or clearing flags that were set by the caller.
44 
45 Care must be taken when using the `sticky` policy to insert sufficient `if (IntFlags.local)` checks; otherwise
46 `checkedint` will not provide much protection from integer math related bugs.
47 
48 Copyright: Copyright Thomas Stuart Bockman 2015
49 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
50 Authors: Thomas Stuart Bockman
51 **/
52 module checkedint.flags;
53 
54 import future.bitop, std.algorithm, std.array, std.format, std.range/+.primitives+/;
55 
56 /// The module description, $(LINK2 ./_flags.html, above), explains the different policies.
57 enum IntFlagPolicy
58 {
59     none = 0,
60     sticky = 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.sticky > IntFlagPolicy.none);
69     assert(IntFlagPolicy.asserts > IntFlagPolicy.sticky);
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.sticky)) == IFP.sticky);
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.sticky)
168     {
169     pragma(inline, true):
170         void raise(IntFlags flags) @safe nothrow @nogc
171         {
172             IntFlags.local |= flags;
173         }
174         void raise(IntFlag flag) @safe nothrow @nogc
175         {
176             IntFlags.local |= flag;
177         }
178     }
179     else static if (policy == IntFlagPolicy.none)
180     {
181     pragma(inline, true):
182         void raise(IntFlags flags) pure @safe nothrow @nogc { }
183         void raise(IntFlag flag) pure @safe nothrow @nogc { }
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.sticky : raise; // set IntFlagPolicy.sticky
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.sticky)(IntFlag.negOver);
238         raise!(IFP.sticky)(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     /// Overflow occured.
279     enum IntFlag over    = 1;
280     /// Overflow occured because a value was too large.
281     enum IntFlag posOver = 2;
282     /// Overflow occured because a value was too negative.
283     enum IntFlag negOver = 3;
284     /// The result is imaginary, and as such not representable by an integral type.
285     enum IntFlag imag    = 4;
286     /// The result of the operation is undefined mathematically, by the API, or both.
287     enum IntFlag undef   = 5;
288     /// A division by zero was attempted.
289     enum IntFlag div0    = 6;
290 
291     /// `false` if this `IntFlag` is set to one of the error signals listed above. Otherwise `true`.
292     @property bool isNull() const pure @safe nothrow @nogc
293     {
294         return index == 0;
295     }
296     ///
297     unittest
298     {
299         assert( IntFlag.init.isNull);
300         assert(!IntFlag.div0.isNull);
301     }
302 
303     /// An `IntFlag` value is implicitly convertible to an `IntFlags` with only the one flag raised.
304     @property IntFlags mask() const pure @safe nothrow @nogc
305     {
306         return IntFlags(1u << index);
307     }
308     /// ditto
309     alias mask this;
310     ///
311     unittest
312     {
313         IntFlags flags = IntFlag.over;
314         assert(flags == IntFlag.over);
315     }
316 
317     /// Get a description of this error flag.
318     @property string desc() const pure @safe nothrow @nogc
319     {
320         return strs[index][1 .. ($ - 1)];
321     }
322     ///
323     unittest
324     {
325         assert(IntFlag.over.desc == "overflow");
326         assert(IntFlag.init.desc == "NULL");
327     }
328 
329     /// Get a string representation of this `IntFlag`. The format is the same as that returned by `IntFlags.toString()`.
330     string toString() const pure @safe nothrow @nogc
331     {
332         return strs[index];
333     }
334     ///
335     unittest
336     {
337         assert(IntFlag.over.toString() == "{overflow}");
338         assert(IntFlag.over.toString() == IntFlag.over.mask.toString());
339     }
340     /**
341     Puts a `string` representation of this `IntFlag` into `w`. This overload will not allocate, unless
342     `std.range.primitives.put(w, ...)` allocates.
343 
344     Params:
345         w = An output range that will receive the `string`
346         fmt = An optional format specifier
347     */
348     void toString(Writer, Char = char)(Writer w, FormatSpec!Char fmt = (FormatSpec!Char).init) const
349     {
350         formatValue(w, strs[index], fmt);
351     }
352 }
353 
354 /**
355 A bitset that can be used to track integer math failures.
356 
357 `IntFlags` is also a forward range which can be used to iterate over the set (raised)
358 $(LINK2 ./_flags.html#IntFlag, `IntFlag`) values. Fully consuming the range is equivalent to calling `clear()`;
359 iterate over a copy made with `save()`, instead, if this clearing is undesired.
360 **/
361 struct IntFlags
362 {
363 /+pragma(inline, true):+/
364 private:
365     uint _bits = 0;
366     @property uint bits() const pure @safe nothrow @nogc
367     {
368         return _bits;
369     }
370     @property void bits(uint bits) pure @safe nothrow @nogc
371     {
372         // filter out {NULL}
373         _bits = bits & ~1;
374     }
375 
376     this(uint bits) pure @safe nothrow @nogc
377     {
378         this.bits = bits;
379     }
380 
381 public:
382     /**
383     Assign the set of flags represented by `that` to this `IntFlags`. Note that $(LINK2 ./_flags.html#IntFlag, `IntFlag`)
384     values are accepted also, because `IntFlag` is implicitly convertible to `IntFlags`.
385     **/
386     this(IntFlags that) pure @safe nothrow @nogc
387     {
388         bits = that.bits;
389     }
390     ref IntFlags opAssign(IntFlags that) return pure @safe nothrow @nogc
391     {
392         bits = that.bits;
393         return this;
394     }
395     ///
396     unittest
397     {
398         IntFlags flags = IntFlag.div0;
399         assert(flags == IntFlag.div0);
400         flags = IntFlag.negOver | IntFlag.imag;
401         assert(flags == (IntFlag.negOver | IntFlag.imag));
402 
403         IntFlags.local = flags;
404         assert(IntFlags.local.clear() == (IntFlag.negOver | IntFlag.imag));
405     }
406 
407     /**
408     Clear all flags, and return the set of flags that were previously set.
409 
410     `raise!(IntFlagPolicy.throws)(IntFlags.local.clear())` is a convenient way that the caller of a `nothrow`
411     function can convert any flags that were raised into an exception.
412     **/
413     IntFlags clear() pure @safe nothrow @nogc
414     {
415         IntFlags ret = this;
416         _bits = 0;
417         return ret;
418     }
419     ///
420     unittest
421     {
422         IntFlags.local = IntFlag.posOver | IntFlag.negOver;
423         assert(IntFlags.local.clear() == (IntFlag.posOver | IntFlag.negOver));
424         assert(!IntFlags.local);
425     }
426 
427     /// Test (`&`), set (`|`), or unset (`-`) individual flags.
428     IntFlags opBinary(string op)(IntFlags that) const @safe pure nothrow @nogc
429         if (op.among!("&", "|", "-"))
430     {
431         IntFlags ret = this;
432         return ret.opOpAssign!op(that);
433     }
434     /// ditto
435     ref IntFlags opOpAssign(string op)(IntFlags that) return pure @safe nothrow @nogc
436         if (op.among!("&", "|", "-"))
437     {
438         static if (op == "&")
439             bits = this.bits & that.bits;
440         else static if (op == "|")
441             bits = this.bits | that.bits;
442         else static if (op == "-")
443             bits = this.bits & ~(that.bits);
444 
445         return this;
446     }
447     ///
448     unittest
449     {
450         IntFlags flags = IntFlag.undef | IntFlag.posOver | IntFlag.negOver;
451 
452         flags &= IntFlag.posOver | IntFlag.negOver;
453         assert(!(flags & IntFlag.undef));
454 
455         flags -= IntFlag.undef | IntFlag.negOver;
456         assert(  flags & IntFlag.posOver);
457         assert(!(flags & IntFlag.negOver));
458     }
459 
460     /**
461     `true` if any non-null flag is set, otherwise `false`.
462 
463     An `IntFlags` value is implicitly convertible to `bool` through `anySet`.
464     **/
465     @property bool anySet() const pure @safe nothrow @nogc
466     {
467         return bits != 0;
468     }
469     /// ditto
470     alias anySet this;
471     ///
472     unittest
473     {
474         IntFlags flags;
475         assert(!flags);
476         flags = IntFlag.imag | IntFlag.undef;
477         assert( flags);
478     }
479 
480     /// `true` if no non-null flags are set.
481     @property bool empty() const pure @safe nothrow @nogc
482     {
483         return bits == 0;
484     }
485     /// Get the first set `IntFlag`.
486     @property IntFlag front() const pure @safe nothrow @nogc
487     {
488         // bsr() is undefined for 0.
489         return IntFlag(bsr(bits | 1));
490     }
491     /// Clear the first set `IntFlag`. This is equivalent to `flags -= flags.front`.
492     ref IntFlags popFront() return pure @safe nothrow @nogc
493     {
494         // bsr() is undefined for 0.
495         bits = bits & ~(1u << bsr(bits | 1));
496         return this;
497     }
498     /// Get a mutable copy of this `IntFlags` value, so as not to `clear()` the original by iterating through it.
499     @property IntFlags save() const pure @safe nothrow @nogc
500     {
501         return this;
502     }
503     /// Get the number of raised flags.
504     @property uint length() const pure @safe nothrow @nogc
505     {
506         return popcnt(bits);
507     }
508 
509     unittest
510     {
511         import std.range : isForwardRange, hasLength;
512         static assert(isForwardRange!IntFlags);
513         static assert(hasLength!IntFlags);
514     }
515 
516     /// The standard `IntFlags` set for the current thread. `raise!(IntFlagPolicy.sticky)()` mutates this variable.
517     static IntFlags local;
518 
519     /**
520     A `mixin` string that may be used to (effectively) push a new `IntFlags.local` variable onto the stack at the
521     beginning of a scope, and restore the previous one at the end.
522 
523     Any flags raised during the scope must be manually checked, handled, and cleared before the end, otherwise a
524     debugging `assert` will be triggered to warn that restoring the old `IntFlags.local` value would cause a
525     loss of information.
526     **/
527     enum string pushPop = r"
528 IntFlags outerIntFlags = IntFlags.local.clear();
529 scope(exit)
530 {
531     assert(IntFlags.local.empty);
532     IntFlags.local = outerIntFlags;
533 }";
534     ///
535     unittest
536     {
537         import checkedint.sticky : raise; // set IntFlagPolicy.sticky
538 
539         string[] log;
540 
541         void onlyZero(int x)
542         {
543             mixin(IntFlags.pushPop);
544 
545             if (x < 0)
546                 raise(IntFlag.negOver);
547             if (x > 0)
548                 raise(IntFlag.posOver);
549 
550             if (IntFlags.local)
551                 log ~= IntFlags.local.clear().toString();
552         }
553 
554         IntFlags.local = IntFlag.imag;
555         onlyZero(-50);
556         onlyZero(22);
557         onlyZero(0);
558         assert(IntFlags.local.clear() == IntFlag.imag);
559 
560         assert(log == ["{negative overflow}", "{positive overflow}"]);
561     }
562 
563 pragma(inline):
564     /// Get a string representation of the list of set flags.
565     string toString() const pure @safe
566     {
567         switch (length)
568         {
569         case 0:
570             return "{}";
571         case 1:
572             return front.toString();
573         default:
574             auto buff = appender!string();
575             toString(buff);
576             return cast(immutable)(buff.data);
577         }
578     }
579     ///
580     unittest
581     {
582         IntFlags flags;
583         assert(flags.toString() == "{}");
584 
585         flags = IntFlag.undef;
586         assert((flags.toString() == "{undefined result}") && (flags.toString() == IntFlag.undef.toString()));
587 
588         flags |= IntFlag.imag;
589         assert(flags.toString() == "{undefined result, imaginary component}", flags.toString());
590     }
591     /**
592     Puts a `string` representation of the list of set flags into `w`. This overload will not allocate, unless
593     `std.range.primitives.put(w, ...)` allocates.
594 
595     Params:
596         w = An output range that will receive the `string`
597         fmt = An optional format specifier
598     */
599     void toString(Writer, Char = char)(Writer w, FormatSpec!Char fmt = (FormatSpec!Char).init) const
600     {
601         put(w, '{');
602 
603         bool first = true;
604         foreach (fd; this.save())
605         {
606             if (first)
607                 first = false;
608             else
609                 put(w, ", ");
610             put(w, fd.desc);
611         }
612 
613         put(w, '}');
614     }
615 
616     /// An IntFlags value with all possible flags raised.
617     enum IntFlags all = IntFlags((~0u >>> (8*_bits.sizeof - IntFlag.strs.length)) ^ 0x1);
618     ///
619     unittest
620     {
621         assert(IntFlags.all.length == 6);
622     }
623 }
624 
625 /**
626 An `Exception` representing the cause of an integer math failure.
627 
628 A new instances may be created and thrown using `raise!(IntFlagPolicy.throws)()`.
629 **/
630 class CheckedIntException : Exception
631 {
632     /// An `IntFlags` bitset indicating the proximate cause(s) of the exception.
633     const IntFlags intFlags;
634 
635 private:
636     enum msg0 = "Integer math exception(s): ";
637     private this(IntFlag flag, string fn = __FILE__, size_t ln = __LINE__) pure @safe nothrow
638     {
639         intFlags = flag;
640         super(msg0 ~ flag.toString(), fn, ln);
641     }
642     private this(IntFlags flags, string fn = __FILE__, size_t ln = __LINE__) pure @safe nothrow
643     {
644         intFlags = flags;
645 
646         auto buff = appender(msg0);
647         flags.toString(buff);
648 
649         super(cast(immutable)(buff.data), fn, ln);
650     }
651 }