-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
string_id::NULL_ID isn't always initialized in time #17197
Comments
I say the item factory (which causes the linked bug) should be initialized from the game class (maybe from its constructor). Currently it's statically created, the same as the NULL_ID and that causes the problem. Alternatively it could be created as static object inside a static wrapper function (similar to |
I'd much prefer initializing from the game object between those
alternatives.
|
Would it be possible to have a single "has ID" class with an "init null ID" method, invoke the method in init.cpp and statically warn if a class uses a null ID but doesn't have said class? Alternatively, ensure that using null ID causes a crash if it isn't explicitly invoked somewhere. |
Why not change static const string_id<Foo> &string_id<Foo>::NULL_ID() {
static const string_id<Foo> instance( "..." );
return instance;
} That has no external dependencies and is initialized as soon as it's needed. |
What would happen if it was invoked circularly? Crash or unspecified behavior? |
It's simply not invoked circular. The constructor (that gets invoked when If we have a circular dependency, it needs to be changed anyway. Function static objects are the best solution because C++ guarantees their constructor is invoked at most once (it's even thread-safe). |
This keeps coming up in different parts of the code and is due to the idiom
of returning const references to things and then sometimes not having a
reference to return. Do we have a clear idea of the benefit of this idiom,
because the cost so far is crash bugs scattered around in the code?
If we return by value instead this issue disappears for good.
|
When did local reference return cause crashes? It should cause a compiler warning rather than undefined behavior later on. NULL_ID not being initialized in time is not related to references, but rather static initialization. At the moment NULL_IDs are initialized in static context, which means the order varies between OSes and compilers (Windows+mingw doesn't sort alphabetically, for example). |
Local reference return is undefined, and we have had crashes caused by it
since the local goes out of scope and is reused. The safer option is
returning by value, which allows us to return a local variable safely.
|
Yes, but the compiler should warn about it. I'm sure it does in the most common case. It is only risky in the case of objects that change a lot between worlds. But I don't recall this happening in the core game (someone would need to redefine "null" object or something like that) and those objects also benefit the most from not being rebuilt every time. |
In theory yes, in practice this isn't the first time this specific idiom (returning a const reference) has broken things, and I guarantee it won't be the last. My question is, do we have evidence that the performance benefit of doing it this way is worth the fragility that comes with it? People seem to find it easy to internalize the "return objects by const reference is good" mantra, but when one of these methods needs to return a null object, they frequently do not know or think to use the safe idiom. I'm proposing that we can simply make the method return a copy instead, which has none of this fragility. The only down side to this approach is a potential performance penalty, which AFAIK is totally unquantified, which is why I said:
|
When did it break things? The only fragile part of it I can think of here is item factory, which changes between save+load and sometimes during single game. But then, it HAS to return references/pointers in many cases, because items need pointers to types.
I don't see that fragility. I recall multiple crashes related to pointer/reference getting scrambled, but I can't think of a single case when fixing it by returning a value was a real option. The crash related to NULL_ID would still happen just fine with by-value returns since it depends on constructors being invoked in unspecified order, which by-value return doesn't affect.
You mean the null id function or everything in general? It's the static order that causes the crash here, because As for performance: |
Remove the definition but leave the declaration for that operator. You should get a series of linker errors for each usage. You may need to adjust your compiler flags to suppress the '...and 43 other occurrences' contraction. |
This is purely a code quality issue. |
Can I take it off from blockers? |
I don't know if this is wholly related, but clang-3.8 and above warns about undefined instantiations of ::NULL_ID variants in various templated classes. Example:
I don't know if adding explicit instatiation declarations where they are needed will help the problem, but at least this current design is causing build failures with the current setup with RELEASE=1 on clang-3.8+. |
@kevingranade with #20946 merged, is this closed? |
Good catch, the new template code does force us to in it everything up front, so this should not recur. |
I was mistaken, we're still vulnerable to this: |
Causes #17177, currently hacked around with #17179 but it's still a big risk of future crashes and so should be fixed by actually addressing the problem.
The text was updated successfully, but these errors were encountered: