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.

llvm-svn: 97209
This commit is contained in:
Douglas Gregor 2010-02-26 05:06:18 +00:00
parent 044ada532c
commit 6642ca217e
6 changed files with 343 additions and 3 deletions

View File

@ -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<

View File

@ -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)

View File

@ -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<CXXMethodDecl>(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<Expr*, 8> 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
// functions name is operator new and the deallocation functions
// name is operator delete. If the allocated type is an array
// type, the allocation functions name is operator new[] and the
// deallocation functions 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<CXXRecordDecl>(AllocType->getAs<RecordType>()->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 functions name is looked up in the global
// scope. Otherwise, if the allocated type is a class type T or an
// array thereof, the deallocation functions 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 functions name is looked up in the global scope.
LookupResult FoundDelete(*this, DeleteName, StartLoc, LookupOrdinaryName);
if (AllocType->isRecordType() && !UseGlobal) {
CXXRecordDecl *RD
= cast<CXXRecordDecl>(AllocType->getAs<RecordType>()->getDecl());
LookupQualifiedName(FoundDelete, RD);
}
if (FoundDelete.empty()) {
DeclareGlobalNewDelete();
LookupQualifiedName(FoundDelete, Context.getTranslationUnitDecl());
}
FoundDelete.suppressDiagnostics();
llvm::SmallVector<NamedDecl *, 4> 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<FunctionProtoType>();
llvm::SmallVector<QualType, 4> 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<FunctionTemplateDecl>((*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<FunctionDecl>((*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<FunctionDecl>((*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<FunctionDecl>(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;
}

View File

@ -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<int I>
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<int I>
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}}
}

View File

@ -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'}}
}

View File

@ -0,0 +1,141 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
typedef __SIZE_TYPE__ size_t;
// Overloaded operator delete with two arguments
template<int I>
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<int I>
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<int I>
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<typename T>
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<typename T>
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<int I>
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<typename T>
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<int I>
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