1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 
25 #include "pxr/pxr.h"
26 
27 #include "pxr/base/tf/error.h"
28 #include "pxr/base/tf/errorMark.h"
29 #include "pxr/base/tf/pyEnum.h"
30 #include "pxr/base/tf/pyError.h"
31 #include "pxr/base/tf/pyErrorInternal.h"
32 #include "pxr/base/tf/pyInterpreter.h"
33 #include "pxr/base/tf/pyLock.h"
34 #include "pxr/base/tf/pyUtils.h"
35 #include "pxr/base/tf/scriptModuleLoader.h"
36 
37 #include "pxr/base/arch/defines.h"
38 #include "pxr/base/tf/registryManager.h"
39 
40 #include <boost/python.hpp>
41 #include <boost/python/detail/api_placeholder.hpp>
42 
43 #include <functional>
44 #include <vector>
45 
46 using namespace boost::python;
47 
48 using std::string;
49 using std::vector;
50 
51 PXR_NAMESPACE_OPEN_SCOPE
52 
53 void
TfPyThrowIndexError(string const & msg)54 TfPyThrowIndexError(string const &msg)
55 {
56     TfPyLock pyLock;
57     PyErr_SetString(PyExc_IndexError, msg.c_str());
58     boost::python::throw_error_already_set();
59 }
60 
61 void
TfPyThrowRuntimeError(string const & msg)62 TfPyThrowRuntimeError(string const &msg)
63 {
64     TfPyLock pyLock;
65     PyErr_SetString(PyExc_RuntimeError, msg.c_str());
66     boost::python::throw_error_already_set();
67 }
68 
69 void
TfPyThrowStopIteration(string const & msg)70 TfPyThrowStopIteration(string const &msg)
71 {
72     TfPyLock pyLock;
73     PyErr_SetString(PyExc_StopIteration, msg.c_str());
74     boost::python::throw_error_already_set();
75 }
76 
77 void
TfPyThrowKeyError(string const & msg)78 TfPyThrowKeyError(string const &msg)
79 {
80     TfPyLock pyLock;
81     PyErr_SetString(PyExc_KeyError, msg.c_str());
82     boost::python::throw_error_already_set();
83 }
84 
85 void
TfPyThrowValueError(string const & msg)86 TfPyThrowValueError(string const &msg)
87 {
88     TfPyLock pyLock;
89     PyErr_SetString(PyExc_ValueError, msg.c_str());
90     boost::python::throw_error_already_set();
91 }
92 
93 void
TfPyThrowTypeError(string const & msg)94 TfPyThrowTypeError(string const &msg)
95 {
96     TfPyLock pyLock;
97     PyErr_SetString(PyExc_TypeError, msg.c_str());
98     boost::python::throw_error_already_set();
99 }
100 
101 bool
TfPyIsNone(boost::python::object const & obj)102 TfPyIsNone(boost::python::object const &obj)
103 {
104     return obj.ptr() == Py_None;
105 }
106 
107 bool
TfPyIsNone(boost::python::handle<> const & obj)108 TfPyIsNone(boost::python::handle<> const &obj)
109 {
110     return !obj.get() || obj.get() == Py_None;
111 }
112 
Tf_PyLoadScriptModule(std::string const & moduleName)113 void Tf_PyLoadScriptModule(std::string const &moduleName)
114 {
115     if (TfPyIsInitialized()) {
116         TfPyLock pyLock;
117         string tmp(moduleName);
118         PyObject *result =
119             PyImport_ImportModule(const_cast<char *>(tmp.c_str()));
120         if (!result) {
121             // CODE_COVERAGE_OFF
122             TF_WARN("Import failed for module '%s'!", moduleName.c_str());
123             TfPyPrintError();
124             // CODE_COVERAGE_ON
125         }
126     } else {
127         // CODE_COVERAGE_OFF
128         TF_WARN("Attempted to load module '%s' but Python is not initialized.",
129                 moduleName.c_str());
130         // CODE_COVERAGE_ON
131     }
132 }
133 
134 bool
TfPyIsInitialized()135 TfPyIsInitialized()
136 {
137     return Py_IsInitialized();
138 }
139 
140 string
TfPyObjectRepr(boost::python::object const & t)141 TfPyObjectRepr(boost::python::object const &t)
142 {
143     if (!TfPyIsInitialized()) {
144         // CODE_COVERAGE_OFF
145         TF_CODING_ERROR("Called TfPyRepr without python being initialized!");
146         return "<error: python not initialized>";
147         // CODE_COVERAGE_ON
148     }
149     // Take the interpreter lock as we're about to call back to Python.
150     TfPyLock pyLock;
151 
152     // In case the try block throws, we'll return this string.
153     string reprString("<invalid repr>");
154 
155     try {
156         handle<> repr(PyObject_Repr(t.ptr()));
157         reprString = extract<string>(repr.get());
158 
159         // Python's repr() for NaN and Inf are not valid python which evaluates
160         // to themselves.  Special case them here to produce python which has
161         // this property.  This is unpleasant since we're not producing the real
162         // python repr, but we want everything coming out of here (if at all
163         // possible) to have this property.
164         if (reprString == "nan")
165             reprString = "float('nan')";
166         if (reprString == "inf")
167             reprString = "float('inf')";
168         if (reprString == "-inf")
169             reprString = "-float('inf')";
170 
171     } catch (error_already_set const &) {
172         PyErr_Clear();
173     }
174     return reprString;
175 }
176 
177 
178 boost::python::object
TfPyEvaluate(std::string const & expr,dict const & extraGlobals)179 TfPyEvaluate(std::string const &expr, dict const& extraGlobals)
180 {
181     TfPyLock lock;
182     try {
183         // Get the modules dict for the loaded script modules.
184         dict modulesDict =
185             TfScriptModuleLoader::GetInstance().GetModulesDict();
186 
187         // Make sure the builtins are available
188         handle<> modHandle(PyImport_ImportModule(TfPyBuiltinModuleName));
189         modulesDict["__builtins__"] = object(modHandle);
190         modulesDict.update(extraGlobals);
191 
192         // Eval the expression in that enviornment.
193         return object(TfPyRunString(expr, Py_eval_input,
194                                     modulesDict, modulesDict));
195     } catch (boost::python::error_already_set const &) {
196         TfPyConvertPythonExceptionToTfErrors();
197         PyErr_Clear();
198     }
199     return boost::python::object();
200 }
201 
202 
203 int64_t
TfPyNormalizeIndex(int64_t index,uint64_t size,bool throwError)204 TfPyNormalizeIndex(int64_t index, uint64_t size, bool throwError)
205 {
206     if (index < 0)
207         index += size;
208 
209     if (throwError && (index < 0 || static_cast<uint64_t>(index) >= size)) {
210         TfPyThrowIndexError("Index out of range.");
211     }
212 
213     return index < 0 ? 0 :
214                    static_cast<uint64_t>(index) >= size ? size - 1 : index;
215 }
216 
217 
218 TF_API void
Tf_PyWrapOnceImpl(boost::python::type_info const & type,std::function<void ()> const & wrapFunc,bool * isTypeWrapped)219 Tf_PyWrapOnceImpl(
220     boost::python::type_info const &type,
221     std::function<void()> const &wrapFunc,
222     bool * isTypeWrapped)
223 {
224     static std::mutex pyWrapOnceMutex;
225 
226     if (!wrapFunc) {
227         TF_CODING_ERROR("Got null wrapFunc");
228         return;
229     }
230 
231     // Acquire the GIL here, just so we can be sure that it is released before
232     // attempting to acquire our internal mutex.
233     TfPyLock pyLock;
234     pyLock.BeginAllowThreads();
235     std::lock_guard<std::mutex> lock(pyWrapOnceMutex);
236     pyLock.EndAllowThreads();
237 
238     // XXX: Double-checked locking
239     if (*isTypeWrapped) {
240         return;
241     }
242 
243     boost::python::type_handle pyType =
244         boost::python::objects::registered_class_object(type);
245 
246     if (!pyType) {
247         wrapFunc();
248     }
249 
250     *isTypeWrapped = true;
251 }
252 
253 
254 boost::python::object
TfPyGetClassObject(std::type_info const & type)255 TfPyGetClassObject(std::type_info const &type) {
256     TfPyLock pyLock;
257     return boost::python::object
258         (boost::python::objects::registered_class_object(type));
259 }
260 
TfPyGetClassName(object const & obj)261 string TfPyGetClassName(object const &obj)
262 {
263     // Take the interpreter lock as we're about to call back to Python.
264     TfPyLock pyLock;
265 
266     object classObject(obj.attr("__class__"));
267     if (classObject) {
268         object typeNameObject(classObject.attr("__name__"));
269         extract<string> typeName(typeNameObject);
270         if (typeName.check())
271             return typeName();
272     }
273 
274     // CODE_COVERAGE_OFF This shouldn't really happen.
275     TF_WARN("Couldn't get class name for python object '%s'",
276             TfPyRepr(obj).c_str());
277     return "<unknown>";
278     // CODE_COVERAGE_ON
279 }
280 
281 boost::python::object
TfPyCopyBufferToByteArray(const char * buffer,size_t size)282 TfPyCopyBufferToByteArray(const char* buffer, size_t size)
283 {
284     TfPyLock lock;
285     boost::python::object result;
286 
287     try {
288         // boost python doesn't include a bytearray object, but we can return
289         // one through the C api directly. The c api takes an array of char
290         // and a size, so uses the name FromString, but this is really just
291         // a buffer.
292         PyObject* buf = PyByteArray_FromStringAndSize(buffer, size);
293         boost::python::handle<> hbuf(buf);
294         result = boost::python::object(hbuf);
295     } catch (boost::python::error_already_set const &) {
296         TfPyConvertPythonExceptionToTfErrors();
297         PyErr_Clear();
298     }
299 
300     return result;
301 }
302 
TfPyGetTraceback()303 vector<string> TfPyGetTraceback()
304 {
305     vector<string> result;
306 
307     if (!TfPyIsInitialized())
308         return result;
309 
310     TfPyLock lock;
311     // Save the exception state so we can restore it -- getting a traceback
312     // should not affect the exception state.
313     TfPyExceptionStateScope exceptionStateScope;
314     try {
315         object tbModule(handle<>(PyImport_ImportModule("traceback")));
316         object stack = tbModule.attr("format_stack")();
317         size_t size = len(stack);
318         result.reserve(size);
319         for (size_t i = 0; i < size; ++i) {
320             string s = extract<string>(stack[i]);
321             result.push_back(s);
322         }
323     } catch (boost::python::error_already_set const &) {
324         TfPyConvertPythonExceptionToTfErrors();
325     }
326     return result;
327 }
328 
329 void
TfPyGetStackFrames(vector<uintptr_t> * frames)330 TfPyGetStackFrames(vector<uintptr_t> *frames)
331 {
332     if (!TfPyIsInitialized())
333         return;
334 
335     TfPyLock lock;
336     try {
337         object tbModule(handle<>(PyImport_ImportModule("traceback")));
338         object stack = tbModule.attr("format_stack")();
339         size_t size = len(stack);
340         frames->reserve(size);
341         // Reverse the order of stack frames so that the stack is ordered
342         // like the output of ArchGetStackFrames() (deepest function call at
343         // the top of stack).
344         for (long i = static_cast<long>(size)-1; i >= 0; --i) {
345             string *s = new string(extract<string>(stack[i]));
346             frames->push_back((uintptr_t)s);
347         }
348     } catch (boost::python::error_already_set const &) {
349         TfPyConvertPythonExceptionToTfErrors();
350         PyErr_Clear();
351     }
352 }
353 
TfPyDumpTraceback()354 void TfPyDumpTraceback() {
355     printf("Traceback (most recent call last):\n");
356     vector<string> tb = TfPyGetTraceback();
357     TF_FOR_ALL(i, tb)
358         printf("%s", i->c_str());
359 }
360 
361 
362 static object
_GetOsEnviron()363 _GetOsEnviron()
364 {
365     // In theory, we could just check that the os module has been imported,
366     // rather than forcing an import ourself.  However, it's possible that
367     // os.environ is actually a re-export from another module (ie. posix,
368     // which is the case as of CPython 2.6) that may have been imported
369     // without importing os.  Rather than check a hardcoded list of potential
370     // modules, we always import os if Python is initialized.  If this turns
371     // out to be problematic, we may want to consider the other approach.
372     boost::python::object
373         module(boost::python::handle<>(PyImport_ImportModule("os")));
374     boost::python::object environObj(module.attr("environ"));
375     return environObj;
376 }
377 
378 bool
TfPySetenv(const std::string & name,const std::string & value)379 TfPySetenv(const std::string & name, const std::string & value)
380 {
381     if (!TfPyIsInitialized()) {
382         TF_CODING_ERROR("Python is uninitialized.");
383         return false;
384     }
385 
386     TfPyLock lock;
387 
388     try {
389         object environObj(_GetOsEnviron());
390         environObj[name] = value;
391         return true;
392     }
393     catch (boost::python::error_already_set&) {
394         PyErr_Clear();
395     }
396 
397     return false;
398 }
399 
400 bool
TfPyUnsetenv(const std::string & name)401 TfPyUnsetenv(const std::string & name)
402 {
403     if (!TfPyIsInitialized()) {
404         TF_CODING_ERROR("Python is uninitialized.");
405         return false;
406     }
407 
408     TfPyLock lock;
409 
410     try {
411         object environObj(_GetOsEnviron());
412         object has_key = environObj.attr("__contains__");
413         if (has_key(name)) {
414             environObj[name].del();
415         }
416         return true;
417     }
418     catch (boost::python::error_already_set&) {
419         PyErr_Clear();
420     }
421 
422     return false;
423 }
424 
Tf_PyEvaluateWithErrorCheck(const std::string & expr,boost::python::object * obj)425 bool Tf_PyEvaluateWithErrorCheck(
426     const std::string & expr, boost::python::object * obj)
427 {
428     TfErrorMark m;
429     *obj = TfPyEvaluate(expr);
430     return m.IsClean();
431 }
432 
433 void
TfPyPrintError()434 TfPyPrintError()
435 {
436     if (!PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
437         PyErr_Print();
438     }
439 }
440 
441 void
Tf_PyObjectError(bool printError)442 Tf_PyObjectError(bool printError)
443 {
444     // Silently pass these exceptions through.
445     if (PyErr_ExceptionMatches(PyExc_SystemExit) ||
446         PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
447         return;
448     }
449 
450     // Report and clear.
451     if (printError) {
452         PyErr_Print();
453     }
454     else {
455         PyErr_Clear();
456     }
457 }
458 
459 PXR_NAMESPACE_CLOSE_SCOPE
460