From: Richard Smith Date: Fri, 17 Jan 2014 02:09:33 +0000 (+0000) Subject: Issue a warning if a throwing operator new or operator new[] returns a null X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d5695297241d71a4aa38b32562a9d465027b007b;p=clang Issue a warning if a throwing operator new or operator new[] returns a null pointer, since this invokes undefined behavior. Based on a patch by Artyom Skrobov! Handling of dependent exception specifications and some additional testcases by me. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@199452 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/Type.h b/include/clang/AST/Type.h index 13c4fe908c..3a14c01f43 100644 --- a/include/clang/AST/Type.h +++ b/include/clang/AST/Type.h @@ -3036,15 +3036,10 @@ public: return 0; return reinterpret_cast(arg_type_end())[1]; } - bool isNothrow(const ASTContext &Ctx) const { - ExceptionSpecificationType EST = getExceptionSpecType(); - assert(EST != EST_Unevaluated && EST != EST_Uninstantiated); - if (EST == EST_DynamicNone || EST == EST_BasicNoexcept) - return true; - if (EST != EST_ComputedNoexcept) - return false; - return getNoexceptSpec(Ctx) == NR_Nothrow; - } + /// \brief Determine whether this function type has a non-throwing exception + /// specification. If this depends on template arguments, returns + /// \c ResultIfDependent. + bool isNothrow(const ASTContext &Ctx, bool ResultIfDependent = false) const; bool isVariadic() const { return Variadic; } diff --git a/include/clang/Basic/DiagnosticGroups.td b/include/clang/Basic/DiagnosticGroups.td index bf77259ed4..e1320bea5a 100644 --- a/include/clang/Basic/DiagnosticGroups.td +++ b/include/clang/Basic/DiagnosticGroups.td @@ -226,6 +226,7 @@ def : DiagGroup<"overflow">; def ForwardClassReceiver : DiagGroup<"receiver-forward-class">; def MethodAccess : DiagGroup<"objc-method-access">; def ObjCReceiver : DiagGroup<"receiver-expr">; +def OperatorNewReturnsNull : DiagGroup<"new-returns-null">; def OverlengthStrings : DiagGroup<"overlength-strings">; def OverloadedVirtual : DiagGroup<"overloaded-virtual">; def PrivateExtern : DiagGroup<"private-extern">; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 1de0c0fd11..77807cec83 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -5996,6 +5996,9 @@ def err_operator_new_delete_too_few_parameters : Error< "%0 must have at least one parameter">; def err_operator_new_delete_template_too_few_parameters : Error< "%0 template must have at least two parameters">; +def warn_operator_new_returns_null : Warning< + "%0 should not return a null pointer unless it is declared 'throw()'" + "%select{| or 'noexcept'}1">, InGroup; def err_operator_new_dependent_param_type : Error< "%0 cannot take a dependent type as first parameter; " diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index ff64a994e3..3d4a7454f7 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -1689,6 +1689,31 @@ FunctionProtoType::getNoexceptSpec(const ASTContext &ctx) const { return value.getBoolValue() ? NR_Nothrow : NR_Throw; } +bool FunctionProtoType::isNothrow(const ASTContext &Ctx, + bool ResultIfDependent) const { + ExceptionSpecificationType EST = getExceptionSpecType(); + assert(EST != EST_Unevaluated && EST != EST_Uninstantiated); + if (EST == EST_DynamicNone || EST == EST_BasicNoexcept) + return true; + + if (EST == EST_Dynamic && ResultIfDependent == true) { + // A dynamic exception specification is throwing unless every exception + // type is an (unexpanded) pack expansion type. + for (unsigned I = 0, N = NumExceptions; I != N; ++I) + if (!getExceptionType(I)->getAs()) + return false; + return ResultIfDependent; + } + + if (EST != EST_ComputedNoexcept) + return false; + + NoexceptResult NR = getNoexceptSpec(Ctx); + if (NR == NR_Dependent) + return ResultIfDependent; + return NR == NR_Nothrow; +} + bool FunctionProtoType::isTemplateVariadic() const { for (unsigned ArgIdx = getNumArgs(); ArgIdx; --ArgIdx) if (isa(getArgType(ArgIdx - 1))) diff --git a/lib/Sema/SemaStmt.cpp b/lib/Sema/SemaStmt.cpp index e4fe9c324c..50a9d8111d 100644 --- a/lib/Sema/SemaStmt.cpp +++ b/lib/Sema/SemaStmt.cpp @@ -2936,6 +2936,28 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) { } CheckReturnStackAddr(RetValExp, FnRetType, ReturnLoc); + + // C++11 [basic.stc.dynamic.allocation]p4: + // If an allocation function declared with a non-throwing + // exception-specification fails to allocate storage, it shall return + // a null pointer. Any other allocation function that fails to allocate + // storage shall indicate failure only by throwing an exception [...] + if (const FunctionDecl *FD = getCurFunctionDecl()) { + OverloadedOperatorKind Op = FD->getOverloadedOperator(); + if (Op == OO_New || Op == OO_Array_New) { + const FunctionProtoType *Proto + = FD->getType()->castAs(); + bool ReturnValueNonNull; + + if (!Proto->isNothrow(Context, /*ResultIfDependent*/true) && + !RetValExp->isValueDependent() && + RetValExp->EvaluateAsBooleanCondition(ReturnValueNonNull, + Context) && + !ReturnValueNonNull) + Diag(ReturnLoc, diag::warn_operator_new_returns_null) + << FD << getLangOpts().CPlusPlus11; + } + } } if (RetValExp) { diff --git a/test/SemaCXX/new-delete.cpp b/test/SemaCXX/new-delete.cpp index 7facd10ca5..59e2574fa1 100644 --- a/test/SemaCXX/new-delete.cpp +++ b/test/SemaCXX/new-delete.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s -triple=i686-pc-linux-gnu +// RUN: %clang_cc1 -fsyntax-only -verify %s -triple=i686-pc-linux-gnu -Wno-new-returns-null #include diff --git a/test/SemaCXX/new-null.cpp b/test/SemaCXX/new-null.cpp new file mode 100644 index 0000000000..b2be0c5b38 --- /dev/null +++ b/test/SemaCXX/new-null.cpp @@ -0,0 +1,63 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++98 +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 + +typedef __SIZE_TYPE__ size_t; + +#if __cplusplus >= 201103L +struct S1 { + void *operator new(size_t n) { + return nullptr; // expected-warning {{'operator new' should not return a null pointer unless it is declared 'throw()' or 'noexcept'}} + } + void *operator new[](size_t n) noexcept { + return __null; + } +}; +#endif + +struct S2 { + static size_t x; + void *operator new(size_t n) throw() { + return 0; + } + void *operator new[](size_t n) { + return (void*)0; +#if __cplusplus >= 201103L + // expected-warning@-2 {{'operator new[]' should not return a null pointer unless it is declared 'throw()' or 'noexcept'}} +#else + // expected-warning-re@-4 {{'operator new[]' should not return a null pointer unless it is declared 'throw()'{{$}}}} +#endif + } +}; + +struct S3 { + void *operator new(size_t n) { + return 1-1; +#if __cplusplus >= 201103L + // expected-error@-2 {{cannot initialize return object of type 'void *' with an rvalue of type 'int'}} +#else + // expected-warning@-4 {{expression which evaluates to zero treated as a null pointer constant of type 'void *'}} + // expected-warning@-5 {{'operator new' should not return a null pointer unless it is declared 'throw()'}} +#endif + } + void *operator new[](size_t n) { + return (void*)(1-1); // expected-warning {{'operator new[]' should not return a null pointer unless it is declared 'throw()'}} + } +}; + +#if __cplusplus >= 201103L +template struct S4 { + void *operator new(size_t n) noexcept(B) { + return 0; // expected-warning {{'operator new' should not return a null pointer}} + } +}; +template struct S4; +template struct S4; // expected-note {{in instantiation of}} +#endif + +template struct S5 { // expected-warning 0-1{{extension}} + void *operator new(size_t n) throw(T...) { + return 0; // expected-warning {{'operator new' should not return a null pointer}} + } +}; +template struct S5<>; +template struct S5; // expected-note {{in instantiation of}} diff --git a/test/SemaCXX/warn-new-overaligned.cpp b/test/SemaCXX/warn-new-overaligned.cpp index 42a4e3523a..710973ccb3 100644 --- a/test/SemaCXX/warn-new-overaligned.cpp +++ b/test/SemaCXX/warn-new-overaligned.cpp @@ -38,7 +38,7 @@ struct Test { } __attribute__((aligned(256))); void* operator new(unsigned long) { - return 0; + return 0; // expected-warning {{'operator new' should not return a null pointer unless it is declared 'throw()'}} } SeparateCacheLines high_contention_data[10]; @@ -59,7 +59,7 @@ struct Test { } __attribute__((aligned(256))); void* operator new[](unsigned long) { - return 0; + return 0; // expected-warning {{'operator new[]' should not return a null pointer unless it is declared 'throw()'}} } SeparateCacheLines high_contention_data[10];