Improved SkSL array size parsing

We previously had no way to signal a parse error from arraySize,
resulting in a cascade of additional errors downstream. This tightens
up the behavior and allows us to fail more gracefully.

Bug: skia:12416
Change-Id: I83d3d5bc1dc63395edb325297375a6eb52415817
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/512952
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
This commit is contained in:
Ethan Nicholas 2022-02-28 10:27:21 -05:00 committed by SkCQ
parent 64629c7a5e
commit f6dc205b14
4 changed files with 59 additions and 33 deletions

View File

@ -420,26 +420,32 @@ bool DSLParser::functionDeclarationEnd(const DSLModifiers& modifiers,
return true;
}
SKSL_INT DSLParser::arraySize() {
bool DSLParser::arraySize(SKSL_INT* outResult) {
DSLExpression sizeExpr = this->expression();
if (!sizeExpr.isValid()) {
return 1;
if (!sizeExpr.hasValue()) {
return false;
}
std::unique_ptr<SkSL::Expression> sizeLiteral = sizeExpr.release();
SKSL_INT size;
if (!ConstantFolder::GetConstantInt(*sizeLiteral, &size)) {
this->error(sizeLiteral->fLine, "array size must be an integer");
return 1;
// Start out with a safe value that won't generate any errors downstream
*outResult = 1;
if (sizeExpr.isValid()) {
std::unique_ptr<SkSL::Expression> sizeLiteral = sizeExpr.release();
SKSL_INT size;
if (!ConstantFolder::GetConstantInt(*sizeLiteral, &size)) {
this->error(sizeLiteral->fLine, "array size must be an integer");
return true;
}
if (size > INT32_MAX) {
this->error(sizeLiteral->fLine, "array size out of bounds");
return true;
}
if (size <= 0) {
this->error(sizeLiteral->fLine, "array size must be positive");
return true;
}
// Now that we've validated it, output the real value
*outResult = size;
}
if (size > INT32_MAX) {
this->error(sizeLiteral->fLine, "array size out of bounds");
return 1;
}
if (size <= 0) {
this->error(sizeLiteral->fLine, "array size must be positive");
return 1;
}
return size;
return true;
}
bool DSLParser::parseArrayDimensions(int line, DSLType* type) {
@ -447,7 +453,11 @@ bool DSLParser::parseArrayDimensions(int line, DSLType* type) {
if (this->checkNext(Token::Kind::TK_RBRACKET)) {
this->error(line, "expected array dimension");
} else {
*type = Array(*type, this->arraySize(), this->position(line));
SKSL_INT size;
if (!this->arraySize(&size)) {
return false;
}
*type = Array(*type, size, this->position(line));
if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) {
return false;
}
@ -633,7 +643,11 @@ std::optional<DSLType> DSLParser::structDeclaration() {
}
while (this->checkNext(Token::Kind::TK_LBRACKET)) {
actualType = dsl::Array(actualType, this->arraySize(), this->position(memberName));
SKSL_INT size;
if (!this->arraySize(&size)) {
return std::nullopt;
}
actualType = dsl::Array(actualType, size, this->position(memberName));
if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) {
return std::nullopt;
}
@ -874,7 +888,11 @@ std::optional<DSLType> DSLParser::type(DSLModifiers* modifiers) {
DSLType result(this->text(type), modifiers, this->position(type));
while (this->checkNext(Token::Kind::TK_LBRACKET)) {
if (this->peek().fKind != Token::Kind::TK_RBRACKET) {
result = Array(result, this->arraySize(), this->position(type));
SKSL_INT size;
if (!this->arraySize(&size)) {
return std::nullopt;
}
result = Array(result, size, this->position(type));
} else {
this->error(this->peek(), "expected array dimension");
}
@ -885,7 +903,7 @@ std::optional<DSLType> DSLParser::type(DSLModifiers* modifiers) {
/* IDENTIFIER LBRACE
varDeclaration+
RBRACE (IDENTIFIER (LBRACKET expression? RBRACKET)*)? SEMICOLON */
RBRACE (IDENTIFIER (LBRACKET expression RBRACKET)*)? SEMICOLON */
bool DSLParser::interfaceBlock(const dsl::DSLModifiers& modifiers) {
Token typeName;
if (!this->expectIdentifier(&typeName)) {
@ -916,8 +934,11 @@ bool DSLParser::interfaceBlock(const dsl::DSLModifiers& modifiers) {
if (this->checkNext(Token::Kind::TK_LBRACKET)) {
Token sizeToken = this->peek();
if (sizeToken.fKind != Token::Kind::TK_RBRACKET) {
actualType = Array(std::move(actualType), this->arraySize(),
this->position(typeName));
SKSL_INT size;
if (!this->arraySize(&size)) {
return false;
}
actualType = Array(std::move(actualType), size, this->position(typeName));
} else {
this->error(sizeToken, "unsized arrays are not permitted");
}
@ -945,11 +966,13 @@ bool DSLParser::interfaceBlock(const dsl::DSLModifiers& modifiers) {
}
std::string_view instanceName;
Token instanceNameToken;
SKSL_INT arraySize = 0;
SKSL_INT size = 0;
if (this->checkIdentifier(&instanceNameToken)) {
instanceName = this->text(instanceNameToken);
if (this->checkNext(Token::Kind::TK_LBRACKET)) {
arraySize = this->arraySize();
if (!this->arraySize(&size)) {
return false;
}
this->expect(Token::Kind::TK_RBRACKET, "']'");
}
}
@ -959,7 +982,7 @@ bool DSLParser::interfaceBlock(const dsl::DSLModifiers& modifiers) {
"' must contain at least one member");
} else {
dsl::InterfaceBlock(modifiers, this->text(typeName), std::move(fields), instanceName,
arraySize, this->position(typeName));
size, this->position(typeName));
}
return true;
}

View File

@ -125,7 +125,14 @@ private:
void declarations();
SKSL_INT arraySize();
/**
* Parses an expression representing an array size. Reports errors if the array size is not
* valid (out of bounds, not a literal integer). Returns true if an expression was
* successfully parsed, even if that array size is not actually valid. In the event of a true
* return, outResult always contains a valid array size (even if the parsed array size was not
* actually valid; invalid array sizes result in a 1 to avoid additional errors downstream).
*/
bool arraySize(SKSL_INT* outResult);
void directive();

View File

@ -1,6 +1,4 @@
### Compilation failed:
error: 1: expected expression, but found ']'
error: 1: expected ']', but found ';'
error: 2: expected ';', but found 'void'
3 errors
1 error

View File

@ -1,6 +1,4 @@
### Compilation failed:
error: 1: expected expression, but found ']'
error: 1: expected ']', but found ';'
error: 1: expected ';', but found 'void'
3 errors
1 error