On Tue, Feb 7, 2012 at 10:38 AM, Douglas Gregor <[email protected]> wrote:

>
> On Feb 6, 2012, at 1:00 PM, Eli Friedman wrote:
>
> > On Mon, Feb 6, 2012 at 2:50 PM, Richard Trieu <[email protected]> wrote:
> >> On Mon, Feb 6, 2012 at 2:02 PM, Eli Friedman <[email protected]>
> wrote:
> >>>
> >>> On Mon, Feb 6, 2012 at 1:55 PM, Richard Trieu <[email protected]>
> wrote:
> >>>> The motivation of this path is to catch code like this:
> >>>>
> >>>> for (int i = 0; i < 10; ++i)
> >>>>   for (int j = 0; j < 10; ++i)
> >>>>     { }
> >>>>
> >>>> The second for loop increments i instead of j causing an infinite
> loop.
> >>>>  The
> >>>> warning also checks the body of the for loop so it will trigger on:
> >>>>
> >>>> for (int i; i <10; ) { }
> >>>>
> >>>> But not trigger on:
> >>>>
> >>>> for (int i; i< 10; ) { ++i; }
> >>>>
> >>>> I'm still fine-tuning the trigger conditions, but would like some
> >>>> feedback
> >>>> on this patch.
> >>>
> >>> Adding an additional recursive traversal of the body of every parsed
> >>> for loop is very expensive.
> >>>
> >>> -Eli
> >>
> >>
> >> Is it that expensive?  I would think that once the AST was constructed,
> the
> >> visitor would be pretty fast, especially if no action is taken on most
> of
> >> the nodes.  I also made the warning default ignore and put in an early
> >> return to prevent the visitors from running in the default case.
> >>
> >> Do you have any suggestions on removing the recursive traversal?
> >
> > Okay, thinking about it a bit more, maybe it's not that expensive, but
> > you should at least measure to make sure.
>
> I'm still very nervous about adding such an AST traversal. Presumably,
> it's not going to be a concern in practice because most of the time, the
> increment statement of the 'for' loop will mention one of the variables in
> the condition, and therefore we'll short-circuit the expensive walk of the
> loop body. It would be helpful to know that's the case.
>
> > I don't have any good suggestion for an alternative.
>
>
> Nor do I.
>
> A couple more comments:
>
> +    ValueDecl *VD = E->getDecl();
> +    for (SmallVectorImpl<ValueDecl*>::iterator I = Decls.begin(),
> +                                               E = Decls.end();
> +         I != E; ++I)
> +      if (*I == VD) {
> +        FoundDecl = true;
> +        return;
> +      }
>
> This is linear; please use a SmallPtrSet instead.
>
Done.

>
> Plus, I think you want to narrow this check to only consider VarDecls.
> Functions and enumerators are not interesting.
>
Done.

>
> +      S.Diag(Second->getLocStart(), diag::warn_variables_not_in_loop_body)
> +          << Second->getSourceRange();
>
> This warning needs to specify which variables were not used or point to
> them in the source.
>
The diagnostic now underlines all the variables in the condition.

>
> Do we want to actually look for modification, e.g., any use of the
> variable that isn't immediately consumed by an lvalue-to-rvalue conversion?
>
Yes, that is exactly what we are checking for.  The VisitCastExpr checks
for LvalueToRvalue casts and skips any DeclRefExpr's that are direct
sub-expressions.

>
>        - Doug
>

I also did a comparison between runs with and without -Wloop-analysis.
 Even with the heavily nested loop, the extra recursive checks don't take
too much extra time to run.

clang loop.cc -fsyntax-only
.460 - .480s

clang loop.cc -fsyntax-only -Wloop-analysis
.520 - .540s

Code with increments removed to trigger 2044 for loop warnings
clang loop.cc -fsyntax-only
.310 - .330s

clang loop.cc -fsyntax-only -Wloop-analysis 2>/dev/null
.740 - .780s

clang loop.cc -fsyntax-only -Wloop-analysis
1.430 - 1.550s

// Test loop code.
#define M(A) A A
#define L1 for(int a1 = 0; a1 < 10;){M(L2) }
#define L2 for(int a2 = 0; a2 < 10;){M(L3) a1++; }
#define L3 for(int a3 = 0; a3 < 10;){M(L4) a2++; }
#define L4 for(int a4 = 0; a4 < 10;){M(L5) a3++; }
#define L5 for(int a5 = 0; a5 < 10;){M(L6) a4++; }
#define L6 for(int a6 = 0; a6 < 10;){M(L7) a5++; }
#define L7 for(int a7 = 0; a7 < 10;){M(L8) a6++; }
#define L8 for(int a8 = 0; a8 < 10;){M(L9) a7++; }
#define L9 for(int a9 = 0; a9 < 10;){M(L) a8++; }
#define L a9++;

void foo() {
  L1
  L1
  L1
  L1
}
Index: test/SemaCXX/warn-loop-analysis.cpp
===================================================================
--- test/SemaCXX/warn-loop-analysis.cpp	(revision 0)
+++ test/SemaCXX/warn-loop-analysis.cpp	(revision 0)
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -fsyntax-only -Wloop-analysis -verify %s
+
+struct S {
+  bool stop() { return false; }
+  bool keep_running;
+};
+
+void by_ref(int &value) { }
+void by_value(int value) { }
+void by_pointer(int *value) {}
+
+void test1() {
+  S s;
+  for (; !s.stop();) {}
+  for (; s.keep_running;) {}
+  for (int i; i < 1; ++i) {}
+  for (int i; i < 1; ) {}  // expected-warning {{variables used in loop condition not modified in loop body}}
+  for (int i; i < 1; ) { ++i; }
+  for (int i; i < 1; ) { return; }
+  for (int i; i < 1; ) { break; }
+  for (int i; i < 1; ) { goto exit_loop; }
+exit_loop:
+  for (int i; i < 1; ) { by_ref(i); }
+  for (int i; i < 1; ) { by_value(i); }  // expected-warning {{variables used in loop condition not modified in loop body}}
+  for (int i; i < 1; ) { by_pointer(&i); }
+
+  for (int i; i < 1; ++i)
+    for (int j; j < 1; ++j)
+      { }
+  for (int i; i < 1; ++i)
+    for (int j; j < 1; ++i)  // expected-warning {{variables used in loop condition not modified in loop body}}
+      { }
+  for (int i; i < 1; ++i)
+    for (int j; i < 1; ++j)  // expected-warning {{variables used in loop condition not modified in loop body}}
+      { }
+}
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td	(revision 150501)
+++ include/clang/Basic/DiagnosticSemaKinds.td	(working copy)
@@ -14,6 +14,11 @@
 let Component = "Sema" in {
 let CategoryName = "Semantic Issue" in {
 
+// For loop analysis
+def warn_variables_not_in_loop_body : Warning<
+  "variables used in loop condition not modified in loop body">,
+  InGroup<DiagGroup<"loop-analysis">>, DefaultIgnore;
+
 // Constant expressions
 def err_expr_not_ice : Error<
   "expression is not an %select{integer|integral}0 constant expression">;
Index: lib/Sema/SemaStmt.cpp
===================================================================
--- lib/Sema/SemaStmt.cpp	(revision 150501)
+++ lib/Sema/SemaStmt.cpp	(working copy)
@@ -19,6 +19,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/CharUnits.h"
 #include "clang/AST/DeclObjC.h"
+#include "clang/AST/EvaluatedExprVisitor.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/StmtObjC.h"
@@ -28,6 +29,7 @@
 #include "clang/Basic/TargetInfo.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
 using namespace clang;
 using namespace sema;
@@ -1009,6 +1011,150 @@
   return Owned(new (Context) DoStmt(Body, Cond, DoLoc, WhileLoc, CondRParen));
 }
 
+namespace {
+  // This visitor will traverse a conditional statement and store all
+  // the evaluated decls into a vector.  Simple is set to true if none
+  // of the excluded constructs are used.
+  class DeclExtractor : public EvaluatedExprVisitor<DeclExtractor> {
+    llvm::SmallPtrSet<VarDecl*, 4> &Decls;
+    bool Simple;
+    PartialDiagnostic &PDiag;
+public:
+  typedef EvaluatedExprVisitor<DeclExtractor> Inherited;
+
+  DeclExtractor(Sema &S, llvm::SmallPtrSet<VarDecl*, 4> &Decls,
+                PartialDiagnostic &PDiag) :
+      Inherited(S.Context), Decls(Decls), Simple(true), PDiag(PDiag) {}
+
+  bool isSimple() { return Simple; }
+
+  void VisitMemberExpr(MemberExpr *E) {
+    // Don't do analysis on classes.
+    Simple = false;
+    return;
+  }
+
+  void VisitCastExpr(CastExpr *E) {
+    // Don't do analysis on function calls or arrays.
+    if (E->getCastKind() == CK_FunctionToPointerDecay ||
+        E->getCastKind() == CK_ArrayToPointerDecay) {
+      Simple = false;
+      return;
+    }
+
+     Visit(E->getSubExpr());
+  }
+
+  void VisitDeclRefExpr(DeclRefExpr *E) {
+    VarDecl *VD = dyn_cast<VarDecl>(E->getDecl());
+    if (!VD) return;
+
+    PDiag << E->getSourceRange();
+
+    // Dont do analysis on pointers.
+    if (VD->getType()->isAnyPointerType()) {
+      Simple = false;
+      return;
+    }
+
+    Decls.insert(VD);
+  }
+
+  }; // end class DeclExtractor
+
+  // DeclMatcher checks to see if the decls are used in a non-evauluated
+  // context.  
+  class DeclMatcher : public StmtVisitor<DeclMatcher> {
+    llvm::SmallPtrSet<VarDecl*, 4> &Decls;
+    bool FoundDecl;
+
+public:
+  DeclMatcher(llvm::SmallPtrSet<VarDecl*, 4> &Decls, Stmt *Statement) :
+      Decls(Decls), FoundDecl(false) {
+    if (!Statement) return;
+    if (Expr *E = dyn_cast<Expr>(Statement)) {
+      VisitExpr(E);
+      return;
+    }
+
+    Visit(Statement);
+  }
+
+  void VisitStmt(Stmt *S) {
+    for (Stmt::child_range C = S->children(); C; ++C)
+      if (*C)
+        this->Visit(*C);
+  }
+
+  void VisitReturnStmt(ReturnStmt *S) {
+    FoundDecl = true;
+  }
+
+  void VisitBreakStmt(BreakStmt *S) {
+    FoundDecl = true;
+  }
+
+  void VisitGotoStmt(GotoStmt *S) {
+    FoundDecl = true;
+  }
+
+  void VisitCastExpr(CastExpr *E) {
+    // Decl is evaluated, not updated.  Ignore.
+    if (E->getCastKind() == CK_LValueToRValue)
+      if (isa<DeclRefExpr>(E->getSubExpr()))
+        return;
+
+    Visit(E->getSubExpr());
+  }
+
+  void VisitDeclRefExpr(DeclRefExpr *E) {
+    if (VarDecl *VD = dyn_cast<VarDecl>(E->getDecl()))
+      if (Decls.count(VD)) {
+        FoundDecl = true;
+        return;
+      }
+  }
+
+  bool FoundDeclInUse() { return FoundDecl; }
+
+  };  // end class DeclMatcher
+
+  void CheckForLoopConditionalStatement(Sema &S, Expr *Second,
+                                        Expr *Third, Stmt *Body) {
+    // Condition is empty
+    if (!Second) return;
+
+    if (S.Diags.getDiagnosticLevel(diag::warn_variables_not_in_loop_body,
+                                   Second->getLocStart())
+        == DiagnosticsEngine::Ignored)
+      return;
+
+    PartialDiagnostic PDiag = S.PDiag(diag::warn_variables_not_in_loop_body);
+    llvm::SmallPtrSet<VarDecl*, 4> Decls;
+    DeclExtractor DE(S, Decls, PDiag);
+    DE.Visit(Second);
+
+    // Don't analyze complex conditionals.
+    if (!DE.isSimple()) return;
+
+    // No decls found.
+    if (Decls.size() == 0) return;
+
+    // Don't warn on volatile decls.
+    for (llvm::SmallPtrSet<VarDecl*, 4>::iterator I = Decls.begin(),
+                                                  E = Decls.end();
+         I != E; ++I)
+      if ((*I)->getType().isVolatileQualified()) return;
+
+    if (!DeclMatcher(Decls, Second).FoundDeclInUse() &&
+        !DeclMatcher(Decls, Third).FoundDeclInUse() &&
+        !DeclMatcher(Decls, Body).FoundDeclInUse()) {
+      S.Diag(Second->getExprLoc(), PDiag);
+    }
+  }
+
+} // end namespace
+
 StmtResult
 Sema::ActOnForStmt(SourceLocation ForLoc, SourceLocation LParenLoc,
                    Stmt *First, FullExprArg second, Decl *secondVar,
@@ -1031,6 +1177,8 @@
     }
   }
 
+  CheckForLoopConditionalStatement(*this, second.get(), third.get(), Body);
+
   ExprResult SecondResult(second.release());
   VarDecl *ConditionVar = 0;
   if (secondVar) {
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to