Commit 809ec546 by Olli Etuaho

Don't evaluate short-circuited preprocessor expressions

Resubmit with clang build issue fixed. The result of a short-circuited operation is now either 0 or 1. ESSL 3.00 spec section 3.4 mentions that the second operand in a logical && or || preprocessor operation is evaluated only if the first operand doesn't short-circuit the expression. The non-evaluated part of a preprocessor expression may also have undefined identifiers. Make the expression parser follow the spec by ignoring errors that are generated inside short-circuited expressions. This includes undefined identifiers and divide by zero. BUG=angleproject:347 TEST=dEQP-GLES3.functional.shaders.preprocessor.undefined_identifiers.* angle_unittests Change-Id: I4163f96ec46d40ac859ffb39d91b89490041e44d Reviewed-on: https://chromium-review.googlesource.com/297252Tested-by: 's avatarOlli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 9a1b49f7
......@@ -64,6 +64,13 @@ struct Context
pp::Lexer* lexer;
pp::Token* token;
int* result;
void startIgnoreErrors() { ++ignoreErrors; }
void endIgnoreErrors() { --ignoreErrors; }
bool isIgnoringErrors() { return ignoreErrors > 0; }
int ignoreErrors;
};
} // namespace
%}
......@@ -79,6 +86,7 @@ static void yyerror(Context* context, const char* reason);
%}
%token TOK_CONST_INT
%token TOK_IDENTIFIER
%left TOK_OP_OR
%left TOK_OP_AND
%left '|'
......@@ -96,17 +104,58 @@ static void yyerror(Context* context, const char* reason);
input
: expression {
*(context->result) = static_cast<int>($1);
YYACCEPT;
}
;
expression
: TOK_CONST_INT
| expression TOK_OP_OR expression {
$$ = $1 || $3;
| TOK_IDENTIFIER {
if (!context->isIgnoringErrors())
{
YYABORT;
}
}
| expression TOK_OP_AND expression {
$$ = $1 && $3;
| 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;
......@@ -148,28 +197,48 @@ expression
$$ = $1 + $3;
}
| expression '%' expression {
if ($3 == 0) {
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());
YYABORT;
} else {
if ($3 == 0)
{
if (context->isIgnoringErrors())
{
$$ = static_cast<YYSTYPE>(0);
}
else
{
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());
YYABORT;
}
}
else
{
$$ = $1 % $3;
}
}
| expression '/' expression {
if ($3 == 0) {
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());
YYABORT;
} else {
if ($3 == 0)
{
if (context->isIgnoringErrors())
{
$$ = static_cast<YYSTYPE>(0);
}
else
{
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());
YYABORT;
}
}
else
{
$$ = $1 / $3;
}
}
......@@ -213,6 +282,15 @@ int yylex(YYSTYPE *lvalp, Context *context)
type = TOK_CONST_INT;
break;
}
case pp::Token::IDENTIFIER:
if (!context->isIgnoringErrors())
{
context->diagnostics->report(pp::Diagnostics::PP_CONDITIONAL_UNEXPECTED_TOKEN,
token->location, token->text);
}
*lvalp = static_cast<YYSTYPE>(-1);
type = TOK_IDENTIFIER;
break;
case pp::Token::OP_OR:
type = TOK_OP_OR;
break;
......@@ -287,6 +365,7 @@ bool ExpressionParser::parse(Token *token, int *result)
context.lexer = mLexer;
context.token = token;
context.result = result;
context.ignoreErrors = 0;
int ret = yyparse(&context);
switch (ret)
{
......
......@@ -697,10 +697,6 @@ TEST_F(IfTest, UndefinedMacro)
ASSERT_TRUE(mPreprocessor.init(1, &str, 0));
EXPECT_CALL(mDiagnostics,
print(pp::Diagnostics::PP_INVALID_EXPRESSION,
pp::SourceLocation(0, 1),
"syntax error"));
EXPECT_CALL(mDiagnostics,
print(pp::Diagnostics::PP_CONDITIONAL_UNEXPECTED_TOKEN,
pp::SourceLocation(0, 1),
"UNDEFINED"));
......@@ -833,3 +829,65 @@ TEST_F(IfTest, UnterminatedIfdef)
mPreprocessor.lex(&token);
}
// The preprocessor only allows one expression to follow an #if directive.
// Supplying two integer expressions should be an error.
TEST_F(IfTest, ExtraIntExpression)
{
const char *str =
"#if 1 1\n"
"#endif\n";
ASSERT_TRUE(mPreprocessor.init(1, &str, 0));
EXPECT_CALL(mDiagnostics, print(pp::Diagnostics::PP_INVALID_EXPRESSION,
pp::SourceLocation(0, 1), "syntax error"));
pp::Token token;
mPreprocessor.lex(&token);
}
// The preprocessor only allows one expression to follow an #if directive.
// Supplying two expressions where one uses a preprocessor define should be an error.
TEST_F(IfTest, ExtraIdentifierExpression)
{
const char *str =
"#define one 1\n"
"#if 1 one\n"
"#endif\n";
ASSERT_TRUE(mPreprocessor.init(1, &str, 0));
EXPECT_CALL(mDiagnostics, print(pp::Diagnostics::PP_INVALID_EXPRESSION,
pp::SourceLocation(0, 2), "syntax error"));
pp::Token token;
mPreprocessor.lex(&token);
}
// Divide by zero that's not evaluated because of short-circuiting should not cause an error.
TEST_F(IfTest, ShortCircuitedDivideByZero)
{
const char *str =
"#if 1 || (2 / 0)\n"
"pass\n"
"#endif\n";
const char *expected =
"\n"
"pass\n"
"\n";
preprocess(str, expected);
}
// Undefined identifier that's not evaluated because of short-circuiting should not cause an error.
TEST_F(IfTest, ShortCircuitedUndefined)
{
const char *str =
"#if 1 || UNDEFINED\n"
"pass\n"
"#endif\n";
const char *expected =
"\n"
"pass\n"
"\n";
preprocess(str, expected);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment