Skip to content

Commit

Permalink
fix(es/minifier): Abort inliner on mutation via property (#8938)
Browse files Browse the repository at this point in the history
**Related issue:**

 - Closes #8937
  • Loading branch information
kdy1 authored May 9, 2024
1 parent 94da124 commit 257afc9
Show file tree
Hide file tree
Showing 20 changed files with 123 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,10 @@ export default function S(u) {
},
getSearchParameters: R,
onSearchForFacetValues: function(a) {
var n = a.facetName, s = a.query, i = a.maxFacetHits;
var n = a.facetName, s = a.query, i = a.maxFacetHits, c = Math.max(1, Math.min(void 0 === i ? 10 : i, 100));
A.setState(r(t({}, A.getState()), {
searchingForFacetValues: !0
})), w.searchForFacetValues(n, s, Math.max(1, Math.min(void 0 === i ? 10 : i, 100))).then(function(a) {
})), w.searchForFacetValues(n, s, c).then(function(a) {
var i;
A.setState(r(t({}, A.getState()), {
error: null,
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_minifier/src/compress/optimize/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ impl Optimizer<'_> {
if v_usage.reassigned
|| v_usage.property_mutation_count
> usage.property_mutation_count
|| v_usage.has_property_access
{
return;
}
Expand Down
54 changes: 31 additions & 23 deletions crates/swc_ecma_minifier/tests/benches-full/echarts.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions crates/swc_ecma_minifier/tests/benches-full/lodash.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
'rearg',
256
]
], argsTag = '[object Arguments]', arrayTag = '[object Array]', boolTag = '[object Boolean]', dateTag = '[object Date]', errorTag = '[object Error]', funcTag = '[object Function]', genTag = '[object GeneratorFunction]', mapTag = '[object Map]', numberTag = '[object Number]', objectTag = '[object Object]', promiseTag = '[object Promise]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', symbolTag = '[object Symbol]', weakMapTag = '[object WeakMap]', arrayBufferTag = '[object ArrayBuffer]', dataViewTag = '[object DataView]', float32Tag = '[object Float32Array]', float64Tag = '[object Float64Array]', int8Tag = '[object Int8Array]', int16Tag = '[object Int16Array]', int32Tag = '[object Int32Array]', uint8Tag = '[object Uint8Array]', uint8ClampedTag = '[object Uint8ClampedArray]', uint16Tag = '[object Uint16Array]', uint32Tag = '[object Uint32Array]', reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g, reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, reUnescapedHtml = /[&<>"']/g, reHasEscapedHtml = RegExp(reEscapedHtml.source), reHasUnescapedHtml = RegExp(reUnescapedHtml.source), reEscape = /<%-([\s\S]+?)%>/g, reEvaluate = /<%([\s\S]+?)%>/g, reInterpolate = /<%=([\s\S]+?)%>/g, reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, reIsPlainProp = /^\w*$/, rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g, reRegExpChar = /[\\^$.*+?()[\]{}|]/g, reHasRegExpChar = RegExp(reRegExpChar.source), reTrimStart = /^\s+/, reWhitespace = /\s/, reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, reSplitDetails = /,? & /, reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g, reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/, reEscapeChar = /\\(\\)?/g, reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g, reFlags = /\w*$/, reIsBadHex = /^[-+]0x[0-9a-f]+$/i, reIsBinary = /^0b[01]+$/i, reIsHostCtor = /^\[object .+?Constructor\]$/, reIsOctal = /^0o[0-7]+$/i, reIsUint = /^(?:0|[1-9]\d*)$/, reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g, reNoMatch = /($^)/, reUnescapedString = /['\n\r\u2028\u2029\\]/g, rsAstralRange = '\\ud800-\\udfff', rsComboRange = "\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff", rsDingbatRange = '\\u2700-\\u27bf', rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', rsVarRange = '\\ufe0e\\ufe0f', rsBreakRange = "\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000", rsApos = "['\u2019]", rsBreak = '[' + rsBreakRange + ']', rsCombo = '[' + rsComboRange + ']', rsLower = '[' + rsLowerRange + ']', rsMisc = '[^' + rsAstralRange + rsBreakRange + '\\d+' + rsDingbatRange + rsLowerRange + rsUpperRange + ']', rsFitz = '\\ud83c[\\udffb-\\udfff]', rsNonAstral = '[^' + rsAstralRange + ']', rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', rsUpper = '[' + rsUpperRange + ']', rsZWJ = '\\u200d', rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', reOptMod = '(?:' + rsCombo + '|' + rsFitz + ")?", rsOptVar = '[' + rsVarRange + ']?', rsOptJoin = '(?:' + rsZWJ + '(?:' + [
], argsTag = '[object Arguments]', arrayTag = '[object Array]', boolTag = '[object Boolean]', dateTag = '[object Date]', errorTag = '[object Error]', funcTag = '[object Function]', genTag = '[object GeneratorFunction]', mapTag = '[object Map]', numberTag = '[object Number]', objectTag = '[object Object]', promiseTag = '[object Promise]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', symbolTag = '[object Symbol]', weakMapTag = '[object WeakMap]', arrayBufferTag = '[object ArrayBuffer]', dataViewTag = '[object DataView]', float32Tag = '[object Float32Array]', float64Tag = '[object Float64Array]', int8Tag = '[object Int8Array]', int16Tag = '[object Int16Array]', int32Tag = '[object Int32Array]', uint8Tag = '[object Uint8Array]', uint8ClampedTag = '[object Uint8ClampedArray]', uint16Tag = '[object Uint16Array]', uint32Tag = '[object Uint32Array]', reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g, reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, reUnescapedHtml = /[&<>"']/g, reHasEscapedHtml = RegExp(reEscapedHtml.source), reHasUnescapedHtml = RegExp(reUnescapedHtml.source), reEscape = /<%-([\s\S]+?)%>/g, reEvaluate = /<%([\s\S]+?)%>/g, reInterpolate = /<%=([\s\S]+?)%>/g, reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, reIsPlainProp = /^\w*$/, rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g, reRegExpChar = /[\\^$.*+?()[\]{}|]/g, reHasRegExpChar = RegExp(reRegExpChar.source), reTrimStart = /^\s+/, reWhitespace = /\s/, reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, reSplitDetails = /,? & /, reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g, reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/, reEscapeChar = /\\(\\)?/g, reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g, reFlags = /\w*$/, reIsBadHex = /^[-+]0x[0-9a-f]+$/i, reIsBinary = /^0b[01]+$/i, reIsHostCtor = /^\[object .+?Constructor\]$/, reIsOctal = /^0o[0-7]+$/i, reIsUint = /^(?:0|[1-9]\d*)$/, reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g, reNoMatch = /($^)/, reUnescapedString = /['\n\r\u2028\u2029\\]/g, rsAstralRange = '\\ud800-\\udfff', rsComboRange = "\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff", rsDingbatRange = '\\u2700-\\u27bf', rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', rsVarRange = '\\ufe0e\\ufe0f', rsBreakRange = "\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000", rsApos = "['\u2019]", rsBreak = '[' + rsBreakRange + ']', rsCombo = '[' + rsComboRange + ']', rsLower = '[' + rsLowerRange + ']', rsMisc = '[^' + rsAstralRange + rsBreakRange + '\\d+' + rsDingbatRange + rsLowerRange + rsUpperRange + ']', rsFitz = '\\ud83c[\\udffb-\\udfff]', rsNonAstral = '[^' + rsAstralRange + ']', rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', rsUpper = '[' + rsUpperRange + ']', rsZWJ = '\\u200d', rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')', rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', reOptMod = '(?:' + rsCombo + '|' + rsFitz + ")?", rsOptVar = '[' + rsVarRange + ']?', rsOptJoin = '(?:' + rsZWJ + '(?:' + [
rsNonAstral,
rsRegional,
rsSurrPair
Expand All @@ -56,7 +56,7 @@
rsUpper,
'$'
].join('|') + ')',
'(?:' + rsUpper + '|' + rsMisc + ")+" + rsOptContrUpper + '(?=' + [
rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [
rsBreak,
rsUpper + rsMiscLower,
'$'
Expand Down
8 changes: 5 additions & 3 deletions crates/swc_ecma_minifier/tests/benches-full/three.js
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,9 @@
return 2 * Math.acos(Math.abs(MathUtils.clamp(this.dot(q), -1, 1)));
}, _proto.rotateTowards = function(q, step) {
var angle = this.angleTo(q);
return 0 === angle || this.slerp(q, Math.min(1, step / angle)), this;
if (0 === angle) return this;
var t = Math.min(1, step / angle);
return this.slerp(q, t), this;
}, _proto.identity = function() {
return this.set(0, 0, 0, 1);
}, _proto.invert = function() {
Expand Down Expand Up @@ -12117,8 +12119,8 @@
}
function HemisphereLightProbe(skyColor, groundColor, intensity) {
LightProbe.call(this, void 0, intensity);
var color1 = new Color().set(skyColor), color2 = new Color().set(groundColor), sky = new Vector3(color1.r, color1.g, color1.b), ground = new Vector3(color2.r, color2.g, color2.b), c0 = Math.sqrt(Math.PI);
this.sh.coefficients[0].copy(sky).add(ground).multiplyScalar(c0), this.sh.coefficients[1].copy(sky).sub(ground).multiplyScalar(c0 * Math.sqrt(0.75));
var color1 = new Color().set(skyColor), color2 = new Color().set(groundColor), sky = new Vector3(color1.r, color1.g, color1.b), ground = new Vector3(color2.r, color2.g, color2.b), c0 = Math.sqrt(Math.PI), c1 = c0 * Math.sqrt(0.75);
this.sh.coefficients[0].copy(sky).add(ground).multiplyScalar(c0), this.sh.coefficients[1].copy(sky).sub(ground).multiplyScalar(c1);
}
function AmbientLightProbe(color, intensity) {
LightProbe.call(this, void 0, intensity);
Expand Down
4 changes: 2 additions & 2 deletions crates/swc_ecma_minifier/tests/benches-full/victory.js
Original file line number Diff line number Diff line change
Expand Up @@ -22674,9 +22674,9 @@
setAnimationState: function(props, nextProps) {
if (props.animate) {
if (props.animate.parentState) {
var nodesWillExit = props.animate.parentState.nodesWillExit;
var oldProps = props.animate.parentState.nodesWillExit ? props : null;
this.setState(lodash_defaults__WEBPACK_IMPORTED_MODULE_8___default()({
oldProps: nodesWillExit ? props : null,
oldProps: oldProps,
nextProps: nextProps
}, props.animate.parentState));
} else {
Expand Down
41 changes: 21 additions & 20 deletions crates/swc_ecma_minifier/tests/benches-full/vue.js
Original file line number Diff line number Diff line change
Expand Up @@ -1722,6 +1722,26 @@
handleError(e, vnode.context, "directive " + dir.name + " " + hook + " hook");
}
}
var baseModules = [
{
create: function(_, vnode) {
registerRef(vnode);
},
update: function(oldVnode, vnode) {
oldVnode.data.ref !== vnode.data.ref && (registerRef(oldVnode, !0), registerRef(vnode));
},
destroy: function(vnode) {
registerRef(vnode, !0);
}
},
{
create: updateDirectives,
update: updateDirectives,
destroy: function(vnode) {
updateDirectives(vnode, emptyNode);
}
}
];
function updateAttrs(oldVnode, vnode) {
var key, cur, opts = vnode.componentOptions;
if (!(isDef(opts) && !1 === opts.Ctor.options.inheritAttrs || isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs))) {
Expand Down Expand Up @@ -2434,26 +2454,7 @@
!0 !== vnode.data.show ? leave(vnode, rm) : rm();
}
} : {}
].concat([
{
create: function(_, vnode) {
registerRef(vnode);
},
update: function(oldVnode, vnode) {
oldVnode.data.ref !== vnode.data.ref && (registerRef(oldVnode, !0), registerRef(vnode));
},
destroy: function(vnode) {
registerRef(vnode, !0);
}
},
{
create: updateDirectives,
update: updateDirectives,
destroy: function(vnode) {
updateDirectives(vnode, emptyNode);
}
}
])
].concat(baseModules)
});
isIE9 && document.addEventListener('selectionchange', function() {
var el = document.activeElement;
Expand Down
27 changes: 27 additions & 0 deletions crates/swc_ecma_minifier/tests/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11223,3 +11223,30 @@ console.log(eval(bar));
console.log(eval(foo));",
);
}

#[test]
fn issue_8937() {
run_default_exec_test(
"
class Container {
constructor(v) {
this.a= v;
}
add(x) {
this.a += x;
}
toString() {
return this.a.toString();
}
};
let x = Math.random();
let a = new Container(x);
let b = new Container(x+1);
let comp = a < b;
while (a < b) {
a.add(1);
}
console.log(comp ? 'smaller' : 'not smaller');
",
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5290,11 +5290,11 @@
},
5607: function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
"use strict";
var $ = __webpack_require__(35437), IndexedObject = __webpack_require__(51478), toIndexedObject = __webpack_require__(74981), arrayMethodIsStrict = __webpack_require__(12707), nativeJoin = [].join, STRICT_METHOD = arrayMethodIsStrict("join", ",");
var $ = __webpack_require__(35437), IndexedObject = __webpack_require__(51478), toIndexedObject = __webpack_require__(74981), arrayMethodIsStrict = __webpack_require__(12707), nativeJoin = [].join, ES3_STRINGS = IndexedObject != Object, STRICT_METHOD = arrayMethodIsStrict("join", ",");
$({
target: "Array",
proto: !0,
forced: IndexedObject != Object || !STRICT_METHOD
forced: ES3_STRINGS || !STRICT_METHOD
}, {
join: function(separator) {
return nativeJoin.call(toIndexedObject(this), void 0 === separator ? "," : separator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@
return it.fromMillis(t.getTime());
}
static fromMillis(t) {
const e = Math.floor(t / 1e3);
return new it(e, Math.floor(1e6 * (t - 1e3 * e)));
const e = Math.floor(t / 1e3), n = Math.floor(1e6 * (t - 1e3 * e));
return new it(e, n);
}
toDate() {
return new Date(this.toMillis());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6724,7 +6724,10 @@
classCallCheck_default()(this, CanvasContainer), defineProperty_default()(this, "ctx", void 0), defineProperty_default()(this, "dom", void 0), this.ctx = new QuaggaContext_CanvasInfo(), this.dom = new QuaggaContext_CanvasInfo();
}, barcode_locator = __webpack_require__(23);
function getViewPort_getViewPort(target) {
return "undefined" == typeof document ? null : target instanceof HTMLElement && target.nodeName && 1 === target.nodeType ? target : document.querySelector("string" == typeof target ? target : "#interactive.viewport");
if ("undefined" == typeof document) return null;
if (target instanceof HTMLElement && target.nodeName && 1 === target.nodeType) return target;
var selector = "string" == typeof target ? target : "#interactive.viewport";
return document.querySelector(selector);
}
function getCanvasAndContext(selector, className) {
var canvas, canvas1 = ((canvas = document.querySelector(selector)) || ((canvas = document.createElement("canvas")).className = className), canvas), context = canvas1.getContext("2d");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,11 @@ export default function createInstantSearchManager({ indexName, initialState = {
},
getSearchParameters,
onSearchForFacetValues: function({ facetName, query, maxFacetHits = 10 }) {
const maxFacetHitsWithinRange = Math.max(1, Math.min(maxFacetHits, 100));
store.setState({
...store.getState(),
searchingForFacetValues: !0
}), helper.searchForFacetValues(facetName, query, Math.max(1, Math.min(maxFacetHits, 100))).then((content)=>{
}), helper.searchForFacetValues(facetName, query, maxFacetHitsWithinRange).then((content)=>{
store.setState({
...store.getState(),
error: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,11 @@ export default function createInstantSearchManager({ indexName, initialState = {
},
getSearchParameters,
onSearchForFacetValues: function({ facetName, query, maxFacetHits = 10 }) {
const maxFacetHitsWithinRange = Math.max(1, Math.min(maxFacetHits, 100));
store.setState({
...store.getState(),
searchingForFacetValues: !0
}), helper.searchForFacetValues(facetName, query, Math.max(1, Math.min(maxFacetHits, 100))).then((content)=>{
}), helper.searchForFacetValues(facetName, query, maxFacetHitsWithinRange).then((content)=>{
store.setState({
...store.getState(),
error: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,10 @@ export default function createInstantSearchManager(param) {
},
getSearchParameters: getSearchParameters,
onSearchForFacetValues: function(param) {
var facetName = param.facetName, query = param.query, _maxFacetHits = param.maxFacetHits;
var facetName = param.facetName, query = param.query, _maxFacetHits = param.maxFacetHits, maxFacetHitsWithinRange = Math.max(1, Math.min(void 0 === _maxFacetHits ? 10 : _maxFacetHits, 100));
store.setState(swcHelpers.objectSpread({}, store.getState(), {
searchingForFacetValues: !0
})), helper.searchForFacetValues(facetName, query, Math.max(1, Math.min(void 0 === _maxFacetHits ? 10 : _maxFacetHits, 100))).then(function(content) {
})), helper.searchForFacetValues(facetName, query, maxFacetHitsWithinRange).then(function(content) {
store.setState(swcHelpers.objectSpread({}, store.getState(), {
error: null,
searchingForFacetValues: !1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2426,8 +2426,8 @@
}(Button);
PlayToggle.prototype.controlText_ = "Play", Component$1.registerComponent("PlayToggle", PlayToggle);
var defaultImplementation = function(seconds, guide) {
var s = Math.floor((seconds = seconds < 0 ? 0 : seconds) % 60), m = Math.floor(seconds / 60 % 60), h = Math.floor(seconds / 3600);
return (isNaN(seconds) || seconds === 1 / 0) && (h = m = s = "-"), m = (((h = h > 0 || Math.floor(guide / 3600) > 0 ? h + ":" : "") || Math.floor(guide / 60 % 60) >= 10) && m < 10 ? "0" + m : m) + ":", h + m + (s = s < 10 ? "0" + s : s);
var s = Math.floor((seconds = seconds < 0 ? 0 : seconds) % 60), m = Math.floor(seconds / 60 % 60), h = Math.floor(seconds / 3600), gm = Math.floor(guide / 60 % 60), gh = Math.floor(guide / 3600);
return (isNaN(seconds) || seconds === 1 / 0) && (h = m = s = "-"), m = (((h = h > 0 || gh > 0 ? h + ":" : "") || gm >= 10) && m < 10 ? "0" + m : m) + ":", h + m + (s = s < 10 ? "0" + s : s);
}, implementation = defaultImplementation;
function formatTime(seconds, guide) {
return void 0 === guide && (guide = seconds), implementation(seconds, guide);
Expand Down
Loading

0 comments on commit 257afc9

Please sign in to comment.