Conversation
| if( _assertHandler is null ) | ||
| throw new AssertError( file, line ); | ||
| _assertHandler( file, line, null); | ||
| onAssertErrorMsg(file, line, null); |
There was a problem hiding this comment.
That should really be done on the compiler level one day
There was a problem hiding this comment.
@WalterBright : Having two function seems unnecessary, as the case of assert(cond) could just be treated as assert(cond, null). That's one less function between DMD and druntime, so less complication.
Also it's a common source of frustration that when debugging we need to break both on _d_assert and _d_assert_msg.
There was a problem hiding this comment.
yah, that's what was happening anyway (with duplicated code)
src/core/exception.d
Outdated
| // Overridable Callbacks | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| private C singleton(C)() if (is(C == class) && is(typeof(new C))) |
There was a problem hiding this comment.
Documentation and unittests please
There was a problem hiding this comment.
unittests are there... documentation not needed for private functions :)
|
Added support for destruction via |
|
,,, @mathias-lang-sociomantic and dox :) |
src/core/exception.d
Outdated
| assert(typeid(C).initializer.length == b.length * size_t.sizeof); | ||
| import core.stdc.string; | ||
| memcpy(b.ptr, typeid(C).init.ptr, b.length); | ||
| result.__ctor; |
There was a problem hiding this comment.
Doesn't that mean that the ctor can be @system and do bad stuff ? ™️
Ditto for the dtor.
When I read that, I immediately wondered about the potential race condition if multiple threads were to
Is it correct to refer to an object as a "singleton" when there's actually one per thread? It's very confusing, etymologically speaking. |
|
@tsbockman good point thx, changed |
src/core/exception.d
Outdated
|
|
||
| /* | ||
| Returns a perThreadInstance object of class type `C`, i.e. repeated calls to | ||
| `perThreadInstance!C` within a given thread return the same instance. For that reason, |
There was a problem hiding this comment.
This is not the standard Ddoc function documentation style.
There was a problem hiding this comment.
At this point the function is private so I just added a casual comment.
| result.__ctor; | ||
| import core.internal.traits; | ||
| static if (hasElaborateDestructor!C) | ||
| { |
There was a problem hiding this comment.
According to a quick test:
import std.traits, core.exception;
static assert(hasElaborateDestructor!AssertError);
// Error: static assert (hasElaborateDestructor!(AssertError)) is falseSo it would be nice to have this line covered. Otherwise, LGTM.
There was a problem hiding this comment.
Thanks. I think we can defer coverage of that to the time we get to use this private function elsewhere.
|
@mathias-lang-sociomantic added coverage |
|
I think other Error should be singleton too. |
| result.__ctor; | ||
| import core.internal.traits; | ||
| static if (hasElaborateDestructor!C) | ||
| { |
There was a problem hiding this comment.
hasElaborateDestructor!C == false if C is not a struct. That's why I proposed dlang/phobos#4119.
| static bool initialized = false; | ||
| auto result = () @trusted { return cast(C) b.ptr; }(); | ||
| if (!initialized) | ||
| { |
There was a problem hiding this comment.
Why not do an early exit:
if (initialized)
return result;
Is it just because you don't want to type return result; twice?
There was a problem hiding this comment.
I'm glad you're giving the same advice I often do :). I'd say when the if is short things are fine either way.
| Throws: If the default constructor of `C` throws. | ||
| */ | ||
| private C perThreadInstance(C)() if (is(C == class) && is(typeof(new C))) | ||
| { |
There was a problem hiding this comment.
There already is a staticError which covers pretty much the same use case. Perhaps you can reuse it / replace it / unify the implementations?
| static size_t[1 + (__traits(classInstanceSize, C) - 1) / size_t.sizeof] b; | ||
| static bool initialized = false; | ||
| auto result = () @trusted { return cast(C) b.ptr; }(); | ||
| if (!initialized) |
There was a problem hiding this comment.
Since we're druntime, I think it's worth going the extra mile (i.e. do the necessary safe-ty hacks - see line 760) in order to return immutable C or tag the function with pure. @nogc, pure and nothrow are orthogonal in the language and therefore I think it's a good idea to allow @nogc pure functions to throw staticErrors. (To me personally, throw staicError!RangeError sound better, than throw perThreadInstance!RangeError, but naming is not that important at this stage.)
| result.__ctor; | ||
| import core.internal.traits; | ||
| static if (hasElaborateDestructor!C) | ||
| { |
There was a problem hiding this comment.
hasElaborateDestructor!C always returns false if C is not a struct (even the one in druntime). That's why I proposed dlang/phobos#4119.
There was a problem hiding this comment.
Hm... I think just remove this static if. There is always a destructor for a class (i.e. rt_finalize2 will always call destructors, even for Object).
| static void destroy() | ||
| { | ||
| (cast(C) b.ptr).__dtor; | ||
| } |
There was a problem hiding this comment.
If you don't want to leverage object.destroy, you should at least use rt_finalize2, because that's what object.destroy does for classes.
The problem with calling obj.__dtor directly is that:
- It does a non-virtual call (see 1 and 2) to
C.~this(), without calling any ofC's base classes' dtors. - It does not destroy
C's__monitorfield. - It does not clear the memory, causing the GC to attempt destroy (this time "properly") the object again - see this (reproduces perfectly on newer compilers too).
There was a problem hiding this comment.
Agree, this was actually a problem with destroy before we fixed it.
| private C perThreadInstance(C)() if (is(C == class) && is(typeof(new C))) | ||
| { | ||
| static size_t[1 + (__traits(classInstanceSize, C) - 1) / size_t.sizeof] b; | ||
| static bool initialized = false; |
There was a problem hiding this comment.
It would have been simpler to do ubyte[__traits(classInstanceSize, C] b. Why do you want to round up to a multiple of size_t
There was a problem hiding this comment.
To be sufficiently paranoid, better add a check for the instance alignment not being larger than size_t then (SIMD vectors, …).
| () @trusted | ||
| { | ||
| memcpy(b.ptr, typeid(C).initializer.ptr, b.length); | ||
| }(); |
There was a problem hiding this comment.
In theory the following could be more efficient auto ci = typeid(C); b[0 .. ci.initializer.length] = ci.initializer[] (assuming b is of type ubyte[]), because the compiler knows at compile time typeid(C).initializer.length and can make a better decision about the codegen (e.g. potentially inline the memcpy for smaller types).
There was a problem hiding this comment.
this code will be executed once per run really
| }(); | ||
| static if (is(typeof(result.__ctor()))) | ||
| result.__ctor; | ||
| import core.internal.traits; |
There was a problem hiding this comment.
You may need to make sure that the BlkAttr for this piece of memory has the BlkAttr.FINALIZE bit unset, otherwise the GC may attempt to call the dtors again at the end of the program.
There was a problem hiding this comment.
Hmmm... this is not GC-allocated memory so it does not have block attributes. No?
MartinNowak
left a comment
There was a problem hiding this comment.
Please use/adopt staticError, it was build for that purpose.
schveiguy
left a comment
There was a problem hiding this comment.
Looks good. Just need some tweaks to destruction, then it should be good to go.
| }(); | ||
| static if (is(typeof(result.__ctor()))) | ||
| result.__ctor; | ||
| import core.internal.traits; |
| static void destroy() | ||
| { | ||
| (cast(C) b.ptr).__dtor; | ||
| } |
There was a problem hiding this comment.
Agree, this was actually a problem with destroy before we fixed it.
| result.__ctor; | ||
| import core.internal.traits; | ||
| static if (hasElaborateDestructor!C) | ||
| { |
There was a problem hiding this comment.
Hm... I think just remove this static if. There is always a destructor for a class (i.e. rt_finalize2 will always call destructors, even for Object).
|
Note, this will close https://issues.dlang.org/show_bug.cgi?id=4587 |
Hm... I think actually @andralex, looking at how |
It already does, see #1798. For the buffer to still be scanned with proper alignment, it should be an array of void*. |
|
I think that there should also be a test showing what happens if an assert triggers during unwinding an AssertError. It doesn't have to support perfect chaining, but it should somehow pass the initial error along. |
|
superseded |
This makes AssertError a singleton, which allows people to use
assertin higher-level applications without needing to link in the garbage collector.This could break code that calls
onAssertErrorrepeatedly and assumes it returns separate objects. That's quite an unusual way to go about things, and it seems we need not guarantee it.If this approach works we can (a) publish
singletonso as to make it available to clients, and (b) convertRangeErrorto a singleton as well so we can throw range errors without worrying about memory allocations.