Skip to content

Commit ec43f38

Browse files
authored
Uplift tests from fluent and fluent.js
* Uplift tests for selector expression validation, fixes #122 * Uplift billion laughs from fluent.js, abort early This ports projectfluent/fluent.js#395, which returns with an empty `FluentNone`, and does so early by raising an exception.
1 parent 5cb6d0e commit ec43f38

File tree

7 files changed

+63
-12
lines changed

7 files changed

+63
-12
lines changed

fluent.runtime/fluent/runtime/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ def format(self, message_id, args=None):
8181
env = ResolverEnvironment(context=self,
8282
current=CurrentEnvironment(args=fluent_args),
8383
errors=errors)
84-
return [resolve(env), errors]
84+
try:
85+
result = resolve(env)
86+
except ValueError as e:
87+
errors.append(e)
88+
result = '{???}'
89+
return [result, errors]
8590

8691
def _get_babel_locale(self):
8792
for l in self.locales:

fluent.runtime/fluent/runtime/resolver.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,13 @@ def __call__(self, env):
111111
if self in env.active_patterns:
112112
env.errors.append(FluentCyclicReferenceError("Cyclic reference"))
113113
return FluentNone()
114-
if env.part_count > self.MAX_PARTS:
115-
return ""
116114
env.active_patterns.add(self)
117115
elements = self.elements
118116
remaining_parts = self.MAX_PARTS - env.part_count
119117
if len(self.elements) > remaining_parts:
120-
elements = elements[:remaining_parts + 1]
121-
env.errors.append(ValueError("Too many parts in message (> {0}), "
122-
"aborting.".format(self.MAX_PARTS)))
118+
env.active_patterns.remove(self)
119+
raise ValueError("Too many parts in message (> {0}), "
120+
"aborting.".format(self.MAX_PARTS))
123121
retval = ''.join(
124122
resolve(element(env), env) for element in elements
125123
)
@@ -133,7 +131,10 @@ def resolve(fluentish, env):
133131
return fluentish.format(env.context._babel_locale)
134132
if isinstance(fluentish, six.string_types):
135133
if len(fluentish) > MAX_PART_LENGTH:
136-
return fluentish[:MAX_PART_LENGTH]
134+
raise ValueError(
135+
"Too many characters in placeable "
136+
"({}, max allowed is {})".format(len(fluentish), Pattern.MAX_PARTS)
137+
)
137138
return fluentish
138139

139140

fluent.runtime/tests/test_bomb.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ def setUp(self):
3232

3333
def test_max_length_protection(self):
3434
val, errs = self.ctx.format('lolz')
35-
self.assertEqual(val, ('0123456789' * 1000)[0:2500])
35+
self.assertEqual(val, '{???}')
3636
self.assertNotEqual(len(errs), 0)
37+
self.assertIn('Too many characters', str(errs[-1]))
3738

3839
def test_max_expansions_protection(self):
3940
# Without protection, emptylolz will take a really long time to
4041
# evaluate, although it generates an empty message.
4142
val, errs = self.ctx.format('emptylolz')
42-
self.assertEqual(val, '')
43+
self.assertEqual(val, '{???}')
4344
self.assertEqual(len(errs), 1)
45+
self.assertIn('Too many parts', str(errs[-1]))

fluent.syntax/fluent/syntax/errors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,6 @@ def get_error_message(code, args):
6565
return 'Unbalanced closing brace in TextElement.'
6666
if code == 'E0028':
6767
return 'Expected an inline expression'
68+
if code == 'E0029':
69+
return 'Expected simple expression as selector'
6870
return code

fluent.syntax/fluent/syntax/parser.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,11 +508,20 @@ def get_expression(self, ps):
508508
else:
509509
raise ParseError('E0018')
510510

511-
if (
511+
elif (
512512
isinstance(selector, ast.TermReference)
513-
and selector.attribute is None
514513
):
515-
raise ParseError('E0017')
514+
if selector.attribute is None:
515+
raise ParseError('E0017')
516+
elif not (
517+
isinstance(selector, (
518+
ast.StringLiteral,
519+
ast.NumberLiteral,
520+
ast.VariableReference,
521+
ast.FunctionReference,
522+
))
523+
):
524+
raise ParseError('E0029')
516525

517526
ps.next()
518527
ps.next()

fluent.syntax/tests/syntax/fixtures_reference/select_expressions.ftl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ invalid-selector-term-variant =
2121
*[key] value
2222
}
2323
24+
# ERROR Nested expressions are not valid selectors
25+
invalid-selector-select-expression =
26+
{ { 3 } ->
27+
*[key] default
28+
}
29+
30+
# ERROR Select expressions are not valid selectors
31+
invalid-selector-nested-expression =
32+
{ { $sel ->
33+
*[key] value
34+
} ->
35+
*[key] default
36+
}
37+
2438
empty-variant =
2539
{ $sel ->
2640
*[key] {""}

fluent.syntax/tests/syntax/fixtures_reference/select_expressions.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,24 @@
145145
"annotations": [],
146146
"content": "invalid-selector-term-variant =\n { -term(case: \"nominative\") ->\n *[key] value\n }\n\n"
147147
},
148+
{
149+
"type": "Comment",
150+
"content": "ERROR Nested expressions are not valid selectors"
151+
},
152+
{
153+
"type": "Junk",
154+
"annotations": [],
155+
"content": "invalid-selector-select-expression =\n { { 3 } ->\n *[key] default\n }\n\n"
156+
},
157+
{
158+
"type": "Comment",
159+
"content": "ERROR Select expressions are not valid selectors"
160+
},
161+
{
162+
"type": "Junk",
163+
"annotations": [],
164+
"content": "invalid-selector-nested-expression =\n { { $sel ->\n *[key] value\n } ->\n *[key] default\n }\n\n"
165+
},
148166
{
149167
"type": "Message",
150168
"id": {

0 commit comments

Comments
 (0)