xref: /aosp_15_r20/external/angle/src/tests/compiler_tests/ExpressionLimit_test.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1 //
2 // Copyright 2002 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 #include <sstream>
7 #include <string>
8 #include <vector>
9 #include "GLSLANG/ShaderLang.h"
10 #include "angle_gl.h"
11 #include "gtest/gtest.h"
12 
13 class ExpressionLimitTest : public testing::Test
14 {
15   protected:
16     static const int kMaxExpressionComplexity = 16;
17     static const int kMaxCallStackDepth       = 16;
18     static const int kMaxFunctionParameters   = 16;
19 
SetUp()20     virtual void SetUp()
21     {
22         memset(&resources, 0, sizeof(resources));
23 
24         GenerateResources(&resources);
25     }
26 
27     // Set up the per compile resources
GenerateResources(ShBuiltInResources * res)28     static void GenerateResources(ShBuiltInResources *res)
29     {
30         sh::InitBuiltInResources(res);
31 
32         res->MaxVertexAttribs             = 8;
33         res->MaxVertexUniformVectors      = 128;
34         res->MaxVaryingVectors            = 8;
35         res->MaxVertexTextureImageUnits   = 0;
36         res->MaxCombinedTextureImageUnits = 8;
37         res->MaxTextureImageUnits         = 8;
38         res->MaxFragmentUniformVectors    = 16;
39         res->MaxDrawBuffers               = 1;
40 
41         res->OES_standard_derivatives = 0;
42         res->OES_EGL_image_external   = 0;
43 
44         res->MaxExpressionComplexity = kMaxExpressionComplexity;
45         res->MaxCallStackDepth       = kMaxCallStackDepth;
46         res->MaxFunctionParameters   = kMaxFunctionParameters;
47     }
48 
GenerateLongExpression(int length,std::stringstream * ss)49     static void GenerateLongExpression(int length, std::stringstream *ss)
50     {
51         for (int ii = 0; ii < length; ++ii)
52         {
53             *ss << "+ vec4(" << ii << ")";
54         }
55     }
56 
GenerateShaderWithLongExpression(int length)57     static std::string GenerateShaderWithLongExpression(int length)
58     {
59         static const char *shaderStart =
60             R"(precision mediump float;
61             uniform vec4 u_color;
62             void main()
63             {
64                gl_FragColor = u_color
65         )";
66 
67         std::stringstream ss;
68         ss << shaderStart;
69         GenerateLongExpression(length, &ss);
70         ss << "; }";
71 
72         return ss.str();
73     }
74 
GenerateShaderWithUnusedLongExpression(int length)75     static std::string GenerateShaderWithUnusedLongExpression(int length)
76     {
77         static const char *shaderStart =
78             R"(precision mediump float;
79             uniform vec4 u_color;
80             void main()
81             {
82                gl_FragColor = u_color;
83             }
84             vec4 someFunction() {
85               return u_color
86         )";
87 
88         std::stringstream ss;
89 
90         ss << shaderStart;
91         GenerateLongExpression(length, &ss);
92         ss << "; }";
93 
94         return ss.str();
95     }
96 
GenerateDeepFunctionStack(int length,std::stringstream * ss)97     static void GenerateDeepFunctionStack(int length, std::stringstream *ss)
98     {
99         static const char *shaderStart =
100             R"(precision mediump float;
101             uniform vec4 u_color;
102             vec4 function0()  {
103               return u_color;
104             }
105         )";
106 
107         *ss << shaderStart;
108         for (int ii = 0; ii < length; ++ii)
109         {
110             *ss << "vec4 function" << (ii + 1) << "() {\n"
111                 << "  return function" << ii << "();\n"
112                 << "}\n";
113         }
114     }
115 
GenerateShaderWithDeepFunctionStack(int length)116     static std::string GenerateShaderWithDeepFunctionStack(int length)
117     {
118         std::stringstream ss;
119 
120         GenerateDeepFunctionStack(length, &ss);
121 
122         ss << "void main() {\n" << "  gl_FragColor = function" << length << "();\n" << "}";
123 
124         return ss.str();
125     }
126 
GenerateShaderWithUnusedDeepFunctionStack(int length)127     static std::string GenerateShaderWithUnusedDeepFunctionStack(int length)
128     {
129         std::stringstream ss;
130 
131         GenerateDeepFunctionStack(length, &ss);
132 
133         ss << "void main() {\n" << "  gl_FragColor = vec4(0,0,0,0);\n" << "}";
134 
135         return ss.str();
136     }
137 
GenerateShaderWithFunctionParameters(int parameters)138     static std::string GenerateShaderWithFunctionParameters(int parameters)
139     {
140         std::stringstream ss;
141 
142         ss << "precision mediump float;\n" << "\n" << "float foo(";
143         for (int i = 0; i < parameters; ++i)
144         {
145             ss << "float f" << i;
146             if (i + 1 < parameters)
147             {
148                 ss << ", ";
149             }
150         }
151         ss << ")\n"
152            << "{\n"
153            << "    return f0;\n"
154            << "}\n"
155            << "\n"
156            << "void main()\n"
157            << "{\n"
158            << "    gl_FragColor = vec4(0,0,0,0);\n"
159            << "}";
160 
161         return ss.str();
162     }
163 
GenerateShaderWithNestingInsideSwitch(int nesting)164     static std::string GenerateShaderWithNestingInsideSwitch(int nesting)
165     {
166         std::stringstream shaderString;
167         shaderString <<
168             R"(#version 300 es
169             uniform int u;
170 
171             void main()
172             {
173                 int x;
174                 switch (u)
175                 {
176                     case 0:
177                         x = x)";
178         for (int i = 0; i < nesting; ++i)
179         {
180             shaderString << " + x";
181         }
182         shaderString <<
183             R"(;
184                 }  // switch (u)
185             })";
186         return shaderString.str();
187     }
188 
GenerateShaderWithNestingInsideGlobalInitializer(int nesting)189     static std::string GenerateShaderWithNestingInsideGlobalInitializer(int nesting)
190     {
191         std::stringstream shaderString;
192         shaderString <<
193             R"(uniform int u;
194             int x = u)";
195 
196         for (int i = 0; i < nesting; ++i)
197         {
198             shaderString << " + u";
199         }
200         shaderString << R"(;
201             void main()
202             {
203                 gl_FragColor = vec4(0.0);
204             })";
205         return shaderString.str();
206     }
207 
208     // Compiles a shader and if there's an error checks for a specific
209     // substring in the error log. This way we know the error is specific
210     // to the issue we are testing.
CheckShaderCompilation(ShHandle compiler,const char * source,const ShCompileOptions & compileOptions,const char * expected_error)211     bool CheckShaderCompilation(ShHandle compiler,
212                                 const char *source,
213                                 const ShCompileOptions &compileOptions,
214                                 const char *expected_error)
215     {
216         bool success = sh::Compile(compiler, &source, 1, compileOptions) != 0;
217         if (success)
218         {
219             success = !expected_error;
220         }
221         else
222         {
223             std::string log = sh::GetInfoLog(compiler);
224             if (expected_error)
225                 success = log.find(expected_error) != std::string::npos;
226 
227             EXPECT_TRUE(success) << log << "\n----shader----\n" << source;
228         }
229         return success;
230     }
231 
232     ShBuiltInResources resources;
233 };
234 
235 constexpr char kExpressionTooComplex[] = "Expression too complex";
236 constexpr char kCallStackTooDeep[]     = "Call stack too deep";
237 constexpr char kHasRecursion[]         = "Recursive function call in the following call chain";
238 constexpr char kTooManyParameters[]    = "Function has too many parameters";
239 constexpr char kTooComplexSwitch[]     = "too complex expressions inside a switch statement";
240 constexpr char kGlobalVariableInit[] = "global variable initializers must be constant expressions";
241 constexpr char kTooManyFields[]      = "Too many fields in the struct";
242 
TEST_F(ExpressionLimitTest,ExpressionComplexity)243 TEST_F(ExpressionLimitTest, ExpressionComplexity)
244 {
245     ShShaderSpec spec       = SH_WEBGL_SPEC;
246     ShShaderOutput output   = SH_ESSL_OUTPUT;
247     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
248     ShCompileOptions compileOptions          = {};
249     compileOptions.limitExpressionComplexity = true;
250 
251     // Test expression under the limit passes.
252     EXPECT_TRUE(CheckShaderCompilation(
253         vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity - 10).c_str(),
254         compileOptions, nullptr));
255     // Test expression over the limit fails.
256     EXPECT_TRUE(CheckShaderCompilation(
257         vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity + 10).c_str(),
258         compileOptions, kExpressionTooComplex));
259     // Test expression over the limit without a limit does not fail.
260     compileOptions.limitExpressionComplexity = false;
261     EXPECT_TRUE(CheckShaderCompilation(
262         vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity + 10).c_str(),
263         compileOptions, nullptr));
264     sh::Destruct(vertexCompiler);
265 }
266 
TEST_F(ExpressionLimitTest,UnusedExpressionComplexity)267 TEST_F(ExpressionLimitTest, UnusedExpressionComplexity)
268 {
269     ShShaderSpec spec       = SH_WEBGL_SPEC;
270     ShShaderOutput output   = SH_ESSL_OUTPUT;
271     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
272     ShCompileOptions compileOptions          = {};
273     compileOptions.limitExpressionComplexity = true;
274 
275     // Test expression under the limit passes.
276     EXPECT_TRUE(CheckShaderCompilation(
277         vertexCompiler,
278         GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity - 10).c_str(),
279         compileOptions, nullptr));
280     // Test expression over the limit fails.
281     EXPECT_TRUE(CheckShaderCompilation(
282         vertexCompiler,
283         GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity + 10).c_str(),
284         compileOptions, kExpressionTooComplex));
285     // Test expression over the limit without a limit does not fail.
286     compileOptions.limitExpressionComplexity = false;
287     EXPECT_TRUE(CheckShaderCompilation(
288         vertexCompiler,
289         GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity + 10).c_str(),
290         compileOptions, nullptr));
291     sh::Destruct(vertexCompiler);
292 }
293 
TEST_F(ExpressionLimitTest,CallStackDepth)294 TEST_F(ExpressionLimitTest, CallStackDepth)
295 {
296     ShShaderSpec spec       = SH_WEBGL_SPEC;
297     ShShaderOutput output   = SH_ESSL_OUTPUT;
298     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
299     ShCompileOptions compileOptions    = {};
300     compileOptions.limitCallStackDepth = true;
301 
302     // Test call stack under the limit passes.
303     EXPECT_TRUE(CheckShaderCompilation(
304         vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth - 10).c_str(),
305         compileOptions, nullptr));
306     // Test call stack over the limit fails.
307     EXPECT_TRUE(CheckShaderCompilation(
308         vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
309         compileOptions, kCallStackTooDeep));
310     // Test call stack over the limit without limit does not fail.
311     compileOptions.limitCallStackDepth = false;
312     EXPECT_TRUE(CheckShaderCompilation(
313         vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
314         compileOptions, nullptr));
315     sh::Destruct(vertexCompiler);
316 }
317 
TEST_F(ExpressionLimitTest,UnusedCallStackDepth)318 TEST_F(ExpressionLimitTest, UnusedCallStackDepth)
319 {
320     ShShaderSpec spec       = SH_WEBGL_SPEC;
321     ShShaderOutput output   = SH_ESSL_OUTPUT;
322     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
323     ShCompileOptions compileOptions    = {};
324     compileOptions.limitCallStackDepth = true;
325 
326     // Test call stack under the limit passes.
327     EXPECT_TRUE(CheckShaderCompilation(
328         vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth - 10).c_str(),
329         compileOptions, nullptr));
330     // Test call stack over the limit fails.
331     EXPECT_TRUE(CheckShaderCompilation(
332         vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
333         compileOptions, kCallStackTooDeep));
334     // Test call stack over the limit without limit does not fail.
335     compileOptions.limitCallStackDepth = false;
336     EXPECT_TRUE(CheckShaderCompilation(
337         vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
338         compileOptions, nullptr));
339     sh::Destruct(vertexCompiler);
340 }
341 
TEST_F(ExpressionLimitTest,Recursion)342 TEST_F(ExpressionLimitTest, Recursion)
343 {
344     ShShaderSpec spec       = SH_WEBGL_SPEC;
345     ShShaderOutput output   = SH_ESSL_OUTPUT;
346     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
347     ShCompileOptions compileOptions = {};
348 
349     static const char *shaderWithRecursion0 =
350         R"(precision mediump float;
351         uniform vec4 u_color;
352         vec4 someFunc()  {
353             return someFunc();
354         }
355 
356         void main() {
357             gl_FragColor = u_color * someFunc();
358         }
359     )";
360 
361     static const char *shaderWithRecursion1 =
362         R"(precision mediump float;
363         uniform vec4 u_color;
364 
365         vec4 someFunc();
366 
367         vec4 someFunc1()  {
368             return someFunc();
369         }
370 
371         vec4 someFunc()  {
372             return someFunc1();
373         }
374 
375         void main() {
376             gl_FragColor = u_color * someFunc();
377         }
378     )";
379 
380     static const char *shaderWithRecursion2 =
381         R"(precision mediump float;
382         uniform vec4 u_color;
383         vec4 someFunc()  {
384             if (u_color.x > 0.5) {
385                 return someFunc();
386             } else {
387                 return vec4(1);
388             }
389         }
390 
391         void main() {
392             gl_FragColor = someFunc();
393         }
394     )";
395 
396     static const char *shaderWithRecursion3 =
397         R"(precision mediump float;
398         uniform vec4 u_color;
399         vec4 someFunc()  {
400             if (u_color.x > 0.5) {
401                 return vec4(1);
402             } else {
403                 return someFunc();
404             }
405         }
406 
407         void main() {
408             gl_FragColor = someFunc();
409         }
410     )";
411 
412     static const char *shaderWithRecursion4 =
413         R"(precision mediump float;
414         uniform vec4 u_color;
415         vec4 someFunc()  {
416             return (u_color.x > 0.5) ? vec4(1) : someFunc();
417         }
418 
419         void main() {
420             gl_FragColor = someFunc();
421         }
422     )";
423 
424     static const char *shaderWithRecursion5 =
425         R"(precision mediump float;
426         uniform vec4 u_color;
427         vec4 someFunc()  {
428             return (u_color.x > 0.5) ? someFunc() : vec4(1);
429         }
430 
431         void main() {
432             gl_FragColor = someFunc();
433         }
434     )";
435 
436     static const char *shaderWithRecursion6 =
437         R"(precision mediump float;
438         uniform vec4 u_color;
439         vec4 someFunc()  {
440             return someFunc();
441         }
442 
443         void main() {
444             gl_FragColor = u_color;
445         }
446     )";
447 
448     static const char *shaderWithNoRecursion =
449         R"(precision mediump float;
450         uniform vec4 u_color;
451 
452         vec3 rgb(int r, int g, int b) {
453             return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0);
454         }
455 
456         void main() {
457             vec3 hairColor0 = rgb(151, 200, 234);
458             vec3 faceColor2 = rgb(183, 148, 133);
459             gl_FragColor = u_color + vec4(hairColor0 + faceColor2, 0);
460         }
461     )";
462 
463     static const char *shaderWithRecursion7 =
464         R"(precision mediump float;
465         uniform vec4 u_color;
466 
467         vec4 function2() {
468             return u_color;
469         }
470 
471         vec4 function1() {
472             vec4 a = function2();
473             vec4 b = function1();
474             return a + b;
475         }
476 
477         void main() {
478             gl_FragColor = function1();
479         }
480     )";
481 
482     static const char *shaderWithRecursion8 =
483         R"(precision mediump float;
484         uniform vec4 u_color;
485 
486         vec4 function1();
487 
488         vec4 function3() {
489             return function1();
490         }
491 
492         vec4 function2() {
493             return function3();
494         }
495 
496         vec4 function1() {
497             return function2();
498         }
499 
500         void main() {
501             gl_FragColor = function1();
502         }
503     )";
504 
505     // Check simple recursions fails.
506     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion0, compileOptions,
507                                        kHasRecursion));
508     // Check simple recursions fails.
509     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion1, compileOptions,
510                                        kHasRecursion));
511     // Check if recursions fails.
512     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion2, compileOptions,
513                                        kHasRecursion));
514     // Check if recursions fails.
515     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion3, compileOptions,
516                                        kHasRecursion));
517     // Check ternary recursions fails.
518     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion4, compileOptions,
519                                        kHasRecursion));
520     // Check ternary recursions fails.
521     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion5, compileOptions,
522                                        kHasRecursion));
523 
524     // Check some more forms of recursion
525     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion6, compileOptions,
526                                        kHasRecursion));
527     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion7, compileOptions,
528                                        kHasRecursion));
529     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion8, compileOptions,
530                                        kHasRecursion));
531     // Check unused recursions fails if limiting call stack
532     // since we check all paths.
533     compileOptions.limitCallStackDepth = true;
534     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion6, compileOptions,
535                                        kHasRecursion));
536 
537     // Check unused recursions passes.
538     EXPECT_TRUE(
539         CheckShaderCompilation(vertexCompiler, shaderWithNoRecursion, compileOptions, nullptr));
540     // Check unused recursions passes if limiting call stack.
541     EXPECT_TRUE(
542         CheckShaderCompilation(vertexCompiler, shaderWithNoRecursion, compileOptions, nullptr));
543     sh::Destruct(vertexCompiler);
544 }
545 
TEST_F(ExpressionLimitTest,FunctionParameterCount)546 TEST_F(ExpressionLimitTest, FunctionParameterCount)
547 {
548     ShShaderSpec spec     = SH_WEBGL_SPEC;
549     ShShaderOutput output = SH_ESSL_OUTPUT;
550     ShHandle compiler     = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
551     ShCompileOptions compileOptions          = {};
552     compileOptions.limitExpressionComplexity = true;
553 
554     // Test parameters under the limit succeeds.
555     EXPECT_TRUE(CheckShaderCompilation(
556         compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters).c_str(),
557         compileOptions, nullptr));
558     // Test parameters over the limit fails.
559     EXPECT_TRUE(CheckShaderCompilation(
560         compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters + 1).c_str(),
561         compileOptions, kTooManyParameters));
562     // Test parameters over the limit without limit does not fail.
563     compileOptions.limitExpressionComplexity = false;
564     EXPECT_TRUE(CheckShaderCompilation(
565         compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters + 1).c_str(),
566         compileOptions, nullptr));
567     sh::Destruct(compiler);
568 }
569 
TEST_F(ExpressionLimitTest,NestingInsideSwitch)570 TEST_F(ExpressionLimitTest, NestingInsideSwitch)
571 {
572     ShShaderSpec spec     = SH_WEBGL2_SPEC;
573     ShShaderOutput output = SH_ESSL_OUTPUT;
574     ShHandle compiler     = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
575     ShCompileOptions compileOptions          = {};
576     compileOptions.limitExpressionComplexity = true;
577 
578     // Test nesting over the limit fails.
579     EXPECT_TRUE(CheckShaderCompilation(
580         compiler, GenerateShaderWithNestingInsideSwitch(kMaxExpressionComplexity + 1).c_str(),
581         compileOptions, kExpressionTooComplex));
582     // Test that nesting way over the limit doesn't cause stack overflow but is handled
583     // gracefully.
584     EXPECT_TRUE(CheckShaderCompilation(compiler,
585                                        GenerateShaderWithNestingInsideSwitch(5000).c_str(),
586                                        compileOptions, kTooComplexSwitch));
587     // Test nesting over the limit without limit does not fail.
588     compileOptions.limitExpressionComplexity = false;
589     EXPECT_TRUE(CheckShaderCompilation(
590         compiler, GenerateShaderWithNestingInsideSwitch(kMaxExpressionComplexity + 1).c_str(),
591         compileOptions, nullptr));
592     sh::Destruct(compiler);
593 }
594 
TEST_F(ExpressionLimitTest,NestingInsideGlobalInitializer)595 TEST_F(ExpressionLimitTest, NestingInsideGlobalInitializer)
596 {
597     ShShaderSpec spec     = SH_WEBGL_SPEC;
598     ShShaderOutput output = SH_ESSL_OUTPUT;
599     ShHandle compiler     = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
600     ShCompileOptions compileOptions          = {};
601     compileOptions.limitExpressionComplexity = true;
602 
603     // Test nesting over the limit fails.
604     EXPECT_TRUE(CheckShaderCompilation(
605         compiler,
606         GenerateShaderWithNestingInsideGlobalInitializer(kMaxExpressionComplexity + 1).c_str(),
607         compileOptions, kExpressionTooComplex));
608     // Test that nesting way over the limit doesn't cause stack overflow but is handled
609     // gracefully.
610     EXPECT_TRUE(CheckShaderCompilation(
611         compiler, GenerateShaderWithNestingInsideGlobalInitializer(5000).c_str(), compileOptions,
612         kGlobalVariableInit));
613     // Test nesting over the limit without limit does not fail.
614     compileOptions.limitExpressionComplexity = false;
615     EXPECT_TRUE(CheckShaderCompilation(
616         compiler,
617         GenerateShaderWithNestingInsideGlobalInitializer(kMaxExpressionComplexity + 1).c_str(),
618         compileOptions, nullptr));
619     sh::Destruct(compiler);
620 }
621 
TEST_F(ExpressionLimitTest,TooManyStructFields)622 TEST_F(ExpressionLimitTest, TooManyStructFields)
623 {
624     ShShaderSpec spec     = SH_WEBGL2_SPEC;
625     ShShaderOutput output = SH_ESSL_OUTPUT;
626     ShHandle compiler     = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
627     ShCompileOptions compileOptions = {};
628 
629     std::ostringstream fs;
630     fs << R"(#version 300 es
631 precision highp float;
632 struct TooManyFields
633 {
634 )";
635     for (uint32_t i = 0; i < (1 << 16); ++i)
636     {
637         fs << "    float field" << i << ";\n";
638     }
639     fs << R"(};
640 uniform B { TooManyFields s; };
641 out vec4 color;
642 void main() {
643     color = vec4(s.field0, 0.0, 0.0, 1.0);
644 })";
645 
646     EXPECT_TRUE(CheckShaderCompilation(compiler, fs.str().c_str(), compileOptions, kTooManyFields));
647     sh::Destruct(compiler);
648 }
649