Detect when built-in modf requires an l-value in AST traversal

This fixes an omission that out parameter tracking had inherited from
EmulatePrecision. Accurate tracking of when values are written is
required for converting dynamic indexing of vectors and matrices to
function calls.

A new test covering this is added to angle_unittests.

TEST=angle_unittests
BUG=angleproject:1116

Change-Id: I05c5fd60355117d0053b84110748ae221375a790
Reviewed-on: https://chromium-review.googlesource.com/290562
Reviewed-by: Zhenyao Mo <zmo@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/IntermTraverse.cpp b/src/compiler/translator/IntermTraverse.cpp
index d39e779..7f213fc 100644
--- a/src/compiler/translator/IntermTraverse.cpp
+++ b/src/compiler/translator/IntermTraverse.cpp
@@ -6,6 +6,7 @@
 
 #include "compiler/translator/IntermNode.h"
 #include "compiler/translator/InfoSink.h"
+#include "compiler/translator/SymbolTable.h"
 
 void TIntermSymbol::traverse(TIntermTraverser *it)
 {
@@ -473,9 +474,41 @@
             if (node->getOp() == EOpSequence)
                 pushParentBlock(node);
 
+            // Find the built-in function corresponding to this op so that we can determine the
+            // in/out qualifiers of its parameters.
+            TFunction *builtInFunc = nullptr;
+            TString opString = GetOperatorString(node->getOp());
+            if (!node->isConstructor() && !opString.empty())
+            {
+                // The return type doesn't affect the mangled name of the function, which is used
+                // to look it up from the symbol table.
+                TType dummyReturnType;
+                TFunction call(&opString, &dummyReturnType, node->getOp());
+                for (auto *child : *sequence)
+                {
+                    TType *paramType = child->getAsTyped()->getTypePointer();
+                    TConstParameter p(paramType);
+                    call.addParameter(p);
+                }
+
+                TSymbol *sym = mSymbolTable.findBuiltIn(call.getMangledName(), mShaderVersion);
+                if (sym != nullptr && sym->isFunction())
+                {
+                    builtInFunc = static_cast<TFunction *>(sym);
+                    ASSERT(builtInFunc->getParamCount() == sequence->size());
+                }
+            }
+
+            size_t paramIndex = 0;
+
             for (auto *child : *sequence)
             {
+                TQualifier qualifier = EvqIn;
+                if (builtInFunc != nullptr)
+                    qualifier = builtInFunc->getParam(paramIndex).type->getQualifier();
+                setInFunctionCallOutParameter(qualifier == EvqOut || qualifier == EvqInOut);
                 child->traverse(this);
+
                 if (visit && inVisit)
                 {
                     if (child != sequence->back())
@@ -484,8 +517,12 @@
 
                 if (node->getOp() == EOpSequence)
                     incrementParentBlockPos();
+
+                ++paramIndex;
             }
 
+            setInFunctionCallOutParameter(false);
+
             if (node->getOp() == EOpSequence)
                 popParentBlock();
         }