Bug 259 – Comparing signed to unsigned does not generate an error

Status
REOPENED
Severity
enhancement
Priority
P4
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2006-07-20T06:34:58Z
Last change time
2024-12-13T17:47:42Z
Keywords
accepts-invalid, industry, preapproved, pull, spec
Assigned to
Lionello Lunesu
Creator
Lionello Lunesu
Depends on
9960
See also
http://d.puremagic.com/issues/show_bug.cgi?id=9960, https://issues.dlang.org/show_bug.cgi?id=12919
Moved to GitHub: dmd#17504 →

Comments

Comment #0 by lio+bugzilla — 2006-07-20T06:34:58Z
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.
Comment #1 by thomas-dloop — 2006-08-26T06:15:29Z
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 [email protected] 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-----
Comment #2 by smjg — 2009-02-22T11:45:45Z
*** Bug 2205 has been marked as a duplicate of this bug. ***
Comment #3 by clugdbug — 2009-02-23T02:28:17Z
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 <g>.
Comment #4 by ddparnell — 2009-02-23T02:34:37Z
Even more critical, any fix for this bug should not make code like the following fail to compile: uint a = 5; if (a > -2) { ... }
Comment #5 by 2korden — 2009-02-23T07:40:14Z
(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!
Comment #6 by jarrett.billingsley — 2009-02-23T07:53:20Z
(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.
Comment #7 by qian.xu — 2009-07-22T00:31:06Z
Is there any official response to this issue? It was reported at 2006, but the status is still "NEW"
Comment #8 by smjg — 2009-07-22T01:17:51Z
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!
Comment #9 by qian.xu — 2009-07-22T01:34:05Z
(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.
Comment #10 by andrei — 2009-07-24T18:08:49Z
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.
Comment #11 by andrei — 2009-08-24T22:17:42Z
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.
Comment #12 by ellery-newcomer — 2009-09-03T08:33:41Z
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?
Comment #13 by clugdbug — 2009-09-03T08:39:48Z
(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.
Comment #14 by ellery-newcomer — 2009-09-03T13:51:16Z
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:; }
Comment #15 by clugdbug — 2009-09-08T09:40:30Z
(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.
Comment #16 by ellery-newcomer — 2009-09-08T10:43:00Z
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
Comment #17 by witold.baryluk+d — 2009-11-18T13:13:14Z
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?
Comment #18 by andrei — 2010-11-26T10:43:45Z
Escalating severity of this dangerous issue. Has 11 votes, too.
Comment #19 by eric.estievenart — 2011-01-14T11:55:24Z
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 ) ) ); }
Comment #20 by kamm-removethis — 2011-10-10T08:58:59Z
Comment #21 by bearophile_hugs — 2011-11-24T17:45:34Z
See: https://github.com/D-Programming-Language/dmd/commit/4536fd5e3102fcea168660e9c4d2a2c80d50e7f5 But druntime and Phobos will probably need some changes.
Comment #22 by bearophile_hugs — 2011-11-24T19:25:19Z
(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.
Comment #23 by bearophile_hugs — 2011-11-25T02:32:32Z
I think Walter is willing to accept that patch if someone improves the patch to remove some of its false positives.
Comment #24 by clugdbug — 2011-11-25T04:05:29Z
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?
Comment #25 by bearophile_hugs — 2011-11-25T04:26:23Z
(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.
Comment #26 by verylonglogin.reg — 2013-03-04T23:44:52Z
Comment #27 by bugzilla — 2013-04-07T01:27:07Z
I think we're going to have to give up on this one.
Comment #28 by verylonglogin.reg — 2013-04-07T09:42:55Z
(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.
Comment #29 by andrei — 2013-04-07T15:03:49Z
(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.
Comment #30 by bugzilla — 2013-04-07T15:49:14Z
(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!
Comment #31 by smjg — 2013-04-07T16:02:40Z
(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?
Comment #32 by bugzilla — 2013-04-07T16:50:41Z
(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.
Comment #33 by andrei — 2013-04-07T17:24:25Z
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.
Comment #34 by bugzilla — 2013-04-07T17:50:06Z
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.
Comment #35 by andrei — 2013-04-07T18:38:23Z
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: 1. 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. 2. 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. 3. 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".
Comment #36 by andrei — 2013-04-07T18:57:27Z
Preapproved FTW! Who wants to implement this?
Comment #37 by andrei — 2013-04-07T19:05:56Z
Resetting asignee to default. If anyone wants to work on this, please assign it to yourself!
Comment #38 by bearophile_hugs — 2013-04-07T19:29:00Z
(In reply to comment #35) > 3. 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?
Comment #39 by andrei — 2013-04-08T07:21:23Z
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.) ============== 1. 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. 2. 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. 3. (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. 4. Otherwise, the comparison is in error. The error message may recommend using the std.traits.unsigned function, which executes a size-informed cast. ==============
Comment #40 by smjg — 2013-04-08T15:57:45Z
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?
Comment #41 by andrei — 2013-04-08T16:14:12Z
(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?
Comment #42 by lio+bugzilla — 2013-04-09T03:02:20Z
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.
Comment #43 by smjg — 2013-04-09T05:21:52Z
(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.
Comment #44 by andrei — 2013-04-09T08:37:50Z
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.
Comment #45 by lio+bugzilla — 2013-04-10T09:20:22Z
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?)
Comment #46 by andrei — 2013-04-10T09:45:57Z
(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.
Comment #47 by lio+bugzilla — 2013-04-11T19:24:53Z
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.
Comment #48 by lio+bugzilla — 2013-04-11T19:42:37Z
// 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(){}
Comment #49 by lio+bugzilla — 2013-04-12T22:01:28Z
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)
Comment #50 by lio+bugzilla — 2013-05-03T14:20:12Z
I've discovered one additional case, 1b. If both types can be cast to the bigger signed type, the cast is safe
Comment #51 by nick — 2013-08-20T03:11:43Z
Comment #52 by lio+bugzilla — 2013-09-07T12:12:57Z
Also submitted fixes to druntime: https://github.com/D-Programming-Language/druntime/pull/601 (Note: check the pull requests for the latest info)
Comment #53 by github-bugzilla — 2013-09-22T14:11:38Z
Commit pushed to master at https://github.com/D-Programming-Language/druntime https://github.com/D-Programming-Language/druntime/commit/439d079dce0079cbacaedd91648d85d2c7d660c0 Merge pull request #601 from lionello/bug259 Bug259: fixed signed-unsigned comparisons in druntime
Comment #54 by bearophile_hugs — 2013-12-07T06:48:38Z
If an immutable int x is inside the if branch that asserts it to be not negative, the assignments and comparisons with an uint should not give warnings: uint z; void main(string[] args) { immutable int x = args.length - 3; if (x >= 0) { uint y = x; // give no errors here if (x > z) {} // give no warning here. } }
Comment #55 by github-bugzilla — 2014-05-22T06:28:04Z
Commit pushed to master at https://github.com/D-Programming-Language/druntime https://github.com/D-Programming-Language/druntime/commit/8890678458e775e93ade1de307a417b06032ee0f Merge pull request #799 from lionello/bug259 Fixed some signed/unsigned warnings in druntime
Comment #56 by andrei — 2015-03-15T03:41:20Z
What's left to do about this?
Comment #57 by lio+bugzilla — 2015-03-18T11:38:26Z
(In reply to Andrei Alexandrescu from comment #56) > What's left to do about this? I need to finish some of the static code analysis that I have been adding so we get less false positives. It's the false positives that will turn Walter (and others) off and prevent merging it in.
Comment #58 by dominikus — 2015-04-17T08:49:01Z
The problem is still there, and the behaviour is completely inconsistent, so braking any code isn't a problem I think because I cannot imagine that anybody really relies on the strange behaviour: unittest { byte a = -3; ubyte b = 2; short c = -3; ushort d = 2; int e = -3; uint f = 2; long g = -3; ulong h = 2; assert(a < b); assert(c < d); assert(e < f); // fails!! assert(g < h); // fails!! assert(a < h); // fails!! assert(b > g); assert(d > e); } So why don't we change to something that simply always works? int opCmp(T, U)(const(T) a, const(U) b) pure @safe @nogc nothrow if(is(Unqual!T == Unqual!U) || isFloatingPoint!T || isFloatingPoint!U) { // Should be buildin. Naive implementation: return a <= b ? a != b ? -1 : 0 : 1; } /// Returns negative value if a < b, 0 if they are equal or positive value if a > b. /// This will always yield a correct result, no matter which integral types are compared. /// It uses one extra comparison operation if and only if /// one type is signed and the other unsigned but has bigger max. int opCmp(T, U)(const(T) a, const(U) b) pure @safe @nogc nothrow if(isIntegral!T && isIntegral!U && !is(Unqual!T == Unqual!U)) { static if(T.sizeof == U.sizeof) alias C = Unsigned!T; else alias C = CommonType!(T, U); // this will be the larger type static if(isSigned!T && isUnsigned!U && T.sizeof <= U.sizeof) { return (a < 0) ? -1 : opCmp(cast(Unsigned!C)a, cast(C)b); } else static if(isUnsigned!T && isSigned!U && T.sizeof >= U.sizeof) { return (b < 0) ? 1 : opCmp(cast(C)a, cast(Unsigned!C)b); } else { // both signed or both unsigned or the unsigned type is smaller // and can therefore be safely cast to the signed type return opCmp(cast(C)a, cast(C)b); } }
Comment #59 by lio+bugzilla — 2015-04-17T09:25:29Z
It's currently using the C integer promotion rules, which are consistent (they're rules after all) but far from simple.
Comment #60 by dominikus — 2015-04-17T11:40:42Z
(In reply to Lionello Lunesu from comment #59) > It's currently using the C integer promotion rules, which are consistent > (they're rules after all) but far from simple. Ah, ok. I see why a<b and c<d work - they are all promoted to int. But never the less: who would be affected by changing the behaviour for int and long? Is really anybody relying on that? And sure that was not a bug from the beginning? I don't think that we really should keep bugs just to be consistend with C. --> this is a case where a compiler warning is good: "Comparing signed with unsigned values. This works in D but may be a performance issue and behaves different from C. Is this intended?" (of course works only after the fix :-)
Comment #61 by smjg — 2015-04-17T12:34:24Z
(In reply to Dominikus Dittes Scherkl from comment #58) > The problem is still there, and the behaviour is completely inconsistent, so > braking any code isn't a problem I think because I cannot imagine that > anybody really relies on the strange behaviour: Exactly, because it won't break any code. It will cause code that is already broken to correctly error.
Comment #62 by jrdemail2000-dlang — 2016-04-20T04:03:54Z
Hit this in my code. My bug, but would have been useful if the compiler had informed me there was a signed vs unsigned comparison. Here's the reduced form of what was happening in my code: void main() { int i = -1; size_t j = 0; assert(i < j); } This code compiles, and the assert fails. Except that I didn't have the assert, just the test.
Comment #63 by dominikus — 2016-08-30T20:49:00Z
(In reply to Dominikus Dittes Scherkl from comment #58) > So why don't we change to something that simply always works? I have meanwhile improved my solution to something more straight forward, with less superfluous casting, and now that the frontend is written in D, it should be no problem to integrate it in the compiler: int opCmp(T, U)(const(T) a, const(U) b) pure @safe @nogc nothrow if(isIntegral!T && isIntegral!U && is(Unqual!T != Unqual!U)) { static if(isSigned!T && isUnsigned!U && T.sizeof <= U.sizeof) return (a < 0) ? -1 : opCmp(cast(U)a, b); else static if(isUnsigned!T && isSigned!U && T.sizeof >= U.sizeof) return (b < 0) ? 1 : opCmp(a, cast(T)b); else // do interger propagation as ever: return opCmp(cast(CommonType!(T, U))a, cast(CommonType!(T, U))b); }
Comment #64 by andrei — 2016-09-03T13:50:47Z
Time to finalize this. We should be fine with a slight loss in efficiency in mixed sign compare. What active PRs are open at this time?
Comment #65 by thomas.bockman — 2016-10-10T17:33:38Z
(In reply to Andrei Alexandrescu from comment #64) > We should be fine with a slight loss in efficiency in > mixed sign compare. The previously agreed upon solution was to default to warning about each signed/unsigned comparison, but omit the warning if VRP could prove it to be safe. There is no loss of run-time efficiency that way, but some code will break and require an explicit cast to fix. Are you suggesting that we should just insert an extra comparison to zero as needed, instead? I'd support that, but I don't know how others (including Walter Bright) would feel about it. The real performance impact should be immeasurably small in nearly all cases (especially with the VRP upgrades discussed below). > What active PRs are open at this time? My DMD #5229 is a continuation of Lionello's work toward improving VRP sufficiently to minimize code breakage: https://github.com/dlang/dmd/pull/5229 However, it's stuck pending a resolution to another DMD bug: https://issues.dlang.org/show_bug.cgi?id=15585 If #5229 ever gets pulled, one more tricky VRP- related follow-up is required to make the compiler smart enough. After that, actually fixing this bug is trivial: I've had that patch ready to go for about a year now, but can't submit it until the VRP stuff is straightened out because it causes too much breakage.
Comment #66 by thomas.bockman — 2016-10-10T17:35:06Z
Oops! That's the wrong DMD bug. This is the one I actually meant: https://issues.dlang.org/show_bug.cgi?id=14835
Comment #67 by andrei — 2016-10-12T16:21:24Z
(In reply to thomas.bockman from comment #66) > Oops! That's the wrong DMD bug. This is the one I > actually meant: > https://issues.dlang.org/show_bug.cgi?id=14835 That makes sense. Thanks, and for the update as well.
Comment #68 by hsteoh — 2017-12-15T21:42:45Z
Whoa. This is approaching 12 years now, and even has a preapproved pull. When will the fix actually get merged?!
Comment #69 by manuelk89 — 2021-05-27T09:44:04Z
I hit this 15 years old bug again, and now feel very insecure about the correctness of all integer comparisons in all my code. At least a warning about signed-unsigned comparison would be great, doing the mathematically correct fast & safe integer promotions would be even better. Not sure how to deal with the slower comparisons like long-unsigned that have no direct assembly comparison instruction, maybe just produce an error message.
Comment #70 by b2.temp — 2021-05-27T09:50:25Z
The promotion trick works unless both operand are 64 bits (and considering that cent/ucent are not a thing yet) but that would be a progress. For long cmp ulong a warning could be emitted. This topic should make its come back on the news group. Even if the current behavior is layered on C semantics that does not mean that it could not change. Following C is not a sacrosant rule.
Comment #71 by thomas.bockman — 2021-05-27T22:30:33Z
(In reply to Manuel König from comment #69) > Not sure how to deal with the slower comparisons like long-unsigned > that have no direct assembly comparison instruction, maybe just > produce an error message. Speed is not the issue. The performance cost of a correct signed-unsigned comparison is almost nothing (less than the cost of a single branch mispredict, I believe), and in those very rare cases where the tiny speed boost of doing the comparison incorrectly is really worth it, it is trivial to cast one of the operands to the type of the other. The real problem is that the current strange behaviour of mixed comparisons is occasionally deliberately used in correct code. D must not be updated in a way that silently breaks all that pre-existing correct code. Hence, a warning (or at least, a *long* deprecation period) is the only good option for D2. Introducing such a warning without drowning people in false positives is surprisingly complicated, and is currently stalled waiting for a chain of compiler changes to be completed: https://github.com/dlang/dmd/pull/12311 https://github.com/dlang/dmd/pull/5229 https://github.com/dlang/dmd/pull/1913
Comment #72 by smjg — 2021-06-21T21:14:38Z
(In reply to thomas.bockman from comment #71) > The real problem is that the current strange behaviour of mixed comparisons > is occasionally deliberately used in correct code. D must not be updated in > a way that silently breaks all that pre-existing correct code. Hence, a > warning (or at least, a *long* deprecation period) is the only good option > for D2. I don't understand. How can code that the spec explicitly forbids possibly be correct?
Comment #73 by thomas.bockman — 2021-06-21T22:05:36Z
(In reply to Stewart Gordon from comment #72) > I don't understand. How can code that the spec explicitly forbids possibly > be correct? Normal programmers, who are not language lawyers, generally consider code to be correct if it reliably does what it is intended to do, in the way it is intended to do it. They do not know or care what the language spec says, and they assume the compiler can be trusted to enforce such simple rules as "mixed signed-unsigned comparisons are forbidden", if necessary. And, this is a perfectly rational approach, since both the compiler and the spec are complex, change over time, and sometimes contradict each other. Where there is a conflict, a programmer must satisfy the compiler in order to get work done, while the spec is important only in theory. Regardless of what the spec says, the de-facto semantics of the D language are that mixed signed-unsigned integer comparisons in D use integer promotion, like other mixed operations. That can certainly be changed, but the compiler needs to point out code that needs to be updated for the new semantics via warnings, deprecations, or errors. People need to be able to write code and then move on with their lives, and must not be forced to memorize the language spec and manually review all of the code they have ever written in D every time a new version of the compiler or spec comes out, to avoid silent breakage.
Comment #74 by robert.schadek — 2024-12-13T17:47:42Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/17504 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB