Skip to content

Commit 28278a8

Browse files
committed
Add tooling to allow a global check for hash instability.
1 parent 3459e6a commit 28278a8

File tree

4 files changed

+67
-1
lines changed

4 files changed

+67
-1
lines changed

typed_python/CompilerVisibleObjectVisitor.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ class CompilerVisibleObjectVisitor {
287287
return records;
288288
}
289289

290+
void resetCache() {
291+
mPastVisits.clear();
292+
}
293+
290294
void checkForInstability() {
291295
std::vector<TypeOrPyobj> unstable;
292296

typed_python/MutuallyRecursiveTypeGroup.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ void MutuallyRecursiveTypeGroup::computeHash() {
466466
}
467467

468468
if (mIsCurrentlyHashing) {
469+
CompilerVisibleObjectVisitor::singleton().checkForInstability();
470+
469471
throw std::runtime_error(
470472
"Somehow we are already computing the hash of this MRTG. "
471473
"This means that when we computed the group's constituents, we missed "

typed_python/_types.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "PySlab.hpp"
3939
#include "PyModuleRepresentation.hpp"
4040
#include "_types.hpp"
41+
#include "CompilerVisibleObjectVisitor.hpp"
4142

4243
PyObject *MakeTupleOrListOfType(PyObject* nullValue, PyObject* args, bool isTuple) {
4344
std::vector<Type*> types;
@@ -2709,6 +2710,46 @@ PyObject *isRecursive(PyObject* nullValue, PyObject* args) {
27092710
});
27102711
}
27112712

2713+
PyDoc_STRVAR(
2714+
resetCompilerVisibleObjectHashCache_doc,
2715+
"resetCompilerVisibleObjectHashCache() -> None\n\n"
2716+
"Reset the list of objects the identity hasher has seen. This is used in test to allow\n"
2717+
"us to inject a fault and then verify we detect it, and not leave that fault lying around."
2718+
);
2719+
2720+
PyObject *resetCompilerVisibleObjectHashCache(PyObject* nullValue, PyObject* args) {
2721+
return translateExceptionToPyObject([&]() {
2722+
CompilerVisibleObjectVisitor::singleton().resetCache();
2723+
2724+
return incref(Py_None);
2725+
});
2726+
}
2727+
2728+
PyDoc_STRVAR(
2729+
checkForHashInstability_doc,
2730+
"checkForHashInstability() -> OneOf(None, str)\n\n"
2731+
"Walk every object that's been seen by the MutuallyRecursiveTypeGroup framework and verify\n"
2732+
"that the exact sequence of objects visible to it have not changed. If they have, return\n"
2733+
"a string detailing which objects changed and how (which could be very large).\n\n"
2734+
"During normal execution, this should always return None. However, if anything visible to\n"
2735+
"the compiler, such as a method on a class, or the identity of a module-level variable \n"
2736+
"changes, this function will find it. If such errors go undetected, it can cause various\n"
2737+
"errors, including multiple copies of the same type (since the hash is not stable) or \n"
2738+
"errors while computing hashes because the assumptions of the hasher are violated."
2739+
);
2740+
2741+
PyObject *checkForHashInstability(PyObject* nullValue, PyObject* args) {
2742+
return translateExceptionToPyObject([&]() {
2743+
try {
2744+
CompilerVisibleObjectVisitor::singleton().checkForInstability();
2745+
2746+
return incref(Py_None);
2747+
} catch(std::runtime_error& err) {
2748+
return PyUnicode_FromString(err.what());
2749+
}
2750+
});
2751+
}
2752+
27122753
PyObject *typesAndObjectsVisibleToCompilerFrom(PyObject* nullValue, PyObject* args) {
27132754
if (PyTuple_Size(args) != 1) {
27142755
PyErr_SetString(PyExc_TypeError, "typesAndObjectsVisibleToCompilerFrom takes 1 positional argument");
@@ -3260,6 +3301,8 @@ static PyMethodDef module_methods[] = {
32603301
{"recursiveTypeGroupRepr", (PyCFunction)recursiveTypeGroupRepr, METH_VARARGS, NULL},
32613302
{"recursiveTypeGroupDeepRepr", (PyCFunction)recursiveTypeGroupDeepRepr, METH_VARARGS, NULL},
32623303
{"recursiveTypeGroupHash", (PyCFunction)recursiveTypeGroupHash, METH_VARARGS, NULL},
3304+
{"checkForHashInstability", (PyCFunction)checkForHashInstability, METH_VARARGS, checkForHashInstability_doc},
3305+
{"resetCompilerVisibleObjectHashCache", (PyCFunction)resetCompilerVisibleObjectHashCache, METH_VARARGS, resetCompilerVisibleObjectHashCache_doc},
32633306
{"typesAndObjectsVisibleToCompilerFrom", (PyCFunction)typesAndObjectsVisibleToCompilerFrom, METH_VARARGS, NULL},
32643307
{"isRecursive", (PyCFunction)isRecursive, METH_VARARGS, NULL},
32653308
{"referencedTypes", (PyCFunction)referencedTypes, METH_VARARGS, NULL},

typed_python/type_identity_test.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
prepareArgumentToBePassedToCompiler,
3030
recursiveTypeGroup,
3131
getCodeGlobalDotAccesses,
32-
typesAndObjectsVisibleToCompilerFrom
32+
typesAndObjectsVisibleToCompilerFrom,
33+
checkForHashInstability,
34+
resetCompilerVisibleObjectHashCache
3335
)
3436

3537

@@ -76,6 +78,21 @@ class C:
7678
with pytest.raises(Exception):
7779
typesAndObjectsVisibleToCompilerFrom(C)
7880

81+
resetCompilerVisibleObjectHashCache()
82+
83+
84+
def test_object_graph_instability_is_noticed_globally():
85+
class C:
86+
pass
87+
88+
typesAndObjectsVisibleToCompilerFrom(C)
89+
90+
C.f = staticmethod(lambda: 10)
91+
92+
assert "staticmethod" in checkForHashInstability()
93+
94+
resetCompilerVisibleObjectHashCache()
95+
7996

8097
def test_class_and_held_class_in_group():
8198
class C(Class):

0 commit comments

Comments
 (0)