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