blob: 6501582e1f0bf341226ffd3064e1bfe7eef40af4 [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
class ReturnChecker extends Visitor {
constructor(program)
{
super();
this.returnStyle = {
DefinitelyReturns: "Definitely Returns",
DefinitelyDoesntReturn: "Definitely Doesn't Return",
HasntReturnedYet: "Hasn't Returned Yet"
};
this._program = program;
}
_mergeReturnStyle(a, b)
{
if (!a)
return b;
if (!b)
return a;
switch (a) {
case this.returnStyle.DefinitelyReturns:
case this.returnStyle.DefinitelyDoesntReturn:
if (a == b)
return a;
return this.returnStyle.HasntReturnedYet;
case this.returnStyle.HasntReturnedYet:
return this.returnStyle.HasntReturnedYet;
default:
throw new Error("Bad return style: " + a);
}
}
visitFuncDef(node)
{
if (node.returnType.equals(node.program.intrinsics.void))
return;
let bodyValue = node.body.visit(this);
if (bodyValue == this.returnStyle.DefinitelyDoesntReturn || bodyValue == this.returnStyle.HasntReturnedYet)
throw new WTypeError(node.origin.originString, "Function does not return");
}
visitBlock(node)
{
for (let statement of node.statements) {
switch (statement.visit(this)) {
case this.returnStyle.DefinitelyReturns:
return this.returnStyle.DefinitelyReturns;
case this.returnStyle.DefinitelyDoesntReturn:
return this.returnStyle.DefinitelyDoesntReturn;
case this.returnStyle.HasntReturnedYet:
continue;
}
}
return this.returnStyle.HasntReturnedYet;
}
visitIfStatement(node)
{
if (node.elseBody) {
let bodyValue = node.body.visit(this);
let elseValue = node.elseBody.visit(this);
return this._mergeReturnStyle(bodyValue, elseValue);
}
return this.returnStyle.HasntReturnedYet;
}
_isBoolCastFromLiteralTrue(node)
{
return node.isCast && node.returnType instanceof TypeRef && node.returnType.equals(this._program.intrinsics.bool) && node.argumentList.length == 1 && node.argumentList[0] instanceof BoolLiteral && node.argumentList[0].value;
}
visitWhileLoop(node)
{
let bodyReturn = node.body.visit(this);
if (node.conditional instanceof CallExpression && this._isBoolCastFromLiteralTrue(node.conditional)) {
switch (bodyReturn) {
case this.returnStyle.DefinitelyReturns:
return this.returnStyle.DefinitelyReturns;
case this.returnStyle.DefinitelyDoesntReturn:
case this.returnStyle.HasntReturnedYet:
return this.returnStyle.HasntReturnedYet;
}
}
return this.returnStyle.HasntReturnedYet;
}
visitDoWhileLoop(node)
{
let result = this.returnStyle.HasntReturnedYet;
switch (node.body.visit(this)) {
case this.returnStyle.DefinitelyReturns:
result = this.returnStyle.DefinitelyReturns;
case this.returnStyle.DefinitelyDoesntReturn:
case this.returnStyle.HasntReturnedYet:
result = this.returnStyle.HasntReturnedYet;
}
return result;
}
visitForLoop(node)
{
let bodyReturn = node.body.visit(this);
if (node.condition === undefined || this._isBoolCastFromLiteralTrue(node.condition)) {
switch (bodyReturn) {
case this.returnStyle.DefinitelyReturns:
return this.returnStyle.DefinitelyReturns;
case this.returnStyle.DefinitelyDoesntReturn:
case this.returnStyle.HasntReturnedYet:
return this.returnStyle.HasntReturnedYet;
}
}
}
visitSwitchStatement(node)
{
// FIXME: This seems like it's missing things. For example, we need to be smart about this:
//
// for (;;) {
// switch (...) {
// ...
// continue; // This continues the for loop
// }
// }
//
// I'm not sure what that means for this analysis. I'm starting to think that the right way to
// build this analysis is to run a visitor that builds a CFG and then analyze the CFG.
// https://bugs.webkit.org/show_bug.cgi?id=177172
let returnStyle = null;
for (let switchCase of node.switchCases) {
let bodyStyle = switchCase.body.visit(this);
// FIXME: The fact that we need this demonstrates the need for CFG analysis.
if (bodyStyle == this.returnStyle.DefinitelyDoesntReturn)
bodyStyle = this.returnStyle.HasntReturnedYet;
returnStyle = this._mergeReturnStyle(returnStyle, bodyStyle);
}
return returnStyle;
}
visitReturn(node)
{
return this.returnStyle.DefinitelyReturns;
}
visitTrapStatement(node)
{
return this.returnStyle.DefinitelyReturns;
}
visitBreak(node)
{
return this.returnStyle.DefinitelyDoesntReturn;
}
visitContinue(node)
{
// FIXME: This seems wrong. Consider a loop like:
//
// int foo() { for (;;) { continue; } }
//
// This program shouldn't claim that the problem is that it doesn't return.
// https://bugs.webkit.org/show_bug.cgi?id=177172
return this.returnStyle.DefinitelyDoesntReturn;
}
}