mirror of
https://github.com/github/codeql.git
synced 2026-04-24 16:25:15 +02:00
183 lines
5.5 KiB
Plaintext
183 lines
5.5 KiB
Plaintext
/**
|
|
* Provides predicates for detecting pairs of identical AST subtrees.
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Gets an opaque integer value encoding the type of AST node `nd`.
|
|
*/
|
|
private int kindOf(AstNode nd) {
|
|
// map expression kinds to even non-negative numbers
|
|
result = nd.(Expr).getKind() * 2
|
|
or
|
|
// map statement kinds to odd non-negative numbers
|
|
result = nd.(Stmt).getKind() * 2 + 1
|
|
or
|
|
// other node types get negative kinds
|
|
nd instanceof TopLevel and result = -1
|
|
or
|
|
nd instanceof Property and result = -2
|
|
or
|
|
nd instanceof ClassDefinition and result = -3
|
|
}
|
|
|
|
/**
|
|
* Holds if `nd` has the given `kind`, and its number of children is `arity`.
|
|
*/
|
|
private predicate kindAndArity(AstNode nd, int kind, int arity) {
|
|
kindOf(nd) = kind and arity = nd.getNumChild()
|
|
}
|
|
|
|
/**
|
|
* Gets the literal value of AST node `nd`, if any, or the name
|
|
* of `nd` if it is an identifier.
|
|
*
|
|
* Every AST node has at most one value.
|
|
*/
|
|
private string valueOf(AstNode nd) {
|
|
result = nd.(Literal).getRawValue() or
|
|
result = nd.(TemplateElement).getRawValue() or
|
|
result = nd.(Identifier).getName()
|
|
}
|
|
|
|
/**
|
|
* A base class for doing structural comparisons of program elements.
|
|
*
|
|
* Clients must override the `candidate()` method to identify the
|
|
* nodes for which structural comparison will be interesting.
|
|
*/
|
|
abstract class StructurallyCompared extends AstNode {
|
|
/**
|
|
* Gets a candidate node that we may want to structurally compare this node to.
|
|
*/
|
|
abstract AstNode candidate();
|
|
|
|
/**
|
|
* Gets a node that structurally corresponds to this node, either because it is
|
|
* a candidate node, or because it is at the same position relative to a
|
|
* candidate node as this node.
|
|
*/
|
|
AstNode candidateInternal() {
|
|
// in order to correspond, nodes need to have the same kind and shape
|
|
exists(int kind, int numChildren | kindAndArity(this, kind, numChildren) |
|
|
result = this.candidateKind(kind, numChildren)
|
|
or
|
|
result = this.uncleKind(kind, numChildren)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a node that structurally corresponds to the parent node of this node,
|
|
* where this node is the `i`th child of its parent.
|
|
*/
|
|
private AstNode getAStructuralUncle(int i) {
|
|
exists(StructurallyCompared parent | this = parent.getChild(i) |
|
|
result = parent.candidateInternal()
|
|
)
|
|
}
|
|
|
|
private AstNode candidateKind(int kind, int numChildren) {
|
|
result = this.candidate() and kindAndArity(result, kind, numChildren)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private AstNode uncleKind(int kind, int numChildren) {
|
|
exists(int i | result = this.getAStructuralUncle(i).getChild(i)) and
|
|
kindAndArity(result, kind, numChildren)
|
|
}
|
|
|
|
/**
|
|
* Holds if the subtree rooted at this node is structurally equal to the subtree
|
|
* rooted at node `that`, where `that` structurally corresponds to `this` as
|
|
* determined by `candidateInternal`.
|
|
*/
|
|
private predicate sameInternal(AstNode that) {
|
|
that = this.candidateInternal() and
|
|
/*
|
|
* Check that `this` and `that` bind to the same variable, if any.
|
|
* Note that it suffices to check the implication one way: since we restrict
|
|
* `this` and `that` to be of the same kind and in the same syntactic
|
|
* position, either both bind to a variable or neither does.
|
|
*/
|
|
|
|
(bind(this, _) implies exists(Variable v | bind(this, v) and bind(that, v))) and
|
|
/*
|
|
* Check that `this` and `that` have the same constant value, if any.
|
|
* As above, it suffices to check one implication.
|
|
*/
|
|
|
|
(exists(valueOf(this)) implies valueOf(this) = valueOf(that)) and
|
|
forall(StructurallyCompared child, int i |
|
|
child = this.getChild(i) and that = child.getAStructuralUncle(i)
|
|
|
|
|
child.sameInternal(that.getChild(i))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the subtree rooted at this node is structurally equal to the subtree
|
|
* rooted at node `that`, which must be a candidate node as determined by
|
|
* `candidate`.
|
|
*/
|
|
predicate same(AstNode that) {
|
|
that = this.candidate() and
|
|
this.sameInternal(that)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A child of a node that is subject to structural comparison.
|
|
*/
|
|
private class InternalCandidate extends StructurallyCompared {
|
|
InternalCandidate() { exists(this.getParent().(StructurallyCompared).candidateInternal()) }
|
|
|
|
override AstNode candidate() { none() }
|
|
}
|
|
|
|
/**
|
|
* A clone detector for finding comparisons where both operands are
|
|
* structurally identical.
|
|
*/
|
|
class OperandComparedToSelf extends StructurallyCompared {
|
|
OperandComparedToSelf() { exists(Comparison comp | this = comp.getLeftOperand()) }
|
|
|
|
override Expr candidate() { result = this.getParent().(Comparison).getRightOperand() }
|
|
}
|
|
|
|
/**
|
|
* A clone detector for finding assignments where both sides are
|
|
* structurally identical.
|
|
*/
|
|
class SelfAssignment extends StructurallyCompared {
|
|
SelfAssignment() { exists(AssignExpr assgn | this = assgn.getLhs()) }
|
|
|
|
override Expr candidate() { result = this.getAssignment().getRhs() }
|
|
|
|
/**
|
|
* Gets the enclosing assignment.
|
|
*/
|
|
AssignExpr getAssignment() { result.getLhs() = this }
|
|
}
|
|
|
|
/**
|
|
* A clone detector for finding structurally identical property
|
|
* initializers.
|
|
*/
|
|
class DuplicatePropertyInitDetector extends StructurallyCompared {
|
|
DuplicatePropertyInitDetector() {
|
|
exists(ObjectExpr oe, string p |
|
|
this = oe.getPropertyByName(p).getInit() and
|
|
oe.getPropertyByName(p) != oe.getPropertyByName(p)
|
|
)
|
|
}
|
|
|
|
override Expr candidate() {
|
|
exists(ObjectExpr oe, string p |
|
|
this = oe.getPropertyByName(p).getInit() and
|
|
result = oe.getPropertyByName(p).getInit() and
|
|
result != this
|
|
)
|
|
}
|
|
}
|