diff options
author | Ismail Pazarbasi <ismail.pazarbasi@gmail.com> | 2015-05-14 16:14:57 +0000 |
---|---|---|
committer | Ismail Pazarbasi <ismail.pazarbasi@gmail.com> | 2015-05-14 16:14:57 +0000 |
commit | 538ef53c139d9915327960217e922b2be7e8b3fd (patch) | |
tree | 3d6a10da310f704215fff7d5b0b3cbec701f3545 /clang/lib | |
parent | 9d08232e28f6bbdfeb080d2d9f138a6dc55d7e93 (diff) | |
download | bcm5719-llvm-538ef53c139d9915327960217e922b2be7e8b3fd.tar.gz bcm5719-llvm-538ef53c139d9915327960217e922b2be7e8b3fd.zip |
Detect uses of mismatching forms of 'new' and 'delete'
Emit warning when operand to `delete` is allocated with `new[]` or
operand to `delete[]` is allocated with `new`.
Reviewers: rtrieu, jordan_rose, rsmith
Subscribers: majnemer, cfe-commits
Differential Revision: http://reviews.llvm.org/D4661
llvm-svn: 237368
Diffstat (limited to 'clang/lib')
-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 |
5 files changed, 332 insertions, 10 deletions
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 6825dfa41fa..dea01bab079 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -860,6 +860,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. @@ -1219,6 +1230,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(); @@ -1467,3 +1481,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..12f06360a1a 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<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 c4b4aec24a0..0aeb1fffaba 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) { @@ -7013,6 +7025,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 29a88a13d38..c1398e26c73 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); |