This project is read-only.
1

Resolved

Inconsistent bool array comparisons

description

using System;

class Test
{
    static void Foo (ref bool b)
    {
        bool b2 = true;
        Console.WriteLine (b == true);
        Console.WriteLine (b == b2);
    }

    static int Main()
    {
        var a = new bool[1];
        Buffer.SetByte (a, 0, 5);

        var b = true;
        Console.WriteLine (a [0] == true);
        Console.WriteLine (a [0] == b);
        Foo (ref a [0]);

        return 0;
    }
}
Compiled and executed produces inconsistent and unexpected results

True
False
True
False

comments

VSadov wrote Jul 24, 2014 at 6:41 PM

This is a case of unfair low level trickery.

From compiler's point of view there is only one kind of "true". Therefore
  Console.WriteLine (b == true);
is the same as
 Console.WriteLine ((true)b);
The code plays with the fact that internally bools are bytes and via bit-fiddling it is possible to get into case when one "true" value is not equal to another.

It does not seem to be common enough case to support. It is not entirely clear how to handle such cases universally. - What happens to boolean logic in general when there are multiple truths? How short-circuiting is handled and so on...

mareksafar wrote Jul 24, 2014 at 7:04 PM

It's quite simple to handle this. You just have to 'and 1' any loaded bool value. csc does it but only is some cases which is strange.

VSadov wrote Aug 8, 2014 at 6:37 PM

ECMA CLI spec specifies bool storage in metadata as a byte with 0 treated as false and all other values as true when used in conditions. When used in computation, bool is represented as int32 and behaves as such.

This really means that bool with a value 2 and bool with a value 4 are both considered true when used as conditions and " if (bool2) ... " will behave the same as " if (bool4) ... "

However " bool2 & bool4 " will result in a false value.
The native behavior of CLI bool is more similar to C++ BOOL than to the c# bool

On the other hand the C# spec specifies bool as having just 2 values - true and false and does prescribe any particular implementation.
Generally this mismatch between C# and CLI is not a problem since bools are typically either results of boolean expressions or true/false literals and therefore we only need to deal with 0 and 1.

It is indeed possible for underlying implementation to leak in the scenario like the one you described. C# spec does not mention anything about what needs to happen - ignore such cases or normalize (when?).
I think what looks like attempts at normalization in csc is not intentional and inconsistent. Possibly an artifact of some codegeneration strategy.

Considering ECMA CLI spec, “val and 1” is not enough to normalize val, since any nonzero value would need to be normalized into 1, not just 1. We would need to do something like "((val ceq 0) ceq 1)" . It seems a bit heavy to do such thing unconditionally.

We need to think a bit more about this scenario. Perhaps we could specify an identity (bool) conversion as a normalization operator?
That would be similar to how identity (float) and (double) conversions can be used to force normalization of machine-dependent internal representation to the nominal IEEE precision values.

.

mareksafar wrote Aug 19, 2014 at 7:05 PM

C# spec about bool type has following note which describes what should happen

[Note: In the C and C++ languages, a zero integral or floating-point value, or a null pointer can be converted to the Boolean value false, and a non-zero integral or floating-point value, or a non-null pointer can be converted to the Boolean value true. In C#, such conversions are accomplished by explicitly comparing an integral or floating-point value to zero, or by explicitly comparing an object reference to null. end note]

VSadov wrote Sep 11, 2014 at 12:55 AM

Yes, the C# spec avoids dealing with nonstandard bools. It does not provide a way to directly convert a byte to a bool nor it specify any behavior for situations where bool can have more than two states.

Language itself would not produce nonstandard Boolean values, but they can be manufactured through the platform related API or through interop with components written in other languages.

Typical examples are (in addition to the case with Buffer APIs and block copying) :
        // unsafe code
        unsafe public static bool ByteToBoolean(byte value)
        {
            return *(bool*)&value;
        }

        // this one is actually safe!!
        [StructLayout(LayoutKind.Explicit)]
        struct Union
        {
            [FieldOffset(0)]
            private byte asByte;
            [FieldOffset(0)]
            private bool asBool;

            public static bool ByteToBoolean(byte value)
            {
                Union u;
                u.asByte = value;
                return u.asBool;
            }
        }
We discussed this a bit and decided that it is ok to leave the behavior with nonstandard bools as undefined in the compiler spec.
I.E. the language will stay entirely unconcerned about nonstandard bools. The particular implementation (as in MS C# on CIL) will acknowledge the existence of nonstandard bools and specify their behavior as undefined.

We also considered adding a small language feature to allow users to normalize boolean values to the standard state via " (bool)nonstandardValue " - similarly how " (float)floatResultOfComputation " normalizes floats, but that appear to be redundant. Unlike the case with normalizing floats, normalizing bools is completely expressible in C# already.
In the unlikely case if user finds himself in need to deal with nonstandard bools, he can just write a helper like:
        static bool Normalize(this bool nonstandard)
        {
            return nonstandard == false ? false : true;
        }
We also considered doing this automatically in the compiler, but the problem is that compiler has no way to know when it deals with compromised booleans and doing this defensively every time a boolean value is loaded would come with a great cost to the users who for the vast majority do not ever see nonstandard bools.

So for now inconsistency is ByDesing since the behavior is undefined.
Affected users can use a helper like the "Normalize" above as a workaround.

.