From c469cac315013adce0d6ec6e8362d289a3e41f9a Mon Sep 17 00:00:00 2001 From: lucas Date: Wed, 11 Mar 2020 20:18:28 +0100 Subject: [PATCH] [FIX] qweb: protect scope in t-call writing recursively Have something like ```xml

``` Where we want to propagate a recursion index through recursive t-calls Before this commit, it did not work as we protected the scope in order to not leak, in the wronf manner. Namely the protected scope only took firt level prototype properties of the original scope. After this commit, this case works as we mark the scope as read only solves #672 --- src/qweb/compilation_context.ts | 7 +- src/qweb/qweb.ts | 2 +- .../__snapshots__/component.test.ts.snap | 3 +- tests/qweb/__snapshots__/qweb.test.ts.snap | 160 +++++++++++++++--- tests/qweb/qweb.test.ts | 29 ++++ 5 files changed, 171 insertions(+), 30 deletions(-) diff --git a/src/qweb/compilation_context.ts b/src/qweb/compilation_context.ts index c2656771..35dde215 100644 --- a/src/qweb/compilation_context.ts +++ b/src/qweb/compilation_context.ts @@ -196,11 +196,12 @@ export class CompilationContext { const protectID = this.generateID(); this.rootContext.protectedScopeNumber++; this.rootContext.shouldDefineScope = true; - const scopeExpr = codeBlock - ? `Object.create(scope);` - : `Object.assign(Object.create(context), scope);`; + const scopeExpr = `Object.create(scope);`; this.addLine(`let _origScope${protectID} = scope;`); this.addLine(`scope = ${scopeExpr}`); + if (!codeBlock) { + this.addLine(`scope.__access_mode__ = 'ro';`); + } return protectID; } stopProtectScope(protectID: number) { diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts index bf48c88a..662a841c 100644 --- a/src/qweb/qweb.ts +++ b/src/qweb/qweb.ts @@ -133,7 +133,7 @@ const UTILS: Utils = { }, getScope(obj, property: string) { const obj0 = obj; - while (obj && !obj.hasOwnProperty(property)) { + while (obj && !obj.hasOwnProperty(property) && !(obj.hasOwnProperty('__access_mode__') && obj.__access_mode__ === 'ro')) { const newObj = obj.__proto__; if (!newObj || isComponent(newObj)) { return obj0; diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 5e7fc722..6ea5f6d3 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -1282,7 +1282,8 @@ exports[`other directives with t-component t-set can't alter in t-call body 1`] } { let _origScope4 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'iter').iter = 'inCall'; diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index 5f587349..3a0d8d16 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -774,12 +774,14 @@ exports[`misc global 1`] = ` } { let _origScope10 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; { let _origScope13 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'foo').foo = 'aaa'; @@ -1150,7 +1152,8 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] = let vn1 = h('div', p1, c1); { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; let c4 = [], p4 = {key:4}; @@ -1198,7 +1201,8 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = ` let vn1 = h('div', p1, c1); { let _origScope12 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; let c13 = [], p13 = {key:13}; @@ -1288,7 +1292,8 @@ exports[`t-call (template calling recursive template, part 2 1`] = ` let vn1 = h('div', p1, c1); { let _origScope11 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'node').node = scope['root']; @@ -1339,7 +1344,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = ` let key1 = i1; { let _origScope9 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'node').node = scope['subtree']; @@ -1365,7 +1371,8 @@ exports[`t-call (template calling recursive template, part 3 1`] = ` let vn1 = h('div', p1, c1); { let _origScope11 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'node').node = scope['root']; @@ -1416,7 +1423,93 @@ exports[`t-call (template calling recursive template, part 3 2`] = ` let key1 = i1; { let _origScope9 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; + { + let c__0 = []; + utils.getScope(scope, 'node').node = scope['subtree']; + scope[utils.zero] = c__0; + } + let k10 = \`__10__\${key0}__\${key1}__\`; + this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10})); + scope = _origScope9; + } + } + scope = _origScope8; +}" +`; + +exports[`t-call (template calling recursive template, part 4: with t-set recursive index 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"Parent\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + let h = this.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + { + let _origScope11 = scope; + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; + { + let c__0 = []; + utils.getScope(scope, 'recursive_idx').recursive_idx = 1; + utils.getScope(scope, 'node').node = scope['root']; + scope[utils.zero] = c__0; + } + this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'})); + scope = _origScope11; + } + return vn1; +}" +`; + +exports[`t-call (template calling recursive template, part 4: with t-set recursive index 2`] = ` +"function anonymous(context, extra +) { + // Template name: \\"nodeTemplate\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + let h = this.h; + let c1 = extra.parentNode; + let key0 = extra.key || \\"\\"; + let c2 = [], p2 = {key:\`\${key0}_2\`}; + let vn2 = h('div', p2, c2); + c1.push(vn2); + scope.recursive_idx = scope.recursive_idx+1; + let c3 = [], p3 = {key:\`\${key0}_3\`}; + let vn3 = h('p', p3, c3); + c2.push(vn3); + let _4 = scope['node'].val; + if (_4 != null) { + c3.push({text: _4}); + } + c3.push({text: \` \`}); + if (scope.recursive_idx != null) { + c3.push({text: scope.recursive_idx}); + } + let _5 = scope['node'].children||[]; + if (!_5) { throw new Error('QWeb error: Invalid loop expression')} + let _6 = _7 = _5; + if (!(_5 instanceof Array)) { + _6 = Object.keys(_5); + _7 = Object.values(_5); + } + let _length6 = _6.length; + let _origScope8 = scope; + scope = Object.create(scope); + for (let i1 = 0; i1 < _length6; i1++) { + scope.subtree_first = i1 === 0 + scope.subtree_last = i1 === _length6 - 1 + scope.subtree_index = i1 + scope.subtree = _6[i1] + scope.subtree_value = _7[i1] + let key1 = i1; + { + let _origScope9 = scope; + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'node').node = scope['subtree']; @@ -1442,7 +1535,8 @@ exports[`t-call (template calling scoped parameters 1`] = ` let vn1 = h('div', p1, c1); { let _origScope2 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'foo').foo = 42; @@ -1517,7 +1611,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = ` utils.getScope(scope, 'val').val = scope['v'].val; { let _origScope8 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'val3').val3 = scope.val*3; @@ -1564,7 +1659,8 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1 else if (scope.v1==='elif') { { let _origScope6 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'v').v = 'success'; @@ -1602,7 +1698,8 @@ exports[`t-call (template calling with unused body 1`] = ` let h = this.h; { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; c__0.push({text: \`WHEEE\`}); @@ -1627,7 +1724,8 @@ exports[`t-call (template calling with unused setbody 1`] = ` let h = this.h; { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'qux').qux = 3; @@ -1652,7 +1750,8 @@ exports[`t-call (template calling with used body 1`] = ` let h = this.h; { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; c__0.push({text: \`ok\`}); @@ -1678,7 +1777,8 @@ exports[`t-call (template calling with used set body 1`] = ` let vn1 = h('span', p1, c1); { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; utils.getScope(scope, 'foo').foo = 'ok'; @@ -1839,7 +1939,8 @@ exports[`t-esc t-esc is escaped 1`] = ` scope.var = c2 if (scope.var != null) { let _origScope4 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.var = scope.var instanceof utils.VDomArray ? utils.vDomToString(scope.var) : scope.var; c1.push({text: scope.var}); scope = _origScope4; @@ -1875,7 +1976,8 @@ exports[`t-esc t-esc=0 is escaped 1`] = ` let vn1 = h('div', p1, c1); { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; { let c__0 = []; let c4 = [], p4 = {key:4}; @@ -2948,7 +3050,8 @@ exports[`t-set set from body literal 1`] = ` scope.value = c1 if (scope.value != null) { let _origScope2 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value; let vn3 = {text: scope.value}; result = vn3 @@ -2975,7 +3078,8 @@ exports[`t-set set from body lookup 1`] = ` scope.stuff = c2 if (scope.stuff != null) { let _origScope4 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.stuff = scope.stuff instanceof utils.VDomArray ? utils.vDomToString(scope.stuff) : scope.stuff; c1.push({text: scope.stuff}); scope = _origScope4; @@ -3123,7 +3227,8 @@ exports[`t-set t-set with content and sub t-esc 1`] = ` scope.setvar = c2 if (scope.setvar != null) { let _origScope4 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.setvar = scope.setvar instanceof utils.VDomArray ? utils.vDomToString(scope.setvar) : scope.setvar; c1.push({text: scope.setvar}); scope = _origScope4; @@ -3215,7 +3320,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = ` } if (scope.ourvar != null) { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); scope = _origScope3; @@ -3243,7 +3349,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 2`] = ` } if (scope.ourvar != null) { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); scope = _origScope3; @@ -3271,7 +3378,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = ` } if (scope.ourvar != null) { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); scope = _origScope3; @@ -3299,7 +3407,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 2`] = ` } if (scope.ourvar != null) { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); scope = _origScope3; @@ -3325,7 +3434,8 @@ exports[`t-set value priority 1`] = ` } if (scope.value != null) { let _origScope3 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value; c1.push({text: scope.value}); scope = _origScope3; diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts index 6e95ab76..e5d75b88 100644 --- a/tests/qweb/qweb.test.ts +++ b/tests/qweb/qweb.test.ts @@ -944,6 +944,35 @@ describe("t-call (template calling", () => { expect(recursiveFn.toString()).toMatchSnapshot(); }); + test("recursive template, part 4: with t-set recursive index", () => { + qweb.addTemplates(` + +
+ + + + +
+
+ +

+ + + + + +
+ +
+ `); + const root = { val: "a", children: [{ val: "b", children: [{ val: "c" , children: [{val: "d"}] }] }] }; + const expected = + "

a 2

b 3

c 4

d 5

"; + expect(renderToString(qweb, "Parent", { root })).toBe(expected); + const recursiveFn = Object.values(qweb.subTemplates)[0] as any; + expect(recursiveFn.toString()).toMatchSnapshot(); + }); + test("t-call, global templates", () => { QWeb.registerTemplate("abcd", '
'); qweb.addTemplate("john", `desk`);