Skip to content

Commit 83efc8b

Browse files
committed
Fix compat lookup stopping on an inherited property name
The depth walk in container.lookup tested `name in d`, which also matches inherited properties. lookupProperty then denies access to them, so the walk returned undefined from a depth that could not supply the name instead of continuing to the parent depth that owned it. Any field named after an Object.prototype member (toString, constructor, valueOf, hasOwnProperty) stopped resolving in compat mode. Testing with lookupProperty and falling back to hasOwnProperty keeps the behaviour #2151 added, where an own property whose value is null or undefined still wins over a parent depth, and keeps a prototype property that the prototype access control allows.
1 parent 13a7a67 commit 83efc8b

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

lib/handlebars/runtime.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,22 @@ export function template(templateSpec, env) {
146146
const d = depths[i];
147147
if (d == null) continue;
148148
if (typeof d === 'object' || typeof d === 'function') {
149-
if (Utils.isMap(d) ? d.has(name) : name in d) {
150-
return container.lookupProperty(d, name);
149+
if (Utils.isMap(d)) {
150+
if (d.has(name)) {
151+
return container.lookupProperty(d, name);
152+
}
153+
} else {
154+
// `name in d` would also match inherited properties, which
155+
// lookupProperty then denies, stopping the walk on a depth that
156+
// cannot supply the name. Own properties still win even when their
157+
// value is null or undefined.
158+
const result = container.lookupProperty(d, name);
159+
if (
160+
result !== undefined ||
161+
Object.prototype.hasOwnProperty.call(d, name)
162+
) {
163+
return result;
164+
}
151165
}
152166
} else {
153167
const result = container.lookupProperty(d, name);

spec/blocks.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,31 @@ describe('blocks', function () {
266266
.toCompileTo('');
267267
});
268268

269+
it('should find a parent value named like an Object.prototype member', function () {
270+
expectTemplate('{{#each items}}{{constructor}}/{{valueOf}}{{/each}}')
271+
.withCompileOptions({ compat: true })
272+
.withInput({
273+
constructor: 'root-constructor',
274+
valueOf: 'root-valueOf',
275+
items: [{ id: 1 }],
276+
})
277+
.toCompileTo('root-constructor/root-valueOf');
278+
});
279+
280+
it('should stop at a depth whose match is an allowed prototype method', function () {
281+
class Item {
282+
greeting() {
283+
return 'item-greeting';
284+
}
285+
}
286+
287+
expectTemplate('{{#each items}}{{greeting}}{{/each}}')
288+
.withCompileOptions({ compat: true })
289+
.withInput({ greeting: 'root-greeting', items: [new Item()] })
290+
.withRuntimeOptions({ allowedProtoMethods: { greeting: true } })
291+
.toCompileTo('item-greeting');
292+
});
293+
269294
it('block with deep recursive lookup lookup', function () {
270295
expectTemplate(
271296
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'

0 commit comments

Comments
 (0)