Wolfgang Thallergcc 15.1.0ada185d1 year agoHistory
3595 lines · 107.1 KB · D
RawDownload
// Written in the D programming language./**$(SCRIPT inhibitQuickIndex = 1;) This module defines facilities for efficient checking of integral operationsagainst overflow, casting with loss of precision, unexpected change of sign,etc. The checking (and possibly correction) can be done at operation level, forexample $(LREF opChecked)$(D !"+"(x, y, overflow)) adds two integrals `x` and`y` and sets `overflow` to `true` if an overflow occurred. The flag `overflow`(a `bool` passed by reference) is not touched if the operation succeeded, so thesame flag can be reused for a sequence of operations and tested at the end. Issuing individual checked operations is flexible and efficient but oftentedious. The $(LREF Checked) facility offers encapsulated integral wrappers thatdo all checking internally and have configurable behavior upon erroneousresults. For example, `Checked!int` is a type that behaves like `int` but abortsexecution immediately whenever involved in an operation that produces thearithmetically wrong result. The accompanying convenience function $(LREFchecked) uses type deduction to convert a value `x` of integral type `T` to`Checked!T` by means of `checked(x)`. For example: ---void main(){    import std.checkedint, std.stdio;    writeln((checked(5) + 7).get); // 12    writeln((checked(10) * 1000 * 1000 * 1000).get); // Overflow}--- Similarly, $(D checked(-1) > uint(0)) aborts execution (even though the built-incomparison $(D int(-1) > uint(0)) is surprisingly true due to language'sconversion rules modeled after C). Thus, `Checked!int` is a virtually drop-inreplacement for `int` useable in debug builds, to be replaced by `int` inrelease mode if efficiency demands it. `Checked`  has customizable behavior with the help of a second type parameter,`Hook`. Depending on what methods `Hook` defines, core operations on theunderlying integral may be verified for overflow or completely redefined. If`Hook` defines no method at all and carries no state, there is no change inbehavior, i.e. $(D Checked!(int, void)) is a wrapper around `int` that adds nocustomization at all. This module provides a few predefined hooks (below) that add useful behavior to`Checked`: $(BOOKTABLE ,    $(TR $(TD $(LREF Abort)) $(TD        fails every incorrect operation with a message to $(REF        stderr, std, stdio) followed by a call to `assert(0)`. It is the default        second parameter, i.e. `Checked!short` is the same as        $(D Checked!(short, Abort)).    ))    $(TR $(TD $(LREF Throw)) $(TD        fails every incorrect operation by throwing an exception.    ))    $(TR $(TD $(LREF Warn)) $(TD        prints incorrect operations to $(REF stderr, std, stdio)        but otherwise preserves the built-in behavior.    ))    $(TR $(TD $(LREF ProperCompare)) $(TD        fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=`        to return correct results in all circumstances,        at a slight cost in efficiency. For example,        $(D Checked!(uint, ProperCompare)(1) > -1) is `true`,        which is not the case for the built-in comparison. Also, comparing        numbers for equality with floating-point numbers only passes if the        integral can be converted to the floating-point number precisely,        so as to preserve transitivity of equality.    ))    $(TR $(TD $(LREF WithNaN)) $(TD        reserves a special "Not a Number" (NaN) value akin to the homonym value        reserved for floating-point values. Once a $(D Checked!(X, WithNaN))        gets this special value, it preserves and propagates it until        reassigned. $(LREF isNaN) can be used to query whether the object        is not a number.    ))    $(TR $(TD $(LREF Saturate)) $(TD        implements saturating arithmetic, i.e. $(D Checked!(int, Saturate))        "stops" at `int.max` for all operations that would cause an `int` to        overflow toward infinity, and at `int.min` for all operations that would        correspondingly overflow toward negative infinity.    )))  These policies may be used alone, e.g. $(D Checked!(uint, WithNaN)) defines a`uint`-like type that reaches a stable NaN state for all erroneous operations.They may also be "stacked" on top of each other, owing to the property that achecked integral emulates an actual integral, which means another checkedintegral can be built on top of it. Some combinations of interest include: $(BOOKTABLE ,    $(TR $(TD $(D Checked!(Checked!int, ProperCompare))))    $(TR $(TDdefines an `int` with fixedcomparison operators that will fail with `assert(0)` upon overflow. (Recall that`Abort` is the default policy.) The order in which policies are combined isimportant because the outermost policy (`ProperCompare` in this case) has thefirst crack at intercepting an operator. The converse combination $(DChecked!(Checked!(int, ProperCompare))) is meaningless because `Abort` willintercept comparison and will fail without giving `ProperCompare` a chance tointervene.    ))    $(TR $(TD))    $(TR $(TDNW $(D Checked!(Checked!(int, ProperCompare), WithNaN))))    $(TR $(TDdefines an `int`-liketype that supports a NaN value. For values that are not NaN, comparison worksproperly. Again the composition order is important; $(D Checked!(Checked!(int,WithNaN), ProperCompare)) does not have good semantics because `ProperCompare`intercepts comparisons before the numbers involved are tested for NaN.    ))) The hook's members are looked up statically in a Design by Introspection mannerand are all optional. The table below illustrates the members that a hook typemay define and their influence over the behavior of the `Checked` type using it.In the table, `hook` is an alias for `Hook` if the type `Hook` does notintroduce any state, or an object of type `Hook` otherwise. $(TABLE ,$(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook))))$(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as thedefault initializer of the payload.))$(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value ofthe payload.))$(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value ofthe payload.))$(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwardedto unconditionally when the payload is to be cast to type `U`.))$(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I not) defined,`onBadCast!U(get)` is forwarded to when the payload is to be cast to type `U`and the cast would lose information or force a change of sign.))$(TR $(TD `hookOpEquals`) $(TD If defined, $(D hook.hookOpEquals(get, rhs)) isforwarded to unconditionally when the payload is compared for equality againstvalue `rhs` of integral, floating point, or Boolean type.))$(TR $(TD `hookOpCmp`) $(TD If defined, $(D hook.hookOpCmp(get, rhs)) isforwarded to unconditionally when the payload is compared for ordering againstvalue `rhs` of integral, floating point, or Boolean type.))$(TR $(TD `hookOpUnary`) $(TD If defined, `hook.hookOpUnary!op(get)` (where `op`is the operator symbol) is forwarded to for unary operators `-` and `~`. Inaddition, for unary operators `++` and `--`, `hook.hookOpUnary!op(payload)` iscalled, where `payload` is a reference to the value wrapped by `Checked` so thehook can change it.))$(TR $(TD `hookOpBinary`) $(TD If defined, $(D hook.hookOpBinary!op(get, rhs))(where `op` is the operator symbol and `rhs` is the right-hand side operand) isforwarded to unconditionally for binary operators `+`,  `-`, `*`, `/`, `%`,`^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.))$(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(Dhook.hookOpBinaryRight!op(lhs, get)) (where `op` is the operator symbol and`lhs` is the left-hand side operand) is forwarded to unconditionally for binaryoperators `+`,  `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.))$(TR $(TD `onOverflow`) $(TD If defined, `hook.onOverflow!op(get)` is forwardedto for unary operators that overflow but only if `hookOpUnary` is not defined.Unary `~` does not overflow; unary `-` overflows only when the most negativevalue of a signed type is negated, and the result of the hook call is returned.When the increment or decrement operators overflow, the payload is assigned theresult of `hook.onOverflow!op(get)`. When a binary operator overflows, theresult of $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` doesnot define `hookOpBinary`.))$(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D hook.hookOpOpAssign!op(payload,rhs)) (where `op` is the operator symbol and `rhs` is the right-hand sideoperand) is forwarded to unconditionally for binary operators `+=`,  `-=`, `*=`, `/=`, `%=`,`^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.))$(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound))(where `value` is the value being assigned) is forwarded to when the result ofbinary operators `+=`,  `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,and `>>>=` is smaller than the smallest value representable by `T`.))$(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound))(where `value` is the value being assigned) is forwarded to when the result ofbinary operators `+=`,  `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,and `>>>=` is larger than the largest value representable by `T`.))$(TR $(TD `hookToHash`) $(TD If defined, $(D hook.hookToHash(payload))(where `payload` is a reference to the value wrapped by Checked) is forwardedto when `toHash` is called on a Checked type. Custom hashing can be implementedin a `Hook`, otherwise the built-in hashing is used.))) Source: $(PHOBOSSRC std/checkedint.d)*/module std.checkedint;import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; ///@safe unittest{    int[] concatAndAdd(int[] a, int[] b, int offset)    {        // Aborts on overflow on size computation        auto r = new int[(checked(a.length) + b.length).get];        // Aborts on overflow on element computation        foreach (i; 0 .. a.length)            r[i] = (a[i] + checked(offset)).get;        foreach (i; 0 .. b.length)            r[i + a.length] = (b[i] + checked(offset)).get;        return r;    }    assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]);}  /// `Saturate` stops at an overflow@safe unittest{    auto x = (cast(byte) 127).checked!Saturate;    assert(x == 127);    x++;    assert(x == 127);} /// `WithNaN` has a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values@safe unittest{    auto x = 100.checked!WithNaN;    assert(x == 100);    x /= 0;    assert(x.isNaN);} /// `ProperCompare` fixes the comparison operators ==, !=, <, <=, >, and >= to return correct results@safe unittest{    uint x = 1;    auto y = x.checked!ProperCompare;    assert(x < -1); // built-in comparison    assert(y > -1); // ProperCompare} /// `Throw` fails every incorrect operation by throwing an exception@safe unittest{    import std.exception : assertThrown;    auto x = -1.checked!Throw;    assertThrown(x / 0);    assertThrown(x + int.min);    assertThrown(x == uint.max);} /**Checked integral type wraps an integral `T` and customizes its behavior with thehelp of a `Hook` type. The type wrapped must be one of the predefined integrals(unqualified), or another instance of `Checked`. Params:    T    = type that is wrapped in the `Checked` type    Hook = hook type that customizes the behavior of the `Checked` type*/struct Checked(T, Hook = Abort)if (isIntegral!T || is(T == Checked!(U, H), U, H)){    import std.algorithm.comparison : among;    import std.experimental.allocator.common : stateSize;    import std.format.spec : FormatSpec;    import std.range.primitives : isInputRange, ElementType;    import std.traits : hasMember, isSomeChar;     /**    The type of the integral subject to checking.    */    alias Representation = T;     // state {    static if (hasMember!(Hook, "defaultValue"))        private T payload = Hook.defaultValue!T;    else        private T payload;    /**    `hook` is a member variable if it has state, or an alias for `Hook`    otherwise.    */    static if (stateSize!Hook > 0) Hook hook;    else alias hook = Hook;    // } state     // get    /**    Returns:        A copy of the underlying value.    */    auto get() inout { return payload; }    ///    @safe unittest    {        auto x = checked(ubyte(42));        static assert(is(typeof(x.get()) == ubyte));        assert(x.get == 42);        const y = checked(ubyte(42));        static assert(is(typeof(y.get()) == const ubyte));        assert(y.get == 42);    }     /**    Defines the minimum and maximum. These values are hookable by defining    `Hook.min` and/or `Hook.max`.    */    static if (hasMember!(Hook, "min"))    {        enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T);        ///        @safe unittest        {            assert(Checked!short.min == -32768);            assert(Checked!(short, WithNaN).min == -32767);            assert(Checked!(uint, WithNaN).max == uint.max - 1);        }    }    else    {        /// ditto        enum Checked!(T, Hook) min = Checked(T.min);    }    static if (hasMember!(Hook, "max"))    {        /// ditto        enum Checked!(T, Hook) max = Checked(Hook.max!T);    }    else    {        /// ditto        enum Checked!(T, Hook) max = Checked(T.max);    }     /**    Constructor taking a value properly convertible to the underlying type. `U`    may be either an integral that can be converted to `T` without a loss, or    another `Checked` instance whose representation may be in turn converted to    `T` without a loss.    */    this(U)(U rhs)    if (valueConvertible!(U, T) ||        !isIntegral!T && is(typeof(T(rhs))) ||        is(U == Checked!(V, W), V, W) &&            is(typeof(Checked!(T, Hook)(rhs.get))))    {        static if (isIntegral!U)            payload = rhs;        else            payload = rhs.payload;    }    ///    @safe unittest    {        auto a = checked(42L);        assert(a == 42);        auto b = Checked!long(4242); // convert 4242 to long        assert(b == 4242);    }     /**    Assignment operator. Has the same constraints as the constructor.     Params:        rhs = The value to assign     Returns:        A reference to `this`    */    ref Checked opAssign(U)(U rhs) return    if (is(typeof(Checked!(T, Hook)(rhs))))    {        static if (isIntegral!U)            payload = rhs;        else            payload = rhs.payload;        return this;    }    ///    @safe unittest    {        Checked!long a;        a = 42L;        assert(a == 42);        a = 4242;        assert(a == 4242);    }     ///    @safe unittest    {        Checked!long a, b;        a = b = 3;        assert(a == 3 && b == 3);    }     /**    Construct from a decimal string. The conversion follows the same rules as    $(REF to, std, conv) converting a string to the wrapped `T` type.     Params:        str = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)              of characters    */    this(Range)(Range str)    if (isInputRange!Range && isSomeChar!(ElementType!Range))    {        import std.conv : to;         this(to!T(str));    }     /**    $(REF to, std, conv) can convert a string to a `Checked!T`:    */    @system unittest    {        import std.conv : to;         const a = to!long("1234");        const b = to!(Checked!long)("1234");        assert(a == b);    }     // opCast    /**    Casting operator to integral, `bool`, or floating point type.     If a cast to a floating-point type is requested and `Hook` defines    `onBadCast`, the cast is verified by ensuring $(D get == cast(T)    U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned.     If a cast to an integral type is requested and `Hook` defines `onBadCast`,    the cast is verified by ensuring `get` and $(D cast(U)    get) are the same arithmetic number. (Note that `int(-1)` and    `uint(1)` are different values arithmetically although they have the same    bitwise representation and compare equal by language rules.) If the numbers    are not arithmetically equal, `hook.onBadCast!U(get)` is    returned.     Params:        U = The type to cast to     Returns:        If `Hook`     defines `hookOpCast`, the call immediately returns        `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D        get != 0) and casting to another integral that can represent all        values of `T` returns `get` promoted to `U`.    */    U opCast(U, this _)()    if (isIntegral!U || isFloatingPoint!U || is(U == bool))    {        static if (hasMember!(Hook, "hookOpCast"))        {            return hook.hookOpCast!U(payload);        }        else static if (is(U == bool))        {            return payload != 0;        }        else static if (valueConvertible!(T, U))        {            return payload;        }        // may lose bits or precision        else static if (!hasMember!(Hook, "onBadCast"))        {            return cast(U) payload;        }        else        {            if (isUnsigned!T || !isUnsigned!U ||                    T.sizeof > U.sizeof || payload >= 0)            {                auto result = cast(U) payload;                // If signedness is different, we need additional checks                if (result == payload &&                        (!isUnsigned!T || isUnsigned!U || result >= 0))                    return result;            }            return hook.onBadCast!U(payload);        }    }    ///    @safe unittest    {        assert(cast(uint) checked(42) == 42);        assert(cast(uint) checked!WithNaN(-42) == uint.max);    }     // opEquals    /**    Compares `this` against `rhs` for equality.     If `U` is also an instance of `Checked`, both hooks (left- and right-hand

Lines 1–500 of 3595.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t