/* * 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; } }