From 4b61e7dd55200372a3b7e9159a52e378b3ee0e3c Mon Sep 17 00:00:00 2001 From: Bastiaan Veelo Date: Mon, 22 Jul 2024 00:47:38 +0200 Subject: [PATCH 01/10] Fix sharedLog assignment. (#8995) * Allow `new shared FileLogger()` * Address review comment * Update documentation. Fix Bugzilla 23487 - std.experimental.logger assigning FileLogger to sharedLog no longer works * Comment on `trusted` section. --------- Co-authored-by: Atila Neves --- std/logger/core.d | 4 ++-- std/logger/filelogger.d | 16 +++++++++++++--- std/logger/package.d | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/std/logger/core.d b/std/logger/core.d index 0633bddee..4081a5bdd 100644 --- a/std/logger/core.d +++ b/std/logger/core.d @@ -1433,7 +1433,7 @@ logger by the user, the default logger's log level is LogLevel.info. Example: ------------- -sharedLog = new FileLogger(yourFile); +sharedLog = new shared FileLogger(yourFile); ------------- The example sets a new `FileLogger` as new `sharedLog`. @@ -1450,7 +1450,7 @@ writing `sharedLog`. The default `Logger` is thread-safe. ------------- if (sharedLog !is myLogger) - sharedLog = new myLogger; + sharedLog = new shared myLogger; ------------- */ @property shared(Logger) sharedLog() @safe diff --git a/std/logger/filelogger.d b/std/logger/filelogger.d index c662ca74e..5ba167c7b 100644 --- a/std/logger/filelogger.d +++ b/std/logger/filelogger.d @@ -37,7 +37,7 @@ class FileLogger : Logger auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes); ------------- */ - this(const string fn, const LogLevel lv = LogLevel.all) @safe + this(this This)(const string fn, const LogLevel lv = LogLevel.all) { this(fn, lv, CreateFolder.yes); } @@ -63,7 +63,7 @@ class FileLogger : Logger auto l2 = new FileLogger(file, LogLevel.fatal); ------------- */ - this(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe + this(this This)(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) { import std.file : exists, mkdirRecurse; import std.path : dirName; @@ -80,7 +80,8 @@ class FileLogger : Logger " created in '", d,"' could not be created.")); } - this.file_.open(this.filename, "a"); + // Cast away `shared` when the constructor is inferred shared. + () @trusted { (cast() this.file_).open(this.filename, "a"); }(); } /** A constructor for the `FileLogger` Logger that takes a reference to @@ -270,3 +271,12 @@ class FileLogger : Logger assert(tl !is null); stdThreadLocalLog.logLevel = LogLevel.all; } + +@safe unittest +{ + // we don't need to actually run the code, only make sure + // it compiles + static _() { + auto l = new shared FileLogger(""); + } +} diff --git a/std/logger/package.d b/std/logger/package.d index 14a439486..215ca20b8 100644 --- a/std/logger/package.d +++ b/std/logger/package.d @@ -64,7 +64,7 @@ using the property called `sharedLog`. This property is a reference to the current default `Logger`. This reference can be used to assign a new default `Logger`. ------------- -sharedLog = new FileLogger("New_Default_Log_File.log"); +sharedLog = new shared FileLogger("New_Default_Log_File.log"); ------------- Additional `Logger` can be created by creating a new instance of the From 9c11552ac28e2e291bd81bc2dad97cb34c558290 Mon Sep 17 00:00:00 2001 From: Luis Ferreira Date: Fri, 26 Jul 2024 20:21:37 +0100 Subject: [PATCH 02/10] Fix issue 24686: SumType stopped working with unmatched DeducedParameterType template --- std/traits.d | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/std/traits.d b/std/traits.d index 5ed37a1f4..957a8f39f 100644 --- a/std/traits.d +++ b/std/traits.d @@ -9236,12 +9236,16 @@ enum isCopyable(S) = __traits(isCopyable, S); * is the same as `T`. For pointer and slice types, it is `T` with the * outer-most layer of qualifiers dropped. */ -package(std) template DeducedParameterType(T) +package(std) alias DeducedParameterType(T) = DeducedParameterTypeImpl!T; +/// ditto +package(std) alias DeducedParameterType(alias T) = DeducedParameterTypeImpl!T; + +private template DeducedParameterTypeImpl(T) { static if (is(T == U*, U) || is(T == U[], U)) - alias DeducedParameterType = Unqual!T; + alias DeducedParameterTypeImpl = Unqual!T; else - alias DeducedParameterType = T; + alias DeducedParameterTypeImpl = T; } @safe unittest @@ -9261,6 +9265,7 @@ package(std) template DeducedParameterType(T) } static assert(is(DeducedParameterType!NoCopy == NoCopy)); + static assert(is(DeducedParameterType!(inout(NoCopy)) == inout(NoCopy))); } @safe unittest From cd026126dbb0537cd98a6b78f8c6b726ecf75be2 Mon Sep 17 00:00:00 2001 From: RubyTheRoobster <71991501+RubyTheRoobster@users.noreply.github.com> Date: Mon, 19 Aug 2024 12:05:36 +0000 Subject: [PATCH 03/10] =?UTF-8?q?Issue=2024095=20-=20std.bitmanip.bitfield?= =?UTF-8?q?s=20no=20longer=20works=20with=20bool=20enum=20t=E2=80=A6=20(#9?= =?UTF-8?q?043)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix Bugzilla Issue 24095 - std.bitmanip.bitfields no longer works with bool enum types * Fix trailing whitespace * Remove extra space --- std/bitmanip.d | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/std/bitmanip.d b/std/bitmanip.d index 639b8214c..f8fa3685f 100644 --- a/std/bitmanip.d +++ b/std/bitmanip.d @@ -106,7 +106,7 @@ private template createAccessors( enum RightShiftOp = ">>>="; } - static if (is(T == bool)) + static if (is(T : bool)) { enum createAccessors = // getter @@ -4724,3 +4724,24 @@ if (isIntegral!T) foreach (i; 0 .. 63) assert(bitsSet(1UL << i).equal([i])); } + +// Fix https://issues.dlang.org/show_bug.cgi?id=24095 +@safe @nogc pure unittest +{ + enum Bar : bool + { + a, + b, + } + + struct Foo + { + mixin(bitfields!(Bar, "bar", 1, ubyte, "", 7,)); + } + + Foo foo; + foo.bar = Bar.a; + assert(foo.bar == Bar.a); + foo.bar = Bar.b; + assert(foo.bar == Bar.b); +} From 10076badd83324c84931cf599a14059f3a298694 Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Tue, 22 Oct 2024 03:10:24 -0400 Subject: [PATCH 04/10] Fix Bugzilla Issue 24824 - ensure we exit a child that does not succeed in exec. (#9065) --- std/process.d | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/std/process.d b/std/process.d index d1783742e..bd98e7312 100644 --- a/std/process.d +++ b/std/process.d @@ -4525,11 +4525,12 @@ else version (Posix) if (childpid == 0) { // Trusted because args and all entries are always zero-terminated - (() @trusted => - core.sys.posix.unistd.execvp(args[0], &args[0]) || - perror(args[0]) // failed to execute - )(); - return; + (() @trusted { + core.sys.posix.unistd.execvp(args[0], &args[0]); + perror(args[0]); + core.sys.posix.unistd._exit(1); + })(); + assert(0, "Child failed to exec"); } if (browser) // Trusted because it's allocated via strdup above From f0c3e4a66b68d766c7601d9571aa1bf65a5c371e Mon Sep 17 00:00:00 2001 From: Jonathan M Davis Date: Sun, 27 Oct 2024 02:16:22 -0600 Subject: [PATCH 05/10] Fix Bugzilla issue 24827: maxElement does not handle opAssign correctly. (#9067) Rebindable2 did not handle types with opAssign correctly, which affected both minElement and maxElement. Namely, Rebindable2 assigned to memory which was not properly initialized when the correct solution in such a situation is to use copyEmplace. Assignment works when assignment is just a memcpy, but in the general case, opAssign needs to have a properly initialized object in order to work correctly. copyEmplace instead copies the object and then places the copy into the unitialized memory, so it avoids assigning to uninitialized memory. This commit also adds additional tests for types with destructors (which do get opAssign automatically) and types with postblit constructors or copy constructors to try to ensure that the code is doing the correct thing in those cases with regards to copying, assignment, and destruction. https://issues.dlang.org/show_bug.cgi?id=24829 was found in the process, and this does not fix that. Namely, types which cannot be assigned to and which also have a postblit constructor or copy constructor do not get copied correctly. So, among the tests added here are commented out tests for that case, since they're an altered version of some of the enabled tests. However, fixing that issue would be involved enough that I'm not attempting to fix it at this time. --- std/algorithm/searching.d | 82 +++++++++ std/typecons.d | 345 +++++++++++++++++++++++++++++++++++++- 2 files changed, 419 insertions(+), 8 deletions(-) diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index 42a9df518..b7119d24b 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -3735,6 +3735,47 @@ if (isInputRange!Range && !isInfinite!Range && assert(arr.minElement!"a.val".val == 0); } +// https://issues.dlang.org/show_bug.cgi?id=24827 +@safe unittest +{ + static struct S + { + int i; + bool destroyed; + + this(int i) @safe + { + this.i = i; + } + + ~this() @safe + { + destroyed = true; + } + + bool opEquals()(auto ref S rhs) + { + return this.i == rhs.i; + } + + int opCmp()(auto ref S rhs) + { + if (this.i < rhs.i) + return -1; + + return this.i == rhs.i ? 0 : 1; + } + + @safe invariant + { + assert(!destroyed); + } + } + + auto arr = [S(19), S(2), S(145), S(7)]; + assert(minElement(arr) == S(2)); +} + /** Iterates the passed range and returns the maximal element. A custom mapping function can be passed to `map`. @@ -3888,6 +3929,47 @@ if (isInputRange!Range && !isInfinite!Range && assert(arr[0].getI == 2); } +// https://issues.dlang.org/show_bug.cgi?id=24827 +@safe unittest +{ + static struct S + { + int i; + bool destroyed; + + this(int i) @safe + { + this.i = i; + } + + ~this() @safe + { + destroyed = true; + } + + bool opEquals()(auto ref S rhs) + { + return this.i == rhs.i; + } + + int opCmp()(auto ref S rhs) + { + if (this.i < rhs.i) + return -1; + + return this.i == rhs.i ? 0 : 1; + } + + @safe invariant + { + assert(!destroyed); + } + } + + auto arr = [S(19), S(2), S(145), S(7)]; + assert(maxElement(arr) == S(145)); +} + // minPos /** Computes a subrange of `range` starting at the first occurrence of `range`'s diff --git a/std/typecons.d b/std/typecons.d index 3c425c7d7..368a5e5f5 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -3102,17 +3102,18 @@ private: { static if (useQualifierCast) { - this.data = cast() value; + static if (hasElaborateAssign!T) + { + import core.lifetime : copyEmplace; + copyEmplace(cast() value, this.data); + } + else + this.data = cast() value; } else { - // As we're escaping a copy of `value`, deliberately leak a copy: - static union DontCallDestructor - { - T value; - } - DontCallDestructor copy = DontCallDestructor(value); - this.data = *cast(Payload*) © + import core.lifetime : copyEmplace; + copyEmplace(cast() value, cast() *cast(T*) &this.data); } } @@ -3137,6 +3138,334 @@ package(std) Rebindable2!T rebindable2(T)(T value) return Rebindable2!T(value); } +// Verify that the destructor is called properly if there is one. +@system unittest +{ + { + bool destroyed; + + struct S + { + int i; + + this(int i) @safe + { + this.i = i; + } + + ~this() @safe + { + destroyed = true; + } + } + + { + auto foo = rebindable2(S(42)); + + // Whether destruction has occurred here depends on whether the + // temporary gets moved or not, so we won't assume that it has or + // hasn't happened. What we care about here is that foo gets destroyed + // properly when it leaves the scope. + destroyed = false; + } + assert(destroyed); + + { + auto foo = rebindable2(const S(42)); + destroyed = false; + } + assert(destroyed); + } + + // Test for double destruction with qualifer cast being used + { + static struct S + { + int i; + bool destroyed; + + this(int i) @safe + { + this.i = i; + } + + ~this() @safe + { + destroyed = true; + } + + @safe invariant + { + assert(!destroyed); + } + } + + { + auto foo = rebindable2(S(42)); + assert(typeof(foo).useQualifierCast); + assert(foo.data.i == 42); + assert(!foo.data.destroyed); + } + { + auto foo = rebindable2(S(42)); + destroy(foo); + } + { + auto foo = rebindable2(const S(42)); + assert(typeof(foo).useQualifierCast); + assert(foo.data.i == 42); + assert(!foo.data.destroyed); + } + { + auto foo = rebindable2(const S(42)); + destroy(foo); + } + } + + // Test for double destruction without qualifer cast being used + { + static struct S + { + int i; + bool destroyed; + + this(int i) @safe + { + this.i = i; + } + + ~this() @safe + { + destroyed = true; + } + + @disable ref S opAssign()(auto ref S rhs); + + @safe invariant + { + assert(!destroyed); + } + } + + { + auto foo = rebindable2(S(42)); + assert(!typeof(foo).useQualifierCast); + assert((cast(S*)&(foo.data)).i == 42); + assert(!(cast(S*)&(foo.data)).destroyed); + } + { + auto foo = rebindable2(S(42)); + destroy(foo); + } + } +} + +// Verify that if there is an overloaded assignment operator, it's not assigned +// to garbage. +@safe unittest +{ + static struct S + { + int i; + bool destroyed; + + this(int i) @safe + { + this.i = i; + } + + ~this() @safe + { + destroyed = true; + } + + ref opAssign()(auto ref S rhs) + { + assert(!this.destroyed); + this.i = rhs.i; + return this; + } + } + + { + auto foo = rebindable2(S(42)); + foo = S(99); + assert(foo.data.i == 99); + } + { + auto foo = rebindable2(S(42)); + foo = const S(99); + assert(foo.data.i == 99); + } +} + +// Verify that postblit or copy constructor is called properly if there is one. +@system unittest +{ + // postblit with type qualifier cast + { + static struct S + { + int i; + static bool copied; + + this(this) @safe + { + copied = true; + } + } + + { + auto foo = rebindable2(S(42)); + + // Whether a copy has occurred here depends on whether the + // temporary gets moved or not, so we won't assume that it has or + // hasn't happened. What we care about here is that foo gets copied + // properly when we copy it below. + S.copied = false; + + auto bar = foo; + assert(S.copied); + } + { + auto foo = rebindable2(const S(42)); + assert(typeof(foo).useQualifierCast); + S.copied = false; + + auto bar = foo; + assert(S.copied); + } + } + + // copy constructor with type qualifier cast + { + static struct S + { + int i; + static bool copied; + + this(ref inout S rhs) @safe inout + { + this.i = i; + copied = true; + } + } + + { + auto foo = rebindable2(S(42)); + assert(typeof(foo).useQualifierCast); + S.copied = false; + + auto bar = foo; + assert(S.copied); + } + { + auto foo = rebindable2(const S(42)); + S.copied = false; + + auto bar = foo; + assert(S.copied); + } + } + + // FIXME https://issues.dlang.org/show_bug.cgi?id=24829 + + // Making this work requires either reworking how the !useQualiferCast + // version works so that the compiler can correctly generate postblit + // constructors and copy constructors as appropriate, or an explicit + // postblit or copy constructor needs to be added for such cases, which + // gets pretty complicated if we want to correctly add the same attributes + // that T's postblit or copy constructor has. + + /+ + // postblit without type qualifier cast + { + static struct S + { + int* ptr; + static bool copied; + + this(int i) + { + ptr = new int(i); + } + + this(this) @safe + { + if (ptr !is null) + ptr = new int(*ptr); + copied = true; + } + + @disable ref S opAssign()(auto ref S rhs); + } + + { + auto foo = rebindable2(S(42)); + assert(!typeof(foo).useQualifierCast); + S.copied = false; + + auto bar = foo; + assert(S.copied); + assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); + assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); + } + { + auto foo = rebindable2(const S(42)); + S.copied = false; + + auto bar = foo; + assert(S.copied); + assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); + assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); + } + } + + // copy constructor without type qualifier cast + { + static struct S + { + int* ptr; + static bool copied; + + this(int i) + { + ptr = new int(i); + } + + this(ref inout S rhs) @safe inout + { + if (rhs.ptr !is null) + ptr = new inout int(*rhs.ptr); + copied = true; + } + + @disable ref S opAssign()(auto ref S rhs); + } + + { + auto foo = rebindable2(S(42)); + assert(!typeof(foo).useQualifierCast); + S.copied = false; + + auto bar = foo; + assert(S.copied); + assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); + assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); + } + { + auto foo = rebindable2(const S(42)); + S.copied = false; + + auto bar = foo; + assert(S.copied); + assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); + assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); + } + } + +/ +} + /** Similar to `Rebindable!(T)` but strips all qualifiers from the reference as opposed to just constness / immutability. Primary intended use case is with From b7743abfcf148850186461e2f968ba40658d1b6b Mon Sep 17 00:00:00 2001 From: Nicholas Wilson Date: Sun, 27 Oct 2024 17:18:41 +0800 Subject: [PATCH 06/10] Fix bugzilla issue 24637 (#9073) --- std/container/dlist.d | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/std/container/dlist.d b/std/container/dlist.d index 4fdf13d48..9c37a8578 100644 --- a/std/container/dlist.d +++ b/std/container/dlist.d @@ -185,6 +185,7 @@ Implements a doubly-linked list. struct DList(T) { import std.range : Take; + import std.traits : isMutable; /* A Node with a Payload. A PayNode. @@ -220,7 +221,10 @@ struct DList(T) { import std.algorithm.mutation : move; - return (new PayNode(BaseNode(prev, next), move(arg))).asBaseNode(); + static if (isMutable!Stuff) + return (new PayNode(BaseNode(prev, next), move(arg))).asBaseNode(); + else + return (new PayNode(BaseNode(prev, next), arg)).asBaseNode(); } void initialize() nothrow @safe pure @@ -1148,3 +1152,22 @@ private: list.removeFront(); assert(list[].walkLength == 0); } + +// https://issues.dlang.org/show_bug.cgi?id=24637 +@safe unittest +{ + import std.algorithm.comparison : equal; + + struct A + { + int c; + } + + DList!A B; + B.insert(A(1)); + assert(B[].equal([A(1)])); + + const a = A(3); + B.insert(a); + assert(B[].equal([A(1), A(3)])); +} From 9e78de4d86ec1b61cc39ed90083373bfa111ccdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Fri, 20 Sep 2024 11:07:22 +0200 Subject: [PATCH 07/10] Fix Bugzilla 24773: Don't invoke destructors on uninitialized elements in stable sort Uses a regular initialized temporary array when sorting elements with an elaborate assignment to avoid undefined behavior when destructors, postblits or copy constructors are invoked during the array assignment. --- std/algorithm/sorting.d | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/std/algorithm/sorting.d b/std/algorithm/sorting.d index c5b085d10..3381194e8 100644 --- a/std/algorithm/sorting.d +++ b/std/algorithm/sorting.d @@ -2385,7 +2385,11 @@ private template TimSortImpl(alias pred, R) size_t stackLen = 0; // Allocate temporary memory if not provided by user - if (temp.length < minTemp) temp = () @trusted { return uninitializedArray!(T[])(minTemp); }(); + if (temp.length < minTemp) + { + static if (hasElaborateAssign!T) temp = new T[](minTemp); + else temp = () @trusted { return uninitializedArray!(T[])(minTemp); }(); + } for (size_t i = 0; i < range.length; ) { @@ -3076,6 +3080,20 @@ private template TimSortImpl(alias pred, R) array.sort!("a < b", SwapStrategy.stable); } +// https://issues.dlang.org/show_bug.cgi?id=24773 +@safe unittest +{ + static struct S + { + int i = 42; + ~this() { assert(i == 42); } + } + + auto array = new S[](400); + array.sort!((a, b) => false, SwapStrategy.stable); +} + + // schwartzSort /** Alternative sorting method that should be used when comparing keys involves an From ef6a991534e085d82eebc65a9b09af68c05b0906 Mon Sep 17 00:00:00 2001 From: Jonathan M Davis Date: Thu, 17 Oct 2024 21:00:02 -0600 Subject: [PATCH 08/10] Fix bugzilla issue 24809: In some cases, stable sort assigns to unininitialized elements (#9057) --- std/algorithm/sorting.d | 73 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/std/algorithm/sorting.d b/std/algorithm/sorting.d index 3381194e8..09f02e0e9 100644 --- a/std/algorithm/sorting.d +++ b/std/algorithm/sorting.d @@ -2625,11 +2625,21 @@ private template TimSortImpl(alias pred, R) // can't use `temp.length` if there's no default constructor static if (__traits(compiles, { T defaultConstructed; cast(void) defaultConstructed; })) { - if (__ctfe) temp.length = newSize; - else temp = () @trusted { return uninitializedArray!(T[])(newSize); }(); + + static if (hasElaborateAssign!T) + temp.length = newSize; + else + { + if (__ctfe) temp.length = newSize; + else temp = () @trusted { return uninitializedArray!(T[])(newSize); }(); + } } else { + static assert(!hasElaborateAssign!T, + "Structs which have opAssign but cannot be default-initialized " ~ + "do not currently work with stable sort: " ~ + "https://issues.dlang.org/show_bug.cgi?id=24810"); temp = () @trusted { return uninitializedArray!(T[])(newSize); }(); } } @@ -3093,6 +3103,65 @@ private template TimSortImpl(alias pred, R) array.sort!((a, b) => false, SwapStrategy.stable); } +// https://issues.dlang.org/show_bug.cgi?id=24809 +@safe unittest +{ + static struct E + { + int value; + int valid = 42; + + ~this() + { + assert(valid == 42); + } + } + + import std.array : array; + import std.range : chain, only, repeat; + auto arr = chain(repeat(E(41), 18), + only(E(39)), + repeat(E(41), 16), + only(E(1)), + repeat(E(42), 33), + only(E(33)), + repeat(E(42), 16), + repeat(E(43), 27), + only(E(33)), + repeat(E(43), 34), + only(E(34)), + only(E(43)), + only(E(63)), + repeat(E(44), 42), + only(E(27)), + repeat(E(44), 11), + repeat(E(45), 64), + repeat(E(46), 3), + only(E(11)), + repeat(E(46), 7), + only(E(4)), + repeat(E(46), 34), + only(E(36)), + repeat(E(46), 17), + repeat(E(47), 36), + only(E(39)), + repeat(E(47), 26), + repeat(E(48), 17), + only(E(21)), + repeat(E(48), 5), + only(E(39)), + repeat(E(48), 14), + only(E(58)), + repeat(E(48), 24), + repeat(E(49), 13), + only(E(40)), + repeat(E(49), 38), + only(E(18)), + repeat(E(49), 11), + repeat(E(50), 6)).array(); + + arr.sort!((a, b) => a.value < b.value, SwapStrategy.stable)(); +} // schwartzSort /** From 567b3758de9bdf6f6f559968136d37619bd324ca Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Thu, 14 Nov 2024 18:59:01 -0500 Subject: [PATCH 09/10] Fix appender code that might not initialize memory that might point at huge allocations (#9084) --- std/array.d | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/std/array.d b/std/array.d index 494fa297b..0e83f1992 100644 --- a/std/array.d +++ b/std/array.d @@ -3607,6 +3607,7 @@ if (isDynamicArray!A) } else { + import core.stdc.string : memcpy, memset; // Time to reallocate. // We need to almost duplicate what's in druntime, except we // have better access to the capacity field. @@ -3618,6 +3619,15 @@ if (isDynamicArray!A) if (u) { // extend worked, update the capacity + // if the type has indirections, we need to zero any new + // data that we requested, as the existing data may point + // at large unused blocks. + static if (hasIndirections!T) + { + immutable addedSize = u - (_data.capacity * T.sizeof); + () @trusted { memset(_data.arr.ptr + _data.capacity, 0, addedSize); }(); + } + _data.capacity = u / T.sizeof; return; } @@ -3633,10 +3643,20 @@ if (isDynamicArray!A) auto bi = (() @trusted => GC.qalloc(nbytes, blockAttribute!T))(); _data.capacity = bi.size / T.sizeof; - import core.stdc.string : memcpy; if (len) () @trusted { memcpy(bi.base, _data.arr.ptr, len * T.sizeof); }(); + _data.arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])(); + + // we requested new bytes that are not in the existing + // data. If T has pointers, then this new data could point at stale + // objects from the last time this block was allocated. Zero that + // new data out, it may point at large unused blocks! + static if (hasIndirections!T) + () @trusted { + memset(bi.base + (len * T.sizeof), 0, (newlen - len) * T.sizeof); + }(); + _data.tryExtendBlock = true; // leave the old data, for safety reasons } @@ -4011,6 +4031,43 @@ if (isDynamicArray!A) app2.toString(); } +// https://issues.dlang.org/show_bug.cgi?id=24856 +@system unittest +{ + import core.memory : GC; + import std.stdio : writeln; + import std.algorithm.searching : canFind; + GC.disable(); + scope(exit) GC.enable(); + void*[] freeme; + // generate some poison blocks to allocate from. + auto poison = cast(void*) 0xdeadbeef; + foreach (i; 0 .. 10) + { + auto blk = new void*[7]; + blk[] = poison; + freeme ~= blk.ptr; + } + + foreach (p; freeme) + GC.free(p); + + int tests = 0; + foreach (i; 0 .. 10) + { + Appender!(void*[]) app; + app.put(null); + // if not a realloc of one of the deadbeef pointers, continue + if (!freeme.canFind(app.data.ptr)) + continue; + ++tests; + assert(!app.data.ptr[0 .. app.capacity].canFind(poison), "Appender not zeroing data!"); + } + // just notify in the log whether this test actually could be done. + if (tests == 0) + writeln("WARNING: test of Appender zeroing did not occur"); +} + //Calculates an efficient growth scheme based on the old capacity //of data, and the minimum requested capacity. //arg curLen: The current length From bf6e70b1048f12e99d1690ce443e636edf01653e Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Sun, 17 Nov 2024 01:08:31 +0100 Subject: [PATCH 10/10] GitHub Actions: Remove macos-12 job, as the image will disappear soon (#9085) --- .github/workflows/main.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed27c241e..7716d6de5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,8 +24,6 @@ jobs: # macOS - job_name: macOS 13 x64 os: macos-13 - - job_name: macOS 12 x64 - os: macos-12 # Windows - job_name: Windows x64 os: windows-2022 @@ -83,13 +81,6 @@ jobs: with: arch: ${{ env.MODEL == '64' && 'x64' || 'x86' }} - # NOTE: Linker ICEs with Xcode 15.0.1 (default version on macos-13) - # * https://issues.dlang.org/show_bug.cgi?id=24407 - # Remove this step if the default gets changed to 15.1 in actions/runner-images. - - name: 'macOS 13: Switch to Xcode v15.1' - if: matrix.os == 'macos-13' - run: sudo xcode-select -switch /Applications/Xcode_15.1.app - - name: 'Posix: Install host compiler' if: runner.os != 'Windows' run: cd ../dmd && ci/run.sh install_host_compiler