Skip to content

Commit b48a877

Browse files
Fix chain potentially mutating a stale object (#10643)
The constructor of the result of std.range.chain copies it's input tuple and then has to check which of the input-ranges in the first non-empty one. The member function empty is not guaranteed to be const, filter is probably the most prominent function that has to be "primed". On the first call to empty(), filter will try to advance all it's input ranges to the first element that matches the predicate. For ranges with full value semantics, using the old input object instead of the new internal buffer means that the work of priming is done twice. If any ranges have reference semantics, the references gets advances, but the value-semantic parts of the range-composition gets reset. This fixes #10561 and #9877
1 parent f52a44d commit b48a877

File tree

1 file changed

+19
-2
lines changed

1 file changed

+19
-2
lines changed

std/range/package.d

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ if (Ranges.length > 0 &&
10421042
// We do this separately to avoid invoking `empty` needlessly.
10431043
// While not recommended, a range may depend on side effects of
10441044
// `empty` call.
1045-
foreach (i, ref v; input) if (!v.empty)
1045+
foreach (i, ref v; source) if (!v.empty)
10461046
{
10471047
frontIndex = i;
10481048
static if (bidirectional) backIndex = i+1;
@@ -1056,7 +1056,7 @@ if (Ranges.length > 0 &&
10561056
static foreach_reverse (i; 1 .. R.length + 1)
10571057
{
10581058
if (i <= frontIndex + 1) return;
1059-
if (!input[i-1].empty)
1059+
if (!source[i-1].empty)
10601060
{
10611061
backIndex = i;
10621062
return;
@@ -11019,6 +11019,23 @@ auto only()()
1101911019
static assert(!__traits(compiles, () { r3[0] = 789; }));
1102011020
}
1102111021

11022+
// https://github.com/dlang/phobos/issues/10561
11023+
@safe unittest
11024+
{
11025+
static struct Range
11026+
{
11027+
private int i;
11028+
11029+
enum bool empty = false;
11030+
int front() => i;
11031+
void popFront() { ++i; }
11032+
}
11033+
import std.algorithm;
11034+
11035+
assert(Range().take(10).filter!"a>8".chain(only(100)).equal([9,100]));
11036+
assert((new Range()).take(10).filter!"a>8".chain(only(100)).equal([9,100]));
11037+
}
11038+
1102211039
/**
1102311040
Iterate over `range` with an attached index variable.
1102411041

0 commit comments

Comments
 (0)