From b48a877814f665757b79ceeeba3fd1a014155f44 Mon Sep 17 00:00:00 2001 From: Inkrementator <70717315+Inkrementator@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:23:34 +0100 Subject: [PATCH] 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 --- std/range/package.d | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/std/range/package.d b/std/range/package.d index 3a135ebba..82580599a 100644 --- a/std/range/package.d +++ b/std/range/package.d @@ -1042,7 +1042,7 @@ if (Ranges.length > 0 && // We do this separately to avoid invoking `empty` needlessly. // While not recommended, a range may depend on side effects of // `empty` call. - foreach (i, ref v; input) if (!v.empty) + foreach (i, ref v; source) if (!v.empty) { frontIndex = i; static if (bidirectional) backIndex = i+1; @@ -1056,7 +1056,7 @@ if (Ranges.length > 0 && static foreach_reverse (i; 1 .. R.length + 1) { if (i <= frontIndex + 1) return; - if (!input[i-1].empty) + if (!source[i-1].empty) { backIndex = i; return; @@ -11019,6 +11019,23 @@ auto only()() static assert(!__traits(compiles, () { r3[0] = 789; })); } +// https://github.com/dlang/phobos/issues/10561 +@safe unittest +{ + static struct Range + { + private int i; + + enum bool empty = false; + int front() => i; + void popFront() { ++i; } + } + import std.algorithm; + + assert(Range().take(10).filter!"a>8".chain(only(100)).equal([9,100])); + assert((new Range()).take(10).filter!"a>8".chain(only(100)).equal([9,100])); +} + /** Iterate over `range` with an attached index variable.