Skip to content

Commit

Permalink
⚡️ Faster initialization of string with faster slices (#5388)
Browse files Browse the repository at this point in the history
**Description**

<!-- Please provide a short description and potentially linked issues
justifying the need for this PR -->

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.

<!-- * Your PR is fixing a bug or regression? Check for existing issues
related to this bug and link them -->
<!-- * Your PR is adding a new feature? Make sure there is a related
issue or discussion attached to it -->

<!-- You can provide any additional context to help into understanding
what's this PR is attempting to solve: reproduction of a bug, code
snippets... -->

**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)

<!-- More about contributing at
https://github.com/dubzzz/fast-check/blob/main/CONTRIBUTING.md -->

**Advanced**

<!-- How to fill the advanced section is detailed below! -->

- [x] Category: ⚡️ Improve performance
- [x] Impacts: Faster init of string arbitrary

<!-- [Category] Please use one of the categories below, it will help us
into better understanding the urgency of the PR -->
<!-- * ✨ Introduce new features -->
<!-- * 📝 Add or update documentation -->
<!-- * ✅ Add or update tests -->
<!-- * 🐛 Fix a bug -->
<!-- * 🏷️ Add or update types -->
<!-- * ⚡️ Improve performance -->
<!-- * _Other(s):_ ... -->

<!-- [Impacts] Please provide a comma separated list of the potential
impacts that might be introduced by this change -->
<!-- * Generated values: Can your change impact any of the existing
generators in terms of generated values, if so which ones? when? -->
<!-- * Shrink values: Can your change impact any of the existing
generators in terms of shrink values, if so which ones? when? -->
<!-- * Performance: Can it require some typings changes on user side?
Please give more details -->
<!-- * Typings: Is there a potential performance impact? In which cases?
-->
  • Loading branch information
dubzzz authored Oct 31, 2024
1 parent d336e2e commit 16377a8
Show file tree
Hide file tree
Showing 10 changed files with 41 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-dancers-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fast-check": minor
---

⚡️ Faster initialization of `string` with faster slices
Original file line number Diff line number Diff line change
@@ -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 ({}).*)
Expand Down Expand Up @@ -30,7 +33,7 @@ const dangerousStrings = [
];

/** @internal */
function computeCandidateString(
function computeCandidateStringLegacy(
dangerous: string,
charArbitrary: Arbitrary<string>,
stringSplitter: (value: string) => string[],
Expand All @@ -53,16 +56,31 @@ function computeCandidateString(
}

/** @internal */
export function createSlicesForString(
export function createSlicesForStringLegacy(
charArbitrary: Arbitrary<string>,
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<string>,
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;
}
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/asciiString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign;
*/
export function asciiString(constraints: StringSharedConstraints = {}): Arbitrary<string> {
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<string> = safeObjectAssign(safeObjectAssign({}, constraints), {
experimentalCustomSlices,
Expand Down
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/base64String.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -31,7 +31,7 @@ function base64String(constraints: StringSharedConstraints = {}): Arbitrary<stri
if (maxLength % 4 !== 0) throw new Error('Maximal length of base64 strings must be a multiple of 4');

const charArbitrary = base64();
const experimentalCustomSlices = createSlicesForString(charArbitrary, codePointsToStringUnmapper);
const experimentalCustomSlices = createSlicesForStringLegacy(charArbitrary, codePointsToStringUnmapper);
const enrichedConstraints: ArrayConstraintsInternal<string> = {
minLength,
maxLength,
Expand Down
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/fullUnicodeString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign;
*/
export function fullUnicodeString(constraints: StringSharedConstraints = {}): Arbitrary<string> {
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<string> = safeObjectAssign(safeObjectAssign({}, constraints), {
experimentalCustomSlices,
Expand Down
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/hexaString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign;
*/
function hexaString(constraints: StringSharedConstraints = {}): Arbitrary<string> {
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<string> = safeObjectAssign(safeObjectAssign({}, constraints), {
experimentalCustomSlices,
Expand Down
2 changes: 1 addition & 1 deletion packages/fast-check/src/arbitrary/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function extractUnitArbitrary(constraints: Pick<StringConstraints, 'unit'>): Arb
export function string(constraints: StringConstraints = {}): Arbitrary<string> {
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<string> = safeObjectAssign(safeObjectAssign({}, constraints), {
experimentalCustomSlices,
Expand Down
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/string16bits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign;
*/
export function string16bits(constraints: StringSharedConstraints = {}): Arbitrary<string> {
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<string> = safeObjectAssign(safeObjectAssign({}, constraints), {
experimentalCustomSlices,
Expand Down
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/stringOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign;
*/
export function stringOf(charArb: Arbitrary<string>, constraints: StringSharedConstraints = {}): Arbitrary<string> {
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<string> = safeObjectAssign(safeObjectAssign({}, constraints), {
experimentalCustomSlices,
Expand Down
4 changes: 2 additions & 2 deletions packages/fast-check/src/arbitrary/unicodeString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +20,7 @@ const safeObjectAssign = Object.assign;
*/
export function unicodeString(constraints: StringSharedConstraints = {}): Arbitrary<string> {
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<string> = safeObjectAssign(safeObjectAssign({}, constraints), {
experimentalCustomSlices,
Expand Down

0 comments on commit 16377a8

Please sign in to comment.