Skip to content

Commit 415c3bb

Browse files
authored
Update testsuite (#1275)
The two primary changes involved are: 1. Removal of `assert_return_canonical_nan`/`arithetic nan` in favor of special `nan:canonical`/`nan:arithmetic` constants that can only be used in test expectations. See: WebAssembly/spec#1104 2. New trapping behaviour for bulk memory operations. Range checks are now performed up front for opterations such as memory.fill and memory.copy. See: WebAssembly/bulk-memory-operations#111 And: WebAssembly/bulk-memory-operations#123 The old behaviour is still kept around to support table.fill which is defined in reference-types proposal and has yet to be updated.
1 parent 7de6d8e commit 415c3bb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1371
-1511
lines changed

fuzz-in/wast.dict

-2
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,5 @@ op_assert_malformed="assert_malformed"
225225
op_assert_invalid="assert_invalid"
226226
op_assert_unlinkable="assert_unlinkable"
227227
op_assert_return="assert_return"
228-
op_assert_return_canonical_nan="assert_return_canonical_nan"
229-
op_assert_return_arithmetic_nan="assert_return_arithmetic_nan"
230228
op_assert_trap="assert_trap"
231229
op_assert_exhaustion="assert_exhaustion"

src/binary-writer-spec.cc

+18-28
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ void BinaryWriterSpec::WriteCommandType(const Command& command) {
131131
"assert_uninstantiable",
132132
"assert_return",
133133
"assert_return_func",
134-
"assert_return_canonical_nan",
135-
"assert_return_arithmetic_nan",
136134
"assert_trap",
137135
"assert_exhaustion",
138136
};
@@ -189,7 +187,15 @@ void BinaryWriterSpec::WriteConst(const Const& const_) {
189187
WriteString("f32");
190188
WriteSeparator();
191189
WriteKey("value");
192-
json_stream_->Writef("\"%u\"", const_.f32_bits);
190+
if (const_.is_expected_nan) {
191+
if (const_.expected == ExpectedNan::Arithmetic) {
192+
WriteString("nan:arithmetic");
193+
} else {
194+
WriteString("nan:canonical");
195+
}
196+
} else {
197+
json_stream_->Writef("\"%u\"", const_.f32_bits);
198+
}
193199
break;
194200
}
195201

@@ -198,7 +204,15 @@ void BinaryWriterSpec::WriteConst(const Const& const_) {
198204
WriteString("f64");
199205
WriteSeparator();
200206
WriteKey("value");
201-
json_stream_->Writef("\"%" PRIu64 "\"", const_.f64_bits);
207+
if (const_.is_expected_nan) {
208+
if (const_.expected == ExpectedNan::Arithmetic) {
209+
WriteString("nan:arithmetic");
210+
} else {
211+
WriteString("nan:canonical");
212+
}
213+
} else {
214+
json_stream_->Writef("\"%" PRIu64 "\"", const_.f64_bits);
215+
}
202216
break;
203217
}
204218

@@ -490,30 +504,6 @@ void BinaryWriterSpec::WriteCommands() {
490504
break;
491505
}
492506

493-
case CommandType::AssertReturnCanonicalNan: {
494-
auto* assert_return_canonical_nan_command =
495-
cast<AssertReturnCanonicalNanCommand>(command);
496-
WriteLocation(assert_return_canonical_nan_command->action->loc);
497-
WriteSeparator();
498-
WriteAction(*assert_return_canonical_nan_command->action);
499-
WriteSeparator();
500-
WriteKey("expected");
501-
WriteActionResultType(*assert_return_canonical_nan_command->action);
502-
break;
503-
}
504-
505-
case CommandType::AssertReturnArithmeticNan: {
506-
auto* assert_return_arithmetic_nan_command =
507-
cast<AssertReturnArithmeticNanCommand>(command);
508-
WriteLocation(assert_return_arithmetic_nan_command->action->loc);
509-
WriteSeparator();
510-
WriteAction(*assert_return_arithmetic_nan_command->action);
511-
WriteSeparator();
512-
WriteKey("expected");
513-
WriteActionResultType(*assert_return_arithmetic_nan_command->action);
514-
break;
515-
}
516-
517507
case CommandType::AssertTrap: {
518508
auto* assert_trap_command = cast<AssertTrapCommand>(command);
519509
WriteLocation(assert_trap_command->action->loc);

src/common.h

+6
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ enum class SegmentKind {
235235
Declared,
236236
};
237237

238+
// Used in test asserts for special expected values "nan:canonical" and "nan:arithmetic"
239+
enum class ExpectedNan {
240+
Canonical,
241+
Arithmetic,
242+
};
243+
238244
// Matches binary format, do not change.
239245
enum SegmentFlags : uint8_t {
240246
SegFlagsNone = 0,

src/interp/binary-reader-interp.cc

+5-11
Original file line numberDiff line numberDiff line change
@@ -1130,11 +1130,6 @@ wabt::Result BinaryReaderInterp::OnElemSegmentElemExprCount(Index index,
11301130
if (segment_flags_ & SegPassive) {
11311131
elem_segment_info_ = nullptr;
11321132
} else {
1133-
// An active segment still is present in the segment index space, but
1134-
// cannot be used with `table.init` (it's as if it has already been
1135-
// dropped).
1136-
elem_segment_->dropped = true;
1137-
11381133
assert(segment_table_index_ != kInvalidIndex);
11391134
Table* table = GetTableByModuleIndex(segment_table_index_);
11401135
module_->active_elem_segments_.emplace_back(table, table_offset_);
@@ -1146,7 +1141,11 @@ wabt::Result BinaryReaderInterp::OnElemSegmentElemExprCount(Index index,
11461141
wabt::Result BinaryReaderInterp::OnElemSegmentElemExpr_RefNull(
11471142
Index segment_index) {
11481143
assert(segment_flags_ & SegUseElemExprs);
1149-
elem_segment_->elems.push_back({RefType::Null, kInvalidIndex});
1144+
if (segment_flags_ & SegPassive) {
1145+
elem_segment_->elems.push_back({RefType::Null, kInvalidIndex});
1146+
} else {
1147+
elem_segment_info_->src.push_back({RefType::Null, kInvalidIndex});
1148+
}
11501149
return wabt::Result::Ok;
11511150
}
11521151

@@ -1207,11 +1206,6 @@ wabt::Result BinaryReaderInterp::OnDataSegmentData(Index index,
12071206
return wabt::Result::Error;
12081207
}
12091208

1210-
// An active segment still is present in the segment index space, but
1211-
// cannot be used with `memory.init` (it's as if it has already been
1212-
// dropped).
1213-
segment->dropped = true;
1214-
12151209
assert(module_->memory_index != kInvalidIndex);
12161210
Memory* memory = env_->GetMemory(module_->memory_index);
12171211
Address address = init_expr_value_.value.i32;

src/interp/interp.cc

+51-48
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,11 @@ Result Thread::AtomicRmwCmpxchg(const uint8_t** pc) {
10181018
return Push<ResultValueType>(static_cast<ExtendedType>(read));
10191019
}
10201020

1021-
bool ClampToBounds(uint32_t start, uint32_t* length, uint32_t max) {
1021+
// This function is used to clanp the bound for table.fill operations. This
1022+
// can be removed once the behaviour of table.fill is updated to match the
1023+
// new bulk meory semantics, which is to trap early on OOB.
1024+
// See: https://github.com/webassembly/bulk-memory-operations/issues/111
1025+
static bool ClampToBounds(uint32_t start, uint32_t* length, uint32_t max) {
10221026
if (start > max) {
10231027
*length = 0;
10241028
return false;
@@ -1031,6 +1035,14 @@ bool ClampToBounds(uint32_t start, uint32_t* length, uint32_t max) {
10311035
return true;
10321036
}
10331037

1038+
static bool CheckBounds(uint32_t start, uint32_t length, uint32_t max) {
1039+
if (start > max) {
1040+
return false;
1041+
}
1042+
uint32_t avail = max - start;
1043+
return length <= avail;
1044+
}
1045+
10341046
Result Thread::MemoryInit(const uint8_t** pc) {
10351047
Memory* memory = ReadMemory(pc);
10361048
DataSegment* segment = ReadDataSegment(pc);
@@ -1039,22 +1051,20 @@ Result Thread::MemoryInit(const uint8_t** pc) {
10391051
uint32_t size = Pop<uint32_t>();
10401052
uint32_t src = Pop<uint32_t>();
10411053
uint32_t dst = Pop<uint32_t>();
1054+
bool ok = CheckBounds(dst, size, memory_size);
1055+
ok &= CheckBounds(src, size, segment_size);
1056+
if (!ok) {
1057+
TRAP_MSG(MemoryAccessOutOfBounds, "memory.init out of bounds");
1058+
}
10421059
if (size > 0) {
1043-
TRAP_IF(segment->dropped, DataSegmentDropped);
1044-
bool ok = ClampToBounds(dst, &size, memory_size);
1045-
ok &= ClampToBounds(src, &size, segment_size);
1046-
if (!ok) {
1047-
TRAP_MSG(MemoryAccessOutOfBounds, "memory.init out of bounds");
1048-
}
10491060
memcpy(memory->data.data() + dst, segment->data.data() + src, size);
10501061
}
10511062
return ResultType::Ok;
10521063
}
10531064

10541065
Result Thread::DataDrop(const uint8_t** pc) {
10551066
DataSegment* segment = ReadDataSegment(pc);
1056-
TRAP_IF(segment->dropped, DataSegmentDropped);
1057-
segment->dropped = true;
1067+
segment->data.clear();
10581068
return ResultType::Ok;
10591069
}
10601070

@@ -1064,12 +1074,12 @@ Result Thread::MemoryCopy(const uint8_t** pc) {
10641074
uint32_t size = Pop<uint32_t>();
10651075
uint32_t src = Pop<uint32_t>();
10661076
uint32_t dst = Pop<uint32_t>();
1077+
bool ok = CheckBounds(dst, size, memory_size);
1078+
ok &= CheckBounds(src, size, memory_size);
1079+
if (!ok) {
1080+
TRAP_MSG(MemoryAccessOutOfBounds, "memory.copy out of bound");
1081+
}
10671082
if (size > 0) {
1068-
bool ok = ClampToBounds(dst, &size, memory_size);
1069-
ok &= ClampToBounds(src, &size, memory_size);
1070-
if (!ok) {
1071-
TRAP_MSG(MemoryAccessOutOfBounds, "memory.copy out of bound");
1072-
}
10731083
char* data = memory->data.data();
10741084
memmove(data + dst, data + src, size);
10751085
}
@@ -1082,11 +1092,10 @@ Result Thread::MemoryFill(const uint8_t** pc) {
10821092
uint32_t size = Pop<uint32_t>();
10831093
uint8_t value = static_cast<uint8_t>(Pop<uint32_t>());
10841094
uint32_t dst = Pop<uint32_t>();
1095+
if (!CheckBounds(dst, size, memory_size)) {
1096+
TRAP_MSG(MemoryAccessOutOfBounds, "memory.fill out of bounds");
1097+
}
10851098
if (size > 0) {
1086-
bool ok = ClampToBounds(dst, &size, memory_size);
1087-
if (!ok) {
1088-
TRAP_MSG(MemoryAccessOutOfBounds, "memory.fill out of bounds");
1089-
}
10901099
memset(memory->data.data() + dst, value, size);
10911100
}
10921101
return ResultType::Ok;
@@ -1099,13 +1108,12 @@ Result Thread::TableInit(const uint8_t** pc) {
10991108
uint32_t size = Pop<uint32_t>();
11001109
uint32_t src = Pop<uint32_t>();
11011110
uint32_t dst = Pop<uint32_t>();
1111+
bool ok = CheckBounds(dst, size, table->size());
1112+
ok &= CheckBounds(src, size, segment_size);
1113+
if (!ok) {
1114+
TRAP_MSG(TableAccessOutOfBounds, "table.init out of bounds");
1115+
}
11021116
if (size > 0) {
1103-
TRAP_IF(segment->dropped, ElemSegmentDropped);
1104-
bool ok = ClampToBounds(dst, &size, table->size());
1105-
ok &= ClampToBounds(src, &size, segment_size);
1106-
if (!ok) {
1107-
TRAP_MSG(TableAccessOutOfBounds, "table.init out of bounds");
1108-
}
11091117
memcpy(table->entries.data() + dst, segment->elems.data() + src,
11101118
size * sizeof(table->entries[0]));
11111119
}
@@ -1144,14 +1152,13 @@ Result Thread::TableFill(const uint8_t** pc) {
11441152
for (uint32_t i = 0; i < size; i++) {
11451153
table->entries[dst+i] = value;
11461154
}
1147-
TRAP_IF(!ok, MemoryAccessOutOfBounds);
1155+
TRAP_IF(!ok, TableAccessOutOfBounds);
11481156
return ResultType::Ok;
11491157
}
11501158

11511159
Result Thread::ElemDrop(const uint8_t** pc) {
11521160
ElemSegment* segment = ReadElemSegment(pc);
1153-
TRAP_IF(segment->dropped, ElemSegmentDropped);
1154-
segment->dropped = true;
1161+
segment->elems.clear();
11551162
return ResultType::Ok;
11561163
}
11571164

@@ -1162,12 +1169,12 @@ Result Thread::TableCopy(const uint8_t** pc) {
11621169
uint32_t size = Pop<uint32_t>();
11631170
uint32_t src = Pop<uint32_t>();
11641171
uint32_t dst = Pop<uint32_t>();
1172+
bool ok = CheckBounds(dst, size, dst_table->size());
1173+
ok &= CheckBounds(src, size, dst_table->size());
1174+
if (!ok) {
1175+
TRAP_MSG(TableAccessOutOfBounds, "table.copy out of bounds");
1176+
}
11651177
if (size > 0) {
1166-
bool ok = ClampToBounds(dst, &size, dst_table->size());
1167-
ok &= ClampToBounds(src, &size, dst_table->size());
1168-
if (!ok) {
1169-
TRAP_MSG(TableAccessOutOfBounds, "table.copy out of bounds");
1170-
}
11711178
Ref* data_src = src_table->entries.data();
11721179
Ref* data_dst = dst_table->entries.data();
11731180
memmove(data_dst + dst, data_src + src, size * sizeof(Ref));
@@ -3755,40 +3762,36 @@ Result Executor::InitializeSegments(DefinedModule* module) {
37553762
uint32_t table_size = info.table->size();
37563763
uint32_t segment_size = info.src.size();
37573764
uint32_t copy_size = segment_size;
3758-
bool ok = ClampToBounds(info.dst, &copy_size, table_size);
3759-
3760-
if (pass == Init && copy_size > 0) {
3761-
std::copy(info.src.begin(), info.src.begin() + copy_size,
3762-
info.table->entries.begin() + info.dst);
3763-
}
3764-
3765-
if (!ok) {
3765+
if (!CheckBounds(info.dst, copy_size, table_size)) {
37663766
TRAP_MSG(TableAccessOutOfBounds,
37673767
"elem segment is out of bounds: [%u, %" PRIu64
37683768
") >= max value %u",
37693769
info.dst, static_cast<uint64_t>(info.dst) + segment_size,
37703770
table_size);
37713771
}
3772+
3773+
if (pass == Init && copy_size > 0) {
3774+
std::copy(info.src.begin(), info.src.begin() + copy_size,
3775+
info.table->entries.begin() + info.dst);
3776+
}
37723777
}
37733778

37743779
for (const DataSegmentInfo& info : module->active_data_segments_) {
37753780
uint32_t memory_size = info.memory->data.size();
37763781
uint32_t segment_size = info.data.size();
37773782
uint32_t copy_size = segment_size;
3778-
bool ok = ClampToBounds(info.dst, &copy_size, memory_size);
3779-
3780-
if (pass == Init && copy_size > 0) {
3781-
std::copy(info.data.begin(), info.data.begin() + copy_size,
3782-
info.memory->data.begin() + info.dst);
3783-
}
3784-
3785-
if (!ok) {
3783+
if (!CheckBounds(info.dst, copy_size, memory_size)) {
37863784
TRAP_MSG(MemoryAccessOutOfBounds,
37873785
"data segment is out of bounds: [%u, %" PRIu64
37883786
") >= max value %u",
37893787
info.dst, static_cast<uint64_t>(info.dst) + segment_size,
37903788
memory_size);
37913789
}
3790+
3791+
if (pass == Init && copy_size > 0) {
3792+
std::copy(info.data.begin(), info.data.begin() + copy_size,
3793+
info.memory->data.begin() + info.dst);
3794+
}
37923795
}
37933796
}
37943797

src/interp/interp.h

+1-9
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ namespace interp {
6666
V(TrapHostResultTypeMismatch, "host result type mismatch") \
6767
/* we called an import function, but it didn't complete succesfully */ \
6868
V(TrapHostTrapped, "host function trapped") \
69-
/* the data segment has been dropped. */ \
70-
V(TrapDataSegmentDropped, "data segment dropped") \
71-
/* the element segment has been dropped. */ \
72-
V(TrapElemSegmentDropped, "element segment dropped") \
7369
/* table access is out of bounds */ \
7470
V(TrapTableAccessOutOfBounds, "out of bounds table access") \
7571
/* we attempted to call a function with the an argument list that doesn't \
@@ -150,14 +146,12 @@ struct DataSegment {
150146
DataSegment() = default;
151147

152148
std::vector<char> data;
153-
bool dropped = false;
154149
};
155150

156151
struct ElemSegment {
157152
ElemSegment() = default;
158153

159154
std::vector<Ref> elems;
160-
bool dropped = false;
161155
};
162156

163157
struct ElemSegmentInfo {
@@ -208,7 +202,7 @@ union Value {
208202
};
209203

210204
struct TypedValue {
211-
TypedValue() {}
205+
TypedValue() = default;
212206
explicit TypedValue(Type type) : type(type) {}
213207
TypedValue(Type basetype, const Value& value) : type(basetype), value(value) {
214208
UpdateType();
@@ -780,8 +774,6 @@ std::string TypedValueToString(const TypedValue&);
780774
std::string ResultToString(Result);
781775
const char* ResultTypeToString(ResultType);
782776

783-
bool ClampToBounds(uint32_t start, uint32_t* length, uint32_t max);
784-
785777
void WriteTypedValue(Stream* stream, const TypedValue&);
786778
void WriteTypedValues(Stream* stream, const TypedValues&);
787779
void WriteResult(Stream* stream, const char* desc, Result);

0 commit comments

Comments
 (0)