[Fix] (Shader): keep subroutine type declarations as prototypes so reserved type names are rejected

This commit is contained in:
2026-10-12 03:55:51 -04:00
parent 7abc581551
commit 8800c6b068
2 changed files with 33 additions and 2 deletions
@@ -4550,6 +4550,33 @@ subroutine(FuncType) void Func0(int coord) {
}
}
// The type declaration only sheds its keyword, so the type NAME still reaches glslang as a function
// prototype: a reserved word there has to fail the compile (KHR-GL40.CommonBugs.CommonBug_ReservedNames)
// instead of vanishing with a blanked declaration.
TEST_F(ProgramUtilTest, PreprocessSubroutineKeepsTheTypeNameVisibleToTheCompiler) {
using namespace MG_Util::ShaderTranspiler;
const auto lowerAndCompile = [](const String& typeName) {
String source = "#version 400 core\n"
"out vec4 fragColor;\n"
"subroutine void " + typeName + "(void);\n"
"subroutine(" + typeName + ") void Impl(void) { fragColor = vec4(1.0); }\n"
"subroutine uniform " + typeName + " g_routine;\n"
"void main() { g_routine(); }\n";
PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find("subroutine"), String::npos) << source;
EXPECT_NE(source.find("void " + typeName + "(void);"), String::npos)
<< "the type declaration must survive as a prototype:\n" << source;
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
return Bool(ShaderCompiler::CompileShader(attrib));
};
EXPECT_TRUE(lowerAndCompile("Routine"));
for (const char* reserved : {"const", "vec4", "asm", "sampler2D"}) {
EXPECT_FALSE(lowerAndCompile(reserved)) << "a subroutine type named '" << reserved << "' must not compile";
}
}
// The forwarding function is rebuilt from the subroutine TYPE declaration, so it has to carry the
// parameter qualifiers and array shapes across (an parameter that arrives by value writes
// nothing back) and has to return the forwarded value for a non-void subroutine.
@@ -1275,7 +1275,7 @@ namespace {
// it is left to fail at compile time exactly as it does today rather than silently pinned to
// one of the alternatives.
//
// subroutine void FuncType(int coord); -> (blanked)
// subroutine void FuncType(int coord); -> void FuncType(int coord);
// subroutine uniform FuncType g_func; -> void g_func(int mgl_sr_arg0);
// subroutine(FuncType) void Func0(int c) { } -> void Func0(int c) { }
// ...plus, appended at end of source,
@@ -1440,7 +1440,11 @@ namespace {
type.returnText = JoinTokenText(tokens, i + 1, paren - 1);
if (type.returnText.empty()) return;
if (!ParseSubroutineParameters(tokens, paren + 1, listEnd - 1, type.parameters)) return;
blanks.push_back({token.begin, tokens[listEnd].end});
// Only the keyword goes. What is left is an ordinary prototype that is never defined
// or called - legal - and it keeps the type NAME in front of glslang, which is what
// still rejects a reserved word there (`subroutine void const();`) instead of the
// name vanishing with the declaration.
blanks.push_back({token.begin, token.end});
i = listEnd;
}