Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit acfda10

Browse files
committed
fix($parse): Preserve expensive checks when runnning $eval inside an expression
When running an expression with expensive checks, there is a call to `$eval` or `$evalAsync` then that expression is also evaluated using expensive checks Closes: #13850
1 parent 04d4d93 commit acfda10

File tree

4 files changed

+120
-8
lines changed

4 files changed

+120
-8
lines changed

src/ng/parse.js

+37-2
Original file line numberDiff line numberDiff line change
@@ -1757,10 +1757,19 @@ function $ParseProvider() {
17571757
csp: noUnsafeEval,
17581758
expensiveChecks: true
17591759
};
1760+
var runningChecksEnabled = false;
17601761

1761-
return function $parse(exp, interceptorFn, expensiveChecks) {
1762+
$parse.$$runningExpensiveChecks = function() {
1763+
return runningChecksEnabled;
1764+
};
1765+
1766+
return $parse;
1767+
1768+
function $parse(exp, interceptorFn, expensiveChecks) {
17621769
var parsedExpression, oneTime, cacheKey;
17631770

1771+
expensiveChecks = expensiveChecks || runningChecksEnabled;
1772+
17641773
switch (typeof exp) {
17651774
case 'string':
17661775
exp = exp.trim();
@@ -1786,6 +1795,9 @@ function $ParseProvider() {
17861795
} else if (parsedExpression.inputs) {
17871796
parsedExpression.$$watchDelegate = inputsWatchDelegate;
17881797
}
1798+
if (expensiveChecks) {
1799+
parsedExpression = expensiveChecksInterceptor(parsedExpression);
1800+
}
17891801
cache[cacheKey] = parsedExpression;
17901802
}
17911803
return addInterceptor(parsedExpression, interceptorFn);
@@ -1796,7 +1808,30 @@ function $ParseProvider() {
17961808
default:
17971809
return addInterceptor(noop, interceptorFn);
17981810
}
1799-
};
1811+
}
1812+
1813+
function expensiveChecksInterceptor(fn) {
1814+
if (!fn) return fn;
1815+
expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate;
1816+
expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign);
1817+
expensiveCheckFn.constant = fn.constant;
1818+
expensiveCheckFn.literal = fn.literal;
1819+
for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) {
1820+
fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]);
1821+
}
1822+
1823+
return expensiveCheckFn;
1824+
1825+
function expensiveCheckFn(scope, locals, assign, inputs) {
1826+
var expensiveCheckOldValue = runningChecksEnabled;
1827+
runningChecksEnabled = true;
1828+
try {
1829+
return fn(scope, locals, assign, inputs);
1830+
} finally {
1831+
runningChecksEnabled = expensiveCheckOldValue;
1832+
}
1833+
}
1834+
}
18001835

18011836
function expressionInputDirtyCheck(newValue, oldValueOfValue) {
18021837

src/ng/rootScope.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,7 @@ function $RootScopeProvider() {
998998
});
999999
}
10001000

1001-
asyncQueue.push({scope: this, expression: expr, locals: locals});
1001+
asyncQueue.push({scope: this, expression: $parse(expr), locals: locals});
10021002
},
10031003

10041004
$$postDigest: function(fn) {
@@ -1090,6 +1090,7 @@ function $RootScopeProvider() {
10901090
$applyAsync: function(expr) {
10911091
var scope = this;
10921092
expr && applyAsyncQueue.push($applyAsyncExpression);
1093+
expr = $parse(expr);
10931094
scheduleApplyAsync();
10941095

10951096
function $applyAsyncExpression() {

test/ng/parseSpec.js

+77-1
Original file line numberDiff line numberDiff line change
@@ -1681,7 +1681,6 @@ describe('parser', function() {
16811681
$filterProvider = filterProvider;
16821682
}]));
16831683

1684-
16851684
forEach([true, false], function(cspEnabled) {
16861685
describe('csp: ' + cspEnabled, function() {
16871686

@@ -2400,6 +2399,64 @@ describe('parser', function() {
24002399
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
24012400
'Expression: foo.w = 1');
24022401
}));
2402+
2403+
they('should propagate expensive checks when calling $prop',
2404+
['foo.w && true',
2405+
'$eval("foo.w && true")',
2406+
'this["$eval"]("foo.w && true")',
2407+
'bar;$eval("foo.w && true")',
2408+
'$eval("foo.w && true");bar',
2409+
'$eval("foo.w && true", null, false)',
2410+
'$eval("foo");$eval("foo.w && true")',
2411+
'$eval("$eval(\\"foo.w && true\\")")',
2412+
'$eval("foo.e()")',
2413+
'$evalAsync("foo.w && true")',
2414+
'this["$evalAsync"]("foo.w && true")',
2415+
'bar;$evalAsync("foo.w && true")',
2416+
'$evalAsync("foo.w && true");bar',
2417+
'$evalAsync("foo.w && true", null, false)',
2418+
'$evalAsync("foo");$evalAsync("foo.w && true")',
2419+
'$evalAsync("$evalAsync(\\"foo.w && true\\")")',
2420+
'$evalAsync("foo.e()")',
2421+
'$evalAsync("$eval(\\"foo.w && true\\")")',
2422+
'$eval("$evalAsync(\\"foo.w && true\\")")',
2423+
'$watch("foo.w && true")',
2424+
'$watchCollection("foo.w && true", foo.f)',
2425+
'$watchGroup(["foo.w && true"])',
2426+
'$applyAsync("foo.w && true")'], function(expression) {
2427+
inject(function($parse, $window) {
2428+
scope.foo = {
2429+
w: $window,
2430+
bar: 'bar',
2431+
e: function() { scope.$eval("foo.w && true"); },
2432+
f: function() {}
2433+
};
2434+
expect($parse.$$runningExpensiveChecks()).toEqual(false);
2435+
expect(function() {
2436+
scope.$eval($parse(expression, null, true));
2437+
scope.$digest();
2438+
}).toThrowMinErr(
2439+
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
2440+
'Expression: foo.w && true');
2441+
expect($parse.$$runningExpensiveChecks()).toEqual(false);
2442+
});
2443+
});
2444+
2445+
they('should restore the state of $$runningExpensiveChecks when the expression $prop throws',
2446+
['$eval("foo.t()")',
2447+
'$evalAsync("foo.t()", {foo: foo})'], function(expression) {
2448+
inject(function($parse, $window) {
2449+
scope.foo = {
2450+
t: function() { throw new Error(); }
2451+
};
2452+
expect($parse.$$runningExpensiveChecks()).toEqual(false);
2453+
expect(function() {
2454+
scope.$eval($parse(expression, null, true));
2455+
scope.$digest();
2456+
}).toThrow();
2457+
expect($parse.$$runningExpensiveChecks()).toEqual(false);
2458+
});
2459+
});
24032460
});
24042461
});
24052462

@@ -2966,6 +3023,25 @@ describe('parser', function() {
29663023
expect(log).toEqual('');
29673024
}));
29683025

3026+
it('should work with expensive checks', inject(function($parse, $rootScope, log) {
3027+
var fn = $parse('::foo', null, true);
3028+
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
3029+
3030+
$rootScope.$digest();
3031+
expect($rootScope.$$watchers.length).toBe(1);
3032+
3033+
$rootScope.foo = 'bar';
3034+
$rootScope.$digest();
3035+
expect($rootScope.$$watchers.length).toBe(0);
3036+
expect(log).toEqual('bar');
3037+
log.reset();
3038+
3039+
$rootScope.foo = 'man';
3040+
$rootScope.$digest();
3041+
expect($rootScope.$$watchers.length).toBe(0);
3042+
expect(log).toEqual('');
3043+
}));
3044+
29693045
it('should have a stable value if at the end of a $digest it has a defined value', inject(function($parse, $rootScope, log) {
29703046
var fn = $parse('::foo');
29713047
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });

test/ng/rootScopeSpec.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,7 @@ describe('Scope', function() {
13871387
expect(child.log).toBe('child context');
13881388
}));
13891389

1390-
it('should operate only with a single queue across all child and isolate scopes', inject(function($rootScope) {
1390+
it('should operate only with a single queue across all child and isolate scopes', inject(function($rootScope, $parse) {
13911391
var childScope = $rootScope.$new();
13921392
var isolateScope = $rootScope.$new(true);
13931393

@@ -1398,9 +1398,9 @@ describe('Scope', function() {
13981398
expect(childScope.$$asyncQueue).toBe($rootScope.$$asyncQueue);
13991399
expect(isolateScope.$$asyncQueue).toBeUndefined();
14001400
expect($rootScope.$$asyncQueue).toEqual([
1401-
{scope: $rootScope, expression: 'rootExpression'},
1402-
{scope: childScope, expression: 'childExpression'},
1403-
{scope: isolateScope, expression: 'isolateExpression'}
1401+
{scope: $rootScope, expression: $parse('rootExpression')},
1402+
{scope: childScope, expression: $parse('childExpression')},
1403+
{scope: isolateScope, expression: $parse('isolateExpression')}
14041404
]);
14051405
}));
14061406

0 commit comments

Comments
 (0)