【发布时间】:2018-09-23 17:07:28
【问题描述】:
如果设置了错误指示符,Python 的 C API 中的许多函数使用起来不安全。特别是,PyFloat_AsDouble 和类似的函数是不明确,因为它们没有为指示错误保留返回值:如果它们成功(但恰好返回用于错误的值),调用的客户端如果错误指示器已经设置,PyErr_Occurred 会认为他们失败了。 (请注意,PyIter_Next 或多或少保证会发生这种情况。)更一般地说,任何可能失败的函数都会覆盖错误指示符,如果发生这种情况,这可能是可取的,也可能是不可取的。
不幸的是,使用错误指示符集调用此类函数的可能性并非完全不可能:对错误的常见反应是Py_DECREF局部变量,并且(除非所有对象的类型可能是(间接)由它释放的已知)可以执行任意代码。 (这是一个很好的例子,说明清理代码可能会失败。)解释器在此类析构函数中捕获异常引发,但它不能防止异常泄漏进入他们。
在任一端,我们可以使用PyErr_Fetch 和PyErr_Restore 来防止这些问题。调用一个模棱两可的函数,它们可以可靠地确定它是否成功;放在Py_DECREF 周围,它们首先防止在执行任何易受攻击的代码期间设置错误指示器。 (它们甚至可以用于可能会失败的直接调用的清理代码,以便允许选择传播哪个异常。在这种情况下,将它放在哪里是毫无疑问的:清理代码无论如何都不能在多个异常之间进行选择.)
任何一种放置选择都会显着增加代码复杂性和执行时间:有很多对模棱两可的函数的调用,并且在错误处理路径上有很多Py_DECREFs。虽然防御性编程的原则建议在两个地方都使用它,但更好的代码将来自(仔细编程)通用约定(以涵盖正在执行的任意代码)。
C 本身有这样一个约定:errno 必须由任意代码的调用者保存,即使(如 Python 析构函数中的抑制异常)该代码不应将 errno 设置为任何内容。主要原因是它可以被许多成功的库调用重置(但永远不会为0)(让它们在内部处理错误),进一步缩小在errno保持时可以安全执行的操作集一些重要的价值。 (这也可以防止PyErr_Occurred 报告预先存在的错误时出现的问题:C 程序员必须在调用不明确的函数之前将errno 设置为 0。)另一个原因是“调用一些任意代码而不报告错误”不是在大多数 C 程序中都是常见的操作,因此为它增加其他代码的负担是没有意义的。
是否有这样的约定(即使 CPython 本身存在不遵循它的错误代码)?如果做不到这一点,是否有技术原因来指导选择一个建立?或者这可能是一个基于“任意”解读的工程问题:CPython 是否应该在处理析构函数异常时保存和恢复错误指示符本身?
【问题讨论】:
标签: python error-handling cpython python-internals