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 }