diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc8e25da..6a020d89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: jobs: nodejs: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: node-version: @@ -33,7 +33,7 @@ jobs: - run: codecov -f coverage/*.json browser: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: browser: [ChromeHeadless, FirefoxHeadless] diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 8b39600a..257e9ae4 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-node@v2 with: cache: npm - node-version: "16" + node-version: "18" - run: npm ci - run: npm run test:fuzz diff --git a/CHANGELOG.md b/CHANGELOG.md index e8fc7433..f1e84b6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # This is the revision history of @msgpack/msgpack +## NEXT + +* Let `Encoder#encode()` return a copy of the internal buffer, instead of the reference of the buffer (fix #212). + * Introducing `Encoder#encodeSharedRef()` to return the shared reference to the internal buffer. + ## 2.7.2 2022/02/08 https://github.com/msgpack/msgpack-javascript/compare/v2.7.1...v2.7.2 diff --git a/package.json b/package.json index d125a84f..d66dc9b5 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:cover:purejs": "npx nyc --no-clean npm run test:purejs", "test:cover:te": "npx nyc --no-clean npm run test:te", "test:deno": "deno test test/deno_test.ts", - "test:fuzz": "npm exec -- jsfuzz@git+https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz.git --fuzzTime 60 --no-versifier test/decode.jsfuzz.js corpus", + "test:fuzz": "npm exec --yes -- jsfuzz@git+https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz.git --fuzzTime 60 --no-versifier test/decode.jsfuzz.js corpus", "cover:clean": "rimraf .nyc_output coverage/", "cover:report": "npx nyc report --reporter=text-summary --reporter=html --reporter=json", "test:browser": "karma start --single-run", diff --git a/src/Encoder.ts b/src/Encoder.ts index afea365c..14a16322 100644 --- a/src/Encoder.ts +++ b/src/Encoder.ts @@ -23,18 +23,28 @@ export class Encoder { private readonly forceIntegerToFloat = false, ) {} - private getUint8Array(): Uint8Array { - return this.bytes.subarray(0, this.pos); - } - private reinitializeState() { this.pos = 0; } + /** + * This is almost equivalent to {@link Encoder#encode}, but it returns an reference of the encoder's internal buffer and thus much faster than {@link Encoder#encode}. + * + * @returns Encodes the object and returns a shared reference the encoder's internal buffer. + */ + public encodeSharedRef(object: unknown): Uint8Array { + this.reinitializeState(); + this.doEncode(object, 1); + return this.bytes.subarray(0, this.pos); + } + + /** + * @returns Encodes the object and returns a copy of the encoder's internal buffer. + */ public encode(object: unknown): Uint8Array { this.reinitializeState(); this.doEncode(object, 1); - return this.getUint8Array(); + return this.bytes.slice(0, this.pos); } private doEncode(object: unknown, depth: number): void { diff --git a/src/encode.ts b/src/encode.ts index 07d59ad9..7e6a602e 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -77,5 +77,5 @@ export function encode( options.ignoreUndefined, options.forceIntegerToFloat, ); - return encoder.encode(value); + return encoder.encodeSharedRef(value); } diff --git a/test/reuse-instances.test.ts b/test/reuse-instances.test.ts index faa397c8..29a0b786 100644 --- a/test/reuse-instances.test.ts +++ b/test/reuse-instances.test.ts @@ -1,5 +1,5 @@ import { deepStrictEqual } from "assert"; -import { Encoder, Decoder } from "@msgpack/msgpack"; +import { Encoder, Decoder, decode } from "@msgpack/msgpack"; const createStream = async function* (...args: any) { for (const item of args) { @@ -108,5 +108,47 @@ describe("shared instances", () => { deepStrictEqual(a, [[object]], `#${i}`); } }); + + context("regression #212", () => { + it("runs multiple times", () => { + const encoder = new Encoder(); + const decoder = new Decoder(); + + const data1 = { + isCommunication: false, + isWarning: false, + alarmId: "619f65a2774abf00568b7210", + intervalStart: "2022-05-20T12:00:00.000Z", + intervalStop: "2022-05-20T13:00:00.000Z", + triggeredAt: "2022-05-20T13:00:00.000Z", + component: "someComponent", + _id: "6287920245a582301475627d", + }; + + const data2 = { + foo: "bar", + }; + + const arr = [data1, data2]; + const enc = arr.map((x) => [x, encoder.encode(x)] as const); + + enc.forEach(([orig, acc]) => { + const des = decoder.decode(acc); + deepStrictEqual(des, orig); + }); + }); + }); + + context("Encoder#encodeSharedRef()", () => { + it("returns the shared reference", () => { + const encoder = new Encoder(); + + const a = encoder.encodeSharedRef(true); + const b = encoder.encodeSharedRef(false); + + deepStrictEqual(decode(a), decode(b)); // yes, this is the expected behavior + deepStrictEqual(a.buffer, b.buffer); + }); + }); }); });