Swift 如何检查函数是否始终返回值(即“不会从末尾掉下来”)?
我正在构建一个说教式编译器,我想检查函数是否总是返回一个值。我打算在语义分析步骤中这样做(因为这不在语言语法中涵盖) 在所有的流量控制语句中,这种说教式语言只有Swift 如何检查函数是否始终返回值(即“不会从末尾掉下来”)?,swift,compiler-construction,Swift,Compiler Construction,我正在构建一个说教式编译器,我想检查函数是否总是返回一个值。我打算在语义分析步骤中这样做(因为这不在语言语法中涵盖) 在所有的流量控制语句中,这种说教式语言只有if、else和while语句(因此没有do while、for、switch案例等)。请注意,else if也是可能的。以下是所有有效的示例代码段: (a) (b) (c) 我对此搜索了很多,但没有找到一个我能理解的伪算法。我搜索了软件路径测试、白盒测试以及其他相关的堆栈溢出问题,如和 我听说这可以通过使用图形和堆栈来解决,但我不知道如
if
、else
和while
语句(因此没有do while
、for
、switch
案例等)。请注意,else if
也是可能的。以下是所有有效的示例代码段:
(a)
(b)
(c)
我对此搜索了很多,但没有找到一个我能理解的伪算法。我搜索了软件路径测试、白盒测试以及其他相关的堆栈溢出问题,如和
我听说这可以通过使用图形和堆栈来解决,但我不知道如何实现这些策略
任何关于伪代码的帮助都会非常有用
(如果重要的话,我正在用Swift实现我的编译器)如果你有一个控制流图,检查函数是否总是返回就像检查函数末尾的隐式返回是否不可访问一样简单。因此,由于有大量的分析和优化需要一个CFG,所以构建一个CFG并不是一个坏主意 这就是说,即使没有控制流图,假设有一些常见的限制(特别是您可以接受类似
if(cond)return x;if(!cond)return y;
的内容,即使它相当于if(cond),该属性也可以直接检查返回x;否则返回y;
,这是允许的)。我还假设没有goto
,因为您没有在控制流语句列表中列出它(我没有假设break
和continue
,因为它们只出现在循环中,而循环并不重要)
我们只需要考虑法律块(即总是达到回报)的情况:
所以一个空块显然是不允许的,因为如果它是空的,它就不能返回。允许直接(即不在if或循环内)包含返回的块(如果它不在块的末尾,则块中返回后的所有内容都将无法访问,您可能还希望将其转换为错误或警告)
循环并不重要。也就是说,如果您的块包含一个循环,那么它仍然必须在循环之外有一个返回,即使循环包含一个返回,因为循环条件可能为false,所以我们甚至不需要检查循环内部的内容。对于do-while循环,这是不正确的,但是您没有这些
如果块直接包含带有else
的If
,并且then块和else块始终到达返回,则此块也始终到达返回。在这种情况下,if
-else
之后的所有内容都是不可访问的。否则,if
就像循环一样不重要
因此,在伪代码中,这将是:
alwaysReturns( {} ) = false
alwaysReturns( {return exp; ...rest} ) = true
alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
(alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)
所以,在思考了5个小时如何实现这一点后,我想出了一个不错的解决方案(至少到目前为止我还没能打破它)。事实上,我大部分时间都在浏览网页(运气不佳),而不是思考问题并试图自己解决问题 下面是我的实现(在Swift 4.2中,但语法相当容易理解),使用图表:
final class SemanticAnalyzer {
private var currentNode: Node!
private var rootNode: Node!
final class Node {
var nodes: [Node] = []
var returnsExplicitly = false
let parent: Node?
var elseNode: Node!
var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }
init(parent: Node?) {
self.parent = parent
}
func validate() -> Bool {
if alwaysReturns {
return true
} else {
return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
}
}
}
/// Initializes the components of the semantic analyzer.
func startAnalyzing() {
rootNode = Node(parent: nil)
currentNode = rootNode
}
/// Execute when an `if` statement is found.
func handleIfStatementFound() {
let ifNode = Node(parent: currentNode)
let elseNode = Node(parent: currentNode)
// Assigning is not necessary if the current node returns explicitly.
// But assigning is not allowed if the else node always returns, so we check if the current node always returns.
if !currentNode.alwaysReturns {
currentNode.elseNode = elseNode
}
currentNode.nodes += [ ifNode, elseNode ]
currentNode = ifNode
}
/// Execute when an `else` statement is found.
func handleElseStatementFound() {
currentNode = currentNode.elseNode
}
/// Execute when a branch scope is closed.
func handleBranchClosing() {
currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
}
/// Execute when a function return statement is found.
func handleReturnStatementFound() {
currentNode.returnsExplicitly = true
}
/// Determine whether the function analyzed always returns a value.
///
/// - Returns: whether the root node validates.
func validate() -> Bool {
return rootNode.validate()
}
}
基本上它的作用是:
if
语句时,创建2个新节点并将当前节点指向这两个节点(就像在二叉树节点中一样)else
语句时,我们只需将当前节点切换到先前在if
语句中创建的else节点if
语句的}
字符中),它将当前节点切换到父节点return
语句时,它可以假定当前节点将始终有一个返回值这适用于嵌套的if/else语句,以及根本没有返回值的分支。这是一种有趣的思维方式!在发布这个问题5小时后,我想出了一个解决方案(直到现在我才打破它,所以我认为它是正确的哈哈),但看到其他观点仍然很棒。在您的分析中,我要纠正的是,如果条件始终为真,则
while
循环很重要,例如while(true){return value};//从现在开始死代码
,你同意吗?:)@RogerOba是的,我的简化假设基本上是,我们对待所有条件,就好像它们总是对的或错的一样。如果您想特别处理while(true)
,那么是的,您需要关心循环是否包含返回。请注意,然后您还需要关心中断
和继续
(我假设您的语言必须具备while(true)
,这样才有意义)。事实上这不是哈哈,我也没有处理while(true)
,因为我无法判断编译时该条件是否总是真的(使用当前实现)。@RogerOba如果它的字面意思是while(true)
,那么您知道它总是正确的,但是如果它是while(someExp)
,检查someExp
是否始终为真在一般情况下是不可判定的。这实际上可能非常复杂,因为语言语法更复杂。可能它必须理解内部函数和闭包,它必须理解枚举,以查看是否覆盖了开关中的所有情况。它具有understand函数永远不会返回的(fata
if (condition) {
return value1
} else {
return value2
}
// No return value needed here
alwaysReturns( {} ) = false
alwaysReturns( {return exp; ...rest} ) = true
alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
(alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)
final class SemanticAnalyzer {
private var currentNode: Node!
private var rootNode: Node!
final class Node {
var nodes: [Node] = []
var returnsExplicitly = false
let parent: Node?
var elseNode: Node!
var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }
init(parent: Node?) {
self.parent = parent
}
func validate() -> Bool {
if alwaysReturns {
return true
} else {
return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
}
}
}
/// Initializes the components of the semantic analyzer.
func startAnalyzing() {
rootNode = Node(parent: nil)
currentNode = rootNode
}
/// Execute when an `if` statement is found.
func handleIfStatementFound() {
let ifNode = Node(parent: currentNode)
let elseNode = Node(parent: currentNode)
// Assigning is not necessary if the current node returns explicitly.
// But assigning is not allowed if the else node always returns, so we check if the current node always returns.
if !currentNode.alwaysReturns {
currentNode.elseNode = elseNode
}
currentNode.nodes += [ ifNode, elseNode ]
currentNode = ifNode
}
/// Execute when an `else` statement is found.
func handleElseStatementFound() {
currentNode = currentNode.elseNode
}
/// Execute when a branch scope is closed.
func handleBranchClosing() {
currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
}
/// Execute when a function return statement is found.
func handleReturnStatementFound() {
currentNode.returnsExplicitly = true
}
/// Determine whether the function analyzed always returns a value.
///
/// - Returns: whether the root node validates.
func validate() -> Bool {
return rootNode.validate()
}
}