Author: Mariya Podchishchaeva Date: 2026-07-01T10:18:30+02:00 New Revision: 7e31f2cffd397ae65a91b0fb2bb7b2097e8e8a4b
URL: https://github.com/llvm/llvm-project/commit/7e31f2cffd397ae65a91b0fb2bb7b2097e8e8a4b DIFF: https://github.com/llvm/llvm-project/commit/7e31f2cffd397ae65a91b0fb2bb7b2097e8e8a4b.diff LOG: [clang][SYCL] Diagnose reference kernel parameters (#192957) Per SYCL 2020 spec: Reference types are not trivially copyable, so they may not be passed as kernel parameters. This PR adds infrastructure for kernel object visiting and implements diagnostics for reference kernel parameters. The infrastructure will be also used for other kernel parameter restrictions and functional code transformations that will be done in separate PRs. Assisted by: claude in unit test preparation --------- Co-authored-by: Tom Honermann <[email protected]> Added: clang/include/clang/AST/SubobjectVisitor.h clang/test/SemaSYCL/sycl-kernel-param-restrictions.cpp clang/unittests/AST/SubobjectVisitorTest.cpp Modified: clang/docs/ReleaseNotes.md clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Sema/SemaSYCL.cpp clang/unittests/AST/CMakeLists.txt Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index cde904ebaac1e..e4f992f6e0f08 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -1136,6 +1136,8 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the - Fixed `-nolibsycl` being silently ignored on Linux: the SYCL runtime library was unconditionally added to the link line even when the flag was passed. +- Clang now is capable of diagnosing reference kernel parameters which are not + allowed by SYCL 2020 spec. #### Improvements diff --git a/clang/include/clang/AST/SubobjectVisitor.h b/clang/include/clang/AST/SubobjectVisitor.h new file mode 100644 index 0000000000000..473446dd36b4b --- /dev/null +++ b/clang/include/clang/AST/SubobjectVisitor.h @@ -0,0 +1,128 @@ +//===---------- SubobjectVisitor.h - Subobject Visitor ----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the SubobjectVisitor interface, which recursively +// traverses subobjects within a type. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_SUBOBJECTVISITOR_H +#define LLVM_CLANG_AST_SUBOBJECTVISITOR_H + +#include "clang/AST/ASTContext.h" + +namespace clang { + +/// Given a type, subobject visitors visit all subobjects of the type in depth +/// first order. Both pre-order and post-order visitation are performed so that +/// derived classes can maintain an access path to the visited elements. +/// Subobjects include all base classes and non-static data members, including +/// those that are not subobjects according to the C++ standard like data +/// members with a reference type. Virtual base classes are visited each time +/// they appear in a class hierarchy despite there being only one actual +/// subobject present in an object of a most derived type. Array elements are +/// not individually visited; only their containing array is. +template <template <typename> class Ptr, typename Derived> +class SubobjectVisitorBase { + ASTContext &Ctx; + template <typename Class> using ptr_t = typename Ptr<Class>::type; + +public: + SubobjectVisitorBase(ASTContext &Ctx) : Ctx(Ctx) {} + + /// Return a reference to the derived class. + Derived &getDerived() { return *static_cast<Derived *>(this); } + + void visit(QualType QT) { + assert(!QT->isDependentType()); + QT = QT.getDesugaredType(Ctx); + + // FunctionType, FunctionProtoType, and FunctionNoProtoType are never + // the type of a subobject. + + // ObjCObjectType and ObjCInterfaceType are never the type of a + // subobject due to the Objective-C object allocation model. + + // PointerType, BlockPointerType, ReferenceType, LValueReferenceType, + // RValueReferenceType, MemberPointerType, and ObjCObjectPointerType + // may all be the type of a subobject, but they do not contain other + // subobjects; the pointee type should not be visited. + + // ComplexType, VectorType, ExtVectorType, MatrixType, PipeType, + // EnumType, and OverflowBehaviorType all have an element or + // underlying type that could be visited. However, in each of these + // cases, the lower type is constrained to a fundamental type and + // therefore doesn't contain any fields or base classes. + + if (auto *ResAtomicType = QT->getAs<AtomicType>()) { + getDerived().visit(ResAtomicType->getValueType()); + return; + } + + // If the type is an array, visit its element type. Separate traversal of + // arrays is not needed because the array will be encountered as a + // FieldDecl. + if (QT->isArrayType()) { + QualType ElTy = Ctx.getAsArrayType(QT)->getElementType(); + getDerived().visit(ElTy); + return; + } + + if (ptr_t<RecordDecl> RD = QT->getAsRecordDecl()) { + getDerived().traverseRecord(RD); + return; + } + } + + void traverseRecord(ptr_t<RecordDecl> RD) { + if (ptr_t<CXXRecordDecl> CRD = dyn_cast<CXXRecordDecl>(RD)) { + for (auto &BS : CRD->bases()) { + if (getDerived().visitBaseSpecifierPre(&BS)) + getDerived().visit(BS.getType()); + getDerived().visitBaseSpecifierPost(&BS); + } + } + for (ptr_t<FieldDecl> FD : RD->fields()) { + if (getDerived().visitFieldDeclPre(FD)) + getDerived().visit(FD->getType()); + getDerived().visitFieldDeclPost(FD); + } + } + + // Default base class specifier pre-order visitor. + bool visitBaseSpecifierPre(ptr_t<CXXBaseSpecifier> BS) { return true; } + + // Default base class specifier post-order visitor. + void visitBaseSpecifierPost(ptr_t<CXXBaseSpecifier> BS) {} + + // Default field pre-order visitor. + bool visitFieldDeclPre(ptr_t<FieldDecl> FD) { return true; } + + // Default field post-order visitor. + void visitFieldDeclPost(ptr_t<FieldDecl> FD) {} +}; + +template <typename Derived> +class SubobjectVisitor + : public SubobjectVisitorBase<std::add_pointer, Derived> { +public: + SubobjectVisitor(ASTContext &Ctx) + : SubobjectVisitorBase<std::add_pointer, Derived>(Ctx) {} +}; + +template <typename Derived> +class ConstSubobjectVisitor + : public SubobjectVisitorBase<llvm::make_const_ptr, Derived> { +public: + ConstSubobjectVisitor(ASTContext &Ctx) + : SubobjectVisitorBase<llvm::make_const_ptr, Derived>(Ctx) {} +}; + +} // end namespace clang + +#endif // LLVM_CLANG_AST_SUBOBJECTVISITOR diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 7e4a9338d5b1f..86b765fdf1fab 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11847,6 +11847,12 @@ def err_record_with_pointers_kernel_param : Error< "%select{struct|union}0 kernel parameters may not contain pointers">; def note_within_field_of_type : Note< "within field of type %0 declared here">; +def note_within_capture : Note< + "within capture %0 of lambda expression here">; +def note_within_base_of_type : Note< + "within base class of type %0 declared here">; +def note_within_param_of_type : Note< + "within parameter %0 of type %1 declared here">; def note_illegal_field_declared_here : Note< "field of illegal %select{type|pointer type}0 %1 declared here">; def err_opencl_type_struct_or_union_field : Error< diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index 112a6e4416df2..b942f19761f40 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -13,6 +13,7 @@ #include "clang/AST/Mangle.h" #include "clang/AST/SYCLKernelInfo.h" #include "clang/AST/StmtSYCL.h" +#include "clang/AST/SubobjectVisitor.h" #include "clang/AST/TypeOrdering.h" #include "clang/Basic/Diagnostic.h" #include "clang/Sema/Attr.h" @@ -665,6 +666,109 @@ OutlinedFunctionDecl *BuildSYCLKernelEntryPointOutline(Sema &SemaRef, return OFD; } +class KernelParamsChecker : public ConstSubobjectVisitor<KernelParamsChecker> { + SemaSYCL &SemaSYCLRef; + bool IsValid = true; + using ObjectAccess = + llvm::PointerUnion<const ParmVarDecl *, const CXXBaseSpecifier *, + const FieldDecl *>; + SmallVector<ObjectAccess, 4> ObjectAccessPath; + + void emitObjectAccessPathNotes() { + for (auto Parent : llvm::reverse(ObjectAccessPath)) { + if (auto *FD = Parent.dyn_cast<const FieldDecl *>()) { + const CXXRecordDecl *ParentRD = cast<CXXRecordDecl>(FD->getParent()); + if (ParentRD->isLambda()) { + SemaSYCLRef.Diag(ParentRD->getLocation(), diag::note_within_capture) + << ParentRD->getCapture(FD->getFieldIndex())->getCapturedVar(); + } else { + SemaSYCLRef.Diag(ParentRD->getLocation(), + diag::note_within_field_of_type) + << ParentRD; + } + } else if (auto *BS = Parent.dyn_cast<const CXXBaseSpecifier *>()) { + CXXRecordDecl *RD = BS->getType()->getAsCXXRecordDecl(); + assert(RD); + SemaSYCLRef.Diag(BS->getBeginLoc(), diag::note_within_base_of_type) + << RD; + } else { + auto *Param = cast<const ParmVarDecl *>(Parent); + SemaSYCLRef.Diag(Param->getBeginLoc(), diag::note_within_param_of_type) + << Param << Param->getType(); + } + } + } + +public: + KernelParamsChecker(SemaSYCL &SR, SourceLocation Loc) + : ConstSubobjectVisitor<KernelParamsChecker>(SR.getASTContext()), + SemaSYCLRef(SR) {} + + void checkParameter(const ParmVarDecl *PVD) { + ObjectAccessPath.push_back(PVD); + // Check the immediate type of the parameter. + if (checkType(PVD->getType())) { + // If type checking wasn't short circuited, visit subobjects to check + // them. + visit(PVD->getType()); + } + ObjectAccessPath.pop_back(); + assert(ObjectAccessPath.empty()); + } + + bool visitBaseSpecifierPre(const CXXBaseSpecifier *BS) { + ObjectAccessPath.push_back(BS); + return checkType(BS->getType()); + } + + bool visitFieldDeclPre(const FieldDecl *FD) { + ObjectAccessPath.push_back(FD); + return checkType(FD->getType()); + } + + // Returns true if subobjects should be visited and false otherwise. + bool checkType(QualType Ty) { + if (Ty->isReferenceType()) { + auto DirectParent = ObjectAccessPath.back(); + // Reference cannot be a base, so just assume we came via a FieldDecl. + if (isa<const ParmVarDecl *>(DirectParent)) { + // If reference is a kernel parameter, there is nothing to do. We allow + // references in direct kernel parameters for better performance of the + // host code and we eliminate them when building actual kernel. + return true; + } + + auto *DirectFieldParent = cast<const FieldDecl *>(DirectParent); + SemaSYCLRef.Diag(DirectFieldParent->getLocation(), + diag::err_bad_kernel_param_type) + << DirectFieldParent->getType(); + emitObjectAccessPathNotes(); + + // Don't visit the type of the reference since any further invalid + // kernel parameter types contained within the referenced type + // might not be relevant once the programmer addresses the + // invalid use of a reference. + IsValid = false; + return false; + } + return true; + } + + void visitFieldDeclPost(const FieldDecl *FD) { ObjectAccessPath.pop_back(); } + void visitBaseSpecifierPost(const CXXBaseSpecifier *BS) { + ObjectAccessPath.pop_back(); + } + + bool isInvalid() { return !IsValid; } +}; + +bool verifyKernelParams(FunctionDecl *FD, SemaSYCL &SemaSYCLRef) { + KernelParamsChecker KAC(SemaSYCLRef, FD->getLocation()); + for (auto Param : FD->parameters()) + KAC.checkParameter(Param); + return KAC.isInvalid(); +} + } // unnamed namespace StmtResult SemaSYCL::BuildSYCLKernelCallStmt(FunctionDecl *FD, @@ -690,6 +794,8 @@ StmtResult SemaSYCL::BuildSYCLKernelCallStmt(FunctionDecl *FD, getASTContext().getSYCLKernelInfo(SKEPAttr->getKernelName()); assert(declaresSameEntity(SKI.getKernelEntryPointDecl(), FD) && "SYCL kernel name conflict"); + if (verifyKernelParams(FD, *this)) + return StmtError(); // Build the outline of the synthesized device entry point function. OutlinedFunctionDecl *OFD = diff --git a/clang/test/SemaSYCL/sycl-kernel-param-restrictions.cpp b/clang/test/SemaSYCL/sycl-kernel-param-restrictions.cpp new file mode 100644 index 0000000000000..66aa00da18a04 --- /dev/null +++ b/clang/test/SemaSYCL/sycl-kernel-param-restrictions.cpp @@ -0,0 +1,215 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fsyntax-only -Wno-vla-cxx-extension -fsycl-is-host -verify %s +// RUN: %clang_cc1 -triple spirv64 -std=c++17 -fsyntax-only -Wno-vla-cxx-extension -fsycl-is-device -verify %s + +// A unique kernel name type is required for each declared kernel entry point. +template<int, int = 0> struct KN; + +// A generic kernel launch function. +template<typename KNT, typename... Ts> +void sycl_kernel_launch(const char *, Ts...) {} + +// Check that reference captures of kernel that defined as lambda are diagnosed. +namespace badref1 { +// Kernel entry point template definition. +template<typename KNT, typename T> +[[clang::sycl_kernel_entry_point(KNT)]] +void kernel_single_task(T t) {} // expected-note-re 2{{within parameter 't' of type '(lambda at {{.*}})' declared here}} + +void test() { + int p = 0; + double q = 0; + float s = 0; + // expected-note-re@+1 {{in instantiation of function template specialization 'badref1::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<1>>( + [ // expected-note{{within capture 'p' of lambda expression here}} + // expected-note@-1{{within capture 's' of lambda expression here}} + // expected-error@+1 {{'int &' cannot be used as the type of a kernel parameter}} + &p, q, + // expected-error@+1 {{'float &' cannot be used as the type of a kernel parameter}} + &s] { + (void)q; + (void)p; + (void)s; + }); +} +} // namespace badref1 + +// Check reference kernel parameters witin structs or lambdas; +namespace badref2 { +// Kernel entry point template definition. +template<typename KNT, typename T> +[[clang::sycl_kernel_entry_point(KNT)]] +void kernel_single_task(T t) {} // expected-note-re 3{{within parameter 't' of type '(lambda at {{.*}})' declared here}} + +struct S { // expected-note 2{{within field of type 'S' declared here}} + int a; + int &b; //expected-error 2{{'int &' cannot be used as the type of a kernel parameter}} +}; + +void test() { + int p = 0; + auto L = [&]() { (void)p;}; // expected-error {{'int &' cannot be used as the type of a kernel parameter}} + // expected-note@-1 {{within capture 'p' of lambda expression here}} + S Str {p, p}; + // expected-note-re@+1 {{in instantiation of function template specialization 'badref2::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<2>>( + [=] { // expected-note {{within capture 'L' of lambda expression here}} + // expected-note@-1 {{within capture 'Str' of lambda expression here}} + (void)L; + (void)Str; + }); + + // expected-note-re@+1 {{in instantiation of function template specialization 'badref2::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<3>>( + [=] { // // expected-note {{within capture 'Str' of lambda expression here}} + (void)Str; + }); + +} +} // namespace badref2 + +// Check references within array kernel parameters. +namespace badref3 { +// Kernel entry point template definition. +template<typename KNT, typename T> +[[clang::sycl_kernel_entry_point(KNT)]] +void kernel_single_task(T t) {} // expected-note-re 3{{within parameter 't' of type '(lambda at {{.*}})' declared here}} + +struct S { // expected-note {{within field of type 'S' declared here}} + int a; + int &b; //expected-error {{'int &' cannot be used as the type of a kernel parameter}} +}; + +void fooarr(int (&arr)[5]) { +} + +void test(int AS) { + int p = 0; + S Str {p, p}; + S arr[2] = {Str, Str}; + // expected-note-re@+1 {{in instantiation of function template specialization 'badref3::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<4>>( + [=] { // expected-note {{within capture 'arr' of lambda expression here}} + (void)arr; + }); + int arr1[AS]; + // expected-note-re@+1 {{in instantiation of function template specialization 'badref3::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<5>>( + [&] { // expected-note {{within capture 'arr1' of lambda expression here}} + (void)arr1; // expected-error {{'int (&)[AS]' cannot be used as the type of a kernel parameter}} + }); + int arrayints[5] = {0}; + // expected-note-re@+1 {{in instantiation of function template specialization 'badref3::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<7>>( + [&] { // expected-note {{within capture 'arrayints' of lambda expression here}} + fooarr(arrayints); // expected-error {{'int (&)[5]' cannot be used as the type of a kernel parameter}} + }); +} +} // namespace badref3 + +// Check callable objects containing references. +namespace badref4 { +// Kernel entry point template definition. +template<typename KNT, typename T> +[[clang::sycl_kernel_entry_point(KNT)]] +void kernel_single_task(T t) {} // expected-note {{within parameter 't' of type 'badref4::Callable<int &>' declared here}} + // expected-note@-1 {{within parameter 't' of type 'badref4::Derived1' declared here}} + // expected-note@-2 {{within parameter 't' of type 'badref4::Derived2' declared here}} + +template <typename T> class Callable { // expected-note 2{{within field of type 'Callable<int &>' declared here}} + T data; // expected-error 2{{'int &' cannot be used as the type of a kernel parameter}} +public: + Callable(T d) : data(d) {} + void operator()() { + } +}; + +class Derived1 : Callable<int> { // expected-note {{within field of type 'Derived1' declared here}} + int &a; // expected-error {{'int &' cannot be used as the type of a kernel parameter}} +public: + Derived1(int d, int &b) : Callable<int>(d), a(b) {} +}; + +class Derived2 : Callable<int&> { // expected-note {{within base class of type 'Callable<int &>' declared here}} + int a; +public: + Derived2(int d, int &b) : Callable<int&>(b), a(d) {} +}; + +void test(int AS) { + int p = 0; + kernel_single_task<class KN<8>>(Callable<int&>{p}); + // expected-note-re@-1 {{in instantiation of function template specialization 'badref4::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<9>>(Callable<int>{p}); + kernel_single_task<class KN<10>>(Derived1{p, p}); + // expected-note-re@-1 {{in instantiation of function template specialization 'badref4::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + kernel_single_task<class KN<11>>(Derived2{p, p}); + // expected-note-re@-1 {{in instantiation of function template specialization 'badref4::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} +} + +} // namespace badref4 + +// Test virtual bases. +// FIXME: explicitly diagnose virtual bases within kernel parameters. +namespace badref5 { +// Kernel entry point template definition. +template<typename KNT, typename T> +[[clang::sycl_kernel_entry_point(KNT)]] +void kernel_single_task(T t) {} //expected-note {{within parameter 't' of type 'badref5::Derived' declared here}} + +class Base { // expected-note {{within field of type 'Base' declared here}}} + int &data; // expected-error {{'int &' cannot be used as the type of a kernel parameter}} +public: + Base(int &a) : data(a) {} +}; + +class Derived : virtual Base { // expected-note {{within base class of type 'Base' declared here}} +public: + Derived(int &a) : Base(a) {} + +}; + +void test() { + int p = 0; + kernel_single_task<class KN<12>>(Derived{p}); + // expected-note-re@-1 {{in instantiation of function template specialization 'badref5::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} +} +} // namespace badref5 + +// Check that a struct that hold a reference and captured by reference by lambda +// kernel object is diagnosed correctly. +namespace badref6 { +// Kernel entry point template definition. +template<typename KNT, typename T> +[[clang::sycl_kernel_entry_point(KNT)]] +void kernel_single_task(T t) {} //expected-note-re {{within parameter 't' of type '(lambda at {{.*}})' declared here}} + +void test() { + int a; + struct S { + int &dm; + }; + S s {a}; + kernel_single_task<class KN<13>>([&] { (void)s; }); + // expected-error@-1 {{'S &' cannot be used as the type of a kernel parameter}} + // expected-note-re@-2 {{in instantiation of function template specialization 'badref6::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + // expected-note@-3 {{within capture 's' of lambda expression here}} +} +} // namespace badref6 + +// Check that init capture is diagnosed correctly. +namespace badref7 { +// Kernel entry point template definition. +template<typename KNT, typename T> +[[clang::sycl_kernel_entry_point(KNT)]] +void kernel_single_task(T t) {} //expected-note-re {{within parameter 't' of type '(lambda at {{.*}})' declared here}} + +void test() { + int p = 0; + kernel_single_task<class KN<14>>([&x=p] { (void)x; }); + // expected-error@-1 {{'int &' cannot be used as the type of a kernel parameter}} + // expected-note-re@-2 {{in instantiation of function template specialization 'badref7::kernel_single_task<KN<{{[0-9]+}}>, {{.*}}>' requested here}} + // expected-note@-3 {{within capture 'x' of lambda expression here}} +} + +} // namespace badref7 diff --git a/clang/unittests/AST/CMakeLists.txt b/clang/unittests/AST/CMakeLists.txt index 3a12a4a06a33a..81010e0469685 100644 --- a/clang/unittests/AST/CMakeLists.txt +++ b/clang/unittests/AST/CMakeLists.txt @@ -34,6 +34,7 @@ add_clang_unittest(ASTTests SourceLocationTest.cpp StmtPrinterTest.cpp StructuralEquivalenceTest.cpp + SubobjectVisitorTest.cpp TemplateNameTest.cpp TypePrinterTest.cpp UnresolvedSetTest.cpp diff --git a/clang/unittests/AST/SubobjectVisitorTest.cpp b/clang/unittests/AST/SubobjectVisitorTest.cpp new file mode 100644 index 0000000000000..5df382b976420 --- /dev/null +++ b/clang/unittests/AST/SubobjectVisitorTest.cpp @@ -0,0 +1,132 @@ +//===- unittests/AST/SubobjectVisitorTest.cpp - Subobject Visitor tests --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/SubobjectVisitor.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" +#include <string> +#include <vector> + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::tooling; + +namespace { + +// Helper class to record visited subobjects +class RecordingVisitor : public SubobjectVisitor<RecordingVisitor> { +public: + std::vector<std::string> VisitedBases; + std::vector<std::string> VisitedFields; + + RecordingVisitor(ASTContext &Ctx) : SubobjectVisitor<RecordingVisitor>(Ctx) {} + + bool visitBaseSpecifierPre(CXXBaseSpecifier *BS) { + std::string Name = BS->getType()->getAsCXXRecordDecl()->getNameAsString(); + VisitedBases.push_back(Name); + return true; + } + + bool visitFieldDeclPre(FieldDecl *FD) { + std::string Name = FD->getNameAsString(); + VisitedFields.push_back(Name); + return true; + } +}; + +// Helper function to get a CXXRecordDecl by name +const CXXRecordDecl *getCXXRecordDecl(ASTUnit *AST, const std::string &Name) { + auto Result = + match(cxxRecordDecl(hasName(Name)).bind("record"), AST->getASTContext()); + if (Result.empty()) + return nullptr; + return Result[0].getNodeAs<CXXRecordDecl>("record"); +} + +TEST(SubobjectVisitorTest, Basic) { + auto AST = buildASTFromCode(R"cpp( + struct Base { + int BaseF; + }; + struct Inner { + int InnerF; + }; + struct S : public Base { + int MemberF; + Inner StructF; + }; + )cpp"); + ASSERT_TRUE(AST.get()); + + const CXXRecordDecl *RD = getCXXRecordDecl(AST.get(), "S"); + ASSERT_TRUE(RD); + + RecordingVisitor Visitor(AST->getASTContext()); + Visitor.visit(AST->getASTContext().getCanonicalTagType(RD)); + + EXPECT_EQ(Visitor.VisitedBases.size(), 1u); + EXPECT_EQ(Visitor.VisitedBases[0], "Base"); + EXPECT_EQ(Visitor.VisitedFields.size(), 4u); + EXPECT_EQ(Visitor.VisitedFields[0], "BaseF"); + EXPECT_EQ(Visitor.VisitedFields[1], "MemberF"); + EXPECT_EQ(Visitor.VisitedFields[2], "StructF"); + EXPECT_EQ(Visitor.VisitedFields[3], "InnerF"); +} + +TEST(SubobjectVisitorTest, Atomic) { + auto AST = buildASTFromCode(R"cpp( + struct S { + int SField; + }; + typedef S STypDef; + struct T { + _Atomic STypDef InnerAtomic; + }; + )cpp"); + ASSERT_TRUE(AST.get()); + + const CXXRecordDecl *RD = getCXXRecordDecl(AST.get(), "T"); + ASSERT_TRUE(RD); + + RecordingVisitor Visitor(AST->getASTContext()); + Visitor.visit(AST->getASTContext().getCanonicalTagType(RD)); + + EXPECT_EQ(Visitor.VisitedBases.size(), 0u); + EXPECT_EQ(Visitor.VisitedFields.size(), 2u); + EXPECT_EQ(Visitor.VisitedFields[0], "InnerAtomic"); + EXPECT_EQ(Visitor.VisitedFields[1], "SField"); +} + +TEST(SubobjectVisitorTest, FAM) { + auto AST = buildASTFromCode(R"cpp( + struct S { + int SField[]; + }; + struct T { + S Inner; + }; + )cpp"); + ASSERT_TRUE(AST.get()); + + const CXXRecordDecl *RD = getCXXRecordDecl(AST.get(), "T"); + ASSERT_TRUE(RD); + + RecordingVisitor Visitor(AST->getASTContext()); + Visitor.visit(AST->getASTContext().getCanonicalTagType(RD)); + + EXPECT_EQ(Visitor.VisitedBases.size(), 0u); + EXPECT_EQ(Visitor.VisitedFields.size(), 2u); + EXPECT_EQ(Visitor.VisitedFields[0], "Inner"); + EXPECT_EQ(Visitor.VisitedFields[1], "SField"); +} + +} // namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
