Skip to content

Commit c8574eb

Browse files
committed
limit brace expansion to 1_000 items
This is usually more than enough, and prevents a potential OOM crash that will occur with excessively large and complicated brace sections. The default was already reduced for minimatch down to 100_000, but that is still too high when dealing with the added complexities of file system walking. Also, just because the debugging for this issue was annoying without it, a custom inspect method is added to the Pattern class. closes: #533
1 parent 44e1455 commit c8574eb

5 files changed

Lines changed: 70 additions & 1 deletion

File tree

src/glob.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,15 @@ export interface GlobOptions {
339339
* @default true
340340
*/
341341
includeChildMatches?: boolean
342+
343+
/**
344+
* max number of `{...}` patterns to expand. Default `1_000`.
345+
*
346+
* Note: this is much less than minimatch's default of `100_000`,
347+
* because Glob has higher memory requirements due to walking
348+
* the file system tree.
349+
*/
350+
braceExpandMax?: number
342351
}
343352

344353
export type GlobOptionsWithFileTypesTrue = GlobOptions & {
@@ -511,11 +520,12 @@ export class Glob<Opts extends GlobOptions> implements GlobOptions {
511520
this.platform === 'darwin' || this.platform === 'win32'
512521

513522
const mmo: MinimatchOptions = {
514-
// default nocase based on platform
523+
braceExpandMax: 10_000,
515524
...opts,
516525
dot: this.dot,
517526
matchBase: this.matchBase,
518527
nobrace: this.nobrace,
528+
// default nocase based on platform
519529
nocase: this.nocase,
520530
nocaseMagicOnly,
521531
nocomment: true,

src/pattern.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const isPatternList = (pl: MMPattern[]): pl is PatternList =>
2020
pl.length >= 1
2121
const isGlobList = (gl: string[]): gl is GlobList => gl.length >= 1
2222

23+
const customInspect = Symbol.for('nodejs.util.inspect.custom')
24+
2325
/**
2426
* An immutable-ish view on an array of glob parts and their parsed
2527
* results
@@ -102,6 +104,10 @@ export class Pattern {
102104
}
103105
}
104106

107+
[customInspect]() {
108+
return 'Pattern <' + this.#globList.slice(this.#index).join('/') + '>'
109+
}
110+
105111
/**
106112
* The first entry in the parsed list of patterns
107113
*/
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* IMPORTANT
2+
* This snapshot file is auto-generated, but designed for humans.
3+
* It should be checked into source control and tracked carefully.
4+
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5+
* Make sure to inspect the output below. Do not ignore changes!
6+
*/
7+
'use strict'
8+
exports[`test/pattern.ts > TAP > g 1`] = `
9+
Pattern <**>
10+
`
11+
12+
exports[`test/pattern.ts > TAP > r 1`] = `
13+
Pattern <?>
14+
`
15+
16+
exports[`test/pattern.ts > TAP > s 1`] = `
17+
Pattern <x>
18+
`

test/oom.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import t from 'tap'
2+
import { Glob } from '../src/index.js'
3+
4+
const pattern =
5+
'{*z,x*y/z*,a*b*}' +
6+
'{*z,x*y/z*,a*b*}' +
7+
'{*z,x*y/z*,a*b*}' +
8+
'{*z,x*y/z*,a*b*}' +
9+
'{*z,x*y/z*,a*b*}' +
10+
'{*z,x*y/z*,a*b*}' +
11+
'{*z,x*y/z*,a*b*}' +
12+
'{*z,x*y/z*,a*b*}' +
13+
'{*z,x*y/z*,a*b*}' +
14+
'{*z,x*y/z*,a*b*}'
15+
16+
t.test('does not oom on long glob', async t => {
17+
const g = new Glob(pattern, { braceExpandMax: 1_000 })
18+
const results = await g.walk()
19+
20+
console.error(g)
21+
t.pass('did not run out of memory')
22+
t.equal(results.length, 0, 'should not find anything')
23+
})

test/pattern.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { GLOBSTAR } from 'minimatch'
22
import t from 'tap'
33
import { MMPattern, Pattern } from '../dist/esm/pattern.js'
44
import { Glob } from '../dist/esm/index.js'
5+
import { inspect } from 'node:util'
56

67
t.same(
78
new Glob(
@@ -32,6 +33,14 @@ t.throws(() => {
3233
new Pattern([], ['x'], 0, process.platform)
3334
})
3435

36+
const p = new Pattern(
37+
['A', 'B', 'C', 'D'],
38+
['a', 'b', 'c', 'd'],
39+
1,
40+
process.platform,
41+
)
42+
t.equal(inspect(p), 'Pattern <b/c/d>')
43+
3544
t.throws(() => {
3645
new Pattern(['x'], [], 0, process.platform)
3746
})
@@ -56,6 +65,9 @@ const g = new Pattern(
5665
process.platform,
5766
)
5867
const r = new Pattern([/./], ['?'], 0, process.platform)
68+
t.matchSnapshot(inspect(s), 's')
69+
t.matchSnapshot(inspect(r), 'r')
70+
t.matchSnapshot(inspect(g), 'g')
5971
t.equal(s.isString(), true)
6072
t.equal(g.isString(), false)
6173
t.equal(r.isString(), false)

0 commit comments

Comments
 (0)