Skip to content

Commit

Permalink
fix: Avoid stack overflow when stringifying objects (#7721)
Browse files Browse the repository at this point in the history
Add special case for arrays, for compatibility with frameworks or
polyfills that add properties to Array or Array instances.

Add special case for functions, which always contain circular references
and are unexpected in this context. These seem to appear because of the
frameworks/polyfills mentioned above.

Move everything to ObjectUtils, since this is extremely generic.

Closes #7435
  • Loading branch information
joeyparrish committed Dec 11, 2024
1 parent 58537b4 commit 8318911
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 35 deletions.
83 changes: 83 additions & 0 deletions lib/util/object_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,87 @@ shaka.util.ObjectUtils = class {
}
return clone;
}


/**
* Constructs a string out of a value, similar to the JSON.stringify method.
* Unlike that method, this guarantees that the order of the keys in an
* object is alphabetical, so it can be used as a way to reliably compare two
* objects.
*
* @param {?} value
* @return {string}
*/
static alphabeticalKeyOrderStringify(value) {
if (Array.isArray(value)) {
return shaka.util.ObjectUtils.arrayStringify_(value);
} else if (typeof value == 'function') {
// For safety, skip functions. For function x,
// x.prototype.constructor.prototype === x.prototype, so all functions
// contain circular references if treated like Objects.
return '';
} else if (value instanceof Object) {
return shaka.util.ObjectUtils.objectStringify_(value);
} else {
return JSON.stringify(value);
}
}


/**
* Helper for alphabeticalKeyOrderStringify for objects.
*
* @param {!Object} obj
* @return {string}
* @private
*/
static objectStringify_(obj) {
// NOTE: This excludes prototype chain keys. For now, this is intended for
// anonymous objects only, so we don't care. If that changes, go back to a
// for-in loop.
const keys = Object.keys(obj);
// Alphabetically sort the keys, so they will be in a reliable order.
keys.sort();

const terms = [];
for (const key of keys) {
const escapedKey = JSON.stringify(key);
const value = obj[key];
if (value !== undefined) {
const escapedValue =
shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
if (escapedValue) {
terms.push(escapedKey + ':' + escapedValue);
}
}
}
return '{' + terms.join(',') + '}';
}


/**
* Helper for alphabeticalKeyOrderStringify for arrays.
*
* This could itself be JSON.stringify, except we want objects within the
* array to go through our own stringifiers.
*
* @param {!Array} arr
* @return {string}
* @private
*/
static arrayStringify_(arr) {
const terms = [];
for (let index = 0; index < arr.length; index++) {
const escapedKey = index.toString();
const value = arr[index];
if (value !== undefined) {
const escapedValue =
shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(value);
if (escapedValue) {
terms.push(escapedKey + ':' + escapedValue);
}
}
}
return '[' + terms.join(',') + ']';
}
};
36 changes: 1 addition & 35 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,40 +545,6 @@ shaka.util.StreamUtils = class {
}


/**
* Constructs a string out of an object, similar to the JSON.stringify method.
* Unlike that method, this guarantees that the order of the keys is
* alphabetical, so it can be used as a way to reliably compare two objects.
*
* @param {!Object} obj
* @return {string}
* @private
*/
static alphabeticalKeyOrderStringify_(obj) {
const keys = [];
for (const key in obj) {
keys.push(key);
}
// Alphabetically sort the keys, so they will be in a reliable order.
keys.sort();

const terms = [];
for (const key of keys) {
const escapedKey = JSON.stringify(key);
const value = obj[key];
if (value instanceof Object) {
const stringifiedValue =
shaka.util.StreamUtils.alphabeticalKeyOrderStringify_(value);
terms.push(escapedKey + ':' + stringifiedValue);
} else {
const escapedValue = JSON.stringify(value);
terms.push(escapedKey + ':' + escapedValue);
}
}
return '{' + terms.join(',') + '}';
}


/**
* Queries mediaCapabilities for the decoding info for that decoding config,
* and assigns it to the given variant.
Expand Down Expand Up @@ -614,7 +580,7 @@ shaka.util.StreamUtils = class {
const promises = [];
for (const decodingConfig of decodingConfigs) {
const cacheKey =
StreamUtils.alphabeticalKeyOrderStringify_(decodingConfig);
shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(decodingConfig);

const cache = StreamUtils.decodingConfigCache_;
if (cache[cacheKey]) {
Expand Down

0 comments on commit 8318911

Please sign in to comment.