diff --git a/dmoj/executors/compiled_executor.py b/dmoj/executors/compiled_executor.py index 9ff172029..7a9ba2080 100644 --- a/dmoj/executors/compiled_executor.py +++ b/dmoj/executors/compiled_executor.py @@ -1,4 +1,3 @@ -import abc import hashlib import os import signal @@ -10,6 +9,7 @@ import pylru from dmoj.error import CompileError, OutputLimitExceeded +from dmoj.executors.metaclass import ExecutorMeta from dmoj.judgeenv import env from dmoj.utils.communicate import safe_communicate from dmoj.utils.unicode import utf8bytes @@ -26,7 +26,7 @@ # Contract: if cached=True is specified and an entry exists in the cache, # `create_files` and `compile` will not be run, and `_executable` will be loaded # from the cache. -class _CompiledExecutorMeta(abc.ABCMeta): +class _CompiledExecutorMeta(ExecutorMeta): @staticmethod def _cleanup_cache_entry(_key, executor: object) -> None: # Mark the executor as not-cached, so that if this is the very last reference diff --git a/dmoj/executors/metaclass.py b/dmoj/executors/metaclass.py new file mode 100644 index 000000000..cadea735d --- /dev/null +++ b/dmoj/executors/metaclass.py @@ -0,0 +1,20 @@ +import abc + + +class ExecutorMeta(abc.ABCMeta): + """This metaclass treats class attributes that are annotated but never assigned as abstract. + + For example: + >>> class Example(ExecutorMeta): + ... x: int + would be an abstract class unless `x` is implemented. + + This means that when instantiating, an exception will be raised: + >>> Example() + TypeError: Can't instantiate abstract class Example with abstract methods x + """ + + def __new__(mcs, *args, **kwargs): + cls = super().__new__(mcs, *args, **kwargs) + cls.__abstractmethods__ |= {a for a in getattr(cls, '__annotations__', {}).keys() if not hasattr(cls, a)} + return cls diff --git a/dmoj/executors/mixins.py b/dmoj/executors/mixins.py index 30e53d4bc..46487fff4 100644 --- a/dmoj/executors/mixins.py +++ b/dmoj/executors/mixins.py @@ -1,4 +1,3 @@ -import abc import os import re import shutil @@ -7,6 +6,7 @@ from dmoj.cptbox import IsolateTracer, TracedPopen, syscalls from dmoj.cptbox.handlers import ALLOW from dmoj.error import InternalError +from dmoj.executors.metaclass import ExecutorMeta from dmoj.judgeenv import env from dmoj.utils import setbufsize_path from dmoj.utils.unicode import utf8bytes @@ -36,7 +36,7 @@ BASE_FILESYSTEM += [r'/etc/ld\.so\.(?:nohwcap|preload|cache)$'] -class PlatformExecutorMixin(metaclass=abc.ABCMeta): +class PlatformExecutorMixin(metaclass=ExecutorMeta): address_grace = 65536 data_grace = 0 fsize = 0