зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changesets b61921a307e9 and e9e74f6bd12a (bug 1054759) for breaking web compat by implementing Symbol.unscopables without Array.prototype[@@unscopables].
This commit is contained in:
Родитель
04e76674fe
Коммит
b4a736d2a2
|
@ -1,37 +0,0 @@
|
|||
// An Environment for a `with` statement does not observe bindings ruled out by @@unscopables.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`
|
||||
let x = 'global';
|
||||
function f() {
|
||||
let obj = {
|
||||
x: 'obj',
|
||||
y: 'obj',
|
||||
[Symbol.unscopables]: {x: 1},
|
||||
};
|
||||
with (obj)
|
||||
debugger;
|
||||
}
|
||||
`);
|
||||
let dbg = Debugger(g);
|
||||
let hits = 0;
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
let env = frame.environment;
|
||||
|
||||
assertEq(env.find("x") !== env, true);
|
||||
assertEq(env.names().indexOf("x"), -1);
|
||||
assertEq(env.getVariable("x"), undefined);
|
||||
assertThrowsInstanceOf(() => env.setVariable("x", 7), TypeError);
|
||||
|
||||
assertEq(env.find("y") === env, true);
|
||||
assertEq(env.getVariable("y"), "obj");
|
||||
env.setVariable("y", 8);
|
||||
|
||||
assertEq(frame.eval("x").return, "global");
|
||||
assertEq(frame.eval("y").return, 8);
|
||||
hits++;
|
||||
};
|
||||
g.f();
|
||||
assertEq(hits, 1);
|
|
@ -4715,25 +4715,17 @@ JS_PUBLIC_API(JSString*)
|
|||
GetSymbolDescription(HandleSymbol symbol);
|
||||
|
||||
/* Well-known symbols. */
|
||||
#define JS_FOR_EACH_WELL_KNOWN_SYMBOL(macro) \
|
||||
macro(iterator) \
|
||||
macro(match) \
|
||||
macro(species) \
|
||||
macro(toPrimitive) \
|
||||
macro(unscopables)
|
||||
|
||||
enum class SymbolCode : uint32_t {
|
||||
// There is one SymbolCode for each well-known symbol.
|
||||
#define JS_DEFINE_SYMBOL_ENUM(name) name,
|
||||
JS_FOR_EACH_WELL_KNOWN_SYMBOL(JS_DEFINE_SYMBOL_ENUM) // SymbolCode::iterator, etc.
|
||||
#undef JS_DEFINE_SYMBOL_ENUM
|
||||
Limit,
|
||||
iterator, // well-known symbols
|
||||
match,
|
||||
species,
|
||||
toPrimitive,
|
||||
InSymbolRegistry = 0xfffffffe, // created by Symbol.for() or JS::GetSymbolFor()
|
||||
UniqueSymbol = 0xffffffff // created by Symbol() or JS::NewSymbol()
|
||||
};
|
||||
|
||||
/* For use in loops that iterate over the well-known symbols. */
|
||||
const size_t WellKnownSymbolLimit = size_t(SymbolCode::Limit);
|
||||
const size_t WellKnownSymbolLimit = 4;
|
||||
|
||||
/**
|
||||
* Return the SymbolCode telling what sort of symbol `symbol` is.
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
// Basics of @@unscopables support.
|
||||
|
||||
// In with(obj), if obj[@@unscopables][id] is truthy, then the identifier id
|
||||
// is not present as a binding in the with-block's scope.
|
||||
var x = "global";
|
||||
with ({x: "with", [Symbol.unscopables]: {x: true}})
|
||||
assertEq(x, "global");
|
||||
|
||||
// But if obj[@@unscopables][id] is false or not present, there is a binding.
|
||||
with ({y: "with", z: "with", [Symbol.unscopables]: {y: false}}) {
|
||||
assertEq(y, "with");
|
||||
assertEq(z, "with");
|
||||
}
|
||||
|
||||
// ToBoolean(obj[@@unscopables][id]) determines whether there's a binding.
|
||||
let someValues = [0, -0, NaN, "", undefined, null, "x", {}, []];
|
||||
for (let v of someValues) {
|
||||
with ({x: "with", [Symbol.unscopables]: {x: v}})
|
||||
assertEq(x, v ? "global" : "with");
|
||||
}
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,23 +0,0 @@
|
|||
// @@unscopables continues to work after exiting the relevant `with` block,
|
||||
// if the environment is captured by a closure.
|
||||
|
||||
let env = {
|
||||
x: 9000,
|
||||
[Symbol.unscopables]: {x: true}
|
||||
};
|
||||
|
||||
function make_adder(x) {
|
||||
with (env)
|
||||
return function (y) { return x + y; };
|
||||
}
|
||||
assertEq(make_adder(3)(10), 13);
|
||||
|
||||
// Same test, but with a bunch of different parts for bad luck
|
||||
let x = 500;
|
||||
function make_adder_with_eval() {
|
||||
with (env)
|
||||
return eval('y => eval("x + y")');
|
||||
}
|
||||
assertEq(make_adder_with_eval()(10), 510);
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,8 +0,0 @@
|
|||
// @@unscopables prevents a property from having any effect on assigning to a
|
||||
// const binding (which is an error).
|
||||
|
||||
const x = 1;
|
||||
with ({x: 1, [Symbol.unscopables]: {x: true}})
|
||||
assertThrowsInstanceOf(() => {x = 2;}, TypeError);
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,27 +0,0 @@
|
|||
// If obj[@@unscopables][id], then `delete id` works across `with (obj)` scope.
|
||||
|
||||
this.niche = 7;
|
||||
let obj = { niche: 8, [Symbol.unscopables]: { niche: true } };
|
||||
with (obj) {
|
||||
delete niche;
|
||||
}
|
||||
|
||||
assertEq(obj.niche, 8);
|
||||
assertEq("niche" in this, false);
|
||||
|
||||
// Same thing, but delete a variable introduced by sloppy direct eval.
|
||||
this.niche = 9;
|
||||
function f() {
|
||||
eval("var niche = 10;");
|
||||
with (obj) {
|
||||
assertEq(niche, 10);
|
||||
delete niche;
|
||||
}
|
||||
assertEq(niche, 9);
|
||||
}
|
||||
|
||||
// Of course none of this affects a qualified delete.
|
||||
assertEq(delete this.niche, true);
|
||||
assertEq("niche" in this, false);
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,41 +0,0 @@
|
|||
// @@unscopables checks can call getters.
|
||||
|
||||
// The @@unscopables property itself can be a getter.
|
||||
let hit1 = 0;
|
||||
let x = "global x";
|
||||
let env1 = {
|
||||
x: "env1.x",
|
||||
get [Symbol.unscopables]() {
|
||||
hit1++;
|
||||
return {x: true};
|
||||
}
|
||||
};
|
||||
with (env1)
|
||||
assertEq(x, "global x");
|
||||
assertEq(hit1, 1);
|
||||
|
||||
// It can throw; the exception is propagated out.
|
||||
function Fit() {}
|
||||
with ({x: 0, get [Symbol.unscopables]() { throw new Fit; }})
|
||||
assertThrowsInstanceOf(() => x, Fit);
|
||||
|
||||
// Individual properties on the @@unscopables object can have getters.
|
||||
let hit2 = 0;
|
||||
let env2 = {
|
||||
x: "env2.x",
|
||||
[Symbol.unscopables]: {
|
||||
get x() {
|
||||
hit2++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
with (env2)
|
||||
assertEq(x, "global x");
|
||||
assertEq(hit2, 1);
|
||||
|
||||
// And they can throw.
|
||||
with ({x: 0, [Symbol.unscopables]: {get x() { throw new Fit; }}})
|
||||
assertThrowsInstanceOf(() => x, Fit);
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,18 +0,0 @@
|
|||
// @@unscopables does not affect the global environment.
|
||||
|
||||
this.x = "global property x";
|
||||
let y = "global lexical y";
|
||||
this[Symbol.unscopables] = {x: true, y: true};
|
||||
assertEq(x, "global property x");
|
||||
assertEq(y, "global lexical y");
|
||||
assertEq(eval("x"), "global property x");
|
||||
assertEq(eval("y"), "global lexical y");
|
||||
|
||||
// But it does affect `with` statements targeting the global object.
|
||||
{
|
||||
let x = "local x";
|
||||
with (this)
|
||||
assertEq(x, "local x");
|
||||
}
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,22 +0,0 @@
|
|||
// In these cases, @@unscopables should not be consulted.
|
||||
|
||||
// Because obj has no properties `assertEq` or `x`,
|
||||
// obj[@@unscopables] is not checked here:
|
||||
var obj = {
|
||||
get [Symbol.unscopables]() {
|
||||
throw "tried to read @@unscopables";
|
||||
}
|
||||
};
|
||||
var x = 3;
|
||||
with (obj)
|
||||
assertEq(x, 3);
|
||||
|
||||
// If @@unscopables is present but not an object, it is ignored:
|
||||
for (let nonObject of [undefined, null, "nothing", Symbol.for("moon")]) {
|
||||
let y = 4;
|
||||
let obj2 = {[Symbol.unscopables]: nonObject, y: 5};
|
||||
with (obj2)
|
||||
assertEq(y, 5);
|
||||
}
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,7 +0,0 @@
|
|||
// Trying to access a binding that doesn't exist due to @@unscopables
|
||||
// is a ReferenceError.
|
||||
|
||||
with ({x: 1, [Symbol.unscopables]: {x: true}})
|
||||
assertThrowsInstanceOf(() => x, ReferenceError);
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,18 +0,0 @@
|
|||
// When env[@@unscopables].x changes, bindings can appear even if env is inextensible.
|
||||
|
||||
let x = "global";
|
||||
let unscopables = {x: true};
|
||||
let env = Object.create(null);
|
||||
env[Symbol.unscopables] = unscopables;
|
||||
env.x = "object";
|
||||
Object.freeze(env);
|
||||
|
||||
for (let i = 0; i < 1004; i++) {
|
||||
if (i === 1000)
|
||||
unscopables.x = false;
|
||||
with (env) {
|
||||
assertEq(x, i < 1000 ? "global" : "object");
|
||||
}
|
||||
}
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,44 +0,0 @@
|
|||
// When obj[@@unscopables].x changes, bindings appear and disappear accordingly.
|
||||
|
||||
let x = "global";
|
||||
function getX() { return x; }
|
||||
|
||||
let unscopables = {x: true};
|
||||
let obj = {x: "obj", [Symbol.unscopables]: unscopables};
|
||||
|
||||
with (obj) {
|
||||
assertEq(x, "global");
|
||||
x = "global-1";
|
||||
assertEq(x, "global-1");
|
||||
assertEq(obj.x, "obj");
|
||||
|
||||
unscopables.x = false; // suddenly x appears in the with-environment
|
||||
|
||||
assertEq(x, "obj");
|
||||
x = "obj-1";
|
||||
assertEq(getX(), "global-1"); // unchanged
|
||||
assertEq(obj.x, "obj-1");
|
||||
|
||||
unscopables.x = true; // *poof*
|
||||
|
||||
assertEq(x, "global-1");
|
||||
x = "global-2";
|
||||
assertEq(getX(), "global-2");
|
||||
assertEq(obj.x, "obj-1"); // unchanged
|
||||
|
||||
// The determination of which binding is assigned happens when the LHS of
|
||||
// assignment is evaluated, before the RHS. This is observable if we make
|
||||
// the binding appear or disappear during evaluation of the RHS, before
|
||||
// assigning.
|
||||
x = (unscopables.x = false, "global-3");
|
||||
assertEq(getX(), "global-3");
|
||||
assertEq(obj.x, "obj-1");
|
||||
|
||||
x = (unscopables.x = true, "obj-2");
|
||||
assertEq(getX(), "global-3");
|
||||
assertEq(obj.x, "obj-2");
|
||||
}
|
||||
|
||||
assertEq(x, "global-3");
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,39 +0,0 @@
|
|||
// @@unscopables treats properties found on prototype chains the same as other
|
||||
// properties.
|
||||
|
||||
const x = "global x";
|
||||
const y = "global y";
|
||||
|
||||
// obj[@@unscopables].x works when obj.x is inherited via the prototype chain.
|
||||
let proto = {x: "object x", y: "object y"};
|
||||
let env = Object.create(proto);
|
||||
env[Symbol.unscopables] = {x: true, y: false};
|
||||
with (env) {
|
||||
assertEq(x, "global x");
|
||||
assertEq(delete x, false);
|
||||
assertEq(y, "object y");
|
||||
}
|
||||
assertEq(env.x, "object x");
|
||||
|
||||
// @@unscopables works if is inherited via the prototype chain.
|
||||
env = {
|
||||
x: "object",
|
||||
[Symbol.unscopables]: {x: true, y: true}
|
||||
};
|
||||
for (let i = 0; i < 50; i++)
|
||||
env = Object.create(env);
|
||||
env.y = 1;
|
||||
with (env) {
|
||||
assertEq(x, "global x");
|
||||
assertEq(y, "global y");
|
||||
}
|
||||
|
||||
// @@unscopables works if the obj[@@unscopables][id] property is inherited.
|
||||
env = {
|
||||
x: "object",
|
||||
[Symbol.unscopables]: Object.create({x: true})
|
||||
};
|
||||
with (env)
|
||||
assertEq(x, "global x");
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,46 +0,0 @@
|
|||
// Object operations are performed in the right order, as observed by proxies.
|
||||
|
||||
let log = [];
|
||||
function LoggingProxyHandlerWrapper(name, handler={}) {
|
||||
return new Proxy(handler, {
|
||||
get(t, id) {
|
||||
let method = handler[id];
|
||||
return function (...args) {
|
||||
log.push([name + "." + id, ...args.filter(v => typeof v !== "object")]);
|
||||
if (method === undefined)
|
||||
return Reflect[id].apply(null, args);
|
||||
return method.apply(this, args);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function LoggingProxy(name, target) {
|
||||
return new Proxy(target, new LoggingProxyHandlerWrapper(name));
|
||||
}
|
||||
|
||||
let proto = {x: 44};
|
||||
let proto_proxy = new LoggingProxy("proto", proto);
|
||||
let unscopables = {x: true};
|
||||
let unscopables_proxy = new LoggingProxy("unscopables", {x: true});
|
||||
let env = Object.create(proto_proxy, {
|
||||
[Symbol.unscopables]: { value: unscopables_proxy }
|
||||
});
|
||||
let env_proxy = new LoggingProxy("env", env);
|
||||
|
||||
let x = 11;
|
||||
function f() {
|
||||
with (env_proxy)
|
||||
return x;
|
||||
}
|
||||
|
||||
assertEq(f(), 11);
|
||||
|
||||
assertDeepEq(log, [
|
||||
["env.has", "x"],
|
||||
["proto.has", "x"],
|
||||
["env.get", Symbol.unscopables],
|
||||
["unscopables.get", "x"]
|
||||
]);
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,32 +0,0 @@
|
|||
// Strict assignment to the name of a property that's masked by @@unscopables
|
||||
// throws a ReferenceError.
|
||||
|
||||
let env = {k: 1};
|
||||
let f;
|
||||
with (env) {
|
||||
f = function () {
|
||||
"use strict";
|
||||
k = 2;
|
||||
};
|
||||
}
|
||||
|
||||
f();
|
||||
assertEq(env.k, 2);
|
||||
|
||||
env[Symbol.unscopables] = {k: true};
|
||||
assertThrowsInstanceOf(f, ReferenceError);
|
||||
|
||||
// @@unscopables is tested when the LHS of assignment is evaluated, so there is
|
||||
// no effect on the assignment if it is changed while evaluating the RHS.
|
||||
let g;
|
||||
with (env) {
|
||||
g = function () {
|
||||
"use strict";
|
||||
k = (env[Symbol.unscopables].k = true, 3);
|
||||
}
|
||||
}
|
||||
env[Symbol.unscopables].k = false;
|
||||
g();
|
||||
assertEq(env.k, 3);
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -1,9 +0,0 @@
|
|||
// Accessing an uninitialized variable due to @@unscopables is still a ReferenceError.
|
||||
|
||||
with ({x: 1, [Symbol.unscopables]: {x: true}})
|
||||
assertThrowsInstanceOf(() => x, ReferenceError);
|
||||
|
||||
let x;
|
||||
|
||||
reportCompare(0, 0);
|
||||
|
|
@ -5,8 +5,6 @@ var names = [
|
|||
"iterator",
|
||||
"match",
|
||||
"species",
|
||||
"toPrimitive",
|
||||
"unscopables"
|
||||
];
|
||||
|
||||
for (var name of names) {
|
||||
|
|
|
@ -293,7 +293,6 @@
|
|||
macro(match, match, "match") \
|
||||
macro(species, species, "species") \
|
||||
macro(toPrimitive, toPrimitive, "toPrimitive") \
|
||||
macro(unscopables, unscopables, "unscopables") \
|
||||
/* Same goes for the descriptions of the well-known symbols. */ \
|
||||
macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
|
||||
macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
|
||||
|
@ -301,6 +300,7 @@
|
|||
macro(Symbol_match, Symbol_match, "Symbol.match") \
|
||||
macro(Symbol_species, Symbol_species, "Symbol.species") \
|
||||
macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
|
||||
macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \
|
||||
macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
|
||||
/* Function names for properties named by symbols. */ \
|
||||
macro(Symbol_iterator_fun, Symbol_iterator_fun, "[Symbol.iterator]") \
|
||||
|
|
|
@ -8866,14 +8866,6 @@ DebuggerEnv_getVariable(JSContext* cx, unsigned argc, Value* vp)
|
|||
/* This can trigger getters. */
|
||||
ErrorCopier ec(ac);
|
||||
|
||||
bool found;
|
||||
if (!HasProperty(cx, env, id, &found))
|
||||
return false;
|
||||
if (!found) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// For DebugScopeObjects, we get sentinel values for optimized out
|
||||
// slots and arguments instead of throwing (the default behavior).
|
||||
//
|
||||
|
|
|
@ -447,9 +447,10 @@ namespace js {
|
|||
*/
|
||||
struct WellKnownSymbols
|
||||
{
|
||||
#define DECLARE_SYMBOL(name) js::ImmutableSymbolPtr name;
|
||||
JS_FOR_EACH_WELL_KNOWN_SYMBOL(DECLARE_SYMBOL)
|
||||
#undef DECLARE_SYMBOL
|
||||
js::ImmutableSymbolPtr iterator;
|
||||
js::ImmutableSymbolPtr match;
|
||||
js::ImmutableSymbolPtr species;
|
||||
js::ImmutableSymbolPtr toPrimitive;
|
||||
|
||||
const ImmutableSymbolPtr& get(size_t u) const {
|
||||
MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
|
||||
|
|
|
@ -755,26 +755,6 @@ DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclo
|
|||
return obj;
|
||||
}
|
||||
|
||||
/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
|
||||
static bool
|
||||
CheckUnscopables(JSContext *cx, HandleObject obj, HandleId id, bool *scopable)
|
||||
{
|
||||
RootedId unscopablesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols()
|
||||
.get(JS::SymbolCode::unscopables)));
|
||||
RootedValue v(cx);
|
||||
if (!GetProperty(cx, obj, obj, unscopablesId, &v))
|
||||
return false;
|
||||
if (v.isObject()) {
|
||||
RootedObject unscopablesObj(cx, &v.toObject());
|
||||
if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v))
|
||||
return false;
|
||||
*scopable = !ToBoolean(v);
|
||||
} else {
|
||||
*scopable = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
|
||||
MutableHandleObject objp, MutableHandleShape propp)
|
||||
|
@ -785,19 +765,7 @@ with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
|
|||
return true;
|
||||
}
|
||||
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
|
||||
if (!LookupProperty(cx, actual, id, objp, propp))
|
||||
return false;
|
||||
|
||||
if (propp) {
|
||||
bool scopable;
|
||||
if (!CheckUnscopables(cx, actual, id, &scopable))
|
||||
return false;
|
||||
if (!scopable) {
|
||||
objp.set(nullptr);
|
||||
propp.set(nullptr);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return LookupProperty(cx, actual, id, objp, propp);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -814,15 +782,7 @@ with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
|
|||
{
|
||||
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
|
||||
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
|
||||
|
||||
// ES 8.1.1.2.1 step 3-5.
|
||||
if (!HasProperty(cx, actual, id, foundp))
|
||||
return false;
|
||||
if (!*foundp)
|
||||
return true;
|
||||
|
||||
// Steps 7-10. (Step 6 is a no-op.)
|
||||
return CheckUnscopables(cx, actual, id, foundp);
|
||||
return HasProperty(cx, actual, id, foundp);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -2315,25 +2275,12 @@ class DebugScopeProxy : public BaseProxyHandler
|
|||
// target object, the object would indicate that native enumeration is
|
||||
// the thing to do, but native enumeration over the DynamicWithObject
|
||||
// wrapper yields no properties. So instead here we hack around the
|
||||
// issue: punch a hole through to the with object target, then manually
|
||||
// examine @@unscopables.
|
||||
bool isWith = scope->is<DynamicWithObject>();
|
||||
Rooted<JSObject*> target(cx, (isWith ? &scope->as<DynamicWithObject>().object() : scope));
|
||||
// issue, and punch a hole through to the with object target.
|
||||
Rooted<JSObject*> target(cx, (scope->is<DynamicWithObject>()
|
||||
? &scope->as<DynamicWithObject>().object() : scope));
|
||||
if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
|
||||
return false;
|
||||
|
||||
if (isWith) {
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < props.length(); i++) {
|
||||
bool inScope;
|
||||
if (!CheckUnscopables(cx, scope, props[i], &inScope))
|
||||
return false;
|
||||
if (inScope)
|
||||
props[j++].set(props[i]);
|
||||
}
|
||||
props.resize(j);
|
||||
}
|
||||
|
||||
/*
|
||||
* Function scopes are optimized to not contain unaliased variables so
|
||||
* they must be manually appended here.
|
||||
|
|
Загрузка…
Ссылка в новой задаче