New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make SET_FLAG_IF more paranoid #3740
Conversation
This fixes a regression introduced in pyodide#3283. The bug also depends on this branch here: https://github.com/pyodide/pyodide/blob/main/src/core/python2js.c#L833-L834 which is a bit suspect...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
if (cond) { \ | ||
type_flags |= flag; \ | ||
} \ | ||
} catch (e) { \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we propagate the error, or maybe at least show a warning message? If we ignore the error, then the object will have invalid type flags I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't throw an error here because then the JavaScript object becomes completely inaccessible from Python (as previously was the case with objects produced viaObject.create(null)
). It's possible to work around missing type flags via slightly non idiomatic code. It's not possible to work around "touching this object in any way throws an error".
But note also that this is extremely rare. Basically the main thing we are concerned about is stuff like:
py_func({ get length() { throw new Error(); } });
It's actually pretty funny that hypothesis managed to find a code path that is equivalent to this.
But you are right that ideally this should produce a JsProxy
such that len(x)
throws a JsException
, not one that throws TypeError: object of type 'type' has no len()
. Particularly for extremely pathological stuff like:
py_func({
get length() {
if(firstTimeCalled){
throw new Error();
}
return 7;
}
});
To get something that behaves correctly here we should do:
function hasProperty(obj, prop) {
while (obj) {
if (Object.getOwnPropertyDescriptor(obj, prop)) {
return true;
}
obj = Object.getPrototypeOf(obj);
}
return false;
}
I think we should still catch and throw away errors, but we should set fail_test
to true
and when compiled in debug mode we should log them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well currently we have:
$ len(run_js("({ get length() { return 'a'; } })"))
TypeError: object of type 'pyodide.ffi.JsProxy' has no len()
But if we changed it to not execute the getter, we would get:
$ len(run_js("({ get length() { return 'a'; } })"))
TypeError: 'str' object cannot be interpreted as an integer
which is I guess fine. But then what about:
len(run_js("({ length: 'a' })"))
or:
len(run_js("({ length: undefined })"))
I guess in Python you can say:
class A:
def __len__(self):
return None
and the expected behavior is that len(A())
raises
TypeError: 'NoneType' object cannot be interpreted as an integer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I think the most important thing is that JsProxy_create
should never fail.
This is a followup to pyodide#3740
This is a followup to #3740 which rearranges the logic a bit more on further consideration. I found out that if you create a revocable Proxy and then revoke it, a huge number of browser internal operations raise TypeErrors. Such an object is thus an excellent stress test for our handling of explosive objects. Overall we do quite well, `isPyProxy` and `compute_type_flags` are actually the only spots where we run into trouble (and `PyProxy_Check` is the only spot that is raising a fatal error). This changes it so that an object with a `length` getter that throws an error or returns not an integer will have a `__len__` in Python that always throws an error. It can't check the type anymore because it avoids invoking getters so if the result is a return value from a getter it won't know the type. The same is true for the `Error.name`, `Error.message`, and `Error.stack` fields. To check for methods, I still use `typeof x[method] === "function"`, invoking the getter. Usually functions don't have getters anyways and I think it is weirder to have false positives in this case. If there is a getter and it raises an error, we catch it and throw it away.
This fixes a regression introduced in pyodide#3283. To be properly paranoid we should assume that each property access can raise an error. If an error is raised, throw it away and don't set the flag. This could be further improved to avoid some getter calls but this a start.
This is a followup to pyodide#3740 which rearranges the logic a bit more on further consideration. I found out that if you create a revocable Proxy and then revoke it, a huge number of browser internal operations raise TypeErrors. Such an object is thus an excellent stress test for our handling of explosive objects. Overall we do quite well, `isPyProxy` and `compute_type_flags` are actually the only spots where we run into trouble (and `PyProxy_Check` is the only spot that is raising a fatal error). This changes it so that an object with a `length` getter that throws an error or returns not an integer will have a `__len__` in Python that always throws an error. It can't check the type anymore because it avoids invoking getters so if the result is a return value from a getter it won't know the type. The same is true for the `Error.name`, `Error.message`, and `Error.stack` fields. To check for methods, I still use `typeof x[method] === "function"`, invoking the getter. Usually functions don't have getters anyways and I think it is weirder to have false positives in this case. If there is a getter and it raises an error, we catch it and throw it away.
This fixes a regression introduced in #3283. To be properly paranoid we should assume that each property access can raise an error.
The bug also depends on this branch here:
https://github.com/pyodide/pyodide/blob/main/src/core/python2js.c#L833-L834
which is a bit suspect...