com.shapesecurity.shift.semantics.visitor.FinallyJumpReducer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shift-semantics Show documentation
Show all versions of shift-semantics Show documentation
generate an abstract semantic graph from a Shift AST
/*
* Copyright 2016 Shape Security, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.shapesecurity.shift.semantics.visitor;
import com.shapesecurity.functional.Pair;
import com.shapesecurity.functional.data.HashTable;
import com.shapesecurity.functional.data.ImmutableList;
import com.shapesecurity.functional.data.Maybe;
import com.shapesecurity.functional.data.Monoid;
import com.shapesecurity.shift.ast.BreakStatement;
import com.shapesecurity.shift.ast.ContinueStatement;
import com.shapesecurity.shift.ast.DoWhileStatement;
import com.shapesecurity.shift.ast.ForInStatement;
import com.shapesecurity.shift.ast.ForOfStatement;
import com.shapesecurity.shift.ast.ForStatement;
import com.shapesecurity.shift.ast.LabeledStatement;
import com.shapesecurity.shift.ast.Module;
import com.shapesecurity.shift.ast.Node;
import com.shapesecurity.shift.ast.Script;
import com.shapesecurity.shift.ast.SwitchStatement;
import com.shapesecurity.shift.ast.SwitchStatementWithDefault;
import com.shapesecurity.shift.ast.TryFinallyStatement;
import com.shapesecurity.shift.ast.WhileStatement;
import com.shapesecurity.shift.visitor.Director;
import com.shapesecurity.shift.visitor.MonoidalReducer;
import org.jetbrains.annotations.NotNull;
// Almost identical to JumpReducer, but also tracks the number of Finally statements that a jump exits. Try-catch statements with no syntactic finally are considered to have empty finally blocks.
// Gives a map from break/continue statements to the statement/loop broken. For labelled statements, map goes to the body of the statement, not the LabelledStatement
// Relies on the AST being valid: in particular, does not check that labelled continues are breaking loops rather than just statements.
// TODO could be waaaaay more typesafe than a map from nodes to nodes.
public class FinallyJumpReducer extends MonoidalReducer {
public static final FinallyJumpReducer INSTANCE = new FinallyJumpReducer();
private FinallyJumpReducer() {
super(new StateMonoid());
}
public static HashTable> extract(@NotNull FinallyJumpReducer.State state) {
assert state.unlabelledBreaks.length == 0;
assert state.labelledBreaks.length == 0;
assert state.unlabelledContinues.length == 0;
assert state.labelledContinues.length == 0;
return state.knownJumps;
}
@NotNull
public static HashTable> analyze(@NotNull Script script) {
return FinallyJumpReducer.extract(Director.reduceScript(INSTANCE, script));
}
@NotNull
public static HashTable> analyze(@NotNull Module module) {
return FinallyJumpReducer.extract(Director.reduceModule(INSTANCE, module));
}
@NotNull
private State loopHelper(@NotNull Node loopNode, @NotNull State body) { // no labels in loop bounds
HashTable> knownJumps = body.knownJumps;
knownJumps = body.unlabelledBreaks.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(loopNode, kv.right)), knownJumps);
knownJumps = body.unlabelledContinues.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(loopNode, kv.right)), knownJumps);
return new State(
HashTable.emptyUsingIdentity(),
body.labelledBreaks,
HashTable.emptyUsingIdentity(),
body.labelledContinues,
knownJumps
);
}
@NotNull
private State switchHelper(@NotNull Node switchNode, @NotNull State body) { // no labels in discriminant
return new State(
HashTable.emptyUsingIdentity(),
body.labelledBreaks,
body.unlabelledContinues,
body.labelledContinues,
body.unlabelledBreaks.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(switchNode, kv.right)), body.knownJumps)
);
}
@NotNull
@Override
public State reduceBreakStatement(@NotNull BreakStatement node) {
if (node.label.isJust()) {
String label = node.label.fromJust();
return new State(
HashTable.emptyUsingIdentity(),
HashTable.>>emptyUsingEquality().put(
label,
ImmutableList.of(new Pair<>(node, 0))
),
HashTable.emptyUsingIdentity(),
HashTable.emptyUsingEquality(),
HashTable.emptyUsingIdentity()
);
} else {
return new State(
HashTable.emptyUsingIdentity().put(node, 0),
HashTable.emptyUsingEquality(),
HashTable.emptyUsingIdentity(),
HashTable.emptyUsingEquality(),
HashTable.emptyUsingIdentity()
);
}
}
@NotNull
@Override
public State reduceContinueStatement(@NotNull ContinueStatement node) {
if (node.label.isJust()) {
String label = node.label.fromJust();
return new State(
HashTable.emptyUsingIdentity(),
HashTable.emptyUsingEquality(),
HashTable.emptyUsingIdentity(),
HashTable.>>emptyUsingEquality().put(
label,
ImmutableList.of(new Pair<>(node, 0))
),
HashTable.emptyUsingIdentity()
);
} else {
return new State(
HashTable.emptyUsingIdentity(),
HashTable.emptyUsingEquality(),
HashTable.emptyUsingIdentity().put(node, 0),
HashTable.emptyUsingEquality(),
HashTable.emptyUsingIdentity()
);
}
}
@NotNull
@Override
public State reduceDoWhileStatement(@NotNull DoWhileStatement node, @NotNull State body, @NotNull State test) {
return loopHelper(node, super.reduceDoWhileStatement(node, body, test));
}
@NotNull
@Override
public State reduceForInStatement(
@NotNull ForInStatement node, @NotNull State left, @NotNull State right, @NotNull State body
) {
return loopHelper(node, super.reduceForInStatement(node, left, right, body));
}
@NotNull
@Override
public State reduceForOfStatement(
@NotNull ForOfStatement node, @NotNull State left, @NotNull State right, @NotNull State body
) {
return loopHelper(node, super.reduceForOfStatement(node, left, right, body));
}
@NotNull
@Override
public State reduceForStatement(
@NotNull ForStatement node, @NotNull Maybe init, @NotNull Maybe test, @NotNull Maybe update,
@NotNull State body
) {
return loopHelper(node, super.reduceForStatement(node, init, test, update, body));
}
@NotNull
@Override
public State reduceLabeledStatement(@NotNull LabeledStatement node, @NotNull State body) {
ImmutableList> newBreaks = body.labelledBreaks.get(node.label).orJust(ImmutableList.empty());
ImmutableList> newContinues =
body.labelledContinues.get(node.label).orJust(ImmutableList.empty());
HashTable>> labelledBreaks = body.labelledBreaks.remove(node.label);
HashTable>> labelledContinues =
body.labelledContinues.remove(node.label);
HashTable> knownJumps = body.knownJumps;
knownJumps = newBreaks.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(node.body, kv.right)), knownJumps);
knownJumps = newContinues.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(node.body, kv.right)), knownJumps);
return new State(
body.unlabelledBreaks,
labelledBreaks,
body.unlabelledContinues,
labelledContinues,
knownJumps
);
}
@NotNull
@Override
public State reduceSwitchStatement(
@NotNull SwitchStatement node, @NotNull State discriminant, @NotNull ImmutableList cases
) {
return switchHelper(node, super.reduceSwitchStatement(node, discriminant, cases));
}
@NotNull
@Override
public State reduceSwitchStatementWithDefault(
@NotNull SwitchStatementWithDefault node, @NotNull State discriminant, @NotNull ImmutableList preDefaultCases,
@NotNull State defaultCase, @NotNull ImmutableList postDefaultCases
) {
return switchHelper(
node,
super.reduceSwitchStatementWithDefault(node, discriminant, preDefaultCases, defaultCase, postDefaultCases)
);
}
@NotNull
@Override
public State reduceTryFinallyStatement(
@NotNull TryFinallyStatement node, @NotNull State block, @NotNull Maybe catchClause, @NotNull State finalizer
) {
return super.reduceTryFinallyStatement(node, block, catchClause, finalizer.incrementFinalizers());
}
@NotNull
@Override
public State reduceWhileStatement(@NotNull WhileStatement node, @NotNull State test, @NotNull State body) {
return loopHelper(node, super.reduceWhileStatement(node, test, body));
}
public static final class State {
@NotNull
private final HashTable unlabelledBreaks;
@NotNull
private final HashTable>> labelledBreaks;
@NotNull
private final HashTable unlabelledContinues;
@NotNull
private final HashTable>> labelledContinues;
@NotNull
private final HashTable> knownJumps;
public State(
@NotNull HashTable unlabelledBreaks,
@NotNull HashTable>> labelledBreaks,
@NotNull HashTable unlabelledContinues,
@NotNull HashTable>> labelledContinues,
@NotNull HashTable> knownJumps
) {
this.unlabelledBreaks = unlabelledBreaks;
this.labelledBreaks = labelledBreaks;
this.unlabelledContinues = unlabelledContinues;
this.labelledContinues = labelledContinues;
this.knownJumps = knownJumps;
}
public State() {
this.unlabelledBreaks = HashTable.emptyUsingIdentity();
this.labelledBreaks = HashTable.emptyUsingEquality();
this.unlabelledContinues = HashTable.emptyUsingIdentity();
this.labelledContinues = HashTable.emptyUsingEquality();
this.knownJumps = HashTable.emptyUsingIdentity();
}
public State(@NotNull State a, @NotNull State b) {
this.unlabelledBreaks = a.unlabelledBreaks.merge(b.unlabelledBreaks);
this.labelledBreaks = a.labelledBreaks.merge(b.labelledBreaks, ImmutableList::append);
this.unlabelledContinues = a.unlabelledContinues.merge(b.unlabelledContinues);
this.labelledContinues = a.labelledContinues.merge(b.labelledContinues, ImmutableList::append);
this.knownJumps = a.knownJumps.merge(b.knownJumps);
}
public State incrementFinalizers() {
return new State(
this.unlabelledBreaks.map(x -> x + 1),
this.labelledBreaks.map(l -> l.map(p -> new Pair<>(p.left, p.right + 1))),
this.unlabelledContinues.map(x -> x + 1),
this.labelledContinues.map(l -> l.map(p -> new Pair<>(p.left, p.right + 1))),
this.knownJumps
);
}
}
private static final class StateMonoid implements Monoid {
@Override
@NotNull
public State identity() {
return new State();
}
@Override
@NotNull
public State append(State a, State b) {
if (a == b) {
return a;
}
return new State(a, b);
}
}
}