CVE-2024-30253: Insufficient Information (NVD-CWE-noinfo)
value: @solana/web3.js is the Solana JavaScript SDK. Using particular inputs with `@solana/web3.js` will result in memory exhaustion (OOM). If you have a server, client, mobile, or desktop product that accepts untrusted input for use with `@solana/web3.js`, your application/service may crash, resulting in a loss of availability. This vulnerability is fixed in 1.0.1, 1.10.2, 1.11.1, 1.12.1, 1.1.2, 1.13.1, 1.14.1, 1.15.1, 1.16.2, 1.17.1, 1.18.1, 1.19.1, 1.20.3, 1.21.1, 1.22.1, 1.23.1, 1.24.3, 1.25.1, 1.26.1, 1.27.1, 1.28.1, 1.2.8, 1.29.4, 1.30.3, 1.31.1, 1.3.1, 1.32.3, 1.33.1, 1.34.1, 1.35.2, 1.36.1, 1.37.3, 1.38.1, 1.39.2, 1.40.2, 1.41.11, 1.4.1, 1.42.1, 1.43.7, 1.44.4, 1.45.1, 1.46.1, 1.47.5, 1.48.1, 1.49.1, 1.50.2, 1.51.1, 1.5.1, 1.52.1, 1.53.1, 1.54.2, 1.55.1, 1.56.3, 1.57.1, 1.58.1, 1.59.2, 1.60.1, 1.61.2, 1.6.1, 1.62.2, 1.63.2, 1.64.1, 1.65.1, 1.66.6, 1.67.3, 1.68.2, 1.69.1, 1.70.4, 1.71.1, 1.72.1, 1.7.2, 1.73.5, 1.74.1, 1.75.1, 1.76.1, 1.77.4, 1.78.8, 1.79.1, 1.80.1, 1.81.1, 1.8.1, 1.82.1, 1.83.1, 1.84.1, 1.85.1, 1.86.1, 1.87.7, 1.88.1, 1.89.2, 1.90.2, 1.9.2, and 1.91.3.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-17T15:15Z
Repository: https://github.com/solana-labs/solana-web3.js
Language: TypeScript
Files changed: packages/library-legacy/src/message/legacy.ts, packages/library-legacy/src/message/v0.ts, packages/library-legacy/src/transaction/legacy.ts, packages/library-legacy/src/transaction/versioned.ts, packages/library-legacy/src/utils/guarded-array-utils.ts, packages/library-legacy/src/validator-info.ts, packages/library-legacy/test/guarded-array-utils.test.ts
Commit date: 2024-04-01 14:33:05 +0000
Commit message: fix: bounds check

Diff: diff --git a/packages/library-legacy/src/message/legacy.ts b/packages/library-legacy/src/message/legacy.ts
index 587c6df5..6b8ee132 100644
--- a/packages/library-legacy/src/message/legacy.ts
+++ b/packages/library-legacy/src/message/legacy.ts
@@ -14,10 +14,11 @@ import {
   MessageCompiledInstruction,
 } from './index';
 import {TransactionInstruction} from '../transaction';
 import {CompiledKeys} from './compiled-keys';
 import {MessageAccountKeys} from './account-keys';
+import {guardedShift, guardedSplice} from '../utils/guarded-array-utils';
 
 /**
  * An instruction to execute by a program
  *
  * @property {number} programIdIndex
@@ -266,40 +267,40 @@ export class Message {
    */
   static from(buffer: Buffer | Uint8Array | Array<number>): Message {
     // Slice up wire data
     let byteArray = [...buffer];
 
-    const numRequiredSignatures = byteArray.shift()!;
+    const numRequiredSignatures = guardedShift(byteArray);
     if (
       numRequiredSignatures !==
       (numRequiredSignatures & VERSION_PREFIX_MASK)
     ) {
       throw new Error(
         'Versioned messages must be deserialized with VersionedMessage.deserialize()',
       );
     }
 
-    const numReadonlySignedAccounts = byteArray.shift()!;
-    const numReadonlyUnsignedAccounts = byteArray.shift()!;
+    const numReadonlySignedAccounts = guardedShift(byteArray);
+    const numReadonlyUnsignedAccounts = guardedShift(byteArray);
 
     const accountCount = shortvec.decodeLength(byteArray);
     let accountKeys = [];
     for (let i = 0; i < accountCount; i++) {
-      const account = byteArray.splice(0, PUBLIC_KEY_LENGTH);
+      const account = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH);
       accountKeys.push(new PublicKey(Buffer.from(account)));
     }
 
-    const recentBlockhash = byteArray.splice(0, PUBLIC_KEY_LENGTH);
+    const recentBlockhash = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH);
 
     const instructionCount = shortvec.decodeLength(byteArray);
     let instructions: CompiledInstruction[] = [];
     for (let i = 0; i < instructionCount; i++) {
-      const programIdIndex = byteArray.shift()!;
+      const programIdIndex = guardedShift(byteArray);
       const accountCount = shortvec.decodeLength(byteArray);
-      const accounts = byteArray.splice(0, accountCount);
+      const accounts = guardedSplice(byteArray, 0, accountCount);
       const dataLength = shortvec.decodeLength(byteArray);
-      const dataSlice = byteArray.splice(0, dataLength);
+      const dataSlice = guardedSplice(byteArray, 0, dataLength);
       const data = bs58.encode(Buffer.from(dataSlice));
       instructions.push({
         programIdIndex,
         accounts,
         data,
diff --git a/packages/library-legacy/src/message/v0.ts b/packages/library-legacy/src/message/v0.ts
index 10156568..c4263389 100644
--- a/packages/library-legacy/src/message/v0.ts
+++ b/packages/library-legacy/src/message/v0.ts
@@ -14,10 +14,11 @@ import assert from '../utils/assert';
 import {PACKET_DATA_SIZE, VERSION_PREFIX_MASK} from '../transaction/constants';
 import {TransactionInstruction} from '../transaction';
 import {AddressLookupTableAccount} from '../programs';
 import {CompiledKeys} from './compiled-keys';
 import {AccountKeysFromLookups, MessageAccountKeys} from './account-keys';
+import {guardedShift, guardedSplice} from '../utils/guarded-array-utils';
 
 /**
  * Message constructor arguments
  */
 export type MessageV0Args = {
@@ -424,11 +425,11 @@ export class MessageV0 {
   }
 
   static deserialize(serializedMessage: Uint8Array): MessageV0 {
     let byteArray = [...serializedMessage];
 
-    const prefix = byteArray.shift() as number;
+    const prefix = guardedShift(byteArray);
     const maskedPrefix = prefix & VERSION_PREFIX_MASK;
     assert(
       prefix !== maskedPrefix,
       `Expected versioned message but received legacy message`,
     );
@@ -438,48 +439,64 @@ export class MessageV0 {
       version === 0,
       `Expected versioned message with version 0 but found version ${version}`,
     );
 
     const header: MessageHeader = {
-      numRequiredSignatures: byteArray.shift() as number,
-      numReadonlySignedAccounts: byteArray.shift() as number,
-      numReadonlyUnsignedAccounts: byteArray.shift() as number,
+      numRequiredSignatures: guardedShift(byteArray),
+      numReadonlySignedAccounts: guardedShift(byteArray),
+      numReadonlyUnsignedAccounts: guardedShift(byteArray),
     };
 
     const staticAccountKeys = [];
     const staticAccountKeysLength = shortvec.decodeLength(byteArray);
     for (let i = 0; i < staticAccountKeysLength; i++) {
       staticAccountKeys.push(
-        new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)),
+        new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)),
       );
     }
 
-    const recentBlockhash = bs58.encode(byteArray.splice(0, PUBLIC_KEY_LENGTH));
+    const recentBlockhash = bs58.encode(
+      guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
+    );
 
     const instructionCount = shortvec.decodeLength(byteArray);
     const compiledInstructions: MessageCompiledInstruction[] = [];
     for (let i = 0; i < instructionCount; i++) {
-      const programIdIndex = byteArray.shift() as number;
+      const programIdIndex = guardedShift(byteArray);
       const accountKeyIndexesLength = shortvec.decodeLength(byteArray);
-      const accountKeyIndexes = byteArray.splice(0, accountKeyIndexesLength);
+      const accountKeyIndexes = guardedSplice(
+        byteArray,
+        0,
+        accountKeyIndexesLength,
+      );
       const dataLength = shortvec.decodeLength(byteArray);
-      const data = new Uint8Array(byteArray.splice(0, dataLength));
+      const data = new Uint8Array(guardedSplice(byteArray, 0, dataLength));
       compiledInstructions.push({
         programIdIndex,
         accountKeyIndexes,
         data,
       });
     }
 
     const addressTableLookupsCount = shortvec.decodeLength(byteArray);
     const addressTableLookups: MessageAddressTableLookup[] = [];
     for (let i = 0; i < addressTableLookupsCount; i++) {
-      const accountKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH));
+      const accountKey = new PublicKey(
+        guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
+      );
       const writableIndexesLength = shortvec.decodeLength(byteArray);
-      const writableIndexes = byteArray.splice(0, writableIndexesLength);
+      const writableIndexes = guardedSplice(
+        byteArray,
+        0,
+        writableIndexesLength,
+      );
       const readonlyIndexesLength = shortvec.decodeLength(byteArray);
-      const readonlyIndexes = byteArray.splice(0, readonlyIndexesLength);
+      const readonlyIndexes = guardedSplice(
+        byteArray,
+        0,
+        readonlyIndexesLength,
+      );
       addressTableLookups.push({
         accountKey,
         writableIndexes,
         readonlyIndexes,
       });
diff --git a/packages/library-legacy/src/transaction/legacy.ts b/packages/library-legacy/src/transaction/legacy.ts
index ee11644c..3dd38106 100644
--- a/packages/library-legacy/src/transaction/legacy.ts
+++ b/packages/library-legacy/src/transaction/legacy.ts
@@ -10,10 +10,11 @@ import {toBuffer} from '../utils/to-buffer';
 import invariant from '../utils/assert';
 import type {Signer} from '../keypair';
 import type {Blockhash} from '../blockhash';
 import type {CompiledInstruction} from '../message';
 import {sign, verify} from '../utils/ed25519';
+import {guardedSplice} from '../utils/guarded-array-utils';
 
 /** @internal */
 type MessageSignednessErrors = {
   invalid?: PublicKey[];
   missing?: PublicKey[];
@@ -902,11 +903,11 @@ export class Transaction {
     let byteArray = [...buffer];
 
     const signatureCount = shortvec.decodeLength(byteArray);
     let signatures = [];
     for (let i = 0; i < signatureCount; i++) {
-      const signature = byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES);
+      const signature = guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES);
       signatures.push(bs58.encode(Buffer.from(signature)));
     }
 
     return Transaction.populate(Message.from(byteArray), signatures);
   }
diff --git a/packages/library-legacy/src/transaction/versioned.ts b/packages/library-legacy/src/transaction/versioned.ts
index 53e78ade..a810beff 100644
--- a/packages/library-legacy/src/transaction/versioned.ts
+++ b/packages/library-legacy/src/transaction/versioned.ts
@@ -6,10 +6,11 @@ import {VersionedMessage} from '../message/versioned';
 import {SIGNATURE_LENGTH_IN_BYTES} from './constants';
 import * as shortvec from '../utils/shortvec-encoding';
 import * as Layout from '../layout';
 import {sign} from '../utils/ed25519';
 import {PublicKey} from '../publickey';
+import {guardedSplice} from '../utils/guarded-array-utils';
 
 export type TransactionVersion = 'legacy' | 0;
 
 /**
  * Versioned transaction class
@@ -80,11 +81,11 @@ export class VersionedTransaction {
 
     const signatures = [];
     const signaturesLength = shortvec.decodeLength(byteArray);
     for (let i = 0; i < signaturesLength; i++) {
       signatures.push(
-        new Uint8Array(byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES)),
+        new Uint8Array(guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES)),
       );
     }
 
     const message = VersionedMessage.deserialize(new Uint8Array(byteArray));
     return new VersionedTransaction(message, signatures);
diff --git a/packages/library-legacy/src/utils/guarded-array-utils.ts b/packages/library-legacy/src/utils/guarded-array-utils.ts
new file mode 100644
index 00000000..847f1f9a
--- /dev/null
+++ b/packages/library-legacy/src/utils/guarded-array-utils.ts
@@ -0,0 +1,34 @@
+const END_OF_BUFFER_ERROR_MESSAGE = 'Reached end of buffer unexpectedly';
+
+/**
+ * Delegates to `Array#shift`, but throws if the array is zero-length.
+ */
+export function guardedShift<T>(byteArray: T[]): T {
+  if (byteArray.length === 0) {
+    throw new Error(END_OF_BUFFER_ERROR_MESSAGE);
+  }
+  return byteArray.shift() as T;
+}
+
+/**
+ * Delegates to `Array#splice`, but throws if the section being spliced out extends past the end of
+ * the array.
+ */
+export function guardedSplice<T>(
+  byteArray: T[],
+  ...args:
+    | [start: number, deleteCount?: number]
+    | [start: number, deleteCount: number, ...items: T[]]
+): T[] {
+  const [start] = args;
+  if (
+    args.length === 2 // Implies that `deleteCount` was supplied
+      ? start + (args[1] ?? 0) > byteArray.length
+      : start >= byteArray.length
+  ) {
+    throw new Error(END_OF_BUFFER_ERROR_MESSAGE);
+  }
+  return byteArray.splice(
+    ...(args as Parameters<typeof Array.prototype.splice>),
+  );
+}
diff --git a/packages/library-legacy/src/validator-info.ts b/packages/library-legacy/src/validator-info.ts
index 2412b0a7..7e681003 100644
--- a/packages/library-legacy/src/validator-info.ts
+++ b/packages/library-legacy/src/validator-info.ts
@@ -7,10 +7,11 @@ import {
 } from 'superstruct';
 
 import * as Layout from './layout';
 import * as shortvec from './utils/shortvec-encoding';
 import {PublicKey, PUBLIC_KEY_LENGTH} from './publickey';
+import {guardedShift, guardedSplice} from './utils/guarded-array-utils';
 
 export const VALIDATOR_INFO_KEY = new PublicKey(
   'Va1idator1nfo111111111111111111111111111111',
 );
 
@@ -81,12 +82,14 @@ export class ValidatorInfo {
     const configKeyCount = shortvec.decodeLength(byteArray);
     if (configKeyCount !== 2) return null;
 
     const configKeys: Array<ConfigKey> = [];
     for (let i = 0; i < 2; i++) {
-      const publicKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH));
-      const isSigner = byteArray.splice(0, 1)[0] === 1;
+      const publicKey = new PublicKey(
+        guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
+      );
+      const isSigner = guardedShift(byteArray) === 1;
       configKeys.push({publicKey, isSigner});
     }
 
     if (configKeys[0].publicKey.equals(VALIDATOR_INFO_KEY)) {
       if (configKeys[1].isSigner) {
diff --git a/packages/library-legacy/test/guarded-array-utils.test.ts b/packages/library-legacy/test/guarded-array-utils.test.ts
new file mode 100644
index 00000000..06afec81
--- /dev/null
+++ b/packages/library-legacy/test/guarded-array-utils.test.ts
@@ -0,0 +1,52 @@
+import {expect} from 'chai';
+import {spy} from 'sinon';
+
+import {guardedShift, guardedSplice} from '../src/utils/guarded-array-utils';
+
+describe('guardedShift', () => {
+  it('delegates to Array#shift', () => {
+    const arr = [1, 2, 3];
+    const shiftSpy = spy(arr, 'shift');
+    const result = guardedShift(arr);
+    expect(shiftSpy).is.calledWithExactly();
+    expect(result).to.eq(shiftSpy.returnValues[0]);
+  });
+  it('throws when the array is zero-length', () => {
+    const arr: number[] = [];
+    expect(() => guardedShift(arr)).to.throw();
+  });
+});
+
+describe('guardedSplice', () => {
+  it('delegates to Array#splice', () => {
+    const arr = [1, 2, 3];
+    const spliceSpy = spy(arr, 'splice');
+    const result = guardedSplice(
+      arr,
+      /* start */ 0,
+      /* deleteCount */ 3,
+      /* ...items */
+      100,
+      101,
+      102,
+    );
+    expect(spliceSpy).is.calledWithExactly(0, 3, 100, 101, 102);
+    expect(result).to.eq(spliceSpy.returnValues[0]);
+  });
+  it('allows zero-length splices', () => {
+    const arr: number[] = [1, 2, 3];
+    expect(guardedSplice(arr, 0, 0)).to.be.an.empty('array');
+  });
+  it('allows zero-length splices via the `deleteCount` argument being the explicit value `undefined`', () => {
+    const arr: number[] = [1, 2, 3];
+    expect(guardedSplice(arr, 0, undefined)).to.be.an.empty('array');
+  });
+  it('throws when the `start` would take you past the end of the array', () => {
+    const arr: number[] = [1, 2, 3];
+    expect(() => guardedSplice(arr, 3)).to.throw();
+  });
+  it('throws when the `deleteCount` and `start` would take you past the end of the array', () => {
+    const arr: number[] = [1, 2, 3];
+    expect(() => guardedSplice(arr, 1, 3)).to.throw();
+  });
+});

Vulnerable code:     const numRequiredSignatures = byteArray.shift()!;
    const numReadonlySignedAccounts = byteArray.shift()!;
    const numReadonlyUnsignedAccounts = byteArray.shift()!;
      const account = byteArray.splice(0, PUBLIC_KEY_LENGTH);
    const recentBlockhash = byteArray.splice(0, PUBLIC_KEY_LENGTH);
      const programIdIndex = byteArray.shift()!;
      const accounts = byteArray.splice(0, accountCount);
      const dataSlice = byteArray.splice(0, dataLength);
    const prefix = byteArray.shift() as number;
      numRequiredSignatures: byteArray.shift() as number,
      numReadonlySignedAccounts: byteArray.shift() as number,
      numReadonlyUnsignedAccounts: byteArray.shift() as number,
        new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)),
    const recentBlockhash = bs58.encode(byteArray.splice(0, PUBLIC_KEY_LENGTH));
      const programIdIndex = byteArray.shift() as number;
      const accountKeyIndexes = byteArray.splice(0, accountKeyIndexesLength);
      const data = new Uint8Array(byteArray.splice(0, dataLength));
      const accountKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH));
      const writableIndexes = byteArray.splice(0, writableIndexesLength);
      const readonlyIndexes = byteArray.splice(0, readonlyIndexesLength);
      const signature = byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES);
        new Uint8Array(byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES)),
      const publicKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH));
      const isSigner = byteArray.splice(0, 1)[0] === 1;
Fixed code: import {guardedShift, guardedSplice} from '../utils/guarded-array-utils';
    const numRequiredSignatures = guardedShift(byteArray);
    const numReadonlySignedAccounts = guardedShift(byteArray);
    const numReadonlyUnsignedAccounts = guardedShift(byteArray);
      const account = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH);
    const recentBlockhash = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH);
      const programIdIndex = guardedShift(byteArray);
      const accounts = guardedSplice(byteArray, 0, accountCount);
      const dataSlice = guardedSplice(byteArray, 0, dataLength);
import {guardedShift, guardedSplice} from '../utils/guarded-array-utils';
    const prefix = guardedShift(byteArray);
      numRequiredSignatures: guardedShift(byteArray),
      numReadonlySignedAccounts: guardedShift(byteArray),
      numReadonlyUnsignedAccounts: guardedShift(byteArray),
        new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)),
    const recentBlockhash = bs58.encode(
      guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
    );
      const programIdIndex = guardedShift(byteArray);
      const accountKeyIndexes = guardedSplice(
        byteArray,
        0,
        accountKeyIndexesLength,
      );
      const data = new Uint8Array(guardedSplice(byteArray, 0, dataLength));
      const accountKey = new PublicKey(
        guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
      );
      const writableIndexes = guardedSplice(
        byteArray,
        0,
        writableIndexesLength,
      );
      const readonlyIndexes = guardedSplice(
        byteArray,
        0,
        readonlyIndexesLength,
      );
import {guardedSplice} from '../utils/guarded-array-utils';
      const signature = guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES);
import {guardedSplice} from '../utils/guarded-array-utils';
        new Uint8Array(guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES)),
const END_OF_BUFFER_ERROR_MESSAGE = 'Reached end of buffer unexpectedly';

/**
 * Delegates to `Array#shift`, but throws if the array is zero-length.
 */
export function guardedShift<T>(byteArray: T[]): T {
  if (byteArray.length === 0) {
    throw new Error(END_OF_BUFFER_ERROR_MESSAGE);
  }
  return byteArray.shift() as T;
}

/**
 * Delegates to `Array#splice`, but throws if the section being spliced out extends past the end of
 * the array.
 */
export function guardedSplice<T>(
  byteArray: T[],
  ...args:
    | [start: number, deleteCount?: number]
    | [start: number, deleteCount: number, ...items: T[]]
): T[] {
  const [start] = args;
  if (
    args.length === 2 // Implies that `deleteCount` was supplied
      ? start + (args[1] ?? 0) > byteArray.length
      : start >= byteArray.length
  ) {
    throw new Error(END_OF_BUFFER_ERROR_MESSAGE);
  }
  return byteArray.splice(
    ...(args as Parameters<typeof Array.prototype.splice>),
  );
}
import {guardedShift, guardedSplice} from './utils/guarded-array-utils';
      const publicKey = new PublicKey(
        guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
      );
      const isSigner = guardedShift(byteArray) === 1;
import {expect} from 'chai';
import {spy} from 'sinon';

import {guardedShift, guardedSplice} from '../src/utils/guarded-array-utils';

describe('guardedShift', () => {
  it('delegates to Array#shift', () => {
    const arr = [1, 2, 3];
    const shiftSpy = spy(arr, 'shift');
    const result = guardedShift(arr);
    expect(shiftSpy).is.calledWithExactly();
    expect(result).to.eq(shiftSpy.returnValues[0]);
  });
  it('throws when the array is zero-length', () => {
    const arr: number[] = [];
    expect(() => guardedShift(arr)).to.throw();
  });
});

describe('guardedSplice', () => {
  it('delegates to Array#splice', () => {
    const arr = [1, 2, 3];
    const spliceSpy = spy(arr, 'splice');
    const result = guardedSplice(
      arr,
      /* start */ 0,
      /* deleteCount */ 3,
      /* ...items */
      100,
      101,
      102,
    );
    expect(spliceSpy).is.calledWithExactly(0, 3, 100, 101, 102);
    expect(result).to.eq(spliceSpy.returnValues[0]);
  });
  it('allows zero-length splices', () => {
    const arr: number[] = [1, 2, 3];
    expect(guardedSplice(arr, 0, 0)).to.be.an.empty('array');
  });
  it('allows zero-length splices via the `deleteCount` argument being the explicit value `undefined`', () => {
    const arr: number[] = [1, 2, 3];
    expect(guardedSplice(arr, 0, undefined)).to.be.an.empty('array');
  });
  it('throws when the `start` would take you past the end of the array', () => {
    const arr: number[] = [1, 2, 3];
    expect(() => guardedSplice(arr, 3)).to.throw();
  });
  it('throws when the `deleteCount` and `start` would take you past the end of the array', () => {
    const arr: number[] = [1, 2, 3];
    expect(() => guardedSplice(arr, 1, 3)).to.throw();
  });
});