From: Douglas Gregor Date: Fri, 26 Feb 2010 05:06:18 +0000 (+0000) Subject: Implement semantic analysis for C++ [expr.new]p18-20, which describe X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6d90870fe821a9b1a7822d3e28032042d03e5680;p=clang Implement semantic analysis for C++ [expr.new]p18-20, which describe how we find the operator delete that matches withe operator new we found in a C++ new-expression. This will also need CodeGen support. On a happy note, we're now a "nans" away from building tramp3d-v4. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@97209 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index da4713340e..52fbbcbb00 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -2016,6 +2016,9 @@ def err_new_array_nonconst : Error< "only the first dimension of an allocated array may have dynamic size">; def err_new_paren_array_nonconst : Error< "when type is in parentheses, array cannot have dynamic size">; +def err_placement_new_non_placement_delete : Error< + "'new' expression with placement arguments refers to non-placement " + "'operator delete'">; def err_array_size_not_integral : Error< "array size expression must have integral or enumerated type, not %0">; def err_default_init_const : Error< diff --git a/lib/AST/DeclCXX.cpp b/lib/AST/DeclCXX.cpp index 8d2a379f81..73cba1d61e 100644 --- a/lib/AST/DeclCXX.cpp +++ b/lib/AST/DeclCXX.cpp @@ -573,7 +573,13 @@ bool CXXMethodDecl::isUsualDeallocationFunction() const { if (getOverloadedOperator() != OO_Delete && getOverloadedOperator() != OO_Array_Delete) return false; - + + // C++ [basic.stc.dynamic.deallocation]p2: + // A template instance is never a usual deallocation function, + // regardless of its signature. + if (getPrimaryTemplate()) + return false; + // C++ [basic.stc.dynamic.deallocation]p2: // If a class T has a member deallocation function named operator delete // with exactly one parameter, then that function is a usual (non-placement) diff --git a/lib/Sema/SemaExprCXX.cpp b/lib/Sema/SemaExprCXX.cpp index df9442cde5..daa4b5a98e 100644 --- a/lib/Sema/SemaExprCXX.cpp +++ b/lib/Sema/SemaExprCXX.cpp @@ -780,6 +780,12 @@ Sema::BuildCXXNew(SourceLocation StartLoc, bool UseGlobal, ConsArgs = (Expr **)ConvertedConstructorArgs.take(); } + // Mark the new and delete operators as referenced. + if (OperatorNew) + MarkDeclarationReferenced(StartLoc, OperatorNew); + if (OperatorDelete) + MarkDeclarationReferenced(StartLoc, OperatorDelete); + // FIXME: Also check that the destructor is accessible. (C++ 5.3.4p16) PlacementArgs.release(); @@ -819,6 +825,20 @@ bool Sema::CheckAllocatedType(QualType AllocType, SourceLocation Loc, return false; } +/// \brief Determine whether the given function is a non-placement +/// deallocation function. +static bool isNonPlacementDeallocationFunction(FunctionDecl *FD) { + if (FD->isInvalidDecl()) + return false; + + if (CXXMethodDecl *Method = dyn_cast(FD)) + return Method->isUsualDeallocationFunction(); + + return ((FD->getOverloadedOperator() == OO_Delete || + FD->getOverloadedOperator() == OO_Array_Delete) && + FD->getNumParams() == 1); +} + /// FindAllocationFunctions - Finds the overloads of operator new and delete /// that are appropriate for the allocation. bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, @@ -835,7 +855,6 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // operator new. // 3) The first argument is always size_t. Append the arguments from the // placement form. - // FIXME: Also find the appropriate delete operator. llvm::SmallVector AllocArgs(1 + NumPlaceArgs); // We don't care about the actual value of this argument. @@ -848,12 +867,20 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, AllocArgs[0] = &Size; std::copy(PlaceArgs, PlaceArgs + NumPlaceArgs, AllocArgs.begin() + 1); + // C++ [expr.new]p8: + // If the allocated type is a non-array type, the allocation + // function’s name is operator new and the deallocation function’s + // name is operator delete. If the allocated type is an array + // type, the allocation function’s name is operator new[] and the + // deallocation function’s name is operator delete[]. DeclarationName NewName = Context.DeclarationNames.getCXXOperatorName( IsArray ? OO_Array_New : OO_New); + DeclarationName DeleteName = Context.DeclarationNames.getCXXOperatorName( + IsArray ? OO_Array_Delete : OO_Delete); + if (AllocType->isRecordType() && !UseGlobal) { CXXRecordDecl *Record = cast(AllocType->getAs()->getDecl()); - // FIXME: We fail to find inherited overloads. if (FindAllocationOverload(StartLoc, Range, NewName, &AllocArgs[0], AllocArgs.size(), Record, /*AllowMissing=*/true, OperatorNew)) @@ -874,6 +901,110 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, if (NumPlaceArgs > 0) std::copy(&AllocArgs[1], AllocArgs.end(), PlaceArgs); + // C++ [expr.new]p19: + // + // If the new-expression begins with a unary :: operator, the + // deallocation function’s name is looked up in the global + // scope. Otherwise, if the allocated type is a class type T or an + // array thereof, the deallocation function’s name is looked up in + // the scope of T. If this lookup fails to find the name, or if + // the allocated type is not a class type or array thereof, the + // deallocation function’s name is looked up in the global scope. + LookupResult FoundDelete(*this, DeleteName, StartLoc, LookupOrdinaryName); + if (AllocType->isRecordType() && !UseGlobal) { + CXXRecordDecl *RD + = cast(AllocType->getAs()->getDecl()); + LookupQualifiedName(FoundDelete, RD); + } + + if (FoundDelete.empty()) { + DeclareGlobalNewDelete(); + LookupQualifiedName(FoundDelete, Context.getTranslationUnitDecl()); + } + + FoundDelete.suppressDiagnostics(); + llvm::SmallVector Matches; + if (NumPlaceArgs > 1) { + // C++ [expr.new]p20: + // A declaration of a placement deallocation function matches the + // declaration of a placement allocation function if it has the + // same number of parameters and, after parameter transformations + // (8.3.5), all parameter types except the first are + // identical. [...] + // + // To perform this comparison, we compute the function type that + // the deallocation function should have, and use that type both + // for template argument deduction and for comparison purposes. + QualType ExpectedFunctionType; + { + const FunctionProtoType *Proto + = OperatorNew->getType()->getAs(); + llvm::SmallVector ArgTypes; + ArgTypes.push_back(Context.VoidPtrTy); + for (unsigned I = 1, N = Proto->getNumArgs(); I < N; ++I) + ArgTypes.push_back(Proto->getArgType(I)); + + ExpectedFunctionType + = Context.getFunctionType(Context.VoidTy, ArgTypes.data(), + ArgTypes.size(), + Proto->isVariadic(), + 0, false, false, 0, 0, false, CC_Default); + } + + for (LookupResult::iterator D = FoundDelete.begin(), + DEnd = FoundDelete.end(); + D != DEnd; ++D) { + FunctionDecl *Fn = 0; + if (FunctionTemplateDecl *FnTmpl + = dyn_cast((*D)->getUnderlyingDecl())) { + // Perform template argument deduction to try to match the + // expected function type. + TemplateDeductionInfo Info(Context, StartLoc); + if (DeduceTemplateArguments(FnTmpl, 0, ExpectedFunctionType, Fn, Info)) + continue; + } else + Fn = cast((*D)->getUnderlyingDecl()); + + if (Context.hasSameType(Fn->getType(), ExpectedFunctionType)) + Matches.push_back(Fn); + } + } else { + // C++ [expr.new]p20: + // [...] Any non-placement deallocation function matches a + // non-placement allocation function. [...] + for (LookupResult::iterator D = FoundDelete.begin(), + DEnd = FoundDelete.end(); + D != DEnd; ++D) { + if (FunctionDecl *Fn = dyn_cast((*D)->getUnderlyingDecl())) + if (isNonPlacementDeallocationFunction(Fn)) + Matches.push_back(*D); + } + } + + // C++ [expr.new]p20: + // [...] If the lookup finds a single matching deallocation + // function, that function will be called; otherwise, no + // deallocation function will be called. + if (Matches.size() == 1) { + // FIXME: Drops access, using-declaration info! + OperatorDelete = cast(Matches[0]->getUnderlyingDecl()); + + // C++0x [expr.new]p20: + // If the lookup finds the two-parameter form of a usual + // deallocation function (3.7.4.2) and that function, considered + // as a placement deallocation function, would have been + // selected as a match for the allocation function, the program + // is ill-formed. + if (NumPlaceArgs && getLangOptions().CPlusPlus0x && + isNonPlacementDeallocationFunction(OperatorDelete)) { + Diag(StartLoc, diag::err_placement_new_non_placement_delete) + << SourceRange(PlaceArgs[0]->getLocStart(), + PlaceArgs[NumPlaceArgs - 1]->getLocEnd()); + Diag(OperatorDelete->getLocation(), diag::note_previous_decl) + << DeleteName; + } + } + return false; } diff --git a/test/CXX/expr/expr.unary/expr.new/p19.cpp b/test/CXX/expr/expr.unary/expr.new/p19.cpp new file mode 100644 index 0000000000..6134779f1f --- /dev/null +++ b/test/CXX/expr/expr.unary/expr.new/p19.cpp @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +typedef __SIZE_TYPE__ size_t; + +// Operator delete template for placement new with global lookup +template +struct X0 { + X0(); + + static void* operator new(size_t) { + return I; // expected-error{{cannot initialize}} + } + + static void operator delete(void*) { + int *ip = I; // expected-error{{cannot initialize}} + } +}; + +void test_X0() { + // Using the global operator new suppresses the search for a + // operator delete in the class. + ::new X0<2>; + + new X0<3>; // expected-note 2{{instantiation}} +} + +// Operator delete template for placement new[] with global lookup +template +struct X1 { + X1(); + + static void* operator new[](size_t) { + return I; // expected-error{{cannot initialize}} + } + + static void operator delete[](void*) { + int *ip = I; // expected-error{{cannot initialize}} + } +}; + +void test_X1() { + // Using the global operator new suppresses the search for a + // operator delete in the class. + ::new X1<2> [17]; + + new X1<3> [17]; // expected-note 2{{instantiation}} +} diff --git a/test/CXX/expr/expr.unary/expr.new/p20-0x.cpp b/test/CXX/expr/expr.unary/expr.new/p20-0x.cpp new file mode 100644 index 0000000000..c188e1e25e --- /dev/null +++ b/test/CXX/expr/expr.unary/expr.new/p20-0x.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++0x %s +typedef __SIZE_TYPE__ size_t; + +struct S { + // Placement allocation function: + static void* operator new(size_t, size_t); + // Usual (non-placement) deallocation function: + static void operator delete(void*, size_t); // expected-note{{declared here}} +}; + +void testS() { + S* p = new (0) S; // expected-error{{'new' expression with placement arguments refers to non-placement 'operator delete'}} +} diff --git a/test/CXX/expr/expr.unary/expr.new/p20.cpp b/test/CXX/expr/expr.unary/expr.new/p20.cpp new file mode 100644 index 0000000000..71e584e775 --- /dev/null +++ b/test/CXX/expr/expr.unary/expr.new/p20.cpp @@ -0,0 +1,141 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +typedef __SIZE_TYPE__ size_t; + +// Overloaded operator delete with two arguments +template +struct X0 { + X0(); + static void* operator new(size_t); + static void operator delete(void*, size_t) { + int *ip = I; // expected-error{{cannot initialize}} + } +}; + +void test_X0() { + new X0<1>; // expected-note{{instantiation}} +} + +// Overloaded operator delete with one argument +template +struct X1 { + X1(); + + static void* operator new(size_t); + static void operator delete(void*) { + int *ip = I; // expected-error{{cannot initialize}} + } +}; + +void test_X1() { + new X1<1>; // expected-note{{instantiation}} +} + +// Overloaded operator delete for placement new +template +struct X2 { + X2(); + + static void* operator new(size_t, double, double); + static void* operator new(size_t, int, int); + + static void operator delete(void*, const int, int) { + int *ip = I; // expected-error{{cannot initialize}} + } + + static void operator delete(void*, double, double); +}; + +void test_X2() { + new (0, 0) X2<1>; // expected-note{{instantiation}} +} + +// Operator delete template for placement new +struct X3 { + X3(); + + static void* operator new(size_t, double, double); + + template + static void operator delete(void*, T x, T) { + double *dp = &x; + int *ip = &x; // expected-error{{cannot initialize}} + } +}; + +void test_X3() { + new (0, 0) X3; // expected-note{{instantiation}} +} + +// Operator delete template for placement new in global scope. +struct X4 { + X4(); + static void* operator new(size_t, double, double); +}; + +template +void operator delete(void*, T x, T) { + double *dp = &x; + int *ip = &x; // expected-error{{cannot initialize}} +} + +void test_X4() { + new (0, 0) X4; // expected-note{{instantiation}} +} + +// Useless operator delete hides global operator delete template. +struct X5 { + X5(); + static void* operator new(size_t, double, double); + void operator delete(void*, double*, double*); +}; + +void test_X5() { + new (0, 0) X5; // okay, we found X5::operator delete but didn't pick it +} + +// Operator delete template for placement new +template +struct X6 { + X6(); + + static void* operator new(size_t) { + return I; // expected-error{{cannot initialize}} + } + + static void operator delete(void*) { + int *ip = I; // expected-error{{cannot initialize}} + } +}; + +void test_X6() { + new X6<3>; // expected-note 2{{instantiation}} +} + +void *operator new(size_t, double, double, double); + +template +void operator delete(void*, T x, T, T) { + double *dp = &x; + int *ip = &x; // expected-error{{cannot initialize}} +} +void test_int_new() { + new (1.0, 1.0, 1.0) int; // expected-note{{instantiation}} +} + +// We don't need an operator delete if the type has a trivial +// constructor, since we know that constructor cannot throw. +// FIXME: Is this within the standard? Seems fishy, but both EDG+GCC do it. +#if 0 +template +struct X7 { + static void* operator new(size_t); + static void operator delete(void*, size_t) { + int *ip = I; // okay, since it isn't instantiated. + } +}; + +void test_X7() { + new X7<1>; +} +#endif +