mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Fix] (MG_Util, MG_State): reject reserved GLSL identifiers glslang accepts
glslang parses "packed" and "row_major" as plain identifiers outside a layout(...) list and accepts the reserved image*Shadow names outright. A comment/preprocessor-aware pre-scan in the compile path now fails such shaders with a proper info log, while layout(packed)/layout(row_major) qualifier lists stay legal (uniform_block family still passes). KHR-GL31/32/33.CommonBugs.CommonBug_ReservedNames now pass.
This commit is contained in:
@@ -169,6 +169,15 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
}
|
||||
|
||||
const std::optional<String> reservedError =
|
||||
MG_Util::ShaderTranspiler::FindReservedIdentifierViolation(compileSource);
|
||||
if (reservedError) {
|
||||
m_compileStatus = false;
|
||||
m_shader.reset();
|
||||
m_infoLog = *reservedError;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compile for OpenGL here, so that we can do validation and link
|
||||
// like a real OpenGL driver at linking stage
|
||||
// Will compile for other backends later.
|
||||
|
||||
@@ -1342,6 +1342,105 @@ namespace MobileGL {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<String> FindReservedIdentifierViolation(const String& source) {
|
||||
// Reserved anywhere; glslang accepts them as plain identifiers.
|
||||
static constexpr const char* kAlwaysReserved[] = {
|
||||
"image1DShadow",
|
||||
"image2DShadow",
|
||||
"image1DArrayShadow",
|
||||
"image2DArrayShadow",
|
||||
};
|
||||
// Keywords legal only inside a layout(...) qualifier list.
|
||||
static constexpr const char* kLayoutOnlyKeywords[] = {
|
||||
"packed",
|
||||
"row_major",
|
||||
};
|
||||
|
||||
const auto isIdentChar = [](char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
||||
};
|
||||
|
||||
const SizeT length = source.size();
|
||||
SizeT i = 0;
|
||||
Int layoutParenDepth = 0; // >0 while inside layout(...)
|
||||
Bool pendingLayoutParen = false; // saw "layout", awaiting its '('
|
||||
while (i < length) {
|
||||
const char c = source[i];
|
||||
// Comments.
|
||||
if (c == '/' && i + 1 < length && source[i + 1] == '/') {
|
||||
while (i < length && source[i] != '\n') ++i;
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && i + 1 < length && source[i + 1] == '*') {
|
||||
i += 2;
|
||||
while (i + 1 < length && !(source[i] == '*' && source[i + 1] == '/')) ++i;
|
||||
i = (i + 1 < length) ? i + 2 : length;
|
||||
continue;
|
||||
}
|
||||
// Preprocessor lines stay out of scope (macro names may shadow anything).
|
||||
if (c == '#' && (i == 0 || source[i - 1] == '\n' ||
|
||||
source.find_last_not_of(" \t", i - 1) == MobileGL::String::npos ||
|
||||
source[source.find_last_not_of(" \t", i - 1)] == '\n')) {
|
||||
while (i < length && source[i] != '\n') {
|
||||
if (source[i] == '\\' && i + 1 < length && source[i + 1] == '\n') ++i;
|
||||
++i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c == '(') {
|
||||
if (pendingLayoutParen) {
|
||||
layoutParenDepth = 1;
|
||||
pendingLayoutParen = false;
|
||||
} else if (layoutParenDepth > 0) {
|
||||
++layoutParenDepth;
|
||||
}
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (c == ')') {
|
||||
if (layoutParenDepth > 0) --layoutParenDepth;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (isIdentChar(c) && !(c >= '0' && c <= '9')) {
|
||||
const SizeT start = i;
|
||||
while (i < length && isIdentChar(source[i])) ++i;
|
||||
const StringView word(source.data() + start, i - start);
|
||||
if (word == "layout") {
|
||||
pendingLayoutParen = true;
|
||||
continue;
|
||||
}
|
||||
pendingLayoutParen = false;
|
||||
for (const char* reserved : kAlwaysReserved) {
|
||||
if (word == reserved) {
|
||||
return String("ERROR: reserved identifier '") + reserved + "' may not be used.";
|
||||
}
|
||||
}
|
||||
if (layoutParenDepth == 0) {
|
||||
for (const char* keyword : kLayoutOnlyKeywords) {
|
||||
if (word == keyword) {
|
||||
return String("ERROR: '") + keyword +
|
||||
"' is a keyword and may not be used as an identifier.";
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isIdentChar(c)) { // digit-led token: skip the whole number/identifier tail
|
||||
while (i < length && isIdentChar(source[i])) ++i;
|
||||
pendingLayoutParen = false;
|
||||
continue;
|
||||
}
|
||||
pendingLayoutParen = false;
|
||||
++i;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -40,6 +40,11 @@ namespace MobileGL {
|
||||
// uses 420-era syntax without the matching #extension line, which real drivers tend to
|
||||
// accept - can be retried instead of failing to compile.
|
||||
Bool RetargetLegacyVersionDirectiveTo460(String& source);
|
||||
|
||||
// GLSL reserves a few names glslang happily accepts as identifiers ("packed",
|
||||
// "row_major" outside a layout(...) list, the image*Shadow family). Returns the
|
||||
// compile-error text for the first violation, or nullopt for a clean source.
|
||||
std::optional<String> FindReservedIdentifierViolation(const String& source);
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
|
||||
Reference in New Issue
Block a user