StrictModules/pystrictmodule.cpp (627 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "StrictModules/pystrictmodule.h" #include "structmember.h" #ifndef Py_LIMITED_API #ifdef __cplusplus extern "C" { #endif // Analysis Result static PyObject* AnalysisResult_new(PyTypeObject* type, PyObject*, PyObject*) { StrictModuleAnalysisResult* self; self = (StrictModuleAnalysisResult*)type->tp_alloc(type, 0); if (self == NULL) return NULL; self->valid_module = 0; self->module_name = NULL; self->file_name = NULL; self->module_kind = 0; self->stub_kind = 0; self->ast = NULL; self->ast_preprocessed = NULL; self->symtable = NULL; self->errors = NULL; return (PyObject*)self; } static int AnalysisResult_init( StrictModuleAnalysisResult* self, PyObject* args, PyObject*) { PyObject* module_name; PyObject* file_name; int module_kind; int stub_kind; PyObject* ast; PyObject* ast_preprocessed; PyObject* symtable; PyObject* errors; if (!PyArg_ParseTuple( args, "UUiiOOOO", &module_name, &file_name, &module_kind, &stub_kind, &ast, &ast_preprocessed, &symtable, &errors)) { return -1; } self->valid_module = 1; self->module_name = module_name; Py_INCREF(self->module_name); self->file_name = file_name; Py_INCREF(self->file_name); self->module_kind = module_kind; self->stub_kind = stub_kind; self->ast = ast; Py_INCREF(self->ast); self->ast_preprocessed = ast_preprocessed; Py_INCREF(self->ast_preprocessed); self->symtable = symtable; Py_INCREF(self->symtable); self->errors = errors; Py_INCREF(self->errors); return 0; } static PyObject* create_AnalysisResult_Helper( int valid_module, PyObject* module_name, PyObject* file_name, int module_kind, int stub_kind, PyObject* ast, PyObject* ast_preprocessed, PyObject* symtable, PyObject* errors) { StrictModuleAnalysisResult* self; self = (StrictModuleAnalysisResult*)PyObject_GC_New( StrictModuleAnalysisResult, &StrictModuleAnalysisResult_Type); self->valid_module = valid_module; self->module_name = module_name; self->file_name = file_name; self->module_kind = module_kind; self->stub_kind = stub_kind; self->ast = ast; self->ast_preprocessed = ast_preprocessed; self->symtable = symtable; self->errors = errors; PyObject_GC_Track(self); return (PyObject*)self; } static PyObject* create_AnalysisResult( StrictAnalyzedModule* mod, PyObject* module_name, PyObject* errors, PyArena* arena) { if (mod == NULL) { Py_INCREF(module_name); Py_INCREF(errors); return create_AnalysisResult_Helper( 0, module_name, NULL, 0, 0, NULL, NULL, NULL, errors); } // all interface functions return new references PyObject* filename = StrictAnalyzedModule_GetFilename(mod); int mod_kind = StrictAnalyzedModule_GetModuleKind(mod); int stub_kind = StrictAnalyzedModule_GetStubKind(mod); PyObject* ast = StrictAnalyzedModule_GetAST(mod, arena, 0); PyObject* ast_preprocessed = StrictAnalyzedModule_GetAST(mod, arena, 1); PyObject* symtable = StrictAnalyzedModule_GetSymtable(mod); Py_INCREF(module_name); Py_INCREF(errors); return create_AnalysisResult_Helper( 1, module_name, filename, mod_kind, stub_kind, ast, ast_preprocessed, symtable, errors); } static void AnalysisResult_dealloc(StrictModuleAnalysisResult* self) { if (self != NULL) { PyObject_GC_UnTrack(self); Py_XDECREF(self->module_name); Py_XDECREF(self->file_name); Py_XDECREF(self->ast); Py_XDECREF(self->ast_preprocessed); Py_XDECREF(self->symtable); Py_XDECREF(self->errors); PyObject_GC_Del(self); } } static int AnalysisResult_traverse( StrictModuleAnalysisResult* self, visitproc visit, void* arg) { Py_VISIT(self->module_name); Py_VISIT(self->file_name); Py_VISIT(self->ast); Py_VISIT(self->ast_preprocessed); Py_VISIT(self->symtable); Py_VISIT(self->errors); return 0; } static int AnalysisResult_clear(StrictModuleAnalysisResult* self) { Py_CLEAR(self->module_name); Py_CLEAR(self->file_name); Py_CLEAR(self->ast); Py_CLEAR(self->ast_preprocessed); Py_CLEAR(self->symtable); Py_CLEAR(self->errors); return 0; } static PyMemberDef AnalysisResult_members[] = { {"is_valid", T_BOOL, offsetof(StrictModuleAnalysisResult, valid_module), READONLY, "whether the analyzed module is found or valid"}, {"module_name", T_OBJECT, offsetof(StrictModuleAnalysisResult, module_name), READONLY, "module name"}, {"file_name", T_OBJECT, offsetof(StrictModuleAnalysisResult, file_name), READONLY, "file name"}, {"module_kind", T_INT, offsetof(StrictModuleAnalysisResult, module_kind), READONLY, "whether module is strict (1), static (2) or neither (0)"}, {"stub_kind", T_INT, offsetof(StrictModuleAnalysisResult, stub_kind), READONLY, "stub kind represented as a bit mask."}, {"ast", T_OBJECT, offsetof(StrictModuleAnalysisResult, ast), READONLY, "original AST of the module"}, {"ast_preprocessed", T_OBJECT, offsetof(StrictModuleAnalysisResult, ast_preprocessed), READONLY, "preprocessed AST of the module"}, {"symtable", T_OBJECT, offsetof(StrictModuleAnalysisResult, symtable), READONLY, "symbol table of the module"}, {"errors", T_OBJECT, offsetof(StrictModuleAnalysisResult, errors), 0, "list of errors"}, {NULL, 0, 0, 0, NULL} /* Sentinel */ }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" PyTypeObject StrictModuleAnalysisResult_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "strictmodule.StrictModuleAnalysisResult", .tp_basicsize = sizeof(StrictModuleAnalysisResult), .tp_itemsize = 0, .tp_dealloc = (destructor)AnalysisResult_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, .tp_doc = "Analysis result of strict module loader", .tp_traverse = (traverseproc)AnalysisResult_traverse, .tp_clear = (inquiry)AnalysisResult_clear, .tp_members = AnalysisResult_members, .tp_init = (initproc)AnalysisResult_init, .tp_new = AnalysisResult_new, }; #pragma GCC diagnostic pop // Module Loader static PyObject* StrictModuleLoaderObject_new(PyTypeObject* type, PyObject*, PyObject*) { StrictModuleLoaderObject* self; self = (StrictModuleLoaderObject*)type->tp_alloc(type, 0); if (self == NULL) return NULL; self->checker = StrictModuleChecker_New(); return (PyObject*)self; } static int PyListToCharArray(PyObject* pyList, const char** arr, Py_ssize_t size) { PyObject** items = _PyList_ITEMS(pyList); int _i; PyObject* elem; for (_i = 0; _i < size; _i++) { elem = items[_i]; if (!PyUnicode_Check(elem)) { PyErr_Format( PyExc_TypeError, "import path is expect to be str, but got %s object", elem->ob_type->tp_name); return -1; } const char* elem_str = PyUnicode_AsUTF8(elem); if (elem_str == NULL) { return -1; } arr[_i] = elem_str; } return 0; } static int StrictModuleLoaderObject_init( StrictModuleLoaderObject* self, PyObject* args, PyObject*) { PyObject* import_paths_obj; PyObject* stub_import_path_obj; PyObject* allow_list_obj; PyObject* allow_list_exact_obj; PyObject* load_strictmod_builtin; load_strictmod_builtin = Py_True; if (!PyArg_ParseTuple( args, "OOOO|O", &import_paths_obj, &stub_import_path_obj, &allow_list_obj, &allow_list_exact_obj, &load_strictmod_builtin)) { return -1; } if (!PyList_Check(import_paths_obj)) { PyErr_Format( PyExc_TypeError, "import_paths is expect to be list, but got %S object", import_paths_obj); return -1; } if (!PyList_Check(allow_list_obj)) { PyErr_Format( PyExc_TypeError, "allow_list is expect to be list, but got %S object", allow_list_obj); return -1; } if (!PyList_Check(allow_list_exact_obj)) { PyErr_Format( PyExc_TypeError, "allow_list_exact is expect to be list, but got %S object", allow_list_exact_obj); return -1; } if (!PyUnicode_Check(stub_import_path_obj)) { PyErr_Format( PyExc_TypeError, "stub_import_path is expect to be str, but got %S object", stub_import_path_obj); return -1; } // Import paths Py_ssize_t import_size = PyList_GET_SIZE(import_paths_obj); const char* import_paths_arr[import_size]; if (PyListToCharArray(import_paths_obj, import_paths_arr, import_size) < 0) { return -1; } if (StrictModuleChecker_SetImportPaths( self->checker, import_paths_arr, import_size) < 0) { PyErr_SetString( PyExc_RuntimeError, "failed to set import paths on StrictModuleLoader object"); return -1; } // allowlist for module names Py_ssize_t allow_list_size = PyList_GET_SIZE(allow_list_obj); const char* allow_list_arr[allow_list_size]; if (PyListToCharArray(allow_list_obj, allow_list_arr, allow_list_size) < 0) { return -1; } if (StrictModuleChecker_SetAllowListPrefix( self->checker, allow_list_arr, allow_list_size) < 0) { PyErr_SetString( PyExc_RuntimeError, "failed to set allowlist on StrictModuleLoader object"); return -1; } // allowlist for exact module names Py_ssize_t allow_list_exact_size = PyList_GET_SIZE(allow_list_exact_obj); const char* allow_list_exact_arr[allow_list_exact_size]; if (PyListToCharArray( allow_list_exact_obj, allow_list_exact_arr, allow_list_exact_size) < 0) { return -1; } if (StrictModuleChecker_SetAllowListExact( self->checker, allow_list_exact_arr, allow_list_exact_size) < 0) { PyErr_SetString( PyExc_RuntimeError, "failed to set allowlist on StrictModuleLoader object"); return -1; } // stub paths const char* stub_str = PyUnicode_AsUTF8(stub_import_path_obj); if (stub_str == NULL) { return -1; } int stub_path_set = StrictModuleChecker_SetStubImportPath(self->checker, stub_str); if (stub_path_set < 0) { PyErr_SetString( PyExc_RuntimeError, "failed to set the stub import path on StrictModuleLoader object"); return -1; } // load strict module builtins int should_load = PyObject_IsTrue(load_strictmod_builtin); if (should_load < 0) { PyErr_SetString( PyExc_RuntimeError, "error checking 'should_load_builtin' on StrictModuleLoader"); return -1; } if (should_load) { int loaded = StrictModuleChecker_LoadStrictModuleBuiltins(self->checker); if (loaded < 0) { PyErr_SetString( PyExc_RuntimeError, "failed to load the strict module builtins on StrictModuleLoader " "object"); return -1; } } return 0; } /* StrictModuleLoader methods */ static void StrictModuleLoader_dealloc(StrictModuleLoaderObject* self) { StrictModuleChecker_Free(self->checker); PyObject_Del(self); } /* Returns new reference */ static PyObject* errorInfoToTuple(ErrorInfo* info) { PyObject* py_lineno = NULL; PyObject* py_col = NULL; PyObject* result = NULL; py_lineno = PyLong_FromLong(info->lineno); if (!py_lineno) { goto err_cleanup; } py_col = PyLong_FromLong(info->col); if (!py_col) { goto err_cleanup; } result = PyTuple_Pack(4, info->msg, info->filename, py_lineno, py_col); if (!result) { goto err_cleanup; } Py_DECREF(py_lineno); Py_DECREF(py_col); return result; err_cleanup: Py_XDECREF(py_lineno); Py_XDECREF(py_col); return NULL; } static PyObject* StrictModuleLoader_check( StrictModuleLoaderObject* self, PyObject* args) { PyObject* mod_name; PyObject* errors; PyArena* arena; PyObject* result; if (!PyArg_ParseTuple(args, "U", &mod_name)) { return NULL; } int error_count = 0; int is_strict = 0; StrictAnalyzedModule* mod = StrictModuleChecker_Check( self->checker, mod_name, &error_count, &is_strict); errors = PyList_New(error_count); ErrorInfo error_infos[error_count]; if (error_count > 0 && mod != NULL) { if (StrictModuleChecker_GetErrors(mod, error_infos, error_count) < 0) { goto err_cleanup; } for (int i = 0; i < error_count; ++i) { PyObject* py_err_tuple = errorInfoToTuple(&(error_infos[i])); if (!py_err_tuple) { goto err_cleanup; } PyList_SET_ITEM(errors, i, py_err_tuple); } } if (PyErr_Occurred()) { goto err_cleanup; } for (int i = 0; i < error_count; ++i) { ErrorInfo_Clean(&(error_infos[i])); } arena = StrictModuleChecker_GetArena(self->checker); result = create_AnalysisResult(mod, mod_name, errors, arena); Py_XDECREF(errors); return result; err_cleanup: for (int i = 0; i < error_count; ++i) { ErrorInfo_Clean(&(error_infos[i])); } Py_XDECREF(errors); return NULL; } static PyObject* StrictModuleLoader_check_source( StrictModuleLoaderObject* self, PyObject* args) { // args to parse, do not decref since these are borrowed PyObject* source; // str or bytes PyObject* file_name; PyObject* mod_name; PyObject* submodule_search_locations; // list of string // outputs PyObject* errors; PyArena* arena; PyObject* result; // parameter parsing if (!PyArg_ParseTuple( args, "OUUO", &source, &file_name, &mod_name, &submodule_search_locations)) { return NULL; } // source str const char* source_str; // verify search locations if (!PyList_Check(submodule_search_locations)) { PyErr_Format( PyExc_TypeError, "submodule_search_locations is expect to be list, but got %S object", submodule_search_locations); return NULL; } Py_ssize_t search_list_size = PyList_GET_SIZE(submodule_search_locations); const char* search_list[search_list_size]; if (PyListToCharArray( submodule_search_locations, search_list, search_list_size) < 0) { return NULL; } // verify source PyCompilerFlags cf = _PyCompilerFlags_INIT; // buffer containing source str PyObject* source_copy; source_str = _Py_SourceAsString(source, "parse", "str or bytes", &cf, &source_copy); if (source_str == NULL) { return NULL; } int error_count = 0; int is_strict = 0; StrictAnalyzedModule* mod = StrictModuleChecker_CheckSource( self->checker, source_str, mod_name, file_name, search_list, search_list_size, &error_count, &is_strict); errors = PyList_New(error_count); ErrorInfo error_infos[error_count]; if (error_count > 0 && mod != NULL) { if (StrictModuleChecker_GetErrors(mod, error_infos, error_count) < 0) { goto err_cleanup; } for (int i = 0; i < error_count; ++i) { PyObject* py_err_tuple = errorInfoToTuple(&(error_infos[i])); if (!py_err_tuple) { goto err_cleanup; } PyList_SET_ITEM(errors, i, py_err_tuple); } } if (PyErr_Occurred()) { goto err_cleanup; } for (int i = 0; i < error_count; ++i) { ErrorInfo_Clean(&(error_infos[i])); } arena = StrictModuleChecker_GetArena(self->checker); result = create_AnalysisResult(mod, mod_name, errors, arena); Py_XDECREF(errors); Py_XDECREF(source_copy); return result; err_cleanup: for (int i = 0; i < error_count; ++i) { ErrorInfo_Clean(&(error_infos[i])); } Py_XDECREF(errors); Py_XDECREF(source_copy); return NULL; } static PyObject* StrictModuleLoader_set_force_strict( StrictModuleLoaderObject* self, PyObject* args) { PyObject* force_strict; if (!PyArg_ParseTuple(args, "O", &force_strict)) { return NULL; } int ok = StrictModuleChecker_SetForceStrict(self->checker, force_strict); if (ok == 0) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* StrictModuleLoader_set_force_strict_by_name( StrictModuleLoaderObject* self, PyObject* args) { const char* forced_strict_module; if (!PyArg_ParseTuple(args, "s", &forced_strict_module)) { return NULL; } int ok = StrictModuleChecker_SetForceStrictByName(self->checker, forced_strict_module); if (ok == 0) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* StrictModuleLoader_get_analyzed_count( StrictModuleLoaderObject* self) { int count = StrictModuleChecker_GetAnalyzedModuleCount(self->checker); return PyLong_FromLong(count); } static PyObject* StrictModuleLoader_delete_module( StrictModuleLoaderObject* self, PyObject* args) { PyObject* name; if (!PyArg_ParseTuple(args, "U", &name)) { return NULL; } int ok = StrictModuleChecker_DeleteModule(self->checker, PyUnicode_AsUTF8(name)); if (ok == 0) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyMethodDef StrictModuleLoader_methods[] = { {"check", (PyCFunction)StrictModuleLoader_check, METH_VARARGS, PyDoc_STR("check(mod_name: str) -> StrictAnalysisResult")}, {"check_source", (PyCFunction)StrictModuleLoader_check_source, METH_VARARGS, PyDoc_STR("check_source(" "source:str | bytes, file_name: str, mod_name: str, " "submodule_search_locations:List[str])" " -> StrictAnalysisResult")}, {"set_force_strict", (PyCFunction)StrictModuleLoader_set_force_strict, METH_VARARGS, PyDoc_STR("set_force_strict(force: bool) -> bool")}, {"set_force_strict_by_name", (PyCFunction)StrictModuleLoader_set_force_strict_by_name, METH_VARARGS, PyDoc_STR("set_force_strict(modname: str) -> bool")}, {"get_analyzed_count", (PyCFunction)StrictModuleLoader_get_analyzed_count, METH_NOARGS, PyDoc_STR("get_analyzed_count() -> int")}, {"delete_module", (PyCFunction)StrictModuleLoader_delete_module, METH_VARARGS, PyDoc_STR("delete_module(name: str) -> bool")}, {NULL, NULL, 0, NULL} /* sentinel */ }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" PyTypeObject StrictModuleLoader_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "strictmodule.StrictModuleLoader", .tp_basicsize = sizeof(StrictModuleLoaderObject), .tp_itemsize = 0, .tp_dealloc = (destructor)StrictModuleLoader_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Cinder implementation of strict module checker", .tp_methods = StrictModuleLoader_methods, .tp_init = (initproc)StrictModuleLoaderObject_init, .tp_new = StrictModuleLoaderObject_new, }; #pragma GCC diagnostic pop const char* MUTABLE_DECORATOR = "<mutable>"; const char* LOOSE_SLOTS_DECORATOR = "<loose_slots>"; const char* EXTRA_SLOTS_DECORATOR = "<extra_slots>"; const char* ENABLE_SLOTS_DECORATOR = "<enable_slots>"; const char* CACHED_PROP_DECORATOR = "<cached_property>"; // module kind int NONSTRICT_MODULE_KIND = 0; int STRICT_MODULE_KIND = 1; int STATIC_MODULE_KIND = 2; // stub kind int STUB_KIND_MASK_NONE = 0b000; int STUB_KIND_MASK_ALLOWLIST = 0b011; int STUB_KIND_MASK_TYPING = 0b100; int STUB_KIND_MASK_STRICT = 0b001; #ifdef __cplusplus } #endif #endif /* Py_LIMITED_API */