wireprototypes: make `baseprotocolhandler` methods abstract
The documentation says it's an abstract base class, so let's enforce it. The
`typing.Protocol` class is already an ABC, but it only prevents instantiation if
there are abstract attrs that are missing. For example, from `hg debugshell`:
>>> from mercurial import wireprototypes
>>> x = wireprototypes.baseprotocolhandler()
Traceback (most recent call last):
File "<console>", line 1, in <module>
TypeError: Can't instantiate abstract class baseprotocolhandler with abstract method name
>>> class fake(wireprototypes.baseprotocolhandler):
... pass
...
>>> x = fake()
Traceback (most recent call last):
File "<console>", line 1, in <module>
TypeError: Can't instantiate abstract class fake with abstract method name
That's great, but it doesn't protect against calling non-abstract methods at
runtime, rather it depends on the protocol type hint being added to method
signatures or class attrs, and then running a type checker to notice when an
instance is assigned that doesn't conform to the protocol. We don't widely use
type hints yet, and do have a lot of class hierarchy in the repository area,
which could lead to surprises like this:
>>> class fake(wireprototypes.baseprotocolhandler):
... @property
... def name(self) -> bytes:
... return b'name'
...
>>> z = fake()
>>> z.client()
>>> print(z.client())
None
Oops. That was supposed to return `bytes`. So not only is a bad/unexpected
value returned, but it's one that violates the type hints (since the base
client() method will be annotated to return bytes). With this change, we get:
>>> from mercurial import wireprototypes
>>> class fake(wireprototypes.baseprotocolhandler):
... @property
... def name(self) -> bytes:
... return b'name'
...
>>> x = fake()
Traceback (most recent call last):
File "<console>", line 1, in <module>
TypeError: Can't instantiate abstract class fake with abstract methods
addcapabilities, checkperm, client, getargs, getpayload, getprotocaps, mayberedirectstdio
So this looks like a reasonable safety harness to me, and lets us catch problems
by running the standard tests while the type hints are being added, and pytype
is improved. We should probably do this for all Protocol class methods that
don't supply a method implementation.
#include "pyutil.h"
#include <iostream>
#include <string>
namespace contrib
{
#if PY_MAJOR_VERSION >= 3
#define HG_FUZZER_PY3 1
PyMODINIT_FUNC PyInit_parsers(void);
#else
PyMODINIT_FUNC initparsers(void);
#endif
static char cpypath[8192] = "\0";
static PyObject *mainmod;
static PyObject *globals;
void initpy(const char *cselfpath)
{
#ifdef HG_FUZZER_PY3
const std::string subdir = "/sanpy/lib/python3.8";
#else
const std::string subdir = "/sanpy/lib/python2.7";
#endif
/* HACK ALERT: we need a full Python installation built without
pymalloc and with ASAN, so we dump one in
$OUT/sanpy/lib/python2.7. This helps us wire that up. */
std::string selfpath(cselfpath);
std::string pypath;
auto pos = selfpath.rfind("/");
if (pos == std::string::npos) {
char wd[8192];
if (!getcwd(wd, 8192)) {
std::cerr << "Failed to call getcwd: errno " << errno
<< std::endl;
exit(1);
}
pypath = std::string(wd) + subdir;
} else {
pypath = selfpath.substr(0, pos) + subdir;
}
strncpy(cpypath, pypath.c_str(), pypath.size());
setenv("PYTHONPATH", cpypath, 1);
setenv("PYTHONNOUSERSITE", "1", 1);
/* prevent Python from looking up users in the fuzz environment */
setenv("PYTHONUSERBASE", cpypath, 1);
#ifdef HG_FUZZER_PY3
std::wstring wcpypath(pypath.begin(), pypath.end());
Py_SetPythonHome(wcpypath.c_str());
#else
Py_SetPythonHome(cpypath);
#endif
Py_InitializeEx(0);
mainmod = PyImport_AddModule("__main__");
globals = PyModule_GetDict(mainmod);
#ifdef HG_FUZZER_PY3
PyObject *mod = PyInit_parsers();
#else
initparsers();
PyObject *mod = PyImport_ImportModule("parsers");
#endif
PyDict_SetItemString(globals, "parsers", mod);
}
PyObject *pyglobals()
{
return globals;
}
} // namespace contrib