Skip to content

Throw if a component produces an invalid render tree. Fixes #24579 #24650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 7, 2020

Conversation

SteveSandersonMS
Copy link
Member

The cause of #24579 was what I thought. Components aren't allowed to produce invalid trees - this has always been an unsupported situation. However it's unfortunate that developers can produce invalid trees using something as reasonable-seeming as a try/catch block, so we need to do something to prevent this class of problems.

Previously we didn't detect this scenario, so the diffing logic could just get stuck on it. With this PR we check whether there were any unclosed tree entries left behind, and if so, fails the entire rendering process with an exception, so the behavior is properly defined.

Longer term we might want to prevent the try/catch block at compile time in the Razor compiler, but this runtime check is still desirable, and is sufficient to block the situation.

@SteveSandersonMS SteveSandersonMS added area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly labels Aug 7, 2020
@SteveSandersonMS SteveSandersonMS requested a review from a team August 7, 2020 12:58
@SteveSandersonMS SteveSandersonMS added this to the 5.0.0-rc1 milestone Aug 7, 2020
builder.OpenComponent<OtherComponent>(2);
builder.CloseComponent();
builder.CloseRegion();
builder.CloseElement();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have validation for when the close statements balance out but are in different order? For example

builder.OpenElement()
builder.OpenRegion().
builder.CloseElement();
builder.CloseRegion()

Copy link
Member Author

@SteveSandersonMS SteveSandersonMS Aug 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point to raise, but I think we're OK with this one. In practice all of the Close methods will close whatever frame is outstanding on the stack, so even if you do deliberately get the order wrong it won't be harmful. The only weirdness you could cause by this would be failing to pass parameters to a child component because that's the only difference between the close methods.

Considering how perf-critical is this low-level part of rendering, we wouldn't want penalize people using .razor to produce normal components by introducing extra checking logic for cases that a valid .razor component could never trigger. We've always been clear that anyone writing rendertreebuilder logic manually is in the same situation as people writing C# logic manually: if you write a bug, you have a bug.

{
// It's never valid to leave an element/component/region unclosed. Doing so
// could cause undefined behavior in diffing.
ref var invalidFrame = ref _entries.Buffer[_openElementIndices.Peek()];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it always guaranteed that the index is going to be in range (assuming there are no bugs in the RenderTreeBuilder code) I believe that's the case, but given we are dealing with "failure modes" here I ask just in case to be more cautious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but even if not, the code here is certain to throw (either from the throw statement on L695, or an index-out-of-range on L694), which is all we need.

@javiercn
Copy link
Member

javiercn commented Aug 7, 2020

Previously we didn't detect this scenario, so the diffing logic could just get stuck on it. With this PR we check whether there were any unclosed tree entries left behind, and if so, fails the entire rendering process with an exception, so the behavior is properly defined.

Is this something worth patching in 3.1 with an app compat switch? (so that we can turn it on for 3.1 in the templates and users don't get surprised when it breaks later on)

@SteveSandersonMS
Copy link
Member Author

Is this something worth patching in 3.1 with an app compat switch? (so that we can turn it on for 3.1 in the templates and users don't get surprised when it breaks later on)

I don't know we have a mechanism for an appcompat switch given that this is purely a netstandard2.1 class library in 3.1 and doesn't depend on ASP.NET Core libraries.

@javiercn
Copy link
Member

javiercn commented Aug 7, 2020

I don't know we have a mechanism for an appcompat switch given that this is purely a netstandard2.1 class library in 3.1 and doesn't depend on ASP.NET Core libraries.

I was referring to https://docs.microsoft.com/en-us/dotnet/api/system.appcontext?view=netstandard-2.1, wouldn't this work?

@SteveSandersonMS
Copy link
Member Author

Maybe. It’s worth considering. We need to check if it has any meaningful impact on app size after linking. Might not do if other framework code already references it.

@SteveSandersonMS SteveSandersonMS merged commit 8ba7c7b into master Aug 7, 2020
@SteveSandersonMS SteveSandersonMS deleted the stevesa/reject-invalid-render-trees branch August 7, 2020 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants