Check that texture offset is constant and valid

Offset passed to textureOffset and similar functions must be constant.
See ESSL 3.00 spec section 8.8.

It must also be in the valid range between MIN_PROGRAM_TEXEL_OFFSET and
MAX_PROGRAM_TEXEL_OFFSET. Using values outside the valid range makes
the results of the texture lookup undefined, as specified in GLES 3.0.4
section 3.8.10. We generate a compiler error if an offset is outside the
valid range.

BUG=angleproject:1215
TEST=angle_unittests

Change-Id: Ida28361444d2f4050d516160f1491674c31868a1
Reviewed-on: https://chromium-review.googlesource.com/312223
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 46dddc6..9002ce6 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -213,11 +213,9 @@
         ++firstSource;
     }
 
-    bool debugShaderPrecision = getResources().WEBGL_debug_shader_precision == 1;
     TIntermediate intermediate(infoSink);
-    TParseContext parseContext(symbolTable, extensionBehavior, intermediate,
-                               shaderType, shaderSpec, compileOptions, true,
-                               infoSink, debugShaderPrecision);
+    TParseContext parseContext(symbolTable, extensionBehavior, intermediate, shaderType, shaderSpec,
+                               compileOptions, true, infoSink, getResources());
 
     parseContext.setFragmentPrecisionHighOnESSL1(fragmentPrecisionHigh);
     SetGlobalParseContext(&parseContext);
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 6b9ba3f..8e4d487 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -3767,6 +3767,59 @@
     return intermediate.addBranch(op, returnValue, loc);
 }
 
+void TParseContext::checkTextureOffsetConst(TIntermAggregate *functionCall)
+{
+    ASSERT(!functionCall->isUserDefined());
+    const TString &name        = functionCall->getName();
+    TIntermNode *offset        = nullptr;
+    TIntermSequence *arguments = functionCall->getSequence();
+    if (name.compare(0, 16, "texelFetchOffset") == 0 ||
+        name.compare(0, 16, "textureLodOffset") == 0 ||
+        name.compare(0, 20, "textureProjLodOffset") == 0 ||
+        name.compare(0, 17, "textureGradOffset") == 0 ||
+        name.compare(0, 21, "textureProjGradOffset") == 0)
+    {
+        offset = arguments->back();
+    }
+    else if (name.compare(0, 13, "textureOffset") == 0 ||
+             name.compare(0, 17, "textureProjOffset") == 0)
+    {
+        // A bias parameter might follow the offset parameter.
+        ASSERT(arguments->size() >= 3);
+        offset = (*arguments)[2];
+    }
+    if (offset != nullptr)
+    {
+        TIntermConstantUnion *offsetConstantUnion = offset->getAsConstantUnion();
+        if (offset->getAsTyped()->getQualifier() != EvqConst || !offsetConstantUnion)
+        {
+            TString unmangledName = TFunction::unmangleName(name);
+            error(functionCall->getLine(), "Texture offset must be a constant expression",
+                  unmangledName.c_str());
+            recover();
+        }
+        else
+        {
+            ASSERT(offsetConstantUnion->getBasicType() == EbtInt);
+            size_t size                  = offsetConstantUnion->getType().getObjectSize();
+            const TConstantUnion *values = offsetConstantUnion->getUnionArrayPointer();
+            for (size_t i = 0u; i < size; ++i)
+            {
+                int offsetValue = values[i].getIConst();
+                if (offsetValue > mMaxProgramTexelOffset || offsetValue < mMinProgramTexelOffset)
+                {
+                    std::stringstream tokenStream;
+                    tokenStream << offsetValue;
+                    std::string token = tokenStream.str();
+                    error(offset->getLine(), "Texture offset value out of valid range",
+                          token.c_str());
+                    recover();
+                }
+            }
+        }
+    }
+}
+
 TIntermTyped *TParseContext::addFunctionCallOrMethod(TFunction *fnCall,
                                                      TIntermNode *paramNode,
                                                      TIntermNode *thisNode,
@@ -3935,8 +3988,12 @@
 
                 // This needs to happen after the name is set
                 if (builtIn)
+                {
                     aggregate->setBuiltInFunctionPrecision();
 
+                    checkTextureOffsetConst(aggregate);
+                }
+
                 callNode = aggregate;
 
                 functionCallLValueErrorCheck(fnCandidate, aggregate);
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index 1b86a6a..da7c420 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -36,7 +36,7 @@
                   int options,
                   bool checksPrecErrors,
                   TInfoSink &is,
-                  bool debugShaderPrecisionSupported)
+                  const ShBuiltInResources &resources)
         : intermediate(interm),
           symbolTable(symt),
           mDeferredSingleDeclarationErrorCheck(false),
@@ -54,12 +54,17 @@
           mDefaultMatrixPacking(EmpColumnMajor),
           mDefaultBlockStorage(EbsShared),
           mDiagnostics(is),
-          mDirectiveHandler(ext, mDiagnostics, mShaderVersion, debugShaderPrecisionSupported),
+          mDirectiveHandler(ext,
+                            mDiagnostics,
+                            mShaderVersion,
+                            resources.WEBGL_debug_shader_precision == 1),
           mPreprocessor(&mDiagnostics, &mDirectiveHandler),
           mScanner(nullptr),
           mUsesFragData(false),
           mUsesFragColor(false),
-          mUsesSecondaryOutputs(false)
+          mUsesSecondaryOutputs(false),
+          mMinProgramTexelOffset(resources.MinProgramTexelOffset),
+          mMaxProgramTexelOffset(resources.MaxProgramTexelOffset)
     {
     }
 
@@ -324,6 +329,7 @@
     TIntermBranch *addBranch(TOperator op, const TSourceLoc &loc);
     TIntermBranch *addBranch(TOperator op, TIntermTyped *returnValue, const TSourceLoc &loc);
 
+    void checkTextureOffsetConst(TIntermAggregate *functionCall);
     TIntermTyped *addFunctionCallOrMethod(TFunction *fnCall,
                                           TIntermNode *paramNode,
                                           TIntermNode *thisNode,
@@ -381,6 +387,8 @@
     bool mUsesFragColor;
     bool mUsesSecondaryOutputs;  // Track if we are using either gl_SecondaryFragData or
                                  // gl_Secondary FragColor or both.
+    int mMinProgramTexelOffset;
+    int mMaxProgramTexelOffset;
 };
 
 int PaParseStrings(