Skip to content

fix(compiler): fix handling of escaped characters in the lexer - #28978

Closed
petebacondarwin wants to merge 5 commits into
angular:masterfrom
petebacondarwin:ngtsc-lexer-fix
Closed

petebacondarwin wants to merge 5 commits into
angular:masterfrom
petebacondarwin:ngtsc-lexer-fix

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented Feb 26, 2019

Copy link
Copy Markdown
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #28843

Previously the start of a character indicated by an escape sequence
was being incorrectly computed by the lexer, which caused tokens
to include the start of the escaped character sequence in the
preceding token. In particular this affected the name extracted
from opening tags if the name was terminated by an escape sequence.
For example, <t\n> would have the name t\ rather than t.

What is the new behavior?

This fix refactors the lexer to use a "cursor" object to iterate over
the characters in the template source. There are two cursor implementations,
one expects a simple string, the other expects a string that contains
JavaScript escape sequences that need to be unescaped.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Alternative to #28844

@petebacondarwin
petebacondarwin requested a review from a team February 26, 2019 13:45
@petebacondarwin petebacondarwin added type: bug/fix action: review The PR is still awaiting reviews from at least one requested reviewer effort2: days freq2: medium severity3: broken area: core Issues related to the framework runtime target: major This PR is targeted for the next major release risk: low labels Feb 26, 2019
@ngbot ngbot Bot added this to the Backlog milestone Feb 26, 2019

@cexbrayat cexbrayat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As I spent a few hours lately trying to fix this bug, I can only agree that the cursor abstraction makes the whole section much easier to understand, and feels less hacky than our initial ideas. 馃憤

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(unrelated) nit: the param is not correct anymore, should be options.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nor the _tokenizeIcu param either!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

in case this is unvolontary: it used to be forced to lowercase (the parameter of _consumeRawTextWithTagClose is still named lowercaseTagName).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I shall add a test to check :-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually the original code was a bit weird. It used the raw tag name in the opening tag token but then a lower-cased version in the closing tag token. For matching the opening tag to the closing tag it is using the _attemptStrCaseInsensitive() function anyway so it doesn't matter.

As the code is now, the closing tag token is using the same casing as the opening tag token - which I think is more consistent and correct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I changed the other occurrence of lowercaseTagName instead.

@alxhub alxhub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very nice work, I love the new CharacterCursor abstraction. Just a couple typings nits and a question about performance.

Comment thread packages/compiler/src/ml_parser/lexer.ts Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
clone(): CharacterCursor;
clone(): this;

@petebacondarwin petebacondarwin Feb 27, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried that too :-)

Unfortunately if you make the interface use this then the return value actually does have to be this (and not new SomeClass()). I.E. the following code is node invalid:

clone(): this { return new PlainCharacterCursor(this); }

You get the error:

Type 'PlainCharacterCursor' is not assignable to type 'this'.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems expensive (allocating on every advance). Can we avoid having two copies of the same state, or the need to copy between them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wondered about this but without testing it we don't really know if it is a performance hit. These objects are small and always have the same shape, so I wouldn't be surprised if the memory could be used efficiently.

I could definitely move the file, input and end properties out of the CursorState object, which would halve the number of properties that need copying.

Also I could move the copy of the object into the processEscapeSequence so that we only make a copy if we hit an escape. Otherwise the two object stay the same.

@petebacondarwin petebacondarwin added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Feb 27, 2019
@alxhub

alxhub commented Feb 27, 2019

Copy link
Copy Markdown
Member

Presubmit

The parts of a token are supposed to be an array of not-null strings,
but we were using `null` for tags that had no prefix. This has been
fixed to use the empty string in such cases, which allows the `null !`
hack to be removed.
Previously the start of a character indicated by an escape sequence
was being incorrectly computed by the lexer, which caused tokens
to include the start of the escaped character sequence in the
preceding token. In particular this affected the name extracted
from opening tags if the name was terminated by an escape sequence.
For example, `<t\n>` would have the name `t\` rather than `t`.

This fix refactors the lexer to use a "cursor" object to iterate over
the characters in the template source. There are two cursor implementations,
one expects a simple string, the other expects a string that contains
JavaScript escape sequences that need to be unescaped.
@IgorMinar IgorMinar added the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Feb 28, 2019
super(fileOrCursor);
this.internalState = {...fileOrCursor.internalState};
} else {
super(fileOrCursor, range !);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TS was not clever enough to know that we must have come via the second signature.

@IgorMinar

Copy link
Copy Markdown
Contributor

merge-assistance: g3 is good http://test/OCL:236077460:BASE:236077487:1551346219091:61b6ddf8

@IgorMinar IgorMinar closed this in 76979e1 Feb 28, 2019
IgorMinar pushed a commit that referenced this pull request Feb 28, 2019
Previously the start of a character indicated by an escape sequence
was being incorrectly computed by the lexer, which caused tokens
to include the start of the escaped character sequence in the
preceding token. In particular this affected the name extracted
from opening tags if the name was terminated by an escape sequence.
For example, `<t\n>` would have the name `t\` rather than `t`.

This fix refactors the lexer to use a "cursor" object to iterate over
the characters in the template source. There are two cursor implementations,
one expects a simple string, the other expects a string that contains
JavaScript escape sequences that need to be unescaped.

PR Close #28978
@petebacondarwin
petebacondarwin deleted the ngtsc-lexer-fix branch February 28, 2019 10:53
@angular-automatic-lock-bot

Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot Bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime cla: yes effort2: days freq2: medium merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note risk: low target: major This PR is targeted for the next major release type: bug/fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants