// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --wasm-test-streaming --expose-wasm

'use strict';

load('test/mjsunit/wasm/wasm-module-builder.js');

function testErrorPositionAsyncOnly(bytes, pos, message) {
  let buffer = bytes.trunc_buffer();
  // Only test the streaming decoder since this kind of error is out of sync
  // with the non-streaming decoder, hence errors cannot be compared.
  assertThrowsAsync(
      WebAssembly.compile(buffer), WebAssembly.CompileError,
      new RegExp(message + '.*@\\+' + pos));
}

function testErrorPosition(bytes, pos, message) {
  let buffer = bytes.trunc_buffer();
  // First check the non-streaming decoder as a reference.
  assertThrows(
      () => new WebAssembly.Module(buffer), WebAssembly.CompileError,
      new RegExp(message + '.*@\\+' + pos));
  // Next test the actual streaming decoder.
  assertThrowsAsync(
      WebAssembly.compile(buffer), WebAssembly.CompileError,
      new RegExp(message + '.*@\\+' + pos));
}

(function testInvalidMagic() {
  let bytes = new Binary;
  bytes.emit_bytes([
    kWasmH0, kWasmH1 + 1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3
  ]);
  // Error at pos==0 because that's where the magic word is.
  testErrorPosition(bytes, 0, 'expected magic word');
})();

(function testInvalidVersion() {
  let bytes = new Binary;
  bytes.emit_bytes([
    kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1 + 1, kWasmV2, kWasmV3
  ]);
  // Error at pos==4 because that's where the version word is.
  testErrorPosition(bytes, 4, 'expected version');
})();

(function testSectionLengthInvalidVarint() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_u8(kTypeSectionCode);
  bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
  let pos = bytes.length - 1 - 1;
  testErrorPosition(bytes, pos, 'expected section length');
})();

(function testSectionLengthTooBig() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_u8(kTypeSectionCode);
  bytes.emit_u32v(0xffffff23);
  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'maximum function size');
})();

(function testFunctionsCountInvalidVarint() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,  // section id
      1,                 // section length
      0                  // number of types
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      1,                     // section length
      0                      // number of functions
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (arbitrary value > 6)
  ]);
  // Functions count
  bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);

  let pos = bytes.length - 1 - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'expected functions count');
})();

(function testFunctionsCountTooBig() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,  // section id
      1,                 // section length
      0                  // number of types
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      1,                     // section length
      0                      // number of functions
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (arbitrary value > 6)
  ]);
  // Functions count
  bytes.emit_u32v(0xffffff23);

  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'maximum function size');
})();

(function testFunctionsCountDoesNotMatch() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,  // section id
      1,                 // section length
      0                  // number of types
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      1,                     // section length
      0                      // number of functions
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (arbitrary value > 6)
  ]);
  // Functions count (different than the count in the functions section.
  bytes.emit_u32v(5);

  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'function body count 5 mismatch');
})();

(function testBodySizeInvalidVarint() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (arbitrary value > 6)
      1                  // functions count
  ]);
  // Invalid function body size.
  bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);

  let pos = bytes.length - 1 - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'expected body size');
})();

(function testBodySizeTooBig() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (arbitrary value > 6)
      1                  // functions count
  ]);
  // Invalid function body size.
  bytes.emit_u32v(0xffffff23);

  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'maximum function size');
})();

(function testBodySizeDoesNotFit() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (arbitrary value > 6)
      1                  // functions count
  ]);
  // Invalid function body size (does not fit into the code section).
  bytes.emit_u32v(20);

  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'not enough code section bytes');
})();

(function testBodySizeIsZero() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (arbitrary value > 6)
      1                  // functions count
  ]);
  // Invalid function body size (body size of 0 is invalid).
  bytes.emit_u32v(0);

  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'invalid function length');
})();

(function testStaleCodeSectionBytes() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      20,                // section length (too big)
      1,                 // functions count
      2,                 // body size
      0,                 // locals count
      kExprEnd           // body
  ]);

  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'not all code section bytes were used');
})();

(function testInvalidCode() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      6,                 // section length
      1,                 // functions count
      4,                 // body size
      0,                 // locals count
      kExprLocalGet, 0,  // Access a non-existing local
      kExprEnd           // --
  ]);

  // Find error at the index of kExprLocalGet.
  let pos = bytes.length - 1 - 1;
  testErrorPosition(bytes, pos, 'invalid local index');
})();

(function testCodeSectionRepeats() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      4,                 // section length
      1,                 // functions count
      2,                 // body size
      0,                 // locals count
      kExprEnd           // body
  ]);
  let pos = bytes.length;
  bytes.emit_bytes([
      kCodeSectionCode,  // section id (repeating)
      4,                 // section length
      1,                 // functions count
      2,                 // body size
      0,                 // locals count
      kExprEnd           // body
  ]);

  // Find error at the second kCodeSectionCode.
  testErrorPositionAsyncOnly(bytes, pos, 'code section can only appear once');
})();

(function testCodeSectionSizeZero() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      4,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      0,                      // number of parameter
      0                       // number of returns
  ]);
  bytes.emit_bytes([
      kFunctionSectionCode,  // section id
      2,                     // section length
      1,                     // number of functions
      0                      // signature index
  ]);
  bytes.emit_bytes([
      kCodeSectionCode,  // section id
      0,                 // section length (empty)
  ]);

  // Find error at the code section length.
  let pos = bytes.length - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'code section cannot have size 0');
})();

(function testInvalidSection() {
  let bytes = new Binary;
  bytes.emit_header();
  bytes.emit_bytes([
      kTypeSectionCode,       // section id
      5,                      // section length
      1,                      // number of types
      kWasmFunctionTypeForm,  // type
      1,                      // number of parameter
      0x7b,                   // invalid type
      0                       // number of returns
  ]);

  let pos = bytes.length - 1 - 1;
  testErrorPositionAsyncOnly(bytes, pos, 'invalid value type');
})();