35 bug getasset returns copy of asset ident not pointer#69
35 bug getasset returns copy of asset ident not pointer#69tadeas-hejnic wants to merge 14 commits into
Conversation
…r instead of copy
…e of return value type in getAsset
…r instead of copy
There was a problem hiding this comment.
Pull request overview
This PR changes the resolver cache API to return an AssetIdentifier via pointer rather than returning it by value, with the intent of avoiding copies when fetching cached assets.
Changes:
- Updated
ResolverContextCache::getAsset()andPinningFileHandler::getAssetData()to returnAssetIdentifier*. - Updated
AyonUsdResolver::_Resolve()to use the pointer-returning cache API. - Modified cache hit paths to return pointers to elements stored in the cache containers.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/AyonUsdResolver/resolver.cpp |
Switched to using AssetIdentifier* from the cache and dereferencing it to build the resolved path. |
src/AyonUsdResolver/cache/resolverContextCache.h |
Updated public API signatures to return AssetIdentifier*. |
src/AyonUsdResolver/cache/resolverContextCache.cpp |
Implemented pointer-based returns, including heap allocations and returning pointers to unordered_set elements. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -76,9 +76,11 @@ PinningFileHandler::getAssetData(const std::string &resolveKey) { | |||
| return assetEntry; | |||
| } | |||
|
|
|||
| assetEntry = new AssetIdentifier(); | |||
|
|
|||
| if (!pinnedAssetPath.empty()) { | |||
| assetEntry.setAssetIdentifier(resolveKey); | |||
| assetEntry.setResolvedAssetPath(pinnedAssetPath); | |||
| assetEntry->setAssetIdentifier(resolveKey); | |||
| assetEntry->setResolvedAssetPath(pinnedAssetPath); | |||
| } | |||
|
|
|||
| return assetEntry; | |||
There was a problem hiding this comment.
This function now returns a raw pointer allocated with new AssetIdentifier() but there is no corresponding ownership contract or delete, so every successful lookup leaks. Also, when the key is missing you return nullptr, which forces all callers to handle null (and resolver.cpp currently doesn't). Prefer returning by value / std::optional<AssetIdentifier> / std::unique_ptr<AssetIdentifier> (with clear ownership), or return a const AssetIdentifier&/pointer to stable storage managed by the handler.
| if (assetIdentifier.empty()) { | ||
| return asset; | ||
| return new AssetIdentifier(); | ||
| } | ||
| if (m_staticCache) { | ||
| return m_pinningFileHandler->getAssetData(assetIdentifier); | ||
| } |
There was a problem hiding this comment.
getAsset allocates with new AssetIdentifier() on the empty-identifier path. This leaks unless every caller deletes the returned pointer, which is not documented or implemented anywhere in the repo. If you want to avoid copies, prefer returning std::optional<std::reference_wrapper<const AssetIdentifier>>, const AssetIdentifier* to cache-owned storage, or a smart pointer that clearly expresses ownership.
| std::shared_lock<std::shared_mutex> preCacheSharedLock(m_PreCacheSharedMutex); | ||
| hit = m_PreCache.find(assetIdentifier); | ||
| if (hit != m_PreCache.end()) { | ||
| asset = *hit; | ||
| asset = const_cast<AssetIdentifier*>(&(*hit)); // get the pointer without making a copy of the object | ||
| preCacheSharedLock.unlock(); | ||
|
|
||
| TF_DEBUG(AYONUSDRESOLVER_RESOLVER_CONTEXT) | ||
| .Msg("ResolverContextCache::getAsset: PreCache Hit on (%s) with (%s) \n", | ||
| asset.getAssetIdentifier().c_str(), asset.getResolvedAssetPath().GetPathString().c_str()); | ||
| asset->getAssetIdentifier().c_str(), asset->getResolvedAssetPath().GetPathString().c_str()); | ||
| return asset; |
There was a problem hiding this comment.
Returning a non-const AssetIdentifier* to an element inside an std::unordered_set via const_cast is unsafe: callers could mutate fields that participate in hashing/equality (m_assetIdentifier), breaking container invariants and causing undefined behavior. If you need to return a pointer, make it const AssetIdentifier* (and remove the const_cast), or change the cache storage to something designed for stable, mutable values (e.g., unordered_map<std::string, AssetIdentifier>).
|
|
||
| TF_DEBUG(AYONUSDRESOLVER_RESOLVER_CONTEXT).Msg("ResolverContextCache::getAsset: called ayon.resolvePath() \n"); | ||
| this->insert(asset); | ||
| this->insert(*asset); |
There was a problem hiding this comment.
In the AYON resolve branch you call this->insert(*asset), but insert() moves from its argument (m_PreCache.insert(std::move(sourceAssetIdent))). That means the heap object *asset is left in a moved-from/empty state, yet you still return the pointer asset at the end of the function. This will cause callers to observe an empty/invalid AssetIdentifier despite having inserted the resolved entry into the cache. You likely want to either (1) insert a copy and keep returning the same object, or (2) insert and then return a pointer/reference to the cached element (not the moved-from temporary).
| this->insert(*asset); | |
| AssetIdentifier cachedAsset(*asset); | |
| this->insert(cachedAsset); |
| std::shared_lock<std::shared_mutex> CommonCacheSharedLock(m_CommonCacheSharedMutex); | ||
|
|
||
| TF_DEBUG(AYONUSDRESOLVER_RESOLVER_CONTEXT) | ||
| .Msg("ResolverContextCache::getAsset: insert into CommonCache \n"); | ||
| m_CommonCache.insert(asset); | ||
| m_CommonCache.insert(*asset); | ||
| } |
There was a problem hiding this comment.
This inserts into m_CommonCache while holding only a std::shared_lock on m_CommonCacheSharedMutex. unordered_set::insert is a write operation and requires exclusive locking; doing it under a shared lock is a data race in multi-threaded use. Switch this to std::unique_lock (or reuse existing write-locking patterns used elsewhere in this file).
| * @return AssetIdentifier with resolved path | ||
| */ | ||
| AssetIdentifier getAsset(const std::string &assetIdentifier, const CacheName selectedCache, const bool isAyonPath); | ||
| AssetIdentifier* getAsset(const std::string &assetIdentifier, const CacheName selectedCache, const bool isAyonPath); | ||
|
|
There was a problem hiding this comment.
The header change to return raw pointers (AssetIdentifier*) introduces unclear lifetime/ownership for API consumers and makes it easy to leak or dereference a dangling/null pointer. If the goal is to avoid copying, prefer returning const AssetIdentifier*/const AssetIdentifier& tied to cache lifetime, or a smart pointer/optional that makes ownership explicit.
| @@ -161,7 +161,7 @@ AyonUsdResolver::_Resolve(const std::string &assetPath) const { | |||
| if (pos != std::string::npos) { | |||
| sdfArgs = pathToResolve->substr(pos + cleanAssetPath.length()); | |||
| } | |||
| std::string resolvedPathStr = asset.getResolvedAssetPath().GetPathString() + sdfArgs; | |||
| std::string resolvedPathStr = asset->getResolvedAssetPath().GetPathString() + sdfArgs; | |||
| ArResolvedPath resolvedPath(resolvedPathStr); | |||
There was a problem hiding this comment.
resolverCache->getAsset() can return nullptr (e.g., PinningFileHandler::getAssetData returns nullptr when resolveKey is not present). This code unconditionally dereferences asset (asset->getResolvedAssetPath()), which will crash for missing pinning entries or other failure paths. Consider making getAsset() never return null (e.g., return a reference/optional), or add a null check here and fall back to trying the next context / returning the original path.
Changelog Description
Pass the AssetIdentifier as a pointer instead of copy.
Additional review information
The builds work, so it should be all OK.