Program::getUniformInternal: return only one array element

When getUniformInternal detected a mismatch between the glGetUniform
type and the uniform type, it entered a code path where it wrongly wrote
the whole array instead of a single element, causing a buffer overflow.

Adds a regression test.

BUG=595836

Change-Id: Id7372faece276d28363a30bf3183497d97357c76
Reviewed-on: https://chromium-review.googlesource.com/333771
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index bd16fd9..b4aa8dc 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -2508,7 +2508,7 @@
         return;
     }
 
-    int components = VariableComponentCount(uniform.type) * uniform.elementCount();
+    int components = VariableComponentCount(uniform.type);
 
     switch (componentType)
     {
diff --git a/src/tests/gl_tests/UniformTest.cpp b/src/tests/gl_tests/UniformTest.cpp
index 9d1bd8c..a6d19d5 100644
--- a/src/tests/gl_tests/UniformTest.cpp
+++ b/src/tests/gl_tests/UniformTest.cpp
@@ -6,6 +6,7 @@
 
 #include "test_utils/ANGLETest.h"
 
+#include <array>
 #include <cmath>
 
 using namespace angle;
@@ -500,6 +501,117 @@
     glDeleteProgram(program);
 }
 
+template <typename T>
+void CheckOneElement(void (*getUniformv) (GLuint, int, T *),
+                     GLuint program,
+                     const std::string& name,
+                     int components,
+                     T canary)
+{
+    // The buffer getting the results has three chunks
+    //  - A chunk to see underflows
+    //  - A chunk that will hold the result
+    //  - A chunk to see overflows for when components = kChunkSize
+    static const size_t kChunkSize = 4;
+    std::array<T, 3 * kChunkSize> buffer;
+    buffer.fill(canary);
+
+    GLint location = glGetUniformLocation(program, name.c_str());
+    ASSERT_NE(location, -1);
+
+    getUniformv(program, location, &buffer[kChunkSize]);
+    for (size_t i = 0; i < kChunkSize; i++)
+    {
+        ASSERT_EQ(canary, buffer[i]);
+    }
+    for (size_t i = kChunkSize + components; i < buffer.size(); i++)
+    {
+        ASSERT_EQ(canary, buffer[i]);
+    }
+}
+
+// Check that getting an element array doesn't return the whole array.
+TEST_P(UniformTestES3, ReturnsOnlyOneArrayElement)
+{
+    static const size_t kArraySize = 4;
+    struct UniformArrayInfo
+    {
+        UniformArrayInfo(std::string type, std::string name, int components)
+            : type(type), name(name), components(components){}
+        std::string type;
+        std::string name;
+        int components;
+    };
+
+    // Check for various number of components and types
+    std::vector<UniformArrayInfo> uniformArrays;
+    uniformArrays.emplace_back("bool", "uBool", 1);
+    uniformArrays.emplace_back("vec2", "uFloat", 2);
+    uniformArrays.emplace_back("ivec3", "uInt", 3);
+    uniformArrays.emplace_back("uvec4", "uUint", 4);
+
+    std::ostringstream uniformStream;
+    std::ostringstream additionStream;
+    for (const auto &array : uniformArrays)
+    {
+        uniformStream << "uniform " << array.type << " " << array.name << "[" << std::to_string(kArraySize) << "];\n";
+
+        // We need to make use of the uniforms or they get compiled out.
+        for (int i = 0; i < 4; i++)
+        {
+            if (array.components == 1)
+            {
+                additionStream << " + float(" << array.name << "[" << i << "])";
+            }
+            else
+            {
+                for (int component = 0; component < array.components; component++)
+                {
+                    additionStream << " + float(" << array.name << "[" << i << "][" << component
+                                   << "])";
+                }
+            }
+        }
+    }
+
+    const std::string &vertexShader =
+        "#version 300 es\n" +
+        uniformStream.str() +
+        "void main()\n"
+        "{\n"
+        "    gl_Position = vec4(1.0" + additionStream.str() + ");\n"
+        "}";
+
+    const std::string &fragmentShader =
+        "#version 300 es\n"
+        "precision mediump float;\n"
+        "out vec4 color;\n"
+        "void main ()\n"
+        "{\n"
+        "    color = vec4(1, 0, 0, 1);\n"
+        "}";
+
+    GLuint program = CompileProgram(vertexShader, fragmentShader);
+    ASSERT_NE(0u, program);
+
+    for (const auto &uniformArray : uniformArrays)
+    {
+        for (size_t index = 0; index < kArraySize; index++)
+        {
+            std::string strIndex = "[" + std::to_string(index) + "]";
+            // Check all the different glGetUniformv functions
+            CheckOneElement<float>(glGetUniformfv, program, uniformArray.name + strIndex,
+                                   uniformArray.components, 42.4242f);
+            CheckOneElement<int>(glGetUniformiv, program, uniformArray.name + strIndex,
+                                 uniformArray.components, 0x7BADBED5);
+            CheckOneElement<unsigned int>(glGetUniformuiv, program, uniformArray.name + strIndex,
+                                          uniformArray.components, 0xDEADBEEF);
+        }
+    }
+
+    glDeleteProgram(program);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
 ANGLE_INSTANTIATE_TEST(UniformTest,
                        ES2_D3D9(),