1 //
2 // Copyright 2021 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 #include "pxr/base/arch/stackTrace.h"
27 #include "pxr/base/tf/diagnostic.h"
28 #include "pxr/base/tf/exception.h"
29 #include "pxr/base/tf/stringUtils.h"
30 #include "pxr/base/tf/ostreamMethods.h"
31 #include "pxr/base/tf/pyCall.h"
32 #include "pxr/base/tf/pyErrorInternal.h"
33
34 #include <boost/python/def.hpp>
35 #include <boost/python/exception_translator.hpp>
36
37 using namespace boost::python;
38
39 PXR_NAMESPACE_USING_DIRECTIVE
40
41 // This is created below, in the wrap function.
42 static PyObject *tfExceptionClass;
43
Translate(TfBaseException const & exc)44 static void Translate(TfBaseException const &exc)
45 {
46 // Format an error message showing the C++ throw-location for at least a few
47 // frames.
48 static constexpr size_t MaxFramesInMsg = 16;
49 std::vector<uintptr_t> const &throwStack = exc.GetThrowStack();
50 std::vector<std::string> throwStackFrames;
51 std::string framesMsg;
52 if (!throwStack.empty()) {
53 std::stringstream throwStackText;
54 ArchPrintStackFrames(throwStackText, throwStack,
55 /*skipUnknownFrames=*/true);
56 std::vector<std::string> throwStackMsg =
57 TfStringSplit(throwStackText.str(), "\n");
58
59 if (throwStackMsg.size() > MaxFramesInMsg) {
60 size_t additional = throwStackMsg.size() - MaxFramesInMsg;
61 throwStackMsg.resize(MaxFramesInMsg);
62 throwStackMsg.push_back(
63 TfStringPrintf("... %zu more frame%s",
64 additional, additional == 1 ? "" : "s"));
65 }
66 framesMsg = TfStringJoin(throwStackMsg, "\n ");
67 }
68 std::string contextMsg;
69 if (TfCallContext const &cc = exc.GetThrowContext()) {
70 contextMsg = TfStringPrintf(
71 "%s at %s:%zu", cc.GetFunction(), cc.GetFile(), cc.GetLine());
72 }
73
74 // Raise the Python exception.
75 PyErr_Format(tfExceptionClass, "%s - %s%s%s%s",
76 exc.what(),
77 ArchGetDemangled(typeid(exc)).c_str(),
78 contextMsg.empty() ? "" : " thrown:\n -> ",
79 contextMsg.empty() ? "" : contextMsg.c_str(),
80 framesMsg.empty() ? "" : (
81 TfStringPrintf(" from\n %s",
82 framesMsg.c_str()).c_str()));
83
84 // Attach the current c++ exception to the python exception so we can
85 // rethrow it later, possibly, if we return from python back to C++.
86 std::exception_ptr cppExc = std::current_exception();
87 if (TF_VERIFY(cppExc)) {
88 TfPyExceptionStateScope pyExcState;
89 boost::python::object pyErr(pyExcState.Get().GetValue());
90 uintptr_t cppExcAddr;
91 std::unique_ptr<std::exception_ptr>
92 cppExecPtrPtr(new std::exception_ptr(cppExc));
93 std::exception_ptr *eptrAddr = cppExecPtrPtr.get();
94 memcpy(&cppExcAddr, &eptrAddr, sizeof(cppExcAddr));
95 // Stash it.
96 pyErr.attr("_pxr_SavedTfException") = cppExcAddr;
97 cppExecPtrPtr.release();
98 }
99 }
100
101 // Exception class for unit test.
102 class _TestExceptionToPython : public TfBaseException
103 {
104 public:
105 using TfBaseException::TfBaseException;
106 virtual ~_TestExceptionToPython();
107 };
108
~_TestExceptionToPython()109 _TestExceptionToPython::~_TestExceptionToPython()
110 {
111 }
112
113 // Unit test support.
_ThrowTest(std::string message)114 static void _ThrowTest(std::string message)
115 {
116 TF_THROW(_TestExceptionToPython, message);
117 }
118
_CallThrowTest(boost::python::object fn)119 static void _CallThrowTest(boost::python::object fn)
120 {
121 TfPyCall<void> callFn(fn);
122 callFn();
123 }
124
wrapException()125 void wrapException()
126 {
127 char excClassName[] = "pxr.Tf.CppException"; // XXX: Custom py NS here?
128 tfExceptionClass = PyErr_NewException(excClassName, NULL, NULL);
129
130 // Expose the exception class to python.
131 scope().attr("CppException") = boost::python::handle<>(tfExceptionClass);
132
133 // Register the exception translator with boost::python.
134 register_exception_translator<TfBaseException>(Translate);
135
136 // Test support.
137 boost::python::def("_ThrowTest", _ThrowTest);
138 boost::python::def("_CallThrowTest", _CallThrowTest);
139 }
140