Skip to content

Commit

Permalink
bug #35707 [ExpressionLanguage] Fixed collisions of character operato…
Browse files Browse the repository at this point in the history
…rs with object properties (Andrej-in-ua)

This PR was squashed before being merged into the 3.4 branch.

Discussion
----------

[ExpressionLanguage] Fixed collisions of character operators with object properties

| Q             | A
| ------------- | ---
| Branch?       |  3.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | n/a
| License       | MIT
| Doc PR        | n/a

Expression `foo.not in [bar]` compiles to invalid php code:

```
$foo->not in[$bar]
```

Added check for absence of a dot before of the character operators.

PS. I apologize for not starting the issue before create PR. I considered this bug is minor, but obvious.

Commits
-------

4b83ae7 [ExpressionLanguage] Fixed collisions of character operators with object properties
  • Loading branch information
fabpot committed Feb 23, 2020
2 parents 643f34f + 4b83ae7 commit 1676e3a
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/Symfony/Component/ExpressionLanguage/Lexer.php
Expand Up @@ -73,7 +73,7 @@ public function tokenize($expression)
// strings
$tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
$cursor += \strlen($match[0]);
} elseif (preg_match('/not in(?=[\s(])|\!\=\=|not(?=[\s(])|and(?=[\s(])|\=\=\=|\>\=|or(?=[\s(])|\<\=|\*\*|\.\.|in(?=[\s(])|&&|\|\||matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) {
} elseif (preg_match('/(?<=^|[\s(])not in(?=[\s(])|\!\=\=|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\>\=|(?<=^|[\s(])or(?=[\s(])|\<\=|\*\*|\.\.|(?<=^|[\s(])in(?=[\s(])|&&|\|\||(?<=^|[\s(])matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) {
// operators
$tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
$cursor += \strlen($match[0]);
Expand Down
Expand Up @@ -15,9 +15,13 @@

$regex = [];
foreach ($operators as $operator => $length) {
// an operator that ends with a character must be followed by
// a whitespace or a parenthesis
$regex[] = preg_quote($operator, '/').(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : '');
// Collisions of character operators:
// - an operator that begins with a character must have a space or a parenthesis before or starting at the beginning of a string
// - an operator that ends with a character must be followed by a whitespace or a parenthesis
$regex[] =
(ctype_alpha($operator[0]) ? '(?<=^|[\s(])' : '')
.preg_quote($operator, '/')
.(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : '');
}

echo '/'.implode('|', $regex).'/A';
Expand Up @@ -233,6 +233,17 @@ public function testCachingWithDifferentNamesOrder()
$expressionLanguage->compile($expression, ['B' => 'b', 'a']);
}

public function testOperatorCollisions()
{
$expressionLanguage = new ExpressionLanguage();
$expression = 'foo.not in [bar]';
$compiled = $expressionLanguage->compile($expression, ['foo', 'bar']);
$this->assertSame('in_array($foo->not, [0 => $bar])', $compiled);

$result = $expressionLanguage->evaluate($expression, ['foo' => (object) ['not' => 'test'], 'bar' => 'test']);
$this->assertTrue($result);
}

/**
* @dataProvider getRegisterCallbacks
*/
Expand Down
12 changes: 12 additions & 0 deletions src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php
Expand Up @@ -112,6 +112,18 @@ public function getTokenizeData()
[new Token('string', '#foo', 1)],
'"#foo"',
],
[
[
new Token('name', 'foo', 1),
new Token('punctuation', '.', 4),
new Token('name', 'not', 5),
new Token('operator', 'in', 9),
new Token('punctuation', '[', 12),
new Token('name', 'bar', 13),
new Token('punctuation', ']', 16),
],
'foo.not in [bar]',
],
];
}
}
33 changes: 33 additions & 0 deletions src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
Expand Up @@ -53,6 +53,9 @@ public function getParseData()
$arguments->addElement(new Node\ConstantNode(2));
$arguments->addElement(new Node\ConstantNode(true));

$arrayNode = new Node\ArrayNode();
$arrayNode->addElement(new Node\NameNode('bar'));

return [
[
new Node\NameNode('a'),
Expand Down Expand Up @@ -151,6 +154,36 @@ public function getParseData()
'bar',
['foo' => 'bar'],
],

// Operators collisions
[
new Node\BinaryNode(
'in',
new Node\GetAttrNode(
new Node\NameNode('foo'),
new Node\ConstantNode('not', true),
new Node\ArgumentsNode(),
Node\GetAttrNode::PROPERTY_CALL
),
$arrayNode
),
'foo.not in [bar]',
['foo', 'bar'],
],
[
new Node\BinaryNode(
'or',
new Node\UnaryNode('not', new Node\NameNode('foo')),
new Node\GetAttrNode(
new Node\NameNode('foo'),
new Node\ConstantNode('not', true),
new Node\ArgumentsNode(),
Node\GetAttrNode::PROPERTY_CALL
)
),
'not foo or foo.not',
['foo'],
],
];
}

Expand Down

0 comments on commit 1676e3a

Please sign in to comment.