From 524b193e9e95f96c24290f37db590b330c077eee Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 17 Mar 2026 16:19:24 +0000 Subject: [PATCH 1/4] Integrate pg-tidy clang-tidy plugin into the PostgreSQL meson build Add pg-tidy, a clang-tidy plugin with PostgreSQL-specific checks, to the meson build system as an optional component (off by default). The pg_tidy option requires LLVM/clang development libraries and clang-tidy to be available. When enabled, it builds the pg_tidy shared module and runs clang-tidy with only pg-specific checks on all source files as part of the default build. The LLVM detection is extended so that pg_tidy=enabled triggers LLVM detection even when llvm=disabled (for JIT). A new llvm_jit_enabled variable gates the JIT module separately from the LLVM dependency. --- meson.build | 45 ++++++- meson_options.txt | 3 + src/backend/jit/llvm/meson.build | 2 +- src/meson.build | 2 + src/tools/pg-tidy/include/pg_tidy.h | 12 ++ src/tools/pg-tidy/meson.build | 69 ++++++++++ src/tools/pg-tidy/run_pg_tidy_file.py | 35 ++++++ src/tools/pg-tidy/src/PgNoPaddingCheck.cpp | 108 ++++++++++++++++ src/tools/pg-tidy/src/PgNoPaddingCheck.h | 21 ++++ .../pg-tidy/src/PgNoPaddingInitCheck.cpp | 44 +++++++ src/tools/pg-tidy/src/PgNoPaddingInitCheck.h | 18 +++ .../pg-tidy/src/PgRequiresNoPaddingCheck.cpp | 89 +++++++++++++ .../pg-tidy/src/PgRequiresNoPaddingCheck.h | 18 +++ src/tools/pg-tidy/src/PgTidyModule.cpp | 22 ++++ src/tools/pg-tidy/test/lit.cfg.py | 0 src/tools/pg-tidy/test/lit.site.cfg.py.in | 15 +++ src/tools/pg-tidy/test/pg-no-padding-init.c | 28 +++++ src/tools/pg-tidy/test/pg-no-padding.c | 118 ++++++++++++++++++ .../pg-tidy/test/pg-requires-no-padding.c | 54 ++++++++ 19 files changed, 697 insertions(+), 6 deletions(-) create mode 100644 src/tools/pg-tidy/include/pg_tidy.h create mode 100644 src/tools/pg-tidy/meson.build create mode 100644 src/tools/pg-tidy/run_pg_tidy_file.py create mode 100644 src/tools/pg-tidy/src/PgNoPaddingCheck.cpp create mode 100644 src/tools/pg-tidy/src/PgNoPaddingCheck.h create mode 100644 src/tools/pg-tidy/src/PgNoPaddingInitCheck.cpp create mode 100644 src/tools/pg-tidy/src/PgNoPaddingInitCheck.h create mode 100644 src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.cpp create mode 100644 src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.h create mode 100644 src/tools/pg-tidy/src/PgTidyModule.cpp create mode 100644 src/tools/pg-tidy/test/lit.cfg.py create mode 100644 src/tools/pg-tidy/test/lit.site.cfg.py.in create mode 100644 src/tools/pg-tidy/test/pg-no-padding-init.c create mode 100644 src/tools/pg-tidy/test/pg-no-padding.c create mode 100644 src/tools/pg-tidy/test/pg-requires-no-padding.c diff --git a/meson.build b/meson.build index 5122706477d..b0d24e980af 100644 --- a/meson.build +++ b/meson.build @@ -926,13 +926,25 @@ endif ############################################################### llvmopt = get_option('llvm') +pg_tidy_opt = get_option('pg_tidy') llvm = not_found_dep +llvm_binpath = '' +pg_tidy_enabled = false if have_cxx - llvm = dependency('llvm', version: '>=14', method: 'config-tool', required: llvmopt) + # Detect LLVM if needed for JIT or pg_tidy + llvm_required = llvmopt.enabled() or pg_tidy_opt.enabled() + llvm_needed = not llvmopt.disabled() or not pg_tidy_opt.disabled() + if llvm_needed + llvm = dependency('llvm', version: '>=14', method: 'config-tool', + required: llvm_required) + endif if llvm.found() - cdata.set('USE_LLVM', 1) + llvm_jit_enabled = not llvmopt.disabled() + if llvm_jit_enabled + cdata.set('USE_LLVM', 1) + endif llvm_binpath = llvm.get_variable(configtool: 'bindir') @@ -941,17 +953,40 @@ if have_cxx # Some distros put LLVM and clang in different paths, so fallback to # find via PATH, too. clang = find_program(llvm_binpath / 'clang', 'clang', required: true) + else + llvm_jit_enabled = false endif else - msg = 'llvm requires a C++ compiler' - if llvmopt.auto() + llvm_jit_enabled = false + msg = 'llvm/pg_tidy requires a C++ compiler' + if llvmopt.auto() and pg_tidy_opt.auto() message(msg) - elif llvmopt.enabled() + elif llvmopt.enabled() or pg_tidy_opt.enabled() error(msg) endif endif +############################################################### +# Tool: pg-tidy (clang-tidy checks for PostgreSQL) +############################################################### + +clang_tidy_prog = disabler() +clang_cpp_dep = not_found_dep +if not pg_tidy_opt.disabled() and llvm.found() and have_cxx + clang_tidy_prog = find_program(llvm_binpath / 'clang-tidy', 'clang-tidy', + required: pg_tidy_opt) + if clang_tidy_prog.found() + clang_cpp_dep = cxx.find_library('clang-cpp', required: pg_tidy_opt) + if clang_cpp_dep.found() + pg_tidy_enabled = true + endif + endif +elif pg_tidy_opt.enabled() + error('pg_tidy requires LLVM and a C++ compiler') +endif + + ############################################################### # Library: icu diff --git a/meson_options.txt b/meson_options.txt index 6a793f3e479..f8f9b5f4835 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -121,6 +121,9 @@ option('libxslt', type: 'feature', value: 'auto', option('llvm', type: 'feature', value: 'disabled', description: 'LLVM support') +option('pg_tidy', type: 'feature', value: 'disabled', + description: 'pg-tidy clang-tidy checks for PostgreSQL') + option('lz4', type: 'feature', value: 'auto', description: 'LZ4 support') diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build index 7df8453ad6f..1f6025c4d37 100644 --- a/src/backend/jit/llvm/meson.build +++ b/src/backend/jit/llvm/meson.build @@ -1,6 +1,6 @@ # Copyright (c) 2022-2026, PostgreSQL Global Development Group -if not llvm.found() +if not llvm_jit_enabled subdir_done() endif diff --git a/src/meson.build b/src/meson.build index b57d4a5c259..47d72b0252c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,8 @@ subdir('interfaces') subdir('tools/pg_bsd_indent') +subdir('tools/pg-tidy') + ### Generate a Makefile.global that's complete enough for PGXS to work. # diff --git a/src/tools/pg-tidy/include/pg_tidy.h b/src/tools/pg-tidy/include/pg_tidy.h new file mode 100644 index 00000000000..8157794beca --- /dev/null +++ b/src/tools/pg-tidy/include/pg_tidy.h @@ -0,0 +1,12 @@ +#ifndef PG_TIDY_H +#define PG_TIDY_H + +#if defined(__clang__) || defined(__GNUC__) +#define PG_NO_PADDING __attribute__((annotate("pg_no_padding"))) +#define PG_REQUIRE_NO_PADDING __attribute__((annotate("pg_requires_no_padding"))) +#else +#define PG_NO_PADDING +#define PG_REQUIRE_NO_PADDING +#endif + +#endif diff --git a/src/tools/pg-tidy/meson.build b/src/tools/pg-tidy/meson.build new file mode 100644 index 00000000000..c307a8ec48e --- /dev/null +++ b/src/tools/pg-tidy/meson.build @@ -0,0 +1,69 @@ +# pg-tidy: clang-tidy checks for PostgreSQL +# +# This builds a clang-tidy plugin with PostgreSQL-specific checks. +# Requires LLVM/clang development libraries and clang-tidy. + +if not pg_tidy_enabled + subdir_done() +endif + +pg_tidy_lib = shared_module('pg_tidy', + 'src/PgTidyModule.cpp', + 'src/PgNoPaddingCheck.cpp', + 'src/PgNoPaddingInitCheck.cpp', + 'src/PgRequiresNoPaddingCheck.cpp', + dependencies: [llvm, clang_cpp_dep], + cpp_args: ['-fno-exceptions'], + override_options: ['cpp_std=c++17'], +) + +# Run clang-tidy with pg-specific checks on each backend source file. +# Each file gets its own custom_target with a stamp file, so ninja can +# parallelize the checks and only re-run them when sources change. +python = find_program('python3') +pg_tidy_wrapper = meson.current_source_dir() / 'run_pg_tidy_file.py' + +foreach srcfile : backend_sources + srcfilename = fs.parent(srcfile) / fs.name(srcfile) + targetname = 'pg_tidy_' + srcfilename.underscorify() + + custom_target(targetname, + input: [srcfile], + output: targetname + '.stamp', + command: [ + python, pg_tidy_wrapper, + clang_tidy_prog, + pg_tidy_lib, + meson.project_build_root(), + '@INPUT@', + '@OUTPUT@', + ], + depends: [pg_tidy_lib, generated_backend_headers_stamp], + build_by_default: true, + ) +endforeach + +# Tests require lit and FileCheck from LLVM +lit = find_program('lit', required: false) +filecheck = find_program('FileCheck', required: false) + +if lit.found() and filecheck.found() + lit_cfg_data = configuration_data() + lit_cfg_data.set('CLANG_TIDY', clang_tidy_prog.full_path()) + lit_cfg_data.set('FILECHECK', filecheck.full_path()) + lit_cfg_data.set('PG_TIDY_LIB', + meson.current_build_dir() / 'lib' + pg_tidy_lib.name() + '.so') + lit_cfg_data.set('TEST_SOURCE_DIR', meson.current_source_dir() / 'test') + lit_cfg_data.set('INCLUDE_DIR', meson.current_source_dir() / 'include') + + configure_file( + input: 'test/lit.site.cfg.py.in', + output: 'lit.site.cfg.py', + configuration: lit_cfg_data, + ) + + test('pg-tidy-lit-tests', lit, + args: [meson.current_build_dir(), '-v'], + depends: [pg_tidy_lib], + ) +endif diff --git a/src/tools/pg-tidy/run_pg_tidy_file.py b/src/tools/pg-tidy/run_pg_tidy_file.py new file mode 100644 index 00000000000..c85334bcc85 --- /dev/null +++ b/src/tools/pg-tidy/run_pg_tidy_file.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Run clang-tidy with pg-tidy checks on a single source file.""" + +import subprocess +import sys + + +def main(): + clang_tidy = sys.argv[1] + pg_tidy_lib = sys.argv[2] + build_dir = sys.argv[3] + source_file = sys.argv[4] + stamp_file = sys.argv[5] + + result = subprocess.run([ + clang_tidy, + '--load=' + pg_tidy_lib, + '-checks=-*,pg-*', + '-warnings-as-errors=pg-*', + '-p', build_dir, + source_file, + ], capture_output=True, text=True) + + if result.returncode != 0: + # Print clang-tidy output so the user sees the actual diagnostics + if result.stdout: + print(result.stdout, file=sys.stderr, end='') + sys.exit(1) + + with open(stamp_file, 'w'): + pass + + +if __name__ == '__main__': + main() diff --git a/src/tools/pg-tidy/src/PgNoPaddingCheck.cpp b/src/tools/pg-tidy/src/PgNoPaddingCheck.cpp new file mode 100644 index 00000000000..da02607597f --- /dev/null +++ b/src/tools/pg-tidy/src/PgNoPaddingCheck.cpp @@ -0,0 +1,108 @@ +#include "PgNoPaddingCheck.h" +#include +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::pg { + +static bool recordHasPadding(const RecordDecl *RD, const ASTContext &Ctx) { + if (RD->isUnion() || RD->isInvalidDecl() || !RD->isCompleteDefinition()) + return false; + + const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(RD); + unsigned FieldIdx = 0; + uint64_t PrevEnd = 0; + + for (const auto *Field : RD->fields()) { + uint64_t FieldOffset = Layout.getFieldOffset(FieldIdx); + uint64_t FieldSize = Ctx.getTypeSize(Field->getType()); + + if (FieldOffset > PrevEnd) + return true; + + QualType FieldType = Field->getType().getCanonicalType(); + if (const auto *FieldRD = FieldType->getAsRecordDecl()) { + if (recordHasPadding(FieldRD, Ctx)) + return true; + } + + PrevEnd = FieldOffset + FieldSize; + ++FieldIdx; + } + + uint64_t RecordSize = Ctx.getTypeSize( + RD->getTypeForDecl()->getCanonicalTypeInternal()); + return PrevEnd < RecordSize; +} + +static bool hasNoPaddingAnnotation(const RecordDecl *RD) { + for (const auto *Attr : RD->specific_attrs()) { + if (Attr->getAnnotation() == "pg_no_padding") + return true; + } + return false; +} + +void PgNoPaddingCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + recordDecl(isDefinition(), hasAttr(attr::Annotate)).bind("record"), + this); +} + +void PgNoPaddingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *RD = Result.Nodes.getNodeAs("record"); + if (!RD || !hasNoPaddingAnnotation(RD)) + return; + + const ASTContext &Ctx = *Result.Context; + checkRecordForPadding(RD, Ctx); +} + +void PgNoPaddingCheck::checkRecordForPadding(const RecordDecl *RD, + const ASTContext &Ctx) { + if (RD->isUnion() || RD->isInvalidDecl() || !RD->isCompleteDefinition()) + return; + + const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(RD); + unsigned FieldIdx = 0; + uint64_t PrevEnd = 0; + + for (const auto *Field : RD->fields()) { + uint64_t FieldOffset = Layout.getFieldOffset(FieldIdx); + uint64_t FieldSize = Ctx.getTypeSize(Field->getType()); + + if (FieldOffset > PrevEnd) { + uint64_t PadBytes = (FieldOffset - PrevEnd) / Ctx.getCharWidth(); + diag(Field->getLocation(), + "%0 bytes of padding before field %1") + << static_cast(PadBytes) << Field; + } + + QualType FieldType = Field->getType().getCanonicalType(); + if (const auto *FieldRD = FieldType->getAsRecordDecl()) { + if (FieldRD->isCompleteDefinition() && !FieldRD->isUnion()) { + if (recordHasPadding(FieldRD, Ctx)) { + diag(Field->getLocation(), + "field %0 of type %1 contains internal padding") + << Field << FieldType; + } + } + } + + PrevEnd = FieldOffset + FieldSize; + ++FieldIdx; + } + + uint64_t RecordSize = Ctx.getTypeSize(RD->getTypeForDecl()->getCanonicalTypeInternal()); + if (PrevEnd < RecordSize) { + uint64_t PadBytes = (RecordSize - PrevEnd) / Ctx.getCharWidth(); + diag(RD->getBraceRange().getEnd(), + "%0 bytes of trailing padding in struct %1") + << static_cast(PadBytes) << RD; + } +} + +} // namespace clang::tidy::pg diff --git a/src/tools/pg-tidy/src/PgNoPaddingCheck.h b/src/tools/pg-tidy/src/PgNoPaddingCheck.h new file mode 100644 index 00000000000..2e83c9b3fb2 --- /dev/null +++ b/src/tools/pg-tidy/src/PgNoPaddingCheck.h @@ -0,0 +1,21 @@ +#ifndef PG_NO_PADDING_CHECK_H +#define PG_NO_PADDING_CHECK_H + +#include + +namespace clang::tidy::pg { + +class PgNoPaddingCheck : public ClangTidyCheck { +public: + PgNoPaddingCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void checkRecordForPadding(const RecordDecl *RD, const ASTContext &Ctx); +}; + +} // namespace clang::tidy::pg + +#endif diff --git a/src/tools/pg-tidy/src/PgNoPaddingInitCheck.cpp b/src/tools/pg-tidy/src/PgNoPaddingInitCheck.cpp new file mode 100644 index 00000000000..dcb8793b7e1 --- /dev/null +++ b/src/tools/pg-tidy/src/PgNoPaddingInitCheck.cpp @@ -0,0 +1,44 @@ +#include "PgNoPaddingInitCheck.h" +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::pg { + +static bool hasAnnotation(const Decl *D, StringRef Anno) { + for (const auto *Attr : D->specific_attrs()) { + if (Attr->getAnnotation() == Anno) + return true; + } + return false; +} + +void PgNoPaddingInitCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + varDecl(hasLocalStorage(), unless(parmVarDecl())).bind("var"), this); +} + +void PgNoPaddingInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("var"); + if (!VD) + return; + + QualType T = VD->getType().getCanonicalType(); + const RecordDecl *RD = T->getAsRecordDecl(); + if (!RD) + return; + + if (!hasAnnotation(RD, "pg_no_padding")) + return; + + if (VD->hasInit()) + return; + + diag(VD->getLocation(), + "variable %0 of type %1 (marked pg_no_padding) is not zero-initialized") + << VD << T; +} + +} // namespace clang::tidy::pg diff --git a/src/tools/pg-tidy/src/PgNoPaddingInitCheck.h b/src/tools/pg-tidy/src/PgNoPaddingInitCheck.h new file mode 100644 index 00000000000..b74be98c95b --- /dev/null +++ b/src/tools/pg-tidy/src/PgNoPaddingInitCheck.h @@ -0,0 +1,18 @@ +#ifndef PG_NO_PADDING_INIT_CHECK_H +#define PG_NO_PADDING_INIT_CHECK_H + +#include + +namespace clang::tidy::pg { + +class PgNoPaddingInitCheck : public ClangTidyCheck { +public: + PgNoPaddingInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::pg + +#endif diff --git a/src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.cpp b/src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.cpp new file mode 100644 index 00000000000..b5315400fb7 --- /dev/null +++ b/src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.cpp @@ -0,0 +1,89 @@ +#include "PgRequiresNoPaddingCheck.h" +#include +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::pg { + +static bool hasAnnotation(const Decl *D, StringRef Anno) { + for (const auto *Attr : D->specific_attrs()) { + if (Attr->getAnnotation() == Anno) + return true; + } + return false; +} + +static const QualType resolveArgType(const Expr *Arg) { + Arg = Arg->IgnoreParenImpCasts(); + + if (const auto *UO = dyn_cast(Arg)) { + if (UO->getOpcode() == UO_AddrOf) { + return UO->getSubExpr()->IgnoreParenImpCasts()->getType().getCanonicalType(); + } + } + + QualType T = Arg->getType().getCanonicalType(); + if (T->isPointerType()) + return T->getPointeeType().getCanonicalType(); + if (T->isArrayType()) + return T->getAsArrayTypeUnsafe()->getElementType().getCanonicalType(); + + return QualType(); +} + +void PgRequiresNoPaddingCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("call"), this); +} + +void PgRequiresNoPaddingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + if (!Call) + return; + + const FunctionDecl *FD = Call->getDirectCallee(); + if (!FD) + return; + + for (unsigned i = 0; i < FD->getNumParams() && i < Call->getNumArgs(); ++i) { + const ParmVarDecl *Param = FD->getParamDecl(i); + if (!hasAnnotation(Param, "pg_requires_no_padding")) + continue; + + const Expr *Arg = Call->getArg(i); + QualType ArgType = resolveArgType(Arg); + + if (ArgType.isNull()) { + diag(Arg->getExprLoc(), + "cannot verify no-padding requirement on void* argument"); + continue; + } + + if (ArgType->isVoidType()) { + diag(Arg->getExprLoc(), + "cannot verify no-padding requirement on void* argument"); + continue; + } + + if (ArgType->isBuiltinType()) + continue; + + const RecordDecl *RD = ArgType->getAsRecordDecl(); + if (!RD) { + diag(Arg->getExprLoc(), + "cannot verify no-padding requirement on void* argument"); + continue; + } + + if (!hasAnnotation(RD, "pg_no_padding")) { + diag(Arg->getExprLoc(), + "argument type %0 passed to no-padding parameter lacks " + "pg_no_padding annotation") + << ArgType; + } + } +} + +} // namespace clang::tidy::pg diff --git a/src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.h b/src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.h new file mode 100644 index 00000000000..494af66385d --- /dev/null +++ b/src/tools/pg-tidy/src/PgRequiresNoPaddingCheck.h @@ -0,0 +1,18 @@ +#ifndef PG_REQUIRES_NO_PADDING_CHECK_H +#define PG_REQUIRES_NO_PADDING_CHECK_H + +#include + +namespace clang::tidy::pg { + +class PgRequiresNoPaddingCheck : public ClangTidyCheck { +public: + PgRequiresNoPaddingCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::pg + +#endif diff --git a/src/tools/pg-tidy/src/PgTidyModule.cpp b/src/tools/pg-tidy/src/PgTidyModule.cpp new file mode 100644 index 00000000000..3ab486dd087 --- /dev/null +++ b/src/tools/pg-tidy/src/PgTidyModule.cpp @@ -0,0 +1,22 @@ +#include "PgNoPaddingCheck.h" +#include "PgNoPaddingInitCheck.h" +#include "PgRequiresNoPaddingCheck.h" +#include +#include + +namespace clang::tidy::pg { + +class PgTidyModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("pg-no-padding"); + CheckFactories.registerCheck("pg-no-padding-init"); + CheckFactories.registerCheck( + "pg-requires-no-padding"); + } +}; + +static ClangTidyModuleRegistry::Add + X("pg-module", "Checks specific to PostgreSQL."); + +} // namespace clang::tidy::pg diff --git a/src/tools/pg-tidy/test/lit.cfg.py b/src/tools/pg-tidy/test/lit.cfg.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/tools/pg-tidy/test/lit.site.cfg.py.in b/src/tools/pg-tidy/test/lit.site.cfg.py.in new file mode 100644 index 00000000000..f208966b486 --- /dev/null +++ b/src/tools/pg-tidy/test/lit.site.cfg.py.in @@ -0,0 +1,15 @@ +import os +import lit.formats + +config.name = "pg-tidy" +config.test_format = lit.formats.ShTest(True) +config.suffixes = ['.c'] + +config.test_source_root = '@TEST_SOURCE_DIR@' + +config.substitutions.append(('%clang_tidy', '@CLANG_TIDY@')) +config.substitutions.append(('%pg_tidy_lib', '@PG_TIDY_LIB@')) +config.substitutions.append(('%FileCheck', '@FILECHECK@')) +config.substitutions.append(('%include_dir', '@INCLUDE_DIR@')) + +lit_config.load_config(config, os.path.join(config.test_source_root, 'lit.cfg.py')) diff --git a/src/tools/pg-tidy/test/pg-no-padding-init.c b/src/tools/pg-tidy/test/pg-no-padding-init.c new file mode 100644 index 00000000000..28e7dc2e7df --- /dev/null +++ b/src/tools/pg-tidy/test/pg-no-padding-init.c @@ -0,0 +1,28 @@ +// RUN: %clang_tidy --load=%pg_tidy_lib -checks=-*,pg-no-padding-init %s \ +// RUN: -- -I%include_dir 2>&1 | %FileCheck %s +// CHECK: 1 warning generated. + +#include "pg_tidy.h" + +struct PG_NO_PADDING AnnotatedStruct { + int a; + int b; +}; + +struct PlainStruct { + int a; + int b; +}; + +void test(void) { + // CHECK: :[[@LINE+1]]:26: warning: variable 'bad' of type 'struct AnnotatedStruct' (marked pg_no_padding) is not zero-initialized [pg-no-padding-init] + struct AnnotatedStruct bad; + bad.a = 1; + bad.b = 2; + + struct AnnotatedStruct good = {0}; + good.a = 1; + + struct PlainStruct no_warning; + no_warning.a = 1; +} diff --git a/src/tools/pg-tidy/test/pg-no-padding.c b/src/tools/pg-tidy/test/pg-no-padding.c new file mode 100644 index 00000000000..eca3a84347a --- /dev/null +++ b/src/tools/pg-tidy/test/pg-no-padding.c @@ -0,0 +1,118 @@ +// RUN: %clang_tidy --load=%pg_tidy_lib -checks=-*,pg-no-padding %s \ +// RUN: -- -I%include_dir 2>&1 | %FileCheck %s +// CHECK: 10 warnings generated. + +#include "pg_tidy.h" + +// --- Non-annotated struct: no warnings --- +struct NotAnnotated { + char a; + int b; +}; + +// --- Annotated struct with no padding: no warnings --- +struct PG_NO_PADDING NoPadding { + int a; + int b; +}; + +// --- Annotated struct with inter-field padding --- +// CHECK: :[[@LINE+3]]:7: warning: 3 bytes of padding before field 'b' [pg-no-padding] +struct PG_NO_PADDING InterFieldPad { + char a; + int b; +}; + +// --- Annotated struct with trailing padding --- +// CHECK: :[[@LINE+4]]:1: warning: 3 bytes of trailing padding in struct 'TrailingPad' [pg-no-padding] +struct PG_NO_PADDING TrailingPad { + int a; + char b; +}; + +// --- Annotated struct with both inter-field and trailing padding --- +// CHECK: :[[@LINE+4]]:7: warning: 3 bytes of padding before field 'b' [pg-no-padding] +// CHECK: :[[@LINE+5]]:1: warning: 3 bytes of trailing padding in struct 'BothPad' [pg-no-padding] +struct PG_NO_PADDING BothPad { + char a; + int b; + char c; +}; + +// --- Nested struct with internal padding --- +struct InnerPadded { + char x; + int y; +}; + +// CHECK: :[[@LINE+3]]:22: warning: field 'inner' of type 'struct InnerPadded' contains internal padding [pg-no-padding] +struct PG_NO_PADDING HasPaddedInner { + int a; + struct InnerPadded inner; +}; + +// --- Nested struct without padding: no extra warnings --- +struct InnerClean { + int x; + int y; +}; + +struct PG_NO_PADDING HasCleanInner { + int a; + struct InnerClean inner; +}; + +// --- Deeply nested padding --- +struct DeepInner { + char a; + long b; +}; + +struct MidLevel { + struct DeepInner d; +}; + +// CHECK: :[[@LINE+4]]:19: warning: 4 bytes of padding before field 'mid' [pg-no-padding] +// CHECK: :[[@LINE+3]]:19: warning: field 'mid' of type 'struct MidLevel' contains internal padding [pg-no-padding] +struct PG_NO_PADDING DeepNest { + int a; + struct MidLevel mid; +}; + +// --- Empty annotated struct: no warnings --- +struct PG_NO_PADDING AnnotatedEmpty {}; + +// --- Single field, no padding --- +struct PG_NO_PADDING SingleField { + int x; +}; + +// --- All same-size fields, no padding --- +struct PG_NO_PADDING AllSameSize { + int a; + int b; + int c; +}; + +// --- Multiple padding sites --- +// CHECK: :[[@LINE+5]]:7: warning: 3 bytes of padding before field 'b' [pg-no-padding] +// CHECK: :[[@LINE+6]]:7: warning: 3 bytes of padding before field 'c' [pg-no-padding] +// CHECK: :[[@LINE+7]]:1: warning: 3 bytes of trailing padding in struct 'MultiPad' [pg-no-padding] +struct PG_NO_PADDING MultiPad { + char a; + int b; + char x; + int c; + char z; +}; + +// --- Annotated struct that itself is nested in a non-annotated struct: should still check --- +struct PG_NO_PADDING AnnotatedInner { + int a; + int b; +}; + +struct OuterNotAnnotated { + char x; + struct AnnotatedInner inner; +}; diff --git a/src/tools/pg-tidy/test/pg-requires-no-padding.c b/src/tools/pg-tidy/test/pg-requires-no-padding.c new file mode 100644 index 00000000000..8436bfed207 --- /dev/null +++ b/src/tools/pg-tidy/test/pg-requires-no-padding.c @@ -0,0 +1,54 @@ +// RUN: %clang_tidy --load=%pg_tidy_lib -checks=-*,pg-requires-no-padding %s \ +// RUN: -- -I%include_dir 2>&1 | %FileCheck %s +// CHECK: 3 warnings generated. + +#include "pg_tidy.h" + +typedef unsigned int uint32; + +struct PG_NO_PADDING AnnotatedStruct { + int a; + int b; +}; + +struct NotAnnotatedStruct { + int a; + int b; +}; + +extern void XLogRegisterData(const void *data PG_REQUIRE_NO_PADDING, uint32 len); + +extern void NormalFunc(const void *data, uint32 len); + +void test_functions(void) { + struct AnnotatedStruct good; + struct NotAnnotatedStruct bad; + int primitive; + void *opaque = &good; + + XLogRegisterData(&good, sizeof(good)); + + // CHECK: :[[@LINE+1]]:20: warning: argument type 'struct NotAnnotatedStruct' passed to no-padding parameter lacks pg_no_padding annotation [pg-requires-no-padding] + XLogRegisterData(&bad, sizeof(bad)); + + XLogRegisterData(&primitive, sizeof(primitive)); + + // CHECK: :[[@LINE+1]]:20: warning: cannot verify no-padding requirement on void* argument [pg-requires-no-padding] + XLogRegisterData(opaque, sizeof(good)); + + NormalFunc(&bad, sizeof(bad)); + NormalFunc(opaque, sizeof(good)); +} + +void test_pointer_variable(void) { + struct AnnotatedStruct good; + struct NotAnnotatedStruct bad; + + struct AnnotatedStruct *good_ptr = &good; + struct NotAnnotatedStruct *bad_ptr = &bad; + + XLogRegisterData(good_ptr, sizeof(*good_ptr)); + + // CHECK: :[[@LINE+1]]:20: warning: argument type 'struct NotAnnotatedStruct' passed to no-padding parameter lacks pg_no_padding annotation [pg-requires-no-padding] + XLogRegisterData(bad_ptr, sizeof(*bad_ptr)); +} -- 2.43.0