diff options
-rw-r--r-- | clang/include/clang/Basic/DiagnosticSemaKinds.td | 7 | ||||
-rw-r--r-- | clang/include/clang/Sema/ExternalSemaSource.h | 4 | ||||
-rw-r--r-- | clang/include/clang/Sema/MultiplexExternalSemaSource.h | 4 | ||||
-rw-r--r-- | clang/include/clang/Sema/Sema.h | 18 | ||||
-rw-r--r-- | clang/include/clang/Serialization/ASTBitCodes.h | 3 | ||||
-rw-r--r-- | clang/include/clang/Serialization/ASTReader.h | 7 | ||||
-rw-r--r-- | clang/lib/Sema/MultiplexExternalSemaSource.cpp | 10 | ||||
-rw-r--r-- | clang/lib/Sema/Sema.cpp | 19 | ||||
-rw-r--r-- | clang/lib/Sema/SemaExprCXX.cpp | 267 | ||||
-rw-r--r-- | clang/lib/Serialization/ASTReader.cpp | 27 | ||||
-rw-r--r-- | clang/lib/Serialization/ASTWriter.cpp | 19 | ||||
-rw-r--r-- | clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp | 8 | ||||
-rw-r--r-- | clang/test/Analysis/MismatchedDeallocator-checker-test.mm | 11 | ||||
-rw-r--r-- | clang/test/Analysis/MismatchedDeallocator-path-notes.cpp | 179 | ||||
-rw-r--r-- | clang/test/CodeGenCXX/new.cpp | 4 | ||||
-rw-r--r-- | clang/test/SemaCXX/delete-mismatch.h | 15 | ||||
-rw-r--r-- | clang/test/SemaCXX/delete.cpp | 131 |
17 files changed, 692 insertions, 41 deletions
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5facc5440dd..02be44639b6 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5516,7 +5516,12 @@ def err_delete_explicit_conversion : Error< "conversion function">; def note_delete_conversion : Note<"conversion to pointer type %0">; def warn_delete_array_type : Warning< - "'delete' applied to a pointer-to-array type %0 treated as delete[]">; + "'delete' applied to a pointer-to-array type %0 treated as 'delete[]'">; +def warn_mismatched_delete_new : Warning< + "'delete%select{|[]}0' applied to a pointer that was allocated with " + "'new%select{[]|}0'; did you mean 'delete%select{[]|}0'?">, + InGroup<DiagGroup<"mismatched-new-delete">>; +def note_allocated_here : Note<"allocated with 'new%select{[]|}0' here">; def err_no_suitable_delete_member_function_found : Error< "no suitable member %0 in %1">; def err_ambiguous_suitable_delete_member_function_found : Error< diff --git a/clang/include/clang/Sema/ExternalSemaSource.h b/clang/include/clang/Sema/ExternalSemaSource.h index de4672f1afd..ef3d2dbff14 100644 --- a/clang/include/clang/Sema/ExternalSemaSource.h +++ b/clang/include/clang/Sema/ExternalSemaSource.h @@ -27,6 +27,7 @@ template <class T, unsigned n> class SmallSetVector; namespace clang { class CXXConstructorDecl; +class CXXDeleteExpr; class CXXRecordDecl; class DeclaratorDecl; class LookupResult; @@ -79,6 +80,9 @@ public: virtual void ReadUndefinedButUsed( llvm::DenseMap<NamedDecl*, SourceLocation> &Undefined); + virtual void ReadMismatchingDeleteExpressions(llvm::MapVector< + FieldDecl *, llvm::SmallVector<std::pair<SourceLocation, bool>, 4>> &); + /// \brief Do last resort, unqualified lookup on a LookupResult that /// Sema cannot find. /// diff --git a/clang/include/clang/Sema/MultiplexExternalSemaSource.h b/clang/include/clang/Sema/MultiplexExternalSemaSource.h index 16646ba4adf..af7083a82de 100644 --- a/clang/include/clang/Sema/MultiplexExternalSemaSource.h +++ b/clang/include/clang/Sema/MultiplexExternalSemaSource.h @@ -230,6 +230,10 @@ public: void ReadUndefinedButUsed( llvm::DenseMap<NamedDecl*, SourceLocation> &Undefined) override; + void ReadMismatchingDeleteExpressions(llvm::MapVector< + FieldDecl *, llvm::SmallVector<std::pair<SourceLocation, bool>, 4>> & + Exprs) override; + /// \brief Do last resort, unqualified lookup on a LookupResult that /// Sema cannot find. /// diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 41bc8554d52..42d62656788 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -78,6 +78,7 @@ namespace clang { typedef SmallVector<CXXBaseSpecifier*, 4> CXXCastPath; class CXXConstructorDecl; class CXXConversionDecl; + class CXXDeleteExpr; class CXXDestructorDecl; class CXXFieldCollector; class CXXMemberCallExpr; @@ -404,6 +405,15 @@ public: llvm::SmallSetVector<const TypedefNameDecl *, 4> UnusedLocalTypedefNameCandidates; + /// \brief Delete-expressions to be analyzed at the end of translation unit + /// + /// This list contains class members, and locations of delete-expressions + /// that could not be proven as to whether they mismatch with new-expression + /// used in initializer of the field. + typedef std::pair<SourceLocation, bool> DeleteExprLoc; + typedef llvm::SmallVector<DeleteExprLoc, 4> DeleteLocs; + llvm::MapVector<FieldDecl *, DeleteLocs> DeleteExprs; + typedef llvm::SmallPtrSet<const CXXRecordDecl*, 8> RecordDeclSetTy; /// PureVirtualClassDiagSet - a set of class declarations which we have @@ -888,6 +898,11 @@ public: void getUndefinedButUsed( SmallVectorImpl<std::pair<NamedDecl *, SourceLocation> > &Undefined); + /// Retrieves list of suspicious delete-expressions that will be checked at + /// the end of translation unit. + const llvm::MapVector<FieldDecl *, DeleteLocs> & + getMismatchingDeleteExpressions() const; + typedef std::pair<ObjCMethodList, ObjCMethodList> GlobalMethods; typedef llvm::DenseMap<Selector, GlobalMethods> GlobalMethodPool; @@ -8663,6 +8678,9 @@ private: /// attempts to add itself into the container void CheckObjCCircularContainer(ObjCMessageExpr *Message); + void AnalyzeDeleteExprMismatch(const CXXDeleteExpr *DE); + void AnalyzeDeleteExprMismatch(FieldDecl *Field, SourceLocation DeleteLoc, + bool DeleteWasArrayForm); public: /// \brief Register a magic integral constant to be used as a type tag. void RegisterTypeTagForDatatype(const IdentifierInfo *ArgumentKind, diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 511e73b96f9..e0f01c8a354 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -561,6 +561,9 @@ namespace clang { /// \brief Record code for the table of offsets to CXXCtorInitializers /// lists. CXX_CTOR_INITIALIZERS_OFFSETS = 53, + + /// \brief Delete expressions that will be analyzed later. + DELETE_EXPRS_TO_ANALYZE = 54 }; /// \brief Record types used within a source manager block. diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h index 722f7a86faa..e6759e36fa8 100644 --- a/clang/include/clang/Serialization/ASTReader.h +++ b/clang/include/clang/Serialization/ASTReader.h @@ -760,6 +760,9 @@ private: /// SourceLocation of a matching ODR-use. SmallVector<uint64_t, 8> UndefinedButUsed; + /// \brief Delete expressions to analyze at the end of translation unit. + SmallVector<uint64_t, 8> DelayedDeleteExprs; + // \brief A list of late parsed template function data. SmallVector<uint64_t, 1> LateParsedTemplates; @@ -1740,6 +1743,10 @@ public: void ReadUndefinedButUsed( llvm::DenseMap<NamedDecl *, SourceLocation> &Undefined) override; + void ReadMismatchingDeleteExpressions(llvm::MapVector< + FieldDecl *, llvm::SmallVector<std::pair<SourceLocation, bool>, 4>> & + Exprs); + void ReadTentativeDefinitions( SmallVectorImpl<VarDecl *> &TentativeDefs) override; diff --git a/clang/lib/Sema/MultiplexExternalSemaSource.cpp b/clang/lib/Sema/MultiplexExternalSemaSource.cpp index 51a12746fdd..9ecb5a7fefb 100644 --- a/clang/lib/Sema/MultiplexExternalSemaSource.cpp +++ b/clang/lib/Sema/MultiplexExternalSemaSource.cpp @@ -212,7 +212,15 @@ void MultiplexExternalSemaSource::ReadUndefinedButUsed( for(size_t i = 0; i < Sources.size(); ++i) Sources[i]->ReadUndefinedButUsed(Undefined); } - + +void MultiplexExternalSemaSource::ReadMismatchingDeleteExpressions( + llvm::MapVector<FieldDecl *, + llvm::SmallVector<std::pair<SourceLocation, bool>, 4>> & + Exprs) { + for (auto &Source : Sources) + Source->ReadMismatchingDeleteExpressions(Exprs); +} + bool MultiplexExternalSemaSource::LookupUnqualified(LookupResult &R, Scope *S){ for(size_t i = 0; i < Sources.size(); ++i) Sources[i]->LookupUnqualified(R, S); diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index e9619b369c1..50edc426135 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -861,6 +861,17 @@ void Sema::ActOnEndOfTranslationUnit() { } } + if (!Diags.isIgnored(diag::warn_mismatched_delete_new, SourceLocation())) { + if (ExternalSource) + ExternalSource->ReadMismatchingDeleteExpressions(DeleteExprs); + for (const auto &DeletedFieldInfo : DeleteExprs) { + for (const auto &DeleteExprLoc : DeletedFieldInfo.second) { + AnalyzeDeleteExprMismatch(DeletedFieldInfo.first, DeleteExprLoc.first, + DeleteExprLoc.second); + } + } + } + // Check we've noticed that we're no longer parsing the initializer for every // variable. If we miss cases, then at best we have a performance issue and // at worst a rejects-valid bug. @@ -1220,6 +1231,9 @@ void ExternalSemaSource::ReadUndefinedButUsed( llvm::DenseMap<NamedDecl *, SourceLocation> &Undefined) { } +void ExternalSemaSource::ReadMismatchingDeleteExpressions(llvm::MapVector< + FieldDecl *, llvm::SmallVector<std::pair<SourceLocation, bool>, 4>> &) {} + void PrettyDeclStackTraceEntry::print(raw_ostream &OS) const { SourceLocation Loc = this->Loc; if (!Loc.isValid() && TheDecl) Loc = TheDecl->getLocation(); @@ -1468,3 +1482,8 @@ CapturedRegionScopeInfo *Sema::getCurCapturedRegion() { return dyn_cast<CapturedRegionScopeInfo>(FunctionScopes.back()); } + +const llvm::MapVector<FieldDecl *, Sema::DeleteLocs> & +Sema::getMismatchingDeleteExpressions() const { + return DeleteExprs; +} diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index b853eaefbe4..e3633bd9037 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2339,6 +2339,261 @@ bool Sema::FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD, return false; } +namespace { +/// \brief Checks whether delete-expression, and new-expression used for +/// initializing deletee have the same array form. +class MismatchingNewDeleteDetector { +public: + enum MismatchResult { + /// Indicates that there is no mismatch or a mismatch cannot be proven. + NoMismatch, + /// Indicates that variable is initialized with mismatching form of \a new. + VarInitMismatches, + /// Indicates that member is initialized with mismatching form of \a new. + MemberInitMismatches, + /// Indicates that 1 or more constructors' definitions could not been + /// analyzed, and they will be checked again at the end of translation unit. + AnalyzeLater + }; + + /// \param EndOfTU True, if this is the final analysis at the end of + /// translation unit. False, if this is the initial analysis at the point + /// delete-expression was encountered. + explicit MismatchingNewDeleteDetector(bool EndOfTU) + : IsArrayForm(false), Field(nullptr), EndOfTU(EndOfTU), + HasUndefinedConstructors(false) {} + + /// \brief Checks whether pointee of a delete-expression is initialized with + /// matching form of new-expression. + /// + /// If return value is \c VarInitMismatches or \c MemberInitMismatches at the + /// point where delete-expression is encountered, then a warning will be + /// issued immediately. If return value is \c AnalyzeLater at the point where + /// delete-expression is seen, then member will be analyzed at the end of + /// translation unit. \c AnalyzeLater is returned iff at least one constructor + /// couldn't be analyzed. If at least one constructor initializes the member + /// with matching type of new, the return value is \c NoMismatch. + MismatchResult analyzeDeleteExpr(const CXXDeleteExpr *DE); + /// \brief Analyzes a class member. + /// \param Field Class member to analyze. + /// \param DeleteWasArrayForm Array form-ness of the delete-expression used + /// for deleting the \p Field. + MismatchResult analyzeField(FieldDecl *Field, bool DeleteWasArrayForm); + /// List of mismatching new-expressions used for initialization of the pointee + llvm::SmallVector<const CXXNewExpr *, 4> NewExprs; + /// Indicates whether delete-expression was in array form. + bool IsArrayForm; + FieldDecl *Field; + +private: + const bool EndOfTU; + /// \brief Indicates that there is at least one constructor without body. + bool HasUndefinedConstructors; + /// \brief Returns \c CXXNewExpr from given initialization expression. + /// \param E Expression used for initializing pointee in delete-expression. + /// \param E can be a single-element \c InitListExpr consisting of + /// \param E new-expression. + const CXXNewExpr *getNewExprFromInitListOrExpr(const Expr *E); + /// \brief Returns whether member is initialized with mismatching form of + /// \c new either by the member initializer or in-class initialization. + /// + /// If bodies of all constructors are not visible at the end of translation + /// unit or at least one constructor initializes member with the matching + /// form of \c new, mismatch cannot be proven, and this function will return + /// \c NoMismatch. + MismatchResult analyzeMemberExpr(const MemberExpr *ME); + /// \brief Returns whether variable is initialized with mismatching form of + /// \c new. + /// + /// If variable is initialized with matching form of \c new or variable is not + /// initialized with a \c new expression, this function will return true. + /// If variable is initialized with mismatching form of \c new, returns false. + /// \param D Variable to analyze. + bool hasMatchingVarInit(const DeclRefExpr *D); + /// \brief Checks whether the constructor initializes pointee with mismatching + /// form of \c new. + /// + /// Returns true, if member is initialized with matching form of \c new in + /// member initializer list. Returns false, if member is initialized with the + /// matching form of \c new in this constructor's initializer or given + /// constructor isn't defined at the point where delete-expression is seen, or + /// member isn't initialized by the constructor. + bool hasMatchingNewInCtor(const CXXConstructorDecl *CD); + /// \brief Checks whether member is initialized with matching form of + /// \c new in member initializer list. + bool hasMatchingNewInCtorInit(const CXXCtorInitializer *CI); + /// Checks whether member is initialized with mismatching form of \c new by + /// in-class initializer. + MismatchResult analyzeInClassInitializer(); +}; +} + +MismatchingNewDeleteDetector::MismatchResult +MismatchingNewDeleteDetector::analyzeDeleteExpr(const CXXDeleteExpr *DE) { + NewExprs.clear(); + assert(DE && "Expected delete-expression"); + IsArrayForm = DE->isArrayForm(); + const Expr *E = DE->getArgument()->IgnoreParenImpCasts(); + if (const MemberExpr *ME = dyn_cast<const MemberExpr>(E)) { + return analyzeMemberExpr(ME); + } else if (const DeclRefExpr *D = dyn_cast<const DeclRefExpr>(E)) { + if (!hasMatchingVarInit(D)) + return VarInitMismatches; + } + return NoMismatch; +} + +const CXXNewExpr * +MismatchingNewDeleteDetector::getNewExprFromInitListOrExpr(const Expr *E) { + assert(E != nullptr && "Expected a valid initializer expression"); + E = E->IgnoreParenImpCasts(); + if (const InitListExpr *ILE = dyn_cast<const InitListExpr>(E)) { + if (ILE->getNumInits() == 1) + E = dyn_cast<const CXXNewExpr>(ILE->getInit(0)->IgnoreParenImpCasts()); + } + + return dyn_cast_or_null<const CXXNewExpr>(E); +} + +bool MismatchingNewDeleteDetector::hasMatchingNewInCtorInit( + const CXXCtorInitializer *CI) { + const CXXNewExpr *NE = nullptr; + if (Field == CI->getMember() && + (NE = getNewExprFromInitListOrExpr(CI->getInit()))) { + if (NE->isArray() == IsArrayForm) + return true; + else + NewExprs.push_back(NE); + } + return false; +} + +bool MismatchingNewDeleteDetector::hasMatchingNewInCtor( + const CXXConstructorDecl *CD) { + if (CD->isImplicit()) + return false; + const FunctionDecl *Definition = CD; + if (!CD->isThisDeclarationADefinition() && !CD->isDefined(Definition)) { + HasUndefinedConstructors = true; + return EndOfTU; + } + for (const auto *CI : cast<const CXXConstructorDecl>(Definition)->inits()) { + if (hasMatchingNewInCtorInit(CI)) + return true; + } + return false; +} + +MismatchingNewDeleteDetector::MismatchResult +MismatchingNewDeleteDetector::analyzeInClassInitializer() { + assert(Field != nullptr && "This should be called only for members"); + if (const CXXNewExpr *NE = + getNewExprFromInitListOrExpr(Field->getInClassInitializer())) { + if (NE->isArray() != IsArrayForm) { + NewExprs.push_back(NE); + return MemberInitMismatches; + } + } + return NoMismatch; +} + +MismatchingNewDeleteDetector::MismatchResult +MismatchingNewDeleteDetector::analyzeField(FieldDecl *Field, + bool DeleteWasArrayForm) { + assert(Field != nullptr && "Analysis requires a valid class member."); + this->Field = Field; + IsArrayForm = DeleteWasArrayForm; + const CXXRecordDecl *RD = cast<const CXXRecordDecl>(Field->getParent()); + for (const auto *CD : RD->ctors()) { + if (hasMatchingNewInCtor(CD)) + return NoMismatch; + } + if (HasUndefinedConstructors) + return EndOfTU ? NoMismatch : AnalyzeLater; + if (!NewExprs.empty()) + return MemberInitMismatches; + return Field->hasInClassInitializer() ? analyzeInClassInitializer() + : NoMismatch; +} + +MismatchingNewDeleteDetector::MismatchResult +MismatchingNewDeleteDetector::analyzeMemberExpr(const MemberExpr *ME) { + assert(ME != nullptr && "Expected a member expression"); + if (FieldDecl *F = dyn_cast<FieldDecl>(ME->getMemberDecl())) + return analyzeField(F, IsArrayForm); + return NoMismatch; +} + +bool MismatchingNewDeleteDetector::hasMatchingVarInit(const DeclRefExpr *D) { + const CXXNewExpr *NE = nullptr; + if (const VarDecl *VD = dyn_cast<const VarDecl>(D->getDecl())) { + if (VD->hasInit() && (NE = getNewExprFromInitListOrExpr(VD->getInit())) && + NE->isArray() != IsArrayForm) { + NewExprs.push_back(NE); + } + } + return NewExprs.empty(); +} + +static void +DiagnoseMismatchedNewDelete(Sema &SemaRef, SourceLocation DeleteLoc, + const MismatchingNewDeleteDetector &Detector) { + SourceLocation EndOfDelete = SemaRef.getLocForEndOfToken(DeleteLoc); + FixItHint H; + if (!Detector.IsArrayForm) + H = FixItHint::CreateInsertion(EndOfDelete, "[]"); + else { + SourceLocation RSquare = Lexer::findLocationAfterToken( + DeleteLoc, tok::l_square, SemaRef.getSourceManager(), + SemaRef.getLangOpts(), true); + if (RSquare.isValid()) + H = FixItHint::CreateRemoval(SourceRange(EndOfDelete, RSquare)); + } + SemaRef.Diag(DeleteLoc, diag::warn_mismatched_delete_new) + << Detector.IsArrayForm << H; + + for (const auto *NE : Detector.NewExprs) + SemaRef.Diag(NE->getExprLoc(), diag::note_allocated_here) + << Detector.IsArrayForm; +} + +void Sema::AnalyzeDeleteExprMismatch(const CXXDeleteExpr *DE) { + if (Diags.isIgnored(diag::warn_mismatched_delete_new, SourceLocation())) + return; + MismatchingNewDeleteDetector Detector(/*EndOfTU=*/false); + switch (Detector.analyzeDeleteExpr(DE)) { + case MismatchingNewDeleteDetector::VarInitMismatches: + case MismatchingNewDeleteDetector::MemberInitMismatches: { + DiagnoseMismatchedNewDelete(*this, DE->getLocStart(), Detector); + break; + } + case MismatchingNewDeleteDetector::AnalyzeLater: { + DeleteExprs[Detector.Field].push_back( + std::make_pair(DE->getLocStart(), DE->isArrayForm())); + break; + } + case MismatchingNewDeleteDetector::NoMismatch: + break; + } +} + +void Sema::AnalyzeDeleteExprMismatch(FieldDecl *Field, SourceLocation DeleteLoc, + bool DeleteWasArrayForm) { + MismatchingNewDeleteDetector Detector(/*EndOfTU=*/true); + switch (Detector.analyzeField(Field, DeleteWasArrayForm)) { + case MismatchingNewDeleteDetector::VarInitMismatches: + llvm_unreachable("This analysis should have been done for class members."); + case MismatchingNewDeleteDetector::AnalyzeLater: + llvm_unreachable("Analysis cannot be postponed any point beyond end of " + "translation unit."); + case MismatchingNewDeleteDetector::MemberInitMismatches: + DiagnoseMismatchedNewDelete(*this, DeleteLoc, Detector); + break; + case MismatchingNewDeleteDetector::NoMismatch: + break; + } +} + /// ActOnCXXDelete - Parsed a C++ 'delete' expression (C++ 5.3.5), as in: /// @code ::delete ptr; @endcode /// or @@ -2454,12 +2709,6 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, } } - // C++ [expr.delete]p2: - // [Note: a pointer to a const type can be the operand of a - // delete-expression; it is not necessary to cast away the constness - // (5.2.11) of the pointer expression before it is used as the operand - // of the delete-expression. ] - if (Pointee->isArrayType() && !ArrayForm) { Diag(StartLoc, diag::warn_delete_array_type) << Type << Ex.get()->getSourceRange() @@ -2534,7 +2783,7 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, DeleteName); MarkFunctionReferenced(StartLoc, OperatorDelete); - + // Check access and ambiguity of operator delete and destructor. if (PointeeRD) { if (CXXDestructorDecl *Dtor = LookupDestructor(PointeeRD)) { @@ -2544,9 +2793,11 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, } } - return new (Context) CXXDeleteExpr( + CXXDeleteExpr *Result = new (Context) CXXDeleteExpr( Context.VoidTy, UseGlobal, ArrayForm, ArrayFormAsWritten, UsualArrayDeleteWantsSize, OperatorDelete, Ex.get(), StartLoc); + AnalyzeDeleteExprMismatch(Result); + return Result; } /// \brief Check the use of the given variable as a C++ condition in an if, diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 3273ef97445..bc7f68a04e8 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -3021,6 +3021,18 @@ ASTReader::ReadASTBlock(ModuleFile &F, unsigned ClientLoadCapabilities) { ReadSourceLocation(F, Record, I).getRawEncoding()); } break; + case DELETE_EXPRS_TO_ANALYZE: + for (unsigned I = 0, N = Record.size(); I != N;) { + DelayedDeleteExprs.push_back(getGlobalDeclID(F, Record[I++])); + const uint64_t Count = Record[I++]; + DelayedDeleteExprs.push_back(Count); + for (uint64_t C = 0; C < Count; ++C) { + DelayedDeleteExprs.push_back(ReadSourceLocation(F, Record, I).getRawEncoding()); + bool IsArrayForm = Record[I++] == 1; + DelayedDeleteExprs.push_back(IsArrayForm); + } + } + break; case IMPORTED_MODULES: { if (F.Kind != MK_ImplicitModule && F.Kind != MK_ExplicitModule) { @@ -7016,6 +7028,21 @@ void ASTReader::ReadUndefinedButUsed( } } +void ASTReader::ReadMismatchingDeleteExpressions(llvm::MapVector< + FieldDecl *, llvm::SmallVector<std::pair<SourceLocation, bool>, 4>> & + Exprs) { + for (unsigned Idx = 0, N = DelayedDeleteExprs.size(); Idx != N;) { + FieldDecl *FD = cast<FieldDecl>(GetDecl(DelayedDeleteExprs[Idx++])); + uint64_t Count = DelayedDeleteExprs[Idx++]; + for (uint64_t C = 0; C < Count; ++C) { + SourceLocation DeleteLoc = + SourceLocation::getFromRawEncoding(DelayedDeleteExprs[Idx++]); + const bool IsArrayForm = DelayedDeleteExprs[Idx++]; + Exprs[FD].push_back(std::make_pair(DeleteLoc, IsArrayForm)); + } + } +} + void ASTReader::ReadTentativeDefinitions( SmallVectorImpl<VarDecl *> &TentativeDefs) { for (unsigned I = 0, N = TentativeDefinitions.size(); I != N; ++I) { diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index b206342678f..f5c1790ba27 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -4155,6 +4155,20 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, AddSourceLocation(I->second, UndefinedButUsed); } + // Build a record containing all delete-expressions that we would like to + // analyze later in AST. + RecordData DeleteExprsToAnalyze; + + for (const auto &DeleteExprsInfo : + SemaRef.getMismatchingDeleteExpressions()) { + AddDeclRef(DeleteExprsInfo.first, DeleteExprsToAnalyze); + DeleteExprsToAnalyze.push_back(DeleteExprsInfo.second.size()); + for (const auto &DeleteLoc : DeleteExprsInfo.second) { + AddSourceLocation(DeleteLoc.first, DeleteExprsToAnalyze); + DeleteExprsToAnalyze.push_back(DeleteLoc.second); + } + } + // Write the control block WriteControlBlock(PP, Context, isysroot, OutputFile); @@ -4424,7 +4438,10 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, // Write the undefined internal functions and variables, and inline functions. if (!UndefinedButUsed.empty()) Stream.EmitRecord(UNDEFINED_BUT_USED, UndefinedButUsed); - + + if (!DeleteExprsToAnalyze.empty()) + Stream.EmitRecord(DELETE_EXPRS_TO_ANALYZE, DeleteExprsToAnalyze); + // Write the visible updates to DeclContexts. for (auto *DC : UpdatedDeclContexts) WriteDeclContextVisibleUpdate(DC); diff --git a/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp b/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp index fca02aa278f..6fab8bb668e 100644 --- a/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp +++ b/clang/test/Analysis/Malloc+MismatchedDeallocator+NewDelete.cpp @@ -97,9 +97,11 @@ void testShouldReportDoubleFreeNotMismatched() { free(p); delete globalPtr; // expected-warning {{Attempt to free released memory}} } - +int *allocIntArray(unsigned c) { + return new int[c]; +} void testMismatchedChangePointeeThroughAssignment() { - int *arr = new int[4]; + int *arr = allocIntArray(4); globalPtr = arr; delete arr; // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'delete'}} -}
\ No newline at end of file +} diff --git a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm index 15815e80bfa..3cc3e18c7c7 100644 --- a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm +++ b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm @@ -95,8 +95,11 @@ void testNew6() { realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not realloc()}} } +int *allocInt() { + return new int; +} void testNew7() { - int *p = new int; + int *p = allocInt(); delete[] p; // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not 'delete[]'}} } @@ -105,8 +108,12 @@ void testNew8() { delete[] p; // expected-warning{{Memory allocated by operator new should be deallocated by 'delete', not 'delete[]'}} } +int *allocIntArray(unsigned c) { + return new int[c]; +} + void testNew9() { - int *p = new int[1]; + int *p = allocIntArray(1); delete p; // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'delete'}} } diff --git a/clang/test/Analysis/MismatchedDeallocator-path-notes.cpp b/clang/test/Analysis/MismatchedDeallocator-path-notes.cpp index 686497c4a9c..af24197f13e 100644 --- a/clang/test/Analysis/MismatchedDeallocator-path-notes.cpp +++ b/clang/test/Analysis/MismatchedDeallocator-path-notes.cpp @@ -3,9 +3,12 @@ // RUN: FileCheck --input-file=%t.plist %s void changePointee(int *p); +int *allocIntArray(unsigned c) { + return new int[c]; // expected-note {{Memory is allocated}} +} void test() { - int *p = new int[1]; - // expected-note@-1 {{Memory is allocated}} + int *p = allocIntArray(1); // expected-note {{Calling 'allocIntArray'}} + // expected-note@-1 {{Returned allocated memory}} changePointee(p); delete p; // expected-warning {{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'delete'}} // expected-note@-1 {{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'delete'}} @@ -24,13 +27,124 @@ void test() { // CHECK-NEXT: <key>start</key> // CHECK-NEXT: <array> // CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>3</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>5</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: <key>end</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>24</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>kind</key><string>event</string> +// CHECK-NEXT: <key>location</key> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <key>ranges</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>27</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: </array> +// CHECK-NEXT: <key>depth</key><integer>0</integer> +// CHECK-NEXT: <key>extended_message</key> +// CHECK-NEXT: <string>Calling 'allocIntArray'</string> +// CHECK-NEXT: <key>message</key> +// CHECK-NEXT: <string>Calling 'allocIntArray'</string> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>kind</key><string>event</string> +// CHECK-NEXT: <key>location</key> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>6</integer> +// CHECK-NEXT: <key>col</key><integer>1</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <key>depth</key><integer>1</integer> +// CHECK-NEXT: <key>extended_message</key> +// CHECK-NEXT: <string>Entered call from 'test'</string> +// CHECK-NEXT: <key>message</key> +// CHECK-NEXT: <string>Entered call from 'test'</string> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>kind</key><string>control</string> +// CHECK-NEXT: <key>edges</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>start</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>6</integer> +// CHECK-NEXT: <key>col</key><integer>1</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>6</integer> +// CHECK-NEXT: <key>col</key><integer>3</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: <key>end</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> // CHECK-NEXT: <key>line</key><integer>7</integer> // CHECK-NEXT: <key>col</key><integer>3</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: <dict> // CHECK-NEXT: <key>line</key><integer>7</integer> -// CHECK-NEXT: <key>col</key><integer>5</integer> +// CHECK-NEXT: <key>col</key><integer>8</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>kind</key><string>control</string> +// CHECK-NEXT: <key>edges</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>start</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>7</integer> +// CHECK-NEXT: <key>col</key><integer>3</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>7</integer> +// CHECK-NEXT: <key>col</key><integer>8</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: </array> @@ -38,12 +152,12 @@ void test() { // CHECK-NEXT: <array> // CHECK-NEXT: <dict> // CHECK-NEXT: <key>line</key><integer>7</integer> -// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>col</key><integer>10</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: <dict> // CHECK-NEXT: <key>line</key><integer>7</integer> -// CHECK-NEXT: <key>col</key><integer>14</integer> +// CHECK-NEXT: <key>col</key><integer>12</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: </array> @@ -55,7 +169,7 @@ void test() { // CHECK-NEXT: <key>location</key> // CHECK-NEXT: <dict> // CHECK-NEXT: <key>line</key><integer>7</integer> -// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>col</key><integer>10</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: <key>ranges</key> @@ -63,23 +177,52 @@ void test() { // CHECK-NEXT: <array> // CHECK-NEXT: <dict> // CHECK-NEXT: <key>line</key><integer>7</integer> -// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>col</key><integer>10</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: <dict> // CHECK-NEXT: <key>line</key><integer>7</integer> -// CHECK-NEXT: <key>col</key><integer>21</integer> +// CHECK-NEXT: <key>col</key><integer>19</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: </array> // CHECK-NEXT: </array> -// CHECK-NEXT: <key>depth</key><integer>0</integer> +// CHECK-NEXT: <key>depth</key><integer>1</integer> // CHECK-NEXT: <key>extended_message</key> // CHECK-NEXT: <string>Memory is allocated</string> // CHECK-NEXT: <key>message</key> // CHECK-NEXT: <string>Memory is allocated</string> // CHECK-NEXT: </dict> // CHECK-NEXT: <dict> +// CHECK-NEXT: <key>kind</key><string>event</string> +// CHECK-NEXT: <key>location</key> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <key>ranges</key> +// CHECK-NEXT: <array> +// CHECK-NEXT: <array> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>12</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>27</integer> +// CHECK-NEXT: <key>file</key><integer>0</integer> +// CHECK-NEXT: </dict> +// CHECK-NEXT: </array> +// CHECK-NEXT: </array> +// CHECK-NEXT: <key>depth</key><integer>0</integer> +// CHECK-NEXT: <key>extended_message</key> +// CHECK-NEXT: <string>Returned allocated memory</string> +// CHECK-NEXT: <key>message</key> +// CHECK-NEXT: <string>Returned allocated memory</string> +// CHECK-NEXT: </dict> +// CHECK-NEXT: <dict> // CHECK-NEXT: <key>kind</key><string>control</string> // CHECK-NEXT: <key>edges</key> // CHECK-NEXT: <array> @@ -87,25 +230,25 @@ void test() { // CHECK-NEXT: <key>start</key> // CHECK-NEXT: <array> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>7</integer> +// CHECK-NEXT: <key>line</key><integer>10</integer> // CHECK-NEXT: <key>col</key><integer>12</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>7</integer> -// CHECK-NEXT: <key>col</key><integer>14</integer> +// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>col</key><integer>24</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: </array> // CHECK-NEXT: <key>end</key> // CHECK-NEXT: <array> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>line</key><integer>13</integer> // CHECK-NEXT: <key>col</key><integer>3</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>line</key><integer>13</integer> // CHECK-NEXT: <key>col</key><integer>8</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> @@ -117,7 +260,7 @@ void test() { // CHECK-NEXT: <key>kind</key><string>event</string> // CHECK-NEXT: <key>location</key> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>line</key><integer>13</integer> // CHECK-NEXT: <key>col</key><integer>3</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> @@ -125,12 +268,12 @@ void test() { // CHECK-NEXT: <array> // CHECK-NEXT: <array> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>line</key><integer>13</integer> // CHECK-NEXT: <key>col</key><integer>10</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>line</key><integer>13</integer> // CHECK-NEXT: <key>col</key><integer>10</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> @@ -152,7 +295,7 @@ void test() { // CHECK-NEXT: <key>issue_hash</key><string>4</string> // CHECK-NEXT: <key>location</key> // CHECK-NEXT: <dict> -// CHECK-NEXT: <key>line</key><integer>10</integer> +// CHECK-NEXT: <key>line</key><integer>13</integer> // CHECK-NEXT: <key>col</key><integer>3</integer> // CHECK-NEXT: <key>file</key><integer>0</integer> // CHECK-NEXT: </dict> diff --git a/clang/test/CodeGenCXX/new.cpp b/clang/test/CodeGenCXX/new.cpp index 59b899f2620..c8e0acba7b0 100644 --- a/clang/test/CodeGenCXX/new.cpp +++ b/clang/test/CodeGenCXX/new.cpp @@ -321,14 +321,14 @@ namespace N3664 { // CHECK-LABEL: define void @_ZN5N36641fEv void f() { // CHECK: call noalias i8* @_Znwm(i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]] - int *p = new int; + int *p = new int; // expected-note {{allocated with 'new' here}} // CHECK: call void @_ZdlPv({{.*}}) [[ATTR_BUILTIN_DELETE:#[^ ]*]] delete p; // CHECK: call noalias i8* @_Znam(i64 12) [[ATTR_BUILTIN_NEW]] int *q = new int[3]; // CHECK: call void @_ZdaPv({{.*}}) [[ATTR_BUILTIN_DELETE]] - delete [] p; + delete[] p; // expected-warning {{'delete[]' applied to a pointer that was allocated with 'new'; did you mean 'delete'?}} // CHECK: call i8* @_ZnamRKSt9nothrow_t(i64 3, {{.*}}) [[ATTR_BUILTIN_NOTHROW_NEW:#[^ ]*]] (void) new (nothrow) S[3]; diff --git a/clang/test/SemaCXX/delete-mismatch.h b/clang/test/SemaCXX/delete-mismatch.h new file mode 100644 index 00000000000..84fcd611b60 --- /dev/null +++ b/clang/test/SemaCXX/delete-mismatch.h @@ -0,0 +1,15 @@ +// Header for PCH test delete.cpp +namespace pch_test { +struct X { + int *a; + X(); + X(int); + X(bool) + : a(new int[1]) { } // expected-note{{allocated with 'new[]' here}} + ~X() + { + delete a; // expected-warning{{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:9}:"[]" + } +}; +} diff --git a/clang/test/SemaCXX/delete.cpp b/clang/test/SemaCXX/delete.cpp index 5824facc507..f94a8631613 100644 --- a/clang/test/SemaCXX/delete.cpp +++ b/clang/test/SemaCXX/delete.cpp @@ -1,9 +1,130 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s -// RUN: cp %s %t -// RUN: %clang_cc1 -fixit -x c++ %t -// RUN: %clang_cc1 -E -o - %t | FileCheck %s +// Test without PCH +// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 | FileCheck %s + +// Test with PCH +// RUN: %clang_cc1 -x c++-header -std=c++11 -emit-pch -o %t %S/delete-mismatch.h +// RUN: %clang_cc1 -std=c++11 -include-pch %t -DWITH_PCH -fsyntax-only -verify %s -ast-dump void f(int a[10][20]) { - // CHECK: delete[] a; delete a; // expected-warning {{'delete' applied to a pointer-to-array type}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:9}:"[]" +} +namespace MemberCheck { +struct S { + int *a = new int[5]; // expected-note4 {{allocated with 'new[]' here}} + int *b; + int *c; + static int *d; + S(); + S(int); + ~S() { + delete a; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + delete b; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + delete[] c; // expected-warning {{'delete[]' applied to a pointer that was allocated with 'new'; did you mean 'delete'?}} + } + void f(); +}; + +void S::f() +{ + delete a; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + delete b; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} +} + +S::S() +: b(new int[1]), c(new int) {} // expected-note3 {{allocated with 'new[]' here}} +// expected-note@-1 {{allocated with 'new' here}} + +S::S(int i) +: b(new int[i]), c(new int) {} // expected-note3 {{allocated with 'new[]' here}} +// expected-note@-1 {{allocated with 'new' here}} + +struct S2 : S { + ~S2() { + delete a; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + } +}; +int *S::d = new int[42]; // expected-note {{allocated with 'new[]' here}} +void f(S *s) { + int *a = new int[1]; // expected-note {{allocated with 'new[]' here}} + delete a; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + delete s->a; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + delete s->b; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + delete s->c; + delete s->d; + delete S::d; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} +} + +// At least one constructor initializes field with matching form of 'new'. +struct MatchingNewIsOK { + int *p; + bool is_array_; + MatchingNewIsOK() : p{new int}, is_array_(false) {} + explicit MatchingNewIsOK(unsigned c) : p{new int[c]}, is_array_(true) {} + ~MatchingNewIsOK() { + if (is_array_) + delete[] p; + else + delete p; + } +}; + +// At least one constructor's body is missing; no proof of mismatch. +struct CantProve_MissingCtorDefinition { + int *p; + CantProve_MissingCtorDefinition(); + CantProve_MissingCtorDefinition(int); + ~CantProve_MissingCtorDefinition(); +}; + +CantProve_MissingCtorDefinition::CantProve_MissingCtorDefinition() + : p(new int) +{ } + +CantProve_MissingCtorDefinition::~CantProve_MissingCtorDefinition() +{ + delete[] p; +} + +struct base {}; +struct derived : base {}; +struct InitList { + base *p, *p2 = nullptr, *p3{nullptr}, *p4; + InitList(unsigned c) : p(new derived[c]), p4(nullptr) {} // expected-note {{allocated with 'new[]' here}} + InitList(unsigned c, unsigned) : p{new derived[c]}, p4{nullptr} {} // expected-note {{allocated with 'new[]' here}} + ~InitList() { + delete p; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + delete [] p; + delete p2; + delete [] p3; + delete p4; + } +}; +} + +namespace NonMemberCheck { +#define DELETE_ARRAY(x) delete[] (x) +#define DELETE(x) delete (x) +void f() { + int *a = new int(5); // expected-note2 {{allocated with 'new' here}} + delete[] a; // expected-warning {{'delete[]' applied to a pointer that was allocated with 'new'; did you mean 'delete'?}} + int *b = new int; + delete b; + int *c{new int}; // expected-note {{allocated with 'new' here}} + int *d{new int[1]}; // expected-note2 {{allocated with 'new[]' here}} + delete [ ] c; // expected-warning {{'delete[]' applied to a pointer that was allocated with 'new'; did you mean 'delete'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:17}:"" + delete d; // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:9}:"[]" + DELETE_ARRAY(a); // expected-warning {{'delete[]' applied to a pointer that was allocated with 'new'; did you mean 'delete'?}} + DELETE(d); // expected-warning {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} +} } +#ifndef WITH_PCH +pch_test::X::X() + : a(new int[1]) // expected-note{{allocated with 'new[]' here}} +{ } +pch_test::X::X(int i) + : a(new int[i]) // expected-note{{allocated with 'new[]' here}} +{ } +#endif |