Implement a warning to detect when a function will call itself recursively on
every code path. If a program ever calls such a function, the function will
attempt to call itself until it runs out of stack space.
This warning searches the CFG to determine if every codepath results in a self
call. In addition to the test for this warning, several other tests needed to
be fixed, and a pragma to prevent this warning where Clang really wants a stack
overflow.
Testing with this warning has already caught several buggy functions. Common
mistakes include: incorrect namespaces, wrapper classes not forwarding calls
properly, similarly named member function and data member, and failing to call
an overload of the same function. When run outside of template instantiations,
all true positives. In template instantiations, only 25% true positive.
Therefore, this warning is disabled in template instantiations. An example of
such a false positive is in the test cases.
http://llvm-reviews.chandlerc.com/D1864
Files:
test/Analysis/inlining/test-always-inline-size-option.c
test/Analysis/misc-ps-region-store.cpp
test/Analysis/cxx11-crashes.cpp
test/FixIt/typo.m
test/FixIt/fixit.c
test/Sema/unused-expr-system-header.c
test/Sema/warn-unused-function.c
test/Sema/attr-deprecated.c
test/Parser/cxx-using-declaration.cpp
test/Parser/expressions.c
test/CodeGen/functions.c
test/SemaCXX/statements.cpp
test/SemaCXX/warn-bool-conversion.cpp
test/SemaCXX/warn-infinite-recursion.cpp
test/SemaCXX/MicrosoftCompatibility.cpp
test/Lexer/gnu-flags.c
include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Basic/DiagnosticGroups.td
lib/Sema/AnalysisBasedWarnings.cpp
lib/Lex/Pragma.cpp
Index: test/Analysis/inlining/test-always-inline-size-option.c
===================================================================
--- test/Analysis/inlining/test-always-inline-size-option.c
+++ test/Analysis/inlining/test-always-inline-size-option.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-inline-max-stack-depth=3 -analyzer-config ipa-always-inline-size=3 -verify %s
+// RUN: %clang_cc1 -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-inline-max-stack-depth=3 -analyzer-config ipa-always-inline-size=3 -Wno-infinite-recursion -verify %s
void clang_analyzer_eval(int);
int nested5() {
Index: test/Analysis/misc-ps-region-store.cpp
===================================================================
--- test/Analysis/misc-ps-region-store.cpp
+++ test/Analysis/misc-ps-region-store.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -triple i386-apple-darwin9 -analyze -analyzer-checker=core,alpha.core,debug.ExprInspection -analyzer-store=region -verify -fblocks -analyzer-opt-analyze-nested-blocks %s -fexceptions -fcxx-exceptions
-// RUN: %clang_cc1 -triple x86_64-apple-darwin9 -analyze -analyzer-checker=core,alpha.core,debug.ExprInspection -analyzer-store=region -verify -fblocks -analyzer-opt-analyze-nested-blocks %s -fexceptions -fcxx-exceptions
+// RUN: %clang_cc1 -triple i386-apple-darwin9 -analyze -analyzer-checker=core,alpha.core,debug.ExprInspection -analyzer-store=region -verify -fblocks -analyzer-opt-analyze-nested-blocks %s -fexceptions -fcxx-exceptions -Wno-infinite-recursion
+// RUN: %clang_cc1 -triple x86_64-apple-darwin9 -analyze -analyzer-checker=core,alpha.core,debug.ExprInspection -analyzer-store=region -verify -fblocks -analyzer-opt-analyze-nested-blocks %s -fexceptions -fcxx-exceptions -Wno-infinite-recursion
void clang_analyzer_warnIfReached();
Index: test/Analysis/cxx11-crashes.cpp
===================================================================
--- test/Analysis/cxx11-crashes.cpp
+++ test/Analysis/cxx11-crashes.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -analyze -analyzer-checker=core -std=c++11 -verify %s
+// RUN: %clang_cc1 -analyze -analyzer-checker=core -std=c++11 -verify -Wno-infinite-recursion %s
// expected-no-diagnostics
// radar://11485149, PR12871
Index: test/FixIt/typo.m
===================================================================
--- test/FixIt/typo.m
+++ test/FixIt/typo.m
@@ -147,7 +147,8 @@
@end
#endif
-void f(A *a) {
+void f(A *a);
+void test_f(A *a) {
f(a) // expected-error{{expected ';' after expression}}
[a methodA] // expected-error{{expected ';' after expression}}
[A methodA] // expected-error{{expected ';' after expression}}
Index: test/FixIt/fixit.c
===================================================================
--- test/FixIt/fixit.c
+++ test/FixIt/fixit.c
@@ -65,7 +65,6 @@
void removeUnusedLabels(char c) {
L0 /*removed comment*/: c++; // expected-warning {{unused label}}
- removeUnusedLabels(c);
L1: // expected-warning {{unused label}}
c++;
/*preserved comment*/ L2 : c++; // expected-warning {{unused label}}
Index: test/Sema/unused-expr-system-header.c
===================================================================
--- test/Sema/unused-expr-system-header.c
+++ test/Sema/unused-expr-system-header.c
@@ -1,6 +1,7 @@
// RUN: %clang_cc1 -isystem %S/Inputs -fsyntax-only -verify %s
#include <unused-expr-system-header.h>
-void f(int i1, int i2) {
+void f(int i1, int i2);
+void g(int i1, int i2) {
POSSIBLY_BAD_MACRO(5);
STATEMENT_EXPR_MACRO(5);
COMMA_MACRO_1(i1 == i2, f(i1, i2)); // expected-warning {{comparison result unused}} \
Index: test/Sema/warn-unused-function.c
===================================================================
--- test/Sema/warn-unused-function.c
+++ test/Sema/warn-unused-function.c
@@ -1,6 +1,6 @@
-// RUN: %clang_cc1 -fsyntax-only -Wused-but-marked-unused -Wunused-function -Wunneeded-internal-declaration -verify %s
-// RUN: %clang_cc1 -fsyntax-only -verify -Wunused %s
-// RUN: %clang_cc1 -fsyntax-only -verify -Wall %s
+// RUN: %clang_cc1 -fsyntax-only -Wused-but-marked-unused -Wunused-function -Wunneeded-internal-declaration -Wno-infinite-recursion -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wno-infinite-recursion -verify -Wunused %s
+// RUN: %clang_cc1 -fsyntax-only -Wno-infinite-recursion -verify -Wall %s
void foo() {}
static void f2() {}
Index: test/Sema/attr-deprecated.c
===================================================================
--- test/Sema/attr-deprecated.c
+++ test/Sema/attr-deprecated.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -verify -fsyntax-only
+// RUN: %clang_cc1 %s -Wno-infinite-recursion -verify -fsyntax-only
int f() __attribute__((deprecated)); // expected-note 2 {{declared here}}
void g() __attribute__((deprecated));
Index: test/Parser/cxx-using-declaration.cpp
===================================================================
--- test/Parser/cxx-using-declaration.cpp
+++ test/Parser/cxx-using-declaration.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wno-infinite-recursion -verify %s
namespace A {
int VA;
Index: test/Parser/expressions.c
===================================================================
--- test/Parser/expressions.c
+++ test/Parser/expressions.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wno-infinite-recursion -verify %s
void test1() {
if (sizeof (int){ 1}) {} // sizeof compound literal
Index: test/CodeGen/functions.c
===================================================================
--- test/CodeGen/functions.c
+++ test/CodeGen/functions.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -triple i386-unknown-unknown -emit-llvm -o - -verify | FileCheck %s
+// RUN: %clang_cc1 %s -Wno-infinite-recursion -triple i386-unknown-unknown -emit-llvm -o - -verify | FileCheck %s
int g();
Index: test/SemaCXX/statements.cpp
===================================================================
--- test/SemaCXX/statements.cpp
+++ test/SemaCXX/statements.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -fsyntax-only -pedantic -verify
+// RUN: %clang_cc1 %s -fsyntax-only -Wno-infinite-recursion -pedantic -verify
void foo() {
return foo();
Index: test/SemaCXX/warn-bool-conversion.cpp
===================================================================
--- test/SemaCXX/warn-bool-conversion.cpp
+++ test/SemaCXX/warn-bool-conversion.cpp
@@ -2,7 +2,8 @@
int* j = false; // expected-warning{{initialization of pointer of type 'int *' to null from a constant boolean expression}}
-void foo(int* i, int *j=(false)) // expected-warning{{initialization of pointer of type 'int *' to null from a constant boolean expression}}
+void foo(int* i, int *j=(false)); // expected-warning{{initialization of pointer of type 'int *' to null from a constant boolean expression}}
+void bar()
{
foo(false); // expected-warning{{initialization of pointer of type 'int *' to null from a constant boolean expression}}
foo((int*)false); // no-warning: explicit cast
Index: test/SemaCXX/warn-infinite-recursion.cpp
===================================================================
--- test/SemaCXX/warn-infinite-recursion.cpp
+++ test/SemaCXX/warn-infinite-recursion.cpp
@@ -0,0 +1,92 @@
+// RUN: %clang_cc1 %s -fsyntax-only -verify
+
+void a() { // expected-warning{{call itself}}
+ a();
+}
+
+void b(int x) { // expected-warning{{call itself}}
+ if (x)
+ b(x);
+ else
+ b(x+1);
+}
+
+void c(int x) {
+ if (x)
+ c(5);
+}
+
+void d(int x) { // expected-warning{{call itself}}
+ if (x)
+ ++x;
+ return d(x);
+}
+
+// Doesn't warn on mutually recursive functions
+void e();
+void f();
+
+void e() { f(); }
+void f() { e(); }
+
+// Don't warn on infinite loops
+void g() {
+ while (true)
+ g();
+
+ g();
+}
+
+void h(int x) {
+ while (x < 5) {
+ h(x+1);
+ }
+}
+
+void i(int x) { // expected-warning{{call itself}}
+ while (x < 5) {
+ --x;
+ }
+ i(0);
+}
+
+int j() { // expected-warning{{call itself}}
+ return 5 + j();
+}
+
+class S {
+ static void a();
+ void b();
+};
+
+void S::a() { // expected-warning{{call itself}}
+ return a();
+}
+
+void S::b() { // expected-warning{{call itself}}
+ int i = 0;
+ do {
+ ++i;
+ b();
+ } while (i > 5);
+}
+
+// No warnings on templates
+// sum<0>() is instantiated, does recursively call itself, but never runs.
+template <int value>
+int sum() {
+ return value + sum<value/2>;
+}
+
+template<>
+int sum<0>() { return 1; }
+
+template<int x, int y>
+int calculate_value() {
+ if (x != y)
+ return sum<x - y>(); // This instantiates sum<0>() even if never called.
+ else
+ return 0;
+}
+
+int value = calculate_value<1,1>();
Index: test/SemaCXX/MicrosoftCompatibility.cpp
===================================================================
--- test/SemaCXX/MicrosoftCompatibility.cpp
+++ test/SemaCXX/MicrosoftCompatibility.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -triple i686-pc-win32 -fsyntax-only -std=c++11 -Wmicrosoft -verify -fms-compatibility -fexceptions -fcxx-exceptions
+// RUN: %clang_cc1 %s -triple i686-pc-win32 -fsyntax-only -std=c++11 -Wmicrosoft -Wno-infinite-recursion -verify -fms-compatibility -fexceptions -fcxx-exceptions
typedef unsigned short char16_t;
Index: test/Lexer/gnu-flags.c
===================================================================
--- test/Lexer/gnu-flags.c
+++ test/Lexer/gnu-flags.c
@@ -18,14 +18,15 @@
#if ALL || ZEROARGS
-// expected-warning@+9 {{must specify at least one argument for '...' parameter of variadic macro}}
+// expected-warning@+10 {{must specify at least one argument for '...' parameter of variadic macro}}
// expected-note@+4 {{macro 'efoo' defined here}}
// expected-warning@+3 {{token pasting of ',' and __VA_ARGS__ is a GNU extension}}
#endif
#define efoo(format, args...) foo(format , ##args)
-void foo( const char* c )
+void foo( const char* c );
+void foo_test()
{
efoo("6");
}
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -30,6 +30,10 @@
"been assigned">, InGroup<DiagGroup<"duplicate-enum">>, DefaultIgnore;
def note_duplicate_element : Note<"element %0 also has value %1">;
+def warn_infinite_recursive_function : Warning<
+ "all paths through this function will call itself">,
+ InGroup<InfiniteRecursion>;
+
// Constant expressions
def err_expr_not_ice : Error<
"expression is not an %select{integer|integral}0 constant expression">;
Index: include/clang/Basic/DiagnosticGroups.td
===================================================================
--- include/clang/Basic/DiagnosticGroups.td
+++ include/clang/Basic/DiagnosticGroups.td
@@ -161,6 +161,7 @@
def DanglingElse: DiagGroup<"dangling-else">;
def DanglingField : DiagGroup<"dangling-field">;
def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">;
+def InfiniteRecursion : DiagGroup<"infinite-recursion">;
def GNUImaginaryConstant : DiagGroup<"gnu-imaginary-constant">;
def IgnoredQualifiers : DiagGroup<"ignored-qualifiers">;
def : DiagGroup<"import">;
Index: lib/Sema/AnalysisBasedWarnings.cpp
===================================================================
--- lib/Sema/AnalysisBasedWarnings.cpp
+++ lib/Sema/AnalysisBasedWarnings.cpp
@@ -78,6 +78,69 @@
}
//===----------------------------------------------------------------------===//
+// Check for infinite self-recursion in functions
+//===----------------------------------------------------------------------===//
+
+enum RecursiveState {
+ Unknown,
+ AfterFunctionCall,
+ Reachable,
+};
+
+static void CheckForFunctionCall(Sema &S, const FunctionDecl *FD,
+ CFGBlock& Block, unsigned exitID,
+ llvm::SmallVectorImpl<RecursiveState> &states,
+ RecursiveState state) {
+ unsigned ID = Block.getBlockID();
+
+ if (states[ID] < state)
+ states[ID] = state;
+ else
+ return;
+
+ if (state == Reachable) {
+ for (CFGBlock::iterator I = Block.begin(), E = Block.end(); I != E; ++I) {
+ if (I->getKind() != CFGElement::Statement)
+ continue;
+
+ const CallExpr *CE = dyn_cast<CallExpr>(I->getAs<CFGStmt>()->getStmt());
+ if (CE && CE->getCalleeDecl() == FD) {
+ if (const CXXMemberCallExpr *MCE = dyn_cast<CXXMemberCallExpr>(CE)) {
+ if (isa<CXXThisExpr>(MCE->getImplicitObjectArgument())) {
+ state = AfterFunctionCall;
+ break;
+ }
+ } else {
+ state = AfterFunctionCall;
+ break;
+ }
+ }
+ }
+ }
+
+ for (CFGBlock::succ_iterator I = Block.succ_begin(), E = Block.succ_end();
+ I != E; ++I)
+ if (*I)
+ CheckForFunctionCall(S, FD, **I, exitID, states, state);
+}
+
+static void CheckRecursiveFuction(Sema &S, const FunctionDecl *FD,
+ const Stmt *Body,
+ AnalysisDeclContext &AC) {
+ if (FD->isTemplateInstantiation()) return;
+ CFG *cfg = AC.getCFG();
+ if (cfg == 0) return;
+ llvm::SmallVector<RecursiveState, 16> states(cfg->getNumBlockIDs(), Unknown);
+ CheckForFunctionCall(S, FD, cfg->getEntry(), cfg->getExit().getBlockID(),
+ states, Reachable);
+
+ // Check that the exit block is reachable. This prevents triggering the
+ // warning on
+ if (states[cfg->getExit().getBlockID()] == AfterFunctionCall)
+ S.Diag(Body->getLocStart(), diag::warn_infinite_recursive_function);
+}
+
+//===----------------------------------------------------------------------===//
// Check for missing return value.
//===----------------------------------------------------------------------===//
@@ -1770,6 +1833,16 @@
D->getLocStart()) != DiagnosticsEngine::Ignored)
diagnoseRepeatedUseOfWeak(S, fscope, D, AC.getParentMap());
+
+ // Check for infinite self-recursion in functions
+ if (Diags.getDiagnosticLevel(diag::warn_infinite_recursive_function,
+ D->getLocStart())
+ != DiagnosticsEngine::Ignored) {
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ CheckRecursiveFuction(S, FD, Body, AC);
+ }
+ }
+
// Collect statistics about the CFG if it was built.
if (S.CollectStats && AC.isCFGBuilt()) {
++NumFunctionsAnalyzed;
Index: lib/Lex/Pragma.cpp
===================================================================
--- lib/Lex/Pragma.cpp
+++ lib/Lex/Pragma.cpp
@@ -926,12 +926,20 @@
#ifdef _MSC_VER
#pragma warning(disable : 4717)
#endif
+// Disable Clang's warning about infinite recursion.
+#ifdef __clang__
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Winfinite-recursion"
+#endif
void DebugOverflowStack() {
DebugOverflowStack();
}
#ifdef _MSC_VER
#pragma warning(default : 4717)
#endif
+#ifdef __clang__
+ #pragma clang diagnostic pop
+#endif
};
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits