Use RecursionGuard to prevent stack exhaustion in global ancestors cache

This commit is contained in:
Mikhail Golubev
2014-10-20 18:02:07 +04:00
parent 4616dcae2b
commit 5d910d0e42
4 changed files with 63 additions and 6 deletions
@@ -6,11 +6,11 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.*;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.QualifiedName;
import com.intellij.psi.util.*;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
@@ -47,6 +47,8 @@ public class PyTypeInferenceFromUsedAttributesUtil {
);
private static final Logger LOG = Logger.getInstance(PyTypeInferenceFromUsedAttributesUtil.class);
private static final CachedAncestorsFastProvider ourCachedAncestorsProvider = new CachedAncestorsFastProvider();
private static final RecursionGuard ourRecursionGuard = RecursionManager.createGuard("py.types.from.attrs.rec.guard");
private PyTypeInferenceFromUsedAttributesUtil() {
// empty
@@ -103,13 +105,13 @@ public class PyTypeInferenceFromUsedAttributesUtil {
if (PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(candidate.getContainingFile())) {
continue;
}
if (getAllInheritedAttributeNames(candidate, context).containsAll(seenAttrs)) {
if (getAllInheritedAttributeNames(candidate).containsAll(seenAttrs)) {
suitableClasses.add(candidate);
}
}
for (PyClass candidate : Lists.newArrayList(suitableClasses)) {
for (PyClass ancestor : candidate.getAncestorClasses()) {
for (PyClass ancestor : getAncestorClassesFast(candidate)) {
if (suitableClasses.contains(ancestor)) {
suitableClasses.remove(candidate);
}
@@ -126,9 +128,9 @@ public class PyTypeInferenceFromUsedAttributesUtil {
}
@NotNull
private static Set<String> getAllInheritedAttributeNames(@NotNull PyClass candidate, @NotNull TypeEvalContext context) {
private static Set<String> getAllInheritedAttributeNames(@NotNull PyClass candidate) {
final Set<String> availableAttrs = Sets.newHashSet(getAllDeclaredAttributeNames(candidate));
for (PyClass parent : candidate.getAncestorClasses(context)) {
for (PyClass parent : getAncestorClassesFast(candidate)) {
availableAttrs.addAll(getAllDeclaredAttributeNames(parent));
}
return availableAttrs;
@@ -278,6 +280,37 @@ public class PyTypeInferenceFromUsedAttributesUtil {
}
}
@NotNull
private static Set<PyClass> getAncestorClassesFast(@NotNull PyClass pyClass) {
final CachedValuesManager manager = CachedValuesManager.getManager(pyClass.getProject());
return manager.getParameterizedCachedValue(pyClass, CachedAncestorsFastProvider.KEY, ourCachedAncestorsProvider, false, pyClass);
}
private static class CachedAncestorsFastProvider implements ParameterizedCachedValueProvider<Set<PyClass>, PyClass> {
static Key<ParameterizedCachedValue<Set<PyClass>, PyClass>> KEY = Key.create("py.types.from.attrs.cached.ancestors.fast");
@NotNull
@Override
public CachedValueProvider.Result<Set<PyClass>> compute(@NotNull PyClass pyClass) {
final HashSet<PyClass> result = Sets.newHashSet();
for (final PyClass baseClass : pyClass.getSuperClasses()) {
final Computable<Set<PyClass>> computable = new Computable<Set<PyClass>>() {
@Override
public Set<PyClass> compute() {
return getAncestorClassesFast(baseClass);
}
};
final Set<PyClass> baseClassAncestors = ourRecursionGuard.doPreventingRecursion(baseClass, false, computable);
result.add(baseClass);
if (baseClassAncestors != null) {
result.addAll(baseClassAncestors);
}
}
return CachedValueProvider.Result.create(Collections.unmodifiableSet(result),
PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
}
}
enum Priority {
BUILTIN,
SAME_FILE,
@@ -0,0 +1,9 @@
from module import A
class B(A):
def unique(self):
pass
x = undefined()
x.unique()
x
@@ -0,0 +1,4 @@
from main import B
class A(B):
pass
@@ -190,6 +190,17 @@ public class PyTypeFromUsedAttributesTest extends PyTestCase {
"list | MySortable | OtherClassA | OtherClassB | unknown");
}
public void testCyclicInheritance() {
myFixture.copyDirectoryToProject(getTestName(true), "");
myFixture.configureByFile("main.py");
final PyReferenceExpression referenceExpression = findLastReferenceByText("x");
assertNotNull(referenceExpression);
final TypeEvalContext context = TypeEvalContext.userInitiated(referenceExpression.getContainingFile()).withTracing();
final PyType actual = context.getType(referenceExpression);
final String actualType = PythonDocumentationProvider.getTypeName(actual, context);
assertEquals("unknown", actualType);
}
private void doTestType(@NotNull String text, @NotNull String expectedType) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final PyReferenceExpression referenceExpression = findLastReferenceByText("x");