[tint][ir][fuzz] Prevent fuzzer from crashing on malformed stores
This includes generic machinery for checking operands and results that
will be used in following CLs.
Fixes: 352496171
Change-Id: I91efd1dfb937f16afe52d4aa7d74d29866fc241a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/198054
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/tint/lang/core/ir/store.h b/src/tint/lang/core/ir/store.h
index b14cbb1..1e51149 100644
--- a/src/tint/lang/core/ir/store.h
+++ b/src/tint/lang/core/ir/store.h
@@ -44,6 +44,12 @@
/// The offset in Operands() for the `from` value
static constexpr size_t kFromOperandOffset = 1;
+ /// The fixed number of results returned by this instruction
+ static constexpr size_t kNumResults = 0;
+
+ /// The fixed number of operands used by this instruction
+ static constexpr size_t kNumOperands = 2;
+
/// Constructor (no results, no operands)
Store();
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 9a65a25..2b90745 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -297,16 +297,54 @@
/// @returns the styled name for the given block
StyledText NameOf(const Block* block);
+ /// Checks the given result is not null
+ /// @param inst the instruction
+ /// @param idx the result index
+ /// @returns true if the result is not null
+ bool CheckResultNotNull(const ir::Instruction* inst, size_t idx);
+
+ /// Checks the number of results for @p inst are exactly equal to @p count and that none of
+ /// them are null.
+ /// @param inst the instruction
+ /// @param count the number of results to check
+ /// @returns true if the results count is as expected and none are null
+ bool CheckResults(const ir::Instruction* inst, size_t count);
+
+ /// Checks the given operand is not null
+ /// @param inst the instruction
+ /// @param idx the operand index
+ /// @returns true if the operand is not null
+ bool CheckOperandNotNull(const ir::Instruction* inst, size_t idx);
+
+ /// Checks the number of operands for @p inst are exactly equal to @p count and that none of
+ /// them are null.
+ /// @param inst the instruction
+ /// @param count the number of operands to check
+ /// @returns true if the operands count is as expected and none are null
+ bool CheckOperands(const ir::Instruction* inst, size_t count);
+
+ /// Checks the number of results and operands for @p inst are exactly equal to num_results
+ /// and num_operands, respectively, and that none of them are null.
+ /// @param inst the instruction
+ /// @param num_results expected number of results for the instruction
+ /// @param num_operands expected number of operands for the instruction
+ /// @returns true if the result and operand counts are as expected and none are null
+ bool CheckResultsAndOperands(const ir::Instruction* inst,
+ size_t num_results,
+ size_t num_operands);
+
/// Checks the given operand is not null
/// @param inst the instruction
/// @param operand the operand
/// @param idx the operand index
+ // TODO(345196551): Remove this override once it is no longer used.
void CheckOperandNotNull(const ir::Instruction* inst, const ir::Value* operand, size_t idx);
/// Checks all operands in the given range (inclusive) for @p inst are not null
/// @param inst the instruction
/// @param start_operand the first operand to check
/// @param end_operand the last operand to check
+ // TODO(345196551): Remove this override once it is no longer used.
void CheckOperandsNotNull(const ir::Instruction* inst,
size_t start_operand,
size_t end_operand);
@@ -730,12 +768,73 @@
<< Disassemble().NameOf(block);
}
+bool Validator::CheckResultNotNull(const Instruction* inst, size_t idx) {
+ auto* result = inst->Result(idx);
+ if (TINT_UNLIKELY(result == nullptr)) {
+ AddResultError(inst, idx) << "result is undefined";
+ return false;
+ }
+ return true;
+}
+
+bool Validator::CheckResults(const ir::Instruction* inst, size_t count) {
+ if (TINT_UNLIKELY(inst->Results().Length() != count)) {
+ AddError(inst) << "expected exactly " << count << " results, got "
+ << inst->Results().Length();
+ return false;
+ }
+
+ bool passed = true;
+ for (size_t i = 0; i < count; i++) {
+ if (TINT_UNLIKELY(!CheckResultNotNull(inst, i))) {
+ passed = false;
+ }
+ }
+ return passed;
+}
+
+bool Validator::CheckOperandNotNull(const Instruction* inst, size_t idx) {
+ auto* operand = inst->Operand(idx);
+ if (TINT_UNLIKELY(operand == nullptr)) {
+ AddError(inst, idx) << "operand is undefined";
+ return false;
+ }
+ return true;
+}
+
+bool Validator::CheckOperands(const ir::Instruction* inst, size_t count) {
+ if (TINT_UNLIKELY(inst->Operands().Length() != count)) {
+ AddError(inst) << "expected exactly " << count << " operands, got "
+ << inst->Operands().Length();
+ return false;
+ }
+
+ bool passed = true;
+ for (size_t i = 0; i < count; i++) {
+ if (TINT_UNLIKELY(!CheckOperandNotNull(inst, i))) {
+ passed = false;
+ }
+ }
+ return passed;
+}
+
+bool Validator::CheckResultsAndOperands(const ir::Instruction* inst,
+ size_t num_results,
+ size_t num_operands) {
+ // Intentionally avoiding short-circuiting here
+ bool results_passed = CheckResults(inst, num_results);
+ bool operands_passed = CheckOperands(inst, num_operands);
+ return results_passed && operands_passed;
+}
+
+// TODO(345196551): Remove this override once it is no longer used.
void Validator::CheckOperandNotNull(const Instruction* inst, const ir::Value* operand, size_t idx) {
if (operand == nullptr) {
AddError(inst, idx) << "operand is undefined";
}
}
+// TODO(345196551): Remove this override once it is no longer used.
void Validator::CheckOperandsNotNull(const Instruction* inst,
size_t start_operand,
size_t end_operand) {
@@ -1589,7 +1688,9 @@
}
void Validator::CheckStore(const Store* s) {
- CheckOperandsNotNull(s, Store::kToOperandOffset, Store::kFromOperandOffset);
+ if (TINT_UNLIKELY(!CheckResultsAndOperands(s, Store::kNumResults, Store::kNumOperands))) {
+ return;
+ }
if (auto* from = s->From()) {
if (auto* to = s->To()) {
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 20106df..9c5e4c8 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -4735,6 +4735,75 @@
)");
}
+TEST_F(IR_ValidatorTest, Store_NullToAndFrom) {
+ auto* f = b.Function("my_func", ty.void_());
+
+ b.Append(f->Block(), [&] {
+ b.Append(mod.allocators.instructions.Create<ir::Store>(nullptr, nullptr));
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(), R"(:3:11 error: store: operand is undefined
+ store undef, undef
+ ^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+:3:18 error: store: operand is undefined
+ store undef, undef
+ ^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ store undef, undef
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Store_NonEmptyResult) {
+ auto* f = b.Function("my_func", ty.void_());
+
+ b.Append(f->Block(), [&] {
+ auto* var = b.Var(ty.ptr<function, i32>());
+ auto* store =
+ mod.allocators.instructions.Create<ir::Store>(var->Result(0), b.Constant(42_i));
+ store->SetResults(Vector{b.InstructionResult(ty.i32())});
+ b.Append(store);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(), R"(:4:5 error: store: expected exactly 0 results, got 1
+ store %2, 42i
+ ^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ %2:ptr<function, i32, read_write> = var
+ store %2, 42i
+ ret
+ }
+}
+)");
+}
+
TEST_F(IR_ValidatorTest, Store_TargetNotMemoryView) {
auto* f = b.Function("my_func", ty.void_());