Skip to content

Commit 9bd65ab

Browse files
committed
feat(ElementInjector): throw when encounter a cyclic dependency
1 parent b0c9d05 commit 9bd65ab

File tree

5 files changed

+70
-31
lines changed

5 files changed

+70
-31
lines changed

modules/core/src/compiler/element_injector.js

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import {FIELD, isPresent, isBlank, Type, int} from 'facade/lang';
22
import {Math} from 'facade/math';
33
import {List, ListWrapper} from 'facade/collection';
4-
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError} from 'di/di';
4+
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
55
import {Parent, Ancestor} from 'core/annotations/visibility';
66
import {StaticKeys} from './static_keys';
77

8+
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
9+
810
var MAX_DEPTH = Math.pow(2, 30) - 1;
11+
912
var _undefined = new Object();
1013

1114
class TreeNode {
@@ -284,6 +287,7 @@ export class ElementInjector extends TreeNode {
284287
this._obj7 = null;
285288
this._obj8 = null;
286289
this._obj9 = null;
290+
this._constructionCounter = 0;
287291
}
288292

289293
clearDirectives() {
@@ -298,29 +302,34 @@ export class ElementInjector extends TreeNode {
298302
this._obj7 = null;
299303
this._obj8 = null;
300304
this._obj9 = null;
305+
this._constructionCounter = 0;
301306
}
302307

303308
instantiateDirectives(appInjector:Injector) {
304309
this._appInjector = appInjector;
305310

306311
var p = this._proto;
307-
if (isPresent(p._keyId0)) this._obj0 = this._new(p._binding0);
308-
if (isPresent(p._keyId1)) this._obj1 = this._new(p._binding1);
309-
if (isPresent(p._keyId2)) this._obj2 = this._new(p._binding2);
310-
if (isPresent(p._keyId3)) this._obj3 = this._new(p._binding3);
311-
if (isPresent(p._keyId4)) this._obj4 = this._new(p._binding4);
312-
if (isPresent(p._keyId5)) this._obj5 = this._new(p._binding5);
313-
if (isPresent(p._keyId6)) this._obj6 = this._new(p._binding6);
314-
if (isPresent(p._keyId7)) this._obj7 = this._new(p._binding7);
315-
if (isPresent(p._keyId8)) this._obj8 = this._new(p._binding8);
316-
if (isPresent(p._keyId9)) this._obj9 = this._new(p._binding9);
312+
if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0);
313+
if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1);
314+
if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2);;
315+
if (isPresent(p._keyId3)) this._getDirectiveByKeyId(p._keyId3);;
316+
if (isPresent(p._keyId4)) this._getDirectiveByKeyId(p._keyId4);;
317+
if (isPresent(p._keyId5)) this._getDirectiveByKeyId(p._keyId5);;
318+
if (isPresent(p._keyId6)) this._getDirectiveByKeyId(p._keyId6);;
319+
if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7);;
320+
if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8);;
321+
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);;
317322
}
318323

319324
get(token) {
320325
return this._getByKey(Key.get(token), 0);
321326
}
322327

323328
_new(binding:Binding) {
329+
if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) {
330+
throw new CyclicDependencyError(binding.key);
331+
}
332+
324333
var factory = binding.factory;
325334
var deps = binding.dependencies;
326335
var length = deps.length;
@@ -365,7 +374,6 @@ export class ElementInjector extends TreeNode {
365374
return this._getByKey(dep.key, dep.depth);
366375
}
367376

368-
369377
/*
370378
* It is fairly easy to annotate keys with metadata.
371379
* For example, key.metadata = 'directive'.
@@ -381,10 +389,10 @@ export class ElementInjector extends TreeNode {
381389
_getByKey(key:Key, depth:int) {
382390
var ei = this;
383391
while (ei != null && depth >= 0) {
384-
var specObj = ei._getSpecialObjectByKey(key);
392+
var specObj = ei._getSpecialObjectByKeyId(key.id);
385393
if (specObj !== _undefined) return specObj;
386394

387-
var dir = ei._getDirectiveByKey(key);
395+
var dir = ei._getDirectiveByKeyId(key.id);
388396
if (dir !== _undefined) return dir;
389397

390398
ei = ei._parent;
@@ -393,28 +401,25 @@ export class ElementInjector extends TreeNode {
393401
return this._appInjector.get(key);
394402
}
395403

396-
_getSpecialObjectByKey(key:Key) {
404+
_getSpecialObjectByKeyId(keyId:int) {
397405
var staticKeys = StaticKeys.instance();
398-
var keyId = key.id;
399-
400406
if (keyId === staticKeys.viewId) return this._view;
401407
//TODO add other objects as needed
402408
return _undefined;
403409
}
404410

405-
_getDirectiveByKey(key:Key) {
411+
_getDirectiveByKeyId(keyId:int) {
406412
var p = this._proto;
407-
var keyId = key.id;
408-
if (p._keyId0 === keyId) return this._obj0;
409-
if (p._keyId1 === keyId) return this._obj1;
410-
if (p._keyId2 === keyId) return this._obj2;
411-
if (p._keyId3 === keyId) return this._obj3;
412-
if (p._keyId4 === keyId) return this._obj4;
413-
if (p._keyId5 === keyId) return this._obj5;
414-
if (p._keyId6 === keyId) return this._obj6;
415-
if (p._keyId7 === keyId) return this._obj7;
416-
if (p._keyId8 === keyId) return this._obj8;
417-
if (p._keyId9 === keyId) return this._obj9;
413+
if (p._keyId0 === keyId) {if (isBlank(this._obj0)){this._obj0 = this._new(p._binding0);} return this._obj0;}
414+
if (p._keyId1 === keyId) {if (isBlank(this._obj1)){this._obj1 = this._new(p._binding1);} return this._obj1;}
415+
if (p._keyId2 === keyId) {if (isBlank(this._obj2)){this._obj2 = this._new(p._binding2);} return this._obj2;}
416+
if (p._keyId3 === keyId) {if (isBlank(this._obj3)){this._obj3 = this._new(p._binding3);} return this._obj3;}
417+
if (p._keyId4 === keyId) {if (isBlank(this._obj4)){this._obj4 = this._new(p._binding4);} return this._obj4;}
418+
if (p._keyId5 === keyId) {if (isBlank(this._obj5)){this._obj5 = this._new(p._binding5);} return this._obj5;}
419+
if (p._keyId6 === keyId) {if (isBlank(this._obj6)){this._obj6 = this._new(p._binding6);} return this._obj6;}
420+
if (p._keyId7 === keyId) {if (isBlank(this._obj7)){this._obj7 = this._new(p._binding7);} return this._obj7;}
421+
if (p._keyId8 === keyId) {if (isBlank(this._obj8)){this._obj8 = this._new(p._binding8);} return this._obj8;}
422+
if (p._keyId9 === keyId) {if (isBlank(this._obj9)){this._obj9 = this._new(p._binding9);} return this._obj9;}
418423
return _undefined;
419424
}
420425

modules/core/test/compiler/element_injector_spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ class NeedsService {
4040
}
4141
}
4242

43+
class A_Needs_B {
44+
constructor(dep){}
45+
}
46+
47+
class B_Needs_A {
48+
constructor(dep){}
49+
}
50+
4351
class NeedsView {
4452
@FIELD("view:Object")
4553
constructor(@Inject(View) view) {
@@ -215,6 +223,16 @@ export function main() {
215223

216224
expect(inj.get(View)).toEqual(view);
217225
});
226+
227+
it("should handle cyclic dependencies", function () {
228+
expect(() => {
229+
injector([
230+
bind(A_Needs_B).toFactory((a) => new A_Needs_B(a), [B_Needs_A]),
231+
bind(B_Needs_A).toFactory((a) => new B_Needs_A(a), [A_Needs_B])
232+
]);
233+
}).toThrowError('Cannot instantiate cyclic dependency! ' +
234+
'(A_Needs_B -> B_Needs_A -> A_Needs_B)');
235+
});
218236
});
219237
});
220238
}

modules/di/src/exceptions.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,22 @@ import {ListWrapper, List} from 'facade/collection';
22
import {stringify} from 'facade/lang';
33
import {Key} from './key';
44

5+
function findFirstClosedCycle(keys:List) {
6+
var res = [];
7+
for(var i = 0; i < keys.length; ++i) {
8+
if (ListWrapper.contains(res, keys[i])) {
9+
ListWrapper.push(res, keys[i]);
10+
return res;
11+
} else {
12+
ListWrapper.push(res, keys[i]);
13+
}
14+
}
15+
return res;
16+
}
17+
518
function constructResolvingPath(keys:List) {
619
if (keys.length > 1) {
7-
var reversed = ListWrapper.reversed(keys);
20+
var reversed = findFirstClosedCycle(ListWrapper.reversed(keys));
821
var tokenStrs = ListWrapper.map(reversed, (k) => stringify(k.token));
922
return " (" + tokenStrs.join(' -> ') + ")";
1023
} else {

modules/facade/src/collection.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ListWrapper {
3131
static List createFixedSize(int size) => new List(size);
3232
static get(m, k) => m[k];
3333
static void set(m, k, v) { m[k] = v; }
34-
static contains(m, k) => m.containsKey(k);
34+
static contains(List m, k) => m.contains(k);
3535
static map(list, fn) => list.map(fn).toList();
3636
static filter(List list, fn) => list.where(fn).toList();
3737
static find(List list, fn) => list.firstWhere(fn, orElse:() => null);

modules/facade/src/collection.es6

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export class ListWrapper {
6969
}
7070
return false;
7171
}
72+
static contains(list:List, el) {
73+
return list.indexOf(el) !== -1;
74+
}
7275
static reversed(array) {
7376
var a = ListWrapper.clone(array);
7477
return a.reverse();

0 commit comments

Comments
 (0)