]> granicus.if.org Git - clang/commitdiff
[analyzer] Correctly devirtualize virtual method calls in destructors.
authorJordan Rose <jordan_rose@apple.com>
Wed, 15 Aug 2012 00:51:56 +0000 (00:51 +0000)
committerJordan Rose <jordan_rose@apple.com>
Wed, 15 Aug 2012 00:51:56 +0000 (00:51 +0000)
C++11 [class.cdtor]p4: When a virtual function is called directly or
  indirectly from a constructor or from a destructor, including during
  the construction or destruction of the class’s non-static data members,
  and the object to which the call applies is the object under
  construction or destruction, the function called is the final overrider
  in the constructor's or destructor's class and not one overriding it in
  a more-derived class.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@161915 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
lib/StaticAnalyzer/Core/CallEvent.cpp
lib/StaticAnalyzer/Core/ProgramState.cpp
test/Analysis/dtor.cpp

index d36aa1bd1bcbe6b362bd965c179d498b5629b7f7..b0c51dd5b92857b247995247bf5af081680a202d 100644 (file)
@@ -65,10 +65,14 @@ class DynamicTypeInfo {
 
 public:
   DynamicTypeInfo() : T(QualType()) {}
-  DynamicTypeInfo(QualType WithType, bool CanBeSub = true):
-    T(WithType), CanBeASubClass(CanBeSub) {}
-  QualType getType() { return T; }
-  bool canBeASubClass() { return CanBeASubClass; }
+  DynamicTypeInfo(QualType WithType, bool CanBeSub = true)
+    : T(WithType), CanBeASubClass(CanBeSub) {}
+
+  bool isValid() const { return !T.isNull(); }
+
+  QualType getType() const { return T; }
+  bool canBeASubClass() const { return CanBeASubClass; }
+  
   void Profile(llvm::FoldingSetNodeID &ID) const {
     T.Profile(ID);
     ID.AddInteger((unsigned)CanBeASubClass);
index fea57337bbdaa3685a81c8c2be9018e6ac8e04cd..036686e964c24cb85a8e14afed474588c136fbf7 100644 (file)
@@ -25,7 +25,8 @@ using namespace ento;
 
 namespace {
 class DynamicTypePropagation:
-    public Checker< check::PostCall,
+    public Checker< check::PreCall,
+                    check::PostCall,
                     check::PostStmt<ImplicitCastExpr> > {
   const ObjCObjectType *getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE,
                                                     CheckerContext &C) const;
@@ -34,11 +35,45 @@ class DynamicTypePropagation:
   const ObjCObjectPointerType *getBetterObjCType(const Expr *CastE,
                                                  CheckerContext &C) const;
 public:
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
   void checkPostStmt(const ImplicitCastExpr *CastE, CheckerContext &C) const;
 };
 }
 
+void DynamicTypePropagation::checkPreCall(const CallEvent &Call,
+                                          CheckerContext &C) const {
+  if (const CXXDestructorCall *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
+    // C++11 [class.cdtor]p4: When a virtual function is called directly or
+    //   indirectly from a constructor or from a destructor, including during
+    //   the construction or destruction of the class’s non-static data members,
+    //   and the object to which the call applies is the object under
+    //   construction or destruction, the function called is the final overrider
+    //   in the constructor's or destructor's class and not one overriding it in
+    //   a more-derived class.
+    // FIXME: We don't support this behavior yet for constructors.
+
+    const MemRegion *Target = Dtor->getCXXThisVal().getAsRegion();
+    if (!Target)
+      return;
+
+    // FIXME: getRuntimeDefinition() can be expensive. It would be better to do
+    // this when we are entering the stack frame for the destructor.
+    const Decl *D = Dtor->getRuntimeDefinition().getDecl();
+    if (!D)
+      return;
+
+    const CXXRecordDecl *Class = cast<CXXDestructorDecl>(D)->getParent();
+
+    ASTContext &Ctx = C.getASTContext();
+    QualType Ty = Ctx.getPointerType(Ctx.getRecordType(Class));
+
+    ProgramStateRef State = C.getState();
+    State = State->setDynamicTypeInfo(Target, Ty, /*CanBeSubclass=*/false);
+    C.addTransition(State);
+  }
+}
+
 void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
                                            CheckerContext &C) const {
   // We can obtain perfect type info for return values from some calls.
index 7a0cb4abe42b17c0ac9ec48624cc5657f1a7c985..db48fba60e8035fb693533e09d88dbec82bbd7c3 100644 (file)
@@ -376,47 +376,49 @@ void CXXInstanceCall::getExtraInvalidatedRegions(RegionList &Regions) const {
     Regions.push_back(R);
 }
 
-static const CXXMethodDecl *devirtualize(const CXXMethodDecl *MD, SVal ThisVal){
-  const MemRegion *R = ThisVal.getAsRegion();
-  if (!R)
-    return 0;
-
-  const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(R->StripCasts());
-  if (!TR)
-    return 0;
-
-  const CXXRecordDecl *RD = TR->getValueType()->getAsCXXRecordDecl();
-  if (!RD)
-    return 0;
-
-  const CXXMethodDecl *Result = MD->getCorrespondingMethodInClass(RD);
-  const FunctionDecl *Definition;
-  if (!Result->hasBody(Definition))
-    return 0;
-
-  return cast<CXXMethodDecl>(Definition);
-}
-
 
 RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
+  // Do we have a decl at all?
   const Decl *D = getDecl();
   if (!D)
     return RuntimeDefinition();
 
+  // If the method is non-virtual, we know we can inline it.
   const CXXMethodDecl *MD = cast<CXXMethodDecl>(D);
   if (!MD->isVirtual())
     return AnyFunctionCall::getRuntimeDefinition();
 
-  // If the method is virtual, see if we can find the actual implementation
-  // based on context-sensitivity.
-  // FIXME: Virtual method calls behave differently when an object is being
-  // constructed or destructed. It's not as simple as "no devirtualization"
-  // because a /partially/ constructed object can be referred to through a
-  // base pointer. We'll eventually want to use DynamicTypeInfo here.
-  if (const CXXMethodDecl *Devirtualized = devirtualize(MD, getCXXThisVal()))
-    return RuntimeDefinition(Devirtualized);
+  // Do we know the implicit 'this' object being called?
+  const MemRegion *R = getCXXThisVal().getAsRegion();
+  if (!R)
+    return RuntimeDefinition();
 
-  return RuntimeDefinition();
+  // Do we know anything about the type of 'this'?
+  DynamicTypeInfo DynType = getState()->getDynamicTypeInfo(R);
+  if (!DynType.isValid())
+    return RuntimeDefinition();
+
+  // Is the type a C++ class? (This is mostly a defensive check.)
+  QualType RegionType = DynType.getType()->getPointeeType();
+  const CXXRecordDecl *RD = RegionType->getAsCXXRecordDecl();
+  if (!RD)
+    return RuntimeDefinition();
+
+  // Find the decl for this method in that class.
+  const CXXMethodDecl *Result = MD->getCorrespondingMethodInClass(RD);
+  assert(Result && "At the very least the static decl should show up.");
+
+  // Does the decl that we found have an implementation?
+  const FunctionDecl *Definition;
+  if (!Result->hasBody(Definition))
+    return RuntimeDefinition();
+
+  // We found a definition. If we're not sure that this devirtualization is
+  // actually what will happen at runtime, make sure to provide the region so
+  // that ExprEngine can decide what to do with it.
+  if (DynType.canBeASubClass())
+    return RuntimeDefinition(Definition, R->StripCasts());
+  return RuntimeDefinition(Definition, /*DispatchRegion=*/0);
 }
 
 void CXXInstanceCall::getInitialStackFrameContents(
index dc988cc5f1fc8ab782e3d50a07f305b915126f8a..2000338ee0a6098fba6d87453e4d48554ac232d9 100644 (file)
@@ -745,14 +745,16 @@ template<> struct ProgramStateTrait<DynamicTypeMap>
 }}
 
 DynamicTypeInfo ProgramState::getDynamicTypeInfo(const MemRegion *Reg) const {
+  Reg = Reg->StripCasts();
+
   // Look up the dynamic type in the GDM.
   const DynamicTypeInfo *GDMType = get<DynamicTypeMap>(Reg);
   if (GDMType)
     return *GDMType;
 
   // Otherwise, fall back to what we know about the region.
-  if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(Reg))
-    return DynamicTypeInfo(TR->getValueType());
+  if (const TypedRegion *TR = dyn_cast<TypedRegion>(Reg))
+    return DynamicTypeInfo(TR->getLocationType(), /*CanBeSubclass=*/false);
 
   if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(Reg)) {
     SymbolRef Sym = SR->getSymbol();
@@ -764,6 +766,7 @@ DynamicTypeInfo ProgramState::getDynamicTypeInfo(const MemRegion *Reg) const {
 
 ProgramStateRef ProgramState::setDynamicTypeInfo(const MemRegion *Reg,
                                                  DynamicTypeInfo NewTy) const {
+  Reg = Reg->StripCasts();
   ProgramStateRef NewState = set<DynamicTypeMap>(Reg, NewTy);
   assert(NewState);
   return NewState;
index 620994858c7c97681d5c1a78c1e4c183b5ae94a5..1f45925561c996d7590f96e561fab48ae0568d48 100644 (file)
@@ -173,3 +173,57 @@ void testDefaultArg() {
   // Force a bug to be emitted.
   *(char *)0 = 1; // expected-warning{{Dereference of null pointer}}
 }
+
+
+namespace DestructorVirtualCalls {
+  class A {
+  public:
+    int *out1, *out2, *out3;
+
+    virtual int get() { return 1; }
+
+    ~A() {
+      *out1 = get();
+    }
+  };
+
+  class B : public A {
+  public:
+    virtual int get() { return 2; }
+
+    ~B() {
+      *out2 = get();
+    }
+  };
+
+  class C : public B {
+  public:
+    virtual int get() { return 3; }
+
+    ~C() {
+      *out3 = get();
+    }
+  };
+
+  void test() {
+    int a, b, c;
+
+    // New scope for the C object.
+    {
+      C obj;
+      clang_analyzer_eval(obj.get() == 3); // expected-warning{{TRUE}}
+
+      // Sanity check for devirtualization.
+      A *base = &obj;
+      clang_analyzer_eval(base->get() == 3); // expected-warning{{TRUE}}
+
+      obj.out1 = &a;
+      obj.out2 = &b;
+      obj.out3 = &c;
+    }
+
+    clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+    clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+    clang_analyzer_eval(c == 3); // expected-warning{{TRUE}}
+  }
+}