Extract validation from VertexDataManager.cpp to the API.

We can check for buffer overflow at draw validation time, before
processing any vertex data.

BUG=angle:571

Change-Id: I4f49629b98c17ca28e25baed74cad4ae5341b20f
Reviewed-on: https://chromium-review.googlesource.com/210647
Tested-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Shannon Woods <shannonwoods@chromium.org>
diff --git a/src/libGLESv2/validationES.cpp b/src/libGLESv2/validationES.cpp
index 7688780..4b9ff1c 100644
--- a/src/libGLESv2/validationES.cpp
+++ b/src/libGLESv2/validationES.cpp
@@ -20,6 +20,7 @@
 #include "libGLESv2/ProgramBinary.h"
 #include "libGLESv2/TransformFeedback.h"
 #include "libGLESv2/VertexArray.h"
+#include "libGLESv2/renderer/BufferImpl.h"
 
 #include "common/mathutil.h"
 #include "common/utilities.h"
@@ -1301,7 +1302,7 @@
     return true;
 }
 
-static bool ValidateDrawBase(const gl::State &state, GLenum mode, GLsizei count)
+static bool ValidateDrawBase(const gl::State &state, GLenum mode, GLsizei count, GLsizei maxVertex, GLsizei primcount)
 {
     switch (mode)
     {
@@ -1357,11 +1358,55 @@
         return gl::error(GL_INVALID_OPERATION, false);
     }
 
+    // Buffer validations
+    const VertexArray *vao = state.getVertexArray();
+    for (int attributeIndex = 0; attributeIndex < MAX_VERTEX_ATTRIBS; attributeIndex++)
+    {
+        const VertexAttribute &attrib = vao->getVertexAttribute(attributeIndex);
+        bool attribActive = (programBinary->getSemanticIndex(attributeIndex) != -1);
+        if (attribActive && attrib.enabled)
+        {
+            gl::Buffer *buffer = attrib.buffer.get();
+
+            if (buffer)
+            {
+                GLint64 attribStride = static_cast<GLint64>(ComputeVertexAttributeStride(attrib));
+                GLint64 maxVertexElement = 0;
+
+                if (attrib.divisor > 0)
+                {
+                    maxVertexElement = static_cast<GLint64>(primcount) / static_cast<GLint64>(attrib.divisor);
+                }
+                else
+                {
+                    maxVertexElement = static_cast<GLint64>(maxVertex);
+                }
+
+                GLint64 attribDataSize = maxVertexElement * attribStride;
+
+                // [OpenGL ES 3.0.2] section 2.9.4 page 40:
+                // We can return INVALID_OPERATION if our vertex attribute does not have
+                // enough backing data.
+                if (attribDataSize > buffer->getSize())
+                {
+                    return gl::error(GL_INVALID_OPERATION, false);
+                }
+            }
+            else if (attrib.pointer == NULL)
+            {
+                // This is an application error that would normally result in a crash,
+                // but we catch it and return an error
+                ERR("An enabled vertex array has no buffer and no pointer.");
+                return gl::error(GL_INVALID_OPERATION, false);
+            }
+        }
+    }
+
     // No-op if zero count
     return (count > 0);
 }
 
-bool ValidateDrawArrays(const gl::Context *context, GLenum mode, GLint first, GLsizei count)
+bool ValidateDrawArrays(const gl::Context *context, GLenum mode, GLint first, GLsizei count, GLsizei primcount)
 {
     if (first < 0)
     {
@@ -1379,7 +1424,7 @@
         return gl::error(GL_INVALID_OPERATION, false);
     }
 
-    if (!ValidateDrawBase(state, mode, count))
+    if (!ValidateDrawBase(state, mode, count, count, primcount))
     {
         return false;
     }
@@ -1394,7 +1439,7 @@
         return gl::error(GL_INVALID_VALUE, false);
     }
 
-    if (!ValidateDrawArrays(context, mode, first, count))
+    if (!ValidateDrawArrays(context, mode, first, count, primcount))
     {
         return false;
     }
@@ -1403,7 +1448,8 @@
     return (primcount > 0);
 }
 
-bool ValidateDrawElements(const gl::Context *context, GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)
+bool ValidateDrawElements(const gl::Context *context, GLenum mode, GLsizei count, GLenum type,
+                          const GLvoid* indices, GLsizei primcount, rx::RangeUI *indexRangeOut)
 {
     switch (type)
     {
@@ -1436,13 +1482,32 @@
         return gl::error(GL_INVALID_OPERATION, false);
     }
 
-    gl::VertexArray *vao = state.getVertexArray();
-    if (!indices && !vao->getElementArrayBuffer())
+    const gl::VertexArray *vao = state.getVertexArray();
+    const gl::Buffer *elementArrayBuffer = vao->getElementArrayBuffer();
+    if (!indices && !elementArrayBuffer)
     {
         return gl::error(GL_INVALID_OPERATION, false);
     }
 
-    if (!ValidateDrawBase(state, mode, count))
+    // Use max index to validate if our vertex buffers are large enough for the pull.
+    // TODO: offer fast path, with disabled index validation.
+    // TODO: also disable index checking on back-ends that are robust to out-of-range accesses.
+    if (elementArrayBuffer)
+    {
+        unsigned int offset = reinterpret_cast<unsigned int>(indices);
+        if (!elementArrayBuffer->getIndexRangeCache()->findRange(type, offset, count, indexRangeOut, NULL))
+        {
+            const void *dataPointer = elementArrayBuffer->getImplementation()->getData();
+            const uint8_t *offsetPointer = static_cast<const uint8_t *>(dataPointer) + offset;
+            *indexRangeOut = rx::IndexRangeCache::ComputeRange(type, offsetPointer, count);
+        }
+    }
+    else
+    {
+        *indexRangeOut = rx::IndexRangeCache::ComputeRange(type, indices, count);
+    }
+
+    if (!ValidateDrawBase(state, mode, count, static_cast<GLsizei>(indexRangeOut->end), primcount))
     {
         return false;
     }
@@ -1450,15 +1515,17 @@
     return true;
 }
 
-bool ValidateDrawElementsInstanced(const gl::Context *context, GLenum mode, GLsizei count, GLenum type,
-                                   const GLvoid *indices, GLsizei primcount)
+bool ValidateDrawElementsInstanced(const gl::Context *context,
+                                   GLenum mode, GLsizei count, GLenum type,
+                                   const GLvoid *indices, GLsizei primcount,
+                                   rx::RangeUI *indexRangeOut)
 {
     if (primcount < 0)
     {
         return gl::error(GL_INVALID_VALUE, false);
     }
 
-    if (!ValidateDrawElements(context, mode, count, type, indices))
+    if (!ValidateDrawElements(context, mode, count, type, indices, primcount, indexRangeOut))
     {
         return false;
     }