Skip to content

Commit dcd09e8

Browse files
authored
Merge pull request #11869 from CodaFi/unconditional-selfie-ban
Diagnose Infinite Recursion
2 parents d118e5c + 5c7b790 commit dcd09e8

File tree

8 files changed

+300
-1
lines changed

8 files changed

+300
-1
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ WARNING(unreachable_case,none,
243243
WARNING(switch_on_a_constant,none,
244244
"switch condition evaluates to a constant", ())
245245
NOTE(unreachable_code_note,none, "will never be executed", ())
246+
WARNING(warn_infinite_recursive_function,none,
247+
"all paths through this function will call itself", ())
246248

247249
// 'transparent' diagnostics
248250
ERROR(circular_transparent,none,

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ PASS(DefiniteInitialization, "definite-init",
118118
"Definite Initialization for Diagnostics")
119119
PASS(Devirtualizer, "devirtualizer",
120120
"Indirect Call Devirtualization")
121+
PASS(DiagnoseInfiniteRecursion, "diagnose-infinite-recursion",
122+
"Diagnose Infinitely-Recursive Code")
121123
PASS(DiagnoseStaticExclusivity, "diagnose-static-exclusivity",
122124
"Static Enforcement of Law of Exclusivity")
123125
PASS(DiagnoseUnreachable, "diagnose-unreachable",

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ set(MANDATORY_SOURCES
66
Mandatory/DIMemoryUseCollector.cpp
77
Mandatory/DIMemoryUseCollectorOwnership.cpp
88
Mandatory/DataflowDiagnostics.cpp
9+
Mandatory/DiagnoseInfiniteRecursion.cpp
910
Mandatory/DiagnoseStaticExclusivity.cpp
1011
Mandatory/DiagnoseUnreachable.cpp
1112
Mandatory/GuaranteedARCOpts.cpp
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//==-- DiagnoseInfiniteRecursion.cpp - Find infinitely-recursive applies --==//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file implements a diagnostic pass that detects deleterious forms of
14+
// recursive functions.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#define DEBUG_TYPE "infinite-recursion"
19+
#include "swift/AST/DiagnosticsSIL.h"
20+
#include "swift/AST/Expr.h"
21+
#include "swift/Parse/Lexer.h"
22+
#include "swift/SIL/CFG.h"
23+
#include "swift/SIL/SILArgument.h"
24+
#include "swift/SIL/SILInstruction.h"
25+
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
26+
#include "swift/SILOptimizer/PassManager/Passes.h"
27+
#include "swift/SILOptimizer/PassManager/Transforms.h"
28+
#include "swift/SILOptimizer/Utils/Devirtualize.h"
29+
#include "llvm/ADT/PostOrderIterator.h"
30+
#include "llvm/ADT/iterator_range.h"
31+
#include "llvm/Support/Debug.h"
32+
33+
using namespace swift;
34+
35+
template<typename...T, typename...U>
36+
static void diagnose(ASTContext &Context, SourceLoc loc, Diag<T...> diag,
37+
U &&...args) {
38+
Context.Diags.diagnose(loc,
39+
diag, std::forward<U>(args)...);
40+
}
41+
42+
static bool hasRecursiveCallInPath(SILBasicBlock &Block,
43+
SILFunction *Target,
44+
ModuleDecl *TargetModule) {
45+
// Process all instructions in the block to find applies that reference
46+
// the parent function. Also looks through vtables for statically
47+
// dispatched (witness) methods.
48+
for (auto &I : Block) {
49+
auto *AI = dyn_cast<ApplyInst>(&I);
50+
if (AI && AI->getCalleeFunction() && AI->getCalleeFunction() == Target)
51+
return true;
52+
53+
if (FullApplySite FAI = FullApplySite::isa(&I)) {
54+
// Don't touch dynamic dispatch.
55+
if (isa<ObjCMethodInst>(FAI.getCallee()))
56+
continue;
57+
58+
auto &M = FAI.getModule();
59+
if (auto *CMI = dyn_cast<ClassMethodInst>(FAI.getCallee())) {
60+
auto ClassType = CMI->getOperand()->getType();
61+
62+
// FIXME: If we're not inside the module context of the method,
63+
// we may have to deserialize vtables. If the serialized tables
64+
// are damaged, the pass will crash.
65+
//
66+
// Though, this has the added bonus of not looking into vtables
67+
// outside the current module. Because we're not doing IPA, let
68+
// alone cross-module IPA, this is all well and good.
69+
auto *BGC = ClassType.getNominalOrBoundGenericNominal();
70+
if (BGC && BGC->getModuleContext() != TargetModule) {
71+
continue;
72+
}
73+
74+
auto *F = getTargetClassMethod(M, ClassType, CMI);
75+
if (F == Target)
76+
return true;
77+
78+
continue;
79+
}
80+
81+
if (auto *WMI = dyn_cast<WitnessMethodInst>(FAI.getCallee())) {
82+
SILFunction *F;
83+
SILWitnessTable *WT;
84+
85+
std::tie(F, WT) = M.lookUpFunctionInWitnessTable(
86+
WMI->getConformance(), WMI->getMember());
87+
if (F == Target)
88+
return true;
89+
90+
continue;
91+
}
92+
}
93+
}
94+
return false;
95+
}
96+
97+
static bool hasInfinitelyRecursiveApply(SILFunction &Fn,
98+
SILFunction *TargetFn) {
99+
SmallPtrSet<SILBasicBlock *, 16> Visited;
100+
SmallVector<SILBasicBlock *, 16> WorkList;
101+
// Keep track of whether we found at least one recursive path.
102+
bool foundRecursion = false;
103+
104+
auto *TargetModule = TargetFn->getModule().getSwiftModule();
105+
auto analyzeSuccessor = [&](SILBasicBlock *Succ) -> bool {
106+
if (!Visited.insert(Succ).second)
107+
return false;
108+
109+
// If the successor block contains a recursive call, end analysis there.
110+
if (!hasRecursiveCallInPath(*Succ, TargetFn, TargetModule)) {
111+
WorkList.push_back(Succ);
112+
return false;
113+
}
114+
return true;
115+
};
116+
117+
// Seed the work list with the entry block.
118+
foundRecursion |= analyzeSuccessor(Fn.getEntryBlock());
119+
120+
while (!WorkList.empty()) {
121+
SILBasicBlock *CurBlock = WorkList.pop_back_val();
122+
// Found a path to the exit node without a recursive call.
123+
if (CurBlock->getTerminator()->isFunctionExiting())
124+
return false;
125+
126+
for (SILBasicBlock *Succ : CurBlock->getSuccessorBlocks())
127+
foundRecursion |= analyzeSuccessor(Succ);
128+
}
129+
return foundRecursion;
130+
}
131+
132+
namespace {
133+
class DiagnoseInfiniteRecursion : public SILFunctionTransform {
134+
public:
135+
DiagnoseInfiniteRecursion() {}
136+
137+
private:
138+
void run() override {
139+
SILFunction *Fn = getFunction();
140+
// Don't rerun diagnostics on deserialized functions.
141+
if (Fn->wasDeserializedCanonical())
142+
return;
143+
144+
// Ignore empty functions and straight-line thunks.
145+
if (Fn->empty() || Fn->isThunk() != IsNotThunk)
146+
return;
147+
148+
// If we can't diagnose it, there's no sense analyzing it.
149+
if (!Fn->hasLocation() || Fn->getLocation().getSourceLoc().isInvalid())
150+
return;
151+
152+
if (hasInfinitelyRecursiveApply(*Fn, Fn)) {
153+
diagnose(Fn->getModule().getASTContext(),
154+
Fn->getLocation().getSourceLoc(),
155+
diag::warn_infinite_recursive_function);
156+
}
157+
}
158+
};
159+
} // end anonymous namespace
160+
161+
SILTransform *swift::createDiagnoseInfiniteRecursion() {
162+
return new DiagnoseInfiniteRecursion();
163+
}

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ static void addMandatoryOptPipeline(SILPassPipelinePlan &P,
9898
P.addDiagnosticConstantPropagation();
9999
P.addGuaranteedARCOpts();
100100
P.addDiagnoseUnreachable();
101+
P.addDiagnoseInfiniteRecursion();
101102
P.addEmitDFDiagnostics();
102103
// Canonical swift requires all non cond_br critical edges to be split.
103104
P.addSplitNonCondBrCriticalEdges();
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// RUN: %target-swift-frontend -emit-sil -primary-file %s -o /dev/null -verify
2+
3+
func a() { // expected-warning {{all paths through this function will call itself}}
4+
a()
5+
}
6+
7+
func b(_ x : Int) { // expected-warning {{all paths through this function will call itself}}
8+
if x != 0 {
9+
b(x)
10+
} else {
11+
b(x+1)
12+
}
13+
}
14+
15+
func c(_ x : Int) {
16+
if x != 0 {
17+
c(5)
18+
}
19+
}
20+
21+
func d(_ x : Int) { // expected-warning {{all paths through this function will call itself}}
22+
var x = x
23+
if x != 0 {
24+
x += 1
25+
}
26+
return d(x)
27+
}
28+
29+
// Doesn't warn on mutually recursive functions
30+
31+
func e() { f() }
32+
func f() { e() }
33+
34+
func g() { // expected-warning {{all paths through this function will call itself}}
35+
while true { // expected-note {{condition always evaluates to true}}
36+
g()
37+
}
38+
39+
g() // expected-warning {{will never be executed}}
40+
}
41+
42+
func h(_ x : Int) {
43+
while (x < 5) {
44+
h(x+1)
45+
}
46+
}
47+
48+
func i(_ x : Int) { // expected-warning {{all paths through this function will call itself}}
49+
var x = x
50+
while (x < 5) {
51+
x -= 1
52+
}
53+
i(0)
54+
}
55+
56+
func j() -> Int { // expected-warning {{all paths through this function will call itself}}
57+
return 5 + j()
58+
}
59+
60+
func k() -> Any { // expected-warning {{all paths through this function will call itself}}
61+
return type(of: k())
62+
}
63+
64+
class S {
65+
convenience init(a: Int) { // expected-warning {{all paths through this function will call itself}}
66+
self.init(a: a)
67+
}
68+
init(a: Int?) {}
69+
70+
static func a() { // expected-warning {{all paths through this function will call itself}}
71+
return a()
72+
}
73+
74+
func b() { // expected-warning {{all paths through this function will call itself}}
75+
var i = 0
76+
repeat {
77+
i += 1
78+
b()
79+
} while (i > 5)
80+
}
81+
}
82+
83+
class T: S {
84+
// No warning, calls super
85+
override func b() {
86+
var i = 0
87+
repeat {
88+
i += 1
89+
super.b()
90+
} while (i > 5)
91+
}
92+
}
93+
94+
func == (l: S?, r: S?) -> Bool { // expected-warning {{all paths through this function will call itself}}
95+
if l == nil && r == nil { return true }
96+
guard let l = l, let r = r else { return false }
97+
return l === r
98+
}
99+
100+
public func == <Element>(lhs: Array<Element>, rhs: Array<Element>) -> Bool { // expected-warning {{all paths through this function will call itself}}
101+
return lhs == rhs
102+
}
103+
104+
func factorial(_ n : UInt) -> UInt { // expected-warning {{all paths through this function will call itself}}
105+
return (n != 0) ? factorial(n - 1) * n : factorial(1)
106+
}
107+
108+
func tr(_ key: String) -> String { // expected-warning {{all paths through this function will call itself}}
109+
return tr(key) ?? key // expected-warning {{left side of nil coalescing operator '??' has non-optional type}}
110+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %target-swift-frontend -emit-sil -primary-file %s -o /dev/null -verify
2+
3+
// REQUIRES: objc_interop
4+
5+
// A negative test that the infinite recursion pass doesn't diagnose dynamic
6+
// dispatch.
7+
8+
import Foundation
9+
10+
class MyRecursiveClass {
11+
required init() {}
12+
@objc dynamic func foo() {
13+
return type(of: self).foo(self)()
14+
}
15+
16+
@objc dynamic func foo2() {
17+
return self.foo()
18+
}
19+
}
20+

test/SILOptimizer/unreachable_code.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ class r20097963MyClass {
215215
}
216216
}
217217

218-
func die() -> Never { die() }
218+
func die() -> Never { die() } // expected-warning {{all paths through this function will call itself}}
219219

220220
func testGuard(_ a : Int) {
221221
guard case 4 = a else { } // expected-error {{'guard' body must not fall through, consider using a 'return' or 'throw'}}

0 commit comments

Comments
 (0)