Skip to content
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

LibWeb/CSS: Fix crash resolving calc with only percentages #3007

Merged

Conversation

Jaycadox
Copy link

@Jaycadox Jaycadox commented Dec 22, 2024

Fixes #3006 , (relates to) #648

From some analysis, it seems as if this issue only occurs with a only percentages in a calc.

  • calc(10%) crashes.
  • calc(10% + 10%) crashes.
  • calc(10% + 0px) doesn't crash.
  • calc(10vw) doesn't crash.

The resolve function returns a Length in the general case, but returns a Percentage if only percentages are provided.

As a sanity check, I looked at the code before it was changed (commit before change was here), and it only handled the case of percentages and lengths, as I do in this commit.

The old code being:

Optional<Length> CalculatedStyleValue::resolve_length_percentage(Length::ResolutionContext const& resolution_context, Length const& percentage_basis) const
{
    auto result = m_calculation->resolve(resolution_context, percentage_basis);

    return result.value().visit(
        [&](Length const& length) -> Optional<Length> {
            return length;
        },
        [&](Percentage const& percentage) -> Optional<Length> {
            return percentage_basis.percentage_of(percentage);
        },
        [&](auto const&) -> Optional<Length> {
            return {};
        });
}

Not sure if this is perfect, very open to constructive criticism.

@Jaycadox Jaycadox requested a review from AtkinsSJ as a code owner December 22, 2024 13:42
@gmta
Copy link
Collaborator

gmta commented Dec 22, 2024

Hi @Jaycadox, thanks for looking into this! Could you also add a test to expose the original issue?

Comment on lines 1893 to 1905
if (result.type().has_value() && (result.type()->matches_length() || result.type()->matches_percentage()))
return Length::make_px(CSSPixels { result.value() });
Copy link
Member

Choose a reason for hiding this comment

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

This isn't correct. If a percentage gets returned (eg, 35%) it'll return that number as a length (35px).

A more correct thing to do would be to return the percentage applied to the percentage_basis in that case.

The question is why this isn't getting converted to a length earlier on. NumericCalculationNode::resolve() should be doing that.

Copy link
Author

Choose a reason for hiding this comment

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

From my analysis, I believe it would actually return the correct resolved pixel value (as percentage_basis was being called on it in NumericCalculationNode::resolve), the issue was purely that it was being reported as the wrong type, and I do think that my original patch, did fix the specific issue, but agree that it didn't fix the underlying problem.

@Jaycadox Jaycadox force-pushed the fix-calc-percentage-crash branch from 233cdb3 to b307b43 Compare December 23, 2024 03:23
@Jaycadox
Copy link
Author

Jaycadox commented Dec 23, 2024

I had a look again at the issue, it seems that NumericCalculationNode::resolve, when given a percentage, would convert it to a pixel value (via percentage_of) but not update the CSSNumericType, so it would stay as Percentage. When putting the expression as a sum, SumCalculationNode would call add_the_types, and I think this would see the expression "Percentage" + Length, and set the output type to Length, thus fixing the bug.

In my new patch, I visit all the variants of the percentage basis and set the CSSNumericType explicitly, which fixes the bug, though I'm not sure if it makes sense to allow all of these types to be a percentage basis. Still open for feedback.

As the original code (which I put in my original message) only handles the case of Length and Percentage, and returns an empty Option if those weren't provided, I would think that it would make sense to only allow those two cases, though it would require some more code modification.

@Jaycadox Jaycadox requested a review from AtkinsSJ December 23, 2024 03:29
@Jaycadox Jaycadox force-pushed the fix-calc-percentage-crash branch 5 times, most recently from 64566e9 to 9cf2b6c Compare December 23, 2024 06:20
static Optional<CSSNumericType> numeric_type_from_calculated_style_value(CalculatedStyleValue::CalculationResult::Value const& value)
{
return value.visit(
[](Number const&) -> Optional<CSSNumericType> { return {}; },
Copy link
Author

Choose a reason for hiding this comment

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

I don't know what CSSNumericType should occur in the case of Number, I assume there isn't a set one and it can change. But this code should probably be corrected if there is one.

Copy link
Member

Choose a reason for hiding this comment

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

This is actually a duplicate of what's done in NumericCalculationNode::create(), maybe that code can be pulled out and reused.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the suggestion 👍

@Jaycadox Jaycadox force-pushed the fix-calc-percentage-crash branch 5 times, most recently from 6ad0bfd to 3798b2a Compare January 3, 2025 15:09
@Jaycadox
Copy link
Author

Jaycadox commented Jan 3, 2025

My bad, I believe I have now fixed the linting errors.

Copy link
Member

@AtkinsSJ AtkinsSJ left a comment

Choose a reason for hiding this comment

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

Thanks, this is looking good. (And I finally understand what I messed up originally!) Just a couple of comments:

  1. The commit message should be more descriptive of what the commit does. It's good to mention that it fixes a bug somewhere in the message, but the title itself should be what the code change is. Here, that'd be something like "Recalculate calc() numeric type when resolving percentages" (though you can probably word that better).
  2. There are a few VERIFY()s sprinkled about, which seem like they might be from your debugging and testing? It's fine to leave them in but I just want to check they're supposed to be permanent. :^)

So really just the commit message needs adjusting, then this should be good to merge. (And then you won't have to keep waiting for someone to manually run CI. 😅 )

@AtkinsSJ
Copy link
Member

AtkinsSJ commented Jan 4, 2025

Oh also, once a comment is resolved, press the [Resolve conversation] button on it. That makes it clear to reviewers that all the feedback has been dealt with.

@Jaycadox Jaycadox force-pushed the fix-calc-percentage-crash branch from 3798b2a to 50f7857 Compare January 4, 2025 14:02
@ladybird-bot
Copy link
Collaborator

Hello!

One or more of the commit messages in this PR do not match the Ladybird code submission policy, please check the lint_commits CI job for more details on which commits were flagged and why.
Please do not close this PR and open another, instead modify your commit message(s) with git commit --amend and force push those changes to update this PR.

Previously, `percentage_of` would be called on the previous value,
potentially changing its numeric type, yet this potential change
was never reflected as the old numeric type was always used. Now,
the numeric type will be re-calculated every time after the
percentage is resolved. As well, VERIFY checks have been placed to
uphold the requirements for the numeric types to match what the
actual values are.
@Jaycadox Jaycadox force-pushed the fix-calc-percentage-crash branch from 50f7857 to 8ba8166 Compare January 4, 2025 14:07
@Jaycadox
Copy link
Author

Jaycadox commented Jan 4, 2025

Thanks for the feedback, I believe that I have implemented your suggestions.

In this case, I left the VERIFY's on purpose, as I believe they express the intent, that is that the numeric type member should have to match the actual numeric type of the value.

@Jaycadox Jaycadox requested a review from AtkinsSJ January 4, 2025 14:11
Copy link
Member

@AtkinsSJ AtkinsSJ left a comment

Choose a reason for hiding this comment

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

Excellent! Thanks for that.

@AtkinsSJ AtkinsSJ enabled auto-merge (rebase) January 4, 2025 18:00
test(() => {
println(`PASS! (Didn't crash)`);
});
</script>
Copy link
Contributor

Choose a reason for hiding this comment

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

This file doesn't end with a new line. I'm surprised the CI didn't pick it up.

Copy link
Member

Choose a reason for hiding this comment

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

We don't worry about that for tests. Back in the day I think we manually removed the trailing newlines for layout tests just so they wouldn't add the extra text node.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh ok, I'm always fighting the pre-commit hook when I import WPT tests that don't have one.

Copy link
Member

Choose a reason for hiding this comment

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

That's odd, maybe your commit hook is outdated? I don't think I've had that issue but maybe I've been importing tests that don't lack the newline.

@AtkinsSJ AtkinsSJ merged commit db58986 into LadybirdBrowser:master Jan 4, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Crash when resolving transform: translateX(calc(10%))
5 participants