From 16377a83623cd16ed51747db6699dc91f7f3c7ac Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Thu, 31 Oct 2024 22:41:08 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Faster=20initialization=20?= =?UTF-8?q?of=20`string`=20with=20faster=20slices=20(#5388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Description** When initializing the `string` arbitrary, we first check what are the known-to-be-vulnerable-strings that we could possibly generate for this instance. As such we need to take any known-to-be-vulnerable-string and check if it could have been generated by the string builder we are planning to build. This operation is pretty costly as it implies a backtracking algorithm possibly failing and retrying multiple times. The change introduced by this PR reduces the overhead linked to this computation by dropping part of the operations that were done twice. **Checklist** — _Don't delete this checklist and make sure you do the following before opening the PR_ - [x] The name of my PR follows [gitmoji](https://gitmoji.dev/) specification - [x] My PR references one of several related issues (if any) - [x] New features or breaking changes must come with an associated Issue or Discussion - [x] My PR does not add any new dependency without an associated Issue or Discussion - [x] My PR includes bumps details, please run `yarn bump` and flag the impacts properly - [x] My PR adds relevant tests and they would have failed without my PR (when applicable) **Advanced** - [x] Category: ⚡️ Improve performance - [x] Impacts: Faster init of string arbitrary --- .changeset/swift-dancers-drum.md | 5 ++++ .../helpers/SlicesForStringBuilder.ts | 24 ++++++++++++++++--- .../fast-check/src/arbitrary/asciiString.ts | 4 ++-- .../fast-check/src/arbitrary/base64String.ts | 4 ++-- .../src/arbitrary/fullUnicodeString.ts | 4 ++-- .../fast-check/src/arbitrary/hexaString.ts | 4 ++-- packages/fast-check/src/arbitrary/string.ts | 2 +- .../fast-check/src/arbitrary/string16bits.ts | 4 ++-- packages/fast-check/src/arbitrary/stringOf.ts | 4 ++-- .../fast-check/src/arbitrary/unicodeString.ts | 4 ++-- 10 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 .changeset/swift-dancers-drum.md diff --git a/.changeset/swift-dancers-drum.md b/.changeset/swift-dancers-drum.md new file mode 100644 index 00000000000..cfee731d16d --- /dev/null +++ b/.changeset/swift-dancers-drum.md @@ -0,0 +1,5 @@ +--- +"fast-check": minor +--- + +⚡️ Faster initialization of `string` with faster slices diff --git a/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts b/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts index ae4cf24642a..c386c99b4e1 100644 --- a/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts +++ b/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts @@ -1,5 +1,8 @@ import type { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary'; import { safePush } from '../../../utils/globals'; +import type { StringSharedConstraints } from '../../_shared/StringSharedConstraints'; +import { patternsToStringUnmapperIsValidLength } from '../mappers/PatternsToString'; +import { tokenizeString } from './TokenizeString'; const dangerousStrings = [ // Default attributes on raw Object (from ({}).*) @@ -30,7 +33,7 @@ const dangerousStrings = [ ]; /** @internal */ -function computeCandidateString( +function computeCandidateStringLegacy( dangerous: string, charArbitrary: Arbitrary, stringSplitter: (value: string) => string[], @@ -53,16 +56,31 @@ function computeCandidateString( } /** @internal */ -export function createSlicesForString( +export function createSlicesForStringLegacy( charArbitrary: Arbitrary, stringSplitter: (value: string) => string[], ): string[][] { const slicesForString: string[][] = []; for (const dangerous of dangerousStrings) { - const candidate = computeCandidateString(dangerous, charArbitrary, stringSplitter); + const candidate = computeCandidateStringLegacy(dangerous, charArbitrary, stringSplitter); if (candidate !== undefined) { safePush(slicesForString, candidate); } } return slicesForString; } + +/** @internal */ +export function createSlicesForString( + charArbitrary: Arbitrary, + constraints: StringSharedConstraints, +): string[][] { + const slicesForString: string[][] = []; + for (const dangerous of dangerousStrings) { + const candidate = tokenizeString(charArbitrary, dangerous); + if (candidate !== undefined && patternsToStringUnmapperIsValidLength(candidate, constraints)) { + safePush(slicesForString, candidate); + } + } + return slicesForString; +} diff --git a/packages/fast-check/src/arbitrary/asciiString.ts b/packages/fast-check/src/arbitrary/asciiString.ts index 627ceec1ba2..412daa259de 100644 --- a/packages/fast-check/src/arbitrary/asciiString.ts +++ b/packages/fast-check/src/arbitrary/asciiString.ts @@ -4,7 +4,7 @@ import { array } from './array'; import { ascii } from './ascii'; import type { StringSharedConstraints } from './_shared/StringSharedConstraints'; import { codePointsToStringMapper, codePointsToStringUnmapper } from './_internals/mappers/CodePointsToString'; -import { createSlicesForString } from './_internals/helpers/SlicesForStringBuilder'; +import { createSlicesForStringLegacy } from './_internals/helpers/SlicesForStringBuilder'; export type { StringSharedConstraints } from './_shared/StringSharedConstraints'; const safeObjectAssign = Object.assign; @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign; */ export function asciiString(constraints: StringSharedConstraints = {}): Arbitrary { const charArbitrary = ascii(); - const experimentalCustomSlices = createSlicesForString(charArbitrary, codePointsToStringUnmapper); + const experimentalCustomSlices = createSlicesForStringLegacy(charArbitrary, codePointsToStringUnmapper); // TODO - Move back to object spreading as soon as we bump support from es2017 to es2018+ const enrichedConstraints: ArrayConstraintsInternal = safeObjectAssign(safeObjectAssign({}, constraints), { experimentalCustomSlices, diff --git a/packages/fast-check/src/arbitrary/base64String.ts b/packages/fast-check/src/arbitrary/base64String.ts index fcfcd0112ff..2d36131a236 100644 --- a/packages/fast-check/src/arbitrary/base64String.ts +++ b/packages/fast-check/src/arbitrary/base64String.ts @@ -6,7 +6,7 @@ import { MaxLengthUpperBound } from './_internals/helpers/MaxLengthFromMinLength import type { StringSharedConstraints } from './_shared/StringSharedConstraints'; import { codePointsToStringMapper, codePointsToStringUnmapper } from './_internals/mappers/CodePointsToString'; import { stringToBase64Mapper, stringToBase64Unmapper } from './_internals/mappers/StringToBase64'; -import { createSlicesForString } from './_internals/helpers/SlicesForStringBuilder'; +import { createSlicesForStringLegacy } from './_internals/helpers/SlicesForStringBuilder'; export type { StringSharedConstraints } from './_shared/StringSharedConstraints'; /** @@ -31,7 +31,7 @@ function base64String(constraints: StringSharedConstraints = {}): Arbitrary = { minLength, maxLength, diff --git a/packages/fast-check/src/arbitrary/fullUnicodeString.ts b/packages/fast-check/src/arbitrary/fullUnicodeString.ts index 542bf9b4920..b911acbd098 100644 --- a/packages/fast-check/src/arbitrary/fullUnicodeString.ts +++ b/packages/fast-check/src/arbitrary/fullUnicodeString.ts @@ -4,7 +4,7 @@ import { array } from './array'; import { fullUnicode } from './fullUnicode'; import type { StringSharedConstraints } from './_shared/StringSharedConstraints'; import { codePointsToStringMapper, codePointsToStringUnmapper } from './_internals/mappers/CodePointsToString'; -import { createSlicesForString } from './_internals/helpers/SlicesForStringBuilder'; +import { createSlicesForStringLegacy } from './_internals/helpers/SlicesForStringBuilder'; export type { StringSharedConstraints } from './_shared/StringSharedConstraints'; const safeObjectAssign = Object.assign; @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign; */ export function fullUnicodeString(constraints: StringSharedConstraints = {}): Arbitrary { const charArbitrary = fullUnicode(); - const experimentalCustomSlices = createSlicesForString(charArbitrary, codePointsToStringUnmapper); + const experimentalCustomSlices = createSlicesForStringLegacy(charArbitrary, codePointsToStringUnmapper); // TODO - Move back to object spreading as soon as we bump support from es2017 to es2018+ const enrichedConstraints: ArrayConstraintsInternal = safeObjectAssign(safeObjectAssign({}, constraints), { experimentalCustomSlices, diff --git a/packages/fast-check/src/arbitrary/hexaString.ts b/packages/fast-check/src/arbitrary/hexaString.ts index 4ecc6c014aa..b9ed24b3852 100644 --- a/packages/fast-check/src/arbitrary/hexaString.ts +++ b/packages/fast-check/src/arbitrary/hexaString.ts @@ -4,7 +4,7 @@ import { array } from './array'; import { hexa } from './hexa'; import type { StringSharedConstraints } from './_shared/StringSharedConstraints'; import { codePointsToStringMapper, codePointsToStringUnmapper } from './_internals/mappers/CodePointsToString'; -import { createSlicesForString } from './_internals/helpers/SlicesForStringBuilder'; +import { createSlicesForStringLegacy } from './_internals/helpers/SlicesForStringBuilder'; export type { StringSharedConstraints } from './_shared/StringSharedConstraints'; const safeObjectAssign = Object.assign; @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign; */ function hexaString(constraints: StringSharedConstraints = {}): Arbitrary { const charArbitrary = hexa(); - const experimentalCustomSlices = createSlicesForString(charArbitrary, codePointsToStringUnmapper); + const experimentalCustomSlices = createSlicesForStringLegacy(charArbitrary, codePointsToStringUnmapper); // TODO - Move back to object spreading as soon as we bump support from es2017 to es2018+ const enrichedConstraints: ArrayConstraintsInternal = safeObjectAssign(safeObjectAssign({}, constraints), { experimentalCustomSlices, diff --git a/packages/fast-check/src/arbitrary/string.ts b/packages/fast-check/src/arbitrary/string.ts index c251011903b..065e5fd4ed0 100644 --- a/packages/fast-check/src/arbitrary/string.ts +++ b/packages/fast-check/src/arbitrary/string.ts @@ -68,7 +68,7 @@ function extractUnitArbitrary(constraints: Pick): Arb export function string(constraints: StringConstraints = {}): Arbitrary { const charArbitrary = extractUnitArbitrary(constraints); const unmapper = patternsToStringUnmapperFor(charArbitrary, constraints); - const experimentalCustomSlices = createSlicesForString(charArbitrary, unmapper); + const experimentalCustomSlices = createSlicesForString(charArbitrary, constraints); // TODO - Move back to object spreading as soon as we bump support from es2017 to es2018+ const enrichedConstraints: ArrayConstraintsInternal = safeObjectAssign(safeObjectAssign({}, constraints), { experimentalCustomSlices, diff --git a/packages/fast-check/src/arbitrary/string16bits.ts b/packages/fast-check/src/arbitrary/string16bits.ts index afb40c6eea5..4a5539cd2dd 100644 --- a/packages/fast-check/src/arbitrary/string16bits.ts +++ b/packages/fast-check/src/arbitrary/string16bits.ts @@ -4,7 +4,7 @@ import { array } from './array'; import { char16bits } from './char16bits'; import type { StringSharedConstraints } from './_shared/StringSharedConstraints'; import { charsToStringMapper, charsToStringUnmapper } from './_internals/mappers/CharsToString'; -import { createSlicesForString } from './_internals/helpers/SlicesForStringBuilder'; +import { createSlicesForStringLegacy } from './_internals/helpers/SlicesForStringBuilder'; export type { StringSharedConstraints } from './_shared/StringSharedConstraints'; const safeObjectAssign = Object.assign; @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign; */ export function string16bits(constraints: StringSharedConstraints = {}): Arbitrary { const charArbitrary = char16bits(); - const experimentalCustomSlices = createSlicesForString(charArbitrary, charsToStringUnmapper); + const experimentalCustomSlices = createSlicesForStringLegacy(charArbitrary, charsToStringUnmapper); // TODO - Move back to object spreading as soon as we bump support from es2017 to es2018+ const enrichedConstraints: ArrayConstraintsInternal = safeObjectAssign(safeObjectAssign({}, constraints), { experimentalCustomSlices, diff --git a/packages/fast-check/src/arbitrary/stringOf.ts b/packages/fast-check/src/arbitrary/stringOf.ts index b561b372c20..8f1872e8eba 100644 --- a/packages/fast-check/src/arbitrary/stringOf.ts +++ b/packages/fast-check/src/arbitrary/stringOf.ts @@ -3,7 +3,7 @@ import type { ArrayConstraintsInternal } from './array'; import { array } from './array'; import type { StringSharedConstraints } from './_shared/StringSharedConstraints'; import { patternsToStringMapper, patternsToStringUnmapperFor } from './_internals/mappers/PatternsToString'; -import { createSlicesForString } from './_internals/helpers/SlicesForStringBuilder'; +import { createSlicesForStringLegacy } from './_internals/helpers/SlicesForStringBuilder'; export type { StringSharedConstraints } from './_shared/StringSharedConstraints'; const safeObjectAssign = Object.assign; @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign; */ export function stringOf(charArb: Arbitrary, constraints: StringSharedConstraints = {}): Arbitrary { const unmapper = patternsToStringUnmapperFor(charArb, constraints); - const experimentalCustomSlices = createSlicesForString(charArb, unmapper); + const experimentalCustomSlices = createSlicesForStringLegacy(charArb, unmapper); // TODO - Move back to object spreading as soon as we bump support from es2017 to es2018+ const enrichedConstraints: ArrayConstraintsInternal = safeObjectAssign(safeObjectAssign({}, constraints), { experimentalCustomSlices, diff --git a/packages/fast-check/src/arbitrary/unicodeString.ts b/packages/fast-check/src/arbitrary/unicodeString.ts index dd25e6d7a54..5eb542e350f 100644 --- a/packages/fast-check/src/arbitrary/unicodeString.ts +++ b/packages/fast-check/src/arbitrary/unicodeString.ts @@ -4,7 +4,7 @@ import { array } from './array'; import { unicode } from './unicode'; import type { StringSharedConstraints } from './_shared/StringSharedConstraints'; import { codePointsToStringMapper, codePointsToStringUnmapper } from './_internals/mappers/CodePointsToString'; -import { createSlicesForString } from './_internals/helpers/SlicesForStringBuilder'; +import { createSlicesForStringLegacy } from './_internals/helpers/SlicesForStringBuilder'; export type { StringSharedConstraints } from './_shared/StringSharedConstraints'; const safeObjectAssign = Object.assign; @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign; */ export function unicodeString(constraints: StringSharedConstraints = {}): Arbitrary { const charArbitrary = unicode(); - const experimentalCustomSlices = createSlicesForString(charArbitrary, codePointsToStringUnmapper); + const experimentalCustomSlices = createSlicesForStringLegacy(charArbitrary, codePointsToStringUnmapper); // TODO - Move back to object spreading as soon as we bump support from es2017 to es2018+ const enrichedConstraints: ArrayConstraintsInternal = safeObjectAssign(safeObjectAssign({}, constraints), { experimentalCustomSlices,