Bug 14485 – .front of empty filtered zip range is accessible
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P1
Component
phobos
Product
D
Version
D2
Platform
All
OS
All
Creation time
2015-04-24T02:05:00Z
Last change time
2016-10-01T11:45:55Z
Assigned to
nobody
Creator
dlang-bugzilla
Comments
Comment #0 by dlang-bugzilla — 2015-04-24T02:05:56Z
This program should throw, but doesn't:
////////////////////////// test.d /////////////////////////
import std.algorithm;
import std.range;
import std.stdio;
void main()
{
writeln(zip(5.iota, 5.iota).filter!(n => false).front);
}
///////////////////////////////////////////////////////////
Comment #1 by issues.dlang — 2015-04-24T03:50:10Z
It should probably throw a RangeError so the problem is more easily caught, but technically, calling front on an empty range is undefined behavior. There's no requirement or guarantee that calling front on an empty range throw.
Comment #2 by dlang-bugzilla — 2015-04-24T03:53:18Z
The suspicious combination of things needed to reproduce this bug makes it suspicious, though my test case might be reducible a bit.
But even if what you say is true, is there a function in Phobos which checks "empty", returns "front" if false, and throws if true?
Comment #3 by issues.dlang — 2015-04-24T05:03:39Z
(In reply to Vladimir Panteleev from comment #2)
> The suspicious combination of things needed to reproduce this bug makes it
> suspicious, though my test case might be reducible a bit.
Hmmm. Well, the fact that the code doesn't throw or blow up isn't particularly suspicious. It's quite possible that it would just return the last value for front for an inner range. However, digging through the code, the code for zip is a bit weird. filter doesn't do any checks in front, and iota asserts that the range is non-empty in its front, but zip has a function which seems to return the init value of its element type if the range is empty, which seems quite weird, but it probably has to do with how zip handles ranges of differing lengths (I don't use zip much, so I don't remember how it's supposed to handle that).
So, exactly what zip is doing when you call front when it's empty seems a bit weird, but it's a bug whenever you do that, so I'm not sure that zip is really doing anything wrong (though arguably it would be better if it threw a RangeError just so that the bug gets caught).
> But even if what you say is true, is there a function in Phobos which checks
> "empty", returns "front" if false, and throws if true?
Not that I'm aware of. Some ranges assert in front and popFront that the range isn't empty, others throw a RangeError, and others don't check at all - though I think that this idiom has been increasingly used:
version(assert) if (empty) throw new RangeError();
so that it's halfway between asserting and just throwing a RangeError.
Code is supposed to check empty before it calls front or popFront; and IIRC, one of the last newsgroup discussions on that concluded that in the general case, you _had_ to call empty before calling front or popFront even if you knew that the range was non-empty, because some ranges do work in empty. :|
So, your code is clearly buggy in that it's calling front without having called empty first, and while how the ranges in question handle that isn't necessarily as clean as it should be, it's not technically wrong either.
And since it's normally considered a logic bug in a program if it doesn't check empty before calling front or popFront on a range, I'm not sure how much sense it makes to have a function which takes a range, checks empty, and then throws if it's true and returns front if it's not.
Comment #4 by dlang-bugzilla — 2015-04-24T05:11:38Z
(In reply to Jonathan M Davis from comment #3)
> And since it's normally considered a logic bug in a program if it doesn't
> check empty before calling front or popFront on a range, I'm not sure how
> much sense it makes to have a function which takes a range, checks empty,
> and then throws if it's true and returns front if it's not.
Use your imagination :)
Here's my code:
string findProduct(string name)
{
return
("http://www.tp-link.com/en/search/?q=" ~ encodeComponent(name))
.download
.readText
.toXML
.xmlParse
.I!(doc => zip(
doc
.findAll("#productResult > li > dl > dt > a")
.map!(e => e.attributes["href"])
,
doc
.findAll("#productResult > li > dl > dt > a")
.map!(e => e.children[$-1].text.strip)
))
.filter!(t => t[1] == name)
.front[0]
.I!(s => "http://www.tp-link.com" ~ s)
;
}
As you can see, stopping to check .empty in the middle of this UFCS chain would be quite inconvenient. Thus, there should definitely be a "safeFront" function OSLT.
Comment #5 by github-bugzilla — 2016-06-29T22:02:58Z