diff --git a/pxr/usd/usdGeom/wrapXformOp.cpp b/pxr/usd/usdGeom/wrapXformOp.cpp index 7016fbfea9..11b9781656 100644 --- a/pxr/usd/usdGeom/wrapXformOp.cpp +++ b/pxr/usd/usdGeom/wrapXformOp.cpp @@ -86,6 +86,49 @@ _GetOpName(const UsdGeomXformOp &self) } // anonymous namespace + +// We override __getattribute__ for UsdGeomXformOp to check object validity +// and raise an exception instead of crashing from Python. +// +// Store the original __getattribute__ so we can dispatch to it after verifying +// validity +static TfStaticData _object__getattribute__; + +// This function gets wrapped as __getattribute__ on UsdGeomXformOp. +static object +__getattribute__(object selfObj, const char *name) { + // Allow attribute lookups if the attribute name starts with '__', or + // if the object's prim and attribute are both valid, or whitelist a few + // methods if just the prim is valid, or an even smaller subset if neighter + // is valid. + if ((name[0] == '_' && name[1] == '_') || + // prim and attr are valid, let everything through. + (extract(selfObj)().GetAttr().IsValid() && + extract(selfObj)().GetAttr().GetPrim().IsValid()) || + // prim is valid, but attr is invalid, let a few things through. + (extract(selfObj)().GetAttr().GetPrim().IsValid() && + (strcmp(name, "IsInverseOp") == 0 || + strcmp(name, "IsDefined") == 0 || + strcmp(name, "GetName") == 0 || + strcmp(name, "GetBaseName") == 0 || + strcmp(name, "GetNamespace") == 0 || + strcmp(name, "GetOpTransform") == 0 || + strcmp(name, "GetOpName") == 0 || + strcmp(name, "GetOpType") == 0 || + strcmp(name, "SplitName") == 0)) || + // prim and attr are both invalid, let almost nothing through. + strcmp(name, "GetAttr") == 0) { + // Dispatch to object's __getattribute__. + return (*_object__getattribute__)(selfObj, name); + } else { + // Otherwise raise a runtime error. + TfPyThrowRuntimeError( + TfStringPrintf("Accessed schema on invalid prim")); + } + // Unreachable. + return object(); +} + void wrapUsdGeomXformOp() { typedef UsdGeomXformOp XformOp; @@ -93,7 +136,8 @@ void wrapUsdGeomXformOp() TF_PY_WRAP_PUBLIC_TOKENS("XformOpTypes", UsdGeomXformOpTypes, USDGEOM_XFORM_OP_TYPES); - scope s = class_("XformOp") + class_ cls("XformOp"); + scope s = cls .def(init( (arg("attr"), arg("isInverseOp")=false))) @@ -149,5 +193,9 @@ void wrapUsdGeomXformOp() TfPyContainerConversions::from_python_sequence, TfPyContainerConversions::variable_capacity_policy >(); + + // Save existing __getattribute__ and replace. + *_object__getattribute__ = object(cls.attr("__getattribute__")); + cls.def("__getattribute__", __getattribute__); } diff --git a/pxr/usd/usdGeom/xformOp.cpp b/pxr/usd/usdGeom/xformOp.cpp index ed2eac3456..b3d51ff82b 100644 --- a/pxr/usd/usdGeom/xformOp.cpp +++ b/pxr/usd/usdGeom/xformOp.cpp @@ -116,7 +116,8 @@ UsdGeomXformOp::UsdGeomXformOp(const UsdAttribute &attr, bool isInverseOp) _isInverseOp(isInverseOp) { if (!attr) { - TF_CODING_ERROR("UsdGeomXformOp created with invalid UsdAttribute."); + // Legal to construct an XformOp with invalid attr, however IsDefined() + // and explicit bool operator will return false. return; }