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 #include "pxr/usd/pcp/targetIndex.h"
27 #include "pxr/usd/pcp/cache.h"
28 #include "pxr/usd/pcp/layerStack.h"
29 #include "pxr/usd/pcp/node_Iterator.h"
30 #include "pxr/usd/pcp/pathTranslation.h"
31 #include "pxr/usd/pcp/primIndex.h"
32 #include "pxr/usd/pcp/propertyIndex.h"
33 
34 #include "pxr/usd/sdf/listOp.h"
35 #include "pxr/usd/sdf/propertySpec.h"
36 #include "pxr/usd/sdf/types.h"
37 
38 #include "pxr/base/trace/trace.h"
39 
40 #include <boost/optional.hpp>
41 
42 #include <functional>
43 
44 PXR_NAMESPACE_OPEN_SCOPE
45 
46 // Helper structure for deferring the computation of a prim index
47 // until needed, then caching the result to avoid multiple lookups
48 struct Pcp_TargetIndexContext
49 {
Pcp_TargetIndexContextPcp_TargetIndexContext50     Pcp_TargetIndexContext(
51         PcpCache* cache,
52         PcpErrorVector* allErrors,
53         const SdfPath& targetObjectPath)
54         : _cache(cache)
55         , _allErrors(allErrors)
56         , _targetObjectPath(targetObjectPath)
57         , _index(NULL) { }
58 
GetCachePcp_TargetIndexContext59     PcpCache* GetCache()
60     {
61         return _cache;
62     }
63 
GetTargetObjectPrimIndexPcp_TargetIndexContext64     const PcpPrimIndex& GetTargetObjectPrimIndex()
65     {
66         if (!_index) {
67             _index = &_cache->ComputePrimIndex(
68                 _targetObjectPath.GetPrimPath(), _allErrors);
69         }
70         return *_index;
71     }
72 
73 private:
74     PcpCache* _cache;
75     PcpErrorVector* _allErrors;
76     SdfPath _targetObjectPath;
77     const PcpPrimIndex* _index;
78 };
79 
80 // Helper to determine if the connection path was authored in
81 // a class but points to an instance of the class.
82 static bool
_TargetInClassAndTargetsInstance(const SdfPath & connectionPathInNodeNS,const PcpNodeRef & nodeWhereConnectionWasAuthored,Pcp_TargetIndexContext & context)83 _TargetInClassAndTargetsInstance(
84     const SdfPath& connectionPathInNodeNS,
85     const PcpNodeRef& nodeWhereConnectionWasAuthored,
86     Pcp_TargetIndexContext& context)
87 {
88     // Connections authored in an inherited class may not target
89     // an object in an instance of that class, as doing so would
90     // break reverse path translation.
91     if (!PcpIsInheritArc(nodeWhereConnectionWasAuthored.GetArcType())) {
92         return false;
93     }
94 
95     TRACE_FUNCTION();
96 
97     // If the connection path targets a namespace descendent of the class,
98     // we know we're not pointing at an object in an instance of the
99     // class.
100     //
101     // Otherwise, we compute the prim index for the target object and
102     // check if it (or an ancestor) inherits from the class where
103     // the connection was authored. If so, issue an error.
104     const bool connectionPathInsideInheritedClass =
105         connectionPathInNodeNS.HasPrefix(
106             nodeWhereConnectionWasAuthored.GetPathAtIntroduction());
107 
108     if (!connectionPathInsideInheritedClass) {
109         const PcpPrimIndex& targetPrimIndex = context.GetTargetObjectPrimIndex();
110         const PcpLayerStackPtr& layerStackWhereConnectionWasAuthored =
111             nodeWhereConnectionWasAuthored.GetLayerStack();
112         const SdfPath inheritedClassPath =
113             nodeWhereConnectionWasAuthored.GetPathAtIntroduction();
114 
115         for (const PcpNodeRef &n: targetPrimIndex.GetNodeRange()) {
116             if (PcpIsInheritArc(n.GetArcType())
117                 && (n.GetLayerStack() == layerStackWhereConnectionWasAuthored)
118                 && (n.GetPath().HasPrefix(inheritedClassPath))) {
119                 return true;
120             }
121         }
122     }
123 
124     return false;
125 }
126 
127 namespace {
128 
129 enum Pcp_PathTranslationError
130 {
131     NoError = 0,
132     PermissionDenied,
133     InvalidTarget
134 };
135 
136 }
137 
138 static Pcp_PathTranslationError
_CheckTargetPermittedBeneathNode(const SdfPath & connectionPathInRootNS,const PcpNodeRef & node)139 _CheckTargetPermittedBeneathNode(
140     const SdfPath& connectionPathInRootNS, const PcpNodeRef& node)
141 {
142     const bool targetObjectIsProperty = connectionPathInRootNS.IsPropertyPath();
143 
144     TF_FOR_ALL(it, Pcp_GetChildrenRange(node)) {
145         const PcpNodeRef& child = *it;
146 
147         // If the prim has been marked private at this node, the
148         // target is pointing at a restricted object, which is invalid.
149         if (child.IsRestricted() ||
150             child.GetPermission() == SdfPermissionPrivate) {
151             return PermissionDenied;
152         }
153 
154         // Translate the path from the root namespace to this child's
155         // namespace. If the path translation fails, this target is
156         // invalid so we return a general 'invalid target' error.
157         //
158         // At this point, though, path translation should only fail if
159         // the connection is pointing at the pre-relocated source of
160         // an object that has been relocated. We could verify this is
161         // the case by searching the relocates statements in this node's
162         // layer stack for this object, but that is potentially expensive.
163         // So for now, we just let this remain a general error.
164         const SdfPath pathInChildNS =
165             PcpTranslatePathFromRootToNode(child, connectionPathInRootNS);
166         if (pathInChildNS.IsEmpty()) {
167             return InvalidTarget;
168         }
169 
170         if (targetObjectIsProperty) {
171             TF_FOR_ALL(layerIt, child.GetLayerStack()->GetLayers()) {
172                 // Check all property specs up to the owning prim to see if any
173                 // are marked private. This handles the case where the property
174                 // is a relational attribute; in this case, we'd need to check
175                 // not only the attribute, but its owning relationship.
176                 for (SdfPath p = pathInChildNS;
177                      !p.IsPrimPath(); p = p.GetParentPath()) {
178 
179                     if (p.IsPropertyPath()) {
180                         SdfPropertySpecHandle propSpec =
181                             (*layerIt)->GetPropertyAtPath(p);
182                         if (propSpec &&
183                             propSpec->GetPermission() == SdfPermissionPrivate) {
184                             return PermissionDenied;
185                         }
186                     }
187                 }
188             }
189         }
190 
191         const Pcp_PathTranslationError errorUnderChild =
192             _CheckTargetPermittedBeneathNode(connectionPathInRootNS, child);
193         if (errorUnderChild != NoError) {
194             return errorUnderChild;
195         }
196     }
197 
198     return NoError;
199 }
200 
201 // Helper function to determine if the object indicated by the given paths
202 // can be targeted by an attribute connection or relationship. There are two
203 // primary things we verify here:
204 //
205 // - Permissions
206 // A connection is invalid if the object it targets is marked private
207 // in a weaker site than where the connection was authored. For instance:
208 //
209 //        ref     ref
210 //     /A ---> /B ---> /C
211 //
212 // If we have a connection authored in /B, it's OK if the targeted object
213 // was marked private in /A or /B, but not if it was marked private in /C.
214 // See ErrorTargetPermissionDenied for more examples.
215 //
216 // - Relocates
217 // A connection is invalid if the object it targets was relocated, and the
218 // connection is pointing to the object's path prior to relocation. This
219 // is verified indirectly -- see comment in _CheckTargetPermittedBenaethNode.
220 // See ErrorInvalidPreRelocateTargetPath for examples.
221 //
222 static Pcp_PathTranslationError
_TargetIsPermitted(const SdfPath & connectionPathInRootNS,const SdfPath & connectionPathInNodeNS,const PcpNodeRef & nodeWhereConnectionWasAuthored,Pcp_TargetIndexContext & context)223 _TargetIsPermitted(
224     const SdfPath& connectionPathInRootNS,
225     const SdfPath& connectionPathInNodeNS,
226     const PcpNodeRef& nodeWhereConnectionWasAuthored,
227     Pcp_TargetIndexContext& context)
228 {
229     TRACE_FUNCTION();
230 
231     // The approach for figuring out where the given connection is valid
232     // is to compute the prim index for the target object in the root layer
233     // stack (i.e., the layer stack for the given cache), find the node
234     // that corresponds to where the connection was authored, then check
235     // the subtree beneath that node.
236     //
237     // An alternative approach would be to compute the index for the target
238     // object in the layer stack where the connection was authored and to
239     // use that when checking permissions. This would avoid the need to search
240     // for a specific node. However, we wouldn't be able to use the given cache
241     // and would have to compute the index from scratch each time, which is
242     // too expensive.
243 
244     const SdfPath owningPrimInRootNS = connectionPathInRootNS.GetPrimPath();
245     const PcpPrimIndex& owningPrimIndex = context.GetTargetObjectPrimIndex();
246 
247     // Search for the node for the owning prim where the connection was
248     // authored.
249     const SdfPath owningPrimInNodeNS = connectionPathInNodeNS.GetPrimPath();
250     const PcpLayerStackSite owningPrimSiteWhereConnectionWasAuthored(
251         nodeWhereConnectionWasAuthored.GetLayerStack(),
252         owningPrimInNodeNS);
253 
254     PcpNodeRef owningPrimNodeWhereConnectionWasAuthored;
255     for (const PcpNodeRef &node: owningPrimIndex.GetNodeRange()) {
256         if (node.GetSite() == owningPrimSiteWhereConnectionWasAuthored) {
257             owningPrimNodeWhereConnectionWasAuthored = node;
258             break;
259         }
260     }
261 
262     // It's possible that we won't find the node we're looking for because
263     // it was culled out of the graph. This can happen in a few cases
264     // (I think this is a complete list):
265     //
266     //  1. The target object doesn't exist, e.g., a bad path was authored.
267     //  2. The target object does exist in the composed scene, but is brought
268     //     in via a completely separate arc.
269     //  3. The target object is in a payload, but the connection is authored
270     //     outside the payload.
271     //
272     // See /CulledPermissions_{1, 2, 3} in ErrorPermissionDenied for examples
273     // of each case.
274     //
275     // In all cases, we permit the connection. This ignores permissions that
276     // usually are inherited down namespace but maintains legacy behavior
277     // from Csd. If we wanted to make the behavior consistent here, we would
278     // have to walk up namespace to see if there were other permissions to
279     // apply.
280     //
281     // If culling is disabled, we definitely expect to find the node, so
282     // issue an error if we don't.
283     if (!owningPrimNodeWhereConnectionWasAuthored) {
284         TF_VERIFY(context.GetCache()->GetPrimIndexInputs().cull,
285             "Could not find expected node for site %s in prim index for <%s>",
286             TfStringify(owningPrimSiteWhereConnectionWasAuthored).c_str(),
287             owningPrimInRootNS.GetText());
288 
289         return NoError;
290     }
291 
292     return _CheckTargetPermittedBeneathNode(
293             connectionPathInRootNS, owningPrimNodeWhereConnectionWasAuthored);
294 }
295 
296 static void
_RemoveTargetPathErrorsForPath(const SdfPath & composedTargetPath,PcpErrorVector * targetPathErrors)297 _RemoveTargetPathErrorsForPath(
298     const SdfPath& composedTargetPath,
299     PcpErrorVector* targetPathErrors)
300 {
301     if (targetPathErrors->empty()) {
302         return;
303     }
304 
305     PcpErrorVector::iterator
306         it = targetPathErrors->begin(), end = targetPathErrors->end();
307     while (it != end) {
308         if (const PcpErrorTargetPathBase* targetPathError =
309             dynamic_cast<const PcpErrorTargetPathBase*>(it->get())) {
310             if (targetPathError->composedTargetPath == composedTargetPath) {
311                 it = targetPathErrors->erase(it);
312                 end = targetPathErrors->end();
313                 continue;
314             }
315         }
316         ++it;
317     }
318 }
319 
320 // Callback used to translate paths as path list operations from
321 // various nodes are applied.
322 static boost::optional<SdfPath>
_PathTranslateCallback(SdfListOpType opType,const PcpSite & propSite,const PcpNodeRef & node,const SdfPath & inPath,const SdfPropertySpecHandle & owningProp,const SdfSpecType relOrAttrType,PcpCache * cacheForValidation,SdfPathVector * deletedPaths,PcpErrorVector * targetPathErrors,PcpErrorVector * otherErrors)323 _PathTranslateCallback(
324     SdfListOpType opType,
325     const PcpSite &propSite,
326     const PcpNodeRef& node,
327     const SdfPath& inPath,
328     const SdfPropertySpecHandle& owningProp,
329     const SdfSpecType relOrAttrType,
330     PcpCache* cacheForValidation,
331     SdfPathVector *deletedPaths,
332     PcpErrorVector* targetPathErrors,
333     PcpErrorVector* otherErrors)
334 {
335     bool pathIsMappable = false;
336 
337     const SdfPath translatedPath =
338         PcpTranslatePathFromNodeToRoot(node, inPath, &pathIsMappable);
339 
340     // If the given path is part of a delete list operation, we don't
341     // need to perform any of the validation below. Since the specified
342     // path is being deleted from the composed result, we want to delete
343     // any errors associated with that path from our list of errors.
344     //
345     // This is similar to handling for explicit list operations
346     // in PcpBuildFilteredTargetIndex.
347     if (opType == SdfListOpTypeDeleted) {
348         if (pathIsMappable && !translatedPath.IsEmpty()) {
349             if (deletedPaths) {
350                 deletedPaths->push_back(translatedPath);
351             }
352             _RemoveTargetPathErrorsForPath(translatedPath, targetPathErrors);
353             return translatedPath;
354         }
355         return boost::optional<SdfPath>();
356     }
357 
358     if (!pathIsMappable) {
359         PcpErrorInvalidExternalTargetPathPtr err =
360             PcpErrorInvalidExternalTargetPath::New();
361         err->rootSite = propSite;
362         err->targetPath = inPath;
363         err->owningPath = owningProp->GetPath();
364         err->ownerSpecType = relOrAttrType;
365         err->ownerArcType = node.GetArcType();
366         err->ownerIntroPath = node.GetIntroPath();
367         err->layer = owningProp->GetLayer();
368         err->composedTargetPath = SdfPath();
369         targetPathErrors->push_back(err);
370         return boost::optional<SdfPath>();
371     }
372 
373     if (translatedPath.IsEmpty()) {
374         return boost::optional<SdfPath>();
375     }
376 
377     if (cacheForValidation) {
378         Pcp_TargetIndexContext context(
379             cacheForValidation, otherErrors, translatedPath);
380 
381         // Check if this target has been authored in a class but targets
382         // an instance of the class.
383         if (_TargetInClassAndTargetsInstance(inPath, node, context)) {
384             PcpErrorInvalidInstanceTargetPathPtr err =
385                 PcpErrorInvalidInstanceTargetPath::New();
386             err->rootSite = propSite;
387             err->targetPath = inPath;
388             err->owningPath = owningProp->GetPath();
389             err->ownerSpecType = relOrAttrType;
390             err->layer = owningProp->GetLayer();
391             err->composedTargetPath = translatedPath;
392             targetPathErrors->push_back(err);
393             return boost::optional<SdfPath>();
394         }
395 
396         // Check if the connection is invalid due to permissions or
397         // relocates. We do not do this check for Usd caches, since Usd
398         // does not use either feature.
399         if (!cacheForValidation->IsUsd()) {
400 
401             switch (_TargetIsPermitted(translatedPath, inPath, node, context)) {
402             case PermissionDenied:
403             {
404                 PcpErrorTargetPermissionDeniedPtr err =
405                     PcpErrorTargetPermissionDenied::New();
406                 err->rootSite = propSite;
407                 err->targetPath = inPath;
408                 err->owningPath = owningProp->GetPath();
409                 err->ownerSpecType = relOrAttrType;
410                 err->layer = owningProp->GetLayer();
411                 err->composedTargetPath = translatedPath;
412                 targetPathErrors->push_back(err);
413                 return boost::optional<SdfPath>();
414             }
415 
416             case InvalidTarget:
417             {
418                 PcpErrorInvalidTargetPathPtr err =
419                     PcpErrorInvalidTargetPath::New();
420                 err->rootSite = propSite;
421                 err->targetPath = inPath;
422                 err->owningPath = owningProp->GetPath();
423                 err->ownerSpecType = relOrAttrType;
424                 err->layer = owningProp->GetLayer();
425                 err->composedTargetPath = translatedPath;
426                 targetPathErrors->push_back(err);
427                 return boost::optional<SdfPath>();
428             }
429 
430             case NoError:
431                 // Do nothing.
432                 break;
433             }
434         }
435     }
436 
437     return translatedPath;
438 }
439 
440 void
PcpBuildFilteredTargetIndex(const PcpSite & propSite,const PcpPropertyIndex & propertyIndex,const SdfSpecType relOrAttrType,const bool localOnly,const SdfSpecHandle & stopProperty,const bool includeStopProperty,PcpCache * cacheForValidation,PcpTargetIndex * targetIndex,SdfPathVector * deletedPaths,PcpErrorVector * allErrors)441 PcpBuildFilteredTargetIndex(
442     const PcpSite& propSite,
443     const PcpPropertyIndex& propertyIndex,
444     const SdfSpecType relOrAttrType,
445     const bool localOnly,
446     const SdfSpecHandle &stopProperty,
447     const bool includeStopProperty,
448     PcpCache *cacheForValidation,
449     PcpTargetIndex *targetIndex,
450     SdfPathVector *deletedPaths,
451     PcpErrorVector *allErrors)
452 {
453     TRACE_FUNCTION();
454 
455     if (!(relOrAttrType == SdfSpecTypeRelationship ||
456              relOrAttrType == SdfSpecTypeAttribute)) {
457         TF_CODING_ERROR("relOrAttrType msut be either SdfSpecTypeRelationship"
458                         " or SdfSpecTypeAttribute");
459         return;
460     }
461 
462     if (propertyIndex.IsEmpty()) {
463         return;
464     }
465 
466     const PcpPropertyRange propertyRange =
467         propertyIndex.GetPropertyRange(localOnly);
468 
469     // Verify that the type of object at propSite.path matches what
470     // we expect. We only need to check the first spec in the stack since all
471     // other specs should have the same type. This is enforced in the
472     // population of the property index.
473     if (!TF_VERIFY(
474             (*propertyRange.first)->GetSpecType() == relOrAttrType,
475             "<%s> is not %s",
476             propSite.path.GetText(),
477             relOrAttrType == SdfSpecTypeAttribute ?
478                 "an attribute" : "a relationship")) {
479         return;
480     }
481 
482     const TfToken& fieldName =
483         relOrAttrType == SdfSpecTypeAttribute ?
484             SdfFieldKeys->ConnectionPaths : SdfFieldKeys->TargetPaths;
485 
486     SdfPathVector paths;
487     PcpErrorVector targetPathErrors;
488     bool hasTargetOpinions = false;
489 
490     // Walk the property stack from weakest to strongest, applying path list
491     // operations with the appropriate path translations to targetPaths.
492     TF_REVERSE_FOR_ALL(propIt, propertyRange) {
493         const SdfPropertySpecHandle& property = *propIt;
494         if (!includeStopProperty && property == stopProperty) {
495             break;
496         }
497         const VtValue& pathValue = property->GetField(fieldName);
498         if (pathValue.IsEmpty() ||
499             !TF_VERIFY(pathValue.IsHolding<SdfPathListOp>())) {
500             continue;
501         }
502         const SdfPathListOp& pathListOps =
503             pathValue.UncheckedGet<SdfPathListOp>();
504         if (pathListOps.HasKeys()) {
505             hasTargetOpinions = true;
506 
507             // If this list op is explicit, its contents will overwrite
508             // everything we've composed up to this point. Because of this,
509             // we can clear all of the target path errors we've accumulated
510             // since the errorneous paths are being overridden.
511             if (pathListOps.IsExplicit()) {
512                 targetPathErrors.clear();
513                 if (deletedPaths) {
514                     deletedPaths->clear();
515                 }
516             }
517 
518             SdfPathListOp::ApplyCallback pathTranslationCallback =
519                 std::bind(&_PathTranslateCallback,
520                           std::placeholders::_1, std::ref(propSite),
521                           propIt.base().GetNode(), std::placeholders::_2,
522                           std::ref(property), relOrAttrType,
523                           cacheForValidation,
524                           deletedPaths,
525                           &targetPathErrors, allErrors);
526             pathListOps.ApplyOperations(&paths, pathTranslationCallback);
527         }
528         if (property == stopProperty) {
529             break;
530         }
531     }
532 
533     allErrors->insert(
534         allErrors->end(), targetPathErrors.begin(), targetPathErrors.end());
535 
536     targetIndex->paths.swap(paths);
537     targetIndex->localErrors.swap(targetPathErrors);
538     targetIndex->hasTargetOpinions = hasTargetOpinions;
539 }
540 
541 void
PcpBuildTargetIndex(const PcpSite & propSite,const PcpPropertyIndex & propIndex,const SdfSpecType relOrAttrType,PcpTargetIndex * targetIndex,PcpErrorVector * allErrors)542 PcpBuildTargetIndex(
543     const PcpSite& propSite,
544     const PcpPropertyIndex& propIndex,
545     const SdfSpecType relOrAttrType,
546     PcpTargetIndex *targetIndex,
547     PcpErrorVector *allErrors)
548 {
549     PcpBuildFilteredTargetIndex(
550         propSite, propIndex, relOrAttrType,
551         /* localOnly = */ false,
552         /* stopProperty = */ SdfSpecHandle(),
553         /* includeStopProperty = */ false,
554         /* cacheForValidation = */ 0,
555         targetIndex,
556         /* deletedPaths = */ nullptr,
557         allErrors);
558 }
559 
560 PXR_NAMESPACE_CLOSE_SCOPE
561