Skip to content

Commit

Permalink
fix(resolver): limit the depth of resolution (#3380)
Browse files Browse the repository at this point in the history
This change eliminates the infinite recursion when
handling cycles during resolution.

Refs swagger-api/swagger-ui#9513
  • Loading branch information
glowcloud authored Feb 22, 2024
1 parent 900c2b5 commit a80000d
Show file tree
Hide file tree
Showing 3 changed files with 60,126 additions and 7 deletions.
28 changes: 21 additions & 7 deletions src/specmap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import parameters from './lib/parameters.js';
import properties from './lib/properties.js';
import ContextTree from './lib/context-tree.js';

const HARD_LIMIT = 100;
const PLUGIN_DISPATCH_LIMIT = 100;
const TRAVERSE_LIMIT = 1000;
const noop = () => {};

class SpecMap {
Expand Down Expand Up @@ -39,6 +40,7 @@ class SpecMap {
getInstance: () => this,
}),
allowMetaPatches: false,
currentTraverseCount: 0,
},
opts
);
Expand Down Expand Up @@ -70,6 +72,7 @@ class SpecMap {

wrapPlugin(plugin, name) {
const { pathDiscriminator } = this;
const that = this;
let ctx = null;
let fn;

Expand Down Expand Up @@ -105,10 +108,15 @@ class SpecMap {

// eslint-disable-next-line no-restricted-syntax
for (const patch of patches.filter(lib.isAdditiveMutation)) {
yield* traverse(patch.value, patch.path, patch);
if (that.currentTraverseCount < TRAVERSE_LIMIT) {
yield* traverse(patch.value, patch.path, patch);
} else {
return;
}
}

function* traverse(obj, path, patch) {
that.currentTraverseCount += 1;
if (!lib.isObject(obj)) {
if (pluginObj.key === path[path.length - 1]) {
yield pluginObj.plugin(obj, pluginObj.key, path, specmap);
Expand All @@ -134,7 +142,11 @@ class SpecMap {
if (specmap.allowMetaPatches && objRef) {
refCache[objRef] = true;
}
yield* traverse(val, updatedPath, patch);
if (that.currentTraverseCount < TRAVERSE_LIMIT) {
yield* traverse(val, updatedPath, patch);
} else {
return;
}
}
}

Expand Down Expand Up @@ -313,6 +325,8 @@ class SpecMap {
const that = this;
const plugin = this.nextPlugin();

that.currentTraverseCount = 0;

if (!plugin) {
const nextPromise = this.nextPromisedPatch();
if (nextPromise) {
Expand All @@ -328,13 +342,13 @@ class SpecMap {
}

// Makes sure plugin isn't running an endless loop
that.pluginCount = that.pluginCount || {};
that.pluginCount[plugin] = (that.pluginCount[plugin] || 0) + 1;
if (that.pluginCount[plugin] > HARD_LIMIT) {
that.pluginCount = that.pluginCount || new WeakMap();
that.pluginCount.set(plugin, (that.pluginCount.get(plugin) || 0) + 1);
if (that.pluginCount[plugin] > PLUGIN_DISPATCH_LIMIT) {
return Promise.resolve({
spec: that.state,
errors: that.errors.concat(
new Error(`We've reached a hard limit of ${HARD_LIMIT} plugin runs`)
new Error(`We've reached a hard limit of ${PLUGIN_DISPATCH_LIMIT} plugin runs`)
),
});
}
Expand Down
26 changes: 26 additions & 0 deletions test/specmap/complex.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'node:path';
import { globSync } from 'glob';

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

const { refs } = plugins;
const { allOf } = plugins;
Expand Down Expand Up @@ -49,4 +50,29 @@ describe('complex', () => {
runNextTestCase(0);
});
});

test('should partially resolve complex specs with allOf and nested references', async () => {
// Given
const spec = globalThis.loadJsonFile(
path.join(__dirname, 'data', 'complex', 'complex-example.json')
);

// When
const result = await Swagger.resolve({ spec });

// Then
expect(
result.spec.components.schemas[
'com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.StudyTreatments-create'
].properties.scenario.allOf[0].$ref
).toEqual(
'#/components/schemas/com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.Scenarios-create'
);

expect(
result.spec.components.schemas[
'com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.BlindingGroups'
].properties.study.properties.scenarios.items.$$ref
).toEqual('#/components/schemas/com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.Scenarios');
});
});
Loading

0 comments on commit a80000d

Please sign in to comment.