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 }