зеркало из https://github.com/github/codeql.git
Merge pull request #7180 from erik-krogh/apiLabel2
JS: Make the edges of API-graphs into IPA types
This commit is contained in:
Коммит
a077345227
|
@ -11,6 +11,7 @@
|
|||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import internal.CachedStages
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with APIs defined or used in a database.
|
||||
|
@ -33,6 +34,7 @@ module API {
|
|||
* As another example, in the assignment `exports.plusOne = (x) => x+1` the two references to
|
||||
* `x` are uses of the first parameter of `plusOne`.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAUse() {
|
||||
exists(DataFlow::SourceNode src | Impl::use(this, src) |
|
||||
Impl::trackUseNode(src).flowsTo(result)
|
||||
|
@ -105,22 +107,31 @@ module API {
|
|||
* For example, modules have an `exports` member representing their exports, and objects have
|
||||
* their properties as members.
|
||||
*/
|
||||
bindingset[m]
|
||||
bindingset[result]
|
||||
Node getMember(string m) { result = this.getASuccessor(Label::member(m)) }
|
||||
cached
|
||||
Node getMember(string m) {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::member(m))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing a member of this API component where the name of the member is
|
||||
* not known statically.
|
||||
*/
|
||||
Node getUnknownMember() { result = this.getASuccessor(Label::unknownMember()) }
|
||||
cached
|
||||
Node getUnknownMember() {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::unknownMember())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing a member of this API component where the name of the member may
|
||||
* or may not be known statically.
|
||||
*/
|
||||
cached
|
||||
Node getAMember() {
|
||||
result = this.getASuccessor(Label::member(_)) or
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getMember(_)
|
||||
or
|
||||
result = this.getUnknownMember()
|
||||
}
|
||||
|
||||
|
@ -135,7 +146,11 @@ module API {
|
|||
* This predicate may have multiple results when there are multiple constructor calls invoking this API component.
|
||||
* Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls.
|
||||
*/
|
||||
Node getInstance() { result = this.getASuccessor(Label::instance()) }
|
||||
cached
|
||||
Node getInstance() {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::instance())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing the `i`th parameter of the function represented by this node.
|
||||
|
@ -143,16 +158,16 @@ module API {
|
|||
* This predicate may have multiple results when there are multiple invocations of this API component.
|
||||
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
|
||||
*/
|
||||
bindingset[i]
|
||||
Node getParameter(int i) { result = this.getASuccessor(Label::parameter(i)) }
|
||||
cached
|
||||
Node getParameter(int i) {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::parameter(i))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of parameters of the function represented by this node.
|
||||
*/
|
||||
int getNumParameter() {
|
||||
result =
|
||||
max(string s | exists(this.getASuccessor(Label::parameterByStringIndex(s))) | s.toInt()) + 1
|
||||
}
|
||||
int getNumParameter() { result = max(int s | exists(this.getParameter(s))) + 1 }
|
||||
|
||||
/**
|
||||
* Gets a node representing the last parameter of the function represented by this node.
|
||||
|
@ -165,7 +180,11 @@ module API {
|
|||
/**
|
||||
* Gets a node representing the receiver of the function represented by this node.
|
||||
*/
|
||||
Node getReceiver() { result = this.getASuccessor(Label::receiver()) }
|
||||
cached
|
||||
Node getReceiver() {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::receiver())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing a parameter or the receiver of the function represented by this
|
||||
|
@ -175,8 +194,11 @@ module API {
|
|||
* there are multiple invocations of this API component.
|
||||
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
|
||||
*/
|
||||
cached
|
||||
Node getAParameter() {
|
||||
result = this.getASuccessor(Label::parameterByStringIndex(_)) or
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getParameter(_)
|
||||
or
|
||||
result = this.getReceiver()
|
||||
}
|
||||
|
||||
|
@ -186,18 +208,30 @@ module API {
|
|||
* This predicate may have multiple results when there are multiple invocations of this API component.
|
||||
* Consider using `getACall()` if there is a need to distingiush between individual calls.
|
||||
*/
|
||||
Node getReturn() { result = this.getASuccessor(Label::return()) }
|
||||
cached
|
||||
Node getReturn() {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::return())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing the promised value wrapped in the `Promise` object represented by
|
||||
* this node.
|
||||
*/
|
||||
Node getPromised() { result = this.getASuccessor(Label::promised()) }
|
||||
cached
|
||||
Node getPromised() {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::promised())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing the error wrapped in the `Promise` object represented by this node.
|
||||
*/
|
||||
Node getPromisedError() { result = this.getASuccessor(Label::promisedError()) }
|
||||
cached
|
||||
Node getPromisedError() {
|
||||
Stages::APIStage::ref() and
|
||||
result = this.getASuccessor(Label::promisedError())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string representation of the lexicographically least among all shortest access paths
|
||||
|
@ -211,13 +245,13 @@ module API {
|
|||
* Gets a node such that there is an edge in the API graph between this node and the other
|
||||
* one, and that edge is labeled with `lbl`.
|
||||
*/
|
||||
Node getASuccessor(string lbl) { Impl::edge(this, lbl, result) }
|
||||
Node getASuccessor(Label::ApiLabel lbl) { Impl::edge(this, lbl, result) }
|
||||
|
||||
/**
|
||||
* Gets a node such that there is an edge in the API graph between that other node and
|
||||
* this one, and that edge is labeled with `lbl`
|
||||
*/
|
||||
Node getAPredecessor(string lbl) { this = result.getASuccessor(lbl) }
|
||||
Node getAPredecessor(Label::ApiLabel lbl) { this = result.getASuccessor(lbl) }
|
||||
|
||||
/**
|
||||
* Gets a node such that there is an edge in the API graph between this node and the other
|
||||
|
@ -283,9 +317,8 @@ module API {
|
|||
length = 0 and
|
||||
result = ""
|
||||
or
|
||||
exists(Node pred, string lbl, string predpath |
|
||||
exists(Node pred, Label::ApiLabel lbl, string predpath |
|
||||
Impl::edge(pred, lbl, this) and
|
||||
lbl != "" and
|
||||
predpath = pred.getAPath(length - 1) and
|
||||
exists(string space | if length = 1 then space = "" else space = " " |
|
||||
result = "(" + lbl + space + predpath + ")" and
|
||||
|
@ -350,6 +383,9 @@ module API {
|
|||
|
||||
/** Gets a data-flow node that defines this entry point. */
|
||||
abstract DataFlow::Node getARhs();
|
||||
|
||||
/** Gets an API-node for this entry point. */
|
||||
API::Node getNode() { result = root().getASuccessor(Label::entryPoint(this)) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -433,27 +469,19 @@ module API {
|
|||
hasSemantics(imp)
|
||||
}
|
||||
|
||||
/** Gets the definition of module `m`. */
|
||||
private Module importableModule(string m) {
|
||||
exists(NPMPackage pkg, PackageJSON json |
|
||||
json = pkg.getPackageJSON() and not json.isPrivate()
|
||||
|
|
||||
result = pkg.getMainModule() and
|
||||
not result.isExterns() and
|
||||
m = pkg.getPackageName()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
|
||||
* incoming edge from `base` labeled `lbl` in the API graph.
|
||||
*/
|
||||
cached
|
||||
predicate rhs(TApiNode base, string lbl, DataFlow::Node rhs) {
|
||||
predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) {
|
||||
hasSemantics(rhs) and
|
||||
(
|
||||
base = MkRoot() and
|
||||
rhs = lbl.(EntryPoint).getARhs()
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
rhs = e.getARhs()
|
||||
)
|
||||
or
|
||||
exists(string m, string prop |
|
||||
base = MkModuleExport(m) and
|
||||
|
@ -565,7 +593,7 @@ module API {
|
|||
*/
|
||||
pragma[noinline]
|
||||
private predicate propertyRead(
|
||||
DataFlow::SourceNode pred, string propDesc, string lbl, DataFlow::Node ref
|
||||
DataFlow::SourceNode pred, string propDesc, Label::ApiLabel lbl, DataFlow::Node ref
|
||||
) {
|
||||
ref = pred.getAPropertyRead() and
|
||||
lbl = Label::memberFromRef(ref) and
|
||||
|
@ -589,11 +617,14 @@ module API {
|
|||
* `lbl` in the API graph.
|
||||
*/
|
||||
cached
|
||||
predicate use(TApiNode base, string lbl, DataFlow::Node ref) {
|
||||
predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) {
|
||||
hasSemantics(ref) and
|
||||
(
|
||||
base = MkRoot() and
|
||||
ref = lbl.(EntryPoint).getAUse()
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
ref = e.getAUse()
|
||||
)
|
||||
or
|
||||
// property reads
|
||||
exists(DataFlow::SourceNode src, DataFlow::SourceNode pred, string propDesc |
|
||||
|
@ -680,33 +711,6 @@ module API {
|
|||
nd = MkUse(ref)
|
||||
}
|
||||
|
||||
/** Holds if module `m` exports `rhs`. */
|
||||
private predicate exports(string m, DataFlow::Node rhs) {
|
||||
exists(Module mod | mod = importableModule(m) |
|
||||
rhs = mod.(AmdModule).getDefine().getModuleExpr().flow()
|
||||
or
|
||||
exports(m, "default", rhs)
|
||||
or
|
||||
exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod |
|
||||
rhs = assgn.getExpression().flow()
|
||||
)
|
||||
or
|
||||
rhs = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr().flow()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if module `m` exports `rhs` under the name `prop`. */
|
||||
private predicate exports(string m, string prop, DataFlow::Node rhs) {
|
||||
exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) |
|
||||
rhs = exp.getSourceNode(prop)
|
||||
or
|
||||
exists(Variable v |
|
||||
exp.exportsAs(v, prop) and
|
||||
rhs = v.getAnAssignedExpr().flow()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private import semmle.javascript.dataflow.TypeTracking
|
||||
|
||||
/**
|
||||
|
@ -865,10 +869,11 @@ module API {
|
|||
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
||||
*/
|
||||
cached
|
||||
predicate edge(TApiNode pred, string lbl, TApiNode succ) {
|
||||
predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) {
|
||||
Stages::APIStage::ref() and
|
||||
exists(string m |
|
||||
pred = MkRoot() and
|
||||
lbl = Label::mod(m)
|
||||
lbl = Label::moduleLabel(m)
|
||||
|
|
||||
succ = MkModuleDef(m)
|
||||
or
|
||||
|
@ -942,8 +947,6 @@ module API {
|
|||
}
|
||||
}
|
||||
|
||||
import Label as EdgeLabel
|
||||
|
||||
/**
|
||||
* An `InvokeNode` that is connected to the API graph.
|
||||
*
|
||||
|
@ -972,7 +975,8 @@ module API {
|
|||
/**
|
||||
* Gets an API node where a RHS of the node is the `i`th argument to this call.
|
||||
*/
|
||||
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArgument(i) }
|
||||
pragma[noinline]
|
||||
private Node getAParameterCandidate(int i) { result.getARhs() = getArgument(i) }
|
||||
|
||||
/** Gets the API node for a parameter of this invocation. */
|
||||
Node getAParameter() { result = this.getParameter(_) }
|
||||
|
@ -998,89 +1002,236 @@ module API {
|
|||
|
||||
/** A `new` call connected to the API graph. */
|
||||
class NewNode extends InvokeNode, DataFlow::NewNode { }
|
||||
|
||||
/** Provides classes modeling the various edges (labels) in the API graph. */
|
||||
module Label {
|
||||
/** A label in the API-graph */
|
||||
class ApiLabel extends TLabel {
|
||||
/** Gets a string representation of this label. */
|
||||
string toString() { result = "???" }
|
||||
}
|
||||
|
||||
/** Gets the edge label for the module `m`. */
|
||||
LabelModule moduleLabel(string m) { result.getMod() = m }
|
||||
|
||||
/** Gets the `member` edge label for member `m`. */
|
||||
bindingset[m]
|
||||
bindingset[result]
|
||||
LabelMember member(string m) { result.getProperty() = m }
|
||||
|
||||
/** Gets the `member` edge label for the unknown member. */
|
||||
LabelUnknownMember unknownMember() { any() }
|
||||
|
||||
/**
|
||||
* Gets a property name referred to by the given dynamic property access,
|
||||
* allowing one property flow step in the process (to allow flow through imports).
|
||||
*
|
||||
* This is to support code patterns where the property name is actually constant,
|
||||
* but the property name has been factored into a library.
|
||||
*/
|
||||
private string getAnIndirectPropName(DataFlow::PropRef ref) {
|
||||
exists(DataFlow::Node pred |
|
||||
FlowSteps::propertyFlowStep(pred, ref.getPropertyNameExpr().flow()) and
|
||||
result = pred.getStringValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets unique result of `getAnIndirectPropName` if there is one.
|
||||
*/
|
||||
private string getIndirectPropName(DataFlow::PropRef ref) {
|
||||
result = unique(string s | s = getAnIndirectPropName(ref))
|
||||
}
|
||||
|
||||
/** Gets the `member` edge label for the given property reference. */
|
||||
ApiLabel memberFromRef(DataFlow::PropRef pr) {
|
||||
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
|
||||
result = member(pn) and
|
||||
// only consider properties with alphanumeric(-ish) names, excluding special properties
|
||||
// and properties whose names look like they are meant to be internal
|
||||
pn.regexpMatch("(?!prototype$|__)[\\w_$][\\w\\-.$]*")
|
||||
)
|
||||
or
|
||||
not exists(pr.getPropertyName()) and
|
||||
not exists(getIndirectPropName(pr)) and
|
||||
result = unknownMember()
|
||||
}
|
||||
|
||||
/** Gets the `instance` edge label. */
|
||||
LabelInstance instance() { any() }
|
||||
|
||||
/**
|
||||
* Gets the `parameter` edge label for the `i`th parameter.
|
||||
*
|
||||
* The receiver is considered to be parameter -1.
|
||||
*/
|
||||
LabelParameter parameter(int i) { result.getIndex() = i }
|
||||
|
||||
/** Gets the `parameter` edge label for the receiver. */
|
||||
LabelParameter receiver() { result = parameter(-1) }
|
||||
|
||||
/** Gets the `return` edge label. */
|
||||
LabelReturn return() { any() }
|
||||
|
||||
/** Gets the `promised` edge label connecting a promise to its contained value. */
|
||||
LabelPromised promised() { any() }
|
||||
|
||||
/** Gets the `promisedError` edge label connecting a promise to its rejected value. */
|
||||
LabelPromisedError promisedError() { any() }
|
||||
|
||||
/** Gets an entry-point label for the entry-point `e`. */
|
||||
LabelEntryPoint entryPoint(API::EntryPoint e) { result.getEntryPoint() = e }
|
||||
|
||||
private import LabelImpl
|
||||
|
||||
private module LabelImpl {
|
||||
newtype TLabel =
|
||||
MkLabelModule(string mod) {
|
||||
exists(Impl::MkModuleExport(mod)) or
|
||||
exists(Impl::MkModuleImport(mod))
|
||||
} or
|
||||
MkLabelInstance() or
|
||||
MkLabelMember(string prop) {
|
||||
exports(_, prop, _) or
|
||||
exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or
|
||||
prop = "exports" or
|
||||
prop = any(CanonicalName c).getName() or
|
||||
prop = any(DataFlow::PropRef p).getPropertyName() or
|
||||
exists(Impl::MkTypeUse(_, prop)) or
|
||||
exists(any(Module m).getAnExportedValue(prop))
|
||||
} or
|
||||
MkLabelUnknownMember() or
|
||||
MkLabelParameter(int i) {
|
||||
i =
|
||||
[-1 .. max(int args |
|
||||
args = any(InvokeExpr invk).getNumArgument() or
|
||||
args = any(Function f).getNumParameter()
|
||||
)] or
|
||||
i = [0 .. 10]
|
||||
} or
|
||||
MkLabelReturn() or
|
||||
MkLabelPromised() or
|
||||
MkLabelPromisedError() or
|
||||
MkLabelEntryPoint(API::EntryPoint e)
|
||||
|
||||
/** A label for an entry-point. */
|
||||
class LabelEntryPoint extends ApiLabel {
|
||||
API::EntryPoint e;
|
||||
|
||||
LabelEntryPoint() { this = MkLabelEntryPoint(e) }
|
||||
|
||||
/** Gets the EntryPoint associated with this label. */
|
||||
API::EntryPoint getEntryPoint() { result = e }
|
||||
|
||||
override string toString() { result = e }
|
||||
}
|
||||
|
||||
/** A label that gets a promised value. */
|
||||
class LabelPromised extends ApiLabel {
|
||||
LabelPromised() { this = MkLabelPromised() }
|
||||
|
||||
override string toString() { result = "promised" }
|
||||
}
|
||||
|
||||
/** A label that gets a rejected promise. */
|
||||
class LabelPromisedError extends ApiLabel {
|
||||
LabelPromisedError() { this = MkLabelPromisedError() }
|
||||
|
||||
override string toString() { result = "promisedError" }
|
||||
}
|
||||
|
||||
/** A label that gets the return value of a function. */
|
||||
class LabelReturn extends ApiLabel {
|
||||
LabelReturn() { this = MkLabelReturn() }
|
||||
|
||||
override string toString() { result = "return" }
|
||||
}
|
||||
|
||||
/** A label for a module. */
|
||||
class LabelModule extends ApiLabel {
|
||||
string mod;
|
||||
|
||||
LabelModule() { this = MkLabelModule(mod) }
|
||||
|
||||
/** Gets the module associated with this label. */
|
||||
string getMod() { result = mod }
|
||||
|
||||
override string toString() { result = "module " + mod }
|
||||
}
|
||||
|
||||
/** A label that gets an instance from a `new` call. */
|
||||
class LabelInstance extends ApiLabel {
|
||||
LabelInstance() { this = MkLabelInstance() }
|
||||
|
||||
override string toString() { result = "instance" }
|
||||
}
|
||||
|
||||
/** A label for the member named `prop`. */
|
||||
class LabelMember extends ApiLabel {
|
||||
string prop;
|
||||
|
||||
LabelMember() { this = MkLabelMember(prop) }
|
||||
|
||||
/** Gets the property associated with this label. */
|
||||
string getProperty() { result = prop }
|
||||
|
||||
override string toString() { result = "member " + prop }
|
||||
}
|
||||
|
||||
/** A label for a member with an unknown name. */
|
||||
class LabelUnknownMember extends ApiLabel {
|
||||
LabelUnknownMember() { this = MkLabelUnknownMember() }
|
||||
|
||||
override string toString() { result = "member *" }
|
||||
}
|
||||
|
||||
/** A label for parameter `i`. */
|
||||
class LabelParameter extends ApiLabel {
|
||||
int i;
|
||||
|
||||
LabelParameter() { this = MkLabelParameter(i) }
|
||||
|
||||
override string toString() { result = "parameter " + i }
|
||||
|
||||
/** Gets the index of the parameter for this label. */
|
||||
int getIndex() { result = i }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private module Label {
|
||||
/** Gets the edge label for the module `m`. */
|
||||
bindingset[m]
|
||||
bindingset[result]
|
||||
string mod(string m) { result = "module " + m }
|
||||
|
||||
/** Gets the `member` edge label for member `m`. */
|
||||
bindingset[m]
|
||||
bindingset[result]
|
||||
string member(string m) { result = "member " + m }
|
||||
|
||||
/** Gets the `member` edge label for the unknown member. */
|
||||
string unknownMember() { result = "member *" }
|
||||
|
||||
/**
|
||||
* Gets a property name referred to by the given dynamic property access,
|
||||
* allowing one property flow step in the process (to allow flow through imports).
|
||||
*
|
||||
* This is to support code patterns where the property name is actually constant,
|
||||
* but the property name has been factored into a library.
|
||||
*/
|
||||
private string getAnIndirectPropName(DataFlow::PropRef ref) {
|
||||
exists(DataFlow::Node pred |
|
||||
FlowSteps::propertyFlowStep(pred, ref.getPropertyNameExpr().flow()) and
|
||||
result = pred.getStringValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets unique result of `getAnIndirectPropName` if there is one.
|
||||
*/
|
||||
private string getIndirectPropName(DataFlow::PropRef ref) {
|
||||
result = unique(string s | s = getAnIndirectPropName(ref))
|
||||
}
|
||||
|
||||
/** Gets the `member` edge label for the given property reference. */
|
||||
string memberFromRef(DataFlow::PropRef pr) {
|
||||
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
|
||||
result = member(pn) and
|
||||
// only consider properties with alphanumeric(-ish) names, excluding special properties
|
||||
// and properties whose names look like they are meant to be internal
|
||||
pn.regexpMatch("(?!prototype$|__)[\\w_$][\\w\\-.$]*")
|
||||
/** Holds if module `m` exports `rhs`. */
|
||||
private predicate exports(string m, DataFlow::Node rhs) {
|
||||
exists(Module mod | mod = importableModule(m) |
|
||||
rhs = mod.(AmdModule).getDefine().getModuleExpr().flow()
|
||||
or
|
||||
exports(m, "default", rhs)
|
||||
or
|
||||
exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod |
|
||||
rhs = assgn.getExpression().flow()
|
||||
)
|
||||
or
|
||||
not exists(pr.getPropertyName()) and
|
||||
not exists(getIndirectPropName(pr)) and
|
||||
result = unknownMember()
|
||||
}
|
||||
|
||||
/** Gets the `instance` edge label. */
|
||||
string instance() { result = "instance" }
|
||||
|
||||
/**
|
||||
* Gets the `parameter` edge label for the parameter `s`.
|
||||
*
|
||||
* This is an internal helper predicate; use `parameter` instead.
|
||||
*/
|
||||
bindingset[result]
|
||||
bindingset[s]
|
||||
string parameterByStringIndex(string s) {
|
||||
result = "parameter " + s and
|
||||
s.toInt() >= -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `parameter` edge label for the `i`th parameter.
|
||||
*
|
||||
* The receiver is considered to be parameter -1.
|
||||
*/
|
||||
bindingset[i]
|
||||
string parameter(int i) { result = parameterByStringIndex(i.toString()) }
|
||||
|
||||
/** Gets the `parameter` edge label for the receiver. */
|
||||
string receiver() { result = "parameter -1" }
|
||||
|
||||
/** Gets the `return` edge label. */
|
||||
string return() { result = "return" }
|
||||
|
||||
/** Gets the `promised` edge label connecting a promise to its contained value. */
|
||||
string promised() { result = "promised" }
|
||||
|
||||
/** Gets the `promisedError` edge label connecting a promise to its rejected value. */
|
||||
string promisedError() { result = "promisedError" }
|
||||
rhs = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr().flow()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if module `m` exports `rhs` under the name `prop`. */
|
||||
private predicate exports(string m, string prop, DataFlow::Node rhs) {
|
||||
exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) |
|
||||
rhs = exp.getSourceNode(prop)
|
||||
or
|
||||
exists(Variable v |
|
||||
exp.exportsAs(v, prop) and
|
||||
rhs = v.getAnAssignedExpr().flow()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the definition of module `m`. */
|
||||
private Module importableModule(string m) {
|
||||
exists(NPMPackage pkg, PackageJSON json | json = pkg.getPackageJSON() and not json.isPrivate() |
|
||||
result = pkg.getMainModule() and
|
||||
not result.isExterns() and
|
||||
m = pkg.getPackageName()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
/**
|
||||
* An expression that evaluates to a constant primitive value.
|
||||
*/
|
||||
cached
|
||||
abstract class ConstantExpr extends Expr { }
|
||||
|
||||
/**
|
||||
|
@ -16,6 +18,7 @@ module SyntacticConstants {
|
|||
/**
|
||||
* An expression that evaluates to a constant value according to a bottom-up syntactic analysis.
|
||||
*/
|
||||
cached
|
||||
abstract class SyntacticConstant extends ConstantExpr { }
|
||||
|
||||
/**
|
||||
|
@ -23,8 +26,11 @@ module SyntacticConstants {
|
|||
*
|
||||
* Note that `undefined`, `NaN` and `Infinity` are global variables, and are not covered by this class.
|
||||
*/
|
||||
cached
|
||||
class PrimitiveLiteralConstant extends SyntacticConstant {
|
||||
cached
|
||||
PrimitiveLiteralConstant() {
|
||||
Stages::Ast::ref() and
|
||||
this instanceof NumberLiteral
|
||||
or
|
||||
this instanceof StringLiteral
|
||||
|
@ -43,19 +49,27 @@ module SyntacticConstants {
|
|||
/**
|
||||
* A literal null expression.
|
||||
*/
|
||||
class NullConstant extends SyntacticConstant, NullLiteral { }
|
||||
cached
|
||||
class NullConstant extends SyntacticConstant, NullLiteral {
|
||||
cached
|
||||
NullConstant() { Stages::Ast::ref() and this = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A unary operation on a syntactic constant.
|
||||
*/
|
||||
cached
|
||||
class UnaryConstant extends SyntacticConstant, UnaryExpr {
|
||||
cached
|
||||
UnaryConstant() { getOperand() instanceof SyntacticConstant }
|
||||
}
|
||||
|
||||
/**
|
||||
* A binary operation on syntactic constants.
|
||||
*/
|
||||
cached
|
||||
class BinaryConstant extends SyntacticConstant, BinaryExpr {
|
||||
cached
|
||||
BinaryConstant() {
|
||||
getLeftOperand() instanceof SyntacticConstant and
|
||||
getRightOperand() instanceof SyntacticConstant
|
||||
|
@ -65,7 +79,9 @@ module SyntacticConstants {
|
|||
/**
|
||||
* A conditional expression on syntactic constants.
|
||||
*/
|
||||
cached
|
||||
class ConditionalConstant extends SyntacticConstant, ConditionalExpr {
|
||||
cached
|
||||
ConditionalConstant() {
|
||||
getCondition() instanceof SyntacticConstant and
|
||||
getConsequent() instanceof SyntacticConstant and
|
||||
|
@ -76,7 +92,9 @@ module SyntacticConstants {
|
|||
/**
|
||||
* A use of the global variable `undefined` or `void e`.
|
||||
*/
|
||||
cached
|
||||
class UndefinedConstant extends SyntacticConstant {
|
||||
cached
|
||||
UndefinedConstant() {
|
||||
this.(GlobalVarAccess).getName() = "undefined" or
|
||||
this instanceof VoidExpr
|
||||
|
@ -86,21 +104,27 @@ module SyntacticConstants {
|
|||
/**
|
||||
* A use of the global variable `NaN`.
|
||||
*/
|
||||
cached
|
||||
class NaNConstant extends SyntacticConstant {
|
||||
cached
|
||||
NaNConstant() { this.(GlobalVarAccess).getName() = "NaN" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A use of the global variable `Infinity`.
|
||||
*/
|
||||
cached
|
||||
class InfinityConstant extends SyntacticConstant {
|
||||
cached
|
||||
InfinityConstant() { this.(GlobalVarAccess).getName() = "Infinity" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that wraps the syntactic constant it evaluates to.
|
||||
*/
|
||||
cached
|
||||
class WrappedConstant extends SyntacticConstant {
|
||||
cached
|
||||
WrappedConstant() { getUnderlyingValue() instanceof SyntacticConstant }
|
||||
}
|
||||
|
||||
|
@ -123,6 +147,8 @@ module SyntacticConstants {
|
|||
/**
|
||||
* An expression that evaluates to a constant string.
|
||||
*/
|
||||
cached
|
||||
class ConstantString extends ConstantExpr {
|
||||
cached
|
||||
ConstantString() { exists(getStringValue()) }
|
||||
}
|
||||
|
|
|
@ -89,7 +89,8 @@ class ExprOrType extends @expr_or_type, Documentable {
|
|||
*
|
||||
* Also see `getUnderlyingReference` and `stripParens`.
|
||||
*/
|
||||
Expr getUnderlyingValue() { result = this }
|
||||
cached
|
||||
Expr getUnderlyingValue() { Stages::Ast::ref() and result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,7 +275,11 @@ private DataFlow::Node getCatchParameterFromStmt(Stmt stmt) {
|
|||
*/
|
||||
class Identifier extends @identifier, ExprOrType {
|
||||
/** Gets the name of this identifier. */
|
||||
string getName() { literals(result, _, this) }
|
||||
cached
|
||||
string getName() {
|
||||
Stages::Ast::ref() and
|
||||
literals(result, _, this)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "Identifier" }
|
||||
}
|
||||
|
|
|
@ -165,6 +165,7 @@ module MembershipCandidate {
|
|||
EnumerationRegExp enumeration;
|
||||
boolean polarity;
|
||||
|
||||
pragma[nomagic]
|
||||
RegExpEnumerationCandidate() {
|
||||
exists(DataFlow::MethodCallNode mcn, DataFlow::Node base, string m, DataFlow::Node firstArg |
|
||||
(
|
||||
|
|
|
@ -939,18 +939,21 @@ private predicate basicFlowStepNoBarrier(
|
|||
* This predicate is field insensitive (it does not distinguish between `x` and `x.p`)
|
||||
* and hence should only be used for purposes of approximation.
|
||||
*/
|
||||
pragma[inline]
|
||||
pragma[noinline]
|
||||
private predicate exploratoryFlowStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg
|
||||
) {
|
||||
basicFlowStepNoBarrier(pred, succ, _, cfg) or
|
||||
exploratoryLoadStep(pred, succ, cfg) or
|
||||
isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or
|
||||
// the following three disjuncts taken together over-approximate flow through
|
||||
// higher-order calls
|
||||
exploratoryCallbackStep(pred, succ) or
|
||||
succ = pred.(DataFlow::FunctionNode).getAParameter() or
|
||||
exploratoryBoundInvokeStep(pred, succ)
|
||||
isRelevantForward(pred, cfg) and
|
||||
(
|
||||
basicFlowStepNoBarrier(pred, succ, _, cfg) or
|
||||
exploratoryLoadStep(pred, succ, cfg) or
|
||||
isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or
|
||||
// the following three disjuncts taken together over-approximate flow through
|
||||
// higher-order calls
|
||||
exploratoryCallbackStep(pred, succ) or
|
||||
succ = pred.(DataFlow::FunctionNode).getAParameter() or
|
||||
exploratoryBoundInvokeStep(pred, succ)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1024,6 +1027,7 @@ private string getAPropertyUsedInLoadStore(DataFlow::Configuration cfg) {
|
|||
* Holds if there exists a store-step from `pred` to `succ` under configuration `cfg`,
|
||||
* and somewhere in the program there exists a load-step that could possibly read the stored value.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate exploratoryForwardStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg
|
||||
) {
|
||||
|
@ -1075,8 +1079,10 @@ private string getABackwardsRelevantStoreProperty(DataFlow::Configuration cfg) {
|
|||
private predicate isRelevantForward(DataFlow::Node nd, DataFlow::Configuration cfg) {
|
||||
isSource(nd, cfg, _) and isLive()
|
||||
or
|
||||
exists(DataFlow::Node mid | isRelevantForward(mid, cfg) |
|
||||
exploratoryFlowStep(mid, nd, cfg) or
|
||||
exists(DataFlow::Node mid |
|
||||
exploratoryFlowStep(mid, nd, cfg)
|
||||
or
|
||||
isRelevantForward(mid, cfg) and
|
||||
exploratoryForwardStoreStep(mid, nd, cfg)
|
||||
)
|
||||
}
|
||||
|
@ -1098,11 +1104,10 @@ private predicate isRelevant(DataFlow::Node nd, DataFlow::Configuration cfg) {
|
|||
private predicate isRelevantBackStep(
|
||||
DataFlow::Node mid, DataFlow::Node nd, DataFlow::Configuration cfg
|
||||
) {
|
||||
exploratoryFlowStep(nd, mid, cfg)
|
||||
or
|
||||
isRelevantForward(nd, cfg) and
|
||||
(
|
||||
exploratoryFlowStep(nd, mid, cfg) or
|
||||
exploratoryBackwardStoreStep(nd, mid, cfg)
|
||||
)
|
||||
exploratoryBackwardStoreStep(nd, mid, cfg)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1273,23 +1278,30 @@ private predicate parameterPropRead(
|
|||
DataFlow::Node arg, string prop, DataFlow::Node succ, DataFlow::Configuration cfg,
|
||||
PathSummary summary
|
||||
) {
|
||||
exists(Function f, DataFlow::Node read, DataFlow::Node invk |
|
||||
exists(Function f, DataFlow::Node read, DataFlow::Node invk, DataFlow::Node parm |
|
||||
reachesReturn(f, read, cfg, summary) and
|
||||
parameterPropReadStep(parm, read, prop, cfg, arg, invk, f, succ)
|
||||
)
|
||||
}
|
||||
|
||||
// all the non-recursive parts of parameterPropRead outlined into a precomputed predicate
|
||||
pragma[noinline]
|
||||
private predicate parameterPropReadStep(
|
||||
DataFlow::SourceNode parm, DataFlow::Node read, string prop, DataFlow::Configuration cfg,
|
||||
DataFlow::Node arg, DataFlow::Node invk, Function f, DataFlow::Node succ
|
||||
) {
|
||||
(
|
||||
not f.isAsyncOrGenerator() and invk = succ
|
||||
or
|
||||
// load from an immediately awaited function call
|
||||
f.isAsync() and
|
||||
invk = getAwaitOperand(succ)
|
||||
|
|
||||
exists(DataFlow::SourceNode parm |
|
||||
callInputStep(f, invk, arg, parm, cfg) and
|
||||
(
|
||||
reachesReturn(f, read, cfg, summary) and
|
||||
read = parm.getAPropertyRead(prop)
|
||||
or
|
||||
reachesReturn(f, read, cfg, summary) and
|
||||
exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg))
|
||||
)
|
||||
)
|
||||
) and
|
||||
callInputStep(f, invk, arg, parm, cfg) and
|
||||
(
|
||||
read = parm.getAPropertyRead(prop)
|
||||
or
|
||||
exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -160,10 +160,12 @@ module TaintTracking {
|
|||
* of the standard library. Override `Configuration::isSanitizerGuard`
|
||||
* for analysis-specific taint sanitizer guards.
|
||||
*/
|
||||
cached
|
||||
abstract class AdditionalSanitizerGuardNode extends SanitizerGuardNode {
|
||||
/**
|
||||
* Holds if this guard applies to the flow in `cfg`.
|
||||
*/
|
||||
cached
|
||||
abstract predicate appliesTo(Configuration cfg);
|
||||
}
|
||||
|
||||
|
@ -1127,7 +1129,7 @@ module TaintTracking {
|
|||
idx = astNode.getAnOperand() and
|
||||
idx.getPropertyNameExpr() = x and
|
||||
// and the other one is guaranteed to be `undefined`
|
||||
forex(InferredType tp | tp = undef.getAType() | tp = TTUndefined())
|
||||
unique(InferredType tp | tp = pragma[only_bind_into](undef.getAType())) = TTUndefined()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ module D3 {
|
|||
or
|
||||
result = API::moduleImport("d3-node").getInstance().getMember("d3")
|
||||
or
|
||||
result = API::root().getASuccessor(any(D3GlobalEntry i))
|
||||
result = any(D3GlobalEntry i).getNode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,7 +17,7 @@ module History {
|
|||
* Gets a reference to the [`history`](https://npmjs.org/package/history) library.
|
||||
*/
|
||||
private API::Node history() {
|
||||
result = [API::moduleImport("history"), API::root().getASuccessor(any(HistoryGlobalEntry h))]
|
||||
result = [API::moduleImport("history"), any(HistoryGlobalEntry h).getNode()]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,7 +27,7 @@ private module Immutable {
|
|||
API::Node immutableImport() {
|
||||
result = API::moduleImport("immutable")
|
||||
or
|
||||
result = API::root().getASuccessor(any(ImmutableGlobalEntry i))
|
||||
result = any(ImmutableGlobalEntry i).getNode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,7 +45,7 @@ private module Console {
|
|||
*/
|
||||
private API::Node console() {
|
||||
result = API::moduleImport("console") or
|
||||
result = API::root().getASuccessor(any(ConsoleGlobalEntry e))
|
||||
result = any(ConsoleGlobalEntry e).getNode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -151,7 +151,7 @@ module NestJS {
|
|||
private API::Node validationPipe() {
|
||||
result = nestjs().getMember("ValidationPipe")
|
||||
or
|
||||
result = API::root().getASuccessor(any(ValidationNodeEntry e))
|
||||
result = any(ValidationNodeEntry e).getNode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1111,9 +1111,7 @@ module Redux {
|
|||
|
||||
/** A heuristic call to `connect`, recognized by it taking arguments named `mapStateToProps` and `mapDispatchToProps`. */
|
||||
private class HeuristicConnectFunction extends ConnectCall {
|
||||
HeuristicConnectFunction() {
|
||||
this = API::root().getASuccessor(any(HeuristicConnectEntryPoint e)).getACall()
|
||||
}
|
||||
HeuristicConnectFunction() { this = any(HeuristicConnectEntryPoint e).getNode().getACall() }
|
||||
|
||||
override API::Node getMapStateToProps() {
|
||||
result = getAParameter() and
|
||||
|
|
|
@ -35,7 +35,7 @@ module Vue {
|
|||
API::Node vueLibrary() {
|
||||
result = API::moduleImport("vue")
|
||||
or
|
||||
result = API::root().getASuccessor(any(GlobalVueEntryPoint e))
|
||||
result = any(GlobalVueEntryPoint e).getNode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,7 +51,7 @@ module Vue {
|
|||
or
|
||||
result = vueLibrary().getMember("component").getReturn()
|
||||
or
|
||||
result = API::root().getASuccessor(any(VueFileImportEntryPoint e))
|
||||
result = any(VueFileImportEntryPoint e).getNode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -69,6 +69,14 @@ module Stages {
|
|||
exists(any(Expr e).getStringValue())
|
||||
or
|
||||
any(ASTNode node).isAmbient()
|
||||
or
|
||||
exists(any(Identifier e).getName())
|
||||
or
|
||||
exists(any(ExprOrType e).getUnderlyingValue())
|
||||
or
|
||||
exists(ConstantExpr e)
|
||||
or
|
||||
exists(SyntacticConstants::NullConstant n)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,6 +241,43 @@ module Stages {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `APIStage` stage.
|
||||
*/
|
||||
cached
|
||||
module APIStage {
|
||||
/**
|
||||
* Always holds.
|
||||
* Ensures that a predicate is evaluated as part of the APIStage stage.
|
||||
*/
|
||||
cached
|
||||
predicate ref() { 1 = 1 }
|
||||
|
||||
/**
|
||||
* DONT USE!
|
||||
* Contains references to each predicate that use the above `ref` predicate.
|
||||
*/
|
||||
cached
|
||||
predicate backref() {
|
||||
1 = 1
|
||||
or
|
||||
exists(
|
||||
API::moduleImport("foo")
|
||||
.getMember("bar")
|
||||
.getUnknownMember()
|
||||
.getAMember()
|
||||
.getAParameter()
|
||||
.getPromised()
|
||||
.getReturn()
|
||||
.getParameter(2)
|
||||
.getUnknownMember()
|
||||
.getInstance()
|
||||
.getReceiver()
|
||||
.getPromisedError()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `taint` stage.
|
||||
*/
|
||||
|
@ -262,6 +307,20 @@ module Stages {
|
|||
exists(Exports::getALibraryInputParameter())
|
||||
or
|
||||
any(RegExpTerm t).isUsedAsRegExp()
|
||||
or
|
||||
any(TaintTracking::AdditionalSanitizerGuardNode e).appliesTo(_)
|
||||
}
|
||||
|
||||
cached
|
||||
class DummySanitizer extends TaintTracking::AdditionalSanitizerGuardNode {
|
||||
cached
|
||||
DummySanitizer() { none() }
|
||||
|
||||
cached
|
||||
override predicate appliesTo(TaintTracking::Configuration cfg) { none() }
|
||||
|
||||
cached
|
||||
override predicate sanitizes(boolean outcome, Expr e) { none() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,11 +211,9 @@ module ExternalAPIUsedWithUntrustedData {
|
|||
node = getNamedParameter(base.getAParameter(), paramName) and
|
||||
result = basename + ".[callback].[param '" + paramName + "']"
|
||||
or
|
||||
exists(string callbackName, string index |
|
||||
node =
|
||||
getNamedParameter(base.getASuccessor("parameter " + index).getMember(callbackName),
|
||||
paramName) and
|
||||
index != "-1" and // ignore receiver
|
||||
exists(string callbackName, int index |
|
||||
node = getNamedParameter(base.getParameter(index).getMember(callbackName), paramName) and
|
||||
index != -1 and // ignore receiver
|
||||
result =
|
||||
basename + ".[callback " + index + " '" + callbackName + "'].[param '" + paramName +
|
||||
"']"
|
||||
|
|
|
@ -117,16 +117,20 @@ private class RemoteFlowSourceAccessPath extends JSONString {
|
|||
string getSourceType() { result = sourceType }
|
||||
|
||||
/** Gets the `i`th component of the access path specifying this remote flow source. */
|
||||
string getComponent(int i) {
|
||||
API::Label::ApiLabel getComponent(int i) {
|
||||
exists(string raw | raw = this.getValue().splitAt(".", i + 1) |
|
||||
i = 0 and
|
||||
result = "ExternalRemoteFlowSourceSpec " + raw
|
||||
result =
|
||||
API::Label::entryPoint(any(ExternalRemoteFlowSourceSpecEntryPoint e | e.getName() = raw))
|
||||
or
|
||||
i > 0 and
|
||||
result = API::EdgeLabel::member(raw)
|
||||
result = API::Label::member(raw)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the first part of this access path. E.g. for "window.user.name" the result is "window". */
|
||||
string getRootPath() { result = this.getValue().splitAt(".", 1) }
|
||||
|
||||
/** Gets the index of the last component of this access path. */
|
||||
int getMaxComponentIndex() { result = max(int i | exists(this.getComponent(i))) }
|
||||
|
||||
|
@ -154,10 +158,12 @@ private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint {
|
|||
string name;
|
||||
|
||||
ExternalRemoteFlowSourceSpecEntryPoint() {
|
||||
this = any(RemoteFlowSourceAccessPath s).getComponent(0) and
|
||||
name = any(RemoteFlowSourceAccessPath s).getRootPath() and
|
||||
this = "ExternalRemoteFlowSourceSpec " + name
|
||||
}
|
||||
|
||||
string getName() { result = name }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef(name) }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
|
|
|
@ -12,4 +12,4 @@ import javascript
|
|||
import meta.MetaMetrics
|
||||
|
||||
select projectRoot(),
|
||||
count(API::Node pred, string lbl, API::Node succ | succ = pred.getASuccessor(lbl))
|
||||
count(API::Node pred, API::Label::ApiLabel lbl, API::Node succ | succ = pred.getASuccessor(lbl))
|
||||
|
|
|
@ -62,7 +62,9 @@ class Assertion extends Comment {
|
|||
i = this.getPathLength() and
|
||||
result = API::root()
|
||||
or
|
||||
result = this.lookup(i + 1).getASuccessor(this.getEdgeLabel(i))
|
||||
result =
|
||||
this.lookup(i + 1)
|
||||
.getASuccessor(any(API::Label::ApiLabel label | label.toString() = this.getEdgeLabel(i)))
|
||||
}
|
||||
|
||||
predicate isNegative() { polarity = "!" }
|
||||
|
@ -79,7 +81,11 @@ class Assertion extends Comment {
|
|||
then
|
||||
suffix =
|
||||
"it does have outgoing edges labelled " +
|
||||
concat(string lbl | exists(nd.getASuccessor(lbl)) | lbl, ", ") + "."
|
||||
concat(string lbl |
|
||||
exists(nd.getASuccessor(any(API::Label::ApiLabel label | label.toString() = lbl)))
|
||||
|
|
||||
lbl, ", "
|
||||
) + "."
|
||||
else suffix = "it has no outgoing edges at all."
|
||||
|
|
||||
result = prefix + " " + suffix
|
||||
|
|
Загрузка…
Ссылка в новой задаче