isuckatcs updated this revision to Diff 489940. isuckatcs added a comment. - Renamed and moved `isMultiLevelConvertiblePointer()` to `ASTContext` - Added a unit test to test the said function
CHANGES SINCE LAST ACTION https://reviews.llvm.org/D135495/new/ https://reviews.llvm.org/D135495 Files: clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp clang/include/clang/AST/ASTContext.h clang/include/clang/AST/Type.h clang/lib/AST/ASTContext.cpp clang/unittests/AST/CMakeLists.txt clang/unittests/AST/PointerConversionTest.cpp
Index: clang/unittests/AST/PointerConversionTest.cpp =================================================================== --- /dev/null +++ clang/unittests/AST/PointerConversionTest.cpp @@ -0,0 +1,245 @@ +//===- unittests/AST/PointerConversionTest.cpp - Pointer conversion 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 +// +//===----------------------------------------------------------------------===// +// +// This file contains tests for ASTContext::isQualificationConvertiblePointer(), +// which checks if one pointer can be converted to the other. +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Tooling.h" + +namespace clang { + +auto DeclMatcher = ast_matchers::declaratorDecl().bind("id"); + +class TypeExtractor : public ast_matchers::MatchFinder::MatchCallback { + int MatchCount = 0; + +public: + QualType T1, T2; + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override { + if (const auto *VD = Result.Nodes.getNodeAs<clang::VarDecl>("id")) { + if (MatchCount == 0) + T1 = VD->getType(); + else + T2 = VD->getType(); + + ++MatchCount; + } else if (const auto *FD = + Result.Nodes.getNodeAs<clang::FunctionDecl>("id")) { + if (MatchCount == 0) + T1 = FD->getType(); + else + T2 = FD->getType(); + + ++MatchCount; + } + } +}; + +bool CheckConvertible(llvm::StringRef Source, LangStandard Std) { + TypeExtractor Extractor; + ast_matchers::MatchFinder Finder; + + Finder.addMatcher(DeclMatcher, &Extractor); + std::unique_ptr<tooling::FrontendActionFactory> Factory( + tooling::newFrontendActionFactory(&Finder)); + + std::string StdArg = "-std="; + StdArg += Std.getName(); + + LangOptions Opts; + std::vector<std::string> Tmp; + LangOptions::setLangDefaults(Opts, Std.Language, {}, Tmp, + Std.getLangKind(Std.getName())); + + tooling::runToolOnCodeWithArgs(Factory->create(), Source, {StdArg}); + + return ASTContext::isQualificationConvertiblePointer(Extractor.T1, + Extractor.T2, Opts); +} + +TEST(QualificationConversion, builtin) { + ASSERT_FALSE(CheckConvertible( + R"cpp(char **p; const char **p1;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, builtin2) { + ASSERT_TRUE(CheckConvertible( + R"cpp(char **p; const char* const * p2;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, builtin3) { + ASSERT_TRUE(CheckConvertible( + R"cpp(char **p; volatile char * const * p3;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, builtin4) { + ASSERT_TRUE(CheckConvertible( + R"cpp(char **p; volatile const char* const* p4;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, builtin5) { + ASSERT_TRUE(CheckConvertible( + R"cpp(double *a[2][3]; double const * const (*ap)[3];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, builtin6) { + ASSERT_FALSE(CheckConvertible( + R"cpp(double *a[2][3]; double * const (*ap1)[];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx17))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(double *a[2][3]; double * const (*ap1)[];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx20))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(double *a[2][3]; double * const (*ap1)[];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx2b))); +} + +TEST(QualificationConversion, builtin7) { + ASSERT_TRUE(CheckConvertible( + R"cpp(int arr[3]; int *p;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, builtin8) { + ASSERT_FALSE(CheckConvertible( + R"cpp(int arr[3][3]; int **p;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(int arr[3][3]; int (*p)[3];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_FALSE(CheckConvertible( + R"cpp(int arr[3][3]; int (*p)[2];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_FALSE(CheckConvertible( + R"cpp(int arr[3][3]; int (*p)[];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(int arr[3][3]; int (*p)[];)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx20))); +} + +TEST(QualificationConversion, builtin9) { + ASSERT_TRUE(CheckConvertible( + R"cpp(void foo(); void (*ptr)() = foo;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(void foo(); void (* const ptr)() = foo;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(void foo(); void (* volatile ptr)() = foo;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(void foo(); void (* const volatile ptr)() = foo;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, builtin10) { + ASSERT_FALSE(CheckConvertible( + R"cpp(const int* p; int* p2;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_TRUE(CheckConvertible( + R"cpp(const int* p; const int* p2;)cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, derived) { + ASSERT_TRUE(CheckConvertible( + R"cpp( + class A {}; + class B : A {}; + + A **p; + volatile const A* const* p4; + )cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_FALSE(CheckConvertible( + R"cpp( + class A {}; + class B : A {}; + + B **p; + volatile const A* const* p4; + )cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_FALSE(CheckConvertible( + R"cpp( + class A {}; + class B : A {}; + + A **p; + volatile const B* const* p4; + )cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +TEST(QualificationConversion, member) { + ASSERT_TRUE(CheckConvertible( + R"cpp( + class A {}; + + char * A::*p; + volatile const char* const A::* p4; + )cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_TRUE(CheckConvertible( + R"cpp( + class A {}; + class B : public A {}; + + char * B::*p; + volatile const char* const B::* p4; + )cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_FALSE(CheckConvertible( + R"cpp( + class A {}; + class B : public A {}; + + char * A::*p; + volatile const char* const B::* p4; + )cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); + + ASSERT_FALSE(CheckConvertible( + R"cpp( + class A {}; + class B : public A {}; + + char * B::*p; + volatile const char* const A::* p4; + )cpp", + LangStandard::getLangStandardForKind(LangStandard::lang_cxx98))); +} + +} // namespace clang Index: clang/unittests/AST/CMakeLists.txt =================================================================== --- clang/unittests/AST/CMakeLists.txt +++ clang/unittests/AST/CMakeLists.txt @@ -34,6 +34,7 @@ StructuralEquivalenceTest.cpp TemplateNameTest.cpp TypePrinterTest.cpp + PointerConversionTest.cpp ) clang_target_link_libraries(ASTTests Index: clang/lib/AST/ASTContext.cpp =================================================================== --- clang/lib/AST/ASTContext.cpp +++ clang/lib/AST/ASTContext.cpp @@ -13456,3 +13456,104 @@ CUIDHash = llvm::utohexstr(llvm::MD5Hash(LangOpts.CUID), /*LowerCase=*/true); return CUIDHash; } + +// Checks if From is qualification convertible to To based on the current +// LangOpts. If From is any array, we perform the array to pointer conversion +// first. The function only performs check based on C++ rules, which can differ +// from the C rules. +// +// The function should only be called in C++ mode. +bool ASTContext::isQualificationConvertiblePointer(QualType From, QualType To, + LangOptions LangOpts, + unsigned CurrentLevel, + bool IsToConstSoFar) { + if (CurrentLevel == 0) { + if (From->isMemberPointerType() != To->isMemberPointerType()) + return false; + + if (From->isMemberPointerType() && To->isMemberPointerType()) { + const auto *FromMember = cast<MemberPointerType>(From); + const auto *ToMember = cast<MemberPointerType>(To); + + if (FromMember->Class != ToMember->Class) + return false; + + From = FromMember->PointeeType; + To = ToMember->PointeeType; + } + + if (!To->isPointerType() || + !(From->canDecayToPointerType() || From->isPointerType())) + return false; + + auto FromPointeeTy = From->getPointeeOrArrayElementQualType(); + auto ToPointeeTy = To->getPointeeOrArrayElementQualType(); + + return isQualificationConvertiblePointer(FromPointeeTy, ToPointeeTy, + LangOpts, CurrentLevel + 1, true); + } + + bool Convertible = true; + + if (From->isArrayType() && To->isArrayType()) { + if (From->isConstantArrayType() && To->isConstantArrayType()) { + Convertible &= + cast<ConstantArrayType>(From->getAsArrayTypeUnsafe())->getSize() == + cast<ConstantArrayType>(To->getAsArrayTypeUnsafe())->getSize(); + } else if (LangOpts.CPlusPlus20) { + // If there is an array type of unknown bound at some level + // (other than level zero) of From, there is an array type of + // unknown bound in the same level of To; (since C++20) + if (From->isIncompleteArrayType()) + Convertible &= To->isIncompleteArrayType(); + + // ... [or there is an array type of known bound in From and + // an array type of unknown bound in To (since C++20)] then + // there must be a 'const' at every single level (other than + // level zero) of To up until the current level. + if (From->isConstantArrayType() && To->isIncompleteArrayType()) + Convertible &= IsToConstSoFar; + } else { + return false; + } + + From = From->getAsArrayTypeUnsafe()->getElementType(); + To = To->getAsArrayTypeUnsafe()->getElementType(); + } + + // If at the current level To is more cv-qualified than From [...], + // then there must be a 'const' at every single level (other than level zero) + // of To up until the current level + bool MoreCVQualified = + To.getQualifiers().isStrictSupersetOf(From.getQualifiers()); + if (MoreCVQualified) + Convertible &= IsToConstSoFar; + + // If the pointers don't have the same amount of levels, they are not + // convertible to each other. + if (!From->isPointerType() || !To->isPointerType()) + return Convertible && + (MoreCVQualified || From.getQualifiers() == To.getQualifiers()) && + From->getCanonicalTypeUnqualified() == + To->getCanonicalTypeUnqualified(); + + // Note that in the C programming language, const/volatile can be added to the + // first level only. + bool CanBeCVQualified = LangOpts.CPlusPlus || CurrentLevel == 1; + + // If the current level (other than level zero) in From is 'const' qualified, + // the same level in To must also be 'const' qualified. + if (From.isConstQualified()) + Convertible &= To.isConstQualified() && CanBeCVQualified; + + // If the current level (other than level zero) in From is 'volatile' + // qualified, the same level in To must also be 'volatile' qualified. + if (From.isVolatileQualified()) + Convertible &= To.isVolatileQualified() && CanBeCVQualified; + + IsToConstSoFar &= To.isConstQualified(); + return Convertible && isQualificationConvertiblePointer( + From->getAs<PointerType>()->getPointeeType(), + To->getAs<PointerType>()->getPointeeType(), + LangOpts, CurrentLevel + 1, IsToConstSoFar); +} Index: clang/include/clang/AST/Type.h =================================================================== --- clang/include/clang/AST/Type.h +++ clang/include/clang/AST/Type.h @@ -2466,6 +2466,10 @@ /// This should never be used when type qualifiers are meaningful. const Type *getPointeeOrArrayElementType() const; + /// If this is a pointer type, return the pointee type. + /// If this is an array type, return the array element type. + QualType getPointeeOrArrayElementQualType() const; + /// If this is a pointer, ObjC object pointer, or block /// pointer, this returns the respective pointee. QualType getPointeeType() const; @@ -7350,13 +7354,18 @@ } inline const Type *Type::getPointeeOrArrayElementType() const { - const Type *type = this; - if (type->isAnyPointerType()) - return type->getPointeeType().getTypePtr(); - else if (type->isArrayType()) - return type->getBaseElementTypeUnsafe(); - return type; + return getPointeeOrArrayElementQualType().getTypePtr(); } + +inline QualType Type::getPointeeOrArrayElementQualType() const { + if (isAnyPointerType()) + return getPointeeType(); + else if (isArrayType()) + return getAsArrayTypeUnsafe()->getElementType(); + + return {this, 0}; +} + /// Insertion operator for partial diagnostics. This allows sending adress /// spaces into a diagnostic with <<. inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &PD, Index: clang/include/clang/AST/ASTContext.h =================================================================== --- clang/include/clang/AST/ASTContext.h +++ clang/include/clang/AST/ASTContext.h @@ -2825,6 +2825,11 @@ bool propertyTypesAreCompatible(QualType, QualType); bool typesAreBlockPointerCompatible(QualType, QualType); + static bool isQualificationConvertiblePointer(QualType From, QualType To, + LangOptions LangOpts, + unsigned CurrentLevel = 0, + bool IsToConstSoFar = false); + bool isObjCIdType(QualType T) const { if (const auto *ET = dyn_cast<ElaboratedType>(T)) T = ET->getNamedType(); Index: clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp +++ clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp @@ -101,6 +101,126 @@ } } +void throw_catch_pointer_c() noexcept { + try { + int a = 1; + throw &a; + } catch(const int *) {} +} + +void throw_catch_pointer_v() noexcept { + try { + int a = 1; + throw &a; + } catch(volatile int *) {} +} + +void throw_catch_pointer_cv() noexcept { + try { + int a = 1; + throw &a; + } catch(const volatile int *) {} +} + +void throw_catch_multi_ptr_1() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_catch_multi_ptr_1' which should not throw exceptions + try { + char **p = 0; + throw p; + } catch (const char **) { + } +} + +void throw_catch_multi_ptr_2() noexcept { + try { + char **p = 0; + throw p; + } catch (const char *const *) { + } +} + +void throw_catch_multi_ptr_3() noexcept { + try { + char **p = 0; + throw p; + } catch (volatile char *const *) { + } +} + +void throw_catch_multi_ptr_4() noexcept { + try { + char **p = 0; + throw p; + } catch (volatile const char *const *) { + } +} + +// FIXME: In this case 'a' is convertible to the handler and should be caught +// but in reality it's thrown. Note that clang doesn't report a warning for +// this either. +void throw_catch_multi_ptr_5() noexcept { + try { + double *a[2][3]; + throw a; + } catch (double *(*)[3]) { + } +} + + +void throw_c_catch_pointer() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_c_catch_pointer' which should not throw exceptions + try { + int a = 1; + const int *p = &a; + throw p; + } catch(int *) {} +} + +void throw_c_catch_pointer_v() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_c_catch_pointer_v' which should not throw exceptions + try { + int a = 1; + const int *p = &a; + throw p; + } catch(volatile int *) {} +} + +void throw_v_catch_pointer() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_v_catch_pointer' which should not throw exceptions + try { + int a = 1; + volatile int *p = &a; + throw p; + } catch(int *) {} +} + +void throw_v_catch_pointer_c() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_v_catch_pointer_c' which should not throw exceptions + try { + int a = 1; + volatile int *p = &a; + throw p; + } catch(const int *) {} +} + +void throw_cv_catch_pointer_c() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_cv_catch_pointer_c' which should not throw exceptions + try { + int a = 1; + const volatile int *p = &a; + throw p; + } catch(const int *) {} +} + +void throw_cv_catch_pointer_v() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_cv_catch_pointer_v' which should not throw exceptions + try { + int a = 1; + const volatile int *p = &a; + throw p; + } catch(volatile int *) {} +} + class base {}; class derived: public base {}; @@ -112,6 +232,24 @@ } } +void throw_derived_catch_base_ptr_c() noexcept { + try { + derived d; + throw &d; + } catch(const base *) { + } +} + +void throw_derived_catch_base_ptr() noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_ptr' which should not throw exceptions + try { + derived d; + const derived *p = &d; + throw p; + } catch(base *) { + } +} + void try_nested_try(int n) noexcept { // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'try_nested_try' which should not throw exceptions try { Index: clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h =================================================================== --- clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h +++ clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h @@ -80,7 +80,7 @@ /// possible to catch multiple exception types by one 'catch' if they /// are a subclass of the 'catch'ed exception type. /// Returns 'true' if some exceptions were filtered, otherwise 'false'. - bool filterByCatch(const Type *BaseClass); + bool filterByCatch(const Type *BaseClass, const LangOptions &LangOpts); /// Filter the set of thrown exception type against a set of ignored /// types that shall not be considered in the exception analysis. Index: clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp =================================================================== --- clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp +++ clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp @@ -56,10 +56,26 @@ [BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; }); } -bool ExceptionAnalyzer::ExceptionInfo::filterByCatch(const Type *BaseClass) { +bool ExceptionAnalyzer::ExceptionInfo::filterByCatch( + const Type *BaseClass, const LangOptions &LangOpts) { llvm::SmallVector<const Type *, 8> TypesToDelete; for (const Type *T : ThrownExceptions) { - if (T == BaseClass || isBaseOf(T, BaseClass)) + if (T->isPointerType() && BaseClass->isPointerType()) { + + SplitQualType TPointeeTy = T->getPointeeType().split(); + SplitQualType BasePointeeTy = BaseClass->getPointeeType().split(); + + if (!TPointeeTy.Ty->isPointerType() && + (TPointeeTy.Ty == BasePointeeTy.Ty || + isBaseOf(TPointeeTy.Ty, BasePointeeTy.Ty)) && + (BasePointeeTy.Quals.isStrictSupersetOf(TPointeeTy.Quals) || + BasePointeeTy.Quals == TPointeeTy.Quals)) { + TypesToDelete.push_back(T); + } else if (ASTContext::isQualificationConvertiblePointer( + QualType(T, 0), QualType(BaseClass, 0), LangOpts)) { + TypesToDelete.push_back(T); + } + } else if (T == BaseClass || isBaseOf(T, BaseClass)) TypesToDelete.push_back(T); } @@ -186,11 +202,14 @@ ->getUnqualifiedDesugaredType(); } + const auto &LangOpts = + Catch->getExceptionDecl()->getASTContext().getLangOpts(); + // If the caught exception will catch multiple previously potential // thrown types (because it's sensitive to inheritance) the throwing // situation changes. First of all filter the exception types and // analyze if the baseclass-exception is rethrown. - if (Uncaught.filterByCatch(CaughtType)) { + if (Uncaught.filterByCatch(CaughtType, LangOpts)) { ExceptionInfo::Throwables CaughtExceptions; CaughtExceptions.insert(CaughtType); ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(),
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits