[FIX] qweb: protect scope in t-call writing recursively

Have something like
```xml
 <div t-name="Parent">
   <t t-call="nodeTemplate">
     <t t-set="recursive_idx" t-value="1"/>
     <t t-set="node" t-value="root"/>
   </t>
 </div>

 <div t-name="nodeTemplate">
   <t t-set="recursive_idx" t-value="recursive_idx + 1"/>
   <p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></p>
   <t t-foreach="node.children or []" t-as="subtree">
     <t t-call="nodeTemplate">
       <t t-set="node" t-value="subtree"/>
     </t>
   </t>
 </div>
```

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
This commit is contained in:
lucas
2020-03-11 20:18:28 +01:00
committed by Géry Debongnie
parent b4ad14edc0
commit c469cac315
5 changed files with 171 additions and 30 deletions
+4 -3
View File
@@ -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) {
+1 -1
View File
@@ -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;
@@ -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';
+135 -25
View File
@@ -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;
+29
View File
@@ -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(`
<templates>
<div t-name="Parent">
<t t-call="nodeTemplate">
<t t-set="recursive_idx" t-value="1"/>
<t t-set="node" t-value="root"/>
</t>
</div>
<div t-name="nodeTemplate">
<t t-set="recursive_idx" t-value="recursive_idx + 1"/>
<p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></p>
<t t-foreach="node.children or []" t-as="subtree">
<t t-call="nodeTemplate">
<t t-set="node" t-value="subtree"/>
</t>
</t>
</div>
</templates>
`);
const root = { val: "a", children: [{ val: "b", children: [{ val: "c" , children: [{val: "d"}] }] }] };
const expected =
"<div><div><p>a 2</p><div><p>b 3</p><div><p>c 4</p><div><p>d 5</p></div></div></div></div></div>";
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", '<div><t t-call="john"/></div>');
qweb.addTemplate("john", `<span>desk</span>`);