Skip to content

Commit 5928fb7

Browse files
dgebrwjblue
authored andcommitted
[BUGFIX release] Prevent triggering V8 memory leak bug through registry / resolver access.
This fix changes the expectations of Registry to accept a `resolver` that’s an object with a `resolve` method instead of a straight function. This allows us to avoid closing over access to a resolver object inside a function. It also allows us to avoid setting functions which shadow prototype functions unnecessarily. Setting `Registry#resolver` to a function is still allowed through a constructor option. The `resolver` function will be converted into an object with a `resolve` method and will result in a single deprecation warning. This fix also eliminates the need for application instances to override their registry’s `normalizeFullName` and `makeToString` methods. Instead, the fallback registry will be checked when evaluating these methods before returning the defaults. Again, this avoids the need to override instance functions that shadow prototype functions. [Fixes #12618] (cherry picked from commit 529bfc3)
1 parent 6538d29 commit 5928fb7

File tree

8 files changed

+242
-114
lines changed

8 files changed

+242
-114
lines changed

packages/container/lib/registry.js

+50-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assert } from 'ember-metal/debug';
1+
import { assert, deprecate } from 'ember-metal/debug';
22
import dictionary from 'ember-metal/dictionary';
33
import assign from 'ember-metal/assign';
44
import Container from './container';
@@ -21,7 +21,13 @@ var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
2121
function Registry(options) {
2222
this.fallback = options && options.fallback ? options.fallback : null;
2323

24-
this.resolver = options && options.resolver ? options.resolver : function() {};
24+
if (options && options.resolver) {
25+
this.resolver = options.resolver;
26+
27+
if (typeof this.resolver === 'function') {
28+
deprecateResolverFunction(this);
29+
}
30+
}
2531

2632
this.registrations = dictionary(options && options.registrations ? options.registrations : null);
2733

@@ -49,9 +55,11 @@ Registry.prototype = {
4955
fallback: null,
5056

5157
/**
58+
An object that has a `resolve` method that resolves a name.
59+
5260
@private
5361
@property resolver
54-
@type function
62+
@type Resolver
5563
*/
5664
resolver: null,
5765

@@ -259,7 +267,13 @@ Registry.prototype = {
259267
@return {string} described fullName
260268
*/
261269
describe(fullName) {
262-
return fullName;
270+
if (this.resolver && this.resolver.lookupDescription) {
271+
return this.resolver.lookupDescription(fullName);
272+
} else if (this.fallback) {
273+
return this.fallback.describe(fullName);
274+
} else {
275+
return fullName;
276+
}
263277
},
264278

265279
/**
@@ -271,7 +285,13 @@ Registry.prototype = {
271285
@return {string} normalized fullName
272286
*/
273287
normalizeFullName(fullName) {
274-
return fullName;
288+
if (this.resolver && this.resolver.normalize) {
289+
return this.resolver.normalize(fullName);
290+
} else if (this.fallback) {
291+
return this.fallback.normalizeFullName(fullName);
292+
} else {
293+
return fullName;
294+
}
275295
},
276296

277297
/**
@@ -297,7 +317,13 @@ Registry.prototype = {
297317
@return {function} toString function
298318
*/
299319
makeToString(factory, fullName) {
300-
return factory.toString();
320+
if (this.resolver && this.resolver.makeToString) {
321+
return this.resolver.makeToString(factory, fullName);
322+
} else if (this.fallback) {
323+
return this.fallback.makeToString(factory, fullName);
324+
} else {
325+
return factory.toString();
326+
}
301327
},
302328

303329
/**
@@ -645,7 +671,7 @@ Registry.prototype = {
645671
fallbackKnown = this.fallback.knownForType(type);
646672
}
647673

648-
if (this.resolver.knownForType) {
674+
if (this.resolver && this.resolver.knownForType) {
649675
resolverKnown = this.resolver.knownForType(type);
650676
}
651677

@@ -723,12 +749,27 @@ Registry.prototype = {
723749
}
724750
};
725751

752+
function deprecateResolverFunction(registry) {
753+
deprecate('Passing a `resolver` function into a Registry is deprecated. Please pass in a Resolver object with a `resolve` method.',
754+
false,
755+
{ id: 'ember-application.registry-resolver-as-function', until: '3.0.0', url: 'http://emberjs.com/deprecations/v2.x#toc_registry-resolver-as-function' });
756+
registry.resolver = {
757+
resolve: registry.resolver
758+
};
759+
}
760+
726761
function resolve(registry, normalizedName) {
727-
var cached = registry._resolveCache[normalizedName];
762+
let cached = registry._resolveCache[normalizedName];
728763
if (cached) { return cached; }
729764
if (registry._failCache[normalizedName]) { return; }
730765

731-
var resolved = registry.resolver(normalizedName) || registry.registrations[normalizedName];
766+
let resolved;
767+
768+
if (registry.resolver) {
769+
resolved = registry.resolver.resolve(normalizedName);
770+
}
771+
772+
resolved = resolved || registry.registrations[normalizedName];
732773

733774
if (resolved) {
734775
registry._resolveCache[normalizedName] = resolved;

packages/container/tests/container_test.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,11 @@ QUnit.test('The container can use a registry hook to resolve factories lazily',
301301
var container = registry.container();
302302
var PostController = factory();
303303

304-
registry.resolver = function(fullName) {
305-
if (fullName === 'controller:post') {
306-
return PostController;
304+
registry.resolver = {
305+
resolve(fullName) {
306+
if (fullName === 'controller:post') {
307+
return PostController;
308+
}
307309
}
308310
};
309311

@@ -347,9 +349,11 @@ QUnit.test('Options can be registered that should be applied to a given factory'
347349
var container = registry.container();
348350
var PostView = factory();
349351

350-
registry.resolver = function(fullName) {
351-
if (fullName === 'view:post') {
352-
return PostView;
352+
registry.resolver = {
353+
resolve(fullName) {
354+
if (fullName === 'view:post') {
355+
return PostView;
356+
}
353357
}
354358
};
355359

@@ -369,9 +373,11 @@ QUnit.test('Options can be registered that should be applied to all factories fo
369373
var container = registry.container();
370374
var PostView = factory();
371375

372-
registry.resolver = function(fullName) {
373-
if (fullName === 'view:post') {
374-
return PostView;
376+
registry.resolver = {
377+
resolve(fullName) {
378+
if (fullName === 'view:post') {
379+
return PostView;
380+
}
375381
}
376382
};
377383

packages/container/tests/registry_test.js

+140-30
Original file line numberDiff line numberDiff line change
@@ -56,27 +56,29 @@ QUnit.test('Throw exception when trying to inject `type:thing` on all type(s)',
5656
});
5757

5858
QUnit.test('The registry can take a hook to resolve factories lazily', function() {
59-
var registry = new Registry();
60-
var PostController = factory();
61-
62-
registry.resolver = function(fullName) {
63-
if (fullName === 'controller:post') {
64-
return PostController;
59+
let PostController = factory();
60+
let resolver = {
61+
resolve(fullName) {
62+
if (fullName === 'controller:post') {
63+
return PostController;
64+
}
6565
}
6666
};
67+
let registry = new Registry({ resolver });
6768

6869
strictEqual(registry.resolve('controller:post'), PostController, 'The correct factory was provided');
6970
});
7071

7172
QUnit.test('The registry respects the resolver hook for `has`', function() {
72-
var registry = new Registry();
73-
var PostController = factory();
74-
75-
registry.resolver = function(fullName) {
76-
if (fullName === 'controller:post') {
77-
return PostController;
73+
let PostController = factory();
74+
let resolver = {
75+
resolve(fullName) {
76+
if (fullName === 'controller:post') {
77+
return PostController;
78+
}
7879
}
7980
};
81+
let registry = new Registry({ resolver });
8082

8183
ok(registry.has('controller:post'), 'the `has` method uses the resolver hook');
8284
});
@@ -196,14 +198,18 @@ QUnit.test('once resolved, always return the same result', function() {
196198

197199
var registry = new Registry();
198200

199-
registry.resolver = function() {
200-
return 'bar';
201+
registry.resolver = {
202+
resolve() {
203+
return 'bar';
204+
}
201205
};
202206

203207
var Bar = registry.resolve('models:bar');
204208

205-
registry.resolver = function() {
206-
return 'not bar';
209+
registry.resolver = {
210+
resolve() {
211+
return 'not bar';
212+
}
207213
};
208214

209215
equal(registry.resolve('models:bar'), Bar);
@@ -213,9 +219,12 @@ QUnit.test('factory resolves are cached', function() {
213219
var registry = new Registry();
214220
var PostController = factory();
215221
var resolveWasCalled = [];
216-
registry.resolver = function(fullName) {
217-
resolveWasCalled.push(fullName);
218-
return PostController;
222+
223+
registry.resolver = {
224+
resolve(fullName) {
225+
resolveWasCalled.push(fullName);
226+
return PostController;
227+
}
219228
};
220229

221230
deepEqual(resolveWasCalled, []);
@@ -230,9 +239,12 @@ QUnit.test('factory for non extendables (MODEL) resolves are cached', function()
230239
var registry = new Registry();
231240
var PostController = factory();
232241
var resolveWasCalled = [];
233-
registry.resolver = function(fullName) {
234-
resolveWasCalled.push(fullName);
235-
return PostController;
242+
243+
registry.resolver = {
244+
resolve(fullName) {
245+
resolveWasCalled.push(fullName);
246+
return PostController;
247+
}
236248
};
237249

238250
deepEqual(resolveWasCalled, []);
@@ -247,9 +259,12 @@ QUnit.test('factory for non extendables resolves are cached', function() {
247259
var registry = new Registry();
248260
var PostController = {};
249261
var resolveWasCalled = [];
250-
registry.resolver = function(fullName) {
251-
resolveWasCalled.push(fullName);
252-
return PostController;
262+
263+
registry.resolver = {
264+
resolve(fullName) {
265+
resolveWasCalled.push(fullName);
266+
return PostController;
267+
}
253268
};
254269

255270
deepEqual(resolveWasCalled, []);
@@ -271,6 +286,84 @@ QUnit.test('registry.container creates a container', function() {
271286
ok(postController instanceof PostController, 'The lookup is an instance of the registered factory');
272287
});
273288

289+
QUnit.test('`describe` will be handled by the resolver, then by the fallback registry, if available', function() {
290+
let fallback = {
291+
describe(fullName) {
292+
return `${fullName}-fallback`;
293+
}
294+
};
295+
296+
let resolver = {
297+
lookupDescription(fullName) {
298+
return `${fullName}-resolver`;
299+
}
300+
};
301+
302+
let registry = new Registry({ fallback, resolver });
303+
304+
equal(registry.describe('controller:post'), 'controller:post-resolver', '`describe` handled by the resolver first.');
305+
306+
registry.resolver = null;
307+
308+
equal(registry.describe('controller:post'), 'controller:post-fallback', '`describe` handled by fallback registry next.');
309+
310+
registry.fallback = null;
311+
312+
equal(registry.describe('controller:post'), 'controller:post', '`describe` by default returns argument.');
313+
});
314+
315+
QUnit.test('`normalizeFullName` will be handled by the resolver, then by the fallback registry, if available', function() {
316+
let fallback = {
317+
normalizeFullName(fullName) {
318+
return `${fullName}-fallback`;
319+
}
320+
};
321+
322+
let resolver = {
323+
normalize(fullName) {
324+
return `${fullName}-resolver`;
325+
}
326+
};
327+
328+
let registry = new Registry({ fallback, resolver });
329+
330+
equal(registry.normalizeFullName('controller:post'), 'controller:post-resolver', '`normalizeFullName` handled by the resolver first.');
331+
332+
registry.resolver = null;
333+
334+
equal(registry.normalizeFullName('controller:post'), 'controller:post-fallback', '`normalizeFullName` handled by fallback registry next.');
335+
336+
registry.fallback = null;
337+
338+
equal(registry.normalizeFullName('controller:post'), 'controller:post', '`normalizeFullName` by default returns argument.');
339+
});
340+
341+
QUnit.test('`makeToString` will be handled by the resolver, then by the fallback registry, if available', function() {
342+
let fallback = {
343+
makeToString(fullName) {
344+
return `${fullName}-fallback`;
345+
}
346+
};
347+
348+
let resolver = {
349+
makeToString(fullName) {
350+
return `${fullName}-resolver`;
351+
}
352+
};
353+
354+
let registry = new Registry({ fallback, resolver });
355+
356+
equal(registry.makeToString('controller:post'), 'controller:post-resolver', '`makeToString` handled by the resolver first.');
357+
358+
registry.resolver = null;
359+
360+
equal(registry.makeToString('controller:post'), 'controller:post-fallback', '`makeToString` handled by fallback registry next.');
361+
362+
registry.fallback = null;
363+
364+
equal(registry.makeToString('controller:post'), 'controller:post', '`makeToString` by default returns argument.');
365+
});
366+
274367
QUnit.test('`resolve` can be handled by a fallback registry', function() {
275368
var fallback = new Registry();
276369

@@ -375,12 +468,13 @@ QUnit.test('`knownForType` includes fallback registry results', function() {
375468
QUnit.test('`knownForType` is called on the resolver if present', function() {
376469
expect(3);
377470

378-
function resolver() { }
379-
resolver.knownForType = function(type) {
380-
ok(true, 'knownForType called on the resolver');
381-
equal(type, 'foo', 'the type was passed through');
471+
let resolver = {
472+
knownForType(type) {
473+
ok(true, 'knownForType called on the resolver');
474+
equal(type, 'foo', 'the type was passed through');
382475

383-
return { 'foo:yorp': true };
476+
return { 'foo:yorp': true };
477+
}
384478
};
385479

386480
var registry = new Registry({
@@ -395,3 +489,19 @@ QUnit.test('`knownForType` is called on the resolver if present', function() {
395489
'foo:bar-baz': true
396490
});
397491
});
492+
493+
QUnit.test('A registry can be created with a deprecated `resolver` function instead of an object', function() {
494+
expect(2);
495+
496+
let registry;
497+
498+
expectDeprecation(function() {
499+
registry = new Registry({
500+
resolver(fullName) {
501+
return `${fullName}-resolved`;
502+
}
503+
});
504+
}, 'Passing a `resolver` function into a Registry is deprecated. Please pass in a Resolver object with a `resolve` method.');
505+
506+
equal(registry.resolve('foo:bar'), 'foo:bar-resolved', '`resolve` still calls the deprecated function');
507+
});

0 commit comments

Comments
 (0)