Skip to content

Commit a06eab1

Browse files
rockymmatera
andauthored
respect $IterationLimit more places (#1477)
Respect $IterationLimit more places. Fixes #1476 There are probably more places where we need to do something similar, but this is a start. --------- Co-authored-by: Juan Mauricio Matera <[email protected]>
1 parent 14775b9 commit a06eab1

File tree

6 files changed

+60
-26
lines changed

6 files changed

+60
-26
lines changed

mathics/builtin/evaluation.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,18 @@ class IterationLimit(Predefined):
8484
Calculations terminated by '\$IterationLimit' return '\$Aborted':
8585
8686
>> $IterationLimit
87-
= 1000
87+
= 4096
88+
89+
The iteration limit protects against runaway definitions:
90+
91+
>> Block[{$IterationLimit = 20}, yin := yang; yang := yin; yin]
92+
: Iteration limit of 20 exceeded.
93+
= $Aborted
94+
8895
"""
8996

9097
name = "$IterationLimit"
91-
value = 1000
98+
value = 4096
9299

93100
rules = {
94101
"$IterationLimit": str(value),
@@ -186,7 +193,7 @@ class Evaluate(Builtin):
186193
>> f[1 + 2]
187194
= f[1 + 2]
188195
189-
'Evaluate' forces evaluation of the argument, even though $f$ has
196+
'Evaluate' forces evaluation of the argument, even though $f$ has \
190197
the 'HoldAll' attribute:
191198
>> f[Evaluate[1 + 2]]
192199
= f[3]
@@ -211,9 +218,9 @@ class Unevaluated(Builtin):
211218
<url>:WMA link:https://reference.wolfram.com/language/ref/Unevaluated.html</url>
212219
213220
<dl>
214-
<dt>'Unevaluated'[$expr$]
215-
<dd>temporarily leaves $expr$ in an unevaluated form when it
216-
appears as a function argument.
221+
<dt>'Unevaluated'[$expr$]
222+
<dd>temporarily leaves $expr$ in an unevaluated form when it \
223+
appears as a function argument.
217224
</dl>
218225
219226
'Unevaluated' is automatically removed when function arguments are

mathics/core/evaluation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ def __init__(
144144
self.quiet_all = False
145145
self.recursion_depth = 0
146146

147+
# iteration is used to keep track of evaluation chains and is
148+
# compared against $IterationLimit.
149+
self.iteration_count = 0
150+
147151
# Interrupt handlers may need access to the shell
148152
# that invoked the evaluation.
149153
self.shell = None

mathics/core/symbols.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# cython: language_level=3
22
# -*- coding: utf-8 -*-
33

4+
import sys
45
from typing import TYPE_CHECKING, Any, Dict, FrozenSet, List, Optional, Sequence, Union
56

67
from mathics.core.element import (
@@ -434,6 +435,23 @@ def evaluate(self, evaluation):
434435
if result is not None and not result.sameQ(self):
435436
if result.is_literal:
436437
return result
438+
439+
# We will be using $IterationLimit, not $RecursionLimit below
440+
# to catch symbolic looping rewrite expansions.
441+
# We do this to model Mathematica behavior more closely.
442+
limit = (
443+
evaluation.definitions.get_config_value("$IterationLimit")
444+
or sys.maxsize
445+
)
446+
if limit is None:
447+
limit = sys.maxsize
448+
if limit != sys.maxsize and evaluation.iteration_count > limit:
449+
evaluation.error("$IterationLimit", "itlim", limit)
450+
from mathics.core.systemsymbols import SymbolAborted
451+
452+
return SymbolAborted
453+
evaluation.iteration_count += 1
454+
437455
return result.evaluate(evaluation)
438456
return self
439457

mathics/session.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def reset(self, add_builtin=True, catch_interrupt=False):
143143
def evaluate(self, str_expression, timeout=None, form=None):
144144
"""Parse str_expression and evaluate using the `evaluate` method of the Expression"""
145145
self.evaluation.out.clear()
146+
self.evaluation.iteration_count = 0
146147
expr = parse(
147148
self.definitions,
148149
MathicsSingleLineFeeder(str_expression, ContainerKind.STREAM),
@@ -155,6 +156,7 @@ def evaluate(self, str_expression, timeout=None, form=None):
155156
def evaluate_as_in_cli(self, str_expression, timeout=None, form=None, src_name=""):
156157
"""This method parse and evaluate the expression using the session.evaluation.evaluate method"""
157158
self.evaluation.out = []
159+
self.evaluation.iteration_count = 0
158160
query = self.evaluation.parse(str_expression, src_name)
159161
if query is not None:
160162
res = self.evaluation.evaluate(query, timeout=timeout, format=form)

test/builtin/test_attributes.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"""
55

66
import os
7-
from test.helper import check_evaluation, check_evaluation_as_in_cli, session
7+
from test.helper import check_evaluation, check_evaluation_as_in_cli
88

99
import pytest
1010

@@ -229,57 +229,60 @@ def test_Attributes_wrong_args(str_expr, arg_count):
229229

230230

231231
@pytest.mark.parametrize(
232-
("str_expr", "msgs", "str_expected", "fail_msg"),
232+
("str_expr", "msgs", "str_expected", "assert_failure_msg"),
233233
[
234234
("CleanAll[u];CleanAll[v];", None, None, None),
235235
("SetAttributes[{u, v}, Flat];u[x_] := {x};u[]", None, "u[]", None),
236236
("u[a]", None, "{a}", None),
237237
("v[x_] := x;v[]", None, "v[]", None),
238238
("v[a]", None, "a", None),
239239
(
240-
"v[a, b]",
240+
"Block[{$IterationLimit = 40}, v[a, b]]",
241241
None,
242242
"v[a, b]",
243-
"in Mathematica: Iteration limit of 4096 exceeded.",
243+
"Test $IterationLimit catches unbounded expansion",
244244
),
245245
("CleanAll[u];CleanAll[v];", None, None, None),
246246
],
247247
)
248-
def test_private_doctests_attributes(str_expr, msgs, str_expected, fail_msg):
248+
def test_private_doctests_attributes(str_expr, msgs, str_expected, assert_failure_msg):
249249
""" """
250250
check_evaluation(
251251
str_expr,
252252
str_expected,
253253
to_string_expr=True,
254254
to_string_expected=True,
255255
hold_expected=True,
256-
failure_message=fail_msg,
256+
failure_message=assert_failure_msg,
257257
expected_messages=msgs,
258258
)
259259

260260

261261
@pytest.mark.parametrize(
262-
("str_expr", "msgs", "str_expected", "fail_msg"),
262+
("str_expr", "msgs", "str_expected", "assert_failure_msg"),
263263
[
264264
("CleanAll[u];CleanAll[v];", None, None, None),
265265
(
266-
"SetAttributes[{u, v}, Flat];u[x_] := {x};u[a, b]",
267-
("Iteration limit of 1000 exceeded.",),
266+
"Block[{$IterationLimit=30}, SetAttributes[{u, v}, Flat];u[x_] := {x};u[a, b]]",
267+
("Iteration limit of 30 exceeded.",),
268268
"$Aborted",
269-
None,
269+
"Test $IterationLimit catches unbounded expansion using SetDelayed, test 1.",
270+
),
271+
(
272+
"Block[{$IterationLimit=20}, u[a, b, c]]",
273+
("Iteration limit of 20 exceeded.",),
274+
"$Aborted",
275+
"Test $IterationLimit catches unbounded expansion in function call.",
270276
),
271-
("u[a, b, c]", ("Iteration limit of 1000 exceeded.",), "$Aborted", None),
272277
(
273-
"v[x_] := x;v[a,b,c]",
274-
("Iteration limit of 1000 exceeded.",),
278+
"Block[{$IterationLimit=20}, v[x_] := x;v[a,b,c]]",
279+
("Iteration limit of 20 exceeded.",),
275280
"$Aborted",
276-
"in Mathematica: Iteration limit of 4096 exceeded.",
281+
"Test $IterationLimit catches unbounded expansion using SetDelayed, test 2.",
277282
),
278283
("CleanAll[u];CleanAll[v];", None, None, None),
279284
],
280285
)
281-
def test_private_doctests_attributes_with_exceptions(
282-
str_expr, msgs, str_expected, fail_msg
283-
):
284-
"""These tests check the behavior of $RecursionLimit and $IterationLimit"""
285-
check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs)
286+
def test_IterationLimit(str_expr, msgs, str_expected, assert_failure_msg):
287+
"""Check the behavior of $RecursionLimit and $IterationLimit"""
288+
check_evaluation_as_in_cli(str_expr, str_expected, assert_failure_msg, msgs)

test/builtin/test_evaluation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
),
3737
(
3838
"ClearAll[f]; f[x_] := f[x + 1];f[x]",
39-
("Iteration limit of 1000 exceeded.",),
39+
("Iteration limit of 4096 exceeded.",),
4040
"$Aborted",
4141
None,
4242
),

0 commit comments

Comments
 (0)