diff --git a/clang/include/clang/Basic/DiagnosticKinds.def b/clang/include/clang/Basic/DiagnosticKinds.def index 726a6bd9eac3..0067061b014d 100644 --- a/clang/include/clang/Basic/DiagnosticKinds.def +++ b/clang/include/clang/Basic/DiagnosticKinds.def @@ -1150,6 +1150,10 @@ DIAG(err_bad_dynamic_cast_not_ptr, ERROR, "'%0' is not a pointer") DIAG(err_bad_dynamic_cast_not_polymorphic, ERROR, "'%0' is not polymorphic") +DIAG(err_ambiguous_base_to_derived_cast, ERROR, + "ambiguous static_cast from base '%0' to derived '%1':%2") +DIAG(err_static_downcast_via_virtual, ERROR, + "cannot cast '%0' to '%1' via virtual base '%2'") DIAG(err_invalid_use_of_function_type, ERROR, "a function type is not allowed here") diff --git a/clang/lib/Sema/Sema.h b/clang/lib/Sema/Sema.h index 385b174c83c6..3645a7f69fdb 100644 --- a/clang/lib/Sema/Sema.h +++ b/clang/lib/Sema/Sema.h @@ -750,12 +750,27 @@ private: void CheckDynamicCast(Expr *&SrcExpr, QualType DestType, const SourceRange &OpRange, const SourceRange &DestRange); + + enum TryStaticCastResult { + TSC_NotApplicable, ///< The cast method is not applicable. + TSC_Success, ///< The cast method is appropriate and successful. + TSC_Failed ///< The cast method is appropriate, but failed. A + ///< diagnostic has been emitted. + }; + bool CastsAwayConstness(QualType SrcType, QualType DestType); - bool IsStaticReferenceDowncast(Expr *SrcExpr, QualType DestType); - bool IsStaticPointerDowncast(QualType SrcType, QualType DestType); - bool IsStaticDowncast(QualType SrcType, QualType DestType); - ImplicitConversionSequence TryDirectInitialization(Expr *SrcExpr, - QualType DestType); + TryStaticCastResult TryStaticReferenceDowncast(Expr *SrcExpr, + QualType DestType, + const SourceRange &OpRange); + TryStaticCastResult TryStaticPointerDowncast(QualType SrcType, + QualType DestType, + const SourceRange &OpRange); + TryStaticCastResult TryStaticDowncast(QualType SrcType, QualType DestType, + const SourceRange &OpRange, + QualType OrigSrcType, + QualType OrigDestType); + TryStaticCastResult TryStaticImplicitCast(Expr *SrcExpr, QualType DestType, + const SourceRange &OpRange); public: //// ActOnCXXThis - Parse 'this' pointer. diff --git a/clang/lib/Sema/SemaNamedCast.cpp b/clang/lib/Sema/SemaNamedCast.cpp index bf559d620bba..246df63373f8 100644 --- a/clang/lib/Sema/SemaNamedCast.cpp +++ b/clang/lib/Sema/SemaNamedCast.cpp @@ -17,6 +17,7 @@ #include "clang/AST/ASTContext.h" #include "clang/Basic/Diagnostic.h" #include "llvm/ADT/SmallVector.h" +#include using namespace clang; /// ActOnCXXNamedCast - Parse {dynamic,static,reinterpret,const}_cast's. @@ -357,23 +358,14 @@ Sema::CheckStaticCast(Expr *&SrcExpr, QualType DestType, // C++ 5.2.9p5, reference downcast. // See the function for details. // DR 427 specifies that this is to be applied before paragraph 2. - if (IsStaticReferenceDowncast(SrcExpr, DestType)) { + if (TryStaticReferenceDowncast(SrcExpr, DestType, OpRange) + > TSC_NotApplicable) { return; } // C++ 5.2.9p2: An expression e can be explicitly converted to a type T // [...] if the declaration "T t(e);" is well-formed, [...]. - ImplicitConversionSequence ICS = TryDirectInitialization(SrcExpr, DestType); - if (ICS.ConversionKind != ImplicitConversionSequence::BadConversion) { - assert(ICS.ConversionKind != ImplicitConversionSequence::EllipsisConversion - && "Direct initialization cannot result in ellipsis conversion"); - // UserDefinedConversionSequence has a StandardConversionSequence as a - // prefix. Accessing Standard is therefore safe. - // FIXME: Of course, this is definitely not enough. - if(ICS.Standard.First != ICK_Identity) { - DefaultFunctionArrayConversion(SrcExpr); - } - // FIXME: Test the details, such as accessible base. + if (TryStaticImplicitCast(SrcExpr, DestType, OpRange) > TSC_NotApplicable) { return; } @@ -408,7 +400,8 @@ Sema::CheckStaticCast(Expr *&SrcExpr, QualType DestType, // Reverse pointer upcast. C++ 4.10p3 specifies pointer upcast. // C++ 5.2.9p8 additionally disallows a cast path through virtual inheritance. - if (IsStaticPointerDowncast(SrcType, DestType)) { + if (TryStaticPointerDowncast(SrcType, DestType, OpRange) + > TSC_NotApplicable) { return; } @@ -448,8 +441,9 @@ Sema::CheckStaticCast(Expr *&SrcExpr, QualType DestType, } /// Tests whether a conversion according to C++ 5.2.9p5 is valid. -bool -Sema::IsStaticReferenceDowncast(Expr *SrcExpr, QualType DestType) +Sema::TryStaticCastResult +Sema::TryStaticReferenceDowncast(Expr *SrcExpr, QualType DestType, + const SourceRange &OpRange) { // C++ 5.2.9p5: An lvalue of type "cv1 B", where B is a class type, can be // cast to type "reference to cv2 D", where D is a class derived from B, @@ -461,21 +455,23 @@ Sema::IsStaticReferenceDowncast(Expr *SrcExpr, QualType DestType) // conversion as well. if (SrcExpr->isLvalue(Context) != Expr::LV_Valid) { - return false; + return TSC_NotApplicable; } const ReferenceType *DestReference = DestType->getAsReferenceType(); if (!DestReference) { - return false; + return TSC_NotApplicable; } QualType DestPointee = DestReference->getPointeeType(); - return IsStaticDowncast(SrcExpr->getType(), DestPointee); + return TryStaticDowncast(SrcExpr->getType(), DestPointee, OpRange, + SrcExpr->getType(), DestType); } /// Tests whether a conversion according to C++ 5.2.9p8 is valid. -bool -Sema::IsStaticPointerDowncast(QualType SrcType, QualType DestType) +Sema::TryStaticCastResult +Sema::TryStaticPointerDowncast(QualType SrcType, QualType DestType, + const SourceRange &OpRange) { // C++ 5.2.9p8: An rvalue of type "pointer to cv1 B", where B is a class // type, can be converted to an rvalue of type "pointer to cv2 D", where D @@ -487,80 +483,131 @@ Sema::IsStaticPointerDowncast(QualType SrcType, QualType DestType) const PointerType *SrcPointer = SrcType->getAsPointerType(); if (!SrcPointer) { - return false; + return TSC_NotApplicable; } const PointerType *DestPointer = DestType->getAsPointerType(); if (!DestPointer) { - return false; + return TSC_NotApplicable; } - return IsStaticDowncast(SrcPointer->getPointeeType(), - DestPointer->getPointeeType()); + return TryStaticDowncast(SrcPointer->getPointeeType(), + DestPointer->getPointeeType(), + OpRange, SrcType, DestType); } -/// IsStaticDowncast - Common functionality of IsStaticReferenceDowncast and -/// IsStaticPointerDowncast. Tests whether a static downcast from SrcType to +/// TryStaticDowncast - Common functionality of TryStaticReferenceDowncast and +/// TryStaticPointerDowncast. Tests whether a static downcast from SrcType to /// DestType, both of which must be canonical, is possible and allowed. -bool -Sema::IsStaticDowncast(QualType SrcType, QualType DestType) +Sema::TryStaticCastResult +Sema::TryStaticDowncast(QualType SrcType, QualType DestType, + const SourceRange &OpRange, QualType OrigSrcType, + QualType OrigDestType) { // Downcast can only happen in class hierarchies, so we need classes. if (!DestType->isRecordType() || !SrcType->isRecordType()) { - return false; - } - - // Comparing cv is cheaper, so do it first. - if (!DestType.isAtLeastAsQualifiedAs(SrcType)) { - return false; + return TSC_NotApplicable; } BasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/false, /*DetectVirtual=*/true); if (!IsDerivedFrom(DestType, SrcType, Paths)) { - return false; + return TSC_NotApplicable; + } + + // Target type does derive from source type. Now we're serious. If an error + // appears now, it's not ignored. + // This may not be entirely in line with the standard. Take for example: + // struct A {}; + // struct B : virtual A { + // B(A&); + // }; + // + // void f() + // { + // (void)static_cast(*((A*)0)); + // } + // As far as the standard is concerned, p5 does not apply (A is virtual), so + // p2 should be used instead - "const B& t(*((A*)0));" is perfectly valid. + // However, both GCC and Comeau reject this example, and accepting it would + // mean more complex code if we're to preserve the nice error message. + // FIXME: Being 100% compliant here would be nice to have. + + // Must preserve cv, as always. + if (!DestType.isAtLeastAsQualifiedAs(SrcType)) { + Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_const_away, + "static_cast", OrigDestType.getAsString(), OrigSrcType.getAsString(), + OpRange); + return TSC_Failed; } if (Paths.isAmbiguous(SrcType.getUnqualifiedType())) { - return false; + // This code is analoguous to that in CheckDerivedToBaseConversion, except + // that it builds the paths in reverse order. + // To sum up: record all paths to the base and build a nice string from + // them. Use it to spice up the error message. + Paths.clear(); + Paths.setRecordingPaths(true); + IsDerivedFrom(DestType, SrcType, Paths); + std::string PathDisplayStr; + std::set DisplayedPaths; + for (BasePaths::paths_iterator Path = Paths.begin(); + Path != Paths.end(); ++Path) { + if (DisplayedPaths.insert(Path->back().SubobjectNumber).second) { + // We haven't displayed a path to this particular base + // class subobject yet. + PathDisplayStr += "\n "; + for (BasePath::const_reverse_iterator Element = Path->rbegin(); + Element != Path->rend(); ++Element) + PathDisplayStr += Element->Base->getType().getAsString() + " -> "; + PathDisplayStr += DestType.getAsString(); + } + } + + Diag(OpRange.getBegin(), diag::err_ambiguous_base_to_derived_cast, + SrcType.getUnqualifiedType().getAsString(), + DestType.getUnqualifiedType().getAsString(), + PathDisplayStr, OpRange); + return TSC_Failed; } if (Paths.getDetectedVirtual() != 0) { - return false; + QualType VirtualBase(Paths.getDetectedVirtual(), 0); + Diag(OpRange.getBegin(), diag::err_static_downcast_via_virtual, + OrigSrcType.getAsString(), OrigDestType.getAsString(), + VirtualBase.getAsString(), OpRange); + return TSC_Failed; } // FIXME: Test accessibility. - return true; + return TSC_Success; } -/// TryDirectInitialization - Attempt to direct-initialize a value of the -/// given type (DestType) from the given expression (SrcExpr), as one would -/// do when creating an object with new with parameters. This function returns -/// an implicit conversion sequence that can be used to perform the -/// initialization. -/// This routine is very similar to TryCopyInitialization; the differences -/// between the two (C++ 8.5p12 and C++ 8.5p14) are: -/// 1) In direct-initialization, all constructors of the target type are -/// considered, including those marked as explicit. -/// 2) In direct-initialization, overload resolution is performed over the -/// constructors of the target type. In copy-initialization, overload -/// resolution is performed over all conversion functions that result in -/// the target type. This can lead to different functions used. -ImplicitConversionSequence -Sema::TryDirectInitialization(Expr *SrcExpr, QualType DestType) +/// TryStaticImplicitCast - Tests whether a conversion according to C++ 5.2.9p2 +/// is valid: +/// +/// An expression e can be explicitly converted to a type T using a +/// @c static_cast if the declaration "T t(e);" is well-formed [...]. +Sema::TryStaticCastResult +Sema::TryStaticImplicitCast(Expr *SrcExpr, QualType DestType, + const SourceRange &OpRange) { - if (!DestType->isRecordType()) { - // For non-class types, copy and direct initialization are identical. - // C++ 8.5p11 - // FIXME: Those parts should be in a common function, actually. - return TryCopyInitialization(SrcExpr, DestType); + if (DestType->isReferenceType()) { + // At this point of CheckStaticCast, if the destination is a reference, + // this has to work. There is no other way that works. + return CheckReferenceInit(SrcExpr, DestType) ? TSC_Failed : TSC_Success; + } + if (DestType->isRecordType()) { + // FIXME: Use an implementation of C++ [over.match.ctor] for this. + return TSC_NotApplicable; } - // FIXME: Not enough support for the rest yet, actually. - ImplicitConversionSequence ICS; - ICS.ConversionKind = ImplicitConversionSequence::BadConversion; - return ICS; + // FIXME: To get a proper error from invalid conversions here, we need to + // reimplement more of this. + ImplicitConversionSequence ICS = TryImplicitConversion(SrcExpr, DestType); + return ICS.ConversionKind == ImplicitConversionSequence::BadConversion ? + TSC_NotApplicable : TSC_Success; } /// CheckDynamicCast - Check that a dynamic_cast\(SrcExpr) is valid. diff --git a/clang/test/SemaCXX/static-cast.cpp b/clang/test/SemaCXX/static-cast.cpp index 6874ccbdd5cb..464ea79aed97 100644 --- a/clang/test/SemaCXX/static-cast.cpp +++ b/clang/test/SemaCXX/static-cast.cpp @@ -37,7 +37,6 @@ void t_529_2() (void)static_cast((int*)0); (void)static_cast((const int*)0); (void)static_cast((B*)0); - // TryCopyInitialization doesn't handle references yet. (void)static_cast(*((B*)0)); (void)static_cast((C1*)0); (void)static_cast(*((C1*)0)); @@ -53,7 +52,7 @@ void t_529_2() //(void)static_cast((H*)0); // {{static_cast from 'struct H *' to 'struct A *' is not allowed}} (void)static_cast((int*)0); // expected-error {{static_cast from 'int *' to 'int' is not allowed}} (void)static_cast((B**)0); // expected-error {{static_cast from 'struct B **' to 'struct A **' is not allowed}} - (void)static_cast(i); // expected-error {{static_cast from 'int' to 'char &' is not allowed}} + (void)static_cast(i); // expected-error {{non-const reference to type 'char' cannot be initialized with a value of type 'int'}} } // Anything to void @@ -73,19 +72,19 @@ void t_529_5_8() // Bad code below - (void)static_cast((A*)0); // expected-error {{static_cast from 'struct A *' to 'struct C1 *' is not allowed}} - (void)static_cast(*((A*)0)); // expected-error {{static_cast from 'struct A' to 'struct C1 &' is not allowed}} - (void)static_cast((A*)0); // expected-error {{static_cast from 'struct A *' to 'struct D *' is not allowed}} - (void)static_cast(*((A*)0)); // expected-error {{static_cast from 'struct A' to 'struct D &' is not allowed}} - (void)static_cast((const A*)0); // expected-error {{static_cast from 'struct A const *' to 'struct B *' is not allowed}} - (void)static_cast(*((const A*)0)); // expected-error {{static_cast from 'struct A const' to 'struct B &' is not allowed}} + (void)static_cast((A*)0); // expected-error {{cannot cast 'struct A *' to 'struct C1 *' via virtual base 'struct B'}} + (void)static_cast(*((A*)0)); // expected-error {{cannot cast 'struct A' to 'struct C1 &' via virtual base 'struct B'}} + (void)static_cast((A*)0); // expected-error {{cannot cast 'struct A *' to 'struct D *' via virtual base 'struct B'}} + (void)static_cast(*((A*)0)); // expected-error {{cannot cast 'struct A' to 'struct D &' via virtual base 'struct B'}} + (void)static_cast((const A*)0); // expected-error {{static_cast from 'struct A const *' to 'struct B *' casts away constness}} + (void)static_cast(*((const A*)0)); // expected-error {{static_cast from 'struct A const' to 'struct B &' casts away constness}} // Accessibility is not yet tested //(void)static_cast((A*)0); // {{static_cast from 'struct A *' to 'struct E *' is not allowed}} //(void)static_cast(*((A*)0)); // {{static_cast from 'struct A' to 'struct E &' is not allowed}} - (void)static_cast((A*)0); // expected-error {{static_cast from 'struct A *' to 'struct H *' is not allowed}} - (void)static_cast(*((A*)0)); // expected-error {{static_cast from 'struct A' to 'struct H &' is not allowed}} + (void)static_cast((A*)0); // expected-error {{ambiguous static_cast from base 'struct A' to derived 'struct H':\n struct A -> struct B -> struct G1 -> struct H\n struct A -> struct B -> struct G2 -> struct H}} + (void)static_cast(*((A*)0)); // expected-error {{ambiguous static_cast from base 'struct A' to derived 'struct H':\n struct A -> struct B -> struct G1 -> struct H\n struct A -> struct B -> struct G2 -> struct H}} (void)static_cast((B*)0); // expected-error {{static_cast from 'struct B *' to 'struct E *' is not allowed}} - (void)static_cast(*((B*)0)); // expected-error {{static_cast from 'struct B' to 'struct E &' is not allowed}} + (void)static_cast(*((B*)0)); // expected-error {{non-const reference to type 'struct E' cannot be initialized with a value of type 'struct B'}} // TODO: Test inaccessible base in context where it's accessible, i.e. // member function and friend.