diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/js/buffer.ts | 46 | ||||
-rw-r--r-- | cli/tests/unit/buffer_test.ts | 102 |
2 files changed, 134 insertions, 14 deletions
diff --git a/cli/js/buffer.ts b/cli/js/buffer.ts index 5c304c127..3308a9a0c 100644 --- a/cli/js/buffer.ts +++ b/cli/js/buffer.ts @@ -11,7 +11,7 @@ import { assert } from "./util.ts"; // buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond // what is required to hold the contents of r, readFrom() will not grow the // underlying buffer. -const MIN_READ = 512; +const MIN_READ = 32 * 1024; const MAX_SIZE = 2 ** 32 - 2; // `off` is the offset into `dst` where it will at which to begin writing values @@ -133,17 +133,17 @@ export class Buffer implements Reader, ReaderSync, Writer, WriterSync { // we instead let capacity get twice as large so we // don't spend all our time copying. copyBytes(this.#buf.subarray(this.#off), this.#buf); - } else if (c > MAX_SIZE - c - n) { + } else if (c + n > MAX_SIZE) { throw new Error("The buffer cannot be grown beyond the maximum size."); } else { // Not enough space anywhere, we need to allocate. - const buf = new Uint8Array(2 * c + n); + const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); copyBytes(this.#buf.subarray(this.#off), buf); this.#buf = buf; } // Restore this.#off and len(this.#buf). this.#off = 0; - this.#reslice(m + n); + this.#reslice(Math.min(m + n, MAX_SIZE)); return m; }; @@ -157,30 +157,48 @@ export class Buffer implements Reader, ReaderSync, Writer, WriterSync { async readFrom(r: Reader): Promise<number> { let n = 0; + const tmp = new Uint8Array(MIN_READ); while (true) { - const i = this.#grow(MIN_READ); - this.#reslice(i); - const fub = new Uint8Array(this.#buf.buffer, i); - const nread = await r.read(fub); + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = await r.read(buf); if (nread === null) { return n; } - this.#reslice(i + nread); + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + n += nread; } } readFromSync(r: ReaderSync): number { let n = 0; + const tmp = new Uint8Array(MIN_READ); while (true) { - const i = this.#grow(MIN_READ); - this.#reslice(i); - const fub = new Uint8Array(this.#buf.buffer, i); - const nread = r.readSync(fub); + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = r.readSync(buf); if (nread === null) { return n; } - this.#reslice(i + nread); + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + n += nread; } } diff --git a/cli/tests/unit/buffer_test.ts b/cli/tests/unit/buffer_test.ts index dc80c9c6d..440dd5495 100644 --- a/cli/tests/unit/buffer_test.ts +++ b/cli/tests/unit/buffer_test.ts @@ -11,11 +11,19 @@ import { unitTest, } from "./test_util.ts"; +const MAX_SIZE = 2 ** 32 - 2; // N controls how many iterations of certain checks are performed. const N = 100; let testBytes: Uint8Array | null; let testString: string | null; +let ignoreMaxSizeTests = false; +try { + new ArrayBuffer(MAX_SIZE); +} catch (e) { + ignoreMaxSizeTests = true; +} + function init(): void { if (testBytes == null) { testBytes = new Uint8Array(N); @@ -167,6 +175,100 @@ unitTest(async function bufferTooLargeByteWrites(): Promise<void> { ); }); +unitTest( + { ignore: ignoreMaxSizeTests }, + function bufferGrowWriteMaxBuffer(): void { + const bufSize = 16 * 1024; + const capacities = [MAX_SIZE, MAX_SIZE - 1]; + for (const capacity of capacities) { + let written = 0; + const buf = new Deno.Buffer(); + const writes = Math.floor(capacity / bufSize); + for (let i = 0; i < writes; i++) + written += buf.writeSync(repeat("x", bufSize)); + + if (written < capacity) { + written += buf.writeSync(repeat("x", capacity - written)); + } + + assertEquals(written, capacity); + } + } +); + +unitTest( + { ignore: ignoreMaxSizeTests }, + async function bufferGrowReadCloseMaxBufferPlus1(): Promise<void> { + const reader = new Deno.Buffer(new ArrayBuffer(MAX_SIZE + 1)); + const buf = new Deno.Buffer(); + + await assertThrowsAsync( + async () => { + await buf.readFrom(reader); + }, + Error, + "grown beyond the maximum size" + ); + } +); + +unitTest( + { ignore: ignoreMaxSizeTests }, + function bufferGrowReadSyncCloseMaxBufferPlus1(): void { + const reader = new Deno.Buffer(new ArrayBuffer(MAX_SIZE + 1)); + const buf = new Deno.Buffer(); + + assertThrows( + () => { + buf.readFromSync(reader); + }, + Error, + "grown beyond the maximum size" + ); + } +); + +unitTest( + { ignore: ignoreMaxSizeTests }, + function bufferGrowReadSyncCloseToMaxBuffer(): void { + const capacities = [MAX_SIZE, MAX_SIZE - 1]; + for (const capacity of capacities) { + const reader = new Deno.Buffer(new ArrayBuffer(capacity)); + const buf = new Deno.Buffer(); + buf.readFromSync(reader); + + assertEquals(buf.length, capacity); + } + } +); + +unitTest( + { ignore: ignoreMaxSizeTests }, + async function bufferGrowReadCloseToMaxBuffer(): Promise<void> { + const capacities = [MAX_SIZE, MAX_SIZE - 1]; + for (const capacity of capacities) { + const reader = new Deno.Buffer(new ArrayBuffer(capacity)); + const buf = new Deno.Buffer(); + await buf.readFrom(reader); + assertEquals(buf.length, capacity); + } + } +); + +unitTest( + { ignore: ignoreMaxSizeTests }, + async function bufferReadCloseToMaxBufferWithInitialGrow(): Promise<void> { + const capacities = [MAX_SIZE, MAX_SIZE - 1, MAX_SIZE - 512]; + for (const capacity of capacities) { + const reader = new Deno.Buffer(new ArrayBuffer(capacity)); + const buf = new Deno.Buffer(); + buf.grow(MAX_SIZE); + await buf.readFrom(reader); + assertEquals(buf.length, capacity); + } + } +); + unitTest(async function bufferLargeByteReads(): Promise<void> { init(); assert(testBytes); |