Open burner opened 18 years ago
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
d-bugmail@puremagic.com schrieb am 2006-07-20:
http://d.puremagic.com/issues/show_bug.cgi?id=259
From the book of Bright, http://www.digitalmars.com/d/expression.html#RelExpression
"It is an error to have one operand be signed and the other unsigned for a <, <=, > or >= expression. Use casts to make both operands signed or both operands unsigned."
...yet...
import std.conv;
int main( char[] args[] ) {
int i = toInt(args[1]);
uint u = toUint(args[2]);
if (i < u)
return 1;
else
return 0;
}
...compiled with "dmd", "dmd -debug" or "dmd -release" does not give an error message, neither at compile time, nor at run time.
Added to DStress as http://dstress.kuehne.cn/nocompile/o/opCmp_08_A.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_B.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_C.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_D.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_E.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_F.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_G.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_H.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_I.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_J.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_K.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_L.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_M.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_N.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_O.d http://dstress.kuehne.cn/nocompile/o/opCmp_08_P.d
Thomas
-----BEGIN PGP SIGNATURE-----
iD8DBQFE8DkSLK5blCcjpWoRAvd4AJ9n86OSH7vfqYKSxBQHFupEzG9/OgCfasr1 EMOV+he335iGTfu8g/Ff45g= =v3rD -----END PGP SIGNATURE-----
Bug 2205 has been marked as a duplicate of this bug.
Note that it's critical than any fix for this bug should not make code like the following fail to compile:
uint a = 5; if (a > 2) { ... }
Otherwise the cure would be worse than the disease
Even more critical, any fix for this bug should not make code like the following fail to compile:
uint a = 5; if (a > -2) { ... }
(In reply to comment #4)
Even more critical, any fix for this bug should not make code like the following fail to compile:
uint a = 5; if (a > -2) { ... }
Why not? This is obviously a bug!
(In reply to comment #5)
uint a = 5; if (a > -2) { ... }
Why not? This is obviously a bug!
Agreed. I can't tell you how many times I've done something stupid like:
for(uint i = something; i >= 0; i--) { / yay, infinite loop! / }
A nontrivial condition that always results in an infinite loop should never be accepted.
Is there any official response to this issue? It was reported at 2006, but the status is still "NEW"
As I look, bug 2006 doesn't seem to have anything to do with this one at all.
But if you find a duplicate of a bug, then mark it as one!
(In reply to comment #8)
As I look, bug 2006 doesn't seem to have anything to do with this one at all.
But if you find a duplicate of a bug, then mark it as one!
sorry, i mean it was reported in year 2006.
This needs to be fixed. I'm taking ownership (though Walter will do all the work) in order to not forget. I'll add a unittest to TDPL too. Until then let's vote this up.
Reassigning this to Walter. Walter, any chance you could please bump the priority on fixing this one. I am bumping priority to major, it's an important bug that is getting rather old.
This morning I've been tinkering with the DMD source, and I've made an edit to expression.c that appears to fix this issue. How do I submit?
(In reply to comment #12)
This morning I've been tinkering with the DMD source, and I've made an edit to expression.c that appears to fix this issue. How do I submit?
Either create a patch using SVN, and attach it, or just post the lines you changed (Walter doesn't actually use patch files, as far as I can tell). Search for bugs with keyword 'patch' for examples.
Okay, so what I have is it checks for
signed cmp unsigned
or vice versa in CmpExp::Semantic just before typeCombine gets called, which works, but then stuff like
1 < 1u
doesn't. So the idea is
signed cmp unsigned
or vice versa is okay if the signed arg is a literal and its value is nonnegative.
This should work fine if sizeof(signed arg) =< sizeof(unsigned arg) because the value of the signed arg is within the range of the unsigned arg, and typeCombine should be able to expand the type of the signed arg to that of the unsigned arg or whatever. It should work if sizeof(signed arg) > sizeof(unsigned arg) because the value of the unsigned arg is within the range of the signed arg, and typeCombine should be able to expand the type of the unsigned arg to that of the signed arg.
I don't know, maybe this should be happening in typeCombine. Insert the following in expression.c CmpExp::semantic before the line
typeCombine(sc);
if ( e1->type->isintegral() && e2->type->isintegral()){
if(e1->type->isunsigned() ^ e2->type->isunsigned()){
if(!e1->type->isunsigned() &&
dynamic_cast<IntegerExp*>(e1) &&
((sinteger_t) e1->toInteger()) >= 0) goto JustKidding;
if(!e2->type->isunsigned() &&
dynamic_cast<IntegerExp*>(e2) &&
((sinteger_t) e2->toInteger()) >= 0) goto JustKidding;
error("comparing signed and unsigned integers");
}
JustKidding:;
}
(In reply to comment #14)
Okay, so what I have is it checks for [snip]
signed cmp unsigned
or vice versa is okay if the signed arg is a literal and its value is nonnegative.
In D2, it's possible to a lot better than this. With the new D2 rules for implicit conversion between integral types (range-based), it should be enough to require that one of the types must implicitly convert to the other. This doesn't quite work yet, since range-based addition (for example) is not yet implemented. This will mean that stuff like: byte b; uint u; b = -67; if (u < b + 0x100) ... is OK because although b + 0x100 is signed, it can never be less than 1, so it can safely be converted to unsigned.
Cool. That sounds like a much better solution than the patch I posted.
Concerning my patch, I just realized that comparison with unsigned and zero generally doesn't make sense either, except maybe
{unsigned} > 0
so now I have
if ( e1->type->isintegral() && e2->type->isintegral()){
if(e1->type->isunsigned() ^ e2->type->isunsigned()){
if(!e1->type->isunsigned() && dynamic_cast<IntegerExp*>(e1)){
sinteger_t v1 = ((sinteger_t) e1->toInteger());
if(v1 > 0) goto JustKidding;
// 0 < uns or 0 !>= uns okay
else if(v1 == 0 && (op == TOKlt || op == TOKul))
goto JustKidding;
}else if(dynamic_cast<IntegerExp*>(e2)){
sinteger_t v2 = ((sinteger_t) e2->toInteger());
if(v2 > 0) goto JustKidding;
// uns > 0 or uns !<= 0 okay
else if(v2 == 0 && (op == TOKgt || op == TOKug))
goto JustKidding;
}
error("comparing signed and unsigned integers");
}
JustKidding:;
}
in case anyone plans to commit it
Hi,
I today found remarkable bug:
int k = -1; uint n = 7; assert(k < n);
Code above should execute without proble, but dmd it computing false as value of (k<n) which absolutly nonsensical. casting n to int, resolves problem
int k = -1; uint n = 7; assert(k < cast(T)n);
How it can be so long time not resolved?
Escalating severity of this dangerous issue. Has 11 votes, too.
Indeed this is rather critical; I hit it in a simple precondition like: void insert( Object o, int ndx = -1 ) // -1 means last in { assert( ndx >= -1 && ndx <= somearray.length ); }
forcing to add a cast everywhere would be very painful...
The following code should compile without error and pass: void main() { uint ulen = 0; int ndx = -1; assert( -1 <= ulen ); assert( ndx <= ulen ); }
If it generates warnings is another issue...
// This one should compile too void func2() { uint u; // should not compile (or at least warn): always yields to true static assert( !is( typeof( u >= 0 ) ) ); static assert( !is( typeof( u >= -1 ) ) ); }
See https://github.com/D-Programming-Language/dmd/pull/119 https://github.com/D-Programming-Language/dmd/pull/444 for two possible fixes.
See:
https://github.com/D-Programming-Language/dmd/commit/4536fd5e3102fcea168660e9c4d2a2c80d50e7f5
But druntime and Phobos will probably need some changes.
(In reply to comment #21)
But druntime and Phobos will probably need some changes.
Keeping this as a warning in the current compiler, and turn it into an (deprecated) error in the next DMD version, will probably allow to update druntime and Phobos.
I think Walter is willing to accept that patch if someone improves the patch to remove some of its false positives.
It seems wrong to me that:
if (-1 < 2u) {...} fails to compile. Both are in the range -int.max .. int.max, so they can safely be compared using signed comparison.
The problems with mixed sign are when one operand can be in the range -int.max..0 and the other can be in the range int.max+1u..uint.max. If at least one of the operands is in the polysemous range 0..int.max, there should be no problem.
But, the patch as it stands allows a polysemous-ranged int to be compared with a uint, but does not allow a polysemous-range uint to be compared with an int. At present the spec doesn't give any reason for unsigned to be treated from signed.
The problem is the silly C promotion rule that converts both operands to unsigned (instead, it should generate an error). In any existing C code that works, it works because the signed operand is in the polysemous range. Of course there could be code which relies on (-1<2u) being false. But surely such code is broken.
Do we really need to retain backwards compatibility with broken C code?
(In reply to comment #24)
Thank you Don.
Of course there could be code which relies on (-1<2u) being false. But surely such code is broken.
I don't like this is in D.
Issue state:
dmd pull #444 https://github.com/D-Programming-Language/dmd/pull/444 was merged, then #ifdef'd out as it was "too disruptive" in https://github.com/D-Programming-Language/dmd/commit/52d8c150ecfbe2cad6672f50094a6ff1230e72e3 then completely removed in dmd pull #1611 https://github.com/D-Programming-Language/dmd/pull/1611
I think we're going to have to give up on this one.
(In reply to comment #27)
I think we're going to have to give up on this one.
Not being able to at least highlight signed to unsigned comparisons in a program is a major lack of functionality. IMHO this is why enhancement Issue 9811 matters as it is the place where we can forget about "disruptiveness" and "is it always and error" questions.
(In reply to comment #27)
I think we're going to have to give up on this one.
This is a bit abrupt. What was the decision process? I just shared an anecdote about a few major bugs at work caused by this exact behavior.
(In reply to comment #29)
This is a bit abrupt. What was the decision process? I just shared an anecdote about a few major bugs at work caused by this exact behavior.
It's been 7 years of discussion now without an answer that works. I don't think it's abrupt!
(In reply to comment #30)
(In reply to comment #29)
This is a bit abrupt. What was the decision process? I just shared an anecdote about a few major bugs at work caused by this exact behavior.
It's been 7 years of discussion now without an answer that works. I don't think it's abrupt!
How does pull request 119 not work?
And why haven't you written a fix yourself in these 7 years - or even made a single comment on this bug until now?
(In reply to comment #31)
How does pull request 119 not work?
Follow the links & comments https://github.com/D-Programming-Language/dmd/pull/119
If you want to discuss that further, please post there.
And why haven't you written a fix yourself in these 7 years - or even made a single comment on this bug until now?
I've discussed it extensively on the n.g. This isn't the only place it's come up.
Reopening. The entire discussion makes it obvious this needs a fix. It's critical and has 20 votes, probably more than probably any other bug. Closing this without any explanation is straight the opposite of listening to the community. Thanks.
I don't mind if it is reopened. It's just that it's sat at the top of the "critical" bug list forever with no movement towards a solution. I'd like to get this resolved one way or another.
Great. Here's a solution Walter and I just discussed:
Consider a comparison a < b, a <= b, a > b, or a >= b, in which a and b are integral types of different signedness. Without loss of generality, let's consider a is signed and b is unsigned and the comparison is a < b. Then we have the following cases:
If a.sizeof > b.sizeof, then a < b is lowered into a < cast(typeof(a)) b. Then signed comparison proceeds normally. This is a classic value-based conversion dating from the C days, and we do it in D as well.
Otherwise, if a is determined through Value Range Propagation to be greater than or equal to zero, then a < b is lowered into cast(U) a < b, where U is the unsigned variant of typeof(a). Then unsigned comparison proceeds normally.
Otherwise, the comparison is in error. The error message may recommend using the std.traits.unsigned function, which executes a size-informed cast.
Walter, if you agree with this resolution please mark this as "preapproved".
Preapproved FTW! Who wants to implement this?
Resetting asignee to default. If anyone wants to work on this, please assign it to yourself!
(In reply to comment #35)
- Otherwise, the comparison is in error. The error message may recommend using the std.traits.unsigned function, which executes a size-informed cast.
This is the source of unsigned:
auto unsigned(T)(T x) if (isIntegral!T) { static if (is(Unqual!T == byte )) return cast(ubyte ) x; else static if (is(Unqual!T == short)) return cast(ushort) x; else static if (is(Unqual!T == int )) return cast(uint ) x; else static if (is(Unqual!T == long )) return cast(ulong ) x; else { static assert(T.min == 0, "Bug in either unsigned or isIntegral"); return cast(Unqual!T) x; } }
Is it better to use template constraints there? And isn't it better for unsigned to accept only unsigned Ts?
Thanks Lionello for taking this over. I thought of one more case this morning so let me insert in the food chain:
(Recall a is signed and b is unsigned.)
==============
If a.sizeof > b.sizeof, then a < b is lowered into a < cast(typeof(a)) b. Then signed comparison proceeds normally. This is a classic value-based conversion dating from the C days, and we do it in D as well.
Otherwise, if a is determined through Value Range Propagation to be greater than or equal to zero, then a < b is lowered into cast(U) a < b, where U is the unsigned variant of typeof(a). Then unsigned comparison proceeds normally.
(NEW) Otherwise, if b is determined through Value Range Propagation to be less than or equal to typeof(b).max / 2, then a < b is lowered into a < cast(S) b, where S is the signed variant of typeof(b). Then signed comparison proceeds normally.
Otherwise, the comparison is in error. The error message may recommend using the std.traits.unsigned function, which executes a size-informed cast.
==============
Sounds good but ... given that people have been trying for 7 years without success to implement the basic rule from the spec, how many more years can we realistically expect it to be before this new scheme being suggested is in the compiler?
(In reply to comment #40)
Sounds good but ... given that people have been trying for 7 years without success to implement the basic rule from the spec, how many more years can we realistically expect it to be before this new scheme being suggested is in the compiler?
Now that we have a preapproved solution, a solid VRP implementation, and a much expanded contribution base, I can only assume it'll take days. Lionello?
Yes, I'll have this done this week.
For point 1: how to cope with the fact that I can safely cast an uint to long, but can't cast an int to an ulong (without issues)?
Surely cast(int)-1 < cast(ulong)1 should evaluate to true? But according to point 1 it would be rewritten as cast(ulong)-1 < cast(ulong)1 which is 0xFFFFFFFFFFFFFFFF < 0x1 and evaluate to false.
(In reply to comment #42)
Yes, I'll have this done this week. For point 1: how to cope with the fact that I can safely cast an uint to long, but can't cast an int to an ulong (without issues)? Surely cast(int)-1 < cast(ulong)1 should evaluate to true? But according to point 1 it would be rewritten as cast(ulong)-1 < cast(ulong)1 which is 0xFFFFFFFFFFFFFFFF < 0x1 and evaluate to false.
No. Point 1 is the case where a.sizeof > b.sizeof. This isn't the case here.
Thanks Lionello for working on this! It will make D noticeably better at bread-and-butter work. Regarding your question - what Stewart said. Let me know if you hit any snag.
uint b; if (b > -2) { ... }
The integral expression -2 doesn't have any size per se, but whole size thing shouldn't even apply here, since the two expressions have ranges that are completely disjoint. Points 2 and 3 won't catch this case either and would cause an error. We could consider all integrals 'long' and apply point 1. Or, test for disjoint ranges and replace the whole comparison with a constant? (I have a feeling this optimization might already be happening in a later pass?)
(In reply to comment #45)
uint b; if (b > -2) { ... }
The integral expression -2 doesn't have any size per se, but whole size thing shouldn't even apply here, since the two expressions have ranges that are completely disjoint. Points 2 and 3 won't catch this case either and would cause an error. We could consider all integrals 'long' and apply point 1. Or, test for disjoint ranges and replace the whole comparison with a constant? (I have a feeling this optimization might already be happening in a later pass?)
I'd say we need to make this an error. If we don't (i.e. make the comparison always true), then we'd silently change behavior.
https://github.com/D-Programming-Language/dmd/pull/1889
There are many things silently(!) breaking, though, as some templates are not being chosen because of internal comparison errors.
For example, FormatSpec.width and FormatSpec.precision are ints but often compared against array lengths (for padding and such). TBH, using negative width and precision to mean "argument index" is a hack and we'd be better off changing them to uint and use a flag for the "argument index" case.
// For the record: my test cases. Will add/fix existing unittests as well. import std.traits; int i; uint ui; long l; ulong ul; // 0. same-signed-ness static assert(traits(compiles, ui>ul)); static assert(traits(compiles, ul>ui)); static assert(traits(compiles, i>l)); static assert(traits(compiles, l>i)); static assert(traits(compiles, 1>2)); static assert(!(1>2)); static assert(traits(compiles, 2>1)); static assert(2>1); // 1. sizeof(signed) > sizeof(unsigned) static assert(traits(compiles, l>ui)); static assert(traits(compiles, ui>l)); static assert(traits(compiles, -1L>2)); static assert(!(-1L>2)); static assert(traits(compiles, 2>-1L)); static assert(2>-1L); // 2. signed.min >= 0 static assert(traits(compiles, ui>cast(int)2)); static assert(traits(compiles, cast(int)2>ui)); static assert(traits(compiles, ul>cast(int)2)); static assert(traits(compiles, cast(int)2>ul)); // 3. unsigned.max < typeof(unsigned.max/2) static assert(traits(compiles, i>cast(uint)2)); static assert(traits(compiles, cast(uint)2>i)); static assert(traits(compiles, cast(int)-1>cast(uint)3)); static assert(traits(compiles, cast(uint)3>cast(int)-1)); static assert(traits(compiles, -1>2UL)); static assert(!(-1>2UL)); static assert(traits(compiles, 2UL>-1)); static assert(2UL>-1); // error static assert(!traits(compiles, ul>-2)); static assert(!traits(compiles, -2>ul)); static assert(!traits(compiles, i>ul)); static assert(!traits(compiles, ul>i)); static assert(!traits(compiles, l>ul)); static assert(!traits(compiles, ul>l)); static assert(!traits(compiles, i>ui)); static assert(!traits(compiles, ui>i));
void main(){}
We should probably consider making this a warning or deprecation, instead of an error. The silent failures within templates make it very hard to fix code.
Warnings and errors during template instantiations are suppressed and the final "errors instantiating template" doesn't say why (not even with -v; this is probably worth a filing a separate bug for.)
I'm having to add a "printf" to expression.c to see those occurrences, \util\dmd2\src\phobos\std\format.d(1216) \util\dmd2\src\phobos\std\format.d(1224) \util\dmd2\src\phobos\std\format.d(1395)
I've discovered one additional case,
1b. If both types can be cast to the bigger signed type, the cast is safe
lio+bugzilla (lionello) reported this on 2006-07-20T06:34:58Z
Transfered from https://issues.dlang.org/show_bug.cgi?id=259
CC List
Description
From the book of Bright, http://www.digitalmars.com/d/expression.html#RelExpression
"It is an error to have one operand be signed and the other unsigned for a <, <=, > or >= expression. Use casts to make both operands signed or both operands unsigned."
...yet...
import std.conv;
int main( char[] args[] ) {
int i = toInt(args[1]);
uint u = toUint(args[2]);
if (i < u)
return 1;
else
return 0;
}
...compiled with "dmd", "dmd -debug" or "dmd -release" does not give an error message, neither at compile time, nor at run time.