diff --git a/clang-tools-extra/CMakeLists.txt b/clang-tools-extra/CMakeLists.txt --- a/clang-tools-extra/CMakeLists.txt +++ b/clang-tools-extra/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(modularize) add_subdirectory(clang-tidy) +add_subdirectory(clang-cast) add_subdirectory(clang-change-namespace) add_subdirectory(clang-doc) add_subdirectory(clang-include-fixer) diff --git a/clang-tools-extra/clang-cast/CMakeLists.txt b/clang-tools-extra/clang-cast/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-cast/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS + support) + +add_clang_tool(clang-cast + ClangCast.cpp + ) + +target_link_libraries(clang-cast + PRIVATE + clangTooling + clangBasic + clangRewriteFrontend + clangASTMatchers + ) diff --git a/clang-tools-extra/clang-cast/Cast.h b/clang-tools-extra/clang-cast/Cast.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-cast/Cast.h @@ -0,0 +1,497 @@ +//===--- Cast.h - clang-cast ------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains classes that manage semantics of C style casts. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CAST_H + +#include "CastOptions.h" +#include "CastUtils.h" +#include "clang/AST/OperationKinds.h" +#include + +//===----------------------------------------------------------------------===// +// CStyleCastOperation +//===----------------------------------------------------------------------===// + +namespace clang { +namespace cppcast { + +/// CStyleCastOperation is responsible for capturing a CStyleCastExpr AST node +/// and perform some basic semantic analysis on the C style cast's equivalent in +/// C++ explicit cast expressions. +/// +/// This class does not have any ownership semantics for the CStyleCastExpr +/// itself, nor does it have ownership semantics for the underlying Context that +/// the CStyleCastExpr is from. +/// +/// Note that although some functions are similar to the Sema library, such as +/// CastsAwayConstness vs. requireConstCast, it is a much simpler version as we +/// assume compilation is successful and we are restricted to C++. The reason +/// Sema was not used here is because the anonymous namespaces and static +/// visibility of the original functions require leaking existing abstractions +/// and a large refactoring in the fundamental Clang codebase. +class CStyleCastOperation { + /// The main expression captured by a matcher + const CStyleCastExpr &MainExpr; + + /// The subexpression type in the C style cast. + /// For example: + /// \code + /// (int) 3.2f; + /// \endcode + /// + /// the subexpression is 3.2f, and its type is float. + const QualType SubExprType; + + /// The cast type in the C style cast. + /// For example: + /// \code + /// (int) 3.2f; + /// \endcode + /// + /// the cast type is int. + const QualType CastType; + + /// The context that the CStyleCastExpr belongs to. + const ASTContext &Context; + + /// The engine in the context responsible for printing diagnostics + const DiagnosticsEngine &DiagEngine; + + /// Whether or not the generated code should comply with -pedantic + const bool Pedantic; + +public: + /// Initializes a new CStyleCastOperation + /// \param CastExpr node to perform semantic analysis on + /// \param Context the context that CastExpr belongs to + /// \param Pedantic whether we should generate code that complies with + /// -pedantic and -pedantic-errors + /// + /// Note that we are canonicalizing the subexpression and cast types. We don't + /// yet provide typedef-preserving code generation. + CStyleCastOperation(const CStyleCastExpr &CastExpr, const ASTContext &Context, + const bool Pedantic) + : MainExpr(CastExpr), + SubExprType( + CastExpr.getSubExprAsWritten()->getType().getCanonicalType()), + CastType(CastExpr.getTypeAsWritten().getCanonicalType()), + Context(Context), DiagEngine(Context.getDiagnostics()), + Pedantic(Pedantic) {} + + CStyleCastOperation() = delete; + CStyleCastOperation(const CStyleCastOperation &) = delete; + CStyleCastOperation(CStyleCastOperation &&) = delete; + CStyleCastOperation &operator=(const CStyleCastOperation &) = delete; + CStyleCastOperation &operator=(CStyleCastOperation &&) = delete; + virtual ~CStyleCastOperation() = default; + + /// public accessor methods, some of which return reference handles. + QualType getSubExprType() const { return SubExprType; } + QualType getCastType() const { return CastType; } + const CStyleCastExpr &getCStyleCastExpr() const { return MainExpr; } + const Expr &getSubExprAsWritten() const { + return *MainExpr.getSubExprAsWritten(); + } + const ASTContext &getContext() const { return Context; } + + /// This method tests to see whether the conversion from SubExprType to + /// CastType is "casting away constness", as the standard describes. + /// + /// The standard states that a conversion is casting away constness when there + /// exists a cv-decomposition such that there exists no implicit qualification + /// conversion from SubExprType to CastType. + /// + /// Clang, without -pedantic or -pedantic-error, allows a few exceptional + /// cases, such as when two nested pointers diverge in type (pointer vs. + /// array, for example), and the rest of the qualifiers are ignored in the + /// cast-away-constness consideration. (exception: two member-pointers that + /// are pointers to different classes are considered the same "type"). + /// + /// Some other notable exceptions include variable-length arrays (VLA) being + /// used in non-pedantic codebases, which we also allow. + bool requireConstCast() const; + + /// This converts From(the cast type) into To(subexpression type)'s qualifiers + /// to prepare for const_cast. + /// \return QualType corresponding to CastType but with minimal qualifiers to + /// satisfy for a non-pedantic conversion. + /// + /// For example: + /// \code + /// const int* const (* const x) [2]; + /// (int***) x; + /// \endcode + /// + /// will yield (int* const* const* const) for typical, non-pedantic usage + /// because const_cast will only modify the qualifiers where they are + /// both locally similar (they stop being similar at array vs. pointer). + /// + /// We need this to first perform static/reinterpret casts and then const + /// cast. In order to do this, we must take the cast type and change its + /// qualifiers so that it can be performed by static/reinterpret cast first. + /// + /// NOTE: There is only one case where we'd need to modify qualifiers and that + /// is for function pointers. Const cast cannot change qualifiers on function + /// pointers. + QualType changeQualifiers() const { + return changeQualifierHelper(CastType, SubExprType); + } + + /// Given the CStyleCastExpr, determine from its CastKind and other metadata + /// to decide what kind of C++ style cast is appropriate, if any. This does + /// not take into consideration const_cast and qualifiers. Use + /// requireConstCast() to determine whether const_cast should be added. + /// + /// \return CXXCast corresponding to the type of conversion. + CXXCast getCastKindFromCStyleCast() const { return castHelper(&MainExpr); } + +private: + /// Auxilliary diagnostic methods + void warnMemberPointerClass(const QualType &SEType, + const QualType &CType) const; + void errorPedanticVLA(const QualType &T) const; + + /// \param SEType, a copy of QualType subexpression + /// \param CType, a copy of QualType cast type + /// \return true if the const was casted away, false otherwise. + /// + /// IMPORTANT: We are assuming that reinterpret_cast will have already taken + /// care of the downcasting of nested function pointers, and if we have nested + /// function pointers, they have the same qualifiers. + bool castAwayConst(const QualType &SEType, const QualType &CType) const; + + /// \param From, the CastType, which needs to be modified to not require const + /// cast + /// \param To, the SubExpression, which has specific qualifiers on it. + /// \return QualType mirroring From but with qualifiers on To. + QualType changeQualifierHelper(QualType From, const QualType &To) const; + + /// Recurse CastExpr AST nodes until a non-cast expression has been reached. + /// \return CXXCast enum corresponding to the lowest power cast required. + CXXCast castHelper(const Expr *Expression) const; + + /// Given a CastExpr, determine from its CastKind and other metadata + /// the corresponding CXXCast to use (NOTE: this does not include + /// the additional const cast for qualifiers if it is + /// static/interpret. Use isQualifierModified for that.) + /// Note that it's a CastExpr, which can be either CStyleCastExpr + /// or ImplicitCastExpr or any of its children. + /// + /// \return CXXCast corresponding to the type of conversion. + CXXCast getCastType(const CastExpr *CastExpression) const; +}; + +bool CStyleCastOperation::requireConstCast() const { + // Case 0 - We just cannot cast function pointers at the very beginning, + // regardless of whether it's being downcasted or not. + if (details::isFunctionPtr(CastType) || details::isFunctionPtr(SubExprType)) { + return false; + } + + // We modify the types for references + // Note that for our subexpr type, we use the type post-implicit conv. + QualType ModifiedImpliedSubExprType = + MainExpr.getSubExpr()->getType().getCanonicalType(); + QualType ModifiedCastType = CastType; + + // Case 1 - reference type: + // remove the reference from both the subexpr and cast and add a pointer + // level. + if (ModifiedCastType->isReferenceType()) { + ModifiedCastType = ModifiedCastType.getNonReferenceType(); + if (ModifiedImpliedSubExprType->isReferenceType()) { + ModifiedImpliedSubExprType = + ModifiedImpliedSubExprType.getNonReferenceType(); + } + ModifiedCastType = Context.getPointerType(ModifiedCastType); + ModifiedImpliedSubExprType = + Context.getPointerType(ModifiedImpliedSubExprType); + } + + // Case 2, 3 - pointer type & POD type + // if the pointer qualifiers are downcasted at any level, then fail. + // if the POD qualifiers are downcasted, then fail. + return castAwayConst(ModifiedImpliedSubExprType, ModifiedCastType); +} + +void CStyleCastOperation::warnMemberPointerClass(const QualType &SEType, + const QualType &CType) const { + // Auxilliary: If the member pointers classes are + // not the same, issue a warning. + if (SEType->isMemberPointerType() && CType->isMemberPointerType()) { + const MemberPointerType *MPSEType = dyn_cast(SEType); + const Type *SEClass = MPSEType->getClass(); + + const MemberPointerType *MPCType = dyn_cast(CType); + const Type *CClass = MPCType->getClass(); + + if (SEClass->getCanonicalTypeUnqualified() != + CClass->getCanonicalTypeUnqualified()) { + reportWithLoc( + Context.getDiagnostics(), DiagnosticsEngine::Warning, + "C style cast performs a member-to-pointer cast from class %0 to " + "%1, which are not equal", + MainExpr.getExprLoc(), QualType(SEClass, /*Quals=*/0), + QualType(CClass, /*Quals=*/0)); + } + } +} + +void CStyleCastOperation::errorPedanticVLA(const QualType &T) const { + if (!Pedantic) + return; + // Auxilliary: If the type is variable length arrays (VLA)s, it should raise + // warnings under --pedantic + if (T->isVariableArrayType()) { + reportWithLoc(Context.getDiagnostics(), DiagnosticsEngine::Error, + "detected variable length array %0 with --pedantic enabled", + MainExpr.getExprLoc(), T); + } +} + +bool CStyleCastOperation::castAwayConst(const QualType &SEType, + const QualType &CType) const { + if (SEType.isMoreQualifiedThan(CType)) { + return true; + } + + // If we follow clang's extensions, then the moment something is not locally + // similar, we consider the source and destination types not equal and so we + // can return false for any nested levels. + if (!details::isLocallySimilar(SEType, CType) && !Pedantic) + return false; + + // Auxiliary warnings during parsing + warnMemberPointerClass(SEType, CType); + errorPedanticVLA(SEType); + errorPedanticVLA(CType); + + if (details::isTerminal(SEType, CType)) + return false; + + QualType SEStrippedType = details::stripLayer(SEType, Context); + QualType CStrippedType = details::stripLayer(CType, Context); + + // Continue recursing for pointer types + return castAwayConst(SEStrippedType, CStrippedType); +} + +QualType CStyleCastOperation::changeQualifierHelper(QualType From, + const QualType &To) const { + // If it's a function pointer, then we don't change the qualifier and we've + // reached the end. + if (details::isFunctionPtr(From)) + return From; + + // We're changing qualifiers because it'd be casting away constness. + // If we follow the standard, we could be casting away constness down to the + // terminal level. + // If we follow clang's extensions, we could be casting away constness until + // we've reached a non-similar stage. + if ((!details::isLocallySimilar(From, To) && !Pedantic) || + details::isTerminal(From, To)) { + From.setLocalFastQualifiers(To.getLocalFastQualifiers()); + return From; + } + + auto StrippedTo = details::stripLayer(To, Context); + auto StrippedFrom = details::stripLayer(From, Context); + + auto Temp = changeQualifierHelper(StrippedFrom, StrippedTo); + // If they are locally similar and non-terminal, we can keep going down + if (From->isPointerType()) { + // modify the nested types + From = Context.getPointerType(Temp); + } else if (From->isMemberPointerType()) { + const MemberPointerType *MPT = dyn_cast(From); + const Type *FromClass = MPT->getClass(); + // modify the nested types + From = Context.getMemberPointerType(Temp, FromClass); + } else if (From->isConstantArrayType()) { + const ConstantArrayType *AT = dyn_cast(From); + // Don't assign yet because we need to change the qualifiers + From = Context.getConstantArrayType(Temp, AT->getSize(), AT->getSizeExpr(), + AT->getSizeModifier(), + AT->getIndexTypeCVRQualifiers()); + } else if (From->isIncompleteArrayType()) { + const IncompleteArrayType *IAT = dyn_cast(From); + From = Context.getIncompleteArrayType(Temp, IAT->getSizeModifier(), + IAT->getIndexTypeCVRQualifiers()); + } else if (From->isVariableArrayType()) { + // The following can only work if we're not in --pedantic + const VariableArrayType *VAT = dyn_cast(From); + Context.getVariableArrayType( + Temp, VAT->getSizeExpr(), VAT->getSizeModifier(), + VAT->getIndexTypeCVRQualifiers(), VAT->getBracketsRange()); + } + // Unwrap the references and reconstruct them + // but we don't need to strip To here (lvalue-to-rvalue would've already + // happened) + else if (From->isLValueReferenceType()) { + From = Context.getLValueReferenceType(Temp); + } else if (From->isRValueReferenceType()) { + From = Context.getRValueReferenceType(Temp); + } + + From.setLocalFastQualifiers(To.getLocalFastQualifiers()); + return From; +} + +CXXCast CStyleCastOperation::castHelper(const Expr *Expression) const { + const auto *CastExpression = dyn_cast(Expression); + + // Base case - we have reached an expression that's not a CastExpr + if (!CastExpression) { + return CXXCast::CC_NoOpCast; + } + + // If it's a cast expression but is not a part of the explicit c-style cast, + // we've also gone too far. + const auto *ImplicitCastExpression = dyn_cast(Expression); + + if (ImplicitCastExpression && + !ImplicitCastExpression->isPartOfExplicitCast()) { + return CXXCast::CC_NoOpCast; + } + + return std::max(getCastType(CastExpression), + castHelper(CastExpression->getSubExpr())); +} + +CXXCast CStyleCastOperation::getCastType(const CastExpr *CastExpression) const { + switch (CastExpression->getCastKind()) { + /// No-op cast type + case CastKind::CK_NoOp: + case CastKind::CK_ArrayToPointerDecay: + case CastKind::CK_LValueToRValue: + return CXXCast::CC_NoOpCast; + + // Static cast (with extension) types + case CastKind::CK_UncheckedDerivedToBase: + case CastKind::CK_DerivedToBase: { + /// Special case: + /// The base class A is inaccessible (private) + /// so we can't static_cast. We can't reinterpret_cast + /// either because reinterpret casting to A* would point + /// to the data segment owned by Pad. We can't convert to C style + /// in this case. + /// + /// \code + /// struct A { int i; }; + /// struct Pad { int i; }; + /// class B: Pad, A {}; + /// + /// A *f(B *b) { return (A*)(b); } + /// \endcode + /// + /// We assume that the base class is unambiguous. + const CXXRecordDecl *Base = CastType->getPointeeCXXRecordDecl(); + const CXXRecordDecl *Derived = SubExprType->getPointeeCXXRecordDecl(); + return details::getBaseDerivedCast(Base, Derived); + } + case CastKind::CK_BaseToDerived: { + const CXXRecordDecl *Base = SubExprType->getPointeeCXXRecordDecl(); + const CXXRecordDecl *Derived = CastType->getPointeeCXXRecordDecl(); + return details::getBaseDerivedCast(Base, Derived); + } + case CastKind::CK_FunctionToPointerDecay: + case CastKind::CK_NullToPointer: + case CastKind::CK_NullToMemberPointer: + case CastKind::CK_BaseToDerivedMemberPointer: + case CastKind::CK_DerivedToBaseMemberPointer: + case CastKind::CK_MemberPointerToBoolean: + case CastKind::CK_UserDefinedConversion: + case CastKind::CK_ConstructorConversion: + case CastKind::CK_PointerToBoolean: + case CastKind::CK_ToVoid: + // vector splats are constant size vectors that can be + // broadcast assigned a single value. + case CastKind::CK_VectorSplat: + // Common integral/float cast types + case CastKind::CK_IntegralCast: + case CastKind::CK_IntegralToBoolean: + case CastKind::CK_IntegralToFloating: + case CastKind::CK_FixedPointCast: + case CastKind::CK_FixedPointToIntegral: + case CastKind::CK_IntegralToFixedPoint: + case CastKind::CK_FixedPointToBoolean: + case CastKind::CK_FloatingToIntegral: + case CastKind::CK_FloatingToBoolean: + case CastKind::CK_BooleanToSignedIntegral: + case CastKind::CK_FloatingCast: + // Floating complex cast types + case CastKind::CK_FloatingRealToComplex: + case CastKind::CK_FloatingComplexToReal: + case CastKind::CK_FloatingComplexToBoolean: + case CastKind::CK_FloatingComplexCast: + case CastKind::CK_FloatingComplexToIntegralComplex: + // Integral complex cast types + case CastKind::CK_IntegralRealToComplex: + case CastKind::CK_IntegralComplexToReal: + case CastKind::CK_IntegralComplexToBoolean: + case CastKind::CK_IntegralComplexCast: + case CastKind::CK_IntegralComplexToFloatingComplex: + // Atomic to non-atomic casts + case CastKind::CK_AtomicToNonAtomic: + case CastKind::CK_NonAtomicToAtomic: + // OpenCL casts + // https://godbolt.org/z/DEz8Rs + case CastKind::CK_ZeroToOCLOpaqueType: + case CastKind::CK_AddressSpaceConversion: + case CastKind::CK_IntToOCLSampler: + return CXXCast::CC_StaticCast; + + // Reinterpret cast types + case CastKind::CK_BitCast: + case CastKind::CK_LValueBitCast: + case CastKind::CK_IntegralToPointer: + case CastKind::CK_LValueToRValueBitCast: + case CastKind::CK_ReinterpretMemberPointer: + case CastKind::CK_PointerToIntegral: + return CXXCast::CC_ReinterpretCast; + + // C style cast types + // Dependent types are left as they are. + case CastKind::CK_Dependent: + return CXXCast::CC_CStyleCast; + + // Invalid cast types for C++ + // Union casts is not available in C++. + case CastKind::CK_ToUnion: + // Built-in functions must be directly called and don't have + // address. This is impossible for C style casts. + case CastKind::CK_BuiltinFnToFnPtr: + // C++ does not officially support the Objective C extensions. + // We mark these as invalid. + // - Objective C pointer types & + // - Objective C automatic reference counting (ARC) + // - Objective C blocks + // - etc + case CastKind::CK_CPointerToObjCPointerCast: + case CastKind::CK_BlockPointerToObjCPointerCast: + case CastKind::CK_AnyPointerToBlockPointerCast: + case CastKind::CK_ObjCObjectLValueCast: + case CastKind::CK_ARCProduceObject: + case CastKind::CK_ARCConsumeObject: + case CastKind::CK_ARCReclaimReturnedObject: + case CastKind::CK_ARCExtendBlockObject: + case CastKind::CK_CopyAndAutoreleaseBlockObject: + default: + return CXXCast::CC_InvalidCast; + } +} + +} // namespace cppcast +} // namespace clang +#endif diff --git a/clang-tools-extra/clang-cast/CastOptions.h b/clang-tools-extra/clang-cast/CastOptions.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-cast/CastOptions.h @@ -0,0 +1,190 @@ +//===--- CastOptions.h - clang-cast -----------------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the enumerations and options that dictate behavior of +/// the Matcher object defined in Matcher.h. +/// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CAST_OPTIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CAST_OPTIONS_H + +#include "clang/Rewrite/Frontend/FixItRewriter.h" +#include + +namespace clang { +namespace cppcast { + +/// Enumerations for cast types +/// The ordering of these enums is important. +/// +/// C style casts in clang are performed incrementally: +/// - CStyleCastExpr +/// - ImplicitCastExpr +/// - ImplicitCastExpr +/// ... +/// - DeclRefExpr (for example) +/// +/// Each one of the cast exprs may require a more "powerful" level of +/// casting. With the exception of dynamic cast, the rest are ordered +/// accordingly. +/// +/// CC_DynamicCast +/// -------------- +/// \code +/// dynamic_cast(derived_ptr); +/// \endcode +/// +/// A conversion from Base to Derived or vice versa that is +/// performed at RUNTIME. This is not possible to be expressed +/// in terms of C style casts. +/// +/// CC_NoOpCast +/// ----------- +/// This is either a cast to itself or an implicit conversion that +/// can be done without casting. +/// +/// CC_ConstCast +/// ------------ +/// \code +/// int x = 1; +/// const int& y = x; +/// const_cast(y); +/// \endcode +/// +/// A conversion from the same type but with different qualifiers on the +/// multilevel pointer-array structure. +/// +/// CC_StaticCast +/// ------------- +/// \code +/// static_cast(true); +/// \endcode +/// +/// Static cast can perform logical conversions between types, +/// call explicitly defined conversion functions such as operator(), +/// and cast up and down an inheritance hierarchy (given access), +/// and more. +/// +/// CC_ReinterpretCast +/// ------------------ +/// \code +/// int* x; +/// (bool*) x; +/// \endcode +/// +/// The above is a bitcast, and is generally the theme of reinterpret cast. +/// We reinterpret the bits of the data type into something else. This cast +/// will only cast A to B if sizeof(A) <= sizeof(B). Out of all the C++ casts, +/// this is the most "rule-breaking" and dangerous, and should be used +/// very sparingly. +/// +/// CC_CStyleCast +/// ------------- +/// \code +/// template +/// void foo() { +/// (T) 0; +/// } +/// \endcode +/// +/// There are some cases where none of the above casts are possible, +/// or suitable for replacement for C style casts, such as when +/// static_cast cannot cast DerivedToBase due to insufficient access, +/// or C style casting dependent (template) types (which can be any type +/// enumerated above, including the DerivedToBase case). It is generally +/// good to convert all C style casts to something of lower power, but +/// sometimes it's not possible without losing power. +/// +/// CC_InvalidCast +/// -------------- +/// This maps to the set of CastKind::CK_* that are not possible to +/// generate in C++. If this enum is encountered, something is wrong. +/// +/// NOTE: We are using enums instead of enum-classes for the following reasons: +/// - We can't use convenience functions from llvm CommandLine.h to define +/// lists. +/// - we want to make these these values bitmasks for inclusivity testing, and +/// there is no implicit conversion from enum-class values to integral types. +enum CXXCast { + CC_DynamicCast = 0b1, + CC_NoOpCast = 0b10, + CC_ConstCast = 0b100, + CC_StaticCast = 0b1000, + CC_ReinterpretCast = 0b10000, + CC_CStyleCast = 0b100000, + CC_InvalidCast = 0b1000000, +}; + +} // namespace cppcast + +namespace cli { + +using clang::cppcast::CXXCast; + +/// Options for CLI to specify which of the following should raise an error +/// upon encountering CStyleCast with equivalent power. +/// +/// NOTE: If a C style cast requires both const and static, having EO_StaticCast +/// is sufficient to trigger an error. +enum ErrorOpts { + EO_StaticCast = CXXCast::CC_StaticCast, + EO_ReinterpretCast = CXXCast::CC_ReinterpretCast, + EO_ConstCast = CXXCast::CC_ConstCast, + EO_CStyleCast = CXXCast::CC_CStyleCast, + EO_NoOpCast = CXXCast::CC_NoOpCast, + EO_All = 0xffffffff, +}; + +/// Options for CLI to specify which of C style casts should be fixed with +/// FixItWriters. +/// +/// NOTE: If a C style cast requires both const and static, it is required to +/// have the bits of 'FO_StaticCast | FO_ConstCast' in the mask to apply a fix. +enum FixOpts { + FO_StaticCast = CXXCast::CC_StaticCast, + FO_ReinterpretCast = CXXCast::CC_ReinterpretCast, + FO_ConstCast = CXXCast::CC_ConstCast, + // NOTE: There is no "fix_cstyle" because we can't fix them. + FO_NoOpCast = CXXCast::CC_NoOpCast, + FO_All = 0xffffffff, +}; + +} // namespace cli + +namespace rewriter { + +/// Custom FixItOptions to allow users to emit to files with added suffix. +class FixItRewriterOptions : public clang::FixItOptions { +public: + FixItRewriterOptions(const std::string &RewriteSuffix) + : RewriteSuffix(RewriteSuffix) { + if (RewriteSuffix.empty()) { + InPlace = true; + } else { + InPlace = false; + } + FixWhatYouCan = true; + } + + std::string RewriteFilename(const std::string &Filename, int &fd) override { + // Set fd to -1 to mean that the file descriptor is not yet opened. + fd = -1; + const auto NewFilename = Filename + RewriteSuffix; + return NewFilename; + } + +private: + std::string RewriteSuffix; +}; + +} // namespace rewriter + +} // namespace clang + +#endif diff --git a/clang-tools-extra/clang-cast/CastUtils.h b/clang-tools-extra/clang-cast/CastUtils.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-cast/CastUtils.h @@ -0,0 +1,195 @@ +//===--- CastUtils.h - clang-cast -------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains utility functions for semantics and diagnostics. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CAST_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CAST_UTILS_H + +#include "CastOptions.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" + +namespace clang { +namespace cppcast { + +/// Given a cast type enum of the form CXXCasts::CC_{type}, return the +/// string representation of that respective type. +std::string cppCastToString(const CXXCast &Cast) { + switch (Cast) { + case CXXCast::CC_ConstCast: + return "const_cast"; + case CXXCast::CC_StaticCast: + return "static_cast"; + case CXXCast::CC_ReinterpretCast: + return "reinterpret_cast"; + // Below are only used for summary diagnostics + case CXXCast::CC_CStyleCast: + return "C style cast"; + case CXXCast::CC_NoOpCast: + return "No-op cast"; + default: + llvm_unreachable("The cast should never occur."); + return {}; + } +} + +/// Reports messages with a source location, typically used to address +/// specific code segments. +/// +/// \tparam N length of string +/// \tparam Args variadic types to be accepted by DiagnosticBuilder +/// \param Engine the diagnostic engine to report with +/// \param Level diagnostic level +/// \param FormatString A C string with \p N characters with potential clang +/// format strings +/// \param Loc The starting location in the translation unit to +/// address +/// \param args data to be formatted into FormatString \return resulting +/// message object to be emitted. +template +DiagnosticBuilder reportWithLoc(DiagnosticsEngine &Engine, + const DiagnosticsEngine::Level &Level, + const char (&FormatString)[N], + const SourceLocation &Loc, Args &&... args) { + unsigned ID = Engine.getCustomDiagID(Level, FormatString); + // Binary left fold + return (Engine.Report(Loc, ID) << ... << std::forward(args)); +} + +/// Reports messages, typically used to address a translation-unit/file wide +/// diagnostic. Refer to reportWithLoc for more information. +template +DiagnosticBuilder report(DiagnosticsEngine &Engine, + DiagnosticsEngine::Level Level, + const char (&FormatString)[N], Args &&... args) { + unsigned ID = Engine.getCustomDiagID(Level, FormatString); + // Binary left fold + return (Engine.Report(ID) << ... << std::forward(args)); +} + +namespace details { + +/// Determines whether Base is accessible from Derived class. +/// +/// \param Base the base class declaration +/// \param Derived the derived class declaration +/// \returns true if \p Base is accessible from \p Derived or are the same +/// class, and false if \p Base is not accessible from \p Derived or are +/// unrelated classes +bool isAccessible(const CXXRecordDecl *Base, const CXXRecordDecl *Derived) { + if (!Base || !Derived) + return false; + + if (clang::declaresSameEntity(Derived, Base)) { + // The class's contents is always accessible to itself. + return true; + } + + for (const CXXBaseSpecifier &Specifier : Derived->bases()) { + // This should already be canonical + const QualType &BaseType = Specifier.getType(); + CXXRecordDecl *BaseClass = (*BaseType).getAsCXXRecordDecl(); + + if (Specifier.getAccessSpecifier() == clang::AccessSpecifier::AS_public && + isAccessible(Base, BaseClass)) + return true; + } + + // These two are unrelated classes. + return false; +} + +/// Determines the proper cast type for a Base to/from Derived conversion +/// based off of accessbility. +/// +/// \param Base the base class declaration +/// \param Derived the derived class declaration +/// \return CXXCast enum corresponding to the lowest power cast required. +CXXCast getBaseDerivedCast(const CXXRecordDecl *Base, + const CXXRecordDecl *Derived) { + assert(Base && Derived && "Base and Derived decls cannot be null."); + if (!details::isAccessible(Base, Derived)) + return CXXCast::CC_CStyleCast; + else + return CXXCast::CC_StaticCast; +} + +/// Removes a layer of pointers, member pointers, arrays. +/// +/// \param T type to strip, assumed to be one of the above. +/// \param Context, the ASTContext to create the array type edge case. +/// \return type corresponding to \p T stripped of one indirection layer. +QualType stripLayer(const QualType &T, const ASTContext &Context) { + if (T->isPointerType()) { + const PointerType *PT = dyn_cast(T); + return PT->getPointeeType(); + } + if (T->isArrayType()) { + const ArrayType *AT = Context.getAsArrayType(T); + return AT->getElementType(); + } + if (T->isMemberPointerType()) { + const MemberPointerType *MPT = dyn_cast(T); + return MPT->getPointeeType(); + } + llvm_unreachable("The type is not a pointer/array/member to pointer type."); + return T; +} + +/// \param T some qualified type +/// \return true if T is a function pointer +bool isFunctionPtr(const QualType &T) { + return T->isMemberFunctionPointerType() || T->isFunctionPointerType(); +} + +/// We define the types A and B to be locally similar if they are both +/// - pointer, i.e. int* is a pointer to an int +/// - member pointer, i.e. given struct t, int t::* const ptr is a +/// pointer to an int member of struct t. +/// - array / array of unknown bound, i.e. int a[2] and int a[], where the +/// latter is likely a partial 'extern' type. +bool isLocallySimilar(const QualType &A, const QualType &B) { + bool AIsPtr = A->isPointerType(); + bool BIsPtr = B->isPointerType(); + bool AIsArr = A->isArrayType(); + bool BIsArr = B->isArrayType(); + bool AIsMemberPtr = A->isMemberPointerType(); + bool BIsMemberPtr = B->isMemberPointerType(); + + bool LocallySimilar = (AIsMemberPtr && BIsMemberPtr) || + (AIsPtr && BIsPtr && !AIsMemberPtr && !BIsMemberPtr) || + (AIsArr && BIsArr); + + return LocallySimilar; +} + +/// Either types are terminal if either one are not pointer-like types that +/// can be traversed. +bool isTerminal(const QualType &A, const QualType &B) { + bool AIsPtr = A->isPointerType(); + bool BIsPtr = B->isPointerType(); + bool AIsArr = A->isArrayType(); + bool BIsArr = B->isArrayType(); + bool AIsMemberPtr = A->isMemberPointerType(); + bool BIsMemberPtr = B->isMemberPointerType(); + + bool IsTerminal = !(AIsPtr || AIsArr || AIsMemberPtr) || + !(BIsPtr || BIsArr || BIsMemberPtr); + + return IsTerminal; +} + +} // namespace details +} // namespace cppcast +} // namespace clang + +#endif diff --git a/clang-tools-extra/clang-cast/ClangCast.cpp b/clang-tools-extra/clang-cast/ClangCast.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-cast/ClangCast.cpp @@ -0,0 +1,147 @@ +//===--- ClangCast.cpp - clang-cast -----------------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file is the main driver for the clang-cast tool. +/// +//===----------------------------------------------------------------------===// + +#include "Consumer.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" + +using namespace clang::ast_matchers; +using namespace clang; +using namespace cppcast; +using namespace llvm; + +namespace clang { +namespace cli { + +static llvm::cl::OptionCategory ClangCastCategory("clang-cast options"); + +llvm::cl::extrahelp ClangCastCategoryHelp(R"( +clang-cast finds C style casts and attempts to convert them into C++ casts. +C++ casts are preferred since they are safer and more explicit than C style casts. + +For example: + +double* p = nullptr; +(int*) p; + +... is converted into: + +double* p = nullptr; +reinterpret_cast(p); + +When running clang-cast on a compilation unit, the tool will emit useful diagnostics. +The tool can be used to modify a file in-place or into a new file with an added suffix. +Clang-cast will not modify system headers, nor any file with the suffix .c (C files). +)"); + +llvm::cl::opt + PedanticOption("pedantic", llvm::cl::init(false), + llvm::cl::desc("If true, clang-cast will not assume " + "qualification conversion extensions \n" + "(this will lead to more false negatives) " + "that clang adds. This is for projects \n" + "which use the -pedantic or -pedantic-errors " + "flag and have extensions turned off."), + llvm::cl::cat(ClangCastCategory)); + +llvm::cl::list ErrorOptList( + llvm::cl::desc("For each flag set, clang-cast will issue an " + "error for any C style casts that are converted " + "to the following types."), + llvm::cl::values( + clEnumValN(EO_StaticCast, "err-static", "Error on static_cast"), + clEnumValN(EO_ReinterpretCast, "err-reinterpret", + "Error on reinterpret_cast"), + clEnumValN(EO_ConstCast, "err-const", "Error on const_cast"), + clEnumValN(EO_CStyleCast, "err-cstyle", + "Error on non-convertible C style casts"), + clEnumValN(EO_NoOpCast, "err-noop", + "Error on unnecessary C style casts"), + clEnumValN(EO_All, "err-all", "Error for all of the above")), + llvm::cl::cat(ClangCastCategory)); + +llvm::cl::list FixOptList( + llvm::cl::desc("For each flag set, clang-cast will apply a fix for the C " + "style casts that can be converted to the following types. " + "Note that for casts that require two consecutive C++ " + "casts, both flags need to be specified (or --fix-all)."), + llvm::cl::values( + clEnumValN(FO_StaticCast, "fix-static", "Apply fixes to static_cast"), + clEnumValN(FO_ReinterpretCast, "fix-reinterpret", + "Apply fixes to reinterpret_cast"), + clEnumValN(FO_ConstCast, "fix-const", "Apply fixes to const_cast"), + clEnumValN(FO_NoOpCast, "fix-noop", "Apply fixes to no-op cast"), + clEnumValN(FO_All, "fix-all", "Apply fixes for all of the above")), + llvm::cl::cat(ClangCastCategory)); + +llvm::cl::opt + SuffixOption("suffix", + llvm::cl::desc("If suffix is set, changes of " + "a file F will be written to F+suffix."), + llvm::cl::cat(ClangCastCategory)); + +llvm::cl::opt + DontExpandIncludes("no-includes", llvm::cl::init(false), + llvm::cl::desc("Don't modify any include files."), + llvm::cl::cat(ClangCastCategory)); + +llvm::cl::opt + PublishSummary("summary", llvm::cl::init(false), + llvm::cl::desc("If true, clang-cast gives a small summary " + "of the statistics of casts through " + "the entire translation unit."), + llvm::cl::cat(ClangCastCategory)); + +llvm::cl::extrahelp + CommonHelp(clang::tooling::CommonOptionsParser::HelpMessage); + +} // namespace cli + +namespace cppcast { + +class Action : public clang::ASTFrontendAction { +public: + using ASTConsumerPointer = std::unique_ptr; + Action() = default; + ASTConsumerPointer CreateASTConsumer(CompilerInstance &Compiler, + StringRef Filename) override { + if (!Compiler.getLangOpts().CPlusPlus) { + llvm::report_fatal_error("clang-cast is only supported for C++."); + } + return std::make_unique( + cli::SuffixOption, cli::PedanticOption, cli::PublishSummary, + cli::DontExpandIncludes, cli::ErrorOptList, cli::FixOptList); + } +}; + +struct ToolFactory : public clang::tooling::FrontendActionFactory { + std::unique_ptr create() override { + std::unique_ptr Ptr; + Ptr.reset(new Action()); + return Ptr; + } +}; + +} // namespace cppcast + +} // namespace clang + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser op(argc, argv, cli::ClangCastCategory); + tooling::ClangTool Tool(op.getCompilations(), op.getSourcePathList()); + int ExitCode = Tool.run(new clang::cppcast::ToolFactory()); + return ExitCode; +} diff --git a/clang-tools-extra/clang-cast/Consumer.h b/clang-tools-extra/clang-cast/Consumer.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-cast/Consumer.h @@ -0,0 +1,45 @@ +//===--- Consumer.h - clang-cast --------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the ASTConsumer class used in FrontendAction. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CONSUMER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_CONSUMER_H + +#include "Matcher.h" +#include "clang/AST/ASTConsumer.h" + +namespace clang { +namespace cppcast { + +class Consumer : public clang::ASTConsumer { +public: + template + Consumer(Args &&... args) : Handler(std::forward(args)...) { + using namespace clang::ast_matchers; + // TODO: Make this a constant instead of hardcode "cast" + StatementMatcher CStyleCastMatcher = cStyleCastExpr().bind("cast"); + MatchFinder.addMatcher(CStyleCastMatcher, &Handler); + } + + void HandleTranslationUnit(clang::ASTContext &Context) override { + MatchFinder.matchAST(Context); + } + +private: + Matcher Handler; + clang::ast_matchers::MatchFinder MatchFinder; +}; + +} // namespace cppcast +} // namespace clang + +#endif diff --git a/clang-tools-extra/clang-cast/Matcher.h b/clang-tools-extra/clang-cast/Matcher.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-cast/Matcher.h @@ -0,0 +1,402 @@ +//===--- Matcher.h - clang-cast ---------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the ASTMatcher responsible for most of the diagnostics. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_MATCHER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_CAST_MATCHER_H + +#include "Cast.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace cppcast { + +/// Matcher is responsible for creating a CStyleCastOperation to perform +/// semantic analysis on the CStyleCastExpr and decide what potential +/// replacements are suitable for the expression. +/// +/// The matcher decides whether to emit a warning or error or to additionally +/// perform a fix based off of the bit masks created by enums in CastOptions.h. +/// +/// The Matcher emits summaries if the flags are set, containing some summary +/// statistics. +class Matcher : public MatchFinder::MatchCallback { + using RewriterPtr = std::unique_ptr; + /// The FixItRewriter is actually a composable DiagnosticConsumer which + /// contains the original DiagnosticConsumer client, which is a + /// TextDiagnosticPrinter. + /// + /// If we have Rewriter set as the client, FixIt's will be fixed on the file. + /// We want to have granular control over which FixIt's to fix, so we do some + /// light pointer manipulation with the DiagnosticEngine in setClient to + /// switch back and forth between the TextDiagnosticPrinter and FixItRewriter. + RewriterPtr Rewriter; + rewriter::FixItRewriterOptions FixItOptions; + DiagnosticConsumer *OriginalWriter; + + /// Whether or not the Matcher should emit code compliant with -pedantic + bool Pedantic; + + /// Whether or not the Matcher should publish a short blurb upon finishing + /// visiting a translation unit. + bool PublishSummary; + + /// Number of C style casts encountered + unsigned TotalCasts; + + /// Whether or not to diagnose & fix includes (Local modifications only) + bool DontExpandIncludes; + + /// Bitmask to determine which C style cast powers should give an error. + /// For example, if we met a cast that requires static cast, + /// and static cast is 0x1, and our mask is 0x10011, since the last bit is a + /// 1, we will emit the error. + unsigned ErrMask; + + /// Bitmask to determine which C style casts to fix. + unsigned FixMask; + + /// Keeps count of how many C++ cast conversion types there are. + /// Note that for casts that require multiple C++ casts, multiple types are + /// updated here. + std::map Statistics; + + /// List of files that were modified from the tool. + std::set ChangedFiles; + +public: + /// Initializes a new Matcher. + /// \param FilenameSuffix the suffix to add to the filenames + /// \param Pedantic whether to check & emit code compliant with -pedantic + /// \param PublishSummary whether to publish a small summary at the end + /// \param DontExpandIncludes whether to parse cast exprs in headers + /// \param ErrorOptions vector of bitmasks for error/warn types of cast + /// \param FixOptions vector of bitmasks for fixing types of cast + Matcher(const std::string &FilenameSuffix, const bool Pedantic, + bool PublishSummary, bool DontExpandIncludes, + std::vector ErrorOptions, + std::vector FixOptions); + + virtual ~Matcher(); + + /// There are a few cases for the replacement string: + /// 1. No-op cast (remove the cast) + /// 2. Only const cast + /// 3. Static cast + /// 4. Static cast + const cast + /// 5. Reinterpret cast + /// 6. Reinterpret cast + const cast + /// 7. C style cast (keep the cast as is) + /// \param Op operation wrapper + /// \param CXXCastKind kind of non const cast applied to the expr (can be + /// noop) + /// \param ConstCastRequired whether const cast should be added to the + /// existing casts + /// \return A string to replace the C style cast operation char range with. + std::string replaceExpression(const CStyleCastOperation &Op, + CXXCast CXXCastKind, bool ConstCastRequired); + + /// Given an ordered list of casts, use the ASTContext to report necessary + /// changes to the cast expression. Also performs a FixIt on the source code + /// if necessary. + /// \param ModifiableContext the context ptr needs to be modifiable in + /// order to set the diagnostic client + /// \param Op the operation wrapper + /// \return true if a FixIt has been applied + bool reportDiagnostic(ASTContext *ModifiableContext, + const CStyleCastOperation &Op); + + virtual void run(const MatchFinder::MatchResult &Result); + +private: + /// Given an expression, gives the character source range of the expr. + CharSourceRange getRangeForExpression(const Expr *Expression, + const ASTContext &Context); + + /// Sets a client to the diagnostic engine depending on Modify: + /// if Modify is true, then we change the diagnostics client to a + /// FixItRewriter which takes and owns the TextDiagnosticPrinter from the + /// engine. + /// + /// If modify is false, then we can't just destroy the FixItRewriter, as the + /// previous FixIt's would be gone. If we already created a rewriter, then the + /// TextDiagnosticPrinter is owned by it when previously it was owned by the + /// engine. We do nothing if the rewriter hasn't been initialized, but this is + /// extremely precarious as it's juggling ownership between two objects as the + /// FixIt's are applied to subsets of the program. + /// + /// This function was born out of a necessity to work with FixItRewriter's + /// composition-of-consumers design which makes ownership very complicated. + void setClient(clang::ASTContext *Context, bool Modify); + + /// A quick diagnostic warning for when symlinks are being overwritten by an + /// actual file. + void checkForSymlinks(const SourceManager &Manager, const SourceLocation &Loc, + DiagnosticsEngine &DiagEngine); +}; + +// We can't initialize the RewriterPtr until we get an ASTContext. +Matcher::Matcher(const std::string &FilenameSuffix, const bool Pedantic, + bool PublishSummary, bool DontExpandIncludes, + std::vector ErrorOptions, + std::vector FixOptions) + : Rewriter(nullptr), FixItOptions(FilenameSuffix), Pedantic(Pedantic), + PublishSummary(PublishSummary), TotalCasts(0), + DontExpandIncludes(DontExpandIncludes), + /* modify in ctr */ ErrMask(0), + /* modify in ctr */ FixMask(0) { + for (unsigned i = 0; i != ErrorOptions.size(); i++) { + ErrMask |= ErrorOptions[i]; + } + for (unsigned i = 0; i != FixOptions.size(); i++) { + FixMask |= FixOptions[i]; + } +} + +// TODO: Is this okay to do? +Matcher::~Matcher() { + if (PublishSummary) { + for (const auto &Pair : Statistics) { + const auto &CXXCastKind = Pair.first; + const auto &Freq = Pair.second; + if (!Freq) + continue; + llvm::errs() << "The type " << cppCastToString(CXXCastKind) + << " has been issued " << Freq + << " times throughout the translation unit.\n"; + } + if (FixMask) { + llvm::errs() << "The following files were modified:\n"; + for (auto const &File : ChangedFiles) { + llvm::errs() << "\t - " << File << "\n"; + } + } + llvm::errs() << "In total, there are " << TotalCasts + << " C style casts in the translation unit. Multiple C++ " + "casts may be used to convert a single C style cast.\n"; + } +} + +CharSourceRange Matcher::getRangeForExpression(const Expr *Expression, + const ASTContext &Context) { + // Also expand on macros: + auto &Manager = Context.getSourceManager(); + + const ParenExpr *ParenExpression; + while ((ParenExpression = dyn_cast(Expression))) { + Expression = ParenExpression->getSubExpr(); + } + auto ExprStart = Manager.getSpellingLoc(Expression->getBeginLoc()); + auto ExprEnd = Lexer::getLocForEndOfToken( + Manager.getSpellingLoc(Expression->getEndLoc()), /*Offset=*/0, + Context.getSourceManager(), Context.getLangOpts()); + + return CharSourceRange::getCharRange(SourceRange{ExprStart, ExprEnd}); +} + +std::string Matcher::replaceExpression(const CStyleCastOperation &Op, + CXXCast CXXCastKind, + bool ConstCastRequired) { + assert(CXXCastKind != CXXCast::CC_ConstCast && + "Const cast enum cannot be passed in as CXXCastKind"); + QualType CastType = Op.getCastType(); + const Expr &SubExpr = Op.getSubExprAsWritten(); + const ASTContext &Context = Op.getContext(); + const auto &LangOpts = Context.getLangOpts(); + + std::string SubExpressionStr = + Lexer::getSourceText(getRangeForExpression(&SubExpr, Context), + Context.getSourceManager(), Context.getLangOpts()) + .str(); + + switch (CXXCastKind) { + case CXXCast::CC_NoOpCast: { + if (!ConstCastRequired) { + // our replacement is simply the subexpression (no cast needed) + return SubExpressionStr; + } + return cppCastToString(CXXCast::CC_ConstCast) + "<" + + CastType.getAsString(LangOpts) + ">(" + SubExpressionStr + ")"; + } + case CXXCast::CC_StaticCast: + case CXXCast::CC_ReinterpretCast: { + std::string CastTypeStr = cppCastToString(CXXCastKind); + if (!ConstCastRequired) { + return CastTypeStr + "<" + CastType.getAsString(LangOpts) + ">(" + + SubExpressionStr + ")"; + } + QualType IntermediateType = Op.changeQualifiers(); + return cppCastToString(CXXCast::CC_ConstCast) + "<" + + CastType.getAsString(LangOpts) + ">(" + CastTypeStr + "<" + + IntermediateType.getAsString(LangOpts) + ">(" + SubExpressionStr + + "))"; + } + default: { + llvm_unreachable( + "The type of cast passed in cannot produce a replacement."); + return {}; + } + } +} + +bool Matcher::reportDiagnostic(ASTContext *ModifiableContext, + const CStyleCastOperation &Op) { + // No FixItRewriter should be set as consumer until we need to fix anything. + setClient(ModifiableContext, /*Modify=*/false); + const auto &Context = Op.getContext(); + auto &DiagEngine = Context.getDiagnostics(); + + // Set diagnostics to warning by default, and to INFO for edge cases. + const auto &CastExpr = Op.getCStyleCastExpr(); + auto StartingLoc = CastExpr.getExprLoc(); + const auto &ExprRange = getRangeForExpression(&CastExpr, Context); + + CXXCast CXXCastKind = Op.getCastKindFromCStyleCast(); + bool ConstCastRequired = Op.requireConstCast(); + unsigned CurMask = CXXCastKind | (ConstCastRequired * CXXCast::CC_ConstCast); + + // Invalid cast or dynamic (this should never happen, and would be a bug) + if (CurMask & (CXXCast::CC_InvalidCast | CXXCast::CC_DynamicCast)) { + reportWithLoc( + DiagEngine, DiagnosticsEngine::Error, + "clang-casts has encountered an error. Currently does not support " + "the following cast", + StartingLoc, ExprRange); + return false; + } + if (CurMask & CXXCast::CC_CStyleCast) { + reportWithLoc(DiagEngine, + (CurMask & ErrMask) ? DiagnosticsEngine::Error + : DiagnosticsEngine::Remark, + "C style cast cannot be converted " + "into a C++ style cast", + StartingLoc, ExprRange); + return false; + } + + const auto Replacement = + replaceExpression(Op, CXXCastKind, ConstCastRequired); + const auto CastRange = getRangeForExpression(&CastExpr, Context); + + Statistics[CXXCast::CC_ConstCast] += ConstCastRequired; + Statistics[CXXCastKind]++; + + // TODO: the location pointer looks funky when the cast expression is + // in a macro. + auto &Manager = Context.getSourceManager(); + auto Level = (CurMask & ErrMask) ? DiagnosticsEngine::Error + : DiagnosticsEngine::Warning; + if (Manager.isMacroBodyExpansion(StartingLoc)) { + reportWithLoc(DiagEngine, Level, + "C style cast can be replaced by '%0' " + "(won't be fixed in macro)", + StartingLoc, Replacement, ExprRange); + return false; + } + + // Set the diagnostic consumer accordingly. + bool Modify = (CurMask & FixMask) == CurMask; + + // TODO: For clang-tidy, we want to put a logical branch here and not input + // FixIt if we don't want to modify it. If we set the consumer as + // FixItRewriter permanently, emitting an error (from --err-all, for example) + // will cause FixIt to emit "FIXIT: detected an error it cannot fix", which + // clutters up the real diagnostics. + setClient(ModifiableContext, Modify); + const auto FixIt = + clang::FixItHint::CreateReplacement(CastRange, Replacement); + reportWithLoc(DiagEngine, Level, "C style cast can be replaced by '%0'", + StartingLoc, Replacement, FixIt); + return Modify; +} + +void Matcher::run(const MatchFinder::MatchResult &Result) { + ASTContext *Context = Result.Context; + auto &DiagEngine = Context->getDiagnostics(); + + const CStyleCastExpr *CastExpression = + Result.Nodes.getNodeAs("cast"); + assert(CastExpression && "CastExpr cannot be null"); + + const auto &Manager = Context->getSourceManager(); + const auto Loc = CastExpression->getBeginLoc(); + // We skip external headers + if (Manager.isInExternCSystemHeader(Loc) || Manager.isInSystemHeader(Loc) || + (!Manager.isInMainFile(Loc) && DontExpandIncludes)) { + return; + } + + TotalCasts++; + CStyleCastOperation Op(*CastExpression, *Context, Pedantic); + + // If anything is to be modified + if (reportDiagnostic(Context, Op)) { + checkForSymlinks(Manager, Loc, DiagEngine); + if (Rewriter->WriteFixedFiles()) { + llvm::errs() << "Writing the FixItHint was unsuccessful.\n"; + } + } +} + +void Matcher::checkForSymlinks(const SourceManager &Manager, + const SourceLocation &Loc, + DiagnosticsEngine &DiagEngine) { + const FileEntry *Entry = Manager.getFileEntryForID(Manager.getFileID(Loc)); + assert(Entry && "File entry cannot be null"); + const auto RealFilename = Entry->tryGetRealPathName(); + const auto Filename = Entry->getName(); + ChangedFiles.insert(Filename); + + if (RealFilename != Filename) { + report(DiagEngine, DiagnosticsEngine::Warning, + "The symlink at %0 pointing to %1 is changed to a file during " + "modifications.", + Filename, RealFilename); + } +} + +void Matcher::setClient(clang::ASTContext *Context, bool Modify) { + auto &DiagEngine = Context->getDiagnostics(); + if (Modify) { + // First time initializing, means that engine owns TDP. + if (!Rewriter) { + OriginalWriter = DiagEngine.getClient(); + Rewriter = std::make_unique( + DiagEngine, Context->getSourceManager(), Context->getLangOpts(), + &FixItOptions); + } + + // If we modify, we don't want the diagnostics engine to own it. + // If we don't modify, we want the diagnostics engine to own the original + // client. + Context->getDiagnostics().setClient(Rewriter.get(), + /*ShouldOwnClient=*/false); + } else { + // Rewriter not initialized, therefore do nothing, as the TDP client + // is still there and owned by engine. + if (!Rewriter) + return; + // Rewriter is initialized, therefore set TDP (not owned by engine, + // owned by FIR) + Context->getDiagnostics().setClient(OriginalWriter, + /*ShouldOwnClient=*/false); + } +} + +} // namespace cppcast +} // namespace clang + +#endif diff --git a/clang-tools-extra/unittests/CMakeLists.txt b/clang-tools-extra/unittests/CMakeLists.txt --- a/clang-tools-extra/unittests/CMakeLists.txt +++ b/clang-tools-extra/unittests/CMakeLists.txt @@ -6,6 +6,7 @@ endfunction() add_subdirectory(clang-apply-replacements) +add_subdirectory(clang-cast) add_subdirectory(clang-change-namespace) add_subdirectory(clang-doc) add_subdirectory(clang-include-fixer) diff --git a/clang-tools-extra/unittests/clang-cast/CMakeLists.txt b/clang-tools-extra/unittests/clang-cast/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-cast/CMakeLists.txt @@ -0,0 +1,30 @@ +set(LLVM_LINK_COMPONENTS Support) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-cast +) + +add_extra_unittest(ClangCastTests + ClangCastTests.cpp +) + +clang_target_link_libraries(ClangCastTests + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangRewrite + clangSerialization + clangTooling + clangToolingCore +) + +target_link_libraries(ClangCastTests + PRIVATE + clangTooling + clangBasic + clangASTMatchers +) + diff --git a/clang-tools-extra/unittests/clang-cast/ClangCXXCastTestCases.h b/clang-tools-extra/unittests/clang-cast/ClangCXXCastTestCases.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-cast/ClangCXXCastTestCases.h @@ -0,0 +1,339 @@ +//===-- ClangCXXCastTestCases.h - clang-cast --------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains test cases for +/// CStyleCastOperation::getCastKindFromCStyleCast (defined in Cast.h) +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_CAST_CLANGCXXCASTTESTCASES_H +#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_CAST_CLANGCXXCASTTESTCASES_H + +namespace testcases { + +/// NoOp cast types - these are conversions +/// that can be done implicitly. +static const char NoOp[] = R"( +void f() { + (int&&) 0; +} +)"; +static const char ArrayToPointerDecay[] = R"( +void f() { + char x[] = ""; + (char*) x; +} +)"; +// NOTE: Unused, as C style casts cannot +// perform this. It must be done implicitly. +// (int&&) x is a NoOp, and std::move(x) is +// FunctionToPointerDecay. +static const char LValueToRValue[] = R"( +void f() { + int x; + int y = x; +} +)"; + +/// Static cast types +static const char BaseToDerived[] = R"( +class A {}; +class B: public A {}; +class C: public B {}; + +void f() { + A* a = nullptr; + (C*) a; +} +)"; +static const char DerivedToBase[] = R"( +class A {}; +class B: public A {}; +class C: public B {}; + +void f() { + C* c = nullptr; + (A*) c; +} +)"; +// NOTE: Unused, as C style casts cannot +// perform this. It must be done implicitly. +static const char UncheckedDerivedToBase[] = R"( +class A { public: void a(){} }; +class B : public A {}; + +void f() { + B *b; + b->a(); +} +)"; +static const char FunctionToPointerDecay[] = R"( +void g() {} + +void f() { + (void (*)()) g; +} +)"; +static const char NullToPointer[] = R"( +void f() { + (void*) 0; +} +)"; +static const char NullToMemberPointer[] = R"( +struct A {}; +void f() { + (int A::*) 0; +} +)"; +static const char BaseToDerivedMemberPointer[] = R"( +struct A { int m; }; +struct B : A {}; +void f() { + (int B::*) &A::m; +} +)"; +static const char DerivedToBaseMemberPointer[] = R"( +struct A {}; +struct B : A { int m; }; +void f() { + (int A::*) &B::m; +} +)"; +static const char MemberPointerToBoolean[] = R"( +struct A { int m; }; +void f() { + (bool) &A::m; +} +)"; +static const char UserDefinedConversion[] = R"( +struct A { operator int(); }; + +void f() { + (int) A(); +} +)"; +static const char ConstructorConversion[] = R"( +struct A { A(int); }; + +void f() { + (A) 10; +} +)"; +static const char PointerToBoolean[] = R"( +void f() { + (bool) nullptr; +} +)"; +static const char ToVoid[] = R"( +void f() { + (void) 0; +} +)"; +static const char VectorSplat[] = R"( +void f() { + typedef float float4 __attribute__((ext_vector_type(4))); + using type = float4; + (type) 0.0f; +} +)"; +static const char IntegralCast[] = R"( +void f() { + (long long int) 0u; +} +)"; +static const char IntegralToBoolean[] = R"( +void f() { + (bool) 10l; +} +)"; +static const char IntegralToFloating[] = R"( +void f() { + (float) 10l; +} +)"; + +// NOTE: Unused, as fixed points are not yet introduced into C++ standard. +// nor the clang extensions. +static const char FixedPointCast[] = R"()"; +// NOTE: Unused, as fixed points are not yet introduced into C++ standard. +// nor the clang extensions. +static const char FixedPointToIntegral[] = R"()"; +// NOTE: Unused, as fixed points are not yet introduced into C++ standard. +// nor the clang extensions. +static const char IntegralToFixedPoint[] = R"()"; +// NOTE: Unused, as fixed points are not yet introduced into C++ standard. +// nor the clang extensions. +static const char FixedPointToBoolean[] = R"()"; + +static const char FloatingToIntegral[] = R"( +void f() { + (int) 0.0f; +} +)"; +static const char FloatingToBoolean[] = R"( +void f() { + (bool) 0.0f; +} +)"; +// TODO: Ask question +// NOTE: Unused, because AFAIK this is not possible, according to +// the comments it will cast to -1/0 for true/false. +static const char BooleanToSignedIntegral[] = R"( +void f() { + (int) true; +} +)"; +static const char FloatingCast[] = R"( +void f() { + (double) 0.0f; +} +)"; +static const char FloatingRealToComplex[] = R"( +void f() { + (_Complex long double) 1.0; +} +)"; +static const char FloatingComplexToReal[] = R"( +void f() { + _Complex long double c; + (long double) c; +} +)"; +static const char FloatingComplexToBoolean[] = R"( +void f() { + _Complex long double c; + (bool) c; +} +)"; +static const char FloatingComplexCast[] = R"( +void f() { + _Complex long double c; + (_Complex float) c; +} +)"; +static const char FloatingComplexToIntegralComplex[] = R"( +void f() { + _Complex long double c; + (_Complex int) c; +} +)"; +static const char IntegralRealToComplex[] = R"( +void f() { + (_Complex long long) 10l; +} +)"; +static const char IntegralComplexToReal[] = R"( +void f() { + _Complex long long c; + (int) c; +} +)"; +static const char IntegralComplexToBoolean[] = R"( +void f() { + _Complex long long c; + (bool) c; +} +)"; +static const char IntegralComplexCast[] = R"( +void f() { + _Complex long long c; + (_Complex int) c; +} +)"; +static const char IntegralComplexToFloatingComplex[] = R"( +void f() { + _Complex long long c; + (_Complex float) c; +} +)"; +static const char AtomicToNonAtomic[] = R"( +void f() { + _Atomic(int) c; + (int) c; +} +)"; +static const char NonAtomicToAtomic[] = R"( +void f() { + int c; + (_Atomic(int)) c; +} +)"; + +/// Reinterpret cast types +const char BitCast[] = R"( +void f() { + char* x; + (int *) x; +} +)"; +const char LValueBitCast[] = R"( +void f() { + char c; + (bool&) c; +} +)"; +static const char IntegralToPointer[] = R"( +void f() { + (int*) 10l; +} +)"; +// NOTE: Unused, as C style casts cannot +// perform this. It must be done by bit_cast. +const char LValueToRValueBitCast[] = R"( +void f() { + int i; + std::bit_cast(i); +} +)"; +static const char ReinterpretMemberPointer[] = R"( +struct A { int val; }; + +void f() { + int A::* ptr = &A::val; + (bool A::*) ptr; +} +)"; +static const char PointerToIntegral[] = R"( +#include +void f() { + (intptr_t) nullptr; +} +)"; + +/// C-style cast types +static const char Dependent[] = R"( +template +void foo() { + (T) 0; +} +)"; + +namespace edgecases { + +static const char BaseToDerivedPrivateSpecifier[] = R"( +struct A { int i; }; +struct Pad { int i; }; +class B: Pad, A {}; + +B* foo(A *a) { return (B*)(a); } +)"; + +static const char DerivedToBasePrivateSpecifier[] = R"( +struct A { int i; }; +struct Pad { int i; }; +class B: Pad, A {}; + +A* foo(B *b) { return (A*)(b); } +)"; + +} // namespace edgecases + +} // namespace testcases + +#endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_CAST_CLANGCXXCASTTESTCASES_H diff --git a/clang-tools-extra/unittests/clang-cast/ClangCastTests.cpp b/clang-tools-extra/unittests/clang-cast/ClangCastTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-cast/ClangCastTests.cpp @@ -0,0 +1,509 @@ +//===-- ClangCastTests.cpp - clang-cast -------------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains tests of: +/// - Getting the correct non-const cast type from a C style cast +/// - Correctly determing whether an expression is casting +/// away constness +/// - Checking edge cases (function ptrs, private access +/// inheritance, etc) +/// +//===----------------------------------------------------------------------===// + +#include "Cast.h" +#include "ClangCXXCastTestCases.h" +#include "ClangChangeQualifierTestCases.h" +#include "ClangFunctionPtrTestCases.h" +#include "ClangQualifierTestCases.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" +#include +#include +#include + +#define CLANG_CXX_CAST_CHECK(cast_kind, cxx_cast) \ + { \ + auto res = parse(cast_kind); \ + ASSERT_GE(res.first.size(), 1ul); \ + ASSERT_EQ(res.second.size(), 1ul); \ + ASSERT_TRUE(res.first.find(CastKind::CK_##cast_kind) != res.first.end()); \ + ASSERT_EQ(res.second[0], CXXCast::CC_##cxx_cast); \ + } + +#define CLANG_QUAL_CHECK(test_case, req_const, pedantic) \ + { \ + auto res = parse(test_case, pedantic); \ + ASSERT_EQ(res, req_const); \ + } + +#define CLANG_FUNC_PTR_CHECK(test_case, detected) \ + { \ + auto res = parse(test_case); \ + ASSERT_EQ(res, detected); \ + } + +using namespace testcases; +using namespace testcases::constcheck; +using namespace testcases::funcptr; +using namespace testcases::changequal; +using namespace clang; +using namespace clang::tooling; +using namespace clang::ast_matchers; +using namespace clang::cppcast; + +static constexpr auto CastVar = "cast"; +static constexpr auto DeclVar = "varDecl"; + +/// Uses CStyleCastCollector to collect all CXXCast enums obtained +/// and CastKinds encountered. +class ClangCXXCastTest : public ::testing::Test { + using CastKindSet = std::set; + using CXXCastVector = std::vector; + StatementMatcher CStyleCastMatcher; + + struct CStyleCastCollector : MatchFinder::MatchCallback { + std::vector Casts; + std::set CastKinds; + CStyleCastCollector() = default; + + virtual void run(const MatchFinder::MatchResult &Result) override { + ASTContext *Context = Result.Context; + const CStyleCastExpr *Expr = + Result.Nodes.getNodeAs(CastVar); + assert(Expr && Context); + CStyleCastOperation Op(*Expr, *Context, /*Pedantic*/ false); + + const CastExpr *GenericCastExpr = dyn_cast(Expr); + // traverse the expr tree and set current expr + // node to GenericCastExpr. + while (GenericCastExpr) { + CastKinds.insert(GenericCastExpr->getCastKind()); + GenericCastExpr = dyn_cast(GenericCastExpr->getSubExpr()); + } + + Casts.push_back(Op.getCastKindFromCStyleCast()); + } + }; + +protected: + ClangCXXCastTest() : CStyleCastMatcher(cStyleCastExpr().bind(CastVar)) {} + + std::pair parse(const StringRef Code) { + // Parses a single translation unit (from text) + // and returns the CXXCasts in order of traversed. + std::unique_ptr ast(clang::tooling::buildASTFromCode(Code)); + CStyleCastCollector Collector; + MatchFinder Finder; + Finder.addMatcher(CStyleCastMatcher, &Collector); + Finder.matchAST(ast->getASTContext()); + return {Collector.CastKinds, Collector.Casts}; + } +}; + +class ClangQualifierModificationTest : public ::testing::Test { + StatementMatcher CStyleCastMatcher; + + struct QualifierChecker : MatchFinder::MatchCallback { + bool RequireConstCast; + bool Pedantic; + + QualifierChecker(const bool Pedantic) : Pedantic(Pedantic){}; + + virtual void run(const MatchFinder::MatchResult &Result) override { + ASTContext *Context = Result.Context; + const CStyleCastExpr *CastExpression = + Result.Nodes.getNodeAs(CastVar); + assert(CastExpression && Context); + CStyleCastOperation Op(*CastExpression, *Context, Pedantic); + + RequireConstCast = Op.requireConstCast(); + } + }; + +protected: + ClangQualifierModificationTest() + : CStyleCastMatcher(cStyleCastExpr().bind(CastVar)) {} + + bool parse(const StringRef Code, bool Pedantic) { + std::unique_ptr ast(clang::tooling::buildASTFromCode(Code)); + QualifierChecker Checker(Pedantic); + MatchFinder Finder; + Finder.addMatcher(CStyleCastMatcher, &Checker); + Finder.matchAST(ast->getASTContext()); + return Checker.RequireConstCast; + } +}; + +class ClangFunctionPtrDetectionTest : public ::testing::Test { + DeclarationMatcher VarDeclMatcher; + + struct FunctionPtrDetector : MatchFinder::MatchCallback { + bool FoundFunctionPtr; + FunctionPtrDetector() = default; + + virtual void run(const MatchFinder::MatchResult &Result) override { + const VarDecl *DeclExpr = Result.Nodes.getNodeAs(DeclVar); + assert(DeclExpr); + QualType CanonicalDeclType = DeclExpr->getType().getCanonicalType(); + FoundFunctionPtr = details::isFunctionPtr(CanonicalDeclType); + } + }; + +protected: + ClangFunctionPtrDetectionTest() : VarDeclMatcher(varDecl().bind(DeclVar)) {} + + bool parse(const StringRef Code) { + std::unique_ptr ast(clang::tooling::buildASTFromCode(Code)); + FunctionPtrDetector Detector; + MatchFinder Finder; + Finder.addMatcher(VarDeclMatcher, &Detector); + Finder.matchAST(ast->getASTContext()); + return Detector.FoundFunctionPtr; + } +}; + +class ChangeQualifierTest : public ::testing::Test { + StatementMatcher CStyleCastMatcher; + DeclarationMatcher DeclMatcher; + + struct QualifierChanger : MatchFinder::MatchCallback { + QualType ChangedCanonicalType; + bool Pedantic; + QualifierChanger(const bool Pedantic) : Pedantic(Pedantic) {} + + virtual void run(const MatchFinder::MatchResult &Result) override { + ASTContext *Context = Result.Context; + const CStyleCastExpr *CastExpression = + Result.Nodes.getNodeAs(CastVar); + assert(CastExpression && Context); + CStyleCastOperation Op(*CastExpression, *Context, Pedantic); + + ChangedCanonicalType = Op.changeQualifiers().getCanonicalType(); + } + }; + + struct DeclTypeMatcher : MatchFinder::MatchCallback { + QualType FoundCanonicalType; + DeclTypeMatcher() = default; + + virtual void run(const MatchFinder::MatchResult &Result) override { + const VarDecl *DeclExpr = Result.Nodes.getNodeAs(DeclVar); + assert(DeclExpr); + + FoundCanonicalType = DeclExpr->getType().getCanonicalType(); + } + }; + +protected: + ChangeQualifierTest() + : CStyleCastMatcher(cStyleCastExpr().bind(CastVar)), + DeclMatcher(varDecl().bind(DeclVar)) {} + + bool parse(const StringRef CastCode, bool Pedantic) { + std::unique_ptr CastAst( + clang::tooling::buildASTFromCode(CastCode)); + std::unique_ptr TypeAst( + clang::tooling::buildASTFromCode(CastCode)); + QualifierChanger Changer(Pedantic); + DeclTypeMatcher TypeMatcher; + { + MatchFinder Finder; + Finder.addMatcher(CStyleCastMatcher, &Changer); + Finder.matchAST(CastAst->getASTContext()); + } + { + MatchFinder Finder; + Finder.addMatcher(DeclMatcher, &TypeMatcher); + Finder.matchAST(TypeAst->getASTContext()); + } + return Changer.ChangedCanonicalType.getAsString() == + TypeMatcher.FoundCanonicalType.getAsString(); + } +}; + +TEST_F(ClangCXXCastTest, TestNoOpCastTypes) { + CLANG_CXX_CAST_CHECK(NoOp, NoOpCast); + CLANG_CXX_CAST_CHECK(ArrayToPointerDecay, NoOpCast); + // Unchecked: CLANG_CXX_CAST_CHECK(LValueToRValue, ConstCast); +} + +TEST_F(ClangCXXCastTest, TestReinterpretCastTypes) { + CLANG_CXX_CAST_CHECK(BitCast, ReinterpretCast); + CLANG_CXX_CAST_CHECK(LValueBitCast, ReinterpretCast); + CLANG_CXX_CAST_CHECK(IntegralToPointer, ReinterpretCast); + CLANG_CXX_CAST_CHECK(ReinterpretMemberPointer, ReinterpretCast); + CLANG_CXX_CAST_CHECK(PointerToIntegral, ReinterpretCast); +} + +TEST_F(ClangCXXCastTest, TestStaticCastTypes) { + CLANG_CXX_CAST_CHECK(BaseToDerived, StaticCast); + CLANG_CXX_CAST_CHECK(DerivedToBase, StaticCast); + // Unchecked: CLANG_CXX_CAST_CHECK(UncheckedDerivedToBase, StaticCast); + CLANG_CXX_CAST_CHECK(FunctionToPointerDecay, StaticCast); + CLANG_CXX_CAST_CHECK(NullToPointer, StaticCast); + CLANG_CXX_CAST_CHECK(NullToMemberPointer, StaticCast); + CLANG_CXX_CAST_CHECK(BaseToDerivedMemberPointer, StaticCast); + CLANG_CXX_CAST_CHECK(DerivedToBaseMemberPointer, StaticCast); + CLANG_CXX_CAST_CHECK(MemberPointerToBoolean, StaticCast); + CLANG_CXX_CAST_CHECK(UserDefinedConversion, StaticCast); + CLANG_CXX_CAST_CHECK(ConstructorConversion, StaticCast); + CLANG_CXX_CAST_CHECK(PointerToBoolean, StaticCast); + CLANG_CXX_CAST_CHECK(ToVoid, StaticCast); + CLANG_CXX_CAST_CHECK(VectorSplat, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralCast, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralToBoolean, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralToFloating, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingToIntegral, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingToBoolean, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingCast, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingRealToComplex, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingComplexToReal, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingComplexToBoolean, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingComplexCast, StaticCast); + CLANG_CXX_CAST_CHECK(FloatingComplexToIntegralComplex, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralRealToComplex, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralComplexToReal, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralComplexToBoolean, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralComplexCast, StaticCast); + CLANG_CXX_CAST_CHECK(IntegralComplexToFloatingComplex, StaticCast); + CLANG_CXX_CAST_CHECK(AtomicToNonAtomic, StaticCast); + CLANG_CXX_CAST_CHECK(NonAtomicToAtomic, StaticCast); +} + +TEST_F(ClangCXXCastTest, TestCStyleCastTypes) { + CLANG_CXX_CAST_CHECK(Dependent, CStyleCast); +} + +TEST_F(ClangCXXCastTest, TestEdgeCases) { + using namespace edgecases; + { + auto res = parse(DerivedToBasePrivateSpecifier); + ASSERT_GE(res.first.size(), 1ul); + ASSERT_GE(res.second.size(), 1ul); + ASSERT_TRUE(res.first.find(CastKind::CK_DerivedToBase) != res.first.end()); + ASSERT_EQ(res.second[0], CXXCast::CC_CStyleCast); + } + { + auto res = parse(BaseToDerivedPrivateSpecifier); + ASSERT_GE(res.first.size(), 1ul); + ASSERT_GE(res.second.size(), 1ul); + ASSERT_TRUE(res.first.find(CastKind::CK_BaseToDerived) != res.first.end()); + ASSERT_EQ(res.second[0], CXXCast::CC_CStyleCast); + } + CLANG_CXX_CAST_CHECK(Dependent, CStyleCast); +} + +/// These tests mean: +/// Does the C-style cast in require a const_cast? +TEST_F(ClangQualifierModificationTest, TestConstCases) { + CLANG_QUAL_CHECK(QualNoOp, false, false); + // add + // we perform these operations in order to first do a sanity check that + // 1. const cast isn't needed for upcasting + // 2. there will be no segmentation faults before we run the removal tests + CLANG_QUAL_CHECK(QualAddConst, false, false); + CLANG_QUAL_CHECK(QualAddPtrToConst, false, false); + CLANG_QUAL_CHECK(QualAddConstPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDoublePtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDiffLevelPtr, false, false); + CLANG_QUAL_CHECK(QualAddMemberPtrToConst, false, false); + CLANG_QUAL_CHECK(QualAddConstMemberPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDoubleMemberPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDiffLevelMemberPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstRef, false, false); + CLANG_QUAL_CHECK(QualAddConstArr, false, false); + CLANG_QUAL_CHECK(QualAddConstPtrToArr, false, false); + CLANG_QUAL_CHECK(QualAddConstPtrToArrOfConstPtrs, false, false); + CLANG_QUAL_CHECK(QualAddArrPtrConstData, false, false); + CLANG_QUAL_CHECK(QualAddDiffLevelArrPtrConstData, false, false); + CLANG_QUAL_CHECK(QualAddConstMixedPtrTypes, false, false); + CLANG_QUAL_CHECK(QualAddConstUnknownArrPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstUnknownArrPtrToKnownArrPtr, false, false); + + // remove + // we perform these operations in order to check the positive cases, along + // with negative edge cases. + // does not require const cast - implicit + CLANG_QUAL_CHECK(QualRemoveConst, false, false); + // does require const cast, base type downcast + CLANG_QUAL_CHECK(QualRemovePtrToConst, true, false); + // does not - implicit + CLANG_QUAL_CHECK(QualRemoveConstPtr, false, false); + // does - downcast + CLANG_QUAL_CHECK(QualRemoveConstDoublePtr, true, false); + // does not - level truncated + CLANG_QUAL_CHECK(QualRemoveConstDiffLevelPtr, false, false); + + // Same as the above 4 + CLANG_QUAL_CHECK(QualRemoveMemberPtrToConst, true, false); + CLANG_QUAL_CHECK(QualRemoveConstMemberPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveConstDoubleMemberPtr, true, false); + CLANG_QUAL_CHECK(QualRemoveConstDiffLevelMemberPtr, false, false); + + // does - downcast + CLANG_QUAL_CHECK(QualRemoveConstRef, true, false); + // does - downcast + CLANG_QUAL_CHECK(QualRemoveConstArr, true, false); + // does not - implicit + CLANG_QUAL_CHECK(QualRemoveConstPtrToArr, false, false); + // does - downcast + CLANG_QUAL_CHECK(QualRemoveConstPtrToArrOfConstPtrs, true, false); + // does - downcast + CLANG_QUAL_CHECK(QualRemoveArrPtrConstData, true, false); + // does not - level truncated + CLANG_QUAL_CHECK(QualRemoveDiffLevelArrPtrConstData, false, false); + // does - similar types going down + CLANG_QUAL_CHECK(QualRemoveSimilarPtrsBeyondArrConstData, true, false); + // does - All pointer-like types are downcasted + CLANG_QUAL_CHECK(QualRemoveConstMixedPtrTypes, true, false); + // does - Unknown size array is similar to unknown size array + CLANG_QUAL_CHECK(QualRemoveConstUnknownArrPtr, true, false); + // does - Unknown size array is similar to known size array + CLANG_QUAL_CHECK(QualRemoveConstUnknownArrPtrToKnownArrPtr, true, false); + + // Checking for pedantic changes + CLANG_QUAL_CHECK(QualNoOp, false, false); + CLANG_QUAL_CHECK(QualAddConst, false, false); + CLANG_QUAL_CHECK(QualAddPtrToConst, false, false); + CLANG_QUAL_CHECK(QualAddConstPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDoublePtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDiffLevelPtr, false, false); + CLANG_QUAL_CHECK(QualAddMemberPtrToConst, false, false); + CLANG_QUAL_CHECK(QualAddConstMemberPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDoubleMemberPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstDiffLevelMemberPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstRef, false, false); + CLANG_QUAL_CHECK(QualAddConstArr, false, false); + CLANG_QUAL_CHECK(QualAddConstPtrToArr, false, false); + CLANG_QUAL_CHECK(QualAddConstPtrToArrOfConstPtrs, false, false); + CLANG_QUAL_CHECK(QualAddArrPtrConstData, false, false); + CLANG_QUAL_CHECK(QualAddDiffLevelArrPtrConstData, false, false); + CLANG_QUAL_CHECK(QualAddConstMixedPtrTypes, false, false); + CLANG_QUAL_CHECK(QualAddConstUnknownArrPtr, false, false); + CLANG_QUAL_CHECK(QualAddConstUnknownArrPtrToKnownArrPtr, false, false); + + CLANG_QUAL_CHECK(QualRemoveConst, false, false); + CLANG_QUAL_CHECK(QualRemovePtrToConst, true, false); + CLANG_QUAL_CHECK(QualRemoveConstPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveConstDoublePtr, true, false); + CLANG_QUAL_CHECK(QualRemoveConstDiffLevelPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveMemberPtrToConst, true, false); + CLANG_QUAL_CHECK(QualRemoveConstMemberPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveConstDoubleMemberPtr, true, false); + CLANG_QUAL_CHECK(QualRemoveConstDiffLevelMemberPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveConstRef, true, false); + CLANG_QUAL_CHECK(QualRemoveConstArr, true, false); + CLANG_QUAL_CHECK(QualRemoveConstPtrToArr, false, false); + CLANG_QUAL_CHECK(QualRemoveConstPtrToArrOfConstPtrs, true, false); + CLANG_QUAL_CHECK(QualRemoveArrPtrConstData, true, false); + CLANG_QUAL_CHECK(QualRemoveDiffLevelArrPtrConstData, false, false); + CLANG_QUAL_CHECK(QualRemoveSimilarPtrsBeyondArrConstData, true, false); + CLANG_QUAL_CHECK(QualRemoveConstMixedPtrTypes, true, false); + CLANG_QUAL_CHECK(QualRemoveConstUnknownArrPtr, true, false); + CLANG_QUAL_CHECK(QualRemoveConstUnknownArrPtrToKnownArrPtr, true, false); +} + +TEST_F(ClangQualifierModificationTest, TestVolatileCases) { + // add + CLANG_QUAL_CHECK(QualAddVolatile, false, false); + CLANG_QUAL_CHECK(QualAddPtrToVolatile, false, false); + CLANG_QUAL_CHECK(QualAddVolatilePtr, false, false); + CLANG_QUAL_CHECK(QualAddVolatileDoublePtr, false, false); + CLANG_QUAL_CHECK(QualAddVolatileDiffLevelPtr, false, false); + CLANG_QUAL_CHECK(QualAddVolatileRef, false, false); + CLANG_QUAL_CHECK(QualAddVolatileArr, false, false); + CLANG_QUAL_CHECK(QualAddVolatilePtrToArr, false, false); + CLANG_QUAL_CHECK(QualAddVolatilePtrToArrOfVolatilePtrs, false, false); + CLANG_QUAL_CHECK(QualAddArrPtrVolatileData, false, false); + CLANG_QUAL_CHECK(QualAddDiffLevelArrPtrVolatileData, false, false); + CLANG_QUAL_CHECK(QualAddVolatileMixedPtrTypes, false, false); + CLANG_QUAL_CHECK(QualAddVolatileUnknownArrPtr, false, false); + CLANG_QUAL_CHECK(QualAddVolatileUnknownArrPtrToKnownArrPtr, false, false); + + // remove + CLANG_QUAL_CHECK(QualRemoveVolatile, false, false); + CLANG_QUAL_CHECK(QualRemovePtrToVolatile, true, false); + CLANG_QUAL_CHECK(QualRemoveVolatilePtr, false, false); + CLANG_QUAL_CHECK(QualRemoveVolatileDoublePtr, true, false); + CLANG_QUAL_CHECK(QualRemoveVolatileDiffLevelPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveVolatileRef, true, false); + CLANG_QUAL_CHECK(QualRemoveVolatileArr, true, false); + CLANG_QUAL_CHECK(QualRemoveVolatilePtrToArr, false, false); + CLANG_QUAL_CHECK(QualRemoveVolatilePtrToArrOfVolatilePtrs, true, false); + CLANG_QUAL_CHECK(QualRemoveArrPtrVolatileData, true, false); + CLANG_QUAL_CHECK(QualRemoveDiffLevelArrPtrVolatileData, false, false); + CLANG_QUAL_CHECK(QualRemoveSimilarPtrsBeyondArrVolatileData, true, false); + CLANG_QUAL_CHECK(QualRemoveVolatileMixedPtrTypes, true, false); + CLANG_QUAL_CHECK(QualRemoveVolatileUnknownArrPtr, true, false); + CLANG_QUAL_CHECK(QualRemoveVolatileUnknownArrPtrToKnownArrPtr, true, false); +} + +TEST_F(ClangQualifierModificationTest, TestRestrictCases) { + // add + CLANG_QUAL_CHECK(QualAddRestrictPtr, false, false); + CLANG_QUAL_CHECK(QualAddRestrictDoublePtr, false, false); + CLANG_QUAL_CHECK(QualAddRestrictDiffLevelPtr, false, false); + CLANG_QUAL_CHECK(QualAddRestrictArr, false, false); + CLANG_QUAL_CHECK(QualAddRestrictPtrToArr, false, false); + CLANG_QUAL_CHECK(QualAddRestrictPtrToArrOfRestrictPtrs, false, false); + CLANG_QUAL_CHECK(QualAddArrPtrRestrictData, false, false); + CLANG_QUAL_CHECK(QualAddDiffLevelArrPtrRestrictData, false, false); + CLANG_QUAL_CHECK(QualAddRestrictMixedPtrTypes, false, false); + CLANG_QUAL_CHECK(QualAddRestrictUnknownArrPtr, false, false); + CLANG_QUAL_CHECK(QualAddRestrictUnknownArrPtrToKnownArrPtr, false, false); + + // remove + CLANG_QUAL_CHECK(QualRemoveRestrictPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveRestrictDoublePtr, true, false); + CLANG_QUAL_CHECK(QualRemoveRestrictDiffLevelPtr, false, false); + CLANG_QUAL_CHECK(QualRemoveRestrictArr, true, false); + CLANG_QUAL_CHECK(QualRemoveRestrictPtrToArr, false, false); + CLANG_QUAL_CHECK(QualRemoveRestrictPtrToArrOfRestrictPtrs, true, false); + CLANG_QUAL_CHECK(QualRemoveArrPtrRestrictData, true, false); + CLANG_QUAL_CHECK(QualRemoveDiffLevelArrPtrRestrictData, false, false); + CLANG_QUAL_CHECK(QualRemoveSimilarPtrsBeyondArrRestrictData, true, false); + CLANG_QUAL_CHECK(QualRemoveRestrictMixedPtrTypes, true, false); + CLANG_QUAL_CHECK(QualRemoveRestrictUnknownArrPtr, true, false); + CLANG_QUAL_CHECK(QualRemoveRestrictUnknownArrPtrToKnownArrPtr, true, false); +} + +TEST_F(ClangFunctionPtrDetectionTest, TestFuncPtrs) { + CLANG_FUNC_PTR_CHECK(Scalar, false); + CLANG_FUNC_PTR_CHECK(ArrOfFreeFunctionPtr, false); + CLANG_FUNC_PTR_CHECK(NestedFreeFunctionPtr, false); + CLANG_FUNC_PTR_CHECK(FreeFunction, true); + CLANG_FUNC_PTR_CHECK(ArrOfMemberFunction, false); + CLANG_FUNC_PTR_CHECK(NestedMemberFunction, false); + CLANG_FUNC_PTR_CHECK(MemberFunction, true); +} + +TEST_F(ChangeQualifierTest, TestChangeQualifiers) { + ASSERT_TRUE(parse(extension::NoQualifierChange, false)); + ASSERT_TRUE(parse(extension::NoQualifierChangeReinterpret, false)); + ASSERT_TRUE(parse(extension::ChangeNestedPointers, false)); + ASSERT_TRUE(parse(extension::ChangeNestedPointersReinterpret, false)); + ASSERT_TRUE(parse(extension::ChangeNestedPointersUntilArray, false)); + ASSERT_TRUE( + parse(extension::ChangeNestedPointersUntilArrayReinterpret, false)); + ASSERT_TRUE(parse(extension::NoModificationToMixedPtrTypes, false)); + ASSERT_TRUE(parse(extension::DontChangeMemberFuncPtr, false)); + ASSERT_TRUE(parse(extension::ChangeNestedPointersUntilMemberVsNot, false)); + + ASSERT_TRUE(parse(pedantic::NoQualifierChange, true)); + ASSERT_TRUE(parse(pedantic::NoQualifierChangeReinterpret, true)); + ASSERT_TRUE(parse(pedantic::ChangeNestedPointers, true)); + ASSERT_TRUE(parse(pedantic::ChangeNestedPointersReinterpret, true)); + ASSERT_TRUE(parse(pedantic::ChangeNestedPointersUntilArray, true)); + ASSERT_TRUE(parse(pedantic::ChangeNestedPointersUntilArrayReinterpret, true)); + ASSERT_TRUE(parse(pedantic::NoModificationToMixedPtrTypes, true)); + ASSERT_TRUE(parse(pedantic::DontChangeMemberFuncPtr, true)); + ASSERT_TRUE(parse(pedantic::ChangeNestedPointersUntilMemberVsNot, true)); +} diff --git a/clang-tools-extra/unittests/clang-cast/ClangChangeQualifierTestCases.h b/clang-tools-extra/unittests/clang-cast/ClangChangeQualifierTestCases.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-cast/ClangChangeQualifierTestCases.h @@ -0,0 +1,199 @@ +//===-- ClangChangeQualifierTestCases.h - clang-cast ------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains test cases for +/// CStyleCast::changeQualifiers (defined in Cast.h) +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_PROJECT_CLANGCHANGEQUALIFIERTESTCASES_H +#define LLVM_PROJECT_CLANGCHANGEQUALIFIERTESTCASES_H + +/// NOTE: We are not performing an implicit cast conversion, so the types +/// are exactly decltype(p) when we pass it into changeQualifiers(). +namespace testcases { +namespace changequal { +namespace extension { +static const char NoQualifierChange[] = R"( +void f() { + const int * const p {}; + (const int * const) p; + // Expected type + const int * const x {}; +} +)"; +static const char NoQualifierChangeReinterpret[] = R"( +void f() { + const int * const p {}; + (const double * const) p; + // Expected type + const double * const x {}; +} +)"; + +// Change all of these pointer qualifiers down +static const char ChangeNestedPointers[] = R"( +void f() { + const int * const * volatile * __restrict p {}; + (int***) p; + // Expected type + const int* const * volatile * __restrict x {}; +} +)"; +static const char ChangeNestedPointersReinterpret[] = R"( +void f() { + const int * const * volatile * __restrict p {}; + (double****) p; + // Expected type (note how we have 1 more layer of ptr) + double* const * const * volatile * __restrict x {}; +} +)"; + +// Stop the const cast up to an array type +static const char ChangeNestedPointersUntilArray[] = R"( +void f() { + int * const * volatile * __restrict (* const * volatile * __restrict p) [2] {}; + (int*** (***)[2]) p; + // Expected type + int * const * volatile * __restrict (* const * volatile * __restrict x) [2] {}; +} +)"; +static const char ChangeNestedPointersUntilArrayReinterpret[] = R"( +void f() { + int * const * volatile * __restrict (* const * volatile * __restrict p) [2] {}; + (double**** (***)[2]) p; + // Expected type + double ** const * volatile * __restrict (* const * volatile * __restrict x) [2] {}; +} +)"; +static const char NoModificationToMixedPtrTypes[] = R"( +struct t{}; +void f() { + double * __restrict t::* __restrict (t::* __restrict * __restrict a) [2]; + (double * t::* (t::* *) [2]) a; + // Expected type + double * __restrict t::* __restrict (t::* __restrict * __restrict x) [2]; +} +)"; + +// The member function dropped const, but const_cast can't perform this +// operation. It's up to reinterpret_cast to perform this. +static const char DontChangeMemberFuncPtr[] = R"( +struct t{}; +void f() { + void (t::* p)(void) const {}; + (void (t::*)(void)) p; + // Expected type + void (t::* x)(void) {}; +} +)"; + +static const char ChangeNestedPointersUntilMemberVsNot[] = R"( +struct t{}; +void f() { + const int* const t::* const t::* const p {}; + (int** t::*) p; + // Expected type + int** const t::* const x {}; +} +)"; +} // namespace extension + +namespace pedantic { + +static const char NoQualifierChange[] = R"( +void f() { + const int * const p {}; + (const int * const) p; + // Expected type + const int * const x {}; +} +)"; +static const char NoQualifierChangeReinterpret[] = R"( +void f() { + const int * const p {}; + (const double * const) p; + // Expected type + const double * const x {}; +} +)"; + +// Change all of these pointer qualifiers down +static const char ChangeNestedPointers[] = R"( +void f() { + const int * const * volatile * __restrict p {}; + (int***) p; + // Expected type + const int* const * volatile * __restrict x {}; +} +)"; +static const char ChangeNestedPointersReinterpret[] = R"( +void f() { + const int * const * volatile * __restrict p {}; + (double****) p; + // Expected type (note how we have 1 more layer of ptr) + double* const * const * volatile * __restrict x {}; +} +)"; + +// Stop the const cast up to an array type +static const char ChangeNestedPointersUntilArray[] = R"( +void f() { + int * const * volatile * __restrict (* const * volatile * __restrict p) [2] {}; + (int*** (***)[2]) p; + // Expected type + int * const * volatile * __restrict (* const * volatile * __restrict x) [2] {}; +} +)"; +static const char ChangeNestedPointersUntilArrayReinterpret[] = R"( +void f() { + int * const * volatile * __restrict (* const * volatile * __restrict p) [2] {}; + (double**** (***)[2]) p; + // Expected type + double ** const * volatile * __restrict (* const * volatile * __restrict x) [2] {}; +} +)"; +static const char NoModificationToMixedPtrTypes[] = R"( +struct t{}; +void f() { + double * __restrict t::* __restrict (t::* __restrict * __restrict a) [2]; + (double * t::* (t::* *) [2]) a; + // Expected type + double * __restrict t::* __restrict (t::* __restrict * __restrict x) [2]; +} +)"; + +// The member function dropped const, but const_cast can't perform this +// operation. It's up to reinterpret_cast to perform this. +static const char DontChangeMemberFuncPtr[] = R"( +struct t{}; +void f() { + void (t::* p)(void) const {}; + (void (t::*)(void)) p; + // Expected type + void (t::* x)(void) {}; +} +)"; + +static const char ChangeNestedPointersUntilMemberVsNot[] = R"( +struct t{}; +void f() { + const int* const t::* const t::* const p {}; + (int** t::*) p; + // Expected type + const int* const* const t::* const x {}; +} +)"; + +} // namespace pedantic + +} // namespace changequal +} // namespace testcases + +#endif // LLVM_PROJECT_CLANGCHANGEQUALIFIERTESTCASES_H diff --git a/clang-tools-extra/unittests/clang-cast/ClangFunctionPtrTestCases.h b/clang-tools-extra/unittests/clang-cast/ClangFunctionPtrTestCases.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-cast/ClangFunctionPtrTestCases.h @@ -0,0 +1,71 @@ +//===-- ClangFunctionPtrTestCases.h - clang-cast ----------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains test cases for +/// details::isFunctionPtr (defined in CastUtils.h) +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_PROJECT_CLANGFUNCTIONPTRTESTCASES_H +#define LLVM_PROJECT_CLANGFUNCTIONPTRTESTCASES_H + +namespace testcases { +namespace funcptr { + +// Out of all of these tests, we should ideally only identify the cases of +// FreeFunction and MemberFunction. + +static const char Scalar[] = R"( +void f() { + bool a {}; +} +)"; + +static const char ArrOfFreeFunctionPtr[] = R"( +void f() { + void (*a[2])(void){}; +} +)"; + +static const char NestedFreeFunctionPtr[] = R"( +void f() { + void (**a)(void) {}; +} +)"; + +static const char FreeFunction[] = R"( +void f() { + void (*a)(void) {}; +} +)"; + +static const char ArrOfMemberFunction[] = R"( +struct t{}; +void f() { + void (t::* a[2])(void) const {}; +} +)"; + +static const char NestedMemberFunction[] = R"( +struct t{}; +void f() { + void (t::** a)(void) const {}; +} +)"; + +static const char MemberFunction[] = R"( +struct t{}; +void f() { + void (t::* a)(void) const {}; +} +)"; +} // namespace funcptr +} // namespace testcases + +#endif // LLVM_PROJECT_CLANGFUNCTIONPTRTESTCASES_H diff --git a/clang-tools-extra/unittests/clang-cast/ClangQualifierTestCases.h b/clang-tools-extra/unittests/clang-cast/ClangQualifierTestCases.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-cast/ClangQualifierTestCases.h @@ -0,0 +1,603 @@ +//===-- ClangQualifierTestCases.h - clang-cast ------------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains test cases for +/// CStyleCastOperation::requireConstCast (defined in Cast.h) +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_CAST_CLANGQUALIFIERTESTCASES_H +#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANG_CAST_CLANGQUALIFIERTESTCASES_H + +namespace testcases { + +namespace constcheck { +/// No-op +static const char QualNoOp[] = R"( +void f() { + (bool) 0; +} +)"; + +/// Const +/// Adding const doesn't require const_cast +static const char QualAddConst[] = R"( +void f() { + int x = 0; + (const int) x; +} +)"; +static const char QualAddPtrToConst[] = R"( +void f() { + int* x = nullptr; + const int* y = (const int*) x; +} +)"; +static const char QualAddConstPtr[] = R"( +void f() { + int* x = nullptr; + int* const y = (int* const) x; +} +)"; +static const char QualAddConstDoublePtr[] = R"( +void f() { + int** x = nullptr; + int* const * const y = (int* const * const) x; +} +)"; +static const char QualAddConstDiffLevelPtr[] = R"( +void f() { + int*** x = nullptr; + const int** y = (const int**) x; +} +)"; +static const char QualAddMemberPtrToConst[] = R"( +struct t{}; +void f() { + int t::* x = nullptr; + const int t::* y = (const int t::*) x; +} +)"; +static const char QualAddConstMemberPtr[] = R"( +struct t{}; +void f() { + int t::* x = nullptr; + int t::* const y = (int t::* const) x; +} +)"; +static const char QualAddConstDoubleMemberPtr[] = R"( +struct t{}; +void f() { + int t::* t::* x = nullptr; + int t::* const t::* const y = (int t::* const t::* const) x; +} +)"; +static const char QualAddConstDiffLevelMemberPtr[] = R"( +struct t{}; +void f() { + int t::* t::* t::* x = nullptr; + const int t::* t::* y = (const int t::* t::*) x; +} +)"; +static const char QualAddConstRef[] = R"( +void f() { + int x = 1; + int& y = x; + const int& z = (const int&) y; +} +)"; +static const char QualAddConstArr[] = R"( +void f() { + double a[2] {1, 2}; + const double* ca = (const double*) a; +} +)"; +static const char QualAddConstPtrToArr[] = R"( +void f() { + double (*a)[2] {}; + (double(* const)[2]) a; +} +)"; +static const char QualAddConstPtrToArrOfConstPtrs[] = R"( +void f() { + double* (*a)[2] {}; + (double * const (* const)[2]) a; +} +)"; +static const char QualAddArrPtrConstData[] = R"( +void f() { + double (*a)[2] {}; + (const double(*)[2]) a; +} +)"; +static const char QualAddDiffLevelArrPtrConstData[] = R"( +void f() { + double (*a)[2] {}; + (const double* (*)[2]) a; +} +)"; +static const char QualAddConstMixedPtrTypes[] = R"( +struct t {}; +void f() { + double * t::* (t::* *a) [2]; + (const double * const t::* const (t::* const * const) [2]) a; +} +)"; +static const char QualAddConstUnknownArrPtr[] = R"( +void f() { + int* (*x) [] {}; + (const int* const (*)[]) x; +} +)"; +static const char QualAddConstUnknownArrPtrToKnownArrPtr[] = R"( +void f() { + int* (*x) [] {}; + (const int* const (*)[2]) x; +} +)"; + +/// Removing const MIGHT require const_cast +static const char QualRemoveConst[] = R"( +void f() { + const int x = 0; + (int) x; +} +)"; +static const char QualRemovePtrToConst[] = R"( +void f() { + const int* x = nullptr; + int* y = (int*) x; +} +)"; +static const char QualRemoveConstPtr[] = R"( +void f() { + int* const x = nullptr; + int* y = (int*) x; +} +)"; +static const char QualRemoveConstDoublePtr[] = R"( +void f() { + int* const * const x = nullptr; + int** y = (int**) x; +} +)"; +static const char QualRemoveConstDiffLevelPtr[] = R"( +void f() { + const int*** x = nullptr; + int** y = (int**) x; +} +)"; +static const char QualRemoveMemberPtrToConst[] = R"( +struct t{}; +void f() { + const int t::* x = nullptr; + int t::* y = (int t::*) x; +} +)"; +static const char QualRemoveConstMemberPtr[] = R"( +struct t{}; +void f() { + int t::* const x = nullptr; + int t::* y = (int t::*) x; +} +)"; +static const char QualRemoveConstDoubleMemberPtr[] = R"( +struct t{}; +void f() { + int t::* const t::* const x = nullptr; + int t::* t::* y = (int t::* t::*) x; +} +)"; +static const char QualRemoveConstDiffLevelMemberPtr[] = R"( +struct t{}; +void f() { + const int t::* t::* t::* x = nullptr; + int t::* t::* y = (int t::* t::*) x; +} +)"; +static const char QualRemoveConstRef[] = R"( +void f() { + int x = 1; + const int& y = x; + int& z = (int&) y; +} +)"; +static const char QualRemoveConstArr[] = R"( +void f() { + const double a[2] {1, 2}; + double* ca = (double*) a; +} +)"; +static const char QualRemoveConstPtrToArr[] = R"( +void f() { + double (* const a)[2] {}; + (double(*)[2]) a; +} +)"; +static const char QualRemoveConstPtrToArrOfConstPtrs[] = R"( +void f() { + double* const (* const a)[2] {}; + (double* (*)[2]) a; +} +)"; +static const char QualRemoveArrPtrConstData[] = R"( +void f() { + const double (*a)[2] {}; + (double(*)[2]) a; +} +)"; +static const char QualRemoveDiffLevelArrPtrConstData[] = R"( +void f() { + const double* (*a)[2] {}; + (double (*)[2]) a; +} +)"; +static const char QualRemoveSimilarPtrsBeyondArrConstData[] = R"( +void f() { + const double* const (* const a)[2] {}; + (double* const (* const)[2]) a; +} +)"; +static const char QualRemoveConstMixedPtrTypes[] = R"( +struct t {}; +void f() { + const double * const t::* const (t::* const * const a) [2] {}; + (double * t::* (t::* *) [2]) a; +} +)"; +static const char QualRemoveConstUnknownArrPtr[] = R"( +void f() { + const int* const (*x) [] {}; + (int* (*)[]) x; +} +)"; +static const char QualRemoveConstUnknownArrPtrToKnownArrPtr[] = R"( +void f() { + const int* const (*x) [] {}; + (int* (*)[2]) x; +} +)"; + +/// Volatile +/// add +static const char QualAddVolatile[] = R"( +void f() { + int x = 0; + (volatile int) x; +} +)"; +static const char QualAddPtrToVolatile[] = R"( +void f() { + int* x = nullptr; + volatile int* y = (volatile int*) x; +} +)"; +static const char QualAddVolatilePtr[] = R"( +void f() { + int* x = nullptr; + int* volatile y = (int* volatile) x; +} +)"; +static const char QualAddVolatileDoublePtr[] = R"( +void f() { + int** x = nullptr; + int* volatile * volatile y = (int* volatile * volatile) x; +} +)"; +static const char QualAddVolatileDiffLevelPtr[] = R"( +void f() { + int*** x = nullptr; + volatile int** y = (volatile int**) x; +} +)"; +static const char QualAddVolatileRef[] = R"( +void f() { + int x = 1; + int& y = x; + volatile int& z = (volatile int&) y; +} +)"; +static const char QualAddVolatileArr[] = R"( +void f() { + double a[2] {1, 2}; + volatile double* ca = (volatile double*) a; +} +)"; +static const char QualAddVolatilePtrToArr[] = R"( +void f() { + double (*a)[2] {}; + (double(* volatile)[2]) a; +} +)"; +static const char QualAddVolatilePtrToArrOfVolatilePtrs[] = R"( +void f() { + double* (*a)[2] {}; + (double * volatile (* volatile)[2]) a; +} +)"; +static const char QualAddArrPtrVolatileData[] = R"( +void f() { + double (*a)[2] {}; + (volatile double(*)[2]) a; +} +)"; +static const char QualAddDiffLevelArrPtrVolatileData[] = R"( +void f() { + double (*a)[2] {}; + (volatile double* (*)[2]) a; +} +)"; +static const char QualRemoveSimilarPtrsBeyondArrVolatileData[] = R"( +void f() { + volatile double* volatile (* volatile a)[2] {}; + (double* volatile (* volatile)[2]) a; +} +)"; +static const char QualAddVolatileMixedPtrTypes[] = R"( +struct t {}; +void f() { + double * t::* (t::* *a) [2]; + (volatile double * volatile t::* volatile (t::* volatile * volatile) [2]) a; +} +)"; +static const char QualAddVolatileUnknownArrPtr[] = R"( +void f() { + int* (*x) [] {}; + (volatile int* volatile (*)[]) x; +} +)"; +static const char QualAddVolatileUnknownArrPtrToKnownArrPtr[] = R"( +void f() { + int* (*x) [] {}; + (volatile int* volatile (*)[2]) x; +} +)"; + +/// remove +static const char QualRemoveVolatile[] = R"( +void f() { + volatile int x = 0; + (int) x; +} +)"; +static const char QualRemovePtrToVolatile[] = R"( +void f() { + volatile int* x = nullptr; + int* y = (int*) x; +} +)"; +static const char QualRemoveVolatilePtr[] = R"( +void f() { + int* volatile x = nullptr; + int* y = (int*) x; +} +)"; +static const char QualRemoveVolatileDoublePtr[] = R"( +void f() { + int* volatile * volatile x = nullptr; + int** y = (int**) x; +} +)"; +static const char QualRemoveVolatileDiffLevelPtr[] = R"( +void f() { + volatile int*** x = nullptr; + int** y = (int**) x; +} +)"; +static const char QualRemoveVolatileRef[] = R"( +void f() { + int x = 1; + volatile int& y = x; + int& z = (int&) y; +} +)"; +static const char QualRemoveVolatileArr[] = R"( +void f() { + volatile double a[2] {1, 2}; + double* ca = (double*) a; +} +)"; +static const char QualRemoveVolatilePtrToArr[] = R"( +void f() { + double (* volatile a)[2] {}; + (double(*)[2]) a; +} +)"; +static const char QualRemoveVolatilePtrToArrOfVolatilePtrs[] = R"( +void f() { + double* volatile (* volatile a)[2] {}; + (double* (*)[2]) a; +} +)"; +static const char QualRemoveArrPtrVolatileData[] = R"( +void f() { + volatile double (*a)[2] {}; + (double(*)[2]) a; +} +)"; +static const char QualRemoveDiffLevelArrPtrVolatileData[] = R"( +void f() { + volatile double* (*a)[2] {}; + (double (*)[2]) a; +} +)"; +static const char QualRemoveVolatileMixedPtrTypes[] = R"( +struct t {}; +void f() { + volatile double * volatile t::* volatile (t::* volatile * volatile a) [2] {}; + (double * t::* (t::* *) [2]) a; +} +)"; +static const char QualRemoveVolatileUnknownArrPtr[] = R"( +void f() { + volatile int* volatile (*x) [] {}; + (int* (*)[]) x; +} +)"; +static const char QualRemoveVolatileUnknownArrPtrToKnownArrPtr[] = R"( +void f() { + volatile int* volatile (*x) [] {}; + (int* (*)[2]) x; +} +)"; + +/// Restricted +/// add +static const char QualAddRestrictPtr[] = R"( +void f() { + int* x = nullptr; + int* __restrict y = (int* __restrict) x; +} +)"; +static const char QualAddRestrictDoublePtr[] = R"( +void f() { + int** x = nullptr; + int* __restrict * __restrict y = (int* __restrict * __restrict) x; +} +)"; +// Add another layer of pointers to decorate __restrict on +static const char QualAddRestrictDiffLevelPtr[] = R"( +void f() { + int** x = nullptr; + int* __restrict *** y = (int* __restrict ***) x; +} +)"; +// Add another layers of pointers to decorate __restrict on +static const char QualAddRestrictArr[] = R"( +void f() { + double* a[2] {}; + double* __restrict * ca = (double* __restrict *) a; +} +)"; +static const char QualAddRestrictPtrToArr[] = R"( +void f() { + double (*a)[2] {}; + (double(* __restrict)[2]) a; +} +)"; +static const char QualAddRestrictPtrToArrOfRestrictPtrs[] = R"( +void f() { + double* (*a)[2] {}; + (double* __restrict (* __restrict)[2]) a; +} +)"; +// Add another layer of pointers to decorate __restrict on +static const char QualAddArrPtrRestrictData[] = R"( +void f() { + double* (*a)[2] {}; + (double* __restrict (*)[2]) a; +} +)"; +// Add another layer of pointers to decorate __restrict on +static const char QualAddDiffLevelArrPtrRestrictData[] = R"( +void f() { + double (*a)[2] {}; + (double* __restrict *(*)[2]) a; +} +)"; +static const char QualAddRestrictMixedPtrTypes[] = R"( +struct t {}; +void f() { + double * t::* (t::* *a) [2]; + (double * __restrict t::* __restrict (t::* __restrict * __restrict) [2]) a; +} +)"; +static const char QualAddRestrictUnknownArrPtr[] = R"( +void f() { + int* (*x) [] {}; + (int* __restrict (*)[]) x; +} +)"; +static const char QualAddRestrictUnknownArrPtrToKnownArrPtr[] = R"( +void f() { + int* (*x) [] {}; + (int* __restrict (*)[2]) x; +} +)"; + +/// remove +static const char QualRemoveRestrictPtr[] = R"( +void f() { + int* __restrict x = nullptr; + int* y = (int*) x; +} +)"; +static const char QualRemoveRestrictDoublePtr[] = R"( +void f() { + int* __restrict * __restrict x = nullptr; + int** y = (int**) x; +} +)"; +static const char QualRemoveRestrictDiffLevelPtr[] = R"( +void f() { + int* __restrict *** x = nullptr; + int** y = (int**) x; +} +)"; +static const char QualRemoveRestrictArr[] = R"( +void f() { + double* __restrict a[2] {}; + double** ca = (double**) a; +} +)"; +static const char QualRemoveRestrictPtrToArr[] = R"( +void f() { + double (* __restrict a)[2] {}; + (double(*)[2]) a; +} +)"; +static const char QualRemoveRestrictPtrToArrOfRestrictPtrs[] = R"( +void f() { + double* __restrict (* __restrict a)[2] {}; + (double* (*)[2]) a; +} +)"; +static const char QualRemoveArrPtrRestrictData[] = R"( +void f() { + double* __restrict (*a)[2] {}; + (double* (*)[2]) a; +} +)"; +static const char QualRemoveDiffLevelArrPtrRestrictData[] = R"( +void f() { + double* __restrict *(*a)[2] {}; + (double (*)[2]) a; +} +)"; +static const char QualRemoveSimilarPtrsBeyondArrRestrictData[] = R"( +void f() { + double* __restrict * __restrict (* __restrict a)[2] {}; + (double* * __restrict (* __restrict)[2]) a; +} +)"; +static const char QualRemoveRestrictMixedPtrTypes[] = R"( +struct t {}; +void f() { + double * __restrict t::* __restrict (t::* __restrict * __restrict a) [2] {}; + (double * t::* (t::* *) [2]) a; +} +)"; +static const char QualRemoveRestrictUnknownArrPtr[] = R"( +void f() { + int* __restrict (*x) [] {}; + (int* (*)[]) x; +} +)"; +static const char QualRemoveRestrictUnknownArrPtrToKnownArrPtr[] = R"( +void f() { + int* __restrict (*x) [] {}; + (int* (*)[2]) x; +} +)"; + +} // namespace constcheck + +} // namespace testcases + +#endif // LLVM_PROJECT_CLANGQUALIFIERTESTCASES_H