Skip to content

Commit a80000d

Browse files
authored
fix(resolver): limit the depth of resolution (#3380)
This change eliminates the infinite recursion when handling cycles during resolution. Refs swagger-api/swagger-ui#9513
1 parent 900c2b5 commit a80000d

File tree

3 files changed

+60126
-7
lines changed

3 files changed

+60126
-7
lines changed

src/specmap/index.js

+21-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import parameters from './lib/parameters.js';
55
import properties from './lib/properties.js';
66
import ContextTree from './lib/context-tree.js';
77

8-
const HARD_LIMIT = 100;
8+
const PLUGIN_DISPATCH_LIMIT = 100;
9+
const TRAVERSE_LIMIT = 1000;
910
const noop = () => {};
1011

1112
class SpecMap {
@@ -39,6 +40,7 @@ class SpecMap {
3940
getInstance: () => this,
4041
}),
4142
allowMetaPatches: false,
43+
currentTraverseCount: 0,
4244
},
4345
opts
4446
);
@@ -70,6 +72,7 @@ class SpecMap {
7072

7173
wrapPlugin(plugin, name) {
7274
const { pathDiscriminator } = this;
75+
const that = this;
7376
let ctx = null;
7477
let fn;
7578

@@ -105,10 +108,15 @@ class SpecMap {
105108

106109
// eslint-disable-next-line no-restricted-syntax
107110
for (const patch of patches.filter(lib.isAdditiveMutation)) {
108-
yield* traverse(patch.value, patch.path, patch);
111+
if (that.currentTraverseCount < TRAVERSE_LIMIT) {
112+
yield* traverse(patch.value, patch.path, patch);
113+
} else {
114+
return;
115+
}
109116
}
110117

111118
function* traverse(obj, path, patch) {
119+
that.currentTraverseCount += 1;
112120
if (!lib.isObject(obj)) {
113121
if (pluginObj.key === path[path.length - 1]) {
114122
yield pluginObj.plugin(obj, pluginObj.key, path, specmap);
@@ -134,7 +142,11 @@ class SpecMap {
134142
if (specmap.allowMetaPatches && objRef) {
135143
refCache[objRef] = true;
136144
}
137-
yield* traverse(val, updatedPath, patch);
145+
if (that.currentTraverseCount < TRAVERSE_LIMIT) {
146+
yield* traverse(val, updatedPath, patch);
147+
} else {
148+
return;
149+
}
138150
}
139151
}
140152

@@ -313,6 +325,8 @@ class SpecMap {
313325
const that = this;
314326
const plugin = this.nextPlugin();
315327

328+
that.currentTraverseCount = 0;
329+
316330
if (!plugin) {
317331
const nextPromise = this.nextPromisedPatch();
318332
if (nextPromise) {
@@ -328,13 +342,13 @@ class SpecMap {
328342
}
329343

330344
// Makes sure plugin isn't running an endless loop
331-
that.pluginCount = that.pluginCount || {};
332-
that.pluginCount[plugin] = (that.pluginCount[plugin] || 0) + 1;
333-
if (that.pluginCount[plugin] > HARD_LIMIT) {
345+
that.pluginCount = that.pluginCount || new WeakMap();
346+
that.pluginCount.set(plugin, (that.pluginCount.get(plugin) || 0) + 1);
347+
if (that.pluginCount[plugin] > PLUGIN_DISPATCH_LIMIT) {
334348
return Promise.resolve({
335349
spec: that.state,
336350
errors: that.errors.concat(
337-
new Error(`We've reached a hard limit of ${HARD_LIMIT} plugin runs`)
351+
new Error(`We've reached a hard limit of ${PLUGIN_DISPATCH_LIMIT} plugin runs`)
338352
),
339353
});
340354
}

test/specmap/complex.js

+26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'node:path';
22
import { globSync } from 'glob';
33

44
import mapSpec, { plugins } from '../../src/specmap/index.js';
5+
import Swagger from '../../src/index.js';
56

67
const { refs } = plugins;
78
const { allOf } = plugins;
@@ -49,4 +50,29 @@ describe('complex', () => {
4950
runNextTestCase(0);
5051
});
5152
});
53+
54+
test('should partially resolve complex specs with allOf and nested references', async () => {
55+
// Given
56+
const spec = globalThis.loadJsonFile(
57+
path.join(__dirname, 'data', 'complex', 'complex-example.json')
58+
);
59+
60+
// When
61+
const result = await Swagger.resolve({ spec });
62+
63+
// Then
64+
expect(
65+
result.spec.components.schemas[
66+
'com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.StudyTreatments-create'
67+
].properties.scenario.allOf[0].$ref
68+
).toEqual(
69+
'#/components/schemas/com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.Scenarios-create'
70+
);
71+
72+
expect(
73+
result.spec.components.schemas[
74+
'com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.BlindingGroups'
75+
].properties.study.properties.scenarios.items.$$ref
76+
).toEqual('#/components/schemas/com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.Scenarios');
77+
});
5278
});

0 commit comments

Comments
 (0)