Skip to content

Commit

Permalink
Merge pull request #1152 from glimmerjs/bugfix/prevent-dynamic-invoca…
Browse files Browse the repository at this point in the history
…tion

[BUGFIX] Prevent accidental dynamic component lookup
  • Loading branch information
rwjblue authored Sep 2, 2020
2 parents e697711 + a8b53cd commit 574e36c
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 27 deletions.
15 changes: 15 additions & 0 deletions packages/@glimmer/integration-tests/lib/suites/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,21 @@ export class BasicComponents extends RenderTest {
this.assertStableRerender();
}

@test({
kind: 'glimmer',
})
'invoking dynamic component (path) via angle brackets does not work for string'() {
class TestHarness extends EmberishGlimmerComponent {
public Foo: any;
}
this.registerComponent('Glimmer', 'TestHarness', '<this.Foo />', TestHarness);
this.registerComponent('Glimmer', 'Foo', 'hello world!');

this.assert.throws(() => {
this.render('<TestHarness @Foo="Foo" />');
}, /Expected a curried component definition, but received Foo. You may have accidentally done <this.Foo>, where \"this.Foo\"/);
}

@test({
kind: 'glimmer',
})
Expand Down
1 change: 1 addition & 0 deletions packages/@glimmer/interfaces/lib/compile/encoder.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export interface DynamicComponentOp {
args: WireFormat.Core.Hash;
blocks: WireFormat.Core.Blocks | NamedBlocks;
atNames: boolean;
curried: boolean;
};
}

Expand Down
41 changes: 21 additions & 20 deletions packages/@glimmer/interfaces/lib/vm-opcodes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,26 +77,27 @@ export const enum Op {
PushDynamicComponentInstance = 78,
PushCurriedComponent = 79,
ResolveDynamicComponent = 80,
PushArgs = 81,
PushEmptyArgs = 82,
PopArgs = 83,
PrepareArgs = 84,
CaptureArgs = 85,
CreateComponent = 86,
RegisterComponentDestructor = 87,
PutComponentOperations = 88,
GetComponentSelf = 89,
GetComponentTagName = 90,
GetComponentLayout = 91,
BindEvalScope = 92,
SetupForEval = 93,
PopulateLayout = 94,
InvokeComponentLayout = 95,
BeginComponentTransaction = 96,
CommitComponentTransaction = 97,
DidCreateElement = 98,
DidRenderLayout = 99,
InvokePartial = 100,
ResolveCurriedComponent = 81,
PushArgs = 82,
PushEmptyArgs = 83,
PopArgs = 84,
PrepareArgs = 85,
CaptureArgs = 86,
CreateComponent = 87,
RegisterComponentDestructor = 88,
PutComponentOperations = 89,
GetComponentSelf = 90,
GetComponentTagName = 91,
GetComponentLayout = 92,
BindEvalScope = 93,
SetupForEval = 94,
PopulateLayout = 95,
InvokeComponentLayout = 96,
BeginComponentTransaction = 97,
CommitComponentTransaction = 98,
DidCreateElement = 99,
DidRenderLayout = 100,
InvokePartial = 101,
ResolveMaybeLocal = 102,
Debugger = 103,
Size = 104,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface AnyComponent {
export interface DynamicComponent extends AnyComponent {
definition: WireFormat.Expression;
atNames: boolean;
curried: boolean;
}

// (component)
Expand Down Expand Up @@ -250,7 +251,7 @@ export function InvokeStaticComponent({

export function InvokeDynamicComponent(
meta: ContainingMetadata,
{ definition, attrs, params, hash, atNames, blocks }: DynamicComponent
{ definition, attrs, params, hash, atNames, blocks, curried }: DynamicComponent
): StatementCompileActions {
return Replayable({
args: () => {
Expand All @@ -263,7 +264,9 @@ export function InvokeDynamicComponent(
body: () => {
return [
op(Op.JumpUnless, label('ELSE')),
op(Op.ResolveDynamicComponent, templateMeta(meta.referrer)),
curried
? op(Op.ResolveCurriedComponent)
: op(Op.ResolveDynamicComponent, templateMeta(meta.referrer)),
op(Op.PushDynamicComponentInstance),
InvokeComponent({
capabilities: true,
Expand Down
2 changes: 2 additions & 0 deletions packages/@glimmer/opcode-compiler/lib/syntax/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ export function populateBuiltins(
args: hash,
atNames: false,
blocks,
curried: false,
});
});

Expand All @@ -259,6 +260,7 @@ export function populateBuiltins(
hash,
atNames: false,
blocks: EMPTY_BLOCKS,
curried: false,
});
});

Expand Down
3 changes: 2 additions & 1 deletion packages/@glimmer/opcode-compiler/lib/syntax/push-compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function DynamicComponent(
context: TemplateCompilationContext,
action: DynamicComponentOp
): StatementCompileActions {
let { definition, attrs, params, args, blocks, atNames } = action.op1;
let { definition, attrs, params, args, blocks, atNames, curried } = action.op1;

let attrsBlock = attrs && attrs.length > 0 ? compilableBlock(attrs, context.meta) : null;

Expand All @@ -80,6 +80,7 @@ function DynamicComponent(
hash: args,
atNames,
blocks: compiled,
curried,
});
}

Expand Down
1 change: 1 addition & 0 deletions packages/@glimmer/opcode-compiler/lib/syntax/statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ STATEMENTS.add(SexpOpcodes.Component, ([, tag, attrs, args, blocks]) => {
args,
blocks,
atNames: true,
curried: true,
});
}
});
Expand Down
18 changes: 18 additions & 0 deletions packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ import { Tag, COMPUTE } from '@glimmer/validator';
import { PartialScopeImpl } from '../../scope';
import { VMArgumentsImpl } from '../../vm/arguments';
import { ComponentInstance, ComponentElementOperations } from './component';
import {
CurriedComponentDefinition,
isCurriedComponentDefinition,
} from '../../component/curried-component';

export const CheckTag: Checker<Tag> = CheckInterface({
[COMPUTE]: CheckFunction,
Expand Down Expand Up @@ -106,6 +110,20 @@ export const CheckComponentDefinition: Checker<ComponentDefinition> = CheckInter
manager: CheckComponentManager,
});

class CurriedComponentDefinitionChecker implements Checker<CurriedComponentDefinition> {
type!: CurriedComponentDefinition;

validate(value: unknown): value is CurriedComponentDefinition {
return isCurriedComponentDefinition(value);
}

expected(): string {
return `CurriedComponentDefinition`;
}
}

export const CheckCurriedComponentDefinition = new CurriedComponentDefinitionChecker();

export const CheckInvocation: Checker<Invocation> = CheckInterface({
handle: CheckNumber,
symbolTable: CheckProgramSymbolTable,
Expand Down
29 changes: 25 additions & 4 deletions packages/@glimmer/runtime/lib/compiled/opcodes/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
CheckInstanceof,
CheckInterface,
CheckProgramSymbolTable,
CheckString,
CheckOr,
} from '@glimmer/debug';
import {
Bounds,
Expand Down Expand Up @@ -74,6 +76,7 @@ import {
CheckFinishedComponentInstance,
CheckInvocation,
CheckReference,
CheckCurriedComponentDefinition,
} from './-debug-strip';
import { UpdateDynamicAttributeOpcode } from './dom';
import { DEBUG } from '@glimmer/env';
Expand Down Expand Up @@ -163,7 +166,10 @@ APPEND_OPCODES.add(Op.PushComponentDefinition, (vm, { op1: handle }) => {

APPEND_OPCODES.add(Op.ResolveDynamicComponent, (vm, { op1: _meta }) => {
let stack = vm.stack;
let component = valueForRef(check(stack.popJs(), CheckReference)) as Maybe<Dict>;
let component = check(
valueForRef(check(stack.popJs(), CheckReference)),
CheckOr(CheckString, CheckCurriedComponentDefinition)
);
let meta = vm[CONSTANTS].getValue(_meta);

vm.loadValue($t1, null); // Clear the temp register
Expand All @@ -174,12 +180,27 @@ APPEND_OPCODES.add(Op.ResolveDynamicComponent, (vm, { op1: _meta }) => {
let resolvedDefinition = resolveComponent(vm.runtime.resolver, component, meta);

definition = expect(resolvedDefinition, `Could not find a component named "${component}"`);
} else if (isCurriedComponentDefinition(component)) {
definition = component;
} else {
throw unreachable();
definition = component;
}

stack.pushJs(definition);
});

APPEND_OPCODES.add(Op.ResolveCurriedComponent, (vm) => {
let stack = vm.stack;
let ref = check(stack.popJs(), CheckReference);
let value = valueForRef(ref);

if (DEBUG && !isCurriedComponentDefinition(value)) {
throw new Error(
`Expected a curried component definition, but received ${value}. You may have accidentally done <${ref.debugLabel}>, where "${ref.debugLabel}" was a string instead of a curried component definition. You must use the {{component}} helper to create a component definition when invoking dynamically.`
);
}

let definition = value as CurriedComponentDefinition;

vm.loadValue($t1, null); // Clear the temp register
stack.pushJs(definition);
});

Expand Down

0 comments on commit 574e36c

Please sign in to comment.