Skip to content

Commit 1b91e3e

Browse files
committed
Add clang-tidy to CI, fix findings, merge lint workflows
closes #2000
1 parent 83ddfc5 commit 1b91e3e

13 files changed

Lines changed: 120 additions & 24 deletions

.clang-tidy

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
# SPDX-License-Identifier: Apache-2.0
3+
Checks: >
4+
-*,
5+
bugprone-*,
6+
cert-*,
7+
clang-analyzer-*,
8+
performance-*,
9+
portability-*,
10+
-bugprone-assignment-in-if-condition,
11+
-bugprone-branch-clone,
12+
-bugprone-easily-swappable-parameters,
13+
-bugprone-invalid-enum-default-initialization,
14+
-bugprone-multi-level-implicit-pointer-conversion,
15+
-bugprone-unchecked-string-to-number-conversion,
16+
-clang-analyzer-optin.core.EnumCastOutOfRange,
17+
-clang-analyzer-optin.performance.Padding,
18+
-performance-no-int-to-ptr,
19+
-portability-avoid-pragma-once
20+
WarningsAsErrors: '*'
21+
HeaderFilterRegex: '.*/loader/.*'
22+
ExcludeHeaderFilterRegex: '.*/loader/(generated/.*|cJSON\.h)$'
23+
FormatStyle: none
Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
# SPDX-License-Identifier: Apache-2.0
55

6-
name: format
6+
name: lint
77

88
on:
99
push:
@@ -33,3 +33,26 @@ jobs:
3333
done
3434
3535
git diff --exit-code
36+
37+
clang-tidy:
38+
name: clang-tidy
39+
runs-on: ubuntu-26.04
40+
timeout-minutes: 30
41+
42+
env:
43+
CLANG_TIDY_VERSION: 22
44+
45+
steps:
46+
- uses: actions/checkout@v7
47+
- uses: actions/setup-python@v7
48+
with:
49+
python-version: '3.11'
50+
- run: sudo apt update
51+
- run: sudo apt install --yes --no-install-recommends libwayland-dev libxrandr-dev
52+
- run: |
53+
cmake -S. -B build \
54+
-D CMAKE_BUILD_TYPE=Release \
55+
-D UPDATE_DEPS=ON \
56+
-D USE_CLANG_TIDY=ON \
57+
-D CLANG_TIDY_EXE=$(command -v clang-tidy-${CLANG_TIDY_VERSION})
58+
- run: cmake --build build

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ if (CODE_COVERAGE)
2626
add_code_coverage_all_targets()
2727
endif()
2828

29+
option(USE_CLANG_TIDY "Run clang-tidy on the loader sources as part of the build")
30+
if (USE_CLANG_TIDY)
31+
if (CMAKE_VERSION VERSION_LESS "3.27")
32+
message(FATAL_ERROR "USE_CLANG_TIDY requires CMake 3.27 or newer (for the SKIP_LINTING source property)")
33+
endif()
34+
find_program(CLANG_TIDY_EXE NAMES clang-tidy REQUIRED)
35+
set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}")
36+
endif()
2937

3038
# This variable enables downstream users to customize the target API
3139
# variant (e.g. Vulkan SC)

loader/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,12 @@ target_link_libraries(vulkan PRIVATE loader_specific_options)
529529
target_link_libraries(vulkan PRIVATE Vulkan::Headers)
530530
add_library(Vulkan::Loader ALIAS vulkan)
531531

532+
if (USE_CLANG_TIDY)
533+
set_target_properties(vulkan PROPERTIES C_CLANG_TIDY "${CLANG_TIDY_COMMAND}")
534+
# cJSON is vendored third-party code; don't hold it to this project's clang-tidy checks.
535+
set_source_files_properties(cJSON.c PROPERTIES SKIP_LINTING ON)
536+
endif()
537+
532538
if (UNKNOWN_FUNCTIONS_SUPPORTED)
533539
target_compile_definitions(vulkan PRIVATE UNKNOWN_FUNCTIONS_SUPPORTED)
534540
add_dependencies(vulkan loader_asm_gen_files)

loader/asm_offset.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ int main(int argc, char **argv) {
130130
};
131131

132132
FILE *file = loader_fopen("gen_defines.asm", "w");
133+
// NOLINTBEGIN(cert-err33-c) - this is a build-time code generator run by developers, not part of the loader's
134+
// runtime attack surface; a failed write here is immediately visible as a broken build, not worth per-call checks.
133135
fprintf(file, "\n");
134136
if (assembler == MASM) {
135137
for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
@@ -168,5 +170,6 @@ int main(int argc, char **argv) {
168170
values[i].comment);
169171
}
170172
}
173+
// NOLINTEND(cert-err33-c)
171174
return fclose(file);
172175
}

loader/dev_ext_trampoline.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ VKAPI_ATTR void VKAPI_CALL vkdev_ext248(VkDevice device) ASM_NAME("vkdev_ext248"
286286
VKAPI_ATTR void VKAPI_CALL vkdev_ext249(VkDevice device) ASM_NAME("vkdev_ext249");
287287

288288
void *loader_get_dev_ext_trampoline(uint32_t index) {
289+
// NOLINTNEXTLINE(bugprone-switch-missing-default-case) - falls through to the return NULL below
289290
switch (index) {
290291
#define CASE_HANDLE(num) case num: return vkdev_ext##num
291292
CASE_HANDLE(0);

loader/dirent_on_windows.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ extern "C" {
2323
typedef ptrdiff_t handle_type; /* C99's intptr_t not sufficiently portable */
2424

2525
struct DIR {
26-
handle_type handle; /* -1 for failed rewind */
27-
struct _finddata_t info;
28-
struct dirent result; /* d_name null iff first time */
29-
char *name; /* null-terminated char string */
26+
handle_type handle; /* -1 for failed rewind */
27+
struct _finddata_t info; // NOLINT(bugprone-reserved-identifier) - matching the Windows CRT type name
28+
struct dirent result; /* d_name null iff first time */
29+
char *name; /* null-terminated char string */
3030
};
3131

3232
DIR *opendir(const VkAllocationCallbacks *pAllocator, const char *name) {

loader/loader.c

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
#endif // LOADER_ENABLE_LINUX_SORT
7575

7676
// Generated file containing all the extension data
77+
// NOLINTNEXTLINE(bugprone-suspicious-include) - intentionally including a .c file to inline generated tables
7778
#include "vk_loader_extensions.c"
7879

7980
struct loader_struct loader = {0};
@@ -701,21 +702,21 @@ uint32_t loader_parse_version_string(char *vers_str) {
701702

702703
vers_tok = thread_safe_strtok(vers_str, ".\"\n\r", &context);
703704
if (NULL != vers_tok) {
704-
major = (uint16_t)atoi(vers_tok);
705+
major = (uint16_t)strtoul(vers_tok, NULL, 10);
705706
vers_tok = thread_safe_strtok(NULL, ".\"\n\r", &context);
706707
if (NULL != vers_tok) {
707-
minor = (uint16_t)atoi(vers_tok);
708+
minor = (uint16_t)strtoul(vers_tok, NULL, 10);
708709
vers_tok = thread_safe_strtok(NULL, ".\"\n\r", &context);
709710
if (NULL != vers_tok) {
710-
patch = (uint16_t)atoi(vers_tok);
711+
patch = (uint16_t)strtoul(vers_tok, NULL, 10);
711712
vers_tok = thread_safe_strtok(NULL, ".\"\n\r", &context);
712713
// check that we are using a 4 part version string
713714
if (NULL != vers_tok) {
714715
// if we are, move the values over into the correct place
715716
variant = major;
716717
major = minor;
717718
minor = patch;
718-
patch = (uint16_t)atoi(vers_tok);
719+
patch = (uint16_t)strtoul(vers_tok, NULL, 10);
719720
}
720721
}
721722
}
@@ -1168,6 +1169,8 @@ VkResult loader_add_to_ext_list(const struct loader_instance *inst, struct loade
11681169
ext_list->capacity *= 2;
11691170
}
11701171

1172+
// every caller passes the address of an array element or on-stack struct, never NULL
1173+
// NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker)
11711174
memcpy(&ext_list->list[ext_list->count], cur_ext, sizeof(VkExtensionProperties));
11721175
ext_list->count++;
11731176
}
@@ -1213,6 +1216,8 @@ VkResult loader_add_to_dev_ext_list(const struct loader_instance *inst, struct l
12131216
ext_list->capacity *= 2;
12141217
}
12151218

1219+
// every caller passes the address of an array element or on-stack struct, never NULL
1220+
// NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker)
12161221
memcpy(&ext_list->list[idx].props, props, sizeof(*props));
12171222
if (entrys) {
12181223
ext_list->list[idx].entrypoints = *entrys;
@@ -1592,7 +1597,7 @@ VkResult loader_get_icd_loader_instance_extensions(const struct loader_instance
15921597

15931598
// Check if a user wants to disable the instance extension filtering behavior
15941599
env_value = loader_getenv("VK_LOADER_DISABLE_INST_EXT_FILTER", inst);
1595-
if (NULL != env_value && atoi(env_value) != 0) {
1600+
if (NULL != env_value && strtol(env_value, NULL, 10) != 0) {
15961601
filter_extensions = false;
15971602
}
15981603
loader_free_getenv(env_value, inst);
@@ -2358,6 +2363,7 @@ void loader_initialize(void) {
23582363

23592364
char *loader_disable_dynamic_library_unloading_env_var = loader_getenv("VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING", NULL);
23602365
if (loader_disable_dynamic_library_unloading_env_var &&
2366+
// NOLINTNEXTLINE(bugprone-not-null-terminated-result) - n=2 intentionally excludes "1x" values like "10"
23612367
0 == strncmp(loader_disable_dynamic_library_unloading_env_var, "1", 2)) {
23622368
loader_disable_dynamic_library_unloading = true;
23632369
loader_log(NULL, VULKAN_LOADER_WARN_BIT, 0, "Vulkan Loader: library unloading is disabled");
@@ -2746,7 +2752,7 @@ void remove_all_non_valid_override_layers(struct loader_instance *inst, struct l
27462752
}
27472753
} else {
27482754
if (global_layer_index == -1) {
2749-
global_layer_index = i;
2755+
global_layer_index = (int)i;
27502756
} else {
27512757
loader_log(
27522758
inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0,
@@ -2875,7 +2881,7 @@ VkResult loader_read_layer_json(const struct loader_instance *inst, struct loade
28752881
result = VK_ERROR_INITIALIZATION_FAILED;
28762882
goto out;
28772883
}
2878-
props.info.implementationVersion = atoi(implementation_version);
2884+
props.info.implementationVersion = (uint32_t)strtoul(implementation_version, NULL, 10);
28792885

28802886
// Parse description
28812887

@@ -3057,7 +3063,7 @@ VkResult loader_read_layer_json(const struct loader_instance *inst, struct loade
30573063
result = loader_parse_json_string(ext_item, "spec_version", &spec_version);
30583064
if (result == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
30593065
if (NULL != spec_version) {
3060-
ext_prop.specVersion = atoi(spec_version);
3066+
ext_prop.specVersion = (uint32_t)strtoul(spec_version, NULL, 10);
30613067
}
30623068
loader_instance_heap_free(inst, spec_version);
30633069
bool ext_unsupported = wsi_unsupported_instance_extension(&ext_prop);
@@ -3092,7 +3098,7 @@ VkResult loader_read_layer_json(const struct loader_instance *inst, struct loade
30923098
result = loader_parse_json_string(ext_item, "spec_version", &spec_version);
30933099
if (result == VK_ERROR_OUT_OF_HOST_MEMORY) goto out;
30943100
if (NULL != spec_version) {
3095-
ext_prop.specVersion = atoi(spec_version);
3101+
ext_prop.specVersion = (uint32_t)strtoul(spec_version, NULL, 10);
30963102
}
30973103
loader_instance_heap_free(inst, spec_version);
30983104

@@ -5010,7 +5016,7 @@ VkResult loader_create_instance_chain(const VkInstanceCreateInfo *pCreateInfo, c
50105016
}
50115017

50125018
// Create instance chain of enabled layers
5013-
for (int32_t i = inst->expanded_activated_layer_list.count - 1; i >= 0; i--) {
5019+
for (int32_t i = (int32_t)inst->expanded_activated_layer_list.count - 1; i >= 0; i--) {
50145020
struct loader_layer_properties *layer_prop = inst->expanded_activated_layer_list.list[i];
50155021
loader_platform_dl_handle lib_handle;
50165022

@@ -5409,7 +5415,7 @@ VkResult loader_create_device_chain(const VkPhysicalDevice pd, const VkDeviceCre
54095415
loader_create_info.pNext = &chain_info;
54105416

54115417
// Create instance chain of enabled layers
5412-
for (int32_t i = inst->expanded_activated_layer_list.count - 1; i >= 0; i--) {
5418+
for (int32_t i = (int32_t)inst->expanded_activated_layer_list.count - 1; i >= 0; i--) {
54135419
struct loader_layer_properties *layer_prop = inst->expanded_activated_layer_list.list[i];
54145420
loader_platform_dl_handle lib_handle = layer_prop->lib_handle;
54155421

@@ -5666,7 +5672,7 @@ VkResult loader_validate_instance_extensions(struct loader_instance *inst, const
56665672

56675673
// Check if a user wants to disable the instance extension filtering behavior
56685674
env_value = loader_getenv("VK_LOADER_DISABLE_INST_EXT_FILTER", inst);
5669-
if (NULL != env_value && atoi(env_value) != 0) {
5675+
if (NULL != env_value && strtol(env_value, NULL, 10) != 0) {
56705676
check_if_known = false;
56715677
}
56725678
loader_free_getenv(env_value, inst);
@@ -5814,6 +5820,9 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateInstance(const VkInstanceCreateI
58145820
// what the application requested, and thus will increase the instance version to a level that suites their needs.
58155821
if (pCreateInfo->pApplicationInfo && pCreateInfo->pApplicationInfo->apiVersion) {
58165822
loader_api_version altered_version = loader_make_version(pCreateInfo->pApplicationInfo->apiVersion);
5823+
// per LLP_LAYER_21 (docs/LoaderLayerInterface.md), a layer that clobbers pInstance before calling down is
5824+
// documented to crash the loader; this is not recoverable here.
5825+
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
58175826
if (altered_version.major != ptr_instance->app_api_version.major ||
58185827
altered_version.minor != ptr_instance->app_api_version.minor) {
58195828
ptr_instance->app_api_version = altered_version;
@@ -5848,12 +5857,14 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateInstance(const VkInstanceCreateI
58485857
} else {
58495858
for (uint32_t j = 0; j < pCreateInfo->enabledExtensionCount; j++) {
58505859
if (!strcmp(pCreateInfo->ppEnabledExtensionNames[j], VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
5860+
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference) - see LLP_LAYER_21 note above
58515861
ptr_instance->supports_get_dev_prop_2 = true;
58525862
break;
58535863
}
58545864
}
58555865
}
58565866

5867+
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference) - see LLP_LAYER_21 note above
58575868
for (uint32_t i = 0; i < ptr_instance->icd_tramp_list.count; i++) {
58585869
icd_term = loader_icd_add(ptr_instance, &ptr_instance->icd_tramp_list.scanned_list[i]);
58595870
if (NULL == icd_term) {
@@ -6221,6 +6232,9 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateDevice(VkPhysicalDevice physical
62216232
dev, dev->loader_dispatch.core_dispatch.magic);
62226233
}
62236234

6235+
// per LLP_LAYER_22 (docs/LoaderLayerInterface.md), a layer that clobbers pDevice before calling down is
6236+
// documented to crash the loader; this is not recoverable here.
6237+
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
62246238
dev->phys_dev_term = phys_dev_term;
62256239

62266240
icd_exts.list = NULL;
@@ -6599,7 +6613,7 @@ VkResult setup_loader_tramp_phys_devs(struct loader_instance *inst, uint32_t phy
65996613
for (uint32_t cur_idx = 0; cur_idx < old_count; ++cur_idx) {
66006614
if (old_to_new_index[cur_idx] == -1) {
66016615
new_phys_devs[new_idx] = inst->phys_devs_tramp[cur_idx];
6602-
old_to_new_index[cur_idx] = new_idx;
6616+
old_to_new_index[cur_idx] = (int32_t)new_idx;
66036617
found_count++;
66046618
break;
66056619
}
@@ -6667,7 +6681,7 @@ bool is_linux_sort_enabled(struct loader_instance *inst) {
66676681
bool sort_items = inst->supports_get_dev_prop_2;
66686682
char *env_value = loader_getenv("VK_LOADER_DISABLE_SELECT", inst);
66696683
if (NULL != env_value) {
6670-
int32_t int_env_val = atoi(env_value);
6684+
int32_t int_env_val = (int32_t)strtol(env_value, NULL, 10);
66716685
loader_free_getenv(env_value, inst);
66726686
if (int_env_val != 0) {
66736687
sort_items = false;
@@ -7792,6 +7806,9 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumeratePhysicalDeviceGroups(
77927806

77937807
// Create a temporary array (on the stack) to keep track of the
77947808
// returned VkPhysicalDevice values.
7809+
// total_count may legitimately be 0 (no physical device groups); every subsequent use of local_phys_dev_groups
7810+
// is bounded by total_count, so a 0-byte alloca is never touched.
7811+
// NOLINTNEXTLINE(clang-analyzer-optin.portability.UnixAPI)
77957812
local_phys_dev_groups = loader_stack_alloc(sizeof(struct loader_physical_device_group_term) * total_count);
77967813
// Initialize the memory to something valid
77977814
memset(local_phys_dev_groups, 0, sizeof(struct loader_physical_device_group_term) * total_count);
@@ -7912,6 +7929,12 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumeratePhysicalDeviceGroups(
79127929
icd_term->scanned_icd->lib_name);
79137930
goto out;
79147931
}
7932+
// Same guard as the other two paths above: the ICD can report a larger completed count here than it did
7933+
// during the counting pass. tmp_group_props and local_phys_dev_groups were both sized to the earlier
7934+
// (clamped) count_this_time, so trusting a larger value here would overflow both stack allocations.
7935+
if (count_this_time > total_count - cur_icd_group_count) {
7936+
count_this_time = total_count - cur_icd_group_count;
7937+
}
79157938
for (uint32_t group = 0; group < count_this_time; ++group) {
79167939
uint32_t cur_index = group + cur_icd_group_count;
79177940
local_phys_dev_groups[cur_index].group_props = tmp_group_props[group];

loader/loader_environment.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ VkResult loader_add_environment_layers(struct loader_instance *inst, const char
520520
}
521521

522522
// We found a layer we're interested in, but has it been disabled...
523-
bool adding = true;
523+
bool adding;
524524
bool is_implicit = (0 == (source_prop->type_flags & VK_LAYER_TYPE_FLAG_EXPLICIT_LAYER));
525525
bool disabled_by_type =
526526
(is_implicit) ? (filters->disable_filter.disable_all_implicit) : (filters->disable_filter.disable_all_explicit);
@@ -530,11 +530,10 @@ VkResult loader_add_environment_layers(struct loader_instance *inst, const char
530530
loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0,
531531
"Layer \"%s\" ignored because it has been disabled by env var \'%s\'", source_prop->info.layerName,
532532
VK_LAYERS_DISABLE_ENV_VAR);
533-
adding = false;
534533
}
535534

536-
// If we are supposed to filter through all layers, we need to compare the layer name against the filter.
537-
// This can override the disable above, so we want to do it second.
535+
// Whether a layer is force-enabled only depends on the check below - the disable check above exists purely to log
536+
// why a layer was skipped, since a layer is only ever added by this function via VK_LOADER_LAYERS_ENABLE.
538537
// Also make sure the layer isn't already in the output_list, skip adding it if it is.
539538
if (check_name_matches_filter_environment_var(source_prop->info.layerName, &filters->enable_filter) &&
540539
!loader_find_layer_name_in_list(source_prop->info.layerName, target_list)) {

loader/loader_json.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,14 @@ static VkResult loader_read_entire_file(const struct loader_instance *inst, cons
133133
goto out;
134134
}
135135
*out_len = stats.st_size + 1;
136+
// out_buff was allocated with stats.st_size + 1 bytes above, and the fread check already verified the file's
137+
// contents did not change size out from under us; this is the last valid byte.
138+
// NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
136139
(*out_buff)[stats.st_size] = '\0';
137140

138141
out:
139142
if (NULL != file) {
143+
// NOLINTNEXTLINE(cert-err33-c) - file was only opened for reading, nothing to flush; res already reflects any read failure
140144
fclose(file);
141145
}
142146
return res;

0 commit comments

Comments
 (0)