Skip to content

Commit

Permalink
Support optional variable binding in "catch"
Browse files Browse the repository at this point in the history
Allow expressions like:

try {
  JSON.parse(text);
  return true;
} catch {
  return false;
}
  • Loading branch information
tuchida authored and gbrail committed Jan 27, 2022
1 parent ef9609b commit 0d8f573
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 84 deletions.
2 changes: 1 addition & 1 deletion src/org/mozilla/javascript/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ private void visitStatement(Node node, int initialStackDepth) {
{
int localIndex = getLocalBlockRef(node);
int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);
String name = child.getString();
String name = child.getType() == Token.NAME ? child.getString() : "";
child = child.getNext();
visitExpression(child, 0); // load expression object
addStringPrefix(name);
Expand Down
40 changes: 25 additions & 15 deletions src/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -1236,27 +1236,34 @@ private Node transformTry(TryStatement node) {
Node catchBlocks = new Block();
for (CatchClause cc : node.getCatchClauses()) {
decompiler.addToken(Token.CATCH);
decompiler.addToken(Token.LP);

String varName = cc.getVarName().getIdentifier();
decompiler.addName(varName);

Name varName = cc.getVarName();
Node catchCond = null;
AstNode ccc = cc.getCatchCondition();
if (ccc != null) {
decompiler.addName(" ");
decompiler.addToken(Token.IF);
catchCond = transform(ccc);
} else {
catchCond = new EmptyExpression();
Node varNameNode = null;

if (varName != null) {
decompiler.addToken(Token.LP);
decompiler.addName(varName.getIdentifier());

varNameNode = createName(varName.getIdentifier());

AstNode ccc = cc.getCatchCondition();
if (ccc != null) {
decompiler.addName(" ");
decompiler.addToken(Token.IF);
catchCond = transform(ccc);
} else {
catchCond = new EmptyExpression();
}

decompiler.addToken(Token.RP);
}
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.LC);

Node body = transform(cc.getBody());
decompiler.addEOL(Token.RC);

catchBlocks.addChildToBack(createCatch(varName, catchCond, body, cc.getLineno()));
catchBlocks.addChildToBack(createCatch(varNameNode, catchCond, body, cc.getLineno()));
}
Node finallyBlock = null;
if (node.getFinallyBlock() != null) {
Expand Down Expand Up @@ -1529,11 +1536,14 @@ private static Node createString(String string) {
* @param stmts the statements in the catch clause
* @param lineno the starting line number of the catch clause
*/
private Node createCatch(String varName, Node catchCond, Node stmts, int lineno) {
private Node createCatch(Node varName, Node catchCond, Node stmts, int lineno) {
if (varName == null) {
varName = new Node(Token.EMPTY);
}
if (catchCond == null) {
catchCond = new Node(Token.EMPTY);
}
return new Node(Token.CATCH, createName(varName), catchCond, stmts, lineno);
return new Node(Token.CATCH, varName, catchCond, stmts, lineno);
}

private static Node initFunction(
Expand Down
79 changes: 54 additions & 25 deletions src/org/mozilla/javascript/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1643,39 +1643,68 @@ private TryStatement tryStatement() throws IOException {
reportError("msg.catch.unreachable");
}
int catchPos = ts.tokenBeg, lp = -1, rp = -1, guardPos = -1;
if (mustMatchToken(Token.LP, "msg.no.paren.catch", true)) lp = ts.tokenBeg;
Name varName = null;
AstNode catchCond = null;

mustMatchToken(Token.NAME, "msg.bad.catchcond", true);
switch (peekToken()) {
case Token.LP:
{
matchToken(Token.LP, true);
lp = ts.tokenBeg;
mustMatchToken(Token.NAME, "msg.bad.catchcond", true);

varName = createNameNode();
Comment jsdocNodeForName = getAndResetJsDoc();
if (jsdocNodeForName != null) {
varName.setJsDocNode(jsdocNodeForName);
}
String varNameString = varName.getIdentifier();
if (inUseStrictDirective) {
if ("eval".equals(varNameString)
|| "arguments".equals(varNameString)) {
reportError("msg.bad.id.strict", varNameString);
}
}

Name varName = createNameNode();
Comment jsdocNodeForName = getAndResetJsDoc();
if (jsdocNodeForName != null) {
varName.setJsDocNode(jsdocNodeForName);
}
String varNameString = varName.getIdentifier();
if (inUseStrictDirective) {
if ("eval".equals(varNameString) || "arguments".equals(varNameString)) {
reportError("msg.bad.id.strict", varNameString);
}
}
if (matchToken(Token.IF, true)) {
guardPos = ts.tokenBeg;
catchCond = expr();
} else {
sawDefaultCatch = true;
}

AstNode catchCond = null;
if (matchToken(Token.IF, true)) {
guardPos = ts.tokenBeg;
catchCond = expr();
} else {
sawDefaultCatch = true;
if (mustMatchToken(Token.RP, "msg.bad.catchcond", true)) {
rp = ts.tokenBeg;
}
mustMatchToken(Token.LC, "msg.no.brace.catchblock", true);
}
break;
case Token.LC:
if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) {
matchToken(Token.LC, true);
} else {
reportError("msg.no.paren.catch");
}
break;
default:
reportError("msg.no.paren.catch");
break;
}

if (mustMatchToken(Token.RP, "msg.bad.catchcond", true)) rp = ts.tokenBeg;
mustMatchToken(Token.LC, "msg.no.brace.catchblock", true);

Block catchBlock = (Block) statements();
tryEnd = getNodeEnd(catchBlock);
Scope catchScope = new Scope(catchPos);
CatchClause catchNode = new CatchClause(catchPos);
catchNode.setLineno(ts.lineno);
pushScope(catchScope);
try {
statements(catchScope);
} finally {
popScope();
}

tryEnd = getNodeEnd(catchScope);
catchNode.setVarName(varName);
catchNode.setCatchCondition(catchCond);
catchNode.setBody(catchBlock);
catchNode.setBody(catchScope);
if (guardPos != -1) {
catchNode.setIfPosition(guardPos - catchPos);
}
Expand Down
4 changes: 3 additions & 1 deletion src/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -4108,7 +4108,9 @@ public static Scriptable newCatchScope(

NativeObject catchScopeObject = new NativeObject();
// See ECMA 12.4
catchScopeObject.defineProperty(exceptionName, obj, ScriptableObject.PERMANENT);
if (exceptionName != null) {
catchScopeObject.defineProperty(exceptionName, obj, ScriptableObject.PERMANENT);
}

if (isVisible(cx, t)) {
// Add special Rhino object __exception__ defined in the catch
Expand Down
58 changes: 26 additions & 32 deletions src/org/mozilla/javascript/ast/CatchClause.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import org.mozilla.javascript.Token;

/**
* Node representing a catch-clause of a try-statement.
* Node type is {@link Token#CATCH}.
* Node representing a catch-clause of a try-statement. Node type is {@link Token#CATCH}.
*
* <pre><i>CatchClause</i> :
* <b>catch</b> ( <i><b>Identifier</b></i> [<b>if</b> Expression] ) Block</pre>
Expand All @@ -19,7 +18,7 @@ public class CatchClause extends AstNode {

private Name varName;
private AstNode catchCondition;
private Block body;
private Scope body;
private int ifPosition = -1;
private int lp = -1;
private int rp = -1;
Expand All @@ -28,8 +27,7 @@ public class CatchClause extends AstNode {
type = Token.CATCH;
}

public CatchClause() {
}
public CatchClause() {}

public CatchClause(int pos) {
super(pos);
Expand All @@ -41,6 +39,7 @@ public CatchClause(int pos, int len) {

/**
* Returns catch variable node
*
* @return catch variable
*/
public Name getVarName() {
Expand All @@ -49,17 +48,19 @@ public Name getVarName() {

/**
* Sets catch variable node, and sets its parent to this node.
*
* @param varName catch variable
* @throws IllegalArgumentException if varName is {@code null}
*/
public void setVarName(Name varName) {
assertNotNull(varName);
this.varName = varName;
varName.setParent(this);
if (varName != null) {
varName.setParent(this);
}
}

/**
* Returns catch condition node, if present
*
* @return catch condition node, {@code null} if not present
*/
public AstNode getCatchCondition() {
Expand All @@ -68,69 +69,61 @@ public AstNode getCatchCondition() {

/**
* Sets catch condition node, and sets its parent to this node.
* @param catchCondition catch condition node. Can be {@code null}.
*
* @param catchCondition catch condition node. Can be {@code null}.
*/
public void setCatchCondition(AstNode catchCondition) {
this.catchCondition = catchCondition;
if (catchCondition != null)
if (catchCondition != null) {
catchCondition.setParent(this);
}
}

/**
* Returns catch body
*/
public Block getBody() {
/** Returns catch body */
public Scope getBody() {
return body;
}

/**
* Sets catch body, and sets its parent to this node.
*
* @throws IllegalArgumentException if body is {@code null}
*/
public void setBody(Block body) {
public void setBody(Scope body) {
assertNotNull(body);
this.body = body;
body.setParent(this);
}

/**
* Returns left paren position
*/
/** Returns left paren position */
public int getLp() {
return lp;
}

/**
* Sets left paren position
*/
/** Sets left paren position */
public void setLp(int lp) {
this.lp = lp;
}

/**
* Returns right paren position
*/
/** Returns right paren position */
public int getRp() {
return rp;
}

/**
* Sets right paren position
*/
/** Sets right paren position */
public void setRp(int rp) {
this.rp = rp;
}

/**
* Sets both paren positions
*/
/** Sets both paren positions */
public void setParens(int lp, int rp) {
this.lp = lp;
this.rp = rp;
}

/**
* Returns position of "if" keyword
*
* @return position of "if" keyword, if present, or -1
*/
public int getIfPosition() {
Expand All @@ -139,6 +132,7 @@ public int getIfPosition() {

/**
* Sets position of "if" keyword
*
* @param ifPosition position of "if" keyword, if present, or -1
*/
public void setIfPosition(int ifPosition) {
Expand All @@ -161,8 +155,8 @@ public String toSource(int depth) {
}

/**
* Visits this node, the catch var name node, the condition if
* non-{@code null}, and the catch body.
* Visits this node, the catch var name node, the condition if non-{@code null}, and the catch
* body.
*/
@Override
public void visit(NodeVisitor v) {
Expand Down
11 changes: 9 additions & 2 deletions src/org/mozilla/javascript/optimizer/BodyCodegen.java
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,10 @@ private void generateStatement(Node node) {
int local = getLocalBlockRegister(node);
int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);

String name = child.getString(); // name of exception
String name = null;
if (child.getType() == Token.NAME) {
name = child.getString(); // name of exception
}
child = child.getNext();
generateExpression(child, node); // load expression object
if (scopeIndex == 0) {
Expand All @@ -701,7 +704,11 @@ private void generateStatement(Node node) {
// Load previous catch scope object
cfw.addALoad(local);
}
cfw.addPush(name);
if (name != null) {
cfw.addPush(name);
} else {
cfw.add(ByteCode.ACONST_NULL);
}
cfw.addALoad(contextLocal);
cfw.addALoad(variableObjectLocal);

Expand Down
3 changes: 1 addition & 2 deletions testsrc/org/mozilla/javascript/tests/ParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.mozilla.javascript.ast.Assignment;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.CatchClause;
import org.mozilla.javascript.ast.Comment;
import org.mozilla.javascript.ast.ConditionalExpression;
Expand Down Expand Up @@ -509,7 +508,7 @@ public void testLinenoTry() {
AstNode tryBlock = tryStmt.getTryBlock();
List<CatchClause> catchBlocks = tryStmt.getCatchClauses();
CatchClause catchClause = catchBlocks.get(0);
Block catchVarBlock = catchClause.getBody();
Scope catchVarBlock = catchClause.getBody();
Name catchVar = catchClause.getVarName();
AstNode finallyBlock = tryStmt.getFinallyBlock();
AstNode finallyStmt = (AstNode) finallyBlock.getFirstChild();
Expand Down
Loading

0 comments on commit 0d8f573

Please sign in to comment.