Fix uninitialized use of TIntermediate::resource (#2424)

TIntermediate was constructed without initializing any of the `resources` fields,
and `TProgram::linkStage()` was not calling `TIntermediate::setLimits()`
after constructing new `TIntermediate`s for non-first stages.

Fields of `resources` were then read in `TIntermediate::finalCheck()`
triggering undefined behavior.

This CL makes three changes:
(1) `TIntermediate::setLimits()` is now called for non-first stages by
    copying the `firstIntermediate`'s limits. This ensures that the
    `resources` fields is initialized, fixing the bug.
(2) `TIntermediate::resources` is now wrapped in a `MustBeAssigned<>`
    helper struct, asserting in non-release builds that this field is
    always initialized before reading.
(3) `TIntermediate::resources` is now zero-initialized, so that if
    the `TIntermediate::resources` field is not set in a release build
    (and so the `assert()` will be disabled) behavior is still
    deterministic.

Fixes #2423
This commit is contained in:
Ben Clayton 2020-10-19 22:21:12 +01:00 committed by GitHub
parent f4f1d8a352
commit 5b99b448b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 23 additions and 4 deletions

View File

@ -2016,7 +2016,7 @@ bool TProgram::linkStage(EShLanguage stage, EShMessages messages)
intermediate[stage] = new TIntermediate(stage,
firstIntermediate->getVersion(),
firstIntermediate->getProfile());
intermediate[stage]->setLimits(firstIntermediate->getLimits());
// The new TIntermediate must use the same origin as the original TIntermediates.
// Otherwise linking will fail due to different coordinate systems.

View File

@ -736,10 +736,10 @@ void TIntermediate::finalCheck(TInfoSink& infoSink, bool keepUncalled)
// "The resulting stride (implicit or explicit), when divided by 4, must be less than or equal to the
// implementation-dependent constant gl_MaxTransformFeedbackInterleavedComponents."
if (xfbBuffers[b].stride > (unsigned int)(4 * resources.maxTransformFeedbackInterleavedComponents)) {
if (xfbBuffers[b].stride > (unsigned int)(4 * resources->maxTransformFeedbackInterleavedComponents)) {
error(infoSink, "xfb_stride is too large:");
infoSink.info.prefix(EPrefixError);
infoSink.info << " xfb_buffer " << (unsigned int)b << ", components (1/4 stride) needed are " << xfbBuffers[b].stride/4 << ", gl_MaxTransformFeedbackInterleavedComponents is " << resources.maxTransformFeedbackInterleavedComponents << "\n";
infoSink.info << " xfb_buffer " << (unsigned int)b << ", components (1/4 stride) needed are " << xfbBuffers[b].stride/4 << ", gl_MaxTransformFeedbackInterleavedComponents is " << resources->maxTransformFeedbackInterleavedComponents << "\n";
}
}

View File

@ -259,6 +259,23 @@ private:
unsigned int features;
};
// MustBeAssigned wraps a T, asserting that it has been assigned with
// operator =() before attempting to read with operator T() or operator ->().
// Used to catch cases where fields are read before they have been assigned.
template<typename T>
class MustBeAssigned
{
public:
MustBeAssigned() = default;
MustBeAssigned(const T& v) : value(v) {}
operator const T&() const { assert(isSet); return value; }
const T* operator ->() const { assert(isSet); return &value; }
MustBeAssigned& operator = (const T& v) { value = v; isSet = true; return *this; }
private:
T value;
bool isSet = false;
};
//
// Set of helper functions to help parse and build the tree.
//
@ -270,6 +287,7 @@ public:
profile(p), version(v),
#endif
treeRoot(0),
resources(TBuiltInResource{}),
numEntryPoints(0), numErrors(0), numPushConstants(0), recursive(false),
invertY(false),
useStorageBuffer(false),
@ -406,6 +424,7 @@ public:
int getNumErrors() const { return numErrors; }
void addPushConstantCount() { ++numPushConstants; }
void setLimits(const TBuiltInResource& r) { resources = r; }
const TBuiltInResource& getLimits() const { return resources; }
bool postProcess(TIntermNode*, EShLanguage);
void removeTree();
@ -955,7 +974,7 @@ protected:
SpvVersion spvVersion;
TIntermNode* treeRoot;
std::set<std::string> requestedExtensions; // cumulation of all enabled or required extensions; not connected to what subset of the shader used them
TBuiltInResource resources;
MustBeAssigned<TBuiltInResource> resources;
int numEntryPoints;
int numErrors;
int numPushConstants;