diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index dbd6d778782353..81f333e644f31c 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -1834,6 +1834,30 @@ Either the comparison is useless or there is division by zero. alpha.cplusplus ^^^^^^^^^^^^^^^ +.. _alpha-cplusplus-ArrayDelete: + +alpha.cplusplus.ArrayDelete (C++) +""""""""""""""""""""""""""""""""" +Reports destructions of arrays of polymorphic objects that are destructed as their base class. +This checker corresponds to the CERT rule `EXP51-CPP: Do not delete an array through a pointer of the incorrect type `_. + +.. code-block:: cpp + + class Base { + virtual ~Base() {} + }; + class Derived : public Base {} + + Base *create() { + Base *x = new Derived[10]; // note: Casting from 'Derived' to 'Base' here + return x; + } + + void foo() { + Base *x = create(); + delete[] x; // warn: Deleting an array of 'Derived' objects as their base class 'Base' is undefined + } + .. _alpha-cplusplus-DeleteWithNonVirtualDtor: alpha.cplusplus.DeleteWithNonVirtualDtor (C++) @@ -1842,13 +1866,17 @@ Reports destructions of polymorphic objects with a non-virtual destructor in the .. code-block:: cpp + class NonVirtual {}; + class NVDerived : public NonVirtual {}; + NonVirtual *create() { - NonVirtual *x = new NVDerived(); // note: conversion from derived to base - // happened here + NonVirtual *x = new NVDerived(); // note: Casting from 'NVDerived' to + // 'NonVirtual' here return x; } - void sink(NonVirtual *x) { + void foo() { + NonVirtual *x = create(); delete x; // warn: destruction of a polymorphic object with no virtual // destructor } diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 65c1595eb6245d..4ca8c98af8706a 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -758,6 +758,11 @@ def ContainerModeling : Checker<"ContainerModeling">, Documentation, Hidden; +def CXXArrayDeleteChecker : Checker<"ArrayDelete">, + HelpText<"Reports destructions of arrays of polymorphic objects that are " + "destructed as their base class.">, + Documentation; + def DeleteWithNonVirtualDtorChecker : Checker<"DeleteWithNonVirtualDtor">, HelpText<"Reports destructions of polymorphic objects with a non-virtual " "destructor in their base class">, diff --git a/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp index 3c142b49ff7288..1a1f5c53029403 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp @@ -1,4 +1,4 @@ -//===-- DeleteWithNonVirtualDtorChecker.cpp -----------------------*- C++ -*--// +//=== CXXDeleteChecker.cpp -------------------------------------*- C++ -*--===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,17 +6,25 @@ // //===----------------------------------------------------------------------===// // -// Defines a checker for the OOP52-CPP CERT rule: Do not delete a polymorphic -// object without a virtual destructor. +// This file defines the following new checkers for C++ delete expressions: // -// Diagnostic flags -Wnon-virtual-dtor and -Wdelete-non-virtual-dtor report if -// an object with a virtual function but a non-virtual destructor exists or is -// deleted, respectively. +// * DeleteWithNonVirtualDtorChecker +// Defines a checker for the OOP52-CPP CERT rule: Do not delete a +// polymorphic object without a virtual destructor. // -// This check exceeds them by comparing the dynamic and static types of the -// object at the point of destruction and only warns if it happens through a -// pointer to a base type without a virtual destructor. The check places a note -// at the last point where the conversion from derived to base happened. +// Diagnostic flags -Wnon-virtual-dtor and -Wdelete-non-virtual-dtor +// report if an object with a virtual function but a non-virtual +// destructor exists or is deleted, respectively. +// +// This check exceeds them by comparing the dynamic and static types of +// the object at the point of destruction and only warns if it happens +// through a pointer to a base type without a virtual destructor. The +// check places a note at the last point where the conversion from +// derived to base happened. +// +// * CXXArrayDeleteChecker +// Defines a checker for the EXP51-CPP CERT rule: Do not delete an array +// through a pointer of the incorrect type. // //===----------------------------------------------------------------------===// @@ -34,13 +42,10 @@ using namespace clang; using namespace ento; namespace { -class DeleteWithNonVirtualDtorChecker - : public Checker> { - mutable std::unique_ptr BT; - - class DeleteBugVisitor : public BugReporterVisitor { +class CXXDeleteChecker : public Checker> { +protected: + class PtrCastVisitor : public BugReporterVisitor { public: - DeleteBugVisitor() = default; void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; ID.AddPointer(&X); @@ -48,37 +53,67 @@ class DeleteWithNonVirtualDtorChecker PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) override; - - private: - bool Satisfied = false; }; + virtual void + checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const = 0; + public: void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; }; -} // end anonymous namespace -void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, - CheckerContext &C) const { +class DeleteWithNonVirtualDtorChecker : public CXXDeleteChecker { + mutable std::unique_ptr BT; + + void + checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const override; +}; + +class CXXArrayDeleteChecker : public CXXDeleteChecker { + mutable std::unique_ptr BT; + + void + checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const override; +}; +} // namespace + +void CXXDeleteChecker::checkPreStmt(const CXXDeleteExpr *DE, + CheckerContext &C) const { const Expr *DeletedObj = DE->getArgument(); const MemRegion *MR = C.getSVal(DeletedObj).getAsRegion(); if (!MR) return; + OverloadedOperatorKind DeleteKind = + DE->getOperatorDelete()->getOverloadedOperator(); + + if (DeleteKind != OO_Delete && DeleteKind != OO_Array_Delete) + return; + const auto *BaseClassRegion = MR->getAs(); const auto *DerivedClassRegion = MR->getBaseRegion()->getAs(); if (!BaseClassRegion || !DerivedClassRegion) return; + checkTypedDeleteExpr(DE, C, BaseClassRegion, DerivedClassRegion); +} + +void DeleteWithNonVirtualDtorChecker::checkTypedDeleteExpr( + const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const { const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); const auto *DerivedClass = DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); if (!BaseClass || !DerivedClass) return; - if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition()) - return; - if (BaseClass->getDestructor()->isVirtual()) return; @@ -94,22 +129,65 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) return; - auto R = std::make_unique(*BT, BT->getDescription(), N); + auto R = + std::make_unique(*BT, BT->getDescription(), N); // Mark region of problematic base class for later use in the BugVisitor. R->markInteresting(BaseClassRegion); - R->addVisitor(std::make_unique()); + R->addVisitor(); C.emitReport(std::move(R)); } -PathDiagnosticPieceRef -DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, - PathSensitiveBugReport &BR) { - // Stop traversal after the first conversion was found on a path. - if (Satisfied) - return nullptr; +void CXXArrayDeleteChecker::checkTypedDeleteExpr( + const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const { + const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); + const auto *DerivedClass = + DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); + if (!BaseClass || !DerivedClass) + return; + if (DE->getOperatorDelete()->getOverloadedOperator() != OO_Array_Delete) + return; + + if (!DerivedClass->isDerivedFrom(BaseClass)) + return; + + if (!BT) + BT.reset(new BugType(this, + "Deleting an array of polymorphic objects " + "is undefined", + "Logic error")); + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + + QualType SourceType = BaseClassRegion->getValueType(); + QualType TargetType = + DerivedClassRegion->getSymbol()->getType()->getPointeeType(); + + OS << "Deleting an array of '" << TargetType.getAsString() + << "' objects as their base class '" + << SourceType.getAsString(C.getASTContext().getPrintingPolicy()) + << "' is undefined"; + + auto R = std::make_unique(*BT, OS.str(), N); + + // Mark region of problematic base class for later use in the BugVisitor. + R->markInteresting(BaseClassRegion); + R->addVisitor(); + C.emitReport(std::move(R)); +} + +PathDiagnosticPieceRef +CXXDeleteChecker::PtrCastVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; @@ -118,12 +196,12 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( if (!CastE) return nullptr; - // Only interested in DerivedToBase implicit casts. - // Explicit casts can have different CastKinds. - if (const auto *ImplCastE = dyn_cast(CastE)) { - if (ImplCastE->getCastKind() != CK_DerivedToBase) - return nullptr; - } + // FIXME: This way of getting base types does not support reference types. + QualType SourceType = CastE->getSubExpr()->getType()->getPointeeType(); + QualType TargetType = CastE->getType()->getPointeeType(); + + if (SourceType.isNull() || TargetType.isNull() || SourceType == TargetType) + return nullptr; // Region associated with the current cast expression. const MemRegion *M = N->getSVal(CastE).getAsRegion(); @@ -134,15 +212,24 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( if (!BR.isInteresting(M)) return nullptr; - // Stop traversal on this path. - Satisfied = true; - SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); - OS << "Conversion from derived to base happened here"; + + OS << "Casting from '" << SourceType.getAsString() << "' to '" + << TargetType.getAsString() << "' here"; + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true); + return std::make_shared(Pos, OS.str(), + /*addPosRange=*/true); +} + +void ento::registerCXXArrayDeleteChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterCXXArrayDeleteChecker(const CheckerManager &mgr) { + return true; } void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { @@ -150,6 +237,6 @@ void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { } bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker( - const CheckerManager &mgr) { + const CheckerManager &mgr) { return true; } diff --git a/clang/test/Analysis/ArrayDelete.cpp b/clang/test/Analysis/ArrayDelete.cpp new file mode 100644 index 00000000000000..3b8d49552376ed --- /dev/null +++ b/clang/test/Analysis/ArrayDelete.cpp @@ -0,0 +1,111 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.cplusplus.ArrayDelete -std=c++11 -verify -analyzer-output=text %s + +struct Base { + virtual ~Base() = default; +}; + +struct Derived : public Base {}; + +struct DoubleDerived : public Derived {}; + +Derived *get(); + +Base *create() { + Base *b = new Derived[3]; // expected-note{{Casting from 'Derived' to 'Base' here}} + return b; +} + +void sink(Base *b) { + delete[] b; // expected-warning{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} +} + +void sink_cast(Base *b) { + delete[] static_cast(b); // no-warning +} + +void sink_derived(Derived *d) { + delete[] d; // no-warning +} + +void same_function() { + Base *sd = new Derived[10]; // expected-note{{Casting from 'Derived' to 'Base' here}} + delete[] sd; // expected-warning{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + + Base *dd = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}} + delete[] dd; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}} +} + +void different_function() { + Base *assigned = get(); // expected-note{{Casting from 'Derived' to 'Base' here}} + delete[] assigned; // expected-warning{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + + Base *indirect; + indirect = get(); // expected-note{{Casting from 'Derived' to 'Base' here}} + delete[] indirect; // expected-warning{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + + Base *created = create(); // expected-note{{Calling 'create'}} + // expected-note@-1{{Returning from 'create'}} + delete[] created; // expected-warning{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'Derived' objects as their base class 'Base' is undefined}} + + Base *sb = new Derived[10]; // expected-note{{Casting from 'Derived' to 'Base' here}} + sink(sb); // expected-note{{Calling 'sink'}} +} + +void safe_function() { + Derived *d = new Derived[10]; + delete[] d; // no-warning + + Base *b = new Derived[10]; + delete[] static_cast(b); // no-warning + + Base *sb = new Derived[10]; + sink_cast(sb); // no-warning + + Derived *sd = new Derived[10]; + sink_derived(sd); // no-warning +} + +void multiple_derived() { + Base *b = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}} + delete[] b; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}} + + Base *b2 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}} + Derived *d2 = static_cast(b2); // expected-note{{Casting from 'Base' to 'Derived' here}} + delete[] d2; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}} + // expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}} + + Derived *d3 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Derived' here}} + Base *b3 = d3; // expected-note{{Casting from 'Derived' to 'Base' here}} + delete[] b3; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}} + // expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}} + + Base *b4 = new DoubleDerived[10]; + Derived *d4 = static_cast(b4); + DoubleDerived *dd4 = static_cast(d4); + delete[] dd4; // no-warning + + Base *b5 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}} + DoubleDerived *dd5 = static_cast(b5); // expected-note{{Casting from 'Base' to 'DoubleDerived' here}} + Derived *d5 = dd5; // expected-note{{Casting from 'DoubleDerived' to 'Derived' here}} + delete[] d5; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}} + // expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}} +} + +void unrelated_casts() { + Base *b = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}} + Base &b2 = *b; // no-note: See the FIXME. + + // FIXME: Displaying casts of reference types is not supported. + Derived &d2 = static_cast(b2); // no-note: See the FIXME. + + Derived *d = &d2; // no-note: See the FIXME. + delete[] d; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}} + // expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}} +} diff --git a/clang/test/Analysis/DeleteWithNonVirtualDtor.cpp b/clang/test/Analysis/DeleteWithNonVirtualDtor.cpp index a9b8a11f3482f3..2809f3758eee1b 100644 --- a/clang/test/Analysis/DeleteWithNonVirtualDtor.cpp +++ b/clang/test/Analysis/DeleteWithNonVirtualDtor.cpp @@ -33,7 +33,7 @@ struct ImplicitNVDerived : public ImplicitNV {}; NVDerived *get(); NonVirtual *create() { - NonVirtual *x = new NVDerived(); // expected-note{{Conversion from derived to base happened here}} + NonVirtual *x = new NVDerived(); // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} return x; } @@ -52,32 +52,32 @@ void sinkParamCast(NVDerived *z) { void singleDerived() { NonVirtual *sd; - sd = new NVDerived(); // expected-note{{Conversion from derived to base happened here}} + sd = new NVDerived(); // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} delete sd; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void singleDerivedArr() { - NonVirtual *sda = new NVDerived[5]; // expected-note{{Conversion from derived to base happened here}} + NonVirtual *sda = new NVDerived[5]; // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} delete[] sda; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void doubleDerived() { - NonVirtual *dd = new NVDoubleDerived(); // expected-note{{Conversion from derived to base happened here}} + NonVirtual *dd = new NVDoubleDerived(); // expected-note{{Casting from 'NVDoubleDerived' to 'NonVirtual' here}} delete (dd); // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void assignThroughFunction() { - NonVirtual *atf = get(); // expected-note{{Conversion from derived to base happened here}} + NonVirtual *atf = get(); // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} delete atf; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void assignThroughFunction2() { NonVirtual *atf2; - atf2 = get(); // expected-note{{Conversion from derived to base happened here}} + atf2 = get(); // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} delete atf2; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } @@ -90,49 +90,49 @@ void createThroughFunction() { } void deleteThroughFunction() { - NonVirtual *dtf = new NVDerived(); // expected-note{{Conversion from derived to base happened here}} + NonVirtual *dtf = new NVDerived(); // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} sink(dtf); // expected-note{{Calling 'sink'}} } void singleCastCStyle() { NVDerived *sccs = new NVDerived(); - NonVirtual *sccs2 = (NonVirtual*)sccs; // expected-note{{Conversion from derived to base happened here}} + NonVirtual *sccs2 = (NonVirtual*)sccs; // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} delete sccs2; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void doubleCastCStyle() { - NonVirtual *dccs = new NVDerived(); - NVDerived *dccs2 = (NVDerived*)dccs; - dccs = (NonVirtual*)dccs2; // expected-note{{Conversion from derived to base happened here}} + NonVirtual *dccs = new NVDerived(); // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} + NVDerived *dccs2 = (NVDerived*)dccs; // expected-note{{Casting from 'NonVirtual' to 'NVDerived' here}} + dccs = (NonVirtual*)dccs2; // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} delete dccs; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void singleCast() { NVDerived *sc = new NVDerived(); - NonVirtual *sc2 = reinterpret_cast(sc); // expected-note{{Conversion from derived to base happened here}} + NonVirtual *sc2 = reinterpret_cast(sc); // expected-note{{Casting from 'NVDerived' to 'NonVirtual' here}} delete sc2; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void doubleCast() { - NonVirtual *dd = new NVDerived(); - NVDerived *dd2 = reinterpret_cast(dd); - dd = reinterpret_cast(dd2); // expected-note {{Conversion from derived to base happened here}} + NonVirtual *dd = new NVDerived(); // expected-note {{Casting from 'NVDerived' to 'NonVirtual' here}} + NVDerived *dd2 = reinterpret_cast(dd); // expected-note {{Casting from 'NonVirtual' to 'NVDerived' here}} + dd = reinterpret_cast(dd2); // expected-note {{Casting from 'NVDerived' to 'NonVirtual' here}} delete dd; // expected-warning {{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void implicitNV() { - ImplicitNV *invd = new ImplicitNVDerived(); // expected-note{{Conversion from derived to base happened here}} + ImplicitNV *invd = new ImplicitNVDerived(); // expected-note{{Casting from 'ImplicitNVDerived' to 'ImplicitNV' here}} delete invd; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } void doubleDecl() { ImplicitNV *dd1, *dd2; - dd1 = new ImplicitNVDerived(); // expected-note{{Conversion from derived to base happened here}} + dd1 = new ImplicitNVDerived(); // expected-note{{Casting from 'ImplicitNVDerived' to 'ImplicitNV' here}} delete dd1; // expected-warning{{Destruction of a polymorphic object with no virtual destructor}} // expected-note@-1{{Destruction of a polymorphic object with no virtual destructor}} } diff --git a/clang/www/analyzer/alpha_checks.html b/clang/www/analyzer/alpha_checks.html index 181ce1b9de591b..cff0284777bc78 100644 --- a/clang/www/analyzer/alpha_checks.html +++ b/clang/www/analyzer/alpha_checks.html @@ -330,6 +330,26 @@

C++ Alpha Checkers

+ + + +