/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// WARNING: THIS FILE IS ALSO LOADED FROM A WORKER IN THE CHROME MOCHITEST
// test_ctypes.xhtml - YOU CANNOT JUST ASSUME AN XPCSHELL ENVIRONMENT.
// There is glue code in xpcshellTestHarnessAdaptor.js to make this work.

/* eslint-disable no-new-wrappers */

try {
  Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
  });
} catch (e) {}

const CTYPES_TEST_LIB = ctypes.libraryName("jsctypes-test");
const CTYPES_UNICODE_LIB = ctypes.libraryName("jsctyp\u00E8s-t\u00EB\u00DFt");

function do_check_throws(f, type, stack) {
  if (!stack) {
    try {
      // We might not have a 'Components' object.
      stack = Components.stack.caller;
    } catch (e) {}
  }

  try {
    f();
  } catch (exc) {
    if (exc.constructor.name === type.name) {
      Assert.ok(true);
      return;
    }
    do_throw("expected " + type.name + " exception, caught " + exc, stack);
  }
  do_throw("expected " + type.name + " exception, none thrown", stack);
}

function run_test() {
  // Test ctypes.CType and ctypes.CData are set up correctly.
  run_abstract_class_tests();

  // open the library
  let libfile = do_get_file(CTYPES_TEST_LIB);
  let library = ctypes.open(libfile.path);

  // Make sure we can call a function in the library.
  run_void_tests(library);

  // Test Int64 and UInt64.
  run_Int64_tests();
  run_UInt64_tests();

  // Test the basic bool, integer, and float types.
  run_bool_tests(library);

  run_integer_tests(library, ctypes.int8_t, "int8_t", 1, true, [-0x80, 0x7f]);
  run_integer_tests(
    library,
    ctypes.int16_t,
    "int16_t",
    2,
    true,
    [-0x8000, 0x7fff]
  );
  run_integer_tests(
    library,
    ctypes.int32_t,
    "int32_t",
    4,
    true,
    [-0x80000000, 0x7fffffff]
  );
  run_integer_tests(library, ctypes.uint8_t, "uint8_t", 1, false, [0, 0xff]);
  run_integer_tests(
    library,
    ctypes.uint16_t,
    "uint16_t",
    2,
    false,
    [0, 0xffff]
  );
  run_integer_tests(
    library,
    ctypes.uint32_t,
    "uint32_t",
    4,
    false,
    [0, 0xffffffff]
  );
  run_integer_tests(library, ctypes.short, "short", 2, true, [-0x8000, 0x7fff]);
  run_integer_tests(
    library,
    ctypes.unsigned_short,
    "unsigned_short",
    2,
    false,
    [0, 0xffff]
  );
  run_integer_tests(
    library,
    ctypes.int,
    "int",
    4,
    true,
    [-0x80000000, 0x7fffffff]
  );
  run_integer_tests(
    library,
    ctypes.unsigned_int,
    "unsigned_int",
    4,
    false,
    [0, 0xffffffff]
  );
  run_integer_tests(
    library,
    ctypes.unsigned,
    "unsigned_int",
    4,
    false,
    [0, 0xffffffff]
  );

  run_float_tests(library, ctypes.float32_t, "float32_t", 4);
  run_float_tests(library, ctypes.float64_t, "float64_t", 8);
  run_float_tests(library, ctypes.float, "float", 4);
  run_float_tests(library, ctypes.double, "double", 8);

  // Test the wrapped integer types.
  const s64limits = [
    "-9223372036854775808",
    "9223372036854775807",
    "-9223372036854775809",
    "9223372036854775808",
  ];
  const u64limits = ["0", "18446744073709551615", "-1", "18446744073709551616"];

  run_wrapped_integer_tests(
    library,
    ctypes.int64_t,
    "int64_t",
    8,
    true,
    ctypes.Int64,
    "ctypes.Int64",
    s64limits
  );
  run_wrapped_integer_tests(
    library,
    ctypes.uint64_t,
    "uint64_t",
    8,
    false,
    ctypes.UInt64,
    "ctypes.UInt64",
    u64limits
  );
  run_wrapped_integer_tests(
    library,
    ctypes.long_long,
    "long_long",
    8,
    true,
    ctypes.Int64,
    "ctypes.Int64",
    s64limits
  );
  run_wrapped_integer_tests(
    library,
    ctypes.unsigned_long_long,
    "unsigned_long_long",
    8,
    false,
    ctypes.UInt64,
    "ctypes.UInt64",
    u64limits
  );

  const s32limits = [-0x80000000, 0x7fffffff, -0x80000001, 0x80000000];
  const u32limits = [0, 0xffffffff, -1, 0x100000000];

  let slimits, ulimits;
  if (ctypes.long.size == 8) {
    slimits = s64limits;
    ulimits = u64limits;
  } else if (ctypes.long.size == 4) {
    slimits = s32limits;
    ulimits = u32limits;
  } else {
    do_throw("ctypes.long is not 4 or 8 bytes");
  }

  run_wrapped_integer_tests(
    library,
    ctypes.long,
    "long",
    ctypes.long.size,
    true,
    ctypes.Int64,
    "ctypes.Int64",
    slimits
  );
  run_wrapped_integer_tests(
    library,
    ctypes.unsigned_long,
    "unsigned_long",
    ctypes.long.size,
    false,
    ctypes.UInt64,
    "ctypes.UInt64",
    ulimits
  );

  if (ctypes.size_t.size == 8) {
    slimits = s64limits;
    ulimits = u64limits;
  } else if (ctypes.size_t.size == 4) {
    slimits = s32limits;
    ulimits = u32limits;
  } else {
    do_throw("ctypes.size_t is not 4 or 8 bytes");
  }

  run_wrapped_integer_tests(
    library,
    ctypes.size_t,
    "size_t",
    ctypes.size_t.size,
    false,
    ctypes.UInt64,
    "ctypes.UInt64",
    ulimits
  );
  run_wrapped_integer_tests(
    library,
    ctypes.ssize_t,
    "ssize_t",
    ctypes.size_t.size,
    true,
    ctypes.Int64,
    "ctypes.Int64",
    slimits
  );
  run_wrapped_integer_tests(
    library,
    ctypes.uintptr_t,
    "uintptr_t",
    ctypes.size_t.size,
    false,
    ctypes.UInt64,
    "ctypes.UInt64",
    ulimits
  );
  run_wrapped_integer_tests(
    library,
    ctypes.intptr_t,
    "intptr_t",
    ctypes.size_t.size,
    true,
    ctypes.Int64,
    "ctypes.Int64",
    slimits
  );

  if (ctypes.off_t.size == 8) {
    slimits = s64limits;
    ulimits = u64limits;
  } else if (ctypes.off_t.size == 4) {
    slimits = s32limits;
    ulimits = u32limits;
  } else {
    do_throw("ctypes.off_t is not 4 or 8 bytes");
  }
  run_wrapped_integer_tests(
    library,
    ctypes.off_t,
    "off_t",
    ctypes.off_t.size,
    true,
    ctypes.Int64,
    "ctypes.Int64",
    slimits
  );

  // Test the character types.
  run_char_tests(library, ctypes.char, "char", 1, true, [-0x80, 0x7f]);
  run_char_tests(
    library,
    ctypes.signed_char,
    "signed_char",
    1,
    true,
    [-0x80, 0x7f]
  );
  run_char_tests(
    library,
    ctypes.unsigned_char,
    "unsigned_char",
    1,
    false,
    [0, 0xff]
  );
  run_char16_tests(library, ctypes.char16_t, "char16_t", [0, 0xffff]);

  // Test the special types.
  run_StructType_tests();
  run_PointerType_tests();
  run_FunctionType_tests();
  run_ArrayType_tests();

  // Check that types print properly.
  run_type_toString_tests();

  // Test the 'name' and 'toSource' of a long typename.
  let ptrTo_ptrTo_arrayOf4_ptrTo_int32s = new ctypes.PointerType(
    new ctypes.PointerType(
      new ctypes.ArrayType(new ctypes.PointerType(ctypes.int32_t), 4)
    )
  );
  Assert.equal(ptrTo_ptrTo_arrayOf4_ptrTo_int32s.name, "int32_t*(**)[4]");

  let source_t = new ctypes.StructType("source", [
    { a: ptrTo_ptrTo_arrayOf4_ptrTo_int32s },
    { b: ctypes.int64_t },
  ]);
  Assert.equal(
    source_t.toSource(),
    'ctypes.StructType("source", [{ "a": ctypes.int32_t.ptr.array(4).ptr.ptr }, ' +
      '{ "b": ctypes.int64_t }])'
  );

  // Test ctypes.cast.
  run_cast_tests();

  run_string_tests(library);
  run_readstring_tests(library);
  run_struct_tests(library);
  run_function_tests(library);
  run_closure_tests(library);
  run_variadic_tests(library);
  run_static_data_tests(library);
  run_cpp_class_tests(library);

  // test library.close
  let test_void_t = library.declare(
    "test_void_t_cdecl",
    ctypes.default_abi,
    ctypes.void_t
  );
  library.close();
  do_check_throws(function () {
    test_void_t();
  }, Error);
  do_check_throws(function () {
    library.declare("test_void_t_cdecl", ctypes.default_abi, ctypes.void_t);
  }, Error);

  // test that library functions throw when bound to other objects
  library = ctypes.open(libfile.path);
  let obj = {};
  obj.declare = library.declare;
  do_check_throws(function () {
    run_void_tests(obj);
  }, Error);
  obj.close = library.close;
  do_check_throws(function () {
    obj.close();
  }, Error);

  // test that functions work as properties of other objects
  let getter = library.declare(
    "get_int8_t_cdecl",
    ctypes.default_abi,
    ctypes.int8_t
  );
  Assert.equal(getter(), 109);
  obj.t = getter;
  Assert.equal(obj.t(), 109);

  // bug 521937
  do_check_throws(function () {
    let nolib = ctypes.open("notfoundlibrary.dll");
    nolib.close();
  }, Error);

  // bug 522360
  Assert.equal(run_load_system_library(), true);

  // Test loading a library with a unicode name (bug 589413). Note that nsIFile
  // implementations are not available in some harnesses; if not, the harness
  // should take care of the copy for us.
  let unicodefile = do_get_file(CTYPES_UNICODE_LIB, true);
  let copy = libfile.copyTo instanceof Function;
  if (copy) {
    libfile.copyTo(null, unicodefile.leafName);
  }
  library = ctypes.open(unicodefile.path);
  run_void_tests(library);
  library.close();
  if (copy) {
    // Tolerate remove() failure because Firefox may have the DLL open
    // for examination.
    try {
      unicodefile.remove(false);
    } catch (e) {}
  }
}

function run_abstract_class_tests() {
  // Test that ctypes.CType is an abstract constructor that throws.
  do_check_throws(function () {
    ctypes.CType();
  }, TypeError);
  do_check_throws(function () {
    new ctypes.CType();
  }, TypeError);

  Assert.ok(ctypes.CType.hasOwnProperty("prototype"));
  do_check_throws(function () {
    ctypes.CType.prototype();
  }, TypeError);
  do_check_throws(function () {
    new ctypes.CType.prototype();
  }, TypeError);

  Assert.ok(ctypes.CType.prototype.hasOwnProperty("constructor"));
  Assert.strictEqual(ctypes.CType.prototype.constructor, ctypes.CType);

  // Check that ctypes.CType.prototype has the correct properties and functions.
  Assert.ok(ctypes.CType.prototype.hasOwnProperty("name"));
  Assert.ok(ctypes.CType.prototype.hasOwnProperty("size"));
  Assert.ok(ctypes.CType.prototype.hasOwnProperty("ptr"));
  Assert.ok(ctypes.CType.prototype.hasOwnProperty("array"));
  Assert.ok(ctypes.CType.prototype.hasOwnProperty("toString"));
  Assert.ok(ctypes.CType.prototype.hasOwnProperty("toSource"));

  // Make sure we can access 'prototype' on a CTypeProto.
  Assert.strictEqual(ctypes.CType.prototype.prototype, ctypes.CData.prototype);

  // Check that the shared properties and functions on ctypes.CType.prototype throw.
  do_check_throws(function () {
    ctypes.CType.prototype.name;
  }, TypeError);
  do_check_throws(function () {
    ctypes.CType.prototype.size;
  }, TypeError);
  do_check_throws(function () {
    ctypes.CType.prototype.ptr;
  }, TypeError);
  do_check_throws(function () {
    ctypes.CType.prototype.array();
  }, TypeError);

  // toString and toSource are called by the web console during inspection,
  // so we don't want them to throw.
  Assert.equal(typeof ctypes.CType.prototype.toString(), "string");
  Assert.equal(typeof ctypes.CType.prototype.toSource(), "string");

  // Test that ctypes.CData is an abstract constructor that throws.
  do_check_throws(function () {
    ctypes.CData();
  }, TypeError);
  do_check_throws(function () {
    new ctypes.CData();
  }, TypeError);

  Assert.strictEqual(
    Object.getPrototypeOf(ctypes.CData),
    ctypes.CType.prototype
  );
  Assert.ok(ctypes.CData instanceof ctypes.CType);

  Assert.ok(ctypes.CData.hasOwnProperty("prototype"));
  Assert.ok(ctypes.CData.prototype.hasOwnProperty("constructor"));
  Assert.strictEqual(ctypes.CData.prototype.constructor, ctypes.CData);

  // Check that ctypes.CData.prototype has the correct properties and functions.
  Assert.ok(ctypes.CData.prototype.hasOwnProperty("value"));
  Assert.ok(ctypes.CData.prototype.hasOwnProperty("address"));
  Assert.ok(ctypes.CData.prototype.hasOwnProperty("readString"));
  Assert.ok(ctypes.CData.prototype.hasOwnProperty("toString"));
  Assert.ok(ctypes.CData.prototype.hasOwnProperty("toSource"));

  // Check that the shared properties and functions on ctypes.CData.prototype throw.
  do_check_throws(function () {
    ctypes.CData.prototype.value;
  }, TypeError);
  do_check_throws(function () {
    ctypes.CData.prototype.value = null;
  }, TypeError);
  do_check_throws(function () {
    ctypes.CData.prototype.address();
  }, TypeError);
  do_check_throws(function () {
    ctypes.CData.prototype.readString();
  }, TypeError);

  // toString and toSource are called by the web console during inspection,
  // so we don't want them to throw.
  Assert.equal(ctypes.CData.prototype.toString(), "[CData proto object]");
  Assert.equal(ctypes.CData.prototype.toSource(), "[CData proto object]");
}

function run_Int64_tests() {
  do_check_throws(function () {
    ctypes.Int64();
  }, TypeError);

  Assert.ok(ctypes.Int64.hasOwnProperty("prototype"));
  Assert.ok(ctypes.Int64.prototype.hasOwnProperty("constructor"));
  Assert.strictEqual(ctypes.Int64.prototype.constructor, ctypes.Int64);

  // Check that ctypes.Int64 and ctypes.Int64.prototype have the correct
  // properties and functions.
  Assert.ok(ctypes.Int64.hasOwnProperty("compare"));
  Assert.ok(ctypes.Int64.hasOwnProperty("lo"));
  Assert.ok(ctypes.Int64.hasOwnProperty("hi"));
  Assert.ok(ctypes.Int64.hasOwnProperty("join"));
  Assert.ok(ctypes.Int64.prototype.hasOwnProperty("toString"));
  Assert.ok(ctypes.Int64.prototype.hasOwnProperty("toSource"));

  // Check that the shared functions on ctypes.Int64.prototype throw.
  do_check_throws(function () {
    ctypes.Int64.prototype.toString();
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.prototype.toSource();
  }, TypeError);

  let int64 = ctypes.Int64(0);
  Assert.strictEqual(Object.getPrototypeOf(int64), ctypes.Int64.prototype);
  Assert.ok(int64 instanceof ctypes.Int64);

  // Test Int64.toString([radix]).
  Assert.equal(int64.toString(), "0");
  for (let radix = 2; radix <= 36; ++radix) {
    Assert.equal(int64.toString(radix), "0");
  }
  do_check_throws(function () {
    int64.toString(0);
  }, RangeError);
  do_check_throws(function () {
    int64.toString(1);
  }, RangeError);
  do_check_throws(function () {
    int64.toString(37);
  }, RangeError);
  do_check_throws(function () {
    int64.toString(10, 2);
  }, TypeError);

  // Test Int64.toSource().
  Assert.equal(int64.toSource(), 'ctypes.Int64("0")');
  do_check_throws(function () {
    int64.toSource(10);
  }, TypeError);

  int64 = ctypes.Int64("0x28590a1c921def71");
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "2907366152271163249");
  Assert.equal(int64.toString(16), "28590a1c921def71");
  Assert.equal(
    int64.toString(2),
    "10100001011001000010100001110010010010000111011110111101110001"
  );
  Assert.equal(int64.toSource(), 'ctypes.Int64("' + int64.toString(10) + '")');

  int64 = ctypes.Int64("-0x28590a1c921def71");
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "-2907366152271163249");
  Assert.equal(int64.toString(16), "-28590a1c921def71");
  Assert.equal(
    int64.toString(2),
    "-10100001011001000010100001110010010010000111011110111101110001"
  );
  Assert.equal(int64.toSource(), 'ctypes.Int64("' + int64.toString(10) + '")');

  int64 = ctypes.Int64("-0X28590A1c921DEf71");
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "-2907366152271163249");
  Assert.equal(int64.toString(16), "-28590a1c921def71");
  Assert.equal(
    int64.toString(2),
    "-10100001011001000010100001110010010010000111011110111101110001"
  );
  Assert.equal(int64.toSource(), 'ctypes.Int64("' + int64.toString(10) + '")');

  // Test Int64(primitive double) constructor.
  int64 = ctypes.Int64(-0);
  Assert.equal(int64.toString(), "0");

  int64 = ctypes.Int64(0x7ffffffffffff000);
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "9223372036854771712");
  Assert.equal(int64.toString(16), "7ffffffffffff000");
  Assert.equal(
    int64.toString(2),
    "111111111111111111111111111111111111111111111111111000000000000"
  );

  int64 = ctypes.Int64(-0x8000000000000000);
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "-9223372036854775808");
  Assert.equal(int64.toString(16), "-8000000000000000");
  Assert.equal(
    int64.toString(2),
    "-1000000000000000000000000000000000000000000000000000000000000000"
  );

  // Test Int64(string) constructor.
  int64 = ctypes.Int64("0x7fffffffffffffff");
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "9223372036854775807");
  Assert.equal(int64.toString(16), "7fffffffffffffff");
  Assert.equal(
    int64.toString(2),
    "111111111111111111111111111111111111111111111111111111111111111"
  );

  int64 = ctypes.Int64("-0x8000000000000000");
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "-9223372036854775808");
  Assert.equal(int64.toString(16), "-8000000000000000");
  Assert.equal(
    int64.toString(2),
    "-1000000000000000000000000000000000000000000000000000000000000000"
  );

  int64 = ctypes.Int64("9223372036854775807");
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "9223372036854775807");
  Assert.equal(int64.toString(16), "7fffffffffffffff");
  Assert.equal(
    int64.toString(2),
    "111111111111111111111111111111111111111111111111111111111111111"
  );

  int64 = ctypes.Int64("-9223372036854775808");
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "-9223372036854775808");
  Assert.equal(int64.toString(16), "-8000000000000000");
  Assert.equal(
    int64.toString(2),
    "-1000000000000000000000000000000000000000000000000000000000000000"
  );

  // Test Int64(other Int64) constructor.
  int64 = ctypes.Int64(ctypes.Int64(0));
  Assert.equal(int64.toString(), "0");

  int64 = ctypes.Int64(ctypes.Int64("0x7fffffffffffffff"));
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "9223372036854775807");
  Assert.equal(int64.toString(16), "7fffffffffffffff");
  Assert.equal(
    int64.toString(2),
    "111111111111111111111111111111111111111111111111111111111111111"
  );

  int64 = ctypes.Int64(ctypes.Int64("-0x8000000000000000"));
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "-9223372036854775808");
  Assert.equal(int64.toString(16), "-8000000000000000");
  Assert.equal(
    int64.toString(2),
    "-1000000000000000000000000000000000000000000000000000000000000000"
  );

  // Test Int64(other UInt64) constructor.
  int64 = ctypes.Int64(ctypes.UInt64(0));
  Assert.equal(int64.toString(), "0");

  int64 = ctypes.Int64(ctypes.UInt64("0x7fffffffffffffff"));
  Assert.equal(int64.toString(), int64.toString(10));
  Assert.equal(int64.toString(10), "9223372036854775807");
  Assert.equal(int64.toString(16), "7fffffffffffffff");
  Assert.equal(
    int64.toString(2),
    "111111111111111111111111111111111111111111111111111111111111111"
  );

  let vals = [
    -0x8000000000001000,
    0x8000000000000000,
    ctypes.UInt64("0x8000000000000000"),
    Infinity,
    -Infinity,
    NaN,
    0.1,
    5.68e21,
    null,
    undefined,
    "",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      ctypes.Int64(vals[i]);
    }, TypeError);
  }

  vals = ["-0x8000000000000001", "0x8000000000000000"];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      ctypes.Int64(vals[i]);
    }, RangeError);
  }

  // Test ctypes.Int64.compare.
  Assert.equal(ctypes.Int64.compare(ctypes.Int64(5), ctypes.Int64(5)), 0);
  Assert.equal(ctypes.Int64.compare(ctypes.Int64(5), ctypes.Int64(4)), 1);
  Assert.equal(ctypes.Int64.compare(ctypes.Int64(4), ctypes.Int64(5)), -1);
  Assert.equal(ctypes.Int64.compare(ctypes.Int64(-5), ctypes.Int64(-5)), 0);
  Assert.equal(ctypes.Int64.compare(ctypes.Int64(-5), ctypes.Int64(-4)), -1);
  Assert.equal(ctypes.Int64.compare(ctypes.Int64(-4), ctypes.Int64(-5)), 1);
  do_check_throws(function () {
    ctypes.Int64.compare(ctypes.Int64(4), ctypes.UInt64(4));
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.compare(4, 5);
  }, TypeError);

  // Test ctypes.Int64.{lo,hi}.
  Assert.equal(ctypes.Int64.lo(ctypes.Int64(0x28590a1c921de000)), 0x921de000);
  Assert.equal(ctypes.Int64.hi(ctypes.Int64(0x28590a1c921de000)), 0x28590a1c);
  Assert.equal(ctypes.Int64.lo(ctypes.Int64(-0x28590a1c921de000)), 0x6de22000);
  Assert.equal(ctypes.Int64.hi(ctypes.Int64(-0x28590a1c921de000)), -0x28590a1d);
  do_check_throws(function () {
    ctypes.Int64.lo(ctypes.UInt64(0));
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.hi(ctypes.UInt64(0));
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.lo(0);
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.hi(0);
  }, TypeError);

  // Test ctypes.Int64.join.
  Assert.equal(ctypes.Int64.join(0, 0).toString(), "0");
  Assert.equal(
    ctypes.Int64.join(0x28590a1c, 0x921de000).toString(16),
    "28590a1c921de000"
  );
  Assert.equal(
    ctypes.Int64.join(-0x28590a1d, 0x6de22000).toString(16),
    "-28590a1c921de000"
  );
  Assert.equal(
    ctypes.Int64.join(0x7fffffff, 0xffffffff).toString(16),
    "7fffffffffffffff"
  );
  Assert.equal(
    ctypes.Int64.join(-0x80000000, 0x00000000).toString(16),
    "-8000000000000000"
  );
  /* eslint-disable mozilla/use-returnValue */
  do_check_throws(function () {
    ctypes.Int64.join(-0x80000001, 0);
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.join(0x80000000, 0);
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.join(0, -0x1);
  }, TypeError);
  do_check_throws(function () {
    ctypes.Int64.join(0, 0x800000000);
  }, TypeError);
  /* eslint-enable mozilla/use-returnValue */
}

function run_UInt64_tests() {
  do_check_throws(function () {
    ctypes.UInt64();
  }, TypeError);

  Assert.ok(ctypes.UInt64.hasOwnProperty("prototype"));
  Assert.ok(ctypes.UInt64.prototype.hasOwnProperty("constructor"));
  Assert.strictEqual(ctypes.UInt64.prototype.constructor, ctypes.UInt64);

  // Check that ctypes.UInt64 and ctypes.UInt64.prototype have the correct
  // properties and functions.
  Assert.ok(ctypes.UInt64.hasOwnProperty("compare"));
  Assert.ok(ctypes.UInt64.hasOwnProperty("lo"));
  Assert.ok(ctypes.UInt64.hasOwnProperty("hi"));
  Assert.ok(ctypes.UInt64.hasOwnProperty("join"));
  Assert.ok(ctypes.UInt64.prototype.hasOwnProperty("toString"));
  Assert.ok(ctypes.UInt64.prototype.hasOwnProperty("toSource"));

  // Check that the shared functions on ctypes.UInt64.prototype throw.
  do_check_throws(function () {
    ctypes.UInt64.prototype.toString();
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.prototype.toSource();
  }, TypeError);

  let uint64 = ctypes.UInt64(0);
  Assert.strictEqual(Object.getPrototypeOf(uint64), ctypes.UInt64.prototype);
  Assert.ok(uint64 instanceof ctypes.UInt64);

  // Test UInt64.toString([radix]).
  Assert.equal(uint64.toString(), "0");
  for (let radix = 2; radix <= 36; ++radix) {
    Assert.equal(uint64.toString(radix), "0");
  }
  do_check_throws(function () {
    uint64.toString(0);
  }, RangeError);
  do_check_throws(function () {
    uint64.toString(1);
  }, RangeError);
  do_check_throws(function () {
    uint64.toString(37);
  }, RangeError);
  do_check_throws(function () {
    uint64.toString(10, 2);
  }, TypeError);

  // Test UInt64.toSource().
  Assert.equal(uint64.toSource(), 'ctypes.UInt64("0")');
  do_check_throws(function () {
    uint64.toSource(10);
  }, TypeError);

  uint64 = ctypes.UInt64("0x28590a1c921def71");
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "2907366152271163249");
  Assert.equal(uint64.toString(16), "28590a1c921def71");
  Assert.equal(
    uint64.toString(2),
    "10100001011001000010100001110010010010000111011110111101110001"
  );
  Assert.equal(
    uint64.toSource(),
    'ctypes.UInt64("' + uint64.toString(10) + '")'
  );

  uint64 = ctypes.UInt64("0X28590A1c921DEf71");
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "2907366152271163249");
  Assert.equal(uint64.toString(16), "28590a1c921def71");
  Assert.equal(
    uint64.toString(2),
    "10100001011001000010100001110010010010000111011110111101110001"
  );
  Assert.equal(
    uint64.toSource(),
    'ctypes.UInt64("' + uint64.toString(10) + '")'
  );

  // Test UInt64(primitive double) constructor.
  uint64 = ctypes.UInt64(-0);
  Assert.equal(uint64.toString(), "0");

  uint64 = ctypes.UInt64(0xfffffffffffff000);
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "18446744073709547520");
  Assert.equal(uint64.toString(16), "fffffffffffff000");
  Assert.equal(
    uint64.toString(2),
    "1111111111111111111111111111111111111111111111111111000000000000"
  );

  // Test UInt64(string) constructor.
  uint64 = ctypes.UInt64("0xffffffffffffffff");
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "18446744073709551615");
  Assert.equal(uint64.toString(16), "ffffffffffffffff");
  Assert.equal(
    uint64.toString(2),
    "1111111111111111111111111111111111111111111111111111111111111111"
  );

  uint64 = ctypes.UInt64("0x0");
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "0");
  Assert.equal(uint64.toString(16), "0");
  Assert.equal(uint64.toString(2), "0");

  uint64 = ctypes.UInt64("18446744073709551615");
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "18446744073709551615");
  Assert.equal(uint64.toString(16), "ffffffffffffffff");
  Assert.equal(
    uint64.toString(2),
    "1111111111111111111111111111111111111111111111111111111111111111"
  );

  uint64 = ctypes.UInt64("0");
  Assert.equal(uint64.toString(), "0");

  // Test UInt64(other UInt64) constructor.
  uint64 = ctypes.UInt64(ctypes.UInt64(0));
  Assert.equal(uint64.toString(), "0");

  uint64 = ctypes.UInt64(ctypes.UInt64("0xffffffffffffffff"));
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "18446744073709551615");
  Assert.equal(uint64.toString(16), "ffffffffffffffff");
  Assert.equal(
    uint64.toString(2),
    "1111111111111111111111111111111111111111111111111111111111111111"
  );

  uint64 = ctypes.UInt64(ctypes.UInt64("0x0"));
  Assert.equal(uint64.toString(), "0");

  // Test UInt64(other Int64) constructor.
  uint64 = ctypes.UInt64(ctypes.Int64(0));
  Assert.equal(uint64.toString(), "0");

  uint64 = ctypes.UInt64(ctypes.Int64("0x7fffffffffffffff"));
  Assert.equal(uint64.toString(), uint64.toString(10));
  Assert.equal(uint64.toString(10), "9223372036854775807");
  Assert.equal(uint64.toString(16), "7fffffffffffffff");
  Assert.equal(
    uint64.toString(2),
    "111111111111111111111111111111111111111111111111111111111111111"
  );

  let vals = [
    -1,
    0x10000000000000000,
    "-1",
    "-0x1",
    ctypes.Int64("-1"),
    Infinity,
    -Infinity,
    NaN,
    0.1,
    5.68e21,
    null,
    undefined,
    "",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      ctypes.UInt64(vals[i]);
    }, TypeError);
  }

  vals = ["0x10000000000000000"];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      ctypes.UInt64(vals[i]);
    }, RangeError);
  }

  // Test ctypes.UInt64.compare.
  Assert.equal(ctypes.UInt64.compare(ctypes.UInt64(5), ctypes.UInt64(5)), 0);
  Assert.equal(ctypes.UInt64.compare(ctypes.UInt64(5), ctypes.UInt64(4)), 1);
  Assert.equal(ctypes.UInt64.compare(ctypes.UInt64(4), ctypes.UInt64(5)), -1);
  do_check_throws(function () {
    ctypes.UInt64.compare(ctypes.UInt64(4), ctypes.Int64(4));
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.compare(4, 5);
  }, TypeError);

  // Test ctypes.UInt64.{lo,hi}.
  Assert.equal(ctypes.UInt64.lo(ctypes.UInt64(0x28590a1c921de000)), 0x921de000);
  Assert.equal(ctypes.UInt64.hi(ctypes.UInt64(0x28590a1c921de000)), 0x28590a1c);
  Assert.equal(ctypes.UInt64.lo(ctypes.UInt64(0xa8590a1c921de000)), 0x921de000);
  Assert.equal(ctypes.UInt64.hi(ctypes.UInt64(0xa8590a1c921de000)), 0xa8590a1c);
  do_check_throws(function () {
    ctypes.UInt64.lo(ctypes.Int64(0));
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.hi(ctypes.Int64(0));
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.lo(0);
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.hi(0);
  }, TypeError);

  // Test ctypes.UInt64.join.
  Assert.equal(ctypes.UInt64.join(0, 0).toString(), "0");
  Assert.equal(
    ctypes.UInt64.join(0x28590a1c, 0x921de000).toString(16),
    "28590a1c921de000"
  );
  Assert.equal(
    ctypes.UInt64.join(0xa8590a1c, 0x921de000).toString(16),
    "a8590a1c921de000"
  );
  Assert.equal(
    ctypes.UInt64.join(0xffffffff, 0xffffffff).toString(16),
    "ffffffffffffffff"
  );
  Assert.equal(ctypes.UInt64.join(0, 0).toString(16), "0");
  /* eslint-disable mozilla/use-returnValue */
  do_check_throws(function () {
    ctypes.UInt64.join(-0x1, 0);
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.join(0x100000000, 0);
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.join(0, -0x1);
  }, TypeError);
  do_check_throws(function () {
    ctypes.UInt64.join(0, 0x1000000000);
  }, TypeError);
  /* eslint-enable mozilla/use-returnValue */
}

function run_basic_abi_tests(
  library,
  t,
  name,
  toprimitive,
  get_test,
  set_tests,
  sum_tests,
  sum_many_tests
) {
  // Test the function call ABI for calls involving the type.
  function declare_fn_cdecl(fn_t, prefix) {
    return library.declare(prefix + name + "_cdecl", fn_t);
  }
  run_single_abi_tests(
    declare_fn_cdecl,
    ctypes.default_abi,
    t,
    toprimitive,
    get_test,
    set_tests,
    sum_tests,
    sum_many_tests
  );

  if ("winLastError" in ctypes) {
    function declare_fn_stdcall(fn_t, prefix) {
      return library.declare(prefix + name + "_stdcall", fn_t);
    }
    run_single_abi_tests(
      declare_fn_stdcall,
      ctypes.stdcall_abi,
      t,
      toprimitive,
      get_test,
      set_tests,
      sum_tests,
      sum_many_tests
    );

    // Check that declaring a WINAPI function gets the right symbol name.
    let libuser32 = ctypes.open("user32.dll");
    let charupper = libuser32.declare(
      "CharUpperA",
      ctypes.winapi_abi,
      ctypes.char.ptr,
      ctypes.char.ptr
    );
    let hello = ctypes.char.array()("hello!");
    Assert.equal(charupper(hello).readString(), "HELLO!");
  }

  // Check the alignment of the type, and its behavior in a struct,
  // against what C says.
  check_struct_stats(library, t);

  // Check the ToSource functions defined in the namespace ABI
  Assert.equal(ctypes.default_abi.toSource(), "ctypes.default_abi");

  let exn;
  try {
    ctypes.default_abi.toSource.call(null);
  } catch (x) {
    exn = x;
  }
  Assert.ok(!!exn); // Check that some exception was raised
}

function run_single_abi_tests(
  decl,
  abi,
  t,
  toprimitive,
  get_test,
  set_tests,
  sum_tests,
  sum_many_tests
) {
  let getter_t = ctypes.FunctionType(abi, t).ptr;
  let getter = decl(getter_t, "get_");
  Assert.equal(toprimitive(getter()), get_test);

  let setter_t = ctypes.FunctionType(abi, t, [t]).ptr;
  let setter = decl(setter_t, "set_");
  for (let i of set_tests) {
    Assert.equal(toprimitive(setter(i)), i);
  }

  let sum_t = ctypes.FunctionType(abi, t, [t, t]).ptr;
  let sum = decl(sum_t, "sum_");
  for (let a of sum_tests) {
    Assert.equal(toprimitive(sum(a[0], a[1])), a[2]);
  }

  let sum_alignb_t = ctypes.FunctionType(abi, t, [
    ctypes.char,
    t,
    ctypes.char,
    t,
    ctypes.char,
  ]).ptr;
  let sum_alignb = decl(sum_alignb_t, "sum_alignb_");
  let sum_alignf_t = ctypes.FunctionType(abi, t, [
    ctypes.float,
    t,
    ctypes.float,
    t,
    ctypes.float,
  ]).ptr;
  let sum_alignf = decl(sum_alignf_t, "sum_alignf_");
  for (let a of sum_tests) {
    Assert.equal(toprimitive(sum_alignb(0, a[0], 0, a[1], 0)), a[2]);
    Assert.equal(toprimitive(sum_alignb(1, a[0], 1, a[1], 1)), a[2]);
    Assert.equal(toprimitive(sum_alignf(0, a[0], 0, a[1], 0)), a[2]);
    Assert.equal(toprimitive(sum_alignf(1, a[0], 1, a[1], 1)), a[2]);
  }

  let sum_many_t = ctypes.FunctionType(abi, t, [
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
    t,
  ]).ptr;
  let sum_many = decl(sum_many_t, "sum_many_");
  for (let a of sum_many_tests) {
    Assert.equal(
      toprimitive(
        sum_many(
          a[0],
          a[1],
          a[2],
          a[3],
          a[4],
          a[5],
          a[6],
          a[7],
          a[8],
          a[9],
          a[10],
          a[11],
          a[12],
          a[13],
          a[14],
          a[15],
          a[16],
          a[17]
        )
      ),
      a[18]
    );
  }
}

function check_struct_stats(library, t) {
  let s_t = ctypes.StructType("s_t", [{ x: ctypes.char }, { y: t }]);
  let n_t = ctypes.StructType("n_t", [
    { a: ctypes.char },
    { b: s_t },
    { c: ctypes.char },
  ]);
  let get_stats = library.declare(
    "get_" + t.name + "_stats",
    ctypes.default_abi,
    ctypes.void_t,
    ctypes.size_t.ptr,
    ctypes.size_t.ptr,
    ctypes.size_t.ptr,
    ctypes.size_t.ptr,
    ctypes.size_t.array()
  );

  let align = ctypes.size_t();
  let size = ctypes.size_t();
  let nalign = ctypes.size_t();
  let nsize = ctypes.size_t();
  let offsets = ctypes.size_t.array(3)();
  get_stats(
    align.address(),
    size.address(),
    nalign.address(),
    nsize.address(),
    offsets
  );

  Assert.equal(size.value, s_t.size);
  Assert.equal(align.value, s_t.size - t.size);
  Assert.equal(align.value, offsetof(s_t, "y"));
  Assert.equal(nsize.value, n_t.size);
  Assert.equal(nalign.value, offsetof(n_t, "b"));
  Assert.equal(offsets[0], offsetof(s_t, "y"));
  Assert.equal(offsets[1], offsetof(n_t, "b"));
  Assert.equal(offsets[2], offsetof(n_t, "c"));
}

// Determine the offset, in bytes, of 'member' within 'struct'.
function offsetof(struct, member) {
  let instance = struct();
  let memberptr = ptrValue(instance.addressOfField(member));
  let chararray = ctypes.cast(instance, ctypes.char.array(struct.size));
  let offset = 0;
  while (memberptr != ptrValue(chararray.addressOfElement(offset))) {
    ++offset;
  }
  return offset;
}

// Test the class and prototype hierarchy for a given basic type 't'.
function run_basic_class_tests(t) {
  Assert.strictEqual(Object.getPrototypeOf(t), ctypes.CType.prototype);
  Assert.ok(t instanceof ctypes.CType);

  Assert.strictEqual(
    Object.getPrototypeOf(t.prototype),
    ctypes.CData.prototype
  );
  Assert.ok(t.prototype instanceof ctypes.CData);
  Assert.strictEqual(t.prototype.constructor, t);

  // Check that the shared properties and functions on 't.prototype' throw.
  do_check_throws(function () {
    t.prototype.value;
  }, TypeError);
  do_check_throws(function () {
    t.prototype.value = null;
  }, TypeError);
  do_check_throws(function () {
    t.prototype.address();
  }, TypeError);
  do_check_throws(function () {
    t.prototype.readString();
  }, TypeError);

  // toString and toSource are called by the web console during inspection,
  // so we don't want them to throw.
  Assert.equal(t.prototype.toString(), "[CData proto object]");
  Assert.equal(t.prototype.toSource(), "[CData proto object]");

  // Test that an instance 'd' of 't' is a CData.
  let d = t();
  Assert.strictEqual(Object.getPrototypeOf(d), t.prototype);
  Assert.ok(d instanceof t);
  Assert.strictEqual(d.constructor, t);
}

function run_bool_tests(library) {
  let t = ctypes.bool;
  run_basic_class_tests(t);

  let name = "bool";
  Assert.equal(t.name, name);
  Assert.ok(t.size == 1 || t.size == 4);

  Assert.equal(t.toString(), "type " + name);
  Assert.equal(t.toSource(), "ctypes." + name);
  Assert.strictEqual(t.ptr, ctypes.PointerType(t));
  Assert.equal(t.array().name, name + "[]");
  Assert.equal(t.array(5).name, name + "[5]");

  let d = t();
  Assert.equal(d.value, 0);
  d.value = 1;
  Assert.equal(d.value, 1);
  d.value = -0;
  Assert.equal(1 / d.value, 1 / 0);
  d.value = false;
  Assert.equal(d.value, 0);
  d.value = true;
  Assert.equal(d.value, 1);
  d = new t(1);
  Assert.equal(d.value, 1);

  // don't convert anything else
  let vals = [
    -1,
    2,
    Infinity,
    -Infinity,
    NaN,
    0.1,
    ctypes.Int64(0),
    ctypes.UInt64(0),
    null,
    undefined,
    "",
    "0",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      d.value = vals[i];
    }, TypeError);
  }

  Assert.strictEqual(d.address().constructor, t.ptr);
  Assert.equal(d.address().contents, d.value);
  Assert.equal(d.toSource(), "ctypes." + name + "(" + d.value + ")");
  Assert.equal(d.toSource(), d.toString());

  // Test the function call ABI for calls involving the type,
  // and check the alignment of the type against what C says.
  function toprimitive(a) {
    return a;
  }
  run_basic_abi_tests(
    library,
    t,
    name,
    toprimitive,
    true,
    [false, true],
    [
      [false, false, false],
      [false, true, true],
      [true, false, true],
      [true, true, true],
    ],
    [
      [
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
        false,
      ],
      [
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
        true,
      ],
    ]
  );
}

function run_integer_tests(library, t, name, size, signed, limits) {
  run_basic_class_tests(t);

  Assert.equal(t.name, name);
  Assert.equal(t.size, size);

  Assert.equal(t.toString(), "type " + name);
  Assert.equal(t.toSource(), "ctypes." + name);
  Assert.strictEqual(t.ptr, ctypes.PointerType(t));
  Assert.equal(t.array().name, name + "[]");
  Assert.equal(t.array(5).name, name + "[5]");

  // Check the alignment of the type, and its behavior in a struct,
  // against what C says.
  check_struct_stats(library, t);

  let d = t();
  Assert.equal(d.value, 0);
  d.value = 5;
  Assert.equal(d.value, 5);
  d = t(10);
  Assert.equal(d.value, 10);
  if (signed) {
    d.value = -10;
    Assert.equal(d.value, -10);
  }
  d = new t(20);
  Assert.equal(d.value, 20);

  d.value = ctypes.Int64(5);
  Assert.equal(d.value, 5);
  if (signed) {
    d.value = ctypes.Int64(-5);
    Assert.equal(d.value, -5);
  }
  d.value = ctypes.UInt64(5);
  Assert.equal(d.value, 5);

  d.value = limits[0];
  Assert.equal(d.value, limits[0]);
  d.value = limits[1];
  Assert.equal(d.value, limits[1]);
  d.value = 0;
  Assert.equal(d.value, 0);
  d.value = -0;
  Assert.equal(1 / d.value, 1 / 0);
  d.value = false;
  Assert.equal(d.value, 0);
  d.value = true;
  Assert.equal(d.value, 1);

  // don't convert anything else
  let vals = [
    limits[0] - 1,
    limits[1] + 1,
    Infinity,
    -Infinity,
    NaN,
    0.1,
    null,
    undefined,
    "",
    "0",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      d.value = vals[i];
    }, TypeError);
  }

  Assert.strictEqual(d.address().constructor, t.ptr);
  Assert.equal(d.address().contents, d.value);
  Assert.equal(d.toSource(), "ctypes." + name + "(" + d.value + ")");
  Assert.equal(d.toSource(), d.toString());

  // Test the function call ABI for calls involving the type,
  // and check the alignment of the type against what C says.
  function toprimitive(a) {
    return a;
  }
  run_basic_abi_tests(
    library,
    t,
    name,
    toprimitive,
    109,
    [0, limits[0], limits[1]],
    [
      [0, 0, 0],
      [0, 1, 1],
      [1, 0, 1],
      [1, 1, 2],
      [limits[0], 1, limits[0] + 1],
      [limits[1], 1, limits[0]],
    ],
    [
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18],
    ]
  );
}

function run_float_tests(library, t, name, size) {
  run_basic_class_tests(t);

  Assert.equal(t.name, name);
  Assert.equal(t.size, size);

  Assert.equal(t.toString(), "type " + name);
  Assert.equal(t.toSource(), "ctypes." + name);
  Assert.strictEqual(t.ptr, ctypes.PointerType(t));
  Assert.equal(t.array().name, name + "[]");
  Assert.equal(t.array(5).name, name + "[5]");

  let d = t();
  Assert.equal(d.value, 0);
  d.value = 5;
  Assert.equal(d.value, 5);
  d.value = 5.25;
  Assert.equal(d.value, 5.25);
  d = t(10);
  Assert.equal(d.value, 10);
  d.value = -10;
  Assert.equal(d.value, -10);
  d = new t(20);
  Assert.equal(d.value, 20);

  do_check_throws(function () {
    d.value = ctypes.Int64(5);
  }, TypeError);
  do_check_throws(function () {
    d.value = ctypes.Int64(-5);
  }, TypeError);
  do_check_throws(function () {
    d.value = ctypes.UInt64(5);
  }, TypeError);

  if (size == 4) {
    d.value = 0x7fffff;
    Assert.equal(d.value, 0x7fffff);

    // allow values that can't be represented precisely as a float
    d.value = 0xffffffff;
    let delta = 1 - d.value / 0xffffffff;
    Assert.notEqual(delta, 0);
    Assert.ok(delta > -0.01 && delta < 0.01);
    d.value = 1 + 1 / 0x80000000;
    Assert.equal(d.value, 1);
  } else {
    d.value = 0xfffffffffffff000;
    Assert.equal(d.value, 0xfffffffffffff000);

    do_check_throws(function () {
      d.value = ctypes.Int64("0x7fffffffffffffff");
    }, TypeError);
  }

  d.value = Infinity;
  Assert.equal(d.value, Infinity);
  d.value = -Infinity;
  Assert.equal(d.value, -Infinity);
  d.value = NaN;
  Assert.ok(isNaN(d.value));
  d.value = 0;
  Assert.equal(d.value, 0);
  d.value = -0;
  Assert.equal(1 / d.value, 1 / -0);

  // don't convert anything else
  let vals = [
    true,
    false,
    null,
    undefined,
    "",
    "0",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      d.value = vals[i];
    }, TypeError);
  }

  // Check that values roundtrip through toSource() correctly.
  function test_roundtrip(tFn, val) {
    let f1 = tFn(val);
    // eslint-disable-next-line no-eval
    var f2 = eval(f1.toSource());
    Assert.equal(f1.value, f2.value);
  }
  vals = [
    Infinity,
    -Infinity,
    -0,
    0,
    1,
    -1,
    1 / 3,
    -1 / 3,
    1 / 4,
    -1 / 4,
    1e-14,
    -1e-14,
    0xfffffffffffff000,
    -0xfffffffffffff000,
  ];
  for (let i = 0; i < vals.length; i++) {
    test_roundtrip(t, vals[i]);
  }
  Assert.equal(t(NaN).toSource(), t.toSource() + "(NaN)");

  Assert.strictEqual(d.address().constructor, t.ptr);
  Assert.equal(d.address().contents, d.value);
  Assert.equal(d.toSource(), "ctypes." + name + "(" + d.value + ")");
  Assert.equal(d.toSource(), d.toString());

  // Test the function call ABI for calls involving the type,
  // and check the alignment of the type against what C says.
  let operand = [];
  if (size == 4) {
    operand[0] = 503859.75;
    operand[1] = 1012385.25;
    operand[2] = 1516245;
  } else {
    operand[0] = 501823873859.75;
    operand[1] = 171290577385.25;
    operand[2] = 673114451245;
  }
  function toprimitive(a) {
    return a;
  }
  run_basic_abi_tests(
    library,
    t,
    name,
    toprimitive,
    109.25,
    [0, operand[0], operand[1]],
    [
      [0, 0, 0],
      [0, 1, 1],
      [1, 0, 1],
      [1, 1, 2],
      [operand[0], operand[1], operand[2]],
    ],
    [
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18],
    ]
  );
}

function run_wrapped_integer_tests(
  library,
  t,
  name,
  size,
  signed,
  w,
  wname,
  limits
) {
  run_basic_class_tests(t);

  Assert.equal(t.name, name);
  Assert.equal(t.size, size);

  Assert.equal(t.toString(), "type " + name);
  Assert.equal(t.toSource(), "ctypes." + name);
  Assert.strictEqual(t.ptr, ctypes.PointerType(t));
  Assert.equal(t.array().name, name + "[]");
  Assert.equal(t.array(5).name, name + "[5]");

  let d = t();
  Assert.ok(d.value instanceof w);
  Assert.equal(d.value, 0);
  d.value = 5;
  Assert.equal(d.value, 5);
  d = t(10);
  Assert.equal(d.value, 10);
  if (signed) {
    d.value = -10;
    Assert.equal(d.value, -10);
  }
  d = new t(20);
  Assert.equal(d.value, 20);

  d.value = ctypes.Int64(5);
  Assert.equal(d.value, 5);
  if (signed) {
    d.value = ctypes.Int64(-5);
    Assert.equal(d.value, -5);
  }
  d.value = ctypes.UInt64(5);
  Assert.equal(d.value, 5);

  d.value = w(limits[0]);
  Assert.equal(d.value, limits[0]);
  d.value = w(limits[1]);
  Assert.equal(d.value, limits[1]);
  d.value = 0;
  Assert.equal(d.value, 0);
  d.value = -0;
  Assert.equal(1 / d.value, 1 / 0);
  d.value = false;
  Assert.equal(d.value, 0);
  d.value = true;
  Assert.equal(d.value, 1);

  // don't convert anything else
  let vals = [
    limits[2],
    limits[3],
    Infinity,
    -Infinity,
    NaN,
    0.1,
    null,
    undefined,
    "",
    "0",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      d.value = vals[i];
    }, TypeError);
  }

  Assert.strictEqual(d.address().constructor, t.ptr);
  Assert.equal(d.address().contents.toString(), d.value.toString());
  Assert.equal(
    d.toSource(),
    "ctypes." + name + "(" + wname + '("' + d.value + '"))'
  );
  Assert.equal(d.toSource(), d.toString());

  // Test the function call ABI for calls involving the type,
  // and check the alignment of the type against what C says.
  function toprimitive(a) {
    return a.toString();
  }
  run_basic_abi_tests(
    library,
    t,
    name,
    toprimitive,
    109,
    [0, w(limits[0]), w(limits[1])],
    [
      [0, 0, 0],
      [0, 1, 1],
      [1, 0, 1],
      [1, 1, 2],
      signed
        ? [w(limits[0]), -1, w(limits[1])]
        : [w(limits[1]), 1, w(limits[0])],
    ],
    [
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18],
    ]
  );
}

function run_char_tests(library, t, name, size, signed, limits) {
  run_basic_class_tests(t);

  Assert.equal(t.name, name);
  Assert.equal(t.size, size);

  Assert.equal(t.toString(), "type " + name);
  Assert.equal(t.toSource(), "ctypes." + name);
  Assert.strictEqual(t.ptr, ctypes.PointerType(t));
  Assert.equal(t.array().name, name + "[]");
  Assert.equal(t.array(5).name, name + "[5]");

  let d = t();
  Assert.equal(d.value, 0);
  d.value = 5;
  Assert.equal(d.value, 5);
  d = t(10);
  Assert.equal(d.value, 10);
  if (signed) {
    d.value = -10;
    Assert.equal(d.value, -10);
  } else {
    do_check_throws(function () {
      d.value = -10;
    }, TypeError);
  }
  d = new t(20);
  Assert.equal(d.value, 20);

  function toprimitive(a) {
    return a;
  }

  d.value = ctypes.Int64(5);
  Assert.equal(d.value, 5);
  if (signed) {
    d.value = ctypes.Int64(-10);
    Assert.equal(d.value, -10);
  }
  d.value = ctypes.UInt64(5);
  Assert.equal(d.value, 5);

  d.value = limits[0];
  Assert.equal(d.value, limits[0]);
  d.value = limits[1];
  Assert.equal(d.value, limits[1]);
  d.value = 0;
  Assert.equal(d.value, 0);
  d.value = -0;
  Assert.equal(1 / d.value, 1 / 0);
  d.value = false;
  Assert.equal(d.value, 0);
  d.value = true;
  Assert.equal(d.value, 1);

  do_check_throws(function () {
    d.value = "5";
  }, TypeError);

  // don't convert anything else
  let vals = [
    limits[0] - 1,
    limits[1] + 1,
    Infinity,
    -Infinity,
    NaN,
    0.1,
    null,
    undefined,
    "",
    "aa",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      d.value = vals[i];
    }, TypeError);
  }

  Assert.strictEqual(d.address().constructor, t.ptr);
  Assert.equal(d.address().contents, 1);
  Assert.equal(d.toSource(), "ctypes." + name + "(" + d.value + ")");
  Assert.equal(d.toSource(), d.toString());

  // Test string autoconversion (and lack thereof).
  let literal = "autoconverted";
  let s = t.array()(literal);
  Assert.equal(s.readString(), literal);
  Assert.equal(s.constructor.length, literal.length + 1);
  s = t.array(50)(literal);
  Assert.equal(s.readString(), literal);
  do_check_throws(function () {
    t.array(3)(literal);
  }, TypeError);

  do_check_throws(function () {
    t.ptr(literal);
  }, TypeError);
  let p = t.ptr(s);
  Assert.equal(p.readString(), literal);

  // Test the function call ABI for calls involving the type,
  // and check the alignment of the type against what C says.
  run_basic_abi_tests(
    library,
    t,
    name,
    toprimitive,
    109,
    [0, limits[0], limits[1]],
    [
      [0, 0, 0],
      [0, 1, 1],
      [1, 0, 1],
      [1, 1, 2],
      [limits[0], 1, limits[0] + 1],
      [limits[1], 1, limits[0]],
    ],
    [
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18],
    ]
  );
}

function run_char16_tests(library, t, name, limits) {
  run_basic_class_tests(t);

  Assert.equal(t.name, name);
  Assert.equal(t.size, 2);

  Assert.equal(t.toString(), "type " + name);
  Assert.equal(t.toSource(), "ctypes." + name);
  Assert.strictEqual(t.ptr, ctypes.PointerType(t));
  Assert.equal(t.array().name, name + "[]");
  Assert.equal(t.array(5).name, name + "[5]");

  function toprimitive(a) {
    return a.charCodeAt(0);
  }

  let d = t();
  Assert.equal(d.value.length, 1);
  Assert.equal(toprimitive(d.value), 0);
  d.value = 5;
  Assert.equal(d.value.length, 1);
  Assert.equal(toprimitive(d.value), 5);
  d = t(10);
  Assert.equal(toprimitive(d.value), 10);
  do_check_throws(function () {
    d.value = -10;
  }, TypeError);
  d = new t(20);
  Assert.equal(toprimitive(d.value), 20);

  d.value = ctypes.Int64(5);
  Assert.equal(d.value.charCodeAt(0), 5);
  do_check_throws(function () {
    d.value = ctypes.Int64(-10);
  }, TypeError);
  d.value = ctypes.UInt64(5);
  Assert.equal(d.value.charCodeAt(0), 5);

  d.value = limits[0];
  Assert.equal(toprimitive(d.value), limits[0]);
  d.value = limits[1];
  Assert.equal(toprimitive(d.value), limits[1]);
  d.value = 0;
  Assert.equal(toprimitive(d.value), 0);
  d.value = -0;
  Assert.equal(1 / toprimitive(d.value), 1 / 0);
  d.value = false;
  Assert.equal(toprimitive(d.value), 0);
  d.value = true;
  Assert.equal(toprimitive(d.value), 1);

  d.value = "\0";
  Assert.equal(toprimitive(d.value), 0);
  d.value = "a";
  Assert.equal(d.value, "a");

  // don't convert anything else
  let vals = [
    limits[0] - 1,
    limits[1] + 1,
    Infinity,
    -Infinity,
    NaN,
    0.1,
    null,
    undefined,
    "",
    "aa",
    {},
    [],
    new Number(16),
    {
      toString() {
        return 7;
      },
    },
    {
      valueOf() {
        return 7;
      },
    },
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      d.value = vals[i];
    }, TypeError);
  }

  Assert.strictEqual(d.address().constructor, t.ptr);
  Assert.equal(d.address().contents, "a");
  Assert.equal(d.toSource(), "ctypes." + name + '("' + d.value + '")');
  Assert.equal(d.toSource(), d.toString());

  // Test string autoconversion (and lack thereof).
  let literal = "autoconverted";
  let s = t.array()(literal);
  Assert.equal(s.readString(), literal);
  Assert.equal(s.constructor.length, literal.length + 1);
  s = t.array(50)(literal);
  Assert.equal(s.readString(), literal);
  do_check_throws(function () {
    t.array(3)(literal);
  }, TypeError);

  do_check_throws(function () {
    t.ptr(literal);
  }, TypeError);
  let p = t.ptr(s);
  Assert.equal(p.readString(), literal);

  // Test the function call ABI for calls involving the type,
  // and check the alignment of the type against what C says.
  run_basic_abi_tests(
    library,
    t,
    name,
    toprimitive,
    109,
    [0, limits[0], limits[1]],
    [
      [0, 0, 0],
      [0, 1, 1],
      [1, 0, 1],
      [1, 1, 2],
      [limits[0], 1, limits[0] + 1],
      [limits[1], 1, limits[0]],
    ],
    [
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18],
    ]
  );
}

// Test the class and prototype hierarchy for a given type constructor 'c'.
function run_type_ctor_class_tests(
  c,
  t,
  t2,
  props = [],
  fns = [],
  instanceProps = [],
  instanceFns = [],
  specialProps = []
) {
  Assert.strictEqual(
    Object.getPrototypeOf(c.prototype),
    ctypes.CType.prototype
  );
  Assert.ok(c.prototype instanceof ctypes.CType);
  Assert.strictEqual(c.prototype.constructor, c);

  // Check that 'c.prototype' has the correct properties and functions.
  for (let p of props) {
    Assert.ok(c.prototype.hasOwnProperty(p));
  }
  for (let f of fns) {
    Assert.ok(c.prototype.hasOwnProperty(f));
  }

  // Check that the shared properties and functions on 'c.prototype' throw.
  for (let p of props) {
    do_check_throws(function () {
      c.prototype[p];
    }, TypeError);
  }
  for (let f of fns) {
    do_check_throws(function () {
      c.prototype[f]();
    }, TypeError);
  }

  Assert.strictEqual(Object.getPrototypeOf(t), c.prototype);
  Assert.ok(t instanceof c);

  // The prototype of 't.prototype' is the common ancestor of all types constructed
  // from 'c'; while not available from 'c' directly, it should be identically
  // equal to the prototype of 't2.prototype' where 't2' is a different CType
  // constructed from 'c'.
  Assert.strictEqual(
    Object.getPrototypeOf(t.prototype),
    Object.getPrototypeOf(t2.prototype)
  );
  if (t instanceof ctypes.FunctionType) {
    Assert.strictEqual(
      Object.getPrototypeOf(Object.getPrototypeOf(t.prototype)),
      ctypes.PointerType.prototype.prototype
    );
  } else {
    Assert.strictEqual(
      Object.getPrototypeOf(Object.getPrototypeOf(t.prototype)),
      ctypes.CData.prototype
    );
  }
  Assert.ok(t.prototype instanceof ctypes.CData);
  Assert.strictEqual(t.prototype.constructor, t);

  // Check that the prototype of 't.prototype' has the correct properties and
  // functions.
  for (let p of instanceProps) {
    Assert.ok(Object.getPrototypeOf(t.prototype).hasOwnProperty(p));
  }
  for (let f of instanceFns) {
    Assert.ok(Object.getPrototypeOf(t.prototype).hasOwnProperty(f));
  }

  // Check that the shared properties and functions on the prototype of
  // 't.prototype' (and thus also 't.prototype') throw.
  for (let p of instanceProps) {
    do_check_throws(function () {
      Object.getPrototypeOf(t.prototype)[p];
    }, TypeError);
    do_check_throws(function () {
      t.prototype[p];
    }, TypeError);
  }
  for (let f of instanceFns) {
    do_check_throws(function () {
      Object.getPrototypeOf(t.prototype)[f]();
    }, TypeError);
    do_check_throws(function () {
      t.prototype[f]();
    }, TypeError);
  }

  // Check that 't.prototype' has the correct special properties.
  for (let p of specialProps) {
    Assert.ok(t.prototype.hasOwnProperty(p));
  }

  // Check that the shared special properties on 't.prototype' throw.
  for (let p of specialProps) {
    do_check_throws(function () {
      t.prototype[p];
    }, TypeError);
  }

  // Make sure we can access 'prototype' on a CTypeProto.
  if (t instanceof ctypes.FunctionType) {
    Assert.strictEqual(
      Object.getPrototypeOf(c.prototype.prototype),
      ctypes.PointerType.prototype.prototype
    );
  } else {
    Assert.strictEqual(
      Object.getPrototypeOf(c.prototype.prototype),
      ctypes.CType.prototype.prototype
    );
  }

  // Test that an instance 'd' of 't' is a CData.
  if (Object.getPrototypeOf(t) != ctypes.FunctionType.prototype) {
    let d = t();
    Assert.strictEqual(Object.getPrototypeOf(d), t.prototype);
    Assert.ok(d instanceof t);
    Assert.strictEqual(d.constructor, t);
    // Other objects that are not instances of 't'.
    Assert.equal({} instanceof t, false);
    Assert.equal(Object.getPrototypeOf(t) instanceof t, false);
    Assert.equal(t.prototype instanceof t, false);
  }
}

function run_StructType_tests() {
  run_type_ctor_class_tests(
    ctypes.StructType,
    ctypes.StructType("s", [{ a: ctypes.int32_t }, { b: ctypes.int64_t }]),
    ctypes.StructType("t", [{ c: ctypes.int32_t }, { d: ctypes.int64_t }]),
    ["fields"],
    ["define"],
    [],
    ["addressOfField"],
    ["a", "b"]
  );

  do_check_throws(function () {
    ctypes.StructType();
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", [], 5);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType(null, []);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", null);
  }, TypeError);

  // Check that malformed descriptors are an error.
  do_check_throws(function () {
    ctypes.StructType("a", [{ x: ctypes.int32_t }, { x: ctypes.int8_t }]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", [5]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", [{}]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", [{ 5: ctypes.int32_t }]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", [{ 5: ctypes.int32_t }]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", [{ x: 5 }]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("a", [{ x: ctypes.int32_t() }]);
  }, TypeError);

  // Check that opaque structs work.
  let opaque_t = ctypes.StructType("a");
  Assert.equal(opaque_t.name, "a");
  Assert.equal(opaque_t.toString(), "type a");
  Assert.equal(opaque_t.toSource(), 'ctypes.StructType("a")');
  Assert.strictEqual(opaque_t.prototype, undefined);
  Assert.strictEqual(opaque_t.fields, undefined);
  Assert.strictEqual(opaque_t.size, undefined);
  do_check_throws(function () {
    opaque_t();
  }, Error);
  let opaqueptr_t = opaque_t.ptr;
  Assert.strictEqual(opaqueptr_t.targetType, opaque_t);
  Assert.equal(opaqueptr_t.name, "a*");
  Assert.equal(opaqueptr_t.toString(), "type a*");
  Assert.equal(opaqueptr_t.toSource(), 'ctypes.StructType("a").ptr');

  // Check that type checking works with opaque structs.
  let opaqueptr = opaqueptr_t();
  opaqueptr.value = opaqueptr_t(1);
  Assert.equal(ptrValue(opaqueptr), 1);
  do_check_throws(function () {
    opaqueptr.value = ctypes.StructType("a").ptr();
  }, TypeError);

  // Check that 'define' works.
  do_check_throws(function () {
    opaque_t.define();
  }, TypeError);
  do_check_throws(function () {
    opaque_t.define([], 0);
  }, TypeError);
  do_check_throws(function () {
    opaque_t.define([{}]);
  }, TypeError);
  do_check_throws(function () {
    opaque_t.define([{ a: 0 }]);
  }, TypeError);
  do_check_throws(function () {
    opaque_t.define([{ a: ctypes.int32_t, b: ctypes.int64_t }]);
  }, TypeError);
  do_check_throws(function () {
    opaque_t.define([{ a: ctypes.int32_t }, { b: 0 }]);
  }, TypeError);
  Assert.ok(!opaque_t.hasOwnProperty("prototype"));

  // Check that circular references work with opaque structs...
  // but not crazy ones.
  do_check_throws(function () {
    opaque_t.define([{ b: opaque_t }]);
  }, TypeError);
  let circular_t = ctypes.StructType("circular", [{ a: opaqueptr_t }]);
  opaque_t.define([{ b: circular_t }]);
  let opaque = opaque_t();
  let circular = circular_t(opaque.address());
  opaque.b = circular;
  Assert.equal(circular.a.toSource(), opaque.address().toSource());
  Assert.equal(opaque.b.toSource(), circular.toSource());

  // Check that attempting to redefine a struct fails and if attempted, the
  // original definition is preserved.
  do_check_throws(function () {
    opaque_t.define([{ c: ctypes.int32_t.array(8) }]);
  }, Error);
  Assert.equal(opaque_t.size, circular_t.size);
  Assert.ok(opaque_t.prototype.hasOwnProperty("b"));
  Assert.ok(!opaque_t.prototype.hasOwnProperty("c"));

  // StructType size, alignment, and offset calculations have already been
  // checked for each basic type. We do not need to check them again.
  let name = "g_t";
  let g_t = ctypes.StructType(name, [
    { a: ctypes.int32_t },
    { b: ctypes.double },
  ]);
  Assert.equal(g_t.name, name);

  Assert.equal(g_t.toString(), "type " + name);
  Assert.equal(
    g_t.toSource(),
    'ctypes.StructType("g_t", [{ "a": ctypes.int32_t }, { "b": ctypes.double }])'
  );
  Assert.strictEqual(g_t.ptr, ctypes.PointerType(g_t));
  Assert.equal(g_t.array().name, name + "[]");
  Assert.equal(g_t.array(5).name, name + "[5]");

  let s_t = new ctypes.StructType("s_t", [
    { a: ctypes.int32_t },
    { b: g_t },
    { c: ctypes.int8_t },
  ]);

  let fields = [
    { a: ctypes.int32_t },
    { b: ctypes.int8_t },
    { c: g_t },
    { d: ctypes.int8_t },
  ];
  let t_t = new ctypes.StructType("t_t", fields);
  Assert.equal(t_t.fields.length, 4);
  Assert.strictEqual(t_t.fields[0].a, ctypes.int32_t);
  Assert.strictEqual(t_t.fields[1].b, ctypes.int8_t);
  Assert.strictEqual(t_t.fields[2].c, g_t);
  Assert.strictEqual(t_t.fields[3].d, ctypes.int8_t);
  /* disabled temporarily per bug 598225.
  do_check_throws(function() { t_t.fields.z = 0; }, Error);
  do_check_throws(function() { t_t.fields[4] = 0; }, Error);
  do_check_throws(function() { t_t.fields[4].a = 0; }, Error);
  do_check_throws(function() { t_t.fields[4].e = 0; }, Error);
*/

  // Check that struct size bounds work, and that large, but not illegal, sizes
  // are OK.
  if (ctypes.size_t.size == 4) {
    // Test 1: overflow struct size + field padding + field size.
    let large_t = ctypes.StructType("large_t", [
      { a: ctypes.int8_t.array(0xffffffff) },
    ]);
    Assert.equal(large_t.size, 0xffffffff);
    do_check_throws(function () {
      ctypes.StructType("large_t", [{ a: large_t }, { b: ctypes.int8_t }]);
    }, RangeError);

    // Test 2: overflow struct size + struct tail padding.
    // To do this, we use a struct with maximum size and alignment 2.
    large_t = ctypes.StructType("large_t", [
      { a: ctypes.int16_t.array(0xfffffffe / 2) },
    ]);
    Assert.equal(large_t.size, 0xfffffffe);
    do_check_throws(function () {
      ctypes.StructType("large_t", [{ a: large_t }, { b: ctypes.int8_t }]);
    }, RangeError);
  } else {
    // Test 1: overflow struct size when converting from size_t to jsdouble.
    let large_t = ctypes.StructType("large_t", [
      { a: ctypes.int8_t.array(0xfffffffffffff800) },
    ]);
    Assert.equal(large_t.size, 0xfffffffffffff800);
    do_check_throws(function () {
      ctypes.StructType("large_t", [{ a: large_t }, { b: ctypes.int8_t }]);
    }, RangeError);
    let small_t = ctypes.int8_t.array(0x400);
    do_check_throws(function () {
      ctypes.StructType("large_t", [{ a: large_t }, { b: small_t }]);
    }, RangeError);

    large_t = ctypes.StructType("large_t", [
      { a: ctypes.int8_t.array(0x1fffffffffffff) },
    ]);
    Assert.equal(large_t.size, 0x1fffffffffffff);
    do_check_throws(function () {
      ctypes.StructType("large_t", [
        { a: large_t.array(2) },
        { b: ctypes.int8_t },
      ]);
    }, RangeError);

    // Test 2: overflow struct size + field padding + field size.
    large_t = ctypes.int8_t.array(0xfffffffffffff800);
    small_t = ctypes.int8_t.array(0x800);
    do_check_throws(function () {
      ctypes.StructType("large_t", [{ a: large_t }, { b: small_t }]);
    }, RangeError);

    // Test 3: overflow struct size + struct tail padding.
    // To do this, we use a struct with maximum size and alignment 2.
    large_t = ctypes.StructType("large_t", [
      { a: ctypes.int16_t.array(0xfffffffffffff000 / 2) },
    ]);
    Assert.equal(large_t.size, 0xfffffffffffff000);
    small_t = ctypes.int8_t.array(0xfff);
    do_check_throws(function () {
      ctypes.StructType("large_t", [{ a: large_t }, { b: small_t }]);
    }, RangeError);
  }

  let g = g_t();
  Assert.equal(g.a, 0);
  Assert.equal(g.b, 0);
  g = new g_t(1, 2);
  Assert.equal(g.a, 1);
  Assert.equal(g.b, 2);
  do_check_throws(function () {
    g_t(1);
  }, TypeError);
  do_check_throws(function () {
    g_t(1, 2, 3);
  }, TypeError);

  for (let field in g) {
    Assert.ok(field == "a" || field == "b");
  }

  let g_a = g.address();
  Assert.strictEqual(g_a.constructor, g_t.ptr);
  Assert.equal(g_a.contents.a, g.a);

  let s = new s_t(3, g, 10);
  Assert.equal(s.a, 3);
  s.a = 4;
  Assert.equal(s.a, 4);
  Assert.equal(s.b.a, 1);
  Assert.equal(s.b.b, 2);
  Assert.equal(s.c, 10);
  let g2 = s.b;
  Assert.equal(g2.a, 1);
  g2.a = 7;
  Assert.equal(g2.a, 7);
  Assert.equal(s.b.a, 7);

  g_a = s.addressOfField("b");
  Assert.strictEqual(g_a.constructor, g_t.ptr);
  Assert.equal(g_a.contents.a, s.b.a);
  do_check_throws(function () {
    s.addressOfField();
  }, TypeError);
  do_check_throws(function () {
    s.addressOfField("d");
  }, TypeError);
  do_check_throws(function () {
    s.addressOfField("a", 2);
  }, TypeError);

  Assert.equal(s.toSource(), 's_t(4, {"a": 7, "b": 2}, 10)');
  Assert.equal(s.toSource(), s.toString());
  // eslint-disable-next-line no-eval
  var s2 = eval(s.toSource());
  Assert.strictEqual(s2.constructor, s_t);
  Assert.equal(s.b.b, s2.b.b);

  // Test that structs can be set from an object using 'value'.
  do_check_throws(function () {
    s.value;
  }, TypeError);
  let s_init = { a: 2, b: { a: 9, b: 5 }, c: 13 };
  s.value = s_init;
  Assert.equal(s.b.a, 9);
  Assert.equal(s.c, 13);
  do_check_throws(function () {
    s.value = 5;
  }, TypeError);
  do_check_throws(function () {
    s.value = ctypes.int32_t();
  }, TypeError);
  do_check_throws(function () {
    s.value = {};
  }, TypeError);
  do_check_throws(function () {
    s.value = { a: 2 };
  }, TypeError);
  do_check_throws(function () {
    s.value = { a: 2, b: 5, c: 10 };
  }, TypeError);
  do_check_throws(function () {
    s.value = { 5: 2, b: { a: 9, b: 5 }, c: 13 };
  }, TypeError);
  do_check_throws(function () {
    s.value = { a: 2, b: { a: 9, b: 5 }, c: 13, d: 17 };
  }, TypeError);
  do_check_throws(function () {
    s.value = { a: 2, b: { a: 9, b: 5, e: 9 }, c: 13 };
  }, TypeError);

  // Test that structs can be constructed similarly through ExplicitConvert,
  // and that the single-field case is disambiguated correctly.
  s = s_t(s_init);
  Assert.equal(s.b.a, 9);
  Assert.equal(s.c, 13);
  let v_t = ctypes.StructType("v_t", [{ x: ctypes.int32_t }]);
  let v = v_t({ x: 5 });
  Assert.equal(v.x, 5);
  v = v_t(8);
  Assert.equal(v.x, 8);
  let w_t = ctypes.StructType("w_t", [{ y: v_t }]);
  do_check_throws(function () {
    w_t(9);
  }, TypeError);
  let w = w_t({ x: 3 });
  Assert.equal(w.y.x, 3);
  w = w_t({ y: { x: 19 } });
  Assert.equal(w.y.x, 19);
  let u_t = ctypes.StructType("u_t", [
    { z: ctypes.ArrayType(ctypes.int32_t, 3) },
  ]);
  let u = u_t([1, 2, 3]);
  Assert.equal(u.z[1], 2);
  u = u_t({ z: [4, 5, 6] });
  Assert.equal(u.z[1], 5);

  // Check that the empty struct has size 1.
  let z_t = ctypes.StructType("z_t", []);
  Assert.equal(z_t.size, 1);
  Assert.equal(z_t.fields.length, 0);

  // Check that structs containing arrays of undefined or zero length
  // are illegal, but arrays of defined length work.
  do_check_throws(function () {
    ctypes.StructType("z_t", [{ a: ctypes.int32_t.array() }]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.StructType("z_t", [{ a: ctypes.int32_t.array(0) }]);
  }, TypeError);
  z_t = ctypes.StructType("z_t", [{ a: ctypes.int32_t.array(6) }]);
  Assert.equal(z_t.size, ctypes.int32_t.size * 6);
  let z = z_t([1, 2, 3, 4, 5, 6]);
  Assert.equal(z.a[3], 4);
}

function ptrValue(p) {
  return ctypes.cast(p, ctypes.uintptr_t).value.toString();
}

function run_PointerType_tests() {
  run_type_ctor_class_tests(
    ctypes.PointerType,
    ctypes.PointerType(ctypes.int32_t),
    ctypes.PointerType(ctypes.int64_t),
    ["targetType"],
    [],
    ["contents"],
    ["isNull", "increment", "decrement"],
    []
  );

  do_check_throws(function () {
    ctypes.PointerType();
  }, TypeError);
  do_check_throws(function () {
    ctypes.PointerType(ctypes.int32_t, 5);
  }, TypeError);
  do_check_throws(function () {
    ctypes.PointerType(null);
  }, TypeError);
  do_check_throws(function () {
    ctypes.PointerType(ctypes.int32_t());
  }, TypeError);
  do_check_throws(function () {
    ctypes.PointerType("void");
  }, TypeError);

  let name = "g_t";
  let g_t = ctypes.StructType(name, [
    { a: ctypes.int32_t },
    { b: ctypes.double },
  ]);
  let g = g_t(1, 2);

  let p_t = ctypes.PointerType(g_t);
  Assert.equal(p_t.name, name + "*");
  Assert.equal(p_t.size, ctypes.uintptr_t.size);
  Assert.strictEqual(p_t.targetType, g_t);
  Assert.strictEqual(p_t, g_t.ptr);

  Assert.equal(p_t.toString(), "type " + name + "*");
  Assert.equal(
    p_t.toSource(),
    'ctypes.StructType("g_t", [{ "a": ctypes.int32_t }, { "b": ctypes.double }]).ptr'
  );
  Assert.strictEqual(p_t.ptr, ctypes.PointerType(p_t));
  Assert.equal(p_t.array().name, name + "*[]");
  Assert.equal(p_t.array(5).name, name + "*[5]");

  // Test ExplicitConvert.
  let p = p_t();
  do_check_throws(function () {
    p.value;
  }, TypeError);
  Assert.equal(ptrValue(p), 0);
  do_check_throws(function () {
    p.contents;
  }, TypeError);
  do_check_throws(function () {
    p.contents = g;
  }, TypeError);
  p = p_t(5);
  Assert.equal(ptrValue(p), 5);
  p = p_t(ctypes.UInt64(10));
  Assert.equal(ptrValue(p), 10);

  // Test ImplicitConvert.
  p.value = null;
  Assert.equal(ptrValue(p), 0);
  do_check_throws(function () {
    p.value = 5;
  }, TypeError);

  // Test opaque pointers.
  let f_t = ctypes.StructType("FILE").ptr;
  Assert.equal(f_t.name, "FILE*");
  Assert.equal(f_t.toSource(), 'ctypes.StructType("FILE").ptr');
  let f = new f_t();
  do_check_throws(function () {
    f.contents;
  }, TypeError);
  do_check_throws(function () {
    f.contents = 0;
  }, TypeError);
  f = f_t(5);
  do_check_throws(function () {
    f.contents = 0;
  }, TypeError);
  Assert.equal(f.toSource(), 'FILE.ptr(ctypes.UInt64("0x5"))');

  do_check_throws(function () {
    f_t(p);
  }, TypeError);
  do_check_throws(function () {
    f.value = p;
  }, TypeError);
  do_check_throws(function () {
    p.value = f;
  }, TypeError);

  // Test void pointers.
  let v_t = ctypes.PointerType(ctypes.void_t);
  Assert.strictEqual(v_t, ctypes.voidptr_t);
  let v = v_t(p);
  Assert.equal(ptrValue(v), ptrValue(p));

  // Test 'contents'.
  let int32_t = ctypes.int32_t(9);
  p = int32_t.address();
  Assert.equal(p.contents, int32_t.value);
  p.contents = ctypes.int32_t(12);
  Assert.equal(int32_t.value, 12);

  // Test 'isNull'.
  let n = f_t(0);
  Assert.strictEqual(n.isNull(), true);
  n = p.address();
  Assert.strictEqual(n.isNull(), false);

  // Test 'increment'/'decrement'.
  g_t = ctypes.StructType("g_t", [{ a: ctypes.int32_t }, { b: ctypes.double }]);
  let a_t = ctypes.ArrayType(g_t, 2);
  let a = new a_t();
  a[0] = g_t(1, 2);
  a[1] = g_t(2, 4);
  let a_p = a.addressOfElement(0).increment();
  Assert.equal(a_p.contents.a, 2);
  Assert.equal(a_p.contents.b, 4);
  a_p = a_p.decrement();
  Assert.equal(a_p.contents.a, 1);
  Assert.equal(a_p.contents.b, 2);

  // Check that pointers to arrays of undefined or zero length are legal,
  // but that the former cannot be dereferenced.
  let z_t = ctypes.int32_t.array().ptr;
  Assert.equal(ptrValue(z_t()), 0);
  do_check_throws(function () {
    z_t().contents;
  }, TypeError);
  z_t = ctypes.int32_t.array(0).ptr;
  Assert.equal(ptrValue(z_t()), 0);
  let z = ctypes.int32_t.array(0)().address();
  Assert.equal(z.contents.length, 0);

  // TODO: Somehow, somewhere we should check that:
  //
  //  (a) ArrayBuffer and TypedArray can be passed by pointer to a C function
  //  (b) SharedArrayBuffer and TypedArray on SAB can NOT be passed in that
  //      way (at least not at the moment).

  // Set up conversion tests on AB, SAB, TA
  let c_arraybuffer = new ArrayBuffer(256);
  let typed_array_samples = [
    [new Int8Array(c_arraybuffer), ctypes.int8_t],
    [new Uint8Array(c_arraybuffer), ctypes.uint8_t],
    [new Uint8ClampedArray(c_arraybuffer), ctypes.uint8_t],
    [new Int16Array(c_arraybuffer), ctypes.int16_t],
    [new Uint16Array(c_arraybuffer), ctypes.uint16_t],
    [new Int32Array(c_arraybuffer), ctypes.int32_t],
    [new Uint32Array(c_arraybuffer), ctypes.uint32_t],
    [new Float32Array(c_arraybuffer), ctypes.float32_t],
    [new Float64Array(c_arraybuffer), ctypes.float64_t],
  ];

  if (typeof SharedArrayBuffer !== "undefined") {
    let c_shared_arraybuffer = new SharedArrayBuffer(256);
    typed_array_samples.push(
      [new Int8Array(c_shared_arraybuffer), ctypes.int8_t],
      [new Uint8Array(c_shared_arraybuffer), ctypes.uint8_t],
      [new Uint8ClampedArray(c_shared_arraybuffer), ctypes.uint8_t],
      [new Int16Array(c_shared_arraybuffer), ctypes.int16_t],
      [new Uint16Array(c_shared_arraybuffer), ctypes.uint16_t],
      [new Int32Array(c_shared_arraybuffer), ctypes.int32_t],
      [new Uint32Array(c_shared_arraybuffer), ctypes.uint32_t],
      [new Float32Array(c_shared_arraybuffer), ctypes.float32_t],
      [new Float64Array(c_shared_arraybuffer), ctypes.float64_t]
    );
  }

  // Check that you can convert (Shared)ArrayBuffer or typed array to a C array
  for (let i = 0; i < typed_array_samples.length; ++i) {
    for (let j = 0; j < typed_array_samples.length; ++j) {
      let view = typed_array_samples[i][0];
      let item_type = typed_array_samples[j][1];
      let number_of_items = c_arraybuffer.byteLength / item_type.size;
      let array_type = item_type.array(number_of_items);

      // Arrays are interconvertible iff they share the same ctypes element type
      // (e.g. Uint8Array and Uint8ClampedArray both map to uint8_t).
      // ctypes type objects are singletons so identity comparison is correct.
      if (typed_array_samples[i][1] !== typed_array_samples[j][1]) {
        info(
          "Checking that typed array " +
            view.constructor.name +
            " can NOT be converted to " +
            item_type +
            " array"
        );
        do_check_throws(function () {
          array_type(view);
        }, TypeError);
      } else {
        info(
          "Checking that typed array " +
            view.constructor.name +
            " can be converted to " +
            item_type +
            " array"
        );

        // Convert ArrayBuffer to array of the right size and check contents
        let c_array = array_type(c_arraybuffer);
        for (let k = 0; k < number_of_items; ++k) {
          Assert.equal(c_array[k], view[k]);
        }

        // Convert typed array to array of the right size and check contents
        c_array = array_type(view);
        for (let k = 0; k < number_of_items; ++k) {
          Assert.equal(c_array[k], view[k]);
        }

        // Convert typed array to array of wrong size, ensure that it fails
        let array_type_too_large = item_type.array(number_of_items + 1);
        let array_type_too_small = item_type.array(number_of_items - 1);

        do_check_throws(function () {
          array_type_too_large(c_arraybuffer);
        }, TypeError);
        do_check_throws(function () {
          array_type_too_small(c_arraybuffer);
        }, TypeError);
        do_check_throws(function () {
          array_type_too_large(view);
        }, TypeError);
        do_check_throws(function () {
          array_type_too_small(view);
        }, TypeError);

        // Convert subarray of typed array to array of right size and check contents
        c_array = array_type_too_small(view.subarray(1));
        for (let k = 1; k < number_of_items; ++k) {
          Assert.equal(c_array[k - 1], view[k]);
        }
      }
    }
  }

  // Typed arrays with no ctypes equivalent are rejected for any ctypes array type.
  for (const [name, ctor] of [
    [
      "BigInt64Array",
      typeof BigInt64Array !== "undefined" ? BigInt64Array : null,
    ],
    [
      "BigUint64Array",
      typeof BigUint64Array !== "undefined" ? BigUint64Array : null,
    ],
    ["Float16Array", typeof Float16Array !== "undefined" ? Float16Array : null],
  ]) {
    if (!ctor) {
      continue;
    }
    let view = new ctor(c_arraybuffer);
    for (let item_type of [ctypes.uint8_t, ctypes.int32_t, ctypes.float64_t]) {
      info(
        "Checking that typed array " +
          name +
          " can NOT be converted to " +
          item_type +
          " array"
      );
      let n = c_arraybuffer.byteLength / item_type.size;
      do_check_throws(function () {
        item_type.array(n)(view);
      }, TypeError);
    }
  }

  // Check that you can't use a (Shared)ArrayBuffer or a typed array as a pointer
  for (let i = 0; i < typed_array_samples.length; ++i) {
    for (let j = 0; j < typed_array_samples.length; ++j) {
      let view = typed_array_samples[i][0];
      let item_type = typed_array_samples[j][1];

      info(
        "Checking that typed array " +
          view.constructor.name +
          " can NOT be converted to " +
          item_type +
          " pointer/array"
      );
      do_check_throws(function () {
        item_type.ptr(c_arraybuffer);
      }, TypeError);
      do_check_throws(function () {
        item_type.ptr(view);
      }, TypeError);
      do_check_throws(function () {
        ctypes.voidptr_t(c_arraybuffer);
      }, TypeError);
      do_check_throws(function () {
        ctypes.voidptr_t(view);
      }, TypeError);
    }
  }
}

function run_FunctionType_tests() {
  run_type_ctor_class_tests(
    ctypes.FunctionType,
    ctypes.FunctionType(ctypes.default_abi, ctypes.void_t),
    ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, [ctypes.int32_t]),
    ["abi", "returnType", "argTypes", "isVariadic"],
    undefined,
    undefined,
    undefined,
    undefined
  );

  do_check_throws(function () {
    ctypes.FunctionType();
  }, TypeError);
  do_check_throws(function () {
    ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, [ctypes.void_t]);
  }, TypeError);
  do_check_throws(function () {
    ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, [ctypes.void_t], 5);
  }, TypeError);
  do_check_throws(function () {
    ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, ctypes.void_t);
  }, TypeError);
  do_check_throws(function () {
    ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, null);
  }, TypeError);
  do_check_throws(function () {
    ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t());
  }, TypeError);
  do_check_throws(function () {
    ctypes.FunctionType(ctypes.void_t, ctypes.void_t);
  }, Error);

  let g_t = ctypes.StructType("g_t", [
    { a: ctypes.int32_t },
    { b: ctypes.double },
  ]);

  let f_t = ctypes.FunctionType(ctypes.default_abi, g_t);
  let name = "g_t()";
  Assert.equal(f_t.name, name);
  Assert.equal(f_t.size, undefined);
  Assert.strictEqual(f_t.abi, ctypes.default_abi);
  Assert.strictEqual(f_t.returnType, g_t);
  Assert.ok(!f_t.argTypes.length);

  Assert.equal(f_t.toString(), "type " + name);
  Assert.equal(f_t.toSource(), "ctypes.FunctionType(ctypes.default_abi, g_t)");

  let fp_t = f_t.ptr;
  name = "g_t(*)()";
  Assert.equal(fp_t.name, name);
  Assert.equal(fp_t.size, ctypes.uintptr_t.size);

  Assert.equal(fp_t.toString(), "type " + name);
  Assert.equal(
    fp_t.toSource(),
    "ctypes.FunctionType(ctypes.default_abi, g_t).ptr"
  );

  // Check that constructing a FunctionType CData directly throws.
  do_check_throws(function () {
    f_t();
  }, TypeError);

  // Test ExplicitConvert.
  let f = fp_t();
  do_check_throws(function () {
    f.value;
  }, TypeError);
  Assert.equal(ptrValue(f), 0);
  f = fp_t(5);
  Assert.equal(ptrValue(f), 5);
  f = fp_t(ctypes.UInt64(10));
  Assert.equal(ptrValue(f), 10);

  // Test ImplicitConvert.
  f.value = null;
  Assert.equal(ptrValue(f), 0);
  do_check_throws(function () {
    f.value = 5;
  }, TypeError);
  Assert.equal(
    f.toSource(),
    'ctypes.FunctionType(ctypes.default_abi, g_t).ptr(ctypes.UInt64("0x0"))'
  );

  // Test ImplicitConvert from a function pointer of different type.
  let f2_t = ctypes.FunctionType(ctypes.default_abi, g_t, [ctypes.int32_t]);
  let f2 = f2_t.ptr();
  do_check_throws(function () {
    f.value = f2;
  }, TypeError);
  do_check_throws(function () {
    f2.value = f;
  }, TypeError);

  // Test that converting to a voidptr_t works.
  let v = ctypes.voidptr_t(f2);
  Assert.equal(v.toSource(), 'ctypes.voidptr_t(ctypes.UInt64("0x0"))');

  // Test some more complex names.
  Assert.equal(fp_t.array().name, "g_t(*[])()");
  Assert.equal(fp_t.array().ptr.name, "g_t(*(*)[])()");

  let f3_t = ctypes
    .FunctionType(ctypes.default_abi, ctypes.char.ptr.array().ptr)
    .ptr.ptr.array(8)
    .array();
  Assert.equal(f3_t.name, "char*(*(**[][8])())[]");

  if ("winLastError" in ctypes) {
    f3_t = ctypes
      .FunctionType(ctypes.stdcall_abi, ctypes.char.ptr.array().ptr)
      .ptr.ptr.array(8)
      .array();
    Assert.equal(f3_t.name, "char*(*(__stdcall**[][8])())[]");
    f3_t = ctypes
      .FunctionType(ctypes.winapi_abi, ctypes.char.ptr.array().ptr)
      .ptr.ptr.array(8)
      .array();
    Assert.equal(f3_t.name, "char*(*(WINAPI**[][8])())[]");
  }

  let f4_t = ctypes.FunctionType(
    ctypes.default_abi,
    ctypes.char.ptr.array().ptr,
    [ctypes.int32_t, fp_t]
  );
  Assert.equal(f4_t.argTypes.length, 2);
  Assert.strictEqual(f4_t.argTypes[0], ctypes.int32_t);
  Assert.strictEqual(f4_t.argTypes[1], fp_t);
  /* disabled temporarily per bug 598225.
  do_check_throws(function() { f4_t.argTypes.z = 0; }, Error);
  do_check_throws(function() { f4_t.argTypes[0] = 0; }, Error);
*/

  let t4_t = f4_t.ptr.ptr.array(8).array();
  Assert.equal(t4_t.name, "char*(*(**[][8])(int32_t, g_t(*)()))[]");

  // Not available in a Worker
  // eslint-disable-next-line mozilla/use-cc-etc
  if ("@mozilla.org/systemprincipal;1" in Components.classes) {
    var sp = Cc["@mozilla.org/systemprincipal;1"].createInstance(
      Ci.nsIPrincipal
    );
    var s = new Cu.Sandbox(sp);
    s.ctypes = ctypes;
    s.equal = equal;
    s.ok = ok;
    Cu.evalInSandbox(
      "var f5_t = ctypes.FunctionType(ctypes.default_abi, ctypes.int, [ctypes.int]);",
      s
    );
    Cu.evalInSandbox(
      "equal(f5_t.toSource(), 'ctypes.FunctionType(ctypes.default_abi, ctypes.int, [ctypes.int])');",
      s
    );
    Cu.evalInSandbox("equal(f5_t.name, 'int(int)');", s);
    Cu.evalInSandbox("function f5(aArg) { return 5; };", s);
    Cu.evalInSandbox("var f = f5_t.ptr(f5);", s);
    Cu.evalInSandbox("ok(f(6) == 5);", s);
  }
}

function run_ArrayType_tests() {
  run_type_ctor_class_tests(
    ctypes.ArrayType,
    ctypes.ArrayType(ctypes.int32_t, 10),
    ctypes.ArrayType(ctypes.int64_t),
    ["elementType", "length"],
    [],
    ["length"],
    ["addressOfElement"]
  );

  do_check_throws(function () {
    ctypes.ArrayType();
  }, TypeError);
  do_check_throws(function () {
    ctypes.ArrayType(null);
  }, TypeError);
  do_check_throws(function () {
    ctypes.ArrayType(ctypes.int32_t, 1, 5);
  }, TypeError);
  do_check_throws(function () {
    ctypes.ArrayType(ctypes.int32_t, -1);
  }, TypeError);

  let name = "g_t";
  let g_t = ctypes.StructType(name, [
    { a: ctypes.int32_t },
    { b: ctypes.double },
  ]);
  let g = g_t(1, 2);

  let a_t = ctypes.ArrayType(g_t, 10);
  Assert.equal(a_t.name, name + "[10]");
  Assert.equal(a_t.length, 10);
  Assert.equal(a_t.size, g_t.size * 10);
  Assert.strictEqual(a_t.elementType, g_t);

  Assert.equal(a_t.toString(), "type " + name + "[10]");
  Assert.equal(
    a_t.toSource(),
    'ctypes.StructType("g_t", [{ "a": ctypes.int32_t }, { "b": ctypes.double }]).array(10)'
  );
  Assert.equal(a_t.array().name, name + "[][10]");
  Assert.equal(a_t.array(5).name, name + "[5][10]");
  do_check_throws(function () {
    ctypes.int32_t.array().array();
  }, Error);

  let a = new a_t();
  Assert.equal(a[0].a, 0);
  Assert.equal(a[0].b, 0);
  a[0] = g;
  Assert.equal(a[0].a, 1);
  Assert.equal(a[0].b, 2);
  do_check_throws(function () {
    a[-1];
  }, TypeError);
  Assert.equal(a[9].a, 0);
  do_check_throws(function () {
    a[10];
  }, RangeError);

  Assert.equal(a[ctypes.Int64(0)].a, 1);
  Assert.equal(a[ctypes.UInt64(0)].b, 2);

  let a_p = a.addressOfElement(0);
  Assert.strictEqual(a_p.constructor.targetType, g_t);
  Assert.strictEqual(a_p.constructor, g_t.ptr);
  Assert.equal(a_p.contents.a, a[0].a);
  Assert.equal(a_p.contents.b, a[0].b);
  a_p.contents.a = 5;
  Assert.equal(a[0].a, 5);

  let a2_t = ctypes.ArrayType(g_t);
  Assert.equal(a2_t.name, "g_t[]");
  Assert.equal(a2_t.length, undefined);
  Assert.equal(a2_t.size, undefined);
  let a2 = new a2_t(5);
  Assert.equal(a2.constructor.length, 5);
  Assert.equal(a2.length, 5);
  Assert.equal(a2.constructor.size, g_t.size * 5);
  do_check_throws(function () {
    new a2_t();
  }, TypeError);
  do_check_throws(function () {
    ctypes.ArrayType(ctypes.ArrayType(g_t));
  }, Error);
  do_check_throws(function () {
    ctypes.ArrayType(ctypes.ArrayType(g_t), 5);
  }, Error);

  let b_t = ctypes.int8_t.array(ctypes.UInt64(0xffff));
  Assert.equal(b_t.length, 0xffff);
  b_t = ctypes.int8_t.array(ctypes.Int64(0xffff));
  Assert.equal(b_t.length, 0xffff);

  // Check that array size bounds work, and that large, but not illegal, sizes
  // are OK.
  if (ctypes.size_t.size == 4) {
    do_check_throws(function () {
      ctypes.ArrayType(ctypes.int8_t, 0x100000000);
    }, TypeError);
    do_check_throws(function () {
      ctypes.ArrayType(ctypes.int16_t, 0x80000000);
    }, RangeError);

    let large_t = ctypes.int8_t.array(0x80000000);
    do_check_throws(function () {
      large_t.array(2);
    }, RangeError);
  } else {
    do_check_throws(function () {
      ctypes.ArrayType(ctypes.int8_t, ctypes.UInt64("0xffffffffffffffff"));
    }, TypeError);
    do_check_throws(function () {
      ctypes.ArrayType(ctypes.int16_t, ctypes.UInt64("0x8000000000000000"));
    }, RangeError);

    let large_t = ctypes.int8_t.array(0x8000000000000000);
    do_check_throws(function () {
      large_t.array(2);
    }, RangeError);
  }

  // Test that arrays ImplicitConvert to pointers.
  let b = ctypes.int32_t.array(10)();
  let p = ctypes.int32_t.ptr();
  p.value = b;
  Assert.equal(ptrValue(b.addressOfElement(0)), ptrValue(p));
  p = ctypes.voidptr_t();
  p.value = b;
  Assert.equal(ptrValue(b.addressOfElement(0)), ptrValue(p));

  // Test that arrays can be constructed through ImplicitConvert.
  let c_t = ctypes.int32_t.array(6);
  let c = c_t();
  c.value = [1, 2, 3, 4, 5, 6];
  Assert.equal(c.toSource(), "ctypes.int32_t.array(6)([1, 2, 3, 4, 5, 6])");
  Assert.equal(c.toSource(), c.toString());
  // eslint-disable-next-line no-eval
  var c2 = eval(c.toSource());
  Assert.equal(c2.constructor.name, "int32_t[6]");
  Assert.equal(c2.length, 6);
  Assert.equal(c2[3], c[3]);

  c.value = c;
  Assert.equal(c[3], 4);
  do_check_throws(function () {
    c.value;
  }, TypeError);
  do_check_throws(function () {
    c.value = [1, 2, 3, 4, 5];
  }, TypeError);
  do_check_throws(function () {
    c.value = [1, 2, 3, 4, 5, 6, 7];
  }, TypeError);
  do_check_throws(function () {
    c.value = [1, 2, 7.4, 4, 5, 6];
  }, TypeError);
  do_check_throws(function () {
    c.value = [];
  }, TypeError);
}

function run_type_toString_tests() {
  var c = ctypes;

  // Figure out whether we can create functions with ctypes.stdcall_abi and ctypes.winapi_abi.
  var haveStdCallABI;
  try {
    c.FunctionType(c.stdcall_abi, c.int);
    haveStdCallABI = true;
  } catch (x) {
    haveStdCallABI = false;
  }

  var haveWinAPIABI;
  try {
    c.FunctionType(c.winapi_abi, c.int);
    haveWinAPIABI = true;
  } catch (x) {
    haveWinAPIABI = false;
  }

  Assert.equal(c.char.toString(), "type char");
  Assert.equal(c.short.toString(), "type short");
  Assert.equal(c.int.toString(), "type int");
  Assert.equal(c.long.toString(), "type long");
  Assert.equal(c.long_long.toString(), "type long_long");
  Assert.equal(c.ssize_t.toString(), "type ssize_t");
  Assert.equal(c.int8_t.toString(), "type int8_t");
  Assert.equal(c.int16_t.toString(), "type int16_t");
  Assert.equal(c.int32_t.toString(), "type int32_t");
  Assert.equal(c.int64_t.toString(), "type int64_t");
  Assert.equal(c.intptr_t.toString(), "type intptr_t");

  Assert.equal(c.unsigned_char.toString(), "type unsigned_char");
  Assert.equal(c.unsigned_short.toString(), "type unsigned_short");
  Assert.equal(c.unsigned_int.toString(), "type unsigned_int");
  Assert.equal(c.unsigned_long.toString(), "type unsigned_long");
  Assert.equal(c.unsigned_long_long.toString(), "type unsigned_long_long");
  Assert.equal(c.size_t.toString(), "type size_t");
  Assert.equal(c.uint8_t.toString(), "type uint8_t");
  Assert.equal(c.uint16_t.toString(), "type uint16_t");
  Assert.equal(c.uint32_t.toString(), "type uint32_t");
  Assert.equal(c.uint64_t.toString(), "type uint64_t");
  Assert.equal(c.uintptr_t.toString(), "type uintptr_t");

  Assert.equal(c.float.toString(), "type float");
  Assert.equal(c.double.toString(), "type double");
  Assert.equal(c.bool.toString(), "type bool");
  Assert.equal(c.void_t.toString(), "type void");
  Assert.equal(c.voidptr_t.toString(), "type void*");
  Assert.equal(c.char16_t.toString(), "type char16_t");

  var simplestruct = c.StructType("simplestruct", [{ smitty: c.voidptr_t }]);
  Assert.equal(simplestruct.toString(), "type simplestruct");

  // One type modifier, int base type.
  Assert.equal(c.int.ptr.toString(), "type int*");
  Assert.equal(c.ArrayType(c.int).toString(), "type int[]");
  Assert.equal(c.ArrayType(c.int, 4).toString(), "type int[4]");
  Assert.equal(c.FunctionType(c.default_abi, c.int).toString(), "type int()");
  Assert.equal(
    c.FunctionType(c.default_abi, c.int, [c.bool]).toString(),
    "type int(bool)"
  );
  Assert.equal(
    c.FunctionType(c.default_abi, c.int, [c.bool, c.short]).toString(),
    "type int(bool, short)"
  );
  if (haveStdCallABI) {
    Assert.equal(
      c.FunctionType(c.stdcall_abi, c.int).toString(),
      "type int __stdcall()"
    );
  }
  if (haveWinAPIABI) {
    Assert.equal(
      c.FunctionType(c.winapi_abi, c.int).toString(),
      "type int WINAPI()"
    );
  }

  // One type modifier, struct base type.
  Assert.equal(simplestruct.ptr.toString(), "type simplestruct*");
  Assert.equal(c.ArrayType(simplestruct).toString(), "type simplestruct[]");
  Assert.equal(c.ArrayType(simplestruct, 4).toString(), "type simplestruct[4]");
  Assert.equal(
    c.FunctionType(c.default_abi, simplestruct).toString(),
    "type simplestruct()"
  );

  // Two levels of type modifiers, int base type.
  Assert.equal(c.int.ptr.ptr.toString(), "type int**");
  Assert.equal(c.ArrayType(c.int.ptr).toString(), "type int*[]");
  Assert.equal(
    c.FunctionType(c.default_abi, c.int.ptr).toString(),
    "type int*()"
  );

  Assert.equal(c.ArrayType(c.int).ptr.toString(), "type int(*)[]");
  Assert.equal(c.ArrayType(c.ArrayType(c.int, 4)).toString(), "type int[][4]");
  // Functions can't return arrays.

  Assert.equal(
    c.FunctionType(c.default_abi, c.int).ptr.toString(),
    "type int(*)()"
  );
  // You can't have an array of functions.
  // Functions can't return functions.

  // We don't try all the permissible three-deep combinations, but this is fun.
  Assert.equal(
    c
      .FunctionType(c.default_abi, c.FunctionType(c.default_abi, c.int).ptr)
      .toString(),
    "type int(*())()"
  );
}

function run_cast_tests() {
  // Test casting between basic types.
  let i = ctypes.int32_t();
  let j = ctypes.cast(i, ctypes.int16_t);
  Assert.equal(ptrValue(i.address()), ptrValue(j.address()));
  Assert.equal(i.value, j.value);
  let k = ctypes.cast(i, ctypes.uint32_t);
  Assert.equal(ptrValue(i.address()), ptrValue(k.address()));
  Assert.equal(i.value, k.value);

  // Test casting to a type of undefined or larger size.
  do_check_throws(function () {
    ctypes.cast(i, ctypes.void_t);
  }, TypeError);
  do_check_throws(function () {
    ctypes.cast(i, ctypes.int32_t.array());
  }, TypeError);
  do_check_throws(function () {
    ctypes.cast(i, ctypes.int64_t);
  }, TypeError);

  // Test casting between special types.
  let g_t = ctypes.StructType("g_t", [
    { a: ctypes.int32_t },
    { b: ctypes.double },
  ]);
  let a_t = ctypes.ArrayType(g_t, 4);
  let f_t = ctypes.FunctionType(ctypes.default_abi, ctypes.void_t).ptr;

  let a = a_t();
  a[0] = { a: 5, b: 7.5 };
  let g = ctypes.cast(a, g_t);
  Assert.equal(ptrValue(a.address()), ptrValue(g.address()));
  Assert.equal(a[0].a, g.a);

  let a2 = ctypes.cast(g, g_t.array(1));
  Assert.equal(ptrValue(a2.address()), ptrValue(g.address()));
  Assert.equal(a2[0].a, g.a);

  let p = g.address();
  let ip = ctypes.cast(p, ctypes.int32_t.ptr);
  Assert.equal(ptrValue(ip), ptrValue(p));
  Assert.equal(ptrValue(ip.address()), ptrValue(p.address()));
  Assert.equal(ip.contents, g.a);

  let f = f_t(0x5);
  let f2 = ctypes.cast(f, ctypes.voidptr_t);
  Assert.equal(ptrValue(f2), ptrValue(f));
  Assert.equal(ptrValue(f2.address()), ptrValue(f.address()));
}

function run_void_tests(library) {
  let test_void_t = library.declare(
    "test_void_t_cdecl",
    ctypes.default_abi,
    ctypes.void_t
  );
  Assert.equal(test_void_t(), undefined);

  // Test that library.declare throws with void function args.
  do_check_throws(function () {
    library.declare(
      "test_void_t_cdecl",
      ctypes.default_abi,
      ctypes.void_t,
      ctypes.void_t
    );
  }, TypeError);

  if ("winLastError" in ctypes) {
    test_void_t = library.declare(
      "test_void_t_stdcall",
      ctypes.stdcall_abi,
      ctypes.void_t
    );
    Assert.equal(test_void_t(), undefined);

    // Check that WINAPI symbol lookup for a regular stdcall function fails on
    // Win32 (it's all the same on Win64 though).
    if (ctypes.voidptr_t.size == 4) {
      do_check_throws(function () {
        library.declare(
          "test_void_t_stdcall",
          ctypes.winapi_abi,
          ctypes.void_t
        );
      }, Error);
    }
  }
}

function run_string_tests(library) {
  let test_ansi_len = library.declare(
    "test_ansi_len",
    ctypes.default_abi,
    ctypes.int32_t,
    ctypes.char.ptr
  );
  Assert.equal(test_ansi_len(""), 0);
  Assert.equal(test_ansi_len("hello world"), 11);

  // don't convert anything else to a string
  let vals = [
    true,
    0,
    1 / 3,
    undefined,
    {},
    {
      toString() {
        return "bad";
      },
    },
    [],
  ];
  for (let i = 0; i < vals.length; i++) {
    do_check_throws(function () {
      test_ansi_len(vals[i]);
    }, TypeError);
  }

  let test_wide_len = library.declare(
    "test_wide_len",
    ctypes.default_abi,
    ctypes.int32_t,
    ctypes.char16_t.ptr
  );
  Assert.equal(test_wide_len("hello world"), 11);

  let test_ansi_ret = library.declare(
    "test_ansi_ret",
    ctypes.default_abi,
    ctypes.char.ptr
  );
  Assert.equal(test_ansi_ret().readString(), "success");

  let test_wide_ret = library.declare(
    "test_wide_ret",
    ctypes.default_abi,
    ctypes.char16_t.ptr
  );
  Assert.equal(test_wide_ret().readString(), "success");

  let test_ansi_echo = library.declare(
    "test_ansi_echo",
    ctypes.default_abi,
    ctypes.char.ptr,
    ctypes.char.ptr
  );
  // We cannot pass a string literal directly into test_ansi_echo, since the
  // conversion to ctypes.char.ptr is only valid for the duration of the ffi
  // call. The escaped pointer that's returned will point to freed memory.
  let arg = ctypes.char.array()("anybody in there?");
  Assert.equal(test_ansi_echo(arg).readString(), "anybody in there?");
  Assert.equal(ptrValue(test_ansi_echo(null)), 0);
}

function run_readstring_tests() {
  // ASCII decode test, "hello world"
  let ascii_string = ctypes.unsigned_char.array(12)();
  ascii_string[0] = 0x68;
  ascii_string[1] = 0x65;
  ascii_string[2] = 0x6c;
  ascii_string[3] = 0x6c;
  ascii_string[4] = 0x6f;
  ascii_string[5] = 0x20;
  ascii_string[6] = 0x77;
  ascii_string[7] = 0x6f;
  ascii_string[8] = 0x72;
  ascii_string[9] = 0x6c;
  ascii_string[10] = 0x64;
  ascii_string[11] = 0;
  Assert.equal("hello world", ascii_string.readStringReplaceMalformed());

  // UTF-8 decode test, "U+AC00 U+B098 U+B2E4"
  let utf8_string = ctypes.unsigned_char.array(10)();
  utf8_string[0] = 0xea;
  utf8_string[1] = 0xb0;
  utf8_string[2] = 0x80;
  utf8_string[3] = 0xeb;
  utf8_string[4] = 0x82;
  utf8_string[5] = 0x98;
  utf8_string[6] = 0xeb;
  utf8_string[7] = 0x8b;
  utf8_string[8] = 0xa4;
  utf8_string[9] = 0x00;
  let utf8_result = utf8_string.readStringReplaceMalformed();
  Assert.equal(0xac00, utf8_result.charCodeAt(0));
  Assert.equal(0xb098, utf8_result.charCodeAt(1));
  Assert.equal(0xb2e4, utf8_result.charCodeAt(2));

  // KS5601 decode test, invalid encoded byte should be replaced with U+FFFD
  let ks5601_string = ctypes.unsigned_char.array(7)();
  ks5601_string[0] = 0xb0;
  ks5601_string[1] = 0xa1;
  ks5601_string[2] = 0xb3;
  ks5601_string[3] = 0xaa;
  ks5601_string[4] = 0xb4;
  ks5601_string[5] = 0xd9;
  ks5601_string[6] = 0x00;
  let ks5601_result = ks5601_string.readStringReplaceMalformed();
  Assert.equal(0xfffd, ks5601_result.charCodeAt(0));
  Assert.equal(0xfffd, ks5601_result.charCodeAt(1));
  Assert.equal(0xfffd, ks5601_result.charCodeAt(2));
  Assert.equal(0xfffd, ks5601_result.charCodeAt(3));
  Assert.equal(0xfffd, ks5601_result.charCodeAt(4));
  Assert.equal(0xfffd, ks5601_result.charCodeAt(5));

  // Mixed decode test, "test" + "U+AC00 U+B098 U+B2E4" + "test"
  // invalid encoded byte should be replaced with U+FFFD
  let mixed_string = ctypes.unsigned_char.array(15)();
  mixed_string[0] = 0x74;
  mixed_string[1] = 0x65;
  mixed_string[2] = 0x73;
  mixed_string[3] = 0x74;
  mixed_string[4] = 0xb0;
  mixed_string[5] = 0xa1;
  mixed_string[6] = 0xb3;
  mixed_string[7] = 0xaa;
  mixed_string[8] = 0xb4;
  mixed_string[9] = 0xd9;
  mixed_string[10] = 0x74;
  mixed_string[11] = 0x65;
  mixed_string[12] = 0x73;
  mixed_string[13] = 0x74;
  mixed_string[14] = 0x00;
  let mixed_result = mixed_string.readStringReplaceMalformed();
  Assert.equal(0x74, mixed_result.charCodeAt(0));
  Assert.equal(0x65, mixed_result.charCodeAt(1));
  Assert.equal(0x73, mixed_result.charCodeAt(2));
  Assert.equal(0x74, mixed_result.charCodeAt(3));
  Assert.equal(0xfffd, mixed_result.charCodeAt(4));
  Assert.equal(0xfffd, mixed_result.charCodeAt(5));
  Assert.equal(0xfffd, mixed_result.charCodeAt(6));
  Assert.equal(0xfffd, mixed_result.charCodeAt(7));
  Assert.equal(0xfffd, mixed_result.charCodeAt(8));
  Assert.equal(0xfffd, mixed_result.charCodeAt(9));
  Assert.equal(0x74, mixed_result.charCodeAt(10));
  Assert.equal(0x65, mixed_result.charCodeAt(11));
  Assert.equal(0x73, mixed_result.charCodeAt(12));
  Assert.equal(0x74, mixed_result.charCodeAt(13));

  // Test of all posible invalid encoded sequence
  let invalid_string = ctypes.unsigned_char.array(27)();
  invalid_string[0] = 0x80; // 10000000
  invalid_string[1] = 0xd0; // 11000000 01110100
  invalid_string[2] = 0x74;
  invalid_string[3] = 0xe0; // 11100000 01110100
  invalid_string[4] = 0x74;
  invalid_string[5] = 0xe0; // 11100000 10100000 01110100
  invalid_string[6] = 0xa0;
  invalid_string[7] = 0x74;
  invalid_string[8] = 0xe0; // 11100000 10000000 01110100
  invalid_string[9] = 0x80;
  invalid_string[10] = 0x74;
  invalid_string[11] = 0xf0; // 11110000 01110100
  invalid_string[12] = 0x74;
  invalid_string[13] = 0xf0; // 11110000 10010000 01110100
  invalid_string[14] = 0x90;
  invalid_string[15] = 0x74;
  invalid_string[16] = 0xf0; // 11110000 10010000 10000000 01110100
  invalid_string[17] = 0x90;
  invalid_string[18] = 0x80;
  invalid_string[19] = 0x74;
  invalid_string[20] = 0xf0; // 11110000 10000000 10000000 01110100
  invalid_string[21] = 0x80;
  invalid_string[22] = 0x80;
  invalid_string[23] = 0x74;
  invalid_string[24] = 0xf0; // 11110000 01110100
  invalid_string[25] = 0x74;
  invalid_string[26] = 0x00;
  let invalid_result = invalid_string.readStringReplaceMalformed();
  Assert.equal(0xfffd, invalid_result.charCodeAt(0)); // 10000000
  Assert.equal(0xfffd, invalid_result.charCodeAt(1)); // 11000000 01110100
  Assert.equal(0x74, invalid_result.charCodeAt(2));
  Assert.equal(0xfffd, invalid_result.charCodeAt(3)); // 11100000 01110100
  Assert.equal(0x74, invalid_result.charCodeAt(4));
  Assert.equal(0xfffd, invalid_result.charCodeAt(5)); // 11100000 10100000 01110100
  Assert.equal(0x74, invalid_result.charCodeAt(6));
  Assert.equal(0xfffd, invalid_result.charCodeAt(7)); // 11100000 10000000 01110100
  Assert.equal(0xfffd, invalid_result.charCodeAt(8));
  Assert.equal(0x74, invalid_result.charCodeAt(9));
  Assert.equal(0xfffd, invalid_result.charCodeAt(10)); // 11110000 01110100
  Assert.equal(0x74, invalid_result.charCodeAt(11));
  Assert.equal(0xfffd, invalid_result.charCodeAt(12)); // 11110000 10010000 01110100
  Assert.equal(0x74, invalid_result.charCodeAt(13));
  Assert.equal(0xfffd, invalid_result.charCodeAt(14)); // 11110000 10010000 10000000 01110100
  Assert.equal(0x74, invalid_result.charCodeAt(15));
  Assert.equal(0xfffd, invalid_result.charCodeAt(16)); // 11110000 10000000 10000000 01110100
  Assert.equal(0xfffd, invalid_result.charCodeAt(17));
  Assert.equal(0xfffd, invalid_result.charCodeAt(18));
  Assert.equal(0x74, invalid_result.charCodeAt(19));
  Assert.equal(0xfffd, invalid_result.charCodeAt(20)); // 11110000 01110100
  Assert.equal(0x74, invalid_result.charCodeAt(21));

  // Test decoding of UTF-8 and CESU-8
  let utf8_cesu8_string = ctypes.unsigned_char.array(10)();
  utf8_cesu8_string[0] = 0xf0; // U+10400 in UTF-8
  utf8_cesu8_string[1] = 0x90;
  utf8_cesu8_string[2] = 0x90;
  utf8_cesu8_string[3] = 0x80;
  utf8_cesu8_string[4] = 0xed; // U+10400 in CESU-8
  utf8_cesu8_string[5] = 0xa0;
  utf8_cesu8_string[6] = 0x81;
  utf8_cesu8_string[7] = 0xed;
  utf8_cesu8_string[8] = 0xb0;
  utf8_cesu8_string[9] = 0x80;
  let utf8_cesu8_result = utf8_cesu8_string.readStringReplaceMalformed();
  Assert.equal(0xd801, utf8_cesu8_result.charCodeAt(0));
  Assert.equal(0xdc00, utf8_cesu8_result.charCodeAt(1));
  Assert.equal(0xfffd, utf8_cesu8_result.charCodeAt(2));
  Assert.equal(0xfffd, utf8_cesu8_result.charCodeAt(3));
}

function run_struct_tests(library) {
  const point_t = new ctypes.StructType("myPOINT", [
    { x: ctypes.int32_t },
    { y: ctypes.int32_t },
  ]);
  const rect_t = new ctypes.StructType("myRECT", [
    { top: ctypes.int32_t },
    { left: ctypes.int32_t },
    { bottom: ctypes.int32_t },
    { right: ctypes.int32_t },
  ]);

  let test_pt_in_rect = library.declare(
    "test_pt_in_rect",
    ctypes.default_abi,
    ctypes.int32_t,
    rect_t,
    point_t
  );
  let rect = new rect_t(10, 5, 5, 10);
  let pt1 = new point_t(6, 6);
  Assert.equal(test_pt_in_rect(rect, pt1), 1);
  let pt2 = new point_t(2, 2);
  Assert.equal(test_pt_in_rect(rect, pt2), 0);

  const inner_t = new ctypes.StructType("INNER", [
    { i1: ctypes.uint8_t },
    { i2: ctypes.int64_t },
    { i3: ctypes.uint8_t },
  ]);
  const nested_t = new ctypes.StructType("NESTED", [
    { n1: ctypes.int32_t },
    { n2: ctypes.int16_t },
    { inner: inner_t },
    { n3: ctypes.int64_t },
    { n4: ctypes.int32_t },
  ]);

  let test_nested_struct = library.declare(
    "test_nested_struct",
    ctypes.default_abi,
    ctypes.int32_t,
    nested_t
  );
  let inner = new inner_t(161, 523412, 43);
  let nested = new nested_t(13155, 1241, inner, 24512115, 1234111);
  // add up all the numbers and make sure the C function agrees
  Assert.equal(test_nested_struct(nested), 26284238);

  // test returning a struct by value
  let test_struct_return = library.declare(
    "test_struct_return",
    ctypes.default_abi,
    point_t,
    rect_t
  );
  let ret = test_struct_return(rect);
  Assert.equal(ret.x, rect.left);
  Assert.equal(ret.y, rect.top);

  // struct parameter ABI depends on size; test returning a large struct by value
  test_struct_return = library.declare(
    "test_large_struct_return",
    ctypes.default_abi,
    rect_t,
    rect_t,
    rect_t
  );
  ret = test_struct_return(rect_t(1, 2, 3, 4), rect_t(5, 6, 7, 8));
  Assert.equal(ret.left, 2);
  Assert.equal(ret.right, 4);
  Assert.equal(ret.top, 5);
  Assert.equal(ret.bottom, 7);

  // ... and tests structs < 8 bytes in size
  for (let i = 1; i < 8; ++i) {
    run_small_struct_test(library, rect_t, i);
  }

  // test passing a struct by pointer
  let test_init_pt = library.declare(
    "test_init_pt",
    ctypes.default_abi,
    ctypes.void_t,
    point_t.ptr,
    ctypes.int32_t,
    ctypes.int32_t
  );
  test_init_pt(pt1.address(), 9, 10);
  Assert.equal(pt1.x, 9);
  Assert.equal(pt1.y, 10);
}

function run_small_struct_test(library, rect_t, bytes) {
  let fields = [];
  for (let i = 0; i < bytes; ++i) {
    let field = {};
    field["f" + i] = ctypes.uint8_t;
    fields.push(field);
  }
  const small_t = new ctypes.StructType("SMALL", fields);

  let test_small_struct_return = library.declare(
    "test_" + bytes + "_byte_struct_return",
    ctypes.default_abi,
    small_t,
    rect_t
  );
  let ret = test_small_struct_return(rect_t(1, 7, 13, 45));

  let exp = [1, 7, 13, 45];
  let j = 0;
  for (let i = 0; i < bytes; ++i) {
    Assert.equal(ret["f" + i], exp[j]);
    if (++j == 4) {
      j = 0;
    }
  }
}

function run_function_tests(library) {
  let test_ansi_len = library.declare(
    "test_ansi_len",
    ctypes.default_abi,
    ctypes.int32_t,
    ctypes.char.ptr
  );
  let fn_t = ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [
    ctypes.char.ptr,
  ]).ptr;

  let test_fnptr = library.declare("test_fnptr", ctypes.default_abi, fn_t);

  // Test that the value handed back by test_fnptr matches the function pointer
  // for test_ansi_len itself.
  let ptr = test_fnptr();
  Assert.equal(ptrValue(test_ansi_len), ptrValue(ptr));

  // Test that we can call ptr().
  Assert.equal(ptr("function pointers rule!"), 23);

  // Test that we can call via call and apply
  /* eslint-disable no-useless-call */
  Assert.equal(ptr.call(null, "function pointers rule!"), 23);
  Assert.equal(ptr.apply(null, ["function pointers rule!"]), 23);

  // Test that we cannot call non-function pointers via call and apply
  let p_t = ctypes.PointerType(ctypes.int32_t);
  let p = p_t();
  do_check_throws(function () {
    p.call(null, "woo");
  }, TypeError);
  do_check_throws(function () {
    p.apply(null, ["woo"]);
  }, TypeError);
  /* eslint-enable no-useless-call */

  // Test the function pointers still behave as regular pointers
  Assert.ok(!ptr.isNull(), "PointerType methods should still be valid");

  // Test that library.declare() returns data of type FunctionType.ptr, and that
  // it is immutable.
  Assert.strictEqual(
    Object.getPrototypeOf(test_ansi_len.constructor.targetType),
    ctypes.FunctionType.prototype
  );
  Assert.equal(
    test_ansi_len.constructor.toSource(),
    "ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [ctypes.char.ptr]).ptr"
  );
  /* disabled temporarily per bug 598225.
  do_check_throws(function() { test_ansi_len.value = null; }, Error);
  Assert.equal(ptrValue(test_ansi_len), ptrValue(ptr));
*/

  // Test that the library.declare(name, functionType) form works.
  let test_ansi_len_2 = library.declare("test_ansi_len", fn_t);
  Assert.strictEqual(test_ansi_len_2.constructor, fn_t);
  Assert.equal(ptrValue(test_ansi_len), ptrValue(test_ansi_len_2));
  /* disabled temporarily per bug 598225.
  do_check_throws(function() { test_ansi_len_2.value = null; }, Error);
  Assert.equal(ptrValue(test_ansi_len_2), ptrValue(ptr));
*/
}

function run_closure_tests(library) {
  run_single_closure_tests(library, ctypes.default_abi, "cdecl");
  if ("winLastError" in ctypes) {
    run_single_closure_tests(library, ctypes.stdcall_abi, "stdcall");

    // Check that attempting to construct a ctypes.winapi_abi closure throws.
    function closure_fn() {
      return 1;
    }
    let fn_t = ctypes.FunctionType(ctypes.winapi_abi, ctypes.int32_t, []).ptr;
    do_check_throws(function () {
      fn_t(closure_fn);
    }, Error);
  }
}

function run_single_closure_tests(library, abi, suffix) {
  let b = 23;

  function closure_fn(i) {
    if (i == 42) {
      throw new Error("7.5 million years for that?");
    }
    return "a" in this ? i + this.a : i + b;
  }

  Assert.equal(closure_fn(7), 7 + b);
  let thisobj = { a: 5 };
  Assert.equal(closure_fn.call(thisobj, 7), 7 + thisobj.a);

  // Construct a closure, and call it ourselves.
  let fn_t = ctypes.FunctionType(abi, ctypes.int32_t, [ctypes.int8_t]).ptr;
  let closure = fn_t(closure_fn);
  Assert.equal(closure(-17), -17 + b);

  // Have C code call it.
  let test_closure = library.declare(
    "test_closure_" + suffix,
    ctypes.default_abi,
    ctypes.int32_t,
    ctypes.int8_t,
    fn_t
  );
  Assert.equal(test_closure(-52, closure), -52 + b);

  // Do the same, but specify 'this'.
  let closure2 = fn_t(closure_fn, thisobj);
  Assert.equal(closure2(-17), -17 + thisobj.a);
  Assert.equal(test_closure(-52, closure2), -52 + thisobj.a);

  // Specify an error sentinel, and have the JS code throw (see bug 599791).
  let closure3 = fn_t(closure_fn, null, 54);
  Assert.equal(closure3(42), 54);
  Assert.equal(test_closure(42, closure3), 54);

  // Check what happens when the return type is bigger than a word.
  var fn_64_t = ctypes.FunctionType(ctypes.default_abi, ctypes.uint64_t, [
    ctypes.bool,
  ]).ptr;
  var bignum1 = ctypes.UInt64.join(0xdeadbeef, 0xbadf00d);
  var bignum2 = ctypes.UInt64.join(0xdefec8ed, 0xd15ea5e);
  function closure_fn_64(fail) {
    if (fail) {
      throw new Error("Just following orders, sir!");
    }
    return bignum1;
  }
  var closure64 = fn_64_t(closure_fn_64, null, bignum2);
  Assert.equal(ctypes.UInt64.compare(closure64(false), bignum1), 0);
  Assert.equal(ctypes.UInt64.compare(closure64(true), bignum2), 0);

  // Test a callback that returns void (see bug 682504).
  var fn_v_t = ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, []).ptr;
  fn_v_t(function () {})(); // Don't crash

  // Code evaluated in a sandbox uses (and pushes) a separate JSContext.
  // Make sure that we don't run into an assertion caused by a cx stack
  // mismatch with the cx stashed in the closure.
  try {
    var sb = Cu.Sandbox("http://www.example.com");
    sb.fn = fn_v_t(function () {
      sb.foo = {};
    });
    Cu.evalInSandbox("fn();", sb);
  } catch (e) {} // Components not available in workers.

  // Make sure that a void callback can't return an error sentinel.
  var sentinelThrew = false;
  try {
    fn_v_t(function () {}, null, -1);
  } catch (e) {
    sentinelThrew = true;
  }
  Assert.ok(sentinelThrew);
}

function run_variadic_tests(library) {
  let sum_va_type = ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [
      ctypes.uint8_t,
      "...",
    ]).ptr,
    sum_va = library.declare(
      "test_sum_va_cdecl",
      ctypes.default_abi,
      ctypes.int32_t,
      ctypes.uint8_t,
      "..."
    );

  Assert.equal(
    sum_va_type.toSource(),
    'ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [ctypes.uint8_t, "..."]).ptr'
  );
  Assert.equal(sum_va.constructor.name, "int32_t(*)(uint8_t, ...)");
  Assert.ok(sum_va.constructor.targetType.isVariadic);

  Assert.equal(
    sum_va(3, ctypes.int32_t(1), ctypes.int32_t(2), ctypes.int32_t(3)),
    6
  );

  do_check_throws(function () {
    ctypes.FunctionType(ctypes.default_abi, ctypes.bool, [
      ctypes.bool,
      "...",
      ctypes.bool,
    ]);
  }, Error);

  do_check_throws(function () {
    ctypes.FunctionType(ctypes.default_abi, ctypes.bool, ["..."]);
  }, Error);

  if ("winLastError" in ctypes) {
    do_check_throws(function () {
      ctypes.FunctionType(ctypes.stdcall_abi, ctypes.bool, [
        ctypes.bool,
        "...",
      ]);
    }, Error);
    do_check_throws(function () {
      ctypes.FunctionType(ctypes.winapi_abi, ctypes.bool, [ctypes.bool, "..."]);
    }, Error);
  }

  do_check_throws(function () {
    // No variadic closure callbacks allowed.
    sum_va_type(function () {});
  }, Error);

  let count_true_va = library.declare(
    "test_sum_va_cdecl",
    ctypes.default_abi,
    ctypes.uint8_t,
    ctypes.uint8_t,
    "..."
  );
  Assert.equal(
    count_true_va(
      8,
      ctypes.bool(false),
      ctypes.bool(false),
      ctypes.bool(false),
      ctypes.bool(true),
      ctypes.bool(true),
      ctypes.bool(false),
      ctypes.bool(true),
      ctypes.bool(true)
    ),
    4
  );

  let add_char_short_int_va = library.declare(
      "test_add_char_short_int_va_cdecl",
      ctypes.default_abi,
      ctypes.void_t,
      ctypes.uint32_t.ptr,
      "..."
    ),
    result = ctypes.uint32_t(3);

  add_char_short_int_va(
    result.address(),
    ctypes.char(5),
    ctypes.short(7),
    ctypes.uint32_t(11)
  );

  Assert.equal(result.value, 3 + 5 + 7 + 11);

  let add_float_double_va = library.declare(
    "test_add_float_double_va_cdecl",
    ctypes.default_abi,
    ctypes.void_t,
    ctypes.double.ptr,
    "..."
  );
  let dresult = ctypes.double(0.0);
  add_float_double_va(
    dresult.address(),
    ctypes.float32_t(1.5),
    ctypes.double(2.25)
  );
  Assert.equal(dresult.value, 3.75);

  let add_uint8_uint16_va = library.declare(
    "test_add_uint8_uint16_va_cdecl",
    ctypes.default_abi,
    ctypes.void_t,
    ctypes.uint32_t.ptr,
    "..."
  );
  let uresult = ctypes.uint32_t(0);
  add_uint8_uint16_va(
    uresult.address(),
    ctypes.uint8_t(200),
    ctypes.uint16_t(1000)
  );
  Assert.equal(uresult.value, 1200);

  result = ctypes.int32_t.array(3)([1, 1, 1]);
  let v1 = ctypes.int32_t.array(4)([1, 2, 3, 5]);
  let v2 = ctypes.int32_t.array(3)([7, 11, 13]);
  let vector_add_va = library.declare(
    "test_vector_add_va_cdecl",
    ctypes.default_abi,
    ctypes.int32_t.ptr,
    ctypes.uint8_t,
    ctypes.uint8_t,
    ctypes.int32_t.ptr,
    "..."
  );
  // Note that vector_add_va zeroes out result first.
  let vec_sum = vector_add_va(2, 3, result, v1, v2);
  Assert.equal(vec_sum.contents, 8);
  Assert.equal(result[0], 8);
  Assert.equal(result[1], 13);
  Assert.equal(result[2], 16);

  Assert.ok(!!(sum_va_type().value = sum_va_type()));
  let sum_notva_type = ctypes.FunctionType(
    sum_va_type.targetType.abi,
    sum_va_type.targetType.returnType,
    [ctypes.uint8_t]
  ).ptr;
  do_check_throws(function () {
    sum_va_type().value = sum_notva_type();
  }, TypeError);
}

function run_static_data_tests(library) {
  const rect_t = new ctypes.StructType("myRECT", [
    { top: ctypes.int32_t },
    { left: ctypes.int32_t },
    { bottom: ctypes.int32_t },
    { right: ctypes.int32_t },
  ]);

  let data_rect = library.declare("data_rect", rect_t);

  // Test reading static data.
  Assert.strictEqual(data_rect.constructor, rect_t);
  Assert.equal(data_rect.top, -1);
  Assert.equal(data_rect.left, -2);
  Assert.equal(data_rect.bottom, 3);
  Assert.equal(data_rect.right, 4);

  // Test writing.
  data_rect.top = 9;
  data_rect.left = 8;
  data_rect.bottom = -11;
  data_rect.right = -12;
  Assert.equal(data_rect.top, 9);
  Assert.equal(data_rect.left, 8);
  Assert.equal(data_rect.bottom, -11);
  Assert.equal(data_rect.right, -12);

  // Make sure it's been written, not copied.
  let data_rect_2 = library.declare("data_rect", rect_t);
  Assert.equal(data_rect_2.top, 9);
  Assert.equal(data_rect_2.left, 8);
  Assert.equal(data_rect_2.bottom, -11);
  Assert.equal(data_rect_2.right, -12);
  Assert.equal(ptrValue(data_rect.address()), ptrValue(data_rect_2.address()));
}

function run_cpp_class_tests(library) {
  // try the gcc mangling, unless we're using MSVC.
  let OS = get_os();
  let ctor_symbol;
  let add_symbol;
  let abi;
  if (OS == "WINNT") {
    // for compatibility for Win32 vs Win64
    abi = ctypes.thiscall_abi;
    if (ctypes.size_t.size == 8) {
      ctor_symbol = "??0TestClass@@QEAA@H@Z";
      add_symbol = "?Add@TestClass@@QEAAHH@Z";
    } else {
      ctor_symbol = "??0TestClass@@QAE@H@Z";
      add_symbol = "?Add@TestClass@@QAEHH@Z";
    }
  } else {
    abi = ctypes.default_abi;
    ctor_symbol = "_ZN9TestClassC1Ei";
    add_symbol = "_ZN9TestClass3AddEi";
  }

  let test_class_ctor = library.declare(
    ctor_symbol,
    abi,
    ctypes.void_t,
    ctypes.int32_t.ptr,
    ctypes.int32_t
  );
  let i = ctypes.int32_t();
  test_class_ctor(i.address(), 8);
  Assert.equal(i.value, 8);

  let test_class_add = library.declare(
    add_symbol,
    abi,
    ctypes.int32_t,
    ctypes.int32_t.ptr,
    ctypes.int32_t
  );
  let j = test_class_add(i.address(), 5);
  Assert.equal(j, 13);
  Assert.equal(i.value, 13);
}

// bug 522360 - try loading system library without full path
function run_load_system_library() {
  let syslib;
  let OS = get_os();
  if (OS == "WINNT") {
    syslib = ctypes.open("user32.dll");
  } else if (OS == "Darwin") {
    syslib = ctypes.open("libm.dylib");
  } else if (OS == "Linux" || OS == "Android" || OS.match(/BSD$/)) {
    try {
      syslib = ctypes.open("libm.so");
    } catch (e) {
      // limb.so wasn't available, try libm.so.6 instead
      syslib = ctypes.open("libm.so.6");
    }
  } else {
    do_throw("please add a system library for this test");
  }
  syslib.close();
  return true;
}
