PyAPI: remove use of stub script to execute files on WIN32
Workaround potential C-API `FILE` incompatibility by reading the file data into memory, compiling & running it - matching existing logic for text buffers text buffers. This replaces the in-lined stub-script that re-opened the file from Python. While the down-side of the stub-script was minor, it required some non-obvious logic and had the disadvantage of requiring 2x scripts to execute whenever a file was executed on WIN32. Expose BLI_file_read_data_as_mem_from_handle as a public function since it's useful to be able to read an existing FILE into memory.
This commit is contained in:
@@ -352,6 +352,17 @@ bool BLI_file_older(const char *file1, const char *file2) ATTR_WARN_UNUSED_RESUL
|
||||
* \return the lines in a linked list (an empty list when file reading fails).
|
||||
*/
|
||||
struct LinkNode *BLI_file_read_as_lines(const char *file) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
|
||||
|
||||
/**
|
||||
* Read the contents of `fp`, returning the result as a buffer or null when it can't be read.
|
||||
*
|
||||
* \param r_size: The size of the file contents read into the buffer (excluding `pad_bytes`).
|
||||
*/
|
||||
void *BLI_file_read_data_as_mem_from_handle(FILE *fp,
|
||||
bool read_size_exact,
|
||||
size_t pad_bytes,
|
||||
size_t *r_size);
|
||||
|
||||
void *BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size);
|
||||
/**
|
||||
* Return the text file data with:
|
||||
@@ -368,6 +379,7 @@ void *BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *
|
||||
* \param pad_bytes: When this is non-zero, the first byte is set to nil,
|
||||
* to simplify parsing the file.
|
||||
* It's recommended to pass in 1, so all text is nil terminated.
|
||||
* \param r_size: The size of the file contents read into the buffer (excluding `pad_bytes`).
|
||||
*
|
||||
* Example looping over lines:
|
||||
*
|
||||
|
||||
@@ -444,10 +444,10 @@ bool BLI_is_file(const char *path)
|
||||
/**
|
||||
* Use for both text and binary file reading.
|
||||
*/
|
||||
static void *file_read_data_as_mem_impl(FILE *fp,
|
||||
bool read_size_exact,
|
||||
size_t pad_bytes,
|
||||
size_t *r_size)
|
||||
void *BLI_file_read_data_as_mem_from_handle(FILE *fp,
|
||||
bool read_size_exact,
|
||||
size_t pad_bytes,
|
||||
size_t *r_size)
|
||||
{
|
||||
BLI_stat_t st;
|
||||
if (BLI_fstat(fileno(fp), &st) == -1) {
|
||||
@@ -504,7 +504,7 @@ void *BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *
|
||||
FILE *fp = BLI_fopen(filepath, "r");
|
||||
void *mem = nullptr;
|
||||
if (fp) {
|
||||
mem = file_read_data_as_mem_impl(fp, false, pad_bytes, r_size);
|
||||
mem = BLI_file_read_data_as_mem_from_handle(fp, false, pad_bytes, r_size);
|
||||
fclose(fp);
|
||||
}
|
||||
return mem;
|
||||
@@ -515,7 +515,7 @@ void *BLI_file_read_binary_as_mem(const char *filepath, size_t pad_bytes, size_t
|
||||
FILE *fp = BLI_fopen(filepath, "rb");
|
||||
void *mem = nullptr;
|
||||
if (fp) {
|
||||
mem = file_read_data_as_mem_impl(fp, true, pad_bytes, r_size);
|
||||
mem = BLI_file_read_data_as_mem_from_handle(fp, true, pad_bytes, r_size);
|
||||
fclose(fp);
|
||||
}
|
||||
return mem;
|
||||
|
||||
@@ -79,6 +79,61 @@ struct PyModuleObject {
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compatibility wrapper for #PyRun_FileExFlags.
|
||||
*/
|
||||
static PyObject *python_compat_wrapper_PyRun_FileExFlags(FILE *fp,
|
||||
const char *filepath,
|
||||
const int start,
|
||||
PyObject *globals,
|
||||
PyObject *locals,
|
||||
const int closeit,
|
||||
PyCompilerFlags *flags)
|
||||
{
|
||||
/* Previously we used #PyRun_File to run directly the code on a FILE
|
||||
* object, but as written in the Python/C API Ref Manual, chapter 2,
|
||||
* 'FILE structs for different C libraries can be different and incompatible'.
|
||||
* So now we load the script file data to a buffer on MS-Windows. */
|
||||
#ifdef _WIN32
|
||||
bool use_file_handle_workaround = true;
|
||||
#else
|
||||
bool use_file_handle_workaround = false;
|
||||
#endif
|
||||
|
||||
if (!use_file_handle_workaround) {
|
||||
return PyRun_FileExFlags(fp, filepath, start, globals, locals, closeit, flags);
|
||||
}
|
||||
|
||||
PyObject *py_result = nullptr;
|
||||
size_t buf_len;
|
||||
char *buf = static_cast<char *>(BLI_file_read_data_as_mem_from_handle(fp, false, 1, &buf_len));
|
||||
if (closeit) {
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
if (UNLIKELY(buf == nullptr)) {
|
||||
PyErr_Format(PyExc_IOError, "Python file \"%s\" could not read buffer", filepath);
|
||||
}
|
||||
else {
|
||||
buf[buf_len] = '\0';
|
||||
PyObject *filepath_py = PyC_UnicodeFromBytes(filepath);
|
||||
PyObject *compiled = Py_CompileStringObject(buf, filepath_py, Py_file_input, flags, -1);
|
||||
MEM_freeN(buf);
|
||||
Py_DECREF(filepath_py);
|
||||
|
||||
if (compiled == nullptr) {
|
||||
if (!PyErr_Occurred()) {
|
||||
PyErr_Format(PyExc_SyntaxError, "Python file \"%s\" could not be compiled", filepath);
|
||||
}
|
||||
}
|
||||
else {
|
||||
py_result = PyEval_EvalCode(compiled, globals, locals);
|
||||
Py_DECREF(compiled);
|
||||
}
|
||||
}
|
||||
return py_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a file-path or text-block.
|
||||
*
|
||||
@@ -147,30 +202,9 @@ static bool python_script_exec(
|
||||
py_result = nullptr;
|
||||
}
|
||||
else {
|
||||
#ifdef _WIN32
|
||||
/* Previously we used PyRun_File to run directly the code on a FILE
|
||||
* object, but as written in the Python/C API Ref Manual, chapter 2,
|
||||
* 'FILE structs for different C libraries can be different and
|
||||
* incompatible'.
|
||||
* So now we load the script file data to a buffer.
|
||||
*
|
||||
* Note on use of 'globals()', it's important not copy the dictionary because
|
||||
* tools may inspect 'sys.modules["__main__"]' for variables defined in the code
|
||||
* where using a copy of 'globals()' causes code execution
|
||||
* to leave the main namespace untouched. see: #51444
|
||||
*
|
||||
* This leaves us with the problem of variables being included,
|
||||
* currently this is worked around using 'dict.__del__' it's ugly but works.
|
||||
*/
|
||||
{
|
||||
const char *pystring =
|
||||
"with open(__file__, 'rb') as f:"
|
||||
"exec(compile(f.read(), __file__, 'exec'), globals().__delitem__('f') or globals())";
|
||||
py_result = PyRun_String(pystring, Py_file_input, py_dict, py_dict);
|
||||
}
|
||||
#else
|
||||
py_result = PyRun_File(fp, filepath, Py_file_input, py_dict, py_dict);
|
||||
#endif
|
||||
py_dict = PyC_DefaultNameSpace(filepath);
|
||||
py_result = python_compat_wrapper_PyRun_FileExFlags(
|
||||
fp, filepath, Py_file_input, py_dict, py_dict, 0, nullptr);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user