Skip to content

Commit 020062b

Browse files
committed
Fix Issue 24165 - Failed readf leaves File in inconsistent state
Previously, a failed call to readf resulted in multiple copies of the same LockingTextWriter being destroyed. Since LockingTextWriter's destructor calls ungetc on the last-read character, this caused that character to appear multiple times in subsequent reads from the File. This change ensures that the destructor in question is only run once by making LockingTextWriter a reference-counted type. Ideally, to avoid unnecessary overhead, this issue would have been fixed by making LockingTextWriter non-copyable. However, non-copyable ranges are poorly-supported by Phobos, and this approach would have required extensive changes to several other modules, including changes to the interfaces of some public symbols.
1 parent d945686 commit 020062b

File tree

1 file changed

+100
-53
lines changed

1 file changed

+100
-53
lines changed

std/stdio.d

Lines changed: 100 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3993,79 +3993,103 @@ enum LockType
39933993

39943994
struct LockingTextReader
39953995
{
3996-
private File _f;
3997-
private char _front;
3998-
private bool _hasChar;
3999-
4000-
this(File f)
3996+
private static struct Impl
40013997
{
4002-
import std.exception : enforce;
4003-
enforce(f.isOpen, "LockingTextReader: File must be open");
4004-
_f = f;
4005-
_FLOCK(_f._p.handle);
4006-
}
3998+
private File _f;
3999+
private char _front;
4000+
private bool _hasChar;
40074001

4008-
this(this)
4009-
{
4010-
_FLOCK(_f._p.handle);
4011-
}
4002+
this(File f)
4003+
{
4004+
import std.exception : enforce;
4005+
enforce(f.isOpen, "LockingTextReader: File must be open");
4006+
_f = f;
4007+
_FLOCK(_f._p.handle);
4008+
}
40124009

4013-
~this()
4014-
{
4015-
if (_hasChar)
4016-
ungetc(_front, cast(FILE*)_f._p.handle);
4010+
@disable this(this);
40174011

4018-
// File locking has its own reference count
4019-
if (_f.isOpen) _FUNLOCK(_f._p.handle);
4020-
}
4012+
~this()
4013+
{
4014+
if (_hasChar)
4015+
ungetc(_front, cast(FILE*)_f._p.handle);
40214016

4022-
void opAssign(LockingTextReader r)
4023-
{
4024-
import std.algorithm.mutation : swap;
4025-
swap(this, r);
4026-
}
4017+
// File locking has its own reference count
4018+
if (_f.isOpen) _FUNLOCK(_f._p.handle);
4019+
}
40274020

4028-
@property bool empty()
4029-
{
4030-
if (!_hasChar)
4021+
void opAssign(typeof(this) r)
4022+
{
4023+
import std.algorithm.mutation : swap;
4024+
swap(this, r);
4025+
}
4026+
4027+
@property bool empty()
40314028
{
4032-
if (!_f.isOpen || _f.eof)
4033-
return true;
4034-
immutable int c = _FGETC(cast(_iobuf*) _f._p.handle);
4035-
if (c == EOF)
4029+
if (!_hasChar)
40364030
{
4037-
.destroy(_f);
4038-
return true;
4031+
if (!_f.isOpen || _f.eof)
4032+
return true;
4033+
immutable int c = _FGETC(cast(_iobuf*) _f._p.handle);
4034+
if (c == EOF)
4035+
{
4036+
.destroy(_f);
4037+
return true;
4038+
}
4039+
_front = cast(char) c;
4040+
_hasChar = true;
40394041
}
4040-
_front = cast(char) c;
4041-
_hasChar = true;
4042+
return false;
40424043
}
4043-
return false;
4044-
}
40454044

4046-
@property char front()
4047-
{
4048-
if (!_hasChar)
4045+
@property char front()
40494046
{
4050-
version (assert)
4047+
if (!_hasChar)
40514048
{
4052-
import core.exception : RangeError;
4053-
if (empty)
4054-
throw new RangeError();
4049+
version (assert)
4050+
{
4051+
import core.exception : RangeError;
4052+
if (empty)
4053+
throw new RangeError();
4054+
}
4055+
else
4056+
{
4057+
empty;
4058+
}
40554059
}
4056-
else
4057-
{
4060+
return _front;
4061+
}
4062+
4063+
void popFront()
4064+
{
4065+
if (!_hasChar)
40584066
empty;
4059-
}
4067+
_hasChar = false;
40604068
}
4061-
return _front;
4069+
}
4070+
4071+
import std.typecons : SafeRefCounted, borrow;
4072+
4073+
private SafeRefCounted!Impl impl;
4074+
4075+
this(File f)
4076+
{
4077+
impl = SafeRefCounted!Impl(f);
4078+
}
4079+
4080+
@property bool empty()
4081+
{
4082+
return impl.borrow!((ref r) => r.empty);
4083+
}
4084+
4085+
@property char front()
4086+
{
4087+
return impl.borrow!((ref r) => r.front);
40624088
}
40634089

40644090
void popFront()
40654091
{
4066-
if (!_hasChar)
4067-
empty;
4068-
_hasChar = false;
4092+
impl.borrow!((ref r) { r.popFront; });
40694093
}
40704094
}
40714095

@@ -4143,6 +4167,29 @@ struct LockingTextReader
41434167
fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot);
41444168
}
41454169

4170+
// https://issues.dlang.org/show_bug.cgi?id=24165
4171+
@system unittest
4172+
{
4173+
// @system due to readf
4174+
static import std.file;
4175+
import std.algorithm.iteration : joiner, map;
4176+
import std.algorithm.searching : canFind;
4177+
import std.array : array;
4178+
import std.utf : byChar;
4179+
4180+
string content = "-hello";
4181+
auto deleteme = testFilename();
4182+
std.file.write(deleteme, content);
4183+
scope(exit) std.file.remove(deleteme);
4184+
File f = File(deleteme);
4185+
int n;
4186+
try f.readf("%d", n);
4187+
catch (Exception e) {}
4188+
// Data read must match what was written
4189+
char[] result = f.byLine(Yes.keepTerminator).map!byChar.joiner.array;
4190+
assert(content.canFind(result));
4191+
}
4192+
41464193
/**
41474194
* Indicates whether `T` is a file handle, i.e. the type
41484195
* is implicitly convertable to $(LREF File) or a pointer to a

0 commit comments

Comments
 (0)