From 616f05496e9867cfa934098a76826cfde7feeaa2 Mon Sep 17 00:00:00 2001 From: titzer Date: Tue, 1 Mar 2016 11:36:19 -0800 Subject: [PATCH] [wasm] Add support for 64-bit LEB encodings. R=binji@chromium.org,ahaas@chromium.org BUG= Review URL: https://codereview.chromium.org/1746063003 Cr-Commit-Position: refs/heads/master@{#34406} --- src/wasm/decoder.h | 97 +++++++---- test/unittests/wasm/decoder-unittest.cc | 215 ++++++++++++++++++++++++ 2 files changed, 279 insertions(+), 33 deletions(-) diff --git a/src/wasm/decoder.h b/src/wasm/decoder.h index 2dfdd02aed..4b35685bd8 100644 --- a/src/wasm/decoder.h +++ b/src/wasm/decoder.h @@ -77,53 +77,44 @@ class Decoder { return check(base, offset, 8, msg) ? read_u64(base + offset) : 0; } + // Reads a variable-length unsigned integer (little endian). uint32_t checked_read_u32v(const byte* base, int offset, int* length, - const char* msg = "expected LEB128") { - if (!check(base, offset, 1, msg)) { - *length = 0; - return 0; - } - - const ptrdiff_t kMaxDiff = 5; // maximum 5 bytes. - const byte* ptr = base + offset; - const byte* end = ptr + kMaxDiff; - if (end > limit_) end = limit_; - int shift = 0; - byte b = 0; - uint32_t result = 0; - while (ptr < end) { - b = *ptr++; - result = result | ((b & 0x7F) << shift); - if ((b & 0x80) == 0) break; - shift += 7; - } - DCHECK_LE(ptr - (base + offset), kMaxDiff); - *length = static_cast(ptr - (base + offset)); - if (ptr == end) { - if (*length == kMaxDiff && (b & 0xF0) != 0) { - error(base, ptr, "extra bits in LEB128"); - return 0; - } - if ((b & 0x80) != 0) { - error(base, ptr, msg); - return 0; - } - } - return result; + const char* msg = "expected LEB32") { + return checked_read_leb(base, offset, length, msg); } // Reads a variable-length signed integer (little endian). int32_t checked_read_i32v(const byte* base, int offset, int* length, - const char* msg = "expected SLEB128") { + const char* msg = "expected SLEB32") { uint32_t result = checked_read_u32v(base, offset, length, msg); if (*length == 5) return bit_cast(result); if (*length > 0) { int shift = 32 - 7 * *length; + // Perform sign extension. return bit_cast(result << shift) >> shift; } return 0; } + // Reads a variable-length unsigned integer (little endian). + uint64_t checked_read_u64v(const byte* base, int offset, int* length, + const char* msg = "expected LEB64") { + return checked_read_leb(base, offset, length, msg); + } + + // Reads a variable-length signed integer (little endian). + int64_t checked_read_i64v(const byte* base, int offset, int* length, + const char* msg = "expected SLEB64") { + uint64_t result = checked_read_u64v(base, offset, length, msg); + if (*length == 10) return bit_cast(result); + if (*length > 0) { + int shift = 64 - 7 * *length; + // Perform sign extension. + return bit_cast(result << shift) >> shift; + } + return 0; + } + // Reads a single 16-bit unsigned integer (little endian). inline uint16_t read_u16(const byte* ptr) { DCHECK(ptr >= start_ && (ptr + 2) <= end_); @@ -333,6 +324,46 @@ class Decoder { const byte* error_pc_; const byte* error_pt_; base::SmartArrayPointer error_msg_; + + private: + template + IntType checked_read_leb(const byte* base, int offset, int* length, + const char* msg) { + if (!check(base, offset, 1, msg)) { + *length = 0; + return 0; + } + + const int kMaxLength = (sizeof(IntType) * 8 + 6) / 7; + const byte* ptr = base + offset; + const byte* end = ptr + kMaxLength; + if (end > limit_) end = limit_; + int shift = 0; + byte b = 0; + IntType result = 0; + while (ptr < end) { + b = *ptr++; + result = result | (static_cast(b & 0x7F) << shift); + if ((b & 0x80) == 0) break; + shift += 7; + } + DCHECK_LE(ptr - (base + offset), kMaxLength); + *length = static_cast(ptr - (base + offset)); + if (ptr == end) { + // Check there are no bits set beyond the bitwidth of {IntType}. + const int kExtraBits = (1 + kMaxLength * 7) - (sizeof(IntType) * 8); + const byte kExtraBitsMask = static_cast(0xFF << (8 - kExtraBits)); + if (*length == kMaxLength && (b & kExtraBitsMask) != 0) { + error(base, ptr, "extra bits in varint"); + return 0; + } + if ((b & 0x80) != 0) { + error(base, ptr, msg); + return 0; + } + } + return result; + } }; #undef TRACE diff --git a/test/unittests/wasm/decoder-unittest.cc b/test/unittests/wasm/decoder-unittest.cc index de6bf49ea8..d773c955c0 100644 --- a/test/unittests/wasm/decoder-unittest.cc +++ b/test/unittests/wasm/decoder-unittest.cc @@ -38,6 +38,26 @@ class DecoderTest : public TestWithZone { EXPECT_EQ(expected_length, length); \ } while (false) +#define CHECK_UINT64V_INLINE(expected, expected_length, ...) \ + do { \ + const byte data[] = {__VA_ARGS__}; \ + decoder.Reset(data, data + sizeof(data)); \ + int length; \ + EXPECT_EQ(expected, \ + decoder.checked_read_u64v(decoder.start(), 0, &length)); \ + EXPECT_EQ(expected_length, length); \ + } while (false) + +#define CHECK_INT64V_INLINE(expected, expected_length, ...) \ + do { \ + const byte data[] = {__VA_ARGS__}; \ + decoder.Reset(data, data + sizeof(data)); \ + int length; \ + EXPECT_EQ(expected, \ + decoder.checked_read_i64v(decoder.start(), 0, &length)); \ + EXPECT_EQ(expected_length, length); \ + } while (false) + TEST_F(DecoderTest, ReadU32v_OneByte) { CHECK_UINT32V_INLINE(0, 1, 0); CHECK_UINT32V_INLINE(5, 1, 5); @@ -408,6 +428,201 @@ TEST_F(DecoderTest, ReadU32v_extra_bits) { } } +TEST_F(DecoderTest, ReadU32v_Bits) { + // A more exhaustive test. + const int kMaxSize = 5; + const uint32_t kVals[] = { + 0xaabbccdd, 0x11223344, 0x33445566, 0xffeeddcc, 0xF0F0F0F0, 0x0F0F0F0F, + 0xEEEEEEEE, 0xAAAAAAAA, 0x12345678, 0x9abcdef0, 0x80309488, 0x729ed997, + 0xc4a0cf81, 0x16c6eb85, 0x4206db8e, 0xf3b089d5, 0xaa2e223e, 0xf99e29c8, + 0x4a4357d8, 0x1890b1c1, 0x8d80a085, 0xacb6ae4c, 0x1b827e10, 0xeb5c7bd9, + 0xbb1bc146, 0xdf57a33l}; + byte data[kMaxSize]; + + // foreach value in above array + for (size_t v = 0; v < arraysize(kVals); v++) { + // foreach length 1...32 + for (int i = 1; i <= 32; i++) { + uint32_t val = kVals[v]; + if (i < 32) val &= ((1 << i) - 1); + + int length = 1 + i / 7; + for (int j = 0; j < kMaxSize; j++) { + data[j] = static_cast((val >> (7 * j)) & MASK_7); + } + for (int j = 0; j < length - 1; j++) { + data[j] |= 0x80; + } + + // foreach buffer size 0...5 + for (int limit = 0; limit <= kMaxSize; limit++) { + decoder.Reset(data, data + limit); + int rlen; + uint32_t result = decoder.checked_read_u32v(data, 0, &rlen); + if (limit < length) { + EXPECT_FALSE(decoder.ok()); + } else { + EXPECT_TRUE(decoder.ok()); + EXPECT_EQ(val, result); + EXPECT_EQ(length, rlen); + } + } + } + } +} + +TEST_F(DecoderTest, ReadU64v_OneByte) { + CHECK_UINT64V_INLINE(0, 1, 0); + CHECK_UINT64V_INLINE(6, 1, 6); + CHECK_UINT64V_INLINE(8, 1, 8); + CHECK_UINT64V_INLINE(12, 1, 12); + CHECK_UINT64V_INLINE(33, 1, 33); + CHECK_UINT64V_INLINE(59, 1, 59); + CHECK_UINT64V_INLINE(110, 1, 110); + CHECK_UINT64V_INLINE(125, 1, 125); + CHECK_UINT64V_INLINE(126, 1, 126); + CHECK_UINT64V_INLINE(127, 1, 127); +} + +TEST_F(DecoderTest, ReadI64v_OneByte) { + CHECK_INT64V_INLINE(0, 1, 0); + CHECK_INT64V_INLINE(4, 1, 4); + CHECK_INT64V_INLINE(6, 1, 6); + CHECK_INT64V_INLINE(9, 1, 9); + CHECK_INT64V_INLINE(33, 1, 33); + CHECK_INT64V_INLINE(61, 1, 61); + CHECK_INT64V_INLINE(63, 1, 63); + + CHECK_INT64V_INLINE(-1, 1, 127); + CHECK_INT64V_INLINE(-2, 1, 126); + CHECK_INT64V_INLINE(-11, 1, 117); + CHECK_INT64V_INLINE(-62, 1, 66); + CHECK_INT64V_INLINE(-63, 1, 65); + CHECK_INT64V_INLINE(-64, 1, 64); +} + +TEST_F(DecoderTest, ReadU64v_PowerOf2) { + const int kMaxSize = 10; + byte data[kMaxSize]; + + for (int i = 0; i < 64; i++) { + const uint64_t val = 1ull << i; + int index = i / 7; + data[index] = 1 << (i % 7); + memset(data, 0x80, index); + + for (int limit = 0; limit <= kMaxSize; limit++) { + decoder.Reset(data, data + limit); + int length; + uint64_t result = decoder.checked_read_u64v(data, 0, &length); + if (limit <= index) { + EXPECT_FALSE(decoder.ok()); + } else { + EXPECT_TRUE(decoder.ok()); + EXPECT_EQ(val, result); + EXPECT_EQ(index + 1, length); + } + } + } +} + +TEST_F(DecoderTest, ReadU64v_Bits) { + const int kMaxSize = 10; + const uint64_t kVals[] = { + 0xaabbccdd11223344ull, 0x33445566ffeeddccull, 0xF0F0F0F0F0F0F0F0ull, + 0x0F0F0F0F0F0F0F0Full, 0xEEEEEEEEEEEEEEEEull, 0xAAAAAAAAAAAAAAAAull, + 0x123456789abcdef0ull, 0x80309488729ed997ull, 0xc4a0cf8116c6eb85ull, + 0x4206db8ef3b089d5ull, 0xaa2e223ef99e29c8ull, 0x4a4357d81890b1c1ull, + 0x8d80a085acb6ae4cull, 0x1b827e10eb5c7bd9ull, 0xbb1bc146df57a338ull}; + byte data[kMaxSize]; + + // foreach value in above array + for (size_t v = 0; v < arraysize(kVals); v++) { + // foreach length 1...64 + for (int i = 1; i <= 64; i++) { + uint64_t val = kVals[v]; + if (i < 64) val &= ((1ull << i) - 1); + + int length = 1 + i / 7; + for (int j = 0; j < kMaxSize; j++) { + data[j] = static_cast((val >> (7 * j)) & MASK_7); + } + for (int j = 0; j < length - 1; j++) { + data[j] |= 0x80; + } + + // foreach buffer size 0...10 + for (int limit = 0; limit <= kMaxSize; limit++) { + decoder.Reset(data, data + limit); + int rlen; + uint64_t result = decoder.checked_read_u64v(data, 0, &rlen); + if (limit < length) { + EXPECT_FALSE(decoder.ok()); + } else { + EXPECT_TRUE(decoder.ok()); + EXPECT_EQ(val, result); + EXPECT_EQ(length, rlen); + } + } + } + } +} + +TEST_F(DecoderTest, ReadI64v_Bits) { + const int kMaxSize = 10; + // Exhaustive signedness test. + const uint64_t kVals[] = { + 0xaabbccdd11223344ull, 0x33445566ffeeddccull, 0xF0F0F0F0F0F0F0F0ull, + 0x0F0F0F0F0F0F0F0Full, 0xEEEEEEEEEEEEEEEEull, 0xAAAAAAAAAAAAAAAAull, + 0x123456789abcdef0ull, 0x80309488729ed997ull, 0xc4a0cf8116c6eb85ull, + 0x4206db8ef3b089d5ull, 0xaa2e223ef99e29c8ull, 0x4a4357d81890b1c1ull, + 0x8d80a085acb6ae4cull, 0x1b827e10eb5c7bd9ull, 0xbb1bc146df57a338ull}; + byte data[kMaxSize]; + + // foreach value in above array + for (size_t v = 0; v < arraysize(kVals); v++) { + // foreach length 1...64 + for (int i = 1; i <= 64; i++) { + const int64_t val = bit_cast(kVals[v] << (64 - i)) >> (64 - i); + + int length = 1 + i / 7; + for (int j = 0; j < kMaxSize; j++) { + const uint64_t uval = bit_cast(val); + data[j] = static_cast((uval >> (7 * j)) & MASK_7); + } + for (int j = 0; j < length - 1; j++) { + data[j] |= 0x80; + } + + // foreach buffer size 0...10 + for (int limit = 0; limit <= kMaxSize; limit++) { + decoder.Reset(data, data + limit); + int rlen; + int64_t result = decoder.checked_read_i64v(data, 0, &rlen); + if (limit < length) { + EXPECT_FALSE(decoder.ok()); + } else { + EXPECT_TRUE(decoder.ok()); + EXPECT_EQ(val, result); + EXPECT_EQ(length, rlen); + } + } + } + } +} + +TEST_F(DecoderTest, ReadU64v_extra_bits) { + byte data[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00}; + for (int i = 1; i < 128; i++) { + data[9] = static_cast(i << 1); + int length = 0; + decoder.Reset(data, data + sizeof(data)); + decoder.checked_read_u64v(decoder.start(), 0, &length); + EXPECT_EQ(10, length); + EXPECT_FALSE(decoder.ok()); + } +} + } // namespace wasm } // namespace internal } // namespace v8