Initialize uninitialized locals in GLSL output

Guarantee that local variables are initialized before they are used
in GLSL output. In HLSL output all variables were already being
initialized.

Locals are initialized using an AST transform. The local variable init
can only be run after some simplification of the AST, so that it is
able to handle complex cases like:

for (int i[2], j = i[0]; i[0] < 3; ++i[0]) {
}

If we're dealing with ESSL 1.00 which lacks array constructors, in
this kind of case the uninitialized array initialization code needs to
be hoisted out of the loop init statement, and the code also needs to
make sure that j's initializer is run after i is initialized.

Another complex case involves nameless structs. This can be an issue
also in ESSL 3.00 and above:

for (struct { float f; } s; s.f < 1.0; ++s.f) {
}

Since the struct doesn't have a name, its constructor can not be used.
We solve this by initializing the struct members individually,
similarly to how arrays are initialized in ESSL 1.00.

Initializing local variables is disabled on Mac and Android for now.
On Mac, invalid behavior was exposed in the WebGL 2.0 tests when
enabling it. On Android, the dEQP test runs failed for an unknown
reason. Bugs have been opened to resolve these issues later.

BUG=angleproject:1966
TEST=angle_end2end_tests, WebGL conformance tests

Change-Id: Ic06927f5b6cc9619bc82c647ee966605cd80bab2
Reviewed-on: https://chromium-review.googlesource.com/504728
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/compiler.gypi b/src/compiler.gypi
index ab71b01..8532be7 100644
--- a/src/compiler.gypi
+++ b/src/compiler.gypi
@@ -70,6 +70,8 @@
             'compiler/translator/InitializeVariables.h',
             'compiler/translator/IntermNode.h',
             'compiler/translator/IntermNode.cpp',
+            'compiler/translator/IntermNodePatternMatcher.cpp',
+            'compiler/translator/IntermNodePatternMatcher.h',
             'compiler/translator/IntermTraverse.cpp',
             'compiler/translator/Intermediate.h',
             'compiler/translator/Intermediate.cpp',
@@ -107,9 +109,13 @@
             'compiler/translator/ScalarizeVecAndMatConstructorArgs.h',
             'compiler/translator/SearchSymbol.cpp',
             'compiler/translator/SearchSymbol.h',
+            'compiler/translator/SeparateDeclarations.cpp',
+            'compiler/translator/SeparateDeclarations.h',
             'compiler/translator/Severity.h',
             'compiler/translator/ShaderLang.cpp',
             'compiler/translator/ShaderVars.cpp',
+            'compiler/translator/SimplifyLoopConditions.cpp',
+            'compiler/translator/SimplifyLoopConditions.h',
             'compiler/translator/SymbolTable.cpp',
             'compiler/translator/SymbolTable.h',
             'compiler/translator/Types.cpp',
@@ -183,8 +189,6 @@
             'compiler/translator/blocklayoutHLSL.h',
             'compiler/translator/BuiltInFunctionEmulatorHLSL.cpp',
             'compiler/translator/BuiltInFunctionEmulatorHLSL.h',
-            'compiler/translator/IntermNodePatternMatcher.cpp',
-            'compiler/translator/IntermNodePatternMatcher.h',
             'compiler/translator/OutputHLSL.cpp',
             'compiler/translator/OutputHLSL.h',
             'compiler/translator/RemoveDynamicIndexing.cpp',
@@ -195,12 +199,8 @@
             'compiler/translator/RewriteElseBlocks.h',
             'compiler/translator/SeparateArrayInitialization.cpp',
             'compiler/translator/SeparateArrayInitialization.h',
-            'compiler/translator/SeparateDeclarations.cpp',
-            'compiler/translator/SeparateDeclarations.h',
             'compiler/translator/SeparateExpressionsReturningArrays.cpp',
             'compiler/translator/SeparateExpressionsReturningArrays.h',
-            'compiler/translator/SimplifyLoopConditions.cpp',
-            'compiler/translator/SimplifyLoopConditions.h',
             'compiler/translator/SplitSequenceOperator.cpp',
             'compiler/translator/SplitSequenceOperator.h',
             'compiler/translator/StructureHLSL.cpp',
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 4ea6e75..e3b7ae3 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -18,6 +18,7 @@
 #include "compiler/translator/EmulatePrecision.h"
 #include "compiler/translator/Initialize.h"
 #include "compiler/translator/InitializeVariables.h"
+#include "compiler/translator/IntermNodePatternMatcher.h"
 #include "compiler/translator/ParseContext.h"
 #include "compiler/translator/PruneEmptyDeclarations.h"
 #include "compiler/translator/RegenerateStructNames.h"
@@ -25,6 +26,8 @@
 #include "compiler/translator/RemovePow.h"
 #include "compiler/translator/RewriteDoWhile.h"
 #include "compiler/translator/ScalarizeVecAndMatConstructorArgs.h"
+#include "compiler/translator/SeparateDeclarations.h"
+#include "compiler/translator/SimplifyLoopConditions.h"
 #include "compiler/translator/UnfoldShortCircuitAST.h"
 #include "compiler/translator/UseInterfaceBlockFields.h"
 #include "compiler/translator/ValidateLimitations.h"
@@ -474,6 +477,29 @@
         {
             DeferGlobalInitializers(root);
         }
+
+        if (success && (compileOptions & SH_INITIALIZE_UNINITIALIZED_LOCALS) && getOutputType())
+        {
+            // Initialize uninitialized local variables.
+            // In some cases initializing can generate extra statements in the parent block, such as
+            // when initializing nameless structs or initializing arrays in ESSL 1.00. In that case
+            // we need to first simplify loop conditions and separate declarations. If we don't
+            // follow the Appendix A limitations, loop init statements can declare arrays or
+            // nameless structs and have multiple declarations.
+
+            if (!shouldRunLoopAndIndexingValidation(compileOptions))
+            {
+                SimplifyLoopConditions(root,
+                                       IntermNodePatternMatcher::kMultiDeclaration |
+                                           IntermNodePatternMatcher::kArrayDeclaration |
+                                           IntermNodePatternMatcher::kNamelessStructDeclaration,
+                                       getTemporaryIndex(), getSymbolTable(), getShaderVersion());
+            }
+            // We only really need to separate array declarations and nameless struct declarations,
+            // but it's simpler to just use the regular SeparateDeclarations.
+            SeparateDeclarations(root);
+            InitializeUninitializedLocals(root, getShaderVersion());
+        }
     }
 
     if (success)
diff --git a/src/compiler/translator/InitializeVariables.cpp b/src/compiler/translator/InitializeVariables.cpp
index 0c9a959..11ba6fb 100644
--- a/src/compiler/translator/InitializeVariables.cpp
+++ b/src/compiler/translator/InitializeVariables.cpp
@@ -19,7 +19,70 @@
 namespace
 {
 
-void InsertInitCode(TIntermSequence *sequence,
+bool IsNamelessStruct(const TIntermTyped *node)
+{
+    return (node->getBasicType() == EbtStruct && node->getType().getStruct()->name() == "");
+}
+
+void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
+                              TIntermSequence *initSequenceOut);
+
+TIntermBinary *CreateZeroInitAssignment(const TIntermTyped *initializedNode)
+{
+    TIntermTyped *zero = TIntermTyped::CreateZero(initializedNode->getType());
+    return new TIntermBinary(EOpAssign, initializedNode->deepCopy(), zero);
+}
+
+void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
+                               TIntermSequence *initSequenceOut)
+{
+    ASSERT(initializedNode->getBasicType() == EbtStruct);
+    TStructure *structType = initializedNode->getType().getStruct();
+    for (int i = 0; i < static_cast<int>(structType->fields().size()); ++i)
+    {
+        TIntermBinary *element = new TIntermBinary(
+            EOpIndexDirectStruct, initializedNode->deepCopy(), TIntermTyped::CreateIndexNode(i));
+        if (element->isArray())
+        {
+            AddArrayZeroInitSequence(element, initSequenceOut);
+        }
+        else if (element->getType().isStructureContainingArrays())
+        {
+            AddStructZeroInitSequence(element, initSequenceOut);
+        }
+        else
+        {
+            // Structs can't be defined inside structs, so the type of a struct field can't be a
+            // nameless struct.
+            ASSERT(!IsNamelessStruct(element));
+            initSequenceOut->push_back(CreateZeroInitAssignment(element));
+        }
+    }
+}
+
+void AddArrayZeroInitSequence(const TIntermTyped *initializedNode, TIntermSequence *initSequenceOut)
+{
+    ASSERT(initializedNode->isArray());
+    // Assign the array elements one by one to keep the AST compatible with ESSL 1.00 which
+    // doesn't have array assignment.
+    // Note that it is important to have the array init in the right order to workaround
+    // http://crbug.com/709317
+    for (unsigned int i = 0; i < initializedNode->getArraySize(); ++i)
+    {
+        TIntermBinary *element = new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(),
+                                                   TIntermTyped::CreateIndexNode(i));
+        if (element->getType().isStructureContainingArrays())
+        {
+            AddStructZeroInitSequence(element, initSequenceOut);
+        }
+        else
+        {
+            initSequenceOut->push_back(CreateZeroInitAssignment(element));
+        }
+    }
+}
+
+void InsertInitCode(TIntermSequence *mainBody,
                     const InitVariableList &variables,
                     const TSymbolTable &symbolTable)
 {
@@ -27,62 +90,118 @@
     {
         TString name = TString(var.name.c_str());
 
+        TIntermSymbol *initializedSymbol = nullptr;
         if (var.isArray())
         {
-            // Assign the array elements one by one to keep the AST compatible with ESSL 1.00 which
-            // doesn't have array assignment.
             size_t pos = name.find_last_of('[');
             if (pos != TString::npos)
             {
                 name = name.substr(0, pos);
             }
-            TType elementType = sh::GetShaderVariableBasicType(var);
-            TType arrayType   = elementType;
+            TType arrayType = sh::GetShaderVariableBasicType(var);
             arrayType.setArraySize(var.elementCount());
-
-            // Workaround for http://crbug.com/709317
-            //   This loop is reversed to initialize elements in increasing
-            // order [0 1 2 ...]. Otherwise, they're initialized in
-            // decreasing order [... 2 1 0], due to
-            // `sequence->insert(sequence->begin(), ...)` below.
-            for (unsigned int i = var.arraySize; i > 0; --i)
-            {
-                unsigned int index = i - 1;
-                TIntermSymbol *arraySymbol = new TIntermSymbol(0, name, arrayType);
-                TIntermBinary *element     = new TIntermBinary(EOpIndexDirect, arraySymbol,
-                                                           TIntermTyped::CreateIndexNode(index));
-
-                TIntermTyped *zero        = TIntermTyped::CreateZero(elementType);
-                TIntermBinary *assignment = new TIntermBinary(EOpAssign, element, zero);
-
-                sequence->insert(sequence->begin(), assignment);
-            }
+            initializedSymbol = new TIntermSymbol(0, name, arrayType);
         }
         else if (var.isStruct())
         {
             TVariable *structInfo = reinterpret_cast<TVariable *>(symbolTable.findGlobal(name));
             ASSERT(structInfo);
 
-            TIntermSymbol *symbol = new TIntermSymbol(0, name, structInfo->getType());
-            TIntermTyped *zero    = TIntermTyped::CreateZero(structInfo->getType());
-
-            TIntermBinary *assign = new TIntermBinary(EOpAssign, symbol, zero);
-            sequence->insert(sequence->begin(), assign);
+            initializedSymbol = new TIntermSymbol(0, name, structInfo->getType());
         }
         else
         {
-            TType type            = sh::GetShaderVariableBasicType(var);
-            TIntermSymbol *symbol = new TIntermSymbol(0, name, type);
-            TIntermTyped *zero    = TIntermTyped::CreateZero(type);
-
-            TIntermBinary *assign = new TIntermBinary(EOpAssign, symbol, zero);
-            sequence->insert(sequence->begin(), assign);
+            TType type        = sh::GetShaderVariableBasicType(var);
+            initializedSymbol = new TIntermSymbol(0, name, type);
         }
+        TIntermSequence *initCode = CreateInitCode(initializedSymbol);
+        mainBody->insert(mainBody->begin(), initCode->begin(), initCode->end());
     }
 }
 
+class InitializeLocalsTraverser : public TIntermTraverser
+{
+  public:
+    InitializeLocalsTraverser(int shaderVersion)
+        : TIntermTraverser(true, false, false), mShaderVersion(shaderVersion)
+    {
+    }
+
+  protected:
+    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+    {
+        for (TIntermNode *declarator : *node->getSequence())
+        {
+            if (!mInGlobalScope && !declarator->getAsBinaryNode())
+            {
+                TIntermSymbol *symbol = declarator->getAsSymbolNode();
+                ASSERT(symbol);
+                if (symbol->getSymbol() == "")
+                {
+                    continue;
+                }
+
+                // Arrays may need to be initialized one element at a time, since ESSL 1.00 does not
+                // support array constructors or assigning arrays.
+                bool arrayConstructorUnavailable =
+                    (symbol->isArray() || symbol->getType().isStructureContainingArrays()) &&
+                    mShaderVersion == 100;
+                // Nameless struct constructors can't be referred to, so they also need to be
+                // initialized one element at a time.
+                if (arrayConstructorUnavailable || IsNamelessStruct(symbol))
+                {
+                    // SimplifyLoopConditions should have been run so the parent node of this node
+                    // should not be a loop.
+                    ASSERT(getParentNode()->getAsLoopNode() == nullptr);
+                    // SeparateDeclarations should have already been run, so we don't need to worry
+                    // about further declarators in this declaration depending on the effects of
+                    // this declarator.
+                    ASSERT(node->getSequence()->size() == 1);
+                    insertStatementsInParentBlock(TIntermSequence(), *CreateInitCode(symbol));
+                }
+                else
+                {
+                    TIntermBinary *init = new TIntermBinary(
+                        EOpInitialize, symbol, TIntermTyped::CreateZero(symbol->getType()));
+                    queueReplacementWithParent(node, symbol, init, OriginalNode::BECOMES_CHILD);
+                }
+            }
+        }
+        return false;
+    }
+
+  private:
+    int mShaderVersion;
+};
+
 }  // namespace anonymous
 
+TIntermSequence *CreateInitCode(const TIntermSymbol *initializedSymbol)
+{
+    TIntermSequence *initCode = new TIntermSequence();
+    if (initializedSymbol->isArray())
+    {
+        AddArrayZeroInitSequence(initializedSymbol, initCode);
+    }
+    else if (initializedSymbol->getType().isStructureContainingArrays() ||
+             IsNamelessStruct(initializedSymbol))
+    {
+        AddStructZeroInitSequence(initializedSymbol, initCode);
+    }
+    else
+    {
+        initCode->push_back(CreateZeroInitAssignment(initializedSymbol));
+    }
+    return initCode;
+}
+
+void InitializeUninitializedLocals(TIntermBlock *root, int shaderVersion)
+{
+    InitializeLocalsTraverser traverser(shaderVersion);
+    root->traverse(&traverser);
+    traverser.updateTree();
+}
+
 void InitializeVariables(TIntermBlock *root,
                          const InitVariableList &vars,
                          const TSymbolTable &symbolTable)
diff --git a/src/compiler/translator/InitializeVariables.h b/src/compiler/translator/InitializeVariables.h
index 5b54689..1e6dfba 100644
--- a/src/compiler/translator/InitializeVariables.h
+++ b/src/compiler/translator/InitializeVariables.h
@@ -9,23 +9,31 @@
 
 #include <GLSLANG/ShaderLang.h>
 
+#include "compiler/translator/IntermNode.h"
+
 namespace sh
 {
-class TIntermBlock;
 class TSymbolTable;
 
 typedef std::vector<sh::ShaderVariable> InitVariableList;
 
-// Currently this function is only capable of initializing variables of basic types,
-// array of basic types, or struct of basic types.
+// Return a sequence of assignment operations to initialize "initializedSymbol". initializedSymbol
+// may be an array, struct or any combination of these, as long as it contains only basic types.
+TIntermSequence *CreateInitCode(const TIntermSymbol *initializedSymbol);
+
+// Initialize all uninitialized local variables, so that undefined behavior is avoided.
+void InitializeUninitializedLocals(TIntermBlock *root, int shaderVersion);
+
+// This function can initialize all the types that CreateInitCode is able to initialize. For struct
+// typed variables it requires that the struct is found from the symbolTable, which is usually not
+// the case for locally defined struct types.
 // For now it is used for the following two scenarios:
 //   1. initializing gl_Position;
-//   2. initializing ESSL 3.00 shaders' output variables (which might be structs).
-// Specifically, it's not feasible to make it work for local variables because if their
-// types are structs, we can't look into TSymbolTable to find the TType data.
+//   2. initializing ESSL 3.00 shaders' output variables.
 void InitializeVariables(TIntermBlock *root,
                          const InitVariableList &vars,
                          const TSymbolTable &symbolTable);
+
 }  // namespace sh
 
 #endif  // COMPILER_TRANSLATOR_INITIALIZEVARIABLES_H_
diff --git a/src/compiler/translator/IntermNodePatternMatcher.cpp b/src/compiler/translator/IntermNodePatternMatcher.cpp
index bc0847d..ed2b9f7 100644
--- a/src/compiler/translator/IntermNodePatternMatcher.cpp
+++ b/src/compiler/translator/IntermNodePatternMatcher.cpp
@@ -109,7 +109,35 @@
 {
     if ((mMask & kMultiDeclaration) != 0)
     {
-        return node->getSequence()->size() > 1;
+        if (node->getSequence()->size() > 1)
+        {
+            return true;
+        }
+    }
+    if ((mMask & kArrayDeclaration) != 0)
+    {
+        if (node->getSequence()->front()->getAsTyped()->getType().isStructureContainingArrays())
+        {
+            return true;
+        }
+        // Need to check from all declarators whether they are arrays since that may vary between
+        // declarators.
+        for (TIntermNode *declarator : *node->getSequence())
+        {
+            if (declarator->getAsTyped()->isArray())
+            {
+                return true;
+            }
+        }
+    }
+    if ((mMask & kNamelessStructDeclaration) != 0)
+    {
+        TIntermTyped *declarator = node->getSequence()->front()->getAsTyped();
+        if (declarator->getBasicType() == EbtStruct &&
+            declarator->getType().getStruct()->name() == "")
+        {
+            return true;
+        }
     }
     return false;
 }
diff --git a/src/compiler/translator/IntermNodePatternMatcher.h b/src/compiler/translator/IntermNodePatternMatcher.h
index c156100..e908723 100644
--- a/src/compiler/translator/IntermNodePatternMatcher.h
+++ b/src/compiler/translator/IntermNodePatternMatcher.h
@@ -32,13 +32,19 @@
 
         // Matches expressions that return arrays with the exception of simple statements where a
         // constructor or function call result is assigned.
-        kExpressionReturningArray = 0x0002,
+        kExpressionReturningArray = 0x0001 << 1,
 
         // Matches dynamic indexing of vectors or matrices in l-values.
-        kDynamicIndexingOfVectorOrMatrixInLValue = 0x0004,
+        kDynamicIndexingOfVectorOrMatrixInLValue = 0x0001 << 2,
 
-        // Matches declarations with more than one declared variables
-        kMultiDeclaration = 0x0008,
+        // Matches declarations with more than one declared variables.
+        kMultiDeclaration = 0x0001 << 3,
+
+        // Matches declarations of arrays.
+        kArrayDeclaration = 0x0001 << 4,
+
+        // Matches declarations of structs where the struct type does not have a name.
+        kNamelessStructDeclaration = 0x0001 << 5
     };
     IntermNodePatternMatcher(const unsigned int mask);
 
diff --git a/src/libANGLE/renderer/gl/ShaderGL.cpp b/src/libANGLE/renderer/gl/ShaderGL.cpp
index 8fc7d46..ecb28b7 100644
--- a/src/libANGLE/renderer/gl/ShaderGL.cpp
+++ b/src/libANGLE/renderer/gl/ShaderGL.cpp
@@ -95,6 +95,11 @@
         options |= SH_REWRITE_FLOAT_UNARY_MINUS_OPERATOR;
     }
 
+    if (!mWorkarounds.dontInitializeUninitializedLocals)
+    {
+        options |= SH_INITIALIZE_UNINITIALIZED_LOCALS;
+    }
+
     return options;
 }
 
diff --git a/src/libANGLE/renderer/gl/WorkaroundsGL.h b/src/libANGLE/renderer/gl/WorkaroundsGL.h
index 85bcdb1..7b456ef 100644
--- a/src/libANGLE/renderer/gl/WorkaroundsGL.h
+++ b/src/libANGLE/renderer/gl/WorkaroundsGL.h
@@ -125,6 +125,10 @@
     // This only seems to affect AMD OpenGL drivers.
     // Tracking bug: http://anglebug.com/1936
     bool emulateMaxVertexAttribStride = false;
+
+    // Initializing uninitialized locals caused odd behavior on Mac in a few WebGL 2 tests.
+    // Tracking bug: http://anglebug/2041
+    bool dontInitializeUninitializedLocals = false;
 };
 }  // namespace rx
 
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 68ead9a..5fa8d76 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -962,6 +962,11 @@
     workarounds->doWhileGLSLCausesGPUHang = true;
     workarounds->useUnusedBlocksWithStandardOrSharedLayout = true;
     workarounds->rewriteFloatUnaryMinusOperator            = IsIntel(vendor);
+    workarounds->dontInitializeUninitializedLocals         = true;
+#endif
+
+#if defined(ANGLE_PLATFORM_ANDROID)
+    workarounds->dontInitializeUninitializedLocals = true;
 #endif
 
     workarounds->finishDoesNotCauseQueriesToBeAvailable =
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index 050651b..e254577 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -2823,6 +2823,142 @@
     glDeleteShader(shader);
 }
 
+// Test that uninitialized local variables are initialized to 0.
+TEST_P(GLSLTest_ES3, InitUninitializedLocals)
+{
+    if (IsAndroid() && IsOpenGLES())
+    {
+        // http://anglebug.com/2046
+        std::cout
+            << "Test skipped on Android GLES because local variable initialization is disabled."
+            << std::endl;
+        return;
+    }
+
+    if (IsOSX() && IsOpenGL())
+    {
+        // http://anglebug.com/2041
+        std::cout << "Test skipped on Mac OpenGL because local variable initialization is disabled."
+                  << std::endl;
+        return;
+    }
+
+    const std::string &fragmentShader =
+        "#version 300 es\n"
+        "precision mediump float;\n"
+        "out vec4 my_FragColor;\n"
+        "int result = 0;\n"
+        "void main()\n"
+        "{\n"
+        "    int u;\n"
+        "    result += u;\n"
+        "    int k = 0;\n"
+        "    for (int i[2], j = i[0] + 1; k < 2; ++k)\n"
+        "    {\n"
+        "        result += j;\n"
+        "    }\n"
+        "    if (result == 2)\n"
+        "    {\n"
+        "        my_FragColor = vec4(0, 1, 0, 1);\n"
+        "    }\n"
+        "    else\n"
+        "    {\n"
+        "        my_FragColor = vec4(1, 0, 0, 1);\n"
+        "    }\n"
+        "}\n";
+
+    ANGLE_GL_PROGRAM(program, mSimpleVSSource, fragmentShader);
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
+// Test that uninitialized structs containing arrays of structs are initialized to 0. This
+// specifically tests with two different struct variables declared in the same block.
+TEST_P(GLSLTest, InitUninitializedStructContainingArrays)
+{
+    if (IsAndroid() && IsOpenGLES())
+    {
+        // http://anglebug.com/2046
+        std::cout
+            << "Test skipped on Android GLES because local variable initialization is disabled."
+            << std::endl;
+        return;
+    }
+
+    if (IsOSX() && IsOpenGL())
+    {
+        // http://anglebug.com/2041
+        std::cout << "Test skipped on Mac OpenGL because local variable initialization is disabled."
+                  << std::endl;
+        return;
+    }
+
+    const std::string &fragmentShader =
+        "precision mediump float;\n"
+        "struct T\n"
+        "{\n"
+        "    int a[2];\n"
+        "};\n"
+        "struct S\n"
+        "{\n"
+        "    T t[2];\n"
+        "};\n"
+        "void main()\n"
+        "{\n"
+        "    S s;\n"
+        "    S s2;\n"
+        "    if (s.t[1].a[1] == 0 && s2.t[1].a[1] == 0)\n"
+        "    {\n"
+        "        gl_FragColor = vec4(0, 1, 0, 1);\n"
+        "    }\n"
+        "    else\n"
+        "    {\n"
+        "        gl_FragColor = vec4(1, 0, 0, 1);\n"
+        "    }\n"
+        "}\n";
+
+    ANGLE_GL_PROGRAM(program, mSimpleVSSource, fragmentShader);
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
+// Test that an uninitialized nameless struct inside a for loop init statement works.
+TEST_P(GLSLTest_ES3, UninitializedNamelessStructInForInitStatement)
+{
+    if (IsAndroid() && IsOpenGLES())
+    {
+        // http://anglebug.com/2046
+        std::cout
+            << "Test skipped on Android GLES because local variable initialization is disabled."
+            << std::endl;
+        return;
+    }
+
+    if (IsOSX() && IsOpenGL())
+    {
+        // http://anglebug.com/2041
+        std::cout << "Test skipped on Mac OpenGL because local variable initialization is disabled."
+                  << std::endl;
+        return;
+    }
+
+    const std::string &fragmentShader =
+        "#version 300 es\n"
+        "precision highp float;\n"
+        "out vec4 my_FragColor;\n"
+        "void main()\n"
+        "{\n"
+        "    my_FragColor = vec4(1, 0, 0, 1);\n"
+        "    for (struct { float q; } b; b.q < 2.0; b.q++) {\n"
+        "        my_FragColor = vec4(0, 1, 0, 1);\n"
+        "    }\n"
+        "}\n";
+
+    ANGLE_GL_PROGRAM(program, mSimpleVSSource, fragmentShader);
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
 ANGLE_INSTANTIATE_TEST(GLSLTest,
                        ES2_D3D9(),