blob: f3afffd5981d4261e9095d01d08bc93c13ee8582 [file] [edit]
/* 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