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 }