[cfe-dev] Additional annotations for static analysis (Objective C designated initializers)

Ted Kremenek kremenek at apple.com
Wed Nov 5 18:51:01 PST 2008


On Nov 1, 2008, at 10:47 PM, Louis Gerbarg wrote:

> Anyway, let me know what you think. I think it should be stylistically
> consistent with the rest of the codebase, but C++ is a little rusty
> (Imagine that, someone trying to enhance the Objective C analyzer
> doesn't write much C++ ;-)

Here's some comments on the actual check.  Comments are inline.  One  
meta comment: the LLVM coding convention is to stay within 80 columns;  
there are a few cases in the patch where the lines are slightly too  
long.


Index: Driver/AnalysisConsumer.cpp
===================================================================
--- Driver/AnalysisConsumer.cpp	(revision 58556)
+++ Driver/AnalysisConsumer.cpp	(working copy)
@@ -431,6 +431,11 @@
                     mgr.getLangOptions(), BR);
  }

+static void ActionWarnObjCDesignatedInitializer(AnalysisManager& mgr) {
+  BugReporter BR(mgr);
+   
CheckObjCDesignatedInitializers 
(cast<ObjCImplementationDecl>(mgr.getCodeDecl()), BR);
+}

Looks good.  Stay within 80 cols.  ;-)

Index: Driver/Analyses.def
===================================================================
--- Driver/Analyses.def	(revision 58556)
+++ Driver/Analyses.def	(working copy)
+ANALYSIS(WarnObjCDesignatedInitializer, "warn-objc-designated- 
initializer",
+ "Warn about Objective-C classes that incorrectly use designated  
initializers",
+  ObjCImplementation)

Looks good!



Analysis/CheckObjCDesignatedInitializers.cpp
===================================================================
--- lib/Analysis/CheckObjCDesignatedInitializers.cpp	(revision 0)
+++ lib/Analysis/CheckObjCDesignatedInitializers.cpp	(revision 0)
...
+using namespace clang;
+
+// FIXME: This should check the control flow to make sure that all
+// paths go through the designated initializer

By "all paths", you mean within an initXXX method that calls [self  
initYYY], where initYYY is the designated initializer?

+
+static bool scan_init_method(Stmt* S,
+                             IdentifierInfo* SelfII,
+                             ObjCInterfaceDecl *C) {
+
+  if (ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(S)) {
+    if (ObjCMethodDecl *MD = C->lookupInstanceMethod(ME- 
 >getSelector()))
+      if (MD->getAttr<DesignatedInitializerAttr>())
+        if (ME->getReceiver())
+          if (Expr* Receiver = ME->getReceiver()->IgnoreParenCasts())
+            if (SelfII) {
+
+              // Self is defined, check to make sure it goes through  
self's
+              // desginated initializer

Spello: s/desginated/designated/.

+              if (DeclRefExpr* E = dyn_cast<DeclRefExpr>(Receiver))
+                if (E->getDecl()->getIdentifier() == SelfII)
+                  return true;

There is unfortunately a difference between the "self" variable which  
as implicit parameter to a method and any variable that can be named  
self.  It is perfectly okay to the following in Objective-C:

(id) myMethod: {
    int self = 42;
}

I would check:

   if (E->getDecl() == MD->getSelfDecl())

You also don't need SelfII in this case either.  Use a flag instead to  
indicate you are looking for self/super (more below).

+            } else {
+
+              // Check to make sure we go through super's designated
+              // initializer
+              if (PredefinedExpr* E =  
dyn_cast<PredefinedExpr>(Receiver))
+                if (E->getIdentType() == PredefinedExpr::ObjCSuper)
+                  return true;
+            }

Recently the represention of 'super' was changed to it's own AST node:

http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20081103/008869.html

Just check: isa<ObjCSuperExpr>(Receiver->ignoreParenCasts())

+
+  // Recurse to children.
+  for (Stmt::child_iterator I = S->child_begin(), E= S->child_end();  
I!=E; ++I)
+    if (*I && scan_init_method(*I, SelfII, C))
+      return true;
+
+  return false;
+}

No SelfII needed in the recursion.


+
+void clang::CheckObjCDesignatedInitializers(ObjCImplementationDecl* ID,
+                                       BugReporter& BR) {
+
+  ObjCInterfaceDecl* D = ID->getClassInterface();
+  ObjCInterfaceDecl* C = D->getSuperClass();

Looks good.

+  bool hasDI = false;
+  bool superHasDI = false;

Looks good.
+
+  // Check if self or super has declared designated initializers
+  for (ObjCInterfaceDecl::instmeth_iterator I=D->instmeth_begin(),
+       E=D->instmeth_end(); I!=E; ++I) {
+
+    ObjCMethodDecl* M = *I;
+    if (M->getAttr<DesignatedInitializerAttr>()) {
+      hasDI = true;
+      break;
+    }
+  }

Looks great.

+
+  if (C) {
+    for (ObjCInterfaceDecl::instmeth_iterator I=C->instmeth_begin(),
+         E=C->instmeth_end(); I!=E; ++I) {
+
+      ObjCMethodDecl* M = *I;
+      if (M->getAttr<DesignatedInitializerAttr>()) {
+        superHasDI = true;
+        break;
+      }
+    }
+  }

Looks good.  Consider refactoring these two for loops into a static  
method that returns a bool.

+
+  if (!hasDI && !superHasDI)
+    return;
+
+  ASTContext& Ctx = BR.getContext();
+  IdentifierInfo* SelfII = &Ctx.Idents.get("self");

SelfII not needed.

+
+  for (ObjCImplementationDecl::instmeth_iterator I=ID- 
 >instmeth_begin(),
+       E=ID->instmeth_end(); I!=E; ++I) {
+
+    ObjCMethodDecl* M = *I;
+    ObjCMethodDecl* IM = D->lookupInstanceMethod(M->getSelector());
+
+    // Check if it is an init method
+    if (!strncmp("init", M->getSelector().getName().c_str(), 4)) {
+      Stmt* S = M->getBody();
+      if (S) {
+

Consider:

if (IM->getAttr<DesignatedInitializerAttr>()) {
   if (superHasDI) { ... }
}
else if (hasDI) {

}

This makes the logic a little more simple, and doesn't require two  
calls to getAttr().


+        // Check if this is designated initializer
+        if (superHasDI && IM->getAttr<DesignatedInitializerAttr>()) {
+
+          // Make sure we go through super's designated init
+          if (!scan_init_method(S, NULL, C)) {

Instead of passing NULL, pass a flag indicating that you are looking  
for super, since you don't need SelfII (and it doesn't work in the  
general case).

+            std::string buf;
+            llvm::raw_string_ostream os(buf);
+
+            os << "The designated initializer method '-[" << D- 
 >getName() << " "
+               << IM->getSelector().getName() << "]' does not call a  
designated "
+               << "initializer for superclass '" << C->getName() <<  
"'";
+
+            BR.EmitBasicReport("Incorrect super initializer",
+                               os.str().c_str(), M->getLocStart());
+          }
+        }

Looks great!

+	
+        // Check if this goes through a designated initializer
+        if (hasDI && !IM->getAttr<DesignatedInitializerAttr>()) {
+
+          // Make sure we go through self's designated init
+          if (!scan_init_method(S, SelfII, D)) {

Again, use a flag.

+            std::string buf;
+            llvm::raw_string_ostream os(buf);
+
+            os << "The initializer method '-[" << D->getName() << " "
+               << IM->getSelector().getName()
+               << "]' does not call a designated initializer";
+
+            BR.EmitBasicReport("Incorrect initializer",
+                               os.str().c_str(), M->getLocStart());
+          }

Looks great!

In the future we can refine this check to use the information from the  
path-sensitive engine.  This will also handle corner cases where self/ 
super get aliases (which should never happen in practice, but you  
never know).

You can also add a "bug category" in addition to a bug type.  This  
could be something like "Coding Convention (Designated  
Initializers)".  It's just a way to further group reports in scan- 
view's table of reports.  The category just follows the bug type, and  
precedes the diagnostic.

This patch is really awesome.  I'm really impressed.



More information about the cfe-dev mailing list