Edit

kc3-lang/angle/src/compiler/preprocessor/ExpressionParser.y

Branch :

  • Show log

    Commit

  • Author : Olli Etuaho
    Date : 2016-10-03 14:32:08
    Hash : 7f9a55f7
    Message : Fix integer math overflows in the preprocessor Evaluating integer expressions in the ESSL preprocessor may result in overflowing the signed integer range. Implement wrapping overflow for preprocessor expressions in a way that doesn't hit any undefined behavior. In the ESSL spec, preprocessor expressions are defined to have mostly the same semantics as in C++. Since C++ doesn't define what happens on signed integer overflow, we choose to make most of the operators wrap on overflow for backward compatibility and consistency with the rest of the ESSL spec. We reuse the existing wrapping overflow helpers that are used for constant folding. To be able to do this, the type used in the preprocessor expression parser is changed from 64-bit to 32-bit. Shifting negative numbers is implemented as a logical shift. This cannot be disallowed since dEQP requires shaders shifting negative numbers to pass compilation. Undefined bitwise shifts where the offset is greater than 31 will now result in a compile-time error. A couple of test cases are now covered by the preprocessor tests rather than full compilation tests. This isolates the tests better and they run faster. BUG=chromium:652223 TEST=angle_unittests Change-Id: I84be40d404c10ecd0846c5d477e626a94a2a8587 Reviewed-on: https://chromium-review.googlesource.com/392146 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: Jamie Madill <jmadill@chromium.org>

  • src/compiler/preprocessor/ExpressionParser.y
  • /*
    //
    // Copyright (c) 2012 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    This file contains the Yacc grammar for GLSL ES preprocessor expression.
    
    IF YOU MODIFY THIS FILE YOU ALSO NEED TO RUN generate_parser.sh,
    WHICH GENERATES THE GLSL ES preprocessor expression parser.
    */
    
    %{
    //
    // Copyright (c) 2012 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    // This file is auto-generated by generate_parser.sh. DO NOT EDIT!
    
    #if defined(__GNUC__)
    // Triggered by the auto-generated pplval variable.
    #if !defined(__clang__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))
    #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
    #else
    #pragma GCC diagnostic ignored "-Wuninitialized"
    #endif
    #elif defined(_MSC_VER)
    #pragma warning(disable: 4065 4244 4701 4702)
    #endif
    
    #include "ExpressionParser.h"
    
    #if defined(_MSC_VER)
    #include <malloc.h>
    #else
    #include <stdlib.h>
    #endif
    
    #include <cassert>
    #include <sstream>
    #include <stdint.h>
    
    #include "DiagnosticsBase.h"
    #include "Lexer.h"
    #include "Token.h"
    #include "common/mathutil.h"
    
    typedef int32_t YYSTYPE;
    typedef uint32_t UNSIGNED_TYPE;
    
    #define YYENABLE_NLS 0
    #define YYLTYPE_IS_TRIVIAL 1
    #define YYSTYPE_IS_TRIVIAL 1
    #define YYSTYPE_IS_DECLARED 1
    
    namespace {
    struct Context
    {
        pp::Diagnostics* diagnostics;
        pp::Lexer* lexer;
        pp::Token* token;
        int* result;
        bool parsePresetToken;
    
        pp::ExpressionParser::ErrorSettings errorSettings;
        bool *valid;
    
        void startIgnoreErrors() { ++ignoreErrors; }
        void endIgnoreErrors() { --ignoreErrors; }
    
        bool isIgnoringErrors() { return ignoreErrors > 0; }
    
        int ignoreErrors;
    };
    }  // namespace
    %}
    
    %pure-parser
    %name-prefix "pp"
    %parse-param {Context *context}
    %lex-param {Context *context}
    
    %{
    static int yylex(YYSTYPE* lvalp, Context* context);
    static void yyerror(Context* context, const char* reason);
    %}
    
    %token TOK_CONST_INT
    %token TOK_IDENTIFIER
    %left TOK_OP_OR
    %left TOK_OP_AND
    %left '|'
    %left '^'
    %left '&'
    %left TOK_OP_EQ TOK_OP_NE
    %left '<' '>' TOK_OP_LE TOK_OP_GE
    %left TOK_OP_LEFT TOK_OP_RIGHT
    %left '+' '-'
    %left '*' '/' '%'
    %right TOK_UNARY
    
    %%
    
    input
        : expression {
            *(context->result) = static_cast<int>($1);
            YYACCEPT;
        }
    ;
    
    expression
        : TOK_CONST_INT
        | TOK_IDENTIFIER {
            if (!context->isIgnoringErrors())
            {
                // This rule should be applied right after the token is lexed, so we can
                // refer to context->token in the error message.
                context->diagnostics->report(context->errorSettings.unexpectedIdentifier,
                                             context->token->location, context->token->text);
                *(context->valid) = false;
            }
            $$ = $1;
        }
        | expression TOK_OP_OR {
            if ($1 != 0)
            {
                // Ignore errors in the short-circuited part of the expression.
                // ESSL3.00 section 3.4:
                // If an operand is not evaluated, the presence of undefined identifiers
                // in the operand will not cause an error.
                // Unevaluated division by zero should not cause an error either.
                context->startIgnoreErrors();
            }
        } expression {
            if ($1 != 0)
            {
                context->endIgnoreErrors();
                $$ = static_cast<YYSTYPE>(1);
            }
            else
            {
                $$ = $1 || $4;
            }
        }
        | expression TOK_OP_AND {
            if ($1 == 0)
            {
                // Ignore errors in the short-circuited part of the expression.
                // ESSL3.00 section 3.4:
                // If an operand is not evaluated, the presence of undefined identifiers
                // in the operand will not cause an error.
                // Unevaluated division by zero should not cause an error either.
                context->startIgnoreErrors();
            }
        } expression {
            if ($1 == 0)
            {
                context->endIgnoreErrors();
                $$ = static_cast<YYSTYPE>(0);
            }
            else
            {
                $$ = $1 && $4;
            }
        }
        | expression '|' expression {
            $$ = $1 | $3;
        }
        | expression '^' expression {
            $$ = $1 ^ $3;
        }
        | expression '&' expression {
            $$ = $1 & $3;
        }
        | expression TOK_OP_NE expression {
            $$ = $1 != $3;
        }
        | expression TOK_OP_EQ expression {
            $$ = $1 == $3;
        }
        | expression TOK_OP_GE expression {
            $$ = $1 >= $3;
        }
        | expression TOK_OP_LE expression {
            $$ = $1 <= $3;
        }
        | expression '>' expression {
            $$ = $1 > $3;
        }
        | expression '<' expression {
            $$ = $1 < $3;
        }
        | expression TOK_OP_RIGHT expression {
            if ($3 < 0 || $3 > 31)
            {
                if (!context->isIgnoringErrors())
                {
                    std::ostringstream stream;
                    stream << $1 << " >> " << $3;
                    std::string text = stream.str();
                    context->diagnostics->report(pp::Diagnostics::PP_UNDEFINED_SHIFT,
                                                 context->token->location,
                                                 text.c_str());
                    *(context->valid) = false;
                }
                $$ = static_cast<YYSTYPE>(0);
            }
            else if ($1 < 0)
            {
                // Logical shift right.
                $$ = static_cast<YYSTYPE>(static_cast<UNSIGNED_TYPE>($1) >> $3);
            }
            else
            {
                $$ = $1 >> $3;
            }
        }
        | expression TOK_OP_LEFT expression {
            if ($3 < 0 || $3 > 31)
            {
                if (!context->isIgnoringErrors())
                {
                    std::ostringstream stream;
                    stream << $1 << " << " << $3;
                    std::string text = stream.str();
                    context->diagnostics->report(pp::Diagnostics::PP_UNDEFINED_SHIFT,
                                                 context->token->location,
                                                 text.c_str());
                    *(context->valid) = false;
                }
                $$ = static_cast<YYSTYPE>(0);
            }
            else if ($1 < 0)
            {
                // Logical shift left.
                $$ = static_cast<YYSTYPE>(static_cast<UNSIGNED_TYPE>($1) << $3);
            }
            else
            {
                $$ = $1 << $3;
            }
        }
        | expression '-' expression {
            $$ = gl::WrappingDiff<YYSTYPE>($1, $3);
        }
        | expression '+' expression {
            $$ = gl::WrappingSum<YYSTYPE>($1, $3);
        }
        | expression '%' expression {
            if ($3 == 0)
            {
                if (!context->isIgnoringErrors())
                {
                    std::ostringstream stream;
                    stream << $1 << " % " << $3;
                    std::string text = stream.str();
                    context->diagnostics->report(pp::Diagnostics::PP_DIVISION_BY_ZERO,
                                                 context->token->location,
                                                 text.c_str());
                    *(context->valid) = false;
                }
                $$ = static_cast<YYSTYPE>(0);
            }
            else
            {
                $$ = $1 % $3;
            }
        }
        | expression '/' expression {
            if ($3 == 0)
            {
                if (!context->isIgnoringErrors())
                {
                    std::ostringstream stream;
                    stream << $1 << " / " << $3;
                    std::string text = stream.str();
                    context->diagnostics->report(pp::Diagnostics::PP_DIVISION_BY_ZERO,
                                                context->token->location,
                                                text.c_str());
                    *(context->valid) = false;
                }
                $$ = static_cast<YYSTYPE>(0);
            }
            else if ($1 == std::numeric_limits<YYSTYPE>::min() && $3 == -1)
            {
                // Check for the special case where the minimum representable number is
                // divided by -1. If left alone this leads to integer overflow in C++, which
                // has undefined results.
                $$ = std::numeric_limits<YYSTYPE>::max();
            }
            else
            {
                $$ = $1 / $3;
            }
        }
        | expression '*' expression {
            $$ = gl::WrappingMul($1, $3);
        }
        | '!' expression %prec TOK_UNARY {
            $$ = ! $2;
        }
        | '~' expression %prec TOK_UNARY {
            $$ = ~ $2;
        }
        | '-' expression %prec TOK_UNARY {
            // Check for negation of minimum representable integer to prevent undefined signed int
            // overflow.
            if ($2 == std::numeric_limits<YYSTYPE>::min())
            {
                $$ = std::numeric_limits<YYSTYPE>::min();
            }
            else
            {
                $$ = -$2;
            }
        }
        | '+' expression %prec TOK_UNARY {
            $$ = + $2;
        }
        | '(' expression ')' {
            $$ = $2;
        }
    ;
    
    %%
    
    int yylex(YYSTYPE *lvalp, Context *context)
    {
        pp::Token *token = context->token;
        if (!context->parsePresetToken)
        {
            context->lexer->lex(token);
        }
        context->parsePresetToken = false;
    
        int type = 0;
    
        switch (token->type)
        {
          case pp::Token::CONST_INT: {
            unsigned int val = 0;
            int testVal = 0;
            if (!token->uValue(&val) || (!token->iValue(&testVal) &&
                                         context->errorSettings.integerLiteralsMustFit32BitSignedRange))
            {
                context->diagnostics->report(pp::Diagnostics::PP_INTEGER_OVERFLOW,
                                             token->location, token->text);
                *(context->valid) = false;
            }
            *lvalp = static_cast<YYSTYPE>(val);
            type = TOK_CONST_INT;
            break;
          }
          case pp::Token::IDENTIFIER:
            *lvalp = static_cast<YYSTYPE>(-1);
            type = TOK_IDENTIFIER;
            break;
          case pp::Token::OP_OR:
            type = TOK_OP_OR;
            break;
          case pp::Token::OP_AND:
            type = TOK_OP_AND;
            break;
          case pp::Token::OP_NE:
            type = TOK_OP_NE;
            break;
          case pp::Token::OP_EQ:
            type = TOK_OP_EQ;
            break;
          case pp::Token::OP_GE:
            type = TOK_OP_GE;
            break;
          case pp::Token::OP_LE:
            type = TOK_OP_LE;
            break;
          case pp::Token::OP_RIGHT:
            type = TOK_OP_RIGHT;
            break;
          case pp::Token::OP_LEFT:
            type = TOK_OP_LEFT;
            break;
          case '|':
          case '^':
          case '&':
          case '>':
          case '<':
          case '-':
          case '+':
          case '%':
          case '/':
          case '*':
          case '!':
          case '~':
          case '(':
          case ')':
            type = token->type;
            break;
    
          default:
            break;
        }
    
        return type;
    }
    
    void yyerror(Context *context, const char *reason)
    {
        context->diagnostics->report(pp::Diagnostics::PP_INVALID_EXPRESSION,
                                     context->token->location,
                                     reason);
    }
    
    namespace pp {
    
    ExpressionParser::ExpressionParser(Lexer *lexer, Diagnostics *diagnostics)
        : mLexer(lexer),
          mDiagnostics(diagnostics)
    {
    }
    
    bool ExpressionParser::parse(Token *token,
                                 int *result,
                                 bool parsePresetToken,
                                 const ErrorSettings &errorSettings,
                                 bool *valid)
    {
        Context context;
        context.diagnostics = mDiagnostics;
        context.lexer = mLexer;
        context.token = token;
        context.result = result;
        context.ignoreErrors = 0;
        context.parsePresetToken = parsePresetToken;
        context.errorSettings    = errorSettings;
        context.valid            = valid;
        int ret = yyparse(&context);
        switch (ret)
        {
          case 0:
          case 1:
            break;
    
          case 2:
            mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token->location, "");
            break;
    
          default:
            assert(false);
            mDiagnostics->report(Diagnostics::PP_INTERNAL_ERROR, token->location, "");
            break;
        }
    
        return ret == 0;
    }
    
    }  // namespace pp