Bug 52542 - SVG CSS property types with <number> don't support exponents
Summary: SVG CSS property types with <number> don't support exponents
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: SVG (show other bugs)
Version: 528+ (Nightly build)
Hardware: PC OS X 10.5
: P2 Normal
Assignee: Dirk Schulze
URL: http://dev.w3.org/SVG/profiles/1.1F2/...
Keywords:
Depends on:
Blocks:
 
Reported: 2011-01-16 12:39 PST by Dirk Schulze
Modified: 2012-07-18 10:40 PDT (History)
9 users (show)

See Also:


Attachments
Patch (20.91 KB, patch)
2012-03-31 09:11 PDT, Dirk Schulze
no flags Details | Formatted Diff | Diff
Patch (20.94 KB, patch)
2012-03-31 09:18 PDT, Dirk Schulze
no flags Details | Formatted Diff | Diff
Patch (26.56 KB, patch)
2012-03-31 20:25 PDT, Dirk Schulze
no flags Details | Formatted Diff | Diff
Patch (22.93 KB, patch)
2012-04-01 08:33 PDT, Dirk Schulze
no flags Details | Formatted Diff | Diff
Patch (14.14 KB, patch)
2012-07-11 10:07 PDT, Dirk Schulze
zimmermann: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Dirk Schulze 2011-01-16 12:39:03 PST
In SVG the type <length> can have exponents: integer ([e|E] interger)? see http://www.w3.org/TR/SVG/types.html#DataTypeNumber
We support this for SVG attributes, but not for SVG CSS properties like 'stroke-width', because we use the CSS parser and that parser doesn't support this.
See attached link for an example.
Comment 1 Dirk Schulze 2011-01-16 12:39:33 PST
Rob do you want to take a look at this as well? ;-)
Comment 2 Rob Buis 2011-01-16 23:08:11 PST
Hi Dirk,

(In reply to comment #1)
> Rob do you want to take a look at this as well? ;-)

Good find! This is very interesting to work on, you can assign to me if you are busy with the animation stuff, I can look into it this week.
Cheers,

Rob.
Comment 3 Dirk Schulze 2011-01-17 00:21:00 PST
(In reply to comment #2)
> Hi Dirk,
> 
> (In reply to comment #1)
> > Rob do you want to take a look at this as well? ;-)
> 
> Good find! This is very interesting to work on, you can assign to me if you are busy with the animation stuff, I can look into it this week.
Sure, I'm happy if you're working on it :-)
Comment 4 Dirk Schulze 2011-01-21 01:14:22 PST
Have to extend this bug. It's not only <length> that can take exponents, all CSS properties with <number> can take exponents: <angle>, <length>, <frequency>, <icccolor>, <percentage>, <time>. Some of them are defined by CSS3 as well, but without of exponents. Means we have to differ <number> for CSS/HTML and for CSS/SVG during parsing numbers.

CSS3: http://www.w3.org/TR/css3-values/#ltnumbergt
SVG:  http://www.w3.org/TR/SVG/types.html#DataTypeNumber
Comment 5 Dirk Schulze 2011-01-21 03:19:42 PST
(In reply to comment #4)
> CSS3: http://www.w3.org/TR/css3-values/#ltnumbergt
> SVG:  http://www.w3.org/TR/SVG/types.html#DataTypeNumber

Ouch! Only if the value gets applied to the SVG _attribute_ it must allow exponent numbers, but it should _not_ on setting the property with CSS!

<rect width="100" height="100" stroke-width="1e1px"> is allowed
<rect width="100" height="100" style="stroke-width:1e1px;"> is not allowed.
Comment 6 Dirk Schulze 2012-03-30 20:13:33 PDT
Will upload a fix for that soon. Have it running in a local build. Just needs more testing.
Comment 7 Dirk Schulze 2012-03-31 09:11:30 PDT
Created attachment 134958 [details]
Patch
Comment 8 Dirk Schulze 2012-03-31 09:18:54 PDT
Created attachment 134960 [details]
Patch

Forgot to add original author of the patch to the changelog :P.
Comment 9 Nikolas Zimmermann 2012-03-31 11:03:11 PDT
Comment on attachment 134960 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=134960&action=review

Nice job, still some caveats, r- for test coverage:

> Source/WebCore/css/CSSParser.cpp:8603
> +            int offset = 1;

why int? I'd use unsigned.

> Source/WebCore/css/CSSParser.cpp:8605
> +            if (m_currentCharacter[offset] == '+') {

Is this safe to access? Is it guaranteed that m_currentCharacter isn't accessed out of boundaries, aka. that 'e' is always followed by another character.

> Source/WebCore/css/CSSParser.cpp:8606
> +                offset++;

++offset.

> Source/WebCore/css/CSSParser.cpp:8609
> +                offset++;

Ditto.

> Source/WebCore/css/CSSParser.cpp:8612
> +            double exponent;

No need for this local here, just declare it in the if (offset > digits) branch directly,

> Source/WebCore/css/CSSParser.cpp:8616
> +                exponent = charactersToDouble(m_currentCharacter + digits, offset - digits);

Can this yield "invalid" doubles, is this clamped somehow? Shall we sanitize the parsed exponent here? (thinking of NaN handling, or Inf)

> LayoutTests/svg/css/script-tests/scientific-numbers.js:13
> +shouldBeEqualToString("document.defaultView.getComputedStyle(text, null).fontSize", "16px");

You can just use "getComputedStyle(text).fontSize".

> LayoutTests/svg/css/script-tests/scientific-numbers.js:232
> +

This needs to be extended;
leading whitespace, trailing whitespace, mid white-space (" 5e10", " 5e1 0", etc. (also including negative values, for both number and exponent).
Arbitary characters inside of exponent, exponents leading to double overflow, etc.
Comment 10 Dirk Schulze 2012-03-31 20:25:49 PDT
Created attachment 134986 [details]
Patch

New approach: Use SVG parser to parse numbers. The logic there is well tested and has a lot of more checks to make sure that we don't miss a case. Added more tests for some edge cases as well as overflow tests. isASCIIDigit() and equal checks prevent us from reading data outside the buffer.
Comment 11 Nikolas Zimmermann 2012-04-01 04:43:24 PDT
(In reply to comment #10)
> Created an attachment (id=134986) [details]
> Patch
> 
> New approach: Use SVG parser to parse numbers. The logic there is well tested and has a lot of more checks to make sure that we don't miss a case. Added more tests for some edge cases as well as overflow tests.
Great, I'll look at thew new patch soon.

> isASCIIDigit() and equal checks prevent us from reading data outside the buffer.
This still worries me. I'll check if the new patch is safer.
Comment 12 Nikolas Zimmermann 2012-04-01 04:53:11 PDT
Comment on attachment 134986 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=134986&action=review

I have some new comments:

> Source/WebCore/css/CSSParser.cpp:79
> +#if ENABLE(SVG)
> +#include "SVGParserUtilities.h"
> +#endif

I don't think you need to guard this.

> Source/WebCore/css/CSSParser.cpp:8606
> +            if (isASCIIAlphaCaselessEqual(*m_currentCharacter, 'e')) {

At this point it's guaranteed that m_currentCharacter exists okay. So dereferencing the UChar* pointer is fine.

> Source/WebCore/css/CSSParser.cpp:8607
> +                if (m_currentCharacter[1] == '-' || m_currentCharacter[1] == '+' || isASCIIDigit(m_currentCharacter[1])) {

But who guarantees that the UChar* m_currentCharacter + 1 that you're dereferencing here exists?

The old code passes m_tokenStart to charactersToDouble as const UChar* parameter, and m_currentCharacter - m_tokenStart as size.
I'd strongly suggest to reuse that way of accessing the string.
const UChar* currentCharacter = m_tokenStart;
unsigned length = m_currentCharacter - m_tokenStart;
if (length > 1 && isASCIIAlphaCaselessEqual(*currentCharacter, 'e')) {
// Now its guaranteed that the passed in string is at least 'eX' (e + another item)
}

If I'm missing something, and this is already guaranteed by the CSSParser code, you should at least assert that the length is > 1.

> Source/WebCore/css/CSSParser.cpp:8612
> +                while (isASCIIDigit(m_currentCharacter[offset]))

If the branch above isn't taken that means the first character following the 'e' is not '+', not '-' nor an ascii digit - why do you continue parsing at this point? You could have exited already.

> Source/WebCore/css/CSSParser.cpp:8615
> +            if (!parseSVGNumber(m_tokenStart, m_currentCharacter + offset - m_tokenStart, yylval->number))

This would then read as if (!parseSVGNumber(m_tokenStart, length + offset, yylval->number).

> Source/WebCore/css/CSSParser.cpp:8616
> +                break;

If parseSVGNumber() fails, is yylval->number reset to a specific value? (Not sure about that, that's why I'm asking).

> Source/WebCore/svg/SVGParserUtilities.cpp:152
> +

One newline too much.

> LayoutTests/svg/css/script-tests/scientific-numbers.js:13
> +shouldBeEqualToString("document.defaultView.getComputedStyle(text).fontSize", "16px");

You still use the long form here, getComputedStyle(text).fontSize should work, without the document.defaultView prefix :-)
Comment 13 Dirk Schulze 2012-04-01 08:20:37 PDT
(In reply to comment #12)
> (From update of attachment 134986 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=134986&action=review
> 
> I have some new comments:
> 
> > Source/WebCore/css/CSSParser.cpp:79
> > +#if ENABLE(SVG)
> > +#include "SVGParserUtilities.h"
> > +#endif
> 
> I don't think you need to guard this.
It is not necessary but prevent including the file on compiling. That was the intention.

> 
> > Source/WebCore/css/CSSParser.cpp:8606
> > +            if (isASCIIAlphaCaselessEqual(*m_currentCharacter, 'e')) {
> 
> At this point it's guaranteed that m_currentCharacter exists okay. So dereferencing the UChar* pointer is fine.
> 
> > Source/WebCore/css/CSSParser.cpp:8607
> > +                if (m_currentCharacter[1] == '-' || m_currentCharacter[1] == '+' || isASCIIDigit(m_currentCharacter[1])) {
> 
> But who guarantees that the UChar* m_currentCharacter + 1 that you're dereferencing here exists?
That is the code before these lines:

        while (true) {
            if (!isASCIIDigit(m_currentCharacter[0])) {
                // Only one dot is allowed for a number,
                // and it must be followed by a digit.
                if (m_currentCharacter[0] != '.' || dotSeen || !isASCIIDigit(m_currentCharacter[1]))
                    break;
                dotSeen = true;
            }
            ++m_currentCharacter;
        }

So no extra check. The string gets terminated by a \n. Just checked it. Therefore is should be save to check the value for 1, since 0 was no /n.

> 
> The old code passes m_tokenStart to charactersToDouble as const UChar* parameter, and m_currentCharacter - m_tokenStart as size.
> I'd strongly suggest to reuse that way of accessing the string.
> const UChar* currentCharacter = m_tokenStart;
> unsigned length = m_currentCharacter - m_tokenStart;
> if (length > 1 && isASCIIAlphaCaselessEqual(*currentCharacter, 'e')) {
> // Now its guaranteed that the passed in string is at least 'eX' (e + another item)
> }
But just for the first char after the 'e'. Like I said, this is not necessary, since the last char is a \n on the string.

> 
> If I'm missing something, and this is already guaranteed by the CSSParser code, you should at least assert that the length is > 1.
Not necessary.

> 
> > Source/WebCore/css/CSSParser.cpp:8612
> > +                while (isASCIIDigit(m_currentCharacter[offset]))
> 
> If the branch above isn't taken that means the first character following the 'e' is not '+', not '-' nor an ascii digit - why do you continue parsing at this point? You could have exited already.
Well, not exiting. If I break I would break stuff. The unit is included in the token (10em wouldn't work, which gets tested by the new test as well). I could put the while loop in the else tree.

> 
> > Source/WebCore/css/CSSParser.cpp:8615
> > +            if (!parseSVGNumber(m_tokenStart, m_currentCharacter + offset - m_tokenStart, yylval->number))
> 
> This would then read as if (!parseSVGNumber(m_tokenStart, length + offset, yylval->number).
> 
> > Source/WebCore/css/CSSParser.cpp:8616
> > +                break;
> 
> If parseSVGNumber() fails, is yylval->number reset to a specific value? (Not sure about that, that's why I'm asking).
m_token get's not set and therefore the value is invalid.

> 
> > Source/WebCore/svg/SVGParserUtilities.cpp:152
> > +
> 
> One newline too much.
Thanks. Will fix that.

> 
> > LayoutTests/svg/css/script-tests/scientific-numbers.js:13
> > +shouldBeEqualToString("document.defaultView.getComputedStyle(text).fontSize", "16px");
> 
> You still use the long form here, getComputedStyle(text).fontSize should work, without the document.defaultView prefix :-)
That was a misunderstanding. I thought you meant the ", null)" on computed style which I fixed. Will fix that.
Comment 14 Dirk Schulze 2012-04-01 08:33:56 PDT
Created attachment 135000 [details]
Patch

Patch. See previous comment.
Comment 15 Nikolas Zimmermann 2012-04-25 09:57:49 PDT
Comment on attachment 135000 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=135000&action=review

Next round of comments, poor Dirk :-)

> Source/WebCore/css/CSSParser.cpp:8604
> +            if (isASCIIAlphaCaselessEqual(*m_currentCharacter, 'e')) {

Are we sure that at least one element follows the 'e' ?
Can we ASSERT(m_currentCharacter - m_tokenStart > 1) here?
Before accessing: if (m_currentCharacter[1].. below

> Source/WebCore/css/CSSParser.cpp:8612
> +                if (m_currentCharacter[1] == '-' || m_currentCharacter[1] == '+' || isASCIIDigit(m_currentCharacter[1])) {
> +                    offset += 2;
> +                    while (isASCIIDigit(m_currentCharacter[offset]))
> +                        ++offset;
> +                    // Use FLOATTOKEN if we have exponents.
> +                    dotSeen = true;
> +                }
> +            }

++m_currentCharacter;
if (*m_currentCharacter == '-' || *m_currentCharacter == '+' || isASCIIDigit(*m_currentCharacter)) {
    ++m_currentCharacter;
    while (isASCIIDigit(*m_currentCharacter))
        ++m_currentCharacter;
    // Use FLOATTOKEN if the string contains exponents.
    dotSeen = true;
}

if (!parseNumber(m_tokenStart, m_currentCharacter, yylval->number, false))
    break;

You'll need a new parseNumber() variant taking a double&, but you can remove the new parseSVGNumber() method that you've added.

> Source/WebCore/svg/SVGParserUtilities.h:36
> +bool parseSVGNumber(UChar*& ptr, size_t length, double& number);

Can be avoided.

> LayoutTests/svg/css/script-tests/scientific-numbers.js:274
> +text.setAttribute("font-size", "50e0.0");
> +shouldBeEqualToString("getComputedStyle(text).fontSize", "16px");

Hard to understand the test, without seeing what you're trying to assign.

I propose following:
function setFontSize(valueString) {
    text.setAttribute("font-size", valueString);
}

function test(valueString, expectedString) {
    debug("text.setAttribute('font-size', '" + valueString + "');");
    setFontSize(valueString);
    shouldBeEqualToString("getComputedStyle(text).fontSize", expectedString);
}

and use:

debug("Valid values");
test("50px", "50px");
test("100px", "100px");

setFontSize("16px");

debug("");
debug("Invalid values");
test("50e0.0", "16px");
test("50e 0", "16px");
....

setFontSize("16px");

debug("");
debug("Next category...");
Comment 16 Dirk Schulze 2012-07-11 01:26:40 PDT
(In reply to comment #15)

Sorry. I am looking at this after a long time.

> if (!parseNumber(m_tokenStart, m_currentCharacter, yylval->number, false))
>     break;
> 

IIRC the problem was that m_tokenStart and m_currentCharacter are of type UChar*, while parseNumber takes const UChar* (and const UChar*&). So I would need a type conversion which does not look pretty.

> You'll need a new parseNumber() variant taking a double&, but you can remove the new parseSVGNumber() method that you've added.

How could I avoid it? Even for parseNumber with a double I would need a new method. I choose parseSVGNumber to indicate from the name that it will be a number that is parsed according to the SVG rules. I still think that this is the proper solution. Especially here where it is not obvious that we call the method in SVGParserUtilities.

> Hard to understand the test, without seeing what you're trying to assign.

Thanks, I'll change the test and the other code suggestions.
Comment 17 Dirk Schulze 2012-07-11 10:07:17 PDT
Created attachment 151721 [details]
Patch
Comment 18 Nikolas Zimmermann 2012-07-18 03:23:38 PDT
Comment on attachment 151721 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=151721&action=review

Looks reasonable, r=me.

> Source/WebCore/ChangeLog:10
> +        The SVG parser is already well tested and has some extra checks for edge cases that prevent
> +        us e.g for overflows.

prevent from overflow, or rather protect from overflow
Comment 19 Dirk Schulze 2012-07-18 08:43:02 PDT
Committed r122978: <http://trac.webkit.org/changeset/122978>
Comment 20 Tony Chang 2012-07-18 10:31:09 PDT
Did you mean for this to be a pixel test?  It looks like including SVGTestCase.js sets "window.enablePixelTesting = true" which causes pixel results to be dumped.
Comment 21 Dirk Schulze 2012-07-18 10:40:06 PDT
(In reply to comment #20)
> Did you mean for this to be a pixel test?  It looks like including SVGTestCase.js sets "window.enablePixelTesting = true" which causes pixel results to be dumped.

No, should be dumpAsText. thanks