mirror of
https://github.com/github/codeql.git
synced 2026-05-25 00:27:09 +02:00
Merge pull request #91 from microsoft/powershell-cfg-for-if-and-match
PS: CFG for `if`, `match`, exceptions
This commit is contained in:
@@ -3,7 +3,7 @@ import powershell
|
||||
class ArrayExpr extends @array_expression, Expr {
|
||||
override SourceLocation getLocation() { array_expression_location(this, result) }
|
||||
|
||||
StmtBlock getStatementBlock() { array_expression(this, result) }
|
||||
StmtBlock getStmtBlock() { array_expression(this, result) }
|
||||
|
||||
override string toString() { result = "ArrayExpression at: " + this.getLocation().toString() }
|
||||
override string toString() { result = "@(...)" }
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ class Attribute extends @attribute, AttributeBase {
|
||||
|
||||
NamedAttributeArgument getANamedArgument() { result = this.getNamedArgument(_) }
|
||||
|
||||
int getNumberOfArguments() { result = count(this.getAPositionalArgument()) }
|
||||
|
||||
Expr getPositionalArgument(int i) { attribute_positional_argument(this, i, result) }
|
||||
|
||||
Expr getAPositionalArgument() { result = this.getPositionalArgument(_) }
|
||||
|
||||
int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgument()) }
|
||||
}
|
||||
|
||||
@@ -16,4 +16,24 @@ class CatchClause extends @catch_clause, Ast {
|
||||
predicate isCatchAll() { not exists(this.getACatchType()) }
|
||||
|
||||
TryStmt getTryStmt() { result.getACatchClause() = this }
|
||||
|
||||
predicate isLast() {
|
||||
exists(TryStmt ts, int last |
|
||||
ts = this.getTryStmt() and
|
||||
last = max(int i | exists(ts.getCatchClause(i))) and
|
||||
this = ts.getCatchClause(last)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralCatchClause extends CatchClause {
|
||||
GeneralCatchClause() { this.isCatchAll() }
|
||||
|
||||
override string toString() { result = "catch {...}" }
|
||||
}
|
||||
|
||||
class SpecificCatchClause extends CatchClause {
|
||||
SpecificCatchClause() { not this.isCatchAll() }
|
||||
|
||||
override string toString() { result = "catch[...] {...}" }
|
||||
}
|
||||
|
||||
@@ -15,9 +15,28 @@ class Cmd extends @command, CmdBase {
|
||||
|
||||
CmdElement getElement(int i) { command_command_element(this, i, result) }
|
||||
|
||||
Redirection getRedirection(int i) { command_redirection(this, i, result) }
|
||||
StringConstExpr getCmdName() { result = this.getElement(0) }
|
||||
|
||||
CmdElement getAnElement() { result = this.getElement(_) }
|
||||
Expr getArgument(int i) {
|
||||
result =
|
||||
rank[i + 1](CmdElement e, int j |
|
||||
e = this.getElement(j) and
|
||||
not e instanceof CmdParameter and
|
||||
j > 0 // 0'th element is the command name itself
|
||||
|
|
||||
e order by j
|
||||
)
|
||||
}
|
||||
|
||||
Expr getNamedArgument(string name) {
|
||||
exists(int i, CmdParameter p |
|
||||
this.getElement(i) = p and
|
||||
p.getName() = name and
|
||||
result = p.getArgument()
|
||||
)
|
||||
}
|
||||
|
||||
Redirection getRedirection(int i) { command_redirection(this, i, result) }
|
||||
|
||||
Redirection getARedirection() { result = this.getRedirection(_) }
|
||||
}
|
||||
|
||||
@@ -5,7 +5,19 @@ class CmdParameter extends @command_parameter, CmdElement {
|
||||
|
||||
string getName() { command_parameter(this, result) }
|
||||
|
||||
Expr getArgument() { command_parameter_argument(this, result) }
|
||||
Expr getArgument() {
|
||||
// When an argumnt is of the form -Name:$var
|
||||
command_parameter_argument(this, result)
|
||||
or
|
||||
// When an argument is of the form -Name $var
|
||||
exists(int i, Cmd cmd |
|
||||
cmd = this.getCmd() and
|
||||
cmd.getElement(i) = this and
|
||||
result = cmd.getElement(i + 1)
|
||||
)
|
||||
}
|
||||
|
||||
Cmd getCmd() { result.getElement(_) = this }
|
||||
|
||||
override string toString() { command_parameter(this, result) }
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ class IfStmt extends @if_statement, Stmt {
|
||||
|
||||
StmtBlock getThen(int i) { if_statement_clause(this, i, _, result) }
|
||||
|
||||
int getNumberOfConditions() { result = count(this.getACondition()) }
|
||||
|
||||
StmtBlock getAThen() { result = this.getThen(_) }
|
||||
|
||||
/** ..., if any. */
|
||||
StmtBlock getElse() { if_statement_else(this, result) }
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import powershell
|
||||
|
||||
class InvokeMemberExpression extends @invoke_member_expression, MemberExprBase {
|
||||
class InvokeMemberExpr extends @invoke_member_expression, MemberExprBase {
|
||||
override SourceLocation getLocation() { invoke_member_expression_location(this, result) }
|
||||
|
||||
Expr getExpression() { invoke_member_expression(this, result, _) }
|
||||
Expr getBase() { invoke_member_expression(this, result, _) }
|
||||
|
||||
CmdElement getMember() { invoke_member_expression(this, _, result) }
|
||||
|
||||
@@ -11,5 +11,5 @@ class InvokeMemberExpression extends @invoke_member_expression, MemberExprBase {
|
||||
|
||||
Expr getAnArgument() { invoke_member_expression_argument(this, _, result) }
|
||||
|
||||
override string toString() { result = "ArrayExpression at: " + this.getLocation().toString() }
|
||||
override string toString() { result = "call to " + this.getMember() }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import powershell
|
||||
|
||||
class NamedAttributeArgument extends @named_attribute_argument {
|
||||
string toString() { result = this.getValue().toString() }
|
||||
class NamedAttributeArgument extends @named_attribute_argument, Ast {
|
||||
final override string toString() { result = this.getValue().toString() }
|
||||
|
||||
SourceLocation getLocation() { named_attribute_argument_location(this, result) }
|
||||
final override SourceLocation getLocation() { named_attribute_argument_location(this, result) }
|
||||
|
||||
string getName() { named_attribute_argument(this, result, _) }
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import powershell
|
||||
|
||||
class StringConstExpression extends @string_constant_expression, BaseConstExpr {
|
||||
class StringConstExpr extends @string_constant_expression, BaseConstExpr {
|
||||
StringLiteral getValue() { string_constant_expression(this, result) }
|
||||
|
||||
/** Get the full string literal with all its parts concatenated */
|
||||
override string toString() { result = getValue().toString() }
|
||||
override string toString() { result = this.getValue().toString() }
|
||||
|
||||
override SourceLocation getLocation() { string_constant_expression_location(this, result) }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ class StringLiteral extends @string_literal {
|
||||
|
||||
/** Get the full string literal with all its parts concatenated */
|
||||
string toString() {
|
||||
result = concat(int i | i = [0 .. getNumContinuations()] | getContinuation(i), "\n")
|
||||
result = this.getValue()
|
||||
}
|
||||
|
||||
string getValue() {
|
||||
result = concat(int i | i = [0 .. this.getNumContinuations()] | this.getContinuation(i), "\n")
|
||||
}
|
||||
|
||||
SourceLocation getLocation() { string_literal_location(this, result) }
|
||||
|
||||
@@ -14,4 +14,5 @@ class TryStmt extends @try_statement, Stmt {
|
||||
|
||||
StmtBlock getBody() { try_statement(this, result) }
|
||||
|
||||
predicate hasFinally() { exists(this.getFinally()) }
|
||||
}
|
||||
|
||||
@@ -69,10 +69,11 @@ module SuccessorTypes {
|
||||
* A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`)
|
||||
* or a matching successor (`MatchingSuccessor`)
|
||||
*/
|
||||
class ConditionalSuccessor extends SuccessorType {
|
||||
abstract class ConditionalSuccessor extends SuccessorType {
|
||||
boolean value;
|
||||
|
||||
ConditionalSuccessor() { this = CfgImpl::TBooleanSuccessor(value) }
|
||||
bindingset[value]
|
||||
ConditionalSuccessor() { any() }
|
||||
|
||||
/** Gets the Boolean value of this successor. */
|
||||
final boolean getValue() { result = value }
|
||||
@@ -80,7 +81,13 @@ module SuccessorTypes {
|
||||
override string toString() { result = this.getValue().toString() }
|
||||
}
|
||||
|
||||
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor { }
|
||||
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor {
|
||||
BooleanSuccessor() { this = CfgImpl::TBooleanSuccessor(value) }
|
||||
}
|
||||
|
||||
class MatchingSuccessor extends ConditionalSuccessor, CfgImpl::TMatchingSuccessor {
|
||||
MatchingSuccessor() { this = CfgImpl::TMatchingSuccessor(value) }
|
||||
}
|
||||
|
||||
class ReturnSuccessor extends SuccessorType, CfgImpl::TReturnSuccessor {
|
||||
final override string toString() { result = "return" }
|
||||
@@ -94,8 +101,8 @@ module SuccessorTypes {
|
||||
final override string toString() { result = "continue" }
|
||||
}
|
||||
|
||||
class RaiseSuccessor extends SuccessorType, CfgImpl::TRaiseSuccessor {
|
||||
final override string toString() { result = "raise" }
|
||||
class ThrowSuccessor extends SuccessorType, CfgImpl::TThrowSuccessor {
|
||||
final override string toString() { result = "throw" }
|
||||
}
|
||||
|
||||
class ExitSuccessor extends SuccessorType, CfgImpl::TExitSuccessor {
|
||||
|
||||
@@ -8,16 +8,23 @@ private import powershell
|
||||
private import semmle.code.powershell.controlflow.ControlFlowGraph
|
||||
private import ControlFlowGraphImpl as CfgImpl
|
||||
private import SuccessorTypes
|
||||
private import codeql.util.Boolean
|
||||
|
||||
// TODO: We most likely need a TrapCompletion as well
|
||||
private newtype TCompletion =
|
||||
TSimpleCompletion() or
|
||||
TBooleanCompletion(boolean b) { b in [false, true] } or
|
||||
TBooleanCompletion(Boolean b) or
|
||||
TReturnCompletion() or
|
||||
TBreakCompletion() or
|
||||
TContinueCompletion() or
|
||||
TRaiseCompletion() or
|
||||
TExitCompletion()
|
||||
TThrowCompletion() or
|
||||
TExitCompletion() or
|
||||
TMatchingCompletion(Boolean b)
|
||||
|
||||
private predicate commandThrows(Cmd c, boolean unconditional) {
|
||||
c.getNamedArgument("ErrorAction").(StringConstExpr).getValue().getValue() = "Stop" and
|
||||
if c.getName() = "Write-Error" then unconditional = true else unconditional = false
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate completionIsValidForStmt(Ast n, Completion c) {
|
||||
@@ -26,6 +33,16 @@ private predicate completionIsValidForStmt(Ast n, Completion c) {
|
||||
or
|
||||
n instanceof ContinueStmt and
|
||||
c instanceof ContinueCompletion
|
||||
or
|
||||
n instanceof ThrowStmt and
|
||||
c instanceof ThrowCompletion
|
||||
or
|
||||
exists(boolean unconditional | commandThrows(n, unconditional) |
|
||||
c instanceof ThrowCompletion
|
||||
or
|
||||
unconditional = false and
|
||||
c instanceof SimpleCompletion
|
||||
)
|
||||
}
|
||||
|
||||
/** A completion of a statement or an expression. */
|
||||
@@ -40,6 +57,14 @@ abstract class Completion extends TCompletion {
|
||||
not isBooleanConstant(n, _) and
|
||||
this = TBooleanCompletion(_)
|
||||
)
|
||||
or
|
||||
mustHaveMatchingCompletion(n) and
|
||||
(
|
||||
exists(boolean value | isMatchingConstant(n, value) | this = TMatchingCompletion(value))
|
||||
or
|
||||
not isMatchingConstant(n, _) and
|
||||
this = TMatchingCompletion(_)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isValidForSpecific(Ast n) { this.isValidForSpecific0(n) }
|
||||
@@ -74,11 +99,18 @@ private predicate isBooleanConstant(Ast n, boolean value) {
|
||||
none() // TODO
|
||||
}
|
||||
|
||||
private predicate isMatchingConstant(Ast n, boolean value) {
|
||||
inMatchingContext(n) and
|
||||
none() // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a normal completion of `n` must be a Boolean completion.
|
||||
*/
|
||||
private predicate mustHaveBooleanCompletion(Ast n) { inBooleanContext(n) }
|
||||
|
||||
private predicate mustHaveMatchingCompletion(Ast n) { inMatchingContext(n) }
|
||||
|
||||
/**
|
||||
* Holds if `n` is used in a Boolean context. That is, the value
|
||||
* that `n` evaluates to determines a true/false branch successor.
|
||||
@@ -128,6 +160,22 @@ private predicate inBooleanContext(Ast n) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* From: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_switch?view=powershell-7.4:
|
||||
* ```
|
||||
* switch [-regex | -wildcard | -exact] [-casesensitive] (<test-expression>)
|
||||
* {
|
||||
* "string" | number | variable | { <value-scriptblock> } { <action-scriptblock> }
|
||||
* default { <action-scriptblock> } # optional
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private predicate inMatchingContext(Ast n) {
|
||||
n = any(SwitchStmt switch).getAPattern()
|
||||
or
|
||||
n = any(CatchClause cc).getACatchType()
|
||||
}
|
||||
|
||||
/**
|
||||
* A completion that represents normal evaluation of a statement or an
|
||||
* expression.
|
||||
@@ -159,7 +207,7 @@ abstract class ConditionalCompletion extends NormalCompletion {
|
||||
* A completion that represents evaluation of an expression
|
||||
* with a Boolean value.
|
||||
*/
|
||||
class BooleanCompletion extends ConditionalCompletion, NormalCompletion, TBooleanCompletion {
|
||||
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
|
||||
BooleanCompletion() { this = TBooleanCompletion(value) }
|
||||
|
||||
/** Gets the dual Boolean completion. */
|
||||
@@ -180,6 +228,18 @@ class FalseCompletion extends BooleanCompletion {
|
||||
FalseCompletion() { this.getValue() = false }
|
||||
}
|
||||
|
||||
class MatchCompletion extends ConditionalCompletion, TMatchingCompletion {
|
||||
MatchCompletion() { this = TMatchingCompletion(value) }
|
||||
|
||||
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
|
||||
|
||||
predicate isMatch() { this.getValue() = true }
|
||||
|
||||
predicate isNonMatch() { this.getValue() = false }
|
||||
|
||||
override string toString() { if this.isMatch() then result = "match" else result = "nonmatch" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A completion that represents evaluation of a statement or an
|
||||
* expression resulting in a return.
|
||||
@@ -214,10 +274,10 @@ class ContinueCompletion extends Completion, TContinueCompletion {
|
||||
* A completion that represents evaluation of a statement or an
|
||||
* expression resulting in a thrown exception.
|
||||
*/
|
||||
class RaiseCompletion extends Completion, TRaiseCompletion {
|
||||
override RaiseSuccessor getAMatchingSuccessorType() { any() }
|
||||
class ThrowCompletion extends Completion, TThrowCompletion {
|
||||
override ThrowSuccessor getAMatchingSuccessorType() { any() }
|
||||
|
||||
override string toString() { result = "raise" }
|
||||
override string toString() { result = "throw" }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
private import powershell
|
||||
private import codeql.controlflow.Cfg as CfgShared
|
||||
private import codeql.util.Boolean
|
||||
private import semmle.code.powershell.controlflow.ControlFlowGraph
|
||||
private import Completion
|
||||
|
||||
@@ -51,7 +52,7 @@ private module CfgInput implements CfgShared::InputSig<Location> {
|
||||
}
|
||||
|
||||
predicate isAbnormalExitType(SuccessorType t) {
|
||||
t instanceof Cfg::SuccessorTypes::RaiseSuccessor or
|
||||
t instanceof Cfg::SuccessorTypes::ThrowSuccessor or
|
||||
t instanceof Cfg::SuccessorTypes::ExitSuccessor
|
||||
}
|
||||
}
|
||||
@@ -418,6 +419,108 @@ module Trees {
|
||||
}
|
||||
}
|
||||
|
||||
class IfStmtTree extends PreOrderTree instanceof IfStmt {
|
||||
final override predicate propagatesAbnormal(AstNode child) {
|
||||
child = super.getACondition()
|
||||
or
|
||||
child = super.getAThen()
|
||||
or
|
||||
child = super.getElse()
|
||||
}
|
||||
|
||||
final override predicate last(AstNode last, Completion c) {
|
||||
last(super.getAThen(), last, c)
|
||||
or
|
||||
last(super.getElse(), last, c)
|
||||
}
|
||||
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
this = pred and
|
||||
first(super.getCondition(0), succ) and
|
||||
completionIsSimple(c)
|
||||
or
|
||||
exists(int i, boolean value |
|
||||
last(super.getCondition(i), pred, c) and value = c.(BooleanCompletion).getValue()
|
||||
|
|
||||
value = true and
|
||||
first(super.getThen(i), succ)
|
||||
or
|
||||
value = false and
|
||||
(
|
||||
first(super.getCondition(i + 1), succ)
|
||||
or
|
||||
i = super.getNumberOfConditions() - 1 and
|
||||
first(super.getElse(), succ)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchStmtTree extends PreOrderTree instanceof SwitchStmt {
|
||||
final override predicate propagatesAbnormal(AstNode child) {
|
||||
child = super.getCondition()
|
||||
or
|
||||
child = super.getACase()
|
||||
or
|
||||
child = super.getDefault()
|
||||
or
|
||||
child = super.getAPattern()
|
||||
}
|
||||
|
||||
final override predicate last(AstNode last, Completion c) {
|
||||
// There are no cases and no default
|
||||
not exists(super.getACase()) and
|
||||
not exists(super.getDefault()) and
|
||||
last(super.getCondition(), last, c) and
|
||||
completionIsNormal(c)
|
||||
or
|
||||
// The last element can be the last statement in the default block
|
||||
last(super.getDefault(), last, c)
|
||||
or
|
||||
// ... or any of the last elements in a case block
|
||||
last(super.getACase(), last, c)
|
||||
or
|
||||
// No default and we reached the final pattern and failed to match
|
||||
not exists(super.getDefault()) and
|
||||
last(super.getPattern(super.getNumberOfCases() - 1), last, c) and
|
||||
c.(MatchCompletion).isNonMatch()
|
||||
}
|
||||
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
// Preorder: Flow from the switch to the condition
|
||||
pred = this and
|
||||
first(super.getCondition(), succ) and
|
||||
completionIsSimple(c)
|
||||
or
|
||||
// Flow from the condition to the first pattern
|
||||
last(super.getCondition(), pred, c) and
|
||||
completionIsNormal(c) and
|
||||
first(super.getPattern(0), succ)
|
||||
or
|
||||
// Flow from a match to:
|
||||
// 1. the corresponding case if the match succeeds, or
|
||||
// 2. the next pattern if the match failed, or
|
||||
// 3. the default case if this is the last pattern and the match failed.
|
||||
exists(int i, boolean match |
|
||||
last(super.getPattern(i), pred, c) and c.(MatchCompletion).getValue() = match
|
||||
|
|
||||
// Case 1
|
||||
match = true and
|
||||
first(super.getCase(i), succ)
|
||||
or
|
||||
match = false and
|
||||
(
|
||||
// Case 2
|
||||
first(super.getPattern(i + 1), succ)
|
||||
or
|
||||
// Case 3
|
||||
i = super.getNumberOfCases() - 1 and
|
||||
first(super.getDefault(), succ)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GotoStmtTree extends LeafTree instanceof GotoStmt { }
|
||||
|
||||
class FunctionStmtTree extends LeafTree instanceof Function { }
|
||||
@@ -440,6 +543,81 @@ module Trees {
|
||||
override AstNode getChildNode(int i) { result = super.getElement(i) }
|
||||
}
|
||||
|
||||
class TypeConstraintTree extends LeafTree instanceof TypeConstraint { }
|
||||
|
||||
class CatchClauseTree extends PreOrderTree instanceof CatchClause {
|
||||
final override predicate propagatesAbnormal(Ast child) { none() }
|
||||
|
||||
final override predicate last(AstNode last, Completion c) {
|
||||
last(super.getBody(), last, c)
|
||||
or
|
||||
// The last catch type failed to matchs
|
||||
last(super.getCatchType(super.getNumberOfCatchTypes() - 1), last, c) and
|
||||
c.(MatchCompletion).isNonMatch()
|
||||
}
|
||||
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
// Preorder: Flow from the catch clause to the first catch type to test,
|
||||
// or to the body if this is a catch all
|
||||
pred = this and
|
||||
completionIsSimple(c) and
|
||||
(
|
||||
first(super.getCatchType(0), succ)
|
||||
or
|
||||
super.isCatchAll() and
|
||||
first(super.getBody(), succ)
|
||||
)
|
||||
or
|
||||
// Flow from a catch type to the next catch type when it fails to match
|
||||
exists(int i, boolean match |
|
||||
last(super.getCatchType(i), pred, c) and
|
||||
match = c.(MatchCompletion).getValue()
|
||||
|
|
||||
match = true and
|
||||
first(super.getBody(), succ)
|
||||
or
|
||||
match = false and
|
||||
first(super.getCatchType(i + 1), succ)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TryStmtBlock extends PreOrderTree instanceof TryStmt {
|
||||
final override predicate propagatesAbnormal(AstNode child) { child = super.getFinally() }
|
||||
|
||||
final override predicate last(AstNode last, Completion c) {
|
||||
last(super.getFinally(), last, c)
|
||||
or
|
||||
not super.hasFinally() and
|
||||
(
|
||||
// Body exits without an exception
|
||||
last(super.getBody(), last, c) and
|
||||
completionIsNormal(c)
|
||||
or
|
||||
// In case there's an exception we exit by evaluating one of the catch clauses
|
||||
// Note that there will always be at least one catch clause if there is no `try`.
|
||||
last(super.getACatchClause(), last, c)
|
||||
)
|
||||
}
|
||||
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
// Preorder: The try/catch statment flows to the body
|
||||
pred = this and
|
||||
first(super.getBody(), succ) and
|
||||
completionIsSimple(c)
|
||||
or
|
||||
// Flow from the body to the finally when the body didn't throw
|
||||
last(super.getBody(), pred, c) and
|
||||
if c instanceof ThrowCompletion
|
||||
then first(super.getCatchClause(0), succ)
|
||||
else first(super.getFinally(), succ)
|
||||
}
|
||||
}
|
||||
|
||||
class ThrowStmtTree extends StandardPreOrderTree instanceof ThrowStmt {
|
||||
override AstNode getChildNode(int i) { i = 0 and result = super.getPipeline() }
|
||||
}
|
||||
|
||||
class ConstExprTree extends LeafTree instanceof ConstExpr { }
|
||||
|
||||
class CmdExprTree extends StandardPreOrderTree instanceof CmdExpr {
|
||||
@@ -470,12 +648,13 @@ private module Cached {
|
||||
cached
|
||||
newtype TSuccessorType =
|
||||
TSuccessorSuccessor() or
|
||||
TBooleanSuccessor(boolean b) { b in [false, true] } or
|
||||
TBooleanSuccessor(Boolean b) or
|
||||
TReturnSuccessor() or
|
||||
TBreakSuccessor() or
|
||||
TContinueSuccessor() or
|
||||
TRaiseSuccessor() or
|
||||
TExitSuccessor()
|
||||
TThrowSuccessor() or
|
||||
TExitSuccessor() or
|
||||
TMatchingSuccessor(Boolean b)
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
Reference in New Issue
Block a user