Detect overflow in !<integer>.

This commit is contained in:
Dejan Mircevski 2015-09-29 17:07:21 -04:00 committed by David Neto
parent 8bed0cea13
commit 14c4b10d44
2 changed files with 76 additions and 61 deletions

View File

@ -49,14 +49,13 @@
using spvutils::BitwiseCast;
bool spvIsValidIDCharacter(const char value) {
return value == '_' || 0 != ::isalnum(value);
}
// Returns true if the given string represents a valid ID name.
bool spvIsValidID(const char *textValue) {
const char *c = textValue;
bool spvIsValidID(const char* textValue) {
const char* c = textValue;
for (; *c != '\0'; ++c) {
if (!spvIsValidIDCharacter(*c)) {
return false;
@ -152,6 +151,34 @@ spv_result_t spvTextToLiteral(const char* textValue, spv_literal_t* pLiteral) {
return SPV_SUCCESS;
}
namespace {
/// Parses an immediate integer from text, guarding against overflow. If
/// successful, adds the parsed value to pInst, advances the context past it,
/// and returns SPV_SUCCESS. Otherwise, leaves pInst alone, emits diagnostics,
/// and returns SPV_ERROR_INVALID_TEXT.
spv_result_t encodeImmediate(libspirv::AssemblyContext* context,
const char* text, spv_instruction_t* pInst) {
assert(*text == '!');
const char* begin = text + 1;
char* end = nullptr;
const uint64_t parseResult = std::strtoull(begin, &end, 0);
size_t length = end - begin;
if (length != strlen(begin)) {
context->diagnostic() << "Invalid immediate integer '" << text << "'.";
return SPV_ERROR_INVALID_TEXT;
} else if (length > 10 || parseResult > UINT32_MAX) {
context->diagnostic() << "Immediate integer '" << text
<< "' is over 32 bits.";
return SPV_ERROR_INVALID_TEXT;
}
context->binaryEncodeU32(parseResult, pInst);
context->seekForward(strlen(text));
return SPV_SUCCESS;
}
} // anonymous namespace
/// @brief Translate an Opcode operand to binary form
///
/// @param[in] grammar the grammar to use for compilation
@ -162,7 +189,7 @@ spv_result_t spvTextToLiteral(const char* textValue, spv_literal_t* pLiteral) {
/// @param[in,out] pExpectedOperands the operand types expected
///
/// @return result code
spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar& grammar,
libspirv::AssemblyContext* context,
const spv_operand_type_t type,
const char* textValue,
@ -170,18 +197,9 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
spv_operand_pattern_t* pExpectedOperands) {
// NOTE: Handle immediate int in the stream
if ('!' == textValue[0]) {
const char* begin = textValue + 1;
char* end = nullptr;
uint32_t immediateInt = strtoul(begin, &end, 0);
size_t size = strlen(textValue);
size_t length = (end - begin);
if (size - 1 != length) {
context->diagnostic() << "Invalid immediate integer '" << textValue << "'.";
return SPV_ERROR_INVALID_TEXT;
if (auto error = encodeImmediate(context, textValue, pInst)) {
return error;
}
context->seekForward(size);
pInst->words[pInst->wordCount] = immediateInt;
pInst->wordCount += 1;
*pExpectedOperands =
spvAlternatePatternFollowingImmediate(*pExpectedOperands);
return SPV_SUCCESS;
@ -211,10 +229,9 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
// NOTE: Special case for extension instruction lookup
if (OpExtInst == pInst->opcode) {
spv_ext_inst_desc extInst;
if (grammar.lookupExtInst(pInst->extInstType, textValue,
&extInst)) {
if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst)) {
context->diagnostic() << "Invalid extended instruction name '"
<< textValue << "'.";
<< textValue << "'.";
return SPV_ERROR_INVALID_TEXT;
}
pInst->words[pInst->wordCount++] = extInst->ext_inst;
@ -233,7 +250,8 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
if (error != SPV_SUCCESS) {
if (error == SPV_ERROR_OUT_OF_MEMORY) return error;
if (spvOperandIsOptional(type)) return SPV_FAILED_MATCH;
context->diagnostic() << "Invalid literal number '" << textValue << "'.";
context->diagnostic() << "Invalid literal number '" << textValue
<< "'.";
return SPV_ERROR_INVALID_TEXT;
}
switch (literal.type) {
@ -269,12 +287,14 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
return SPV_ERROR_INVALID_TEXT;
} break;
case SPV_LITERAL_TYPE_STRING: {
context->diagnostic() << "Expected literal number, found literal string '"
<< textValue << "'.";
context->diagnostic()
<< "Expected literal number, found literal string '" << textValue
<< "'.";
return SPV_FAILED_MATCH;
} break;
default:
context->diagnostic() << "Invalid literal number '" << textValue << "'";
context->diagnostic() << "Invalid literal number '" << textValue
<< "'";
return SPV_ERROR_INVALID_TEXT;
}
} break;
@ -285,12 +305,14 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
if (error != SPV_SUCCESS) {
if (error == SPV_ERROR_OUT_OF_MEMORY) return error;
if (spvOperandIsOptional(type)) return SPV_FAILED_MATCH;
context->diagnostic() << "Invalid literal string '" << textValue << "'.";
context->diagnostic() << "Invalid literal string '" << textValue
<< "'.";
return SPV_ERROR_INVALID_TEXT;
}
if (literal.type != SPV_LITERAL_TYPE_STRING) {
context->diagnostic() << "Expected literal string, found literal number '"
<< textValue << "'.";
context->diagnostic()
<< "Expected literal string, found literal number '" << textValue
<< "'.";
return SPV_FAILED_MATCH;
}
@ -311,7 +333,7 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
uint32_t value;
if (grammar.parseMaskOperand(type, textValue, &value)) {
context->diagnostic() << "Invalid " << spvOperandTypeStr(type) << " '"
<< textValue << "'.";
<< textValue << "'.";
return SPV_ERROR_INVALID_TEXT;
}
if (auto error = context->binaryEncodeU32(value, pInst)) return error;
@ -335,7 +357,8 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
textValue, pInst, pExpectedOperands);
}
if (error) {
context->diagnostic() << "Invalid word following !<integer>: " << textValue;
context->diagnostic() << "Invalid word following !<integer>: "
<< textValue;
return error;
}
if (pExpectedOperands->empty()) {
@ -346,15 +369,14 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar &grammar,
// NOTE: All non literal operands are handled here using the operand
// table.
spv_operand_desc entry;
if (grammar.lookupOperand(type, textValue, strlen(textValue),
&entry)) {
if (grammar.lookupOperand(type, textValue, strlen(textValue), &entry)) {
context->diagnostic() << "Invalid " << spvOperandTypeStr(type) << " '"
<< textValue << "'.";
<< textValue << "'.";
return SPV_ERROR_INVALID_TEXT;
}
if (context->binaryEncodeU32(entry->value, pInst)) {
context->diagnostic() << "Invalid " << spvOperandTypeStr(type) << " '"
<< textValue << "'.";
<< textValue << "'.";
return SPV_ERROR_INVALID_TEXT;
}
@ -373,7 +395,7 @@ namespace {
/// instruction and returns SPV_SUCCESS. Otherwise, returns an error code and
/// leaves position pointing to the error in text.
spv_result_t encodeInstructionStartingWithImmediate(
const libspirv::AssemblyGrammar &grammar,
const libspirv::AssemblyGrammar& grammar,
libspirv::AssemblyContext* context, spv_instruction_t* pInst) {
std::string firstWord;
spv_position_t nextPosition = {};
@ -383,17 +405,9 @@ spv_result_t encodeInstructionStartingWithImmediate(
return error;
}
assert(firstWord[0] == '!');
const char* begin = firstWord.data() + 1;
char* end = nullptr;
uint32_t immediateInt = strtoul(begin, &end, 0);
if ((begin + firstWord.size() - 1) != end) {
context->diagnostic() << "Invalid immediate integer '" << firstWord << "'.";
return SPV_ERROR_INVALID_TEXT;
if ((error = encodeImmediate(context, firstWord.c_str(), pInst))) {
return error;
}
context->seekForward(firstWord.size());
pInst->words[0] = immediateInt;
pInst->wordCount = 1;
while (context->advance() != SPV_END_OF_STREAM) {
// A beginning of a new instruction means we're done.
if (context->isStartOfNewInst()) return SPV_SUCCESS;
@ -435,7 +449,7 @@ spv_result_t encodeInstructionStartingWithImmediate(
/// @param[in,out] pPosition in the text stream
///
/// @return result code
spv_result_t spvTextEncodeOpcode(const libspirv::AssemblyGrammar &grammar,
spv_result_t spvTextEncodeOpcode(const libspirv::AssemblyGrammar& grammar,
libspirv::AssemblyContext* context,
spv_assembly_syntax_format_t format,
spv_instruction_t* pInst) {
@ -473,9 +487,10 @@ spv_result_t spvTextEncodeOpcode(const libspirv::AssemblyGrammar &grammar,
result_id = firstWord;
if ('%' != result_id.front()) {
context->diagnostic() << "Expected <opcode> or <result-id> at the beginning "
"of an instruction, found '"
<< result_id << "'.";
context->diagnostic()
<< "Expected <opcode> or <result-id> at the beginning "
"of an instruction, found '"
<< result_id << "'.";
return SPV_ERROR_INVALID_TEXT;
}
result_id_position = context->position();
@ -516,15 +531,16 @@ spv_result_t spvTextEncodeOpcode(const libspirv::AssemblyGrammar &grammar,
spv_opcode_desc opcodeEntry;
error = grammar.lookupOpcode(pInstName, &opcodeEntry);
if (error) {
context->diagnostic() << "Invalid Opcode name '" << context->getWord() << "'";
context->diagnostic() << "Invalid Opcode name '" << context->getWord()
<< "'";
return error;
}
if (SPV_ASSEMBLY_SYNTAX_FORMAT_ASSIGNMENT == format) {
// If this instruction has <result-id>, check it follows AAF.
if (opcodeEntry->hasResult && result_id.empty()) {
context->diagnostic() << "Expected <result-id> at the beginning of an "
"instruction, found '"
<< firstWord << "'.";
"instruction, found '"
<< firstWord << "'.";
return SPV_ERROR_INVALID_TEXT;
}
}
@ -616,10 +632,11 @@ namespace {
// Translates a given assembly language module into binary form.
// If a diagnostic is generated, it is not yet marked as being
// for a text-based input.
spv_result_t spvTextToBinaryInternal(
const libspirv::AssemblyGrammar &grammar, const spv_text text,
spv_assembly_syntax_format_t format, spv_binary* pBinary,
spv_diagnostic* pDiagnostic) {
spv_result_t spvTextToBinaryInternal(const libspirv::AssemblyGrammar& grammar,
const spv_text text,
spv_assembly_syntax_format_t format,
spv_binary* pBinary,
spv_diagnostic* pDiagnostic) {
if (!pDiagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC;
libspirv::AssemblyContext context(text, pDiagnostic);
if (!text->str || !text->length) {
@ -708,8 +725,7 @@ spv_result_t spvTextWithFormatToBinary(
spv_binary* pBinary, spv_diagnostic* pDiagnostic) {
spv_text_t text = {input_text, input_text_size};
libspirv::AssemblyGrammar grammar(operandTable, opcodeTable,
extInstTable);
libspirv::AssemblyGrammar grammar(operandTable, opcodeTable, extInstTable);
spv_result_t result =
spvTextToBinaryInternal(grammar, &text, format, pBinary, pDiagnostic);
if (pDiagnostic && *pDiagnostic) (*pDiagnostic)->isTextSource = true;

View File

@ -68,7 +68,6 @@ TEST_F(TextToBinaryTest, ImmediateIntOperand) {
using ImmediateIntTest = TextToBinaryTest;
TEST_F(ImmediateIntTest, AnyWordInSimpleStatement) {
// TODO(deki): uncomment assertions below and make them pass.
EXPECT_THAT(CompiledInstructions("!0x0004002B %a %b 123", kCAF),
Eq(MakeInstruction(spv::OpConstant, {1, 2, 123})));
EXPECT_THAT(CompiledInstructions("OpConstant !1 %b 123", kCAF),
@ -115,7 +114,6 @@ TEST_F(ImmediateIntTest, OpCodeInAssignment) {
// Literal integers after !<integer> are handled correctly.
TEST_F(ImmediateIntTest, IntegerFollowingImmediate) {
const SpirvVector original = CompiledInstructions("OpTypeInt %1 8 1", kCAF);
// TODO(deki): uncomment assertions below and make them pass.
EXPECT_EQ(original, CompiledInstructions("!0x00040015 1 8 1", kCAF));
EXPECT_EQ(original, CompiledInstructions("OpTypeInt !1 8 1", kCAF));
@ -127,6 +125,7 @@ TEST_F(ImmediateIntTest, IntegerFollowingImmediate) {
EXPECT_EQ(CompiledInstructions("OpConstant %10 %2 -123", kCAF),
CompiledInstructions("OpConstant %10 !2 -123", kCAF));
// TODO(deki): uncomment assertions below and make them pass.
// Hex value(s).
// EXPECT_EQ(CompileSuccessfully("OpConstant %10 %1 0x12345678", kCAF),
// CompileSuccessfully("OpConstant %10 !1 0x12345678", kCAF));
@ -261,14 +260,14 @@ TEST_F(ImmediateIntTest, ConsecutiveImmediateOpcodes) {
// !<integer> followed by, eg, an enum or '=' or a random bareword.
TEST_F(ImmediateIntTest, ForbiddenOperands) {
// TODO(deki): uncomment assertions below and make them pass.
EXPECT_THAT(CompileFailure("OpMemoryModel !0 OpenCL"), HasSubstr("OpenCL"));
EXPECT_THAT(CompileFailure("!1 %0 = !2"), HasSubstr("="));
#if 0
// Immediate integers longer than one 32-bit word.
EXPECT_THAT(CompileFailure("!5000000000"), HasSubstr("5000000000"));
EXPECT_THAT(CompileFailure("!0x00020049 !5000000000"), HasSubstr("5000000000"));
#endif
EXPECT_THAT(CompileFailure("!999999999999999999"),
HasSubstr("999999999999999999"));
EXPECT_THAT(CompileFailure("!0x00020049 !5000000000"),
HasSubstr("5000000000"));
EXPECT_THAT(CompileFailure("OpMemoryModel !0 random_bareword"),
HasSubstr("random_bareword"));
}