From 6e185f987baad2e4a5ae4781acf3e844a4028e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 19 Oct 2021 15:55:57 +0200 Subject: [PATCH] [FIX] qweb: do not leak values in global context in rare cases In some very rare cases (such as the use of the t-foreach directive), Owl did leak the values in the render context in the global context. This was due to the fact that the compiled template looked like this: let _3 = _4 = _5; instead of let _3 = _5; let _4 = _5; --- src/qweb/base_directives.ts | 3 +- .../__snapshots__/component.test.ts.snap | 33 ++++++---- .../__snapshots__/slots.test.ts.snap | 12 ++-- tests/qweb/__snapshots__/qweb.test.ts.snap | 66 ++++++++++++------- .../__snapshots__/qweb_memory.test.ts.snap | 37 +++++++++++ tests/qweb/qweb_memory.test.ts | 17 +++++ 6 files changed, 130 insertions(+), 38 deletions(-) create mode 100644 tests/qweb/__snapshots__/qweb_memory.test.ts.snap create mode 100644 tests/qweb/qweb_memory.test.ts diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts index 0b01ee48..eadec743 100644 --- a/src/qweb/base_directives.ts +++ b/src/qweb/base_directives.ts @@ -326,7 +326,8 @@ QWeb.addDirective({ ctx.addLine(`if (!_${arrayID}) { throw new Error('QWeb error: Invalid loop expression')}`); let keysID = ctx.generateID(); let valuesID = ctx.generateID(); - ctx.addLine(`let _${keysID} = _${valuesID} = _${arrayID};`); + ctx.addLine(`let _${keysID} = _${arrayID};`); + ctx.addLine(`let _${valuesID} = _${arrayID};`); ctx.addIf(`!(_${arrayID} instanceof Array)`); ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`); ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`); diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 9a75ca41..fa37bbf4 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -51,7 +51,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for let vn1 = h('div', p1, c1); let _2 = scope['state'].s; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -68,7 +69,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for let key1 = i1; let _6 = scope['section'].blips; if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _8 = _6; + let _7 = _6; + let _8 = _6; if (!(_6 instanceof Array)) { _7 = Object.keys(_6); _8 = Object.values(_6); @@ -309,7 +311,8 @@ exports[`composition sub components with some state rendered in a loop 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['state'].numbers; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -597,7 +600,8 @@ exports[`other directives with t-component t-on expression captured in t-foreach scope.iter = 0; let _2 = scope['arr']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -639,7 +643,8 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['state'].values; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -691,7 +696,8 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s scope.bossa = 'nova'; let _2 = scope['state'].values; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -744,7 +750,8 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['state'].values; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -1410,7 +1417,8 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1 scope.iter = 0; let _2 = scope['state'].values; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -1560,7 +1568,8 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument let vn1 = h('div', p1, c1); let _2 = scope['items']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -1838,7 +1847,8 @@ exports[`t-model directive in a t-foreach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['state']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -1878,7 +1888,8 @@ exports[`t-model directive in a t-foreach, part 2 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['state']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); diff --git a/tests/component/__snapshots__/slots.test.ts.snap b/tests/component/__snapshots__/slots.test.ts.snap index 86dab8d4..babf5c21 100644 --- a/tests/component/__snapshots__/slots.test.ts.snap +++ b/tests/component/__snapshots__/slots.test.ts.snap @@ -414,7 +414,8 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = ` c1.push(vn2); let _3 = scope['state'].users; if (!_3) { throw new Error('QWeb error: Invalid loop expression')} - let _4 = _5 = _3; + let _4 = _3; + let _5 = _3; if (!(_3 instanceof Array)) { _4 = Object.keys(_3); _5 = Object.values(_3); @@ -512,7 +513,8 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` c1.push(vn2); let _3 = scope['state'].users; if (!_3) { throw new Error('QWeb error: Invalid loop expression')} - let _4 = _5 = _3; + let _4 = _3; + let _5 = _3; if (!(_3 instanceof Array)) { _4 = Object.keys(_3); _5 = Object.values(_3); @@ -645,7 +647,8 @@ exports[`t-slot directive slots in t-foreach in t-foreach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['tree']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -672,7 +675,8 @@ exports[`t-slot directive slots in t-foreach in t-foreach 1`] = ` c1.push(vn8); let _9 = scope['node1'].nodes; if (!_9) { throw new Error('QWeb error: Invalid loop expression')} - let _10 = _11 = _9; + let _10 = _9; + let _11 = _9; if (!(_9 instanceof Array)) { _10 = Object.keys(_9); _11 = Object.values(_9); diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index 41de1d25..60e1d0f5 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -429,7 +429,8 @@ exports[`foreach does not pollute the rendering context 1`] = ` let vn1 = h('div', p1, c1); let _2 = [1]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -464,7 +465,8 @@ exports[`foreach iterate on items (on a element node) 1`] = ` let vn1 = h('div', p1, c1); let _2 = [1,2]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -502,7 +504,8 @@ exports[`foreach iterate on items 1`] = ` let vn1 = h('div', p1, c1); let _2 = [3,2,1]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -549,7 +552,8 @@ exports[`foreach iterate, dict param 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['value']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -596,7 +600,8 @@ exports[`foreach iterate, position 1`] = ` let vn1 = h('div', p1, c1); let _2 = Array(5); if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -641,7 +646,8 @@ exports[`foreach t-call with body in t-foreach in t-foreach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['numbers']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -658,7 +664,8 @@ exports[`foreach t-call with body in t-foreach in t-foreach 1`] = ` let key1 = i1; let _6 = scope['letters']; if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _8 = _6; + let _7 = _6; + let _8 = _6; if (!(_6 instanceof Array)) { _7 = Object.keys(_6); _8 = Object.values(_6); @@ -729,7 +736,8 @@ exports[`foreach t-call without body in t-foreach in t-foreach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['numbers']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -746,7 +754,8 @@ exports[`foreach t-call without body in t-foreach in t-foreach 1`] = ` let key1 = i1; let _6 = scope['letters']; if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _8 = _6; + let _7 = _6; + let _8 = _6; if (!(_6 instanceof Array)) { _7 = Object.keys(_6); _8 = Object.values(_6); @@ -811,7 +820,8 @@ exports[`foreach t-foreach in t-forach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['numbers']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -828,7 +838,8 @@ exports[`foreach t-foreach in t-forach 1`] = ` let key1 = i1; let _6 = scope['letters']; if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _8 = _6; + let _7 = _6; + let _8 = _6; if (!(_6 instanceof Array)) { _7 = Object.keys(_6); _8 = Object.values(_6); @@ -871,7 +882,8 @@ exports[`foreach warn if no key in some case 1`] = ` let vn1 = h('div', p1, c1); let _2 = [1,2]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -940,7 +952,8 @@ exports[`misc global 1`] = ` let vn1 = h('div', p1, c1); let _2 = [4,5,6]; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -1666,7 +1679,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = ` } let _6 = scope['node'].children||[]; if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _8 = _6; + let _7 = _6; + let _8 = _6; if (!(_6 instanceof Array)) { _7 = Object.keys(_6); _8 = Object.values(_6); @@ -1746,7 +1760,8 @@ exports[`t-call (template calling recursive template, part 3 2`] = ` } let _6 = scope['node'].children||[]; if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _8 = _6; + let _7 = _6; + let _8 = _6; if (!(_6 instanceof Array)) { _7 = Object.keys(_6); _8 = Object.values(_6); @@ -1832,7 +1847,8 @@ exports[`t-call (template calling recursive template, part 4: with t-set recursi } let _6 = scope['node'].children||[]; if (!_6) { throw new Error('QWeb error: Invalid loop expression')} - let _7 = _8 = _6; + let _7 = _6; + let _8 = _6; if (!(_6 instanceof Array)) { _7 = Object.keys(_6); _8 = Object.values(_6); @@ -1998,7 +2014,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['list']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -2775,7 +2792,8 @@ exports[`t-key t-key directive in a list 1`] = ` let vn1 = h('ul', p1, c1); let _2 = scope['beers']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -2879,7 +2897,8 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = ` let vn1 = h('ul', p1, c1); let _2 = ['someval']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -3161,7 +3180,8 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['projects']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -3432,7 +3452,8 @@ exports[`t-ref refs in a loop 1`] = ` let vn1 = h('div', p1, c1); let _2 = scope['items']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); @@ -3676,7 +3697,8 @@ exports[`t-set t-set should reuse variable if possible 1`] = ` scope.v = 1; let _2 = scope['list']; if (!_2) { throw new Error('QWeb error: Invalid loop expression')} - let _3 = _4 = _2; + let _3 = _2; + let _4 = _2; if (!(_2 instanceof Array)) { _3 = Object.keys(_2); _4 = Object.values(_2); diff --git a/tests/qweb/__snapshots__/qweb_memory.test.ts.snap b/tests/qweb/__snapshots__/qweb_memory.test.ts.snap new file mode 100644 index 00000000..3b0b0892 --- /dev/null +++ b/tests/qweb/__snapshots__/qweb_memory.test.ts.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`memory t-foreach does not leak stuff in global scope 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let h = this.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('p', p1, c1); + let _2 = [3,2,1]; + if (!_2) { throw new Error('QWeb error: Invalid loop expression')} + let _3 = _2; + let _4 = _2; + if (!(_2 instanceof Array)) { + _3 = Object.keys(_2); + _4 = Object.values(_2); + } + let _length3 = _3.length; + let _origScope5 = scope; + scope = Object.create(scope); + for (let i1 = 0; i1 < _length3; i1++) { + scope.item_first = i1 === 0 + scope.item_last = i1 === _length3 - 1 + scope.item_index = i1 + scope.item = _3[i1] + scope.item_value = _4[i1] + let key1 = i1; + let _6 = scope['item']; + if (_6 != null) { + c1.push({text: _6}); + } + } + scope = _origScope5; + return vn1; +}" +`; diff --git a/tests/qweb/qweb_memory.test.ts b/tests/qweb/qweb_memory.test.ts new file mode 100644 index 00000000..abb24d1f --- /dev/null +++ b/tests/qweb/qweb_memory.test.ts @@ -0,0 +1,17 @@ +import { QWeb } from "../../src/qweb/index"; +import { renderToString } from "../helpers"; + +describe("memory", () => { + test("t-foreach does not leak stuff in global scope", () => { + let qweb = new QWeb(); + const initialNumberOfGlobals = Object.keys(window).length; + qweb.addTemplate( + "test", + `

` + ); + const result = renderToString(qweb, "test"); + const expected = `

321

`; + expect(result).toBe(expected); + expect(Object.keys(window).length).toBe(initialNumberOfGlobals); + }); +});