| /* Copyright (c) 2015-2026 The Khronos Group Inc. |
| * Copyright (c) 2015-2026 Valve Corporation |
| * Copyright (c) 2015-2026 LunarG, Inc. |
| * Copyright (C) 2015-2026 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "chassis/validation_object.h" |
| #include "containers/small_vector.h" |
| #include "containers/span.h" |
| |
| namespace object_lifetimes { |
| |
| enum ObjectStatusFlags { |
| kObjectStatusNone = 0x0, |
| kObjectStatusCustomAllocator = 0x1, // Allocated with custom allocator |
| kObjectStatusPoisoned = 0x2 // This object references destoyed objects |
| }; |
| |
| struct ObjectState; |
| class Tracker; |
| class Device; |
| |
| using ObjectMap = vvl::concurrent_unordered_map<uint64_t, std::shared_ptr<ObjectState>, 6>; |
| |
| // Used for GPL and we know there are at most only 4 libraries that should be used |
| using ObjectMapGPL = vvl::concurrent_unordered_map<uint64_t, small_vector<std::shared_ptr<ObjectState>, 4>, 6>; |
| |
| struct ObjectState { |
| uint64_t handle; |
| VulkanObjectType object_type; |
| uint32_t status_flags; |
| uint64_t parent_object; |
| |
| // Child objects (used for VkDescriptorPool only) |
| std::unique_ptr<vvl::unordered_set<uint64_t> > child_objects; |
| |
| // ObjectState can be accessed from multiple threads |
| mutable std::shared_mutex poison_lock; |
| auto WriteLock() const { return std::unique_lock<std::shared_mutex>(poison_lock); } |
| auto ReadLock() const { return std::shared_lock<std::shared_mutex>(poison_lock); } |
| |
| // The objects to poison if the current object becomes poisoned. |
| // These objects reference the current object in some way. |
| vvl::unordered_set<VulkanTypedHandle> objects_to_poison; |
| |
| // The objects that can poison this object (they have this object in their objects_to_poison list). |
| // When the current object is destroyed it has to remove itself from all registered poisoners. |
| std::vector<VulkanTypedHandle> poisoners; |
| |
| // If this object is poisoned, then this is a chain of poisoned objects starting with the object |
| // that was deleted and the last object is the one that directly marked this object as poisoned. |
| std::vector<VulkanTypedHandle> poison_chain; |
| |
| void MakePoisonous(Tracker &tracker, VulkanTypedHandle poisoner, const std::vector<VulkanTypedHandle> &parent_poison_chain); |
| VulkanTypedHandle TypedHandle() const; |
| }; |
| |
| class Tracker : public Logger { |
| public: |
| Tracker(DebugReport *dr) : Logger(dr) {} |
| |
| std::shared_ptr<ObjectState> GetObjectState(VulkanTypedHandle object) const; |
| |
| template <typename T1> |
| bool ValidateDestroyObject(T1 object_handle, VulkanObjectType object_type, const VkAllocationCallbacks *pAllocator, |
| const char *expected_custom_allocator_code, const char *expected_default_allocator_code, |
| const Location &loc) const { |
| return ValidateDestroyObject(VulkanTypedHandle(object_handle, object_type), pAllocator, expected_custom_allocator_code, |
| expected_default_allocator_code, loc); |
| } |
| |
| template <typename T1> |
| bool ValidateObject(T1 object, VulkanObjectType object_type, bool null_allowed, bool poisoned_object_allowed, |
| const char *invalid_handle_vuid, const char *wrong_parent_vuid, const Location &loc) const { |
| if (null_allowed && (object == VK_NULL_HANDLE)) { |
| return false; |
| } |
| return CheckObjectValidity(VulkanTypedHandle(object, object_type), poisoned_object_allowed, invalid_handle_vuid, |
| wrong_parent_vuid, loc); |
| } |
| |
| template <typename T1, typename T2> |
| void CreateObject(T1 object, VulkanObjectType object_type, const VkAllocationCallbacks *pAllocator, const Location &loc, |
| T2 parent_object) { |
| CreateObject(VulkanTypedHandle(object, object_type), pAllocator, loc, HandleToUint64(parent_object)); |
| } |
| |
| template <typename T1> |
| void RecordDestroyObject(T1 object_handle, VulkanObjectType object_type, const Location &loc) { |
| RecordDestroyObject(VulkanTypedHandle(object_handle, object_type), loc); |
| } |
| |
| template <typename PoisonerVkType> |
| void RegisterPoisonPairs(VulkanTypedHandle poisonee, vvl::span<PoisonerVkType> poisoners, VulkanObjectType poisoner_type) { |
| if (auto poisonee_state = GetObjectState(poisonee)) { |
| auto poisonee_lock = poisonee_state->WriteLock(); |
| for (PoisonerVkType poisoner_vk_handle : poisoners) { |
| const VulkanTypedHandle poisoner(poisoner_vk_handle, poisoner_type); |
| if (auto poisoner_state = GetObjectState(poisoner)) { |
| auto poisoner_lock = poisoner_state->WriteLock(); |
| poisoner_state->objects_to_poison.emplace(poisonee); |
| } |
| } |
| } |
| } |
| |
| bool TracksObject(VulkanTypedHandle object) const; |
| bool CheckObjectValidity(VulkanTypedHandle object, bool poisoned_object_allowed, const char *invalid_handle_vuid, |
| const char *wrong_parent_vuid, const Location &loc) const; |
| void DestroyObjectSilently(VulkanTypedHandle object, const Location &loc); |
| void DestroyUndestroyedObjects(VulkanObjectType object_type, const Location &loc); |
| |
| void RegisterPoisonPair(VulkanTypedHandle poisonee, VulkanTypedHandle poisoner); |
| bool CheckPoisoning(const ObjectState &object_state, const char *vuid, const Location &loc) const; |
| std::string DescribePoisonChain(const std::vector<VulkanTypedHandle> &poison_chain) const; |
| |
| void SetDeviceHandle(const Device& device); |
| void SetInstanceHandle(VkInstance instance); |
| bool IsMaintenance4Enabled() const { return is_device_maintenance4_enabled_; } |
| |
| private: |
| void CreateObject(VulkanTypedHandle object, const VkAllocationCallbacks *pAllocator, const Location &loc, |
| uint64_t parent_handle); |
| bool ValidateDestroyObject(VulkanTypedHandle object, const VkAllocationCallbacks *pAllocator, |
| const char *expected_custom_allocator_code, const char *expected_default_allocator_code, |
| const Location &loc) const; |
| void RecordDestroyObject(VulkanTypedHandle object, const Location &loc); |
| |
| public: |
| ObjectMap object_map[kVulkanObjectTypeMax + 1]; |
| |
| private: |
| // We don't know the handle (VkDevice or VkInstance) when Tracker is created and need to set afterwards |
| VulkanTypedHandle handle_; |
| |
| // Affects validity of the objects that use pipeline layout |
| bool is_device_maintenance4_enabled_ = false; |
| }; |
| |
| class Instance : public vvl::BaseInstance { |
| public: |
| using Func = vvl::Func; |
| using Struct = vvl::Struct; |
| using Field = vvl::Field; |
| |
| Tracker tracker; |
| |
| Instance(vvl::DispatchInstance* dispatch); |
| ~Instance(); |
| |
| void DestroyLeakedObjects(); |
| bool ReportUndestroyedObjects(const Location &loc) const; |
| void FindLeakedObjects(VulkanObjectType object_type, std::vector<VulkanTypedHandle>& leaked_list) const; |
| bool ReportLeakedObjects(std::vector<VulkanTypedHandle>& leaked_list, const Location& loc) const; |
| |
| void AllocateDisplayKHR(VkPhysicalDevice physical_device, VkDisplayKHR display, const Location &loc); |
| |
| // helper methods for tracker |
| template <typename T1> |
| bool ValidateDestroyObject(T1 object_handle, VulkanObjectType object_type, const VkAllocationCallbacks *pAllocator, |
| const char *expected_custom_allocator_code, const char *expected_default_allocator_code, |
| const Location &loc) const { |
| return tracker.ValidateDestroyObject(object_handle, object_type, pAllocator, expected_custom_allocator_code, |
| expected_default_allocator_code, loc); |
| } |
| template <typename T1> |
| bool ValidateObject(T1 object, VulkanObjectType object_type, bool null_allowed, const char *invalid_handle_vuid, |
| const char *wrong_parent_vuid, const Location &loc) const { |
| return tracker.ValidateObject(object, object_type, null_allowed, true, invalid_handle_vuid, wrong_parent_vuid, loc); |
| } |
| void DestroyObjectSilently(VulkanTypedHandle object, const Location &loc) { return tracker.DestroyObjectSilently(object, loc); } |
| template <typename T1> |
| void RecordDestroyObject(T1 object, VulkanObjectType object_type, const Location &loc) { |
| tracker.RecordDestroyObject(object, object_type, loc); |
| } |
| void DestroyUndestroyedObjects(VulkanObjectType object_type, const Location &loc) { tracker.DestroyUndestroyedObjects(object_type, loc); } |
| |
| #include "generated/object_tracker_instance_methods.h" |
| }; |
| |
| class Device : public vvl::BaseDevice { |
| using Func = vvl::Func; |
| using Struct = vvl::Struct; |
| using Field = vvl::Field; |
| |
| public: |
| // Override chassis read/write locks for this validation object |
| // This override takes a deferred lock. i.e. it is not acquired. |
| // This class does its own locking with a shared mutex. |
| ReadLockGuard ReadLock() const override; |
| WriteLockGuard WriteLock() override; |
| |
| Tracker tracker; |
| mutable std::shared_mutex object_lifetime_mutex; |
| WriteLockGuard WriteSharedLock() { return WriteLockGuard(object_lifetime_mutex); } |
| ReadLockGuard ReadSharedLock() const { return ReadLockGuard(object_lifetime_mutex); } |
| |
| ObjectMapGPL linked_graphics_pipeline_map; |
| |
| // Constructor for object lifetime tracking |
| Device(vvl::DispatchDevice *dev, Instance *instance); |
| ~Device(); |
| |
| void FinishDeviceSetup(const VkDeviceCreateInfo *pCreateInfo, const Location &loc) override; |
| |
| void DestroyLeakedObjects(); |
| bool ReportUndestroyedObjects(const Location &loc) const; |
| void FindLeakedObjects(VulkanObjectType object_type, std::vector<VulkanTypedHandle>& leaked_list) const; |
| bool ReportLeakedObjects(std::vector<VulkanTypedHandle>& leaked_list, const Location& loc) const; |
| |
| void CreateQueue(VkQueue vkObj, const Location &loc); |
| void AllocateCommandBuffer(const VkCommandPool command_pool, const VkCommandBuffer command_buffer, VkCommandBufferLevel level, |
| const Location &loc); |
| void AllocateDescriptorSet(VkDescriptorPool descriptor_pool, VkDescriptorSet descriptor_set, const Location &loc); |
| void CreateSwapchainImageObject(VkImage swapchain_image, VkSwapchainKHR swapchain, const Location &loc); |
| bool ValidateCommandBuffer(VkCommandPool command_pool, VkCommandBuffer command_buffer, const Location &loc) const; |
| bool ValidateDescriptorSet(VkDescriptorPool descriptor_pool, VkDescriptorSet descriptor_set, const Location &loc) const; |
| bool ValidateDescriptorSetLayoutCreateInfo(const VkDescriptorSetLayoutCreateInfo &create_info, |
| const Location &create_info_loc) const; |
| bool ValidateDescriptorWrite(VkWriteDescriptorSet const *desc, bool is_push_descriptor, const Location &loc) const; |
| bool ValidateAnonymousObject(uint64_t object, VkObjectType core_object_type, const char *invalid_handle_vuid, |
| const char *wrong_parent_vuid, const Location &loc) const; |
| bool ValidateAccelerationStructures(const char *src_handle_vuid, const char *dst_handle_vuid, uint32_t count, |
| const VkAccelerationStructureBuildGeometryInfoKHR *infos, const Location &loc) const; |
| bool CheckPipelineObjectValidity(uint64_t object_handle, const char *invalid_handle_vuid, const Location &loc) const; |
| |
| // helper methods for tracker |
| template <typename T1> |
| bool ValidateDestroyObject(T1 object_handle, VulkanObjectType object_type, const VkAllocationCallbacks *pAllocator, |
| const char *expected_custom_allocator_code, const char *expected_default_allocator_code, |
| const Location &loc) const { |
| return tracker.ValidateDestroyObject(object_handle, object_type, pAllocator, expected_custom_allocator_code, |
| expected_default_allocator_code, loc); |
| } |
| |
| template <typename T1> |
| bool ValidateObject(T1 object, VulkanObjectType object_type, bool null_allowed, bool poisoned_object_allowed, |
| const char *invalid_handle_vuid, const char *wrong_parent_vuid, const Location &loc) const { |
| uint64_t object_handle = HandleToUint64(object); |
| |
| // special case if for pipeline if using GPL |
| if (object_type == kVulkanObjectTypePipeline) { |
| if (auto object_state = tracker.GetObjectState(VulkanTypedHandle(object, object_type))) { |
| bool skip = false; |
| if (!poisoned_object_allowed) { |
| skip |= tracker.CheckPoisoning(*object_state, invalid_handle_vuid, loc); |
| } |
| // If destroying, even if the child libraries are gone, the user still |
| // has a way to remove the bad parent pipeline library |
| if (loc.function != Func::vkDestroyPipeline) { |
| skip |= CheckPipelineObjectValidity(object_handle, invalid_handle_vuid, loc); |
| } |
| return skip; |
| } |
| } |
| return tracker.ValidateObject(object, object_type, null_allowed, poisoned_object_allowed, invalid_handle_vuid, |
| wrong_parent_vuid, loc); |
| } |
| |
| template <typename T1> |
| bool ValidateObject(T1 object, VulkanObjectType object_type, bool null_allowed, const char *invalid_handle_vuid, |
| const char *wrong_parent_vuid, const Location &loc) const { |
| return ValidateObject(object, object_type, null_allowed, true, invalid_handle_vuid, wrong_parent_vuid, loc); |
| } |
| |
| template <typename T1> |
| void RecordDestroyObject(T1 object, VulkanObjectType object_type, const Location &loc) { |
| tracker.RecordDestroyObject(object, object_type, loc); |
| } |
| void DestroyUndestroyedObjects(VulkanObjectType object_type, const Location &loc) { tracker.DestroyUndestroyedObjects(object_type, loc); } |
| |
| // RegisterPoisonPair helper when using CreatePipelines APIs |
| template <typename TCreateInfo> |
| void RegisterCommonPipelinePoisoning(const TCreateInfo *create_infos, const VkPipeline *pipelines, uint32_t pipeline_index) { |
| const VulkanTypedHandle pipeline_handle(pipelines[pipeline_index], kVulkanObjectTypePipeline); |
| const VulkanTypedHandle pipeline_layout_handle(create_infos[pipeline_index].layout, kVulkanObjectTypePipelineLayout); |
| tracker.RegisterPoisonPair(pipeline_handle, pipeline_layout_handle); |
| } |
| |
| #include "generated/object_tracker_device_methods.h" |
| }; |
| } // namespace object_lifetimes |