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