python compile less hackish
[Tsunagari.git] / src / python.cpp
blob9b59b9c733939c26d30b58aca7c66a1b4546d127
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** python.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 #include <algorithm> // for std::replace
8 #include <signal.h> // for SIGINT and SIG_DFL
9 #include <string.h> // for strrchr
11 #include <boost/python.hpp>
12 #include <Python.h>
13 #include <Python-ast.h>
15 #include "log.h"
16 #include "python.h"
17 #include "python-bindings.h" // for pythonInitBindings
18 #include "resourcer.h"
21 namespace bp = boost::python;
23 static bp::object modMain, modBltin;
24 static bp::object dictMain, dictBltin;
26 int inPythonScript = 0;
28 //! List of known safe Python modules allowed for importing.
29 static std::string moduleWhitelist[] = {
30 "__builtin__",
31 "__main__",
32 "math",
33 "time",
34 "traceback",
35 "",
38 static bool inWhitelist(const std::string& name)
40 for (int i = 0; moduleWhitelist[i].size(); i++)
41 if (name == moduleWhitelist[i])
42 return true;
43 return false;
47 static void pythonIncludeModule(const char* name)
49 bp::object module(bp::handle<>(PyImport_ImportModule(name)));
50 dictMain[name] = module;
53 static void pythonSetDefaultEncoding(const char* enc)
55 if (PyUnicode_SetDefaultEncoding(enc) != 0) {
56 PyErr_Format(PyExc_SystemError,
57 "encoding %s not found", enc);
58 bp::throw_error_already_set();
63 static PyObject*
64 safeImport(PyObject*, PyObject* args, PyObject* kwds)
66 static const char* kwlist[] = {"name", "globals", "locals", "fromlist",
67 "level", 0};
68 char* _name;
69 std::string name;
70 PyObject* globals, *locals, *fromList;
71 int level = -1;
73 // Validate args from Python.
74 if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOOi:__import__",
75 (char**)kwlist, &_name, &globals, &locals, &fromList,
76 &level))
77 return NULL;
78 name = _name;
80 // Search whitelisted Python modules.
81 if (inWhitelist(name))
82 return PyImport_ImportModuleLevel(_name, globals, locals,
83 fromList, level);
85 Log::info("Python", "import " + name);
87 // Search Python scripts inside World.
88 std::replace(name.begin(), name.end(), '.', '/');
89 name += ".py";
90 Resourcer* rc = Resourcer::instance();
91 if (rc->resourceExists(name)) {
92 rc->runPythonScript(name);
93 return modMain.ptr(); // We have to return a module...
96 // Nothing acceptable found.
97 std::string msg = std::string("Module '") + _name + "' not found or "
98 "not allowed. Note that Tsunagari runs in a sandbox and does "
99 "not allow most external modules.";
100 PyErr_Format(PyExc_ImportError, msg.c_str());
101 return NULL;
104 static PyObject* nullExecfile(PyObject*, PyObject*)
106 PyErr_SetString(PyExc_RuntimeError,
107 "file(): Tsunagari runs scripts in a sandbox and "
108 "does not allow accessing the standard filesystem");
109 return NULL;
112 static PyObject* nullFile(PyObject*, PyObject*)
114 PyErr_SetString(PyExc_RuntimeError,
115 "file(): Tsunagari runs scripts in a sandbox and "
116 "does not allow accessing the standard filesystem");
117 return NULL;
120 static PyObject* nullOpen(PyObject*, PyObject*)
122 PyErr_SetString(PyExc_RuntimeError,
123 "open(): Tsunagari runs scripts in a sandbox and "
124 "does not allow accessing the standard filesystem");
125 return NULL;
128 static PyObject* nullReload(PyObject*, PyObject*)
130 PyErr_SetString(PyExc_RuntimeError,
131 "reload(): Tsunagari does not allow module reloading");
132 return NULL;
135 PyMethodDef nullMethods[] = {
136 {"__import__", (PyCFunction)safeImport, METH_VARARGS | METH_KEYWORDS, ""},
137 {"execfile", nullExecfile, METH_VARARGS, ""},
138 {"file", nullFile, METH_VARARGS, ""},
139 {"open", nullOpen, METH_VARARGS, ""},
140 {"reload", nullReload, METH_O, ""},
141 {NULL, NULL, 0, NULL},
144 bool pythonInit()
146 try {
147 PyImport_AppendInittab("tsunagari", &pythonInitBindings);
148 Py_Initialize();
149 pythonSetDefaultEncoding("utf-8");
151 modMain = bp::import("__main__");
152 dictMain = modMain.attr("__dict__");
153 modBltin = bp::import("__builtin__");
154 dictBltin = modBltin.attr("__dict__");
156 pythonIncludeModule("tsunagari");
158 // Hack in some rough safety. Disable external scripts and IO.
159 // InitModule doesn't remove existing modules, so we can use it to
160 // insert new methods into a pre-existing module.
161 PyObject* module = Py_InitModule("__builtin__", nullMethods);
162 if (module == NULL)
163 bp::throw_error_already_set();
165 // Restore the default SIGINT handler.
166 // Python messes with it. >:(
167 PyOS_setsig(SIGINT, SIG_DFL);
168 } catch (bp::error_already_set) {
169 Log::fatal("Python", "An error occured while populating the "
170 "Python modules:");
171 Log::setVerbosity(V_NORMAL); // Assure message can be seen.
172 pythonErr();
173 return false;
175 return true;
178 void pythonFinalize()
180 Py_Finalize();
183 static std::string extractException(PyObject* exc, PyObject* val, PyObject* tb)
185 using namespace boost::python;
187 handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
188 if (!hval) {
189 return extract<std::string>(str(hexc));
191 else {
192 bp::object traceback(import("traceback"));
193 bp::object format_exception(traceback.attr("format_exception"));
194 bp::object formatted_list(format_exception(hexc, hval, htb));
195 bp::object formatted(str("").join(formatted_list));
196 return extract<std::string>(formatted);
201 static std::string extractException2(PyObject* exc, PyObject* val, PyObject*)
203 char* type = PyExceptionClass_Name(exc);
204 char* dot = strrchr(type, '.');
205 if (dot)
206 type = dot + 1;
207 char* value = PyString_AsString(val);
209 std::string msg = "";
210 msg += type ? type : "<unknown type>";
211 if (value) {
212 msg += ": ";
213 msg += value;
215 return msg;
219 void pythonErr()
221 // Something bad happened. Error is already set in Python.
222 PyObject* exc, *val, *tb;
223 PyErr_Fetch(&exc, &val, &tb);
225 if (!exc) {
226 Log::err("Python",
227 "pythonErr() called, but no exception set");
228 return;
231 PyErr_NormalizeException(&exc, &val, &tb);
233 Log::err("Python", extractException(exc, val, tb));
236 bp::object pythonGlobals()
238 return dictMain;
241 PyCodeObject* pythonCompile(const char* fn, const char* code)
243 PyArena *arena = PyArena_New();
244 if (!arena) {
245 return NULL;
248 mod_ty mod = PyParser_ASTFromString(code, fn, Py_file_input, NULL, arena);
249 if (!mod) {
250 Log::err("Python",
251 std::string(fn) + ": failed to parse");
252 pythonErr();
253 return NULL;
256 PyCodeObject* co = PyAST_Compile(mod, fn, NULL, arena);
257 if (!co) {
258 Log::err("Python",
259 std::string(fn) + ": failed to compile");
260 pythonErr();
261 return NULL;
264 PyArena_Free(arena);
265 return co;
268 bool pythonExec(PyCodeObject* code)
270 if (!code)
271 return false;
273 try {
274 inPythonScript++;
276 // FIXME: locals, globals
277 PyObject* globals = dictMain.ptr();
278 PyObject* result = PyEval_EvalCode(code, globals, globals);
280 inPythonScript--;
281 if (!result)
282 pythonErr();
283 return result;
284 } catch (boost::python::error_already_set) {
285 inPythonScript--;
286 pythonErr();
287 return NULL;