diff --git a/ChangeLog b/ChangeLog index cd4d76eeb1..211fbb75e5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -157,6 +157,12 @@ Release Date: Unknown Close PyCQA/pylint#2159 + * Limit the maximum amount of interable result in an NodeNG.infer() call to + 100 by default for performance issues with variables with large amounts of + possible values. + The max inferable value can be tuned by setting the ASTROID_MAX_INFERABLE environment + variable at start up. + What's New in astroid 1.6.0? ============================ diff --git a/astroid/node_classes.py b/astroid/node_classes.py index a17af1a22e..019216a77e 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -323,7 +323,9 @@ def infer(self, context=None, **kwargs): if key in context.inferred: return iter(context.inferred[key]) - return context.cache_generator(key, self._infer(context, **kwargs)) + gen = context.cache_generator( + key, self._infer(context, **kwargs)) + return util.limit_inference(gen) def _repr_name(self): """Get a name for nice representation. diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py index 5ee53d1f0c..fc86b532a6 100644 --- a/astroid/tests/unittest_inference.py +++ b/astroid/tests/unittest_inference.py @@ -15,6 +15,7 @@ import sys from functools import partial import unittest +from unittest.mock import patch import pytest @@ -4656,5 +4657,34 @@ def f(**kwargs): assert next(extract_node(code).infer()).as_string() == "{'f': 1}" +def test_limit_inference_result_amount(): + """Test setting limit inference result amount""" + code = """ + args = [] + + if True: + args += ['a'] + + if True: + args += ['b'] + + if True: + args += ['c'] + + if True: + args += ['d'] + + args #@ + """ + result = extract_node(code).inferred() + assert len(result) == 16 + with patch('astroid.util.MAX_INFERABLE', 4): + result_limited = extract_node(code).inferred() + # Can't guarentee exact size + assert len(result_limited) < 16 + # Will not always be at the end + assert util.Uninferable in result_limited + + if __name__ == '__main__': unittest.main() diff --git a/astroid/util.py b/astroid/util.py index a53109707a..23d5975f7b 100644 --- a/astroid/util.py +++ b/astroid/util.py @@ -5,10 +5,12 @@ # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER import warnings +import os import importlib import lazy_object_proxy +MAX_INFERABLE = int(os.environ.get("ASTROID_MAX_INFERABLE", 100)) def lazy_descriptor(obj): class DescriptorProxy(lazy_object_proxy.Proxy): @@ -126,5 +128,20 @@ def proxy_alias(alias_name, node_type): return proxy(lambda: node_type) +def limit_inference(iterable): + """Limit inference to MAX_INFERABLE + + Limit inference amount to help with performance issues with + exponentially exploding possible results. + """ + count = 0 + for result in iterable: + count += 1 + if count > MAX_INFERABLE: + yield Uninferable + return + yield result + + # Backwards-compatibility aliases YES = Uninferable