|
|
|
|
@@ -0,0 +1,459 @@
|
|
|
|
|
/**
|
|
|
|
|
* Provides predicates for generating names for classes and functions that are part
|
|
|
|
|
* of the public API of a library.
|
|
|
|
|
*
|
|
|
|
|
* When possible, we try to use the qualified name by which a class/function can be accessed
|
|
|
|
|
* from client code.
|
|
|
|
|
*
|
|
|
|
|
* However, there are cases where classes and functions can be exposed to client
|
|
|
|
|
* code without being accessible as a qualified name. For example;
|
|
|
|
|
* ```js
|
|
|
|
|
* // 'Foo' is internal, but clients can reach its methods via `getFoo().m()`
|
|
|
|
|
* class Foo {
|
|
|
|
|
* m() {}
|
|
|
|
|
* }
|
|
|
|
|
* export function getFoo() {
|
|
|
|
|
* return new Foo();
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* // Clients can reach m() via getObj().m()
|
|
|
|
|
* export function getObj() {
|
|
|
|
|
* return {
|
|
|
|
|
* m() {}
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* In these cases, we try to make up human-readable names for the endpoints.
|
|
|
|
|
* We make an effort to make these unambiguous in practice, though this is not always guaranteed.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
private import javascript
|
|
|
|
|
|
|
|
|
|
/** Concatenates two access paths. */
|
|
|
|
|
bindingset[x, y]
|
|
|
|
|
private string join(string x, string y) {
|
|
|
|
|
if x = "" or y = "" then result = x + y else result = x + "." + y
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private predicate isPackageExport(API::Node node) { node = API::moduleExport(_) }
|
|
|
|
|
|
|
|
|
|
private predicate memberEdge(API::Node pred, API::Node succ) { succ = pred.getAMember() }
|
|
|
|
|
|
|
|
|
|
/** Gets the shortest distance from a packaeg export to `nd` in the API graph. */
|
|
|
|
|
private int distanceFromPackageExport(API::Node nd) =
|
|
|
|
|
shortestDistances(isPackageExport/1, memberEdge/2)(_, nd, result)
|
|
|
|
|
|
|
|
|
|
private predicate isExported(API::Node node) {
|
|
|
|
|
isPackageExport(node)
|
|
|
|
|
or
|
|
|
|
|
exists(API::Node pred |
|
|
|
|
|
isExported(pred) and
|
|
|
|
|
memberEdge(pred, node)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `node` is a default export that can be reinterpreted as a namespace export,
|
|
|
|
|
* because the enclosing module has no named exports.
|
|
|
|
|
*/
|
|
|
|
|
private predicate defaultExportCanBeInterpretedAsNamespaceExport(API::Node node) {
|
|
|
|
|
exists(ES2015Module mod |
|
|
|
|
|
node.asSink() = mod.getAnExportedValue("default") and
|
|
|
|
|
not mod.hasBothNamedAndDefaultExports()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private predicate isPrivateAssignment(DataFlow::Node node) {
|
|
|
|
|
exists(MemberDeclaration decl |
|
|
|
|
|
node = decl.getInit().flow() and
|
|
|
|
|
decl.isPrivate()
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::PropWrite write |
|
|
|
|
|
write.isPrivateField() and
|
|
|
|
|
node = write.getRhs()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private predicate isPrivateLike(API::Node node) { isPrivateAssignment(node.asSink()) }
|
|
|
|
|
|
|
|
|
|
private API::Node getASuccessor(API::Node node, string name, int badness) {
|
|
|
|
|
isExported(node) and
|
|
|
|
|
exists(string member |
|
|
|
|
|
result = node.getMember(member) and
|
|
|
|
|
not isPrivateLike(node) and
|
|
|
|
|
if member = "default"
|
|
|
|
|
then
|
|
|
|
|
if defaultExportCanBeInterpretedAsNamespaceExport(node)
|
|
|
|
|
then (
|
|
|
|
|
badness = 5 and name = ""
|
|
|
|
|
) else (
|
|
|
|
|
badness = 10 and name = "default"
|
|
|
|
|
)
|
|
|
|
|
else (
|
|
|
|
|
name = member and badness = 0
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private API::Node getAPredecessor(API::Node node, string name, int badness) {
|
|
|
|
|
node = getASuccessor(result, name, badness)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets the predecessor of `node` to use when constructing a qualified name for it,
|
|
|
|
|
* and binds `name` and `badness` corresponding to the label on that edge.
|
|
|
|
|
*/
|
|
|
|
|
private API::Node getPreferredPredecessor(API::Node node, string name, int badness) {
|
|
|
|
|
// For root nodes, we prefer not having a predecessor, as we use the package name.
|
|
|
|
|
not isPackageExport(node) and
|
|
|
|
|
// Rank predecessors by name-badness, export-distance, and name.
|
|
|
|
|
// Since min() can only return a single value, we need a separate min() call per column.
|
|
|
|
|
badness = min(int b | exists(getAPredecessor(node, _, b)) | b) and
|
|
|
|
|
result =
|
|
|
|
|
min(API::Node pred, string name1 |
|
|
|
|
|
pred = getAPredecessor(node, name1, badness)
|
|
|
|
|
|
|
|
|
|
|
pred order by distanceFromPackageExport(pred), name1
|
|
|
|
|
) and
|
|
|
|
|
name = min(string n | result = getAPredecessor(node, n, badness) | n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if values escpin
|
|
|
|
|
*/
|
|
|
|
|
private predicate sinkHasNameCandidate(API::Node sink, string package, string name, int badness) {
|
|
|
|
|
sink = API::moduleExport(package) and
|
|
|
|
|
name = "" and
|
|
|
|
|
badness = 0
|
|
|
|
|
or
|
|
|
|
|
exists(API::Node baseNode, string baseName, int baseBadness, string step, int stepBadness |
|
|
|
|
|
sinkHasNameCandidate(baseNode, package, baseName, baseBadness) and
|
|
|
|
|
baseNode = getPreferredPredecessor(sink, step, stepBadness) and
|
|
|
|
|
badness = (baseBadness + stepBadness).minimum(20) and
|
|
|
|
|
name = join(baseName, step)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `(package, name)` is the primary name to associate with `node`.
|
|
|
|
|
*
|
|
|
|
|
* `badness` is bound to the associated badness of the name.
|
|
|
|
|
*/
|
|
|
|
|
private predicate sinkHasPrimaryName(API::Node sink, string package, string name, int badness) {
|
|
|
|
|
badness = min(int b | sinkHasNameCandidate(sink, _, _, b) | b) and
|
|
|
|
|
package = min(string p | sinkHasNameCandidate(sink, p, _, badness) | p) and
|
|
|
|
|
name = min(string n | sinkHasNameCandidate(sink, package, n, badness) | n order by n.length(), n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `(package, name)` is the primary name to associate with `node`.
|
|
|
|
|
*/
|
|
|
|
|
private predicate sinkHasPrimaryName(API::Node sink, string package, string name) {
|
|
|
|
|
sinkHasPrimaryName(sink, package, name, _)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `(package, name)` is an alias for `node`.
|
|
|
|
|
*
|
|
|
|
|
* This means it is a valid name for it, but was not chosen as the primary name.
|
|
|
|
|
*/
|
|
|
|
|
predicate sinkHasAlias(API::Node sink, string package, string name) {
|
|
|
|
|
not sinkHasPrimaryName(sink, package, name) and
|
|
|
|
|
(
|
|
|
|
|
exists(string baseName, string step |
|
|
|
|
|
sinkHasPrimaryName(getAPredecessor(sink, step, _), package, baseName) and
|
|
|
|
|
name = join(baseName, step)
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
sink = API::moduleExport(package) and
|
|
|
|
|
name = ""
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a sink node reachable from `node`. */
|
|
|
|
|
bindingset[node]
|
|
|
|
|
private API::Node getASinkNode(DataFlow::SourceNode node) { result.getAValueReachingSink() = node }
|
|
|
|
|
|
|
|
|
|
bindingset[qualifiedName]
|
|
|
|
|
private int getBadnessOfClassName(string qualifiedName) {
|
|
|
|
|
if qualifiedName.matches("%.constructor")
|
|
|
|
|
then result = 10
|
|
|
|
|
else
|
|
|
|
|
if qualifiedName = ""
|
|
|
|
|
then result = 5
|
|
|
|
|
else result = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if `(package, name)` is a potential name for `cls`, with the given `badness`. */
|
|
|
|
|
private predicate classObjectHasNameCandidate(
|
|
|
|
|
DataFlow::ClassNode cls, string package, string name, int badness
|
|
|
|
|
) {
|
|
|
|
|
// There can be multiple API nodes associated with `cls`.
|
|
|
|
|
// For example:
|
|
|
|
|
///
|
|
|
|
|
// class C {}
|
|
|
|
|
// module.exports.A = C; // first sink
|
|
|
|
|
// module.exports.B = C; // second sink
|
|
|
|
|
//
|
|
|
|
|
exists(int baseBadness |
|
|
|
|
|
sinkHasPrimaryName(getASinkNode(cls), package, name, baseBadness) and
|
|
|
|
|
badness = baseBadness + getBadnessOfClassName(name)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private predicate classObjectHasPrimaryName(
|
|
|
|
|
DataFlow::ClassNode cls, string package, string name, int badness
|
|
|
|
|
) {
|
|
|
|
|
badness = min(int b | classObjectHasNameCandidate(cls, _, _, b) | b) and
|
|
|
|
|
package = min(string p | classObjectHasNameCandidate(cls, p, _, badness) | p) and
|
|
|
|
|
name = min(string n | classObjectHasNameCandidate(cls, package, n, badness) | n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if `(package, name)` is the primary name for the class object of `cls`. */
|
|
|
|
|
predicate classObjectHasPrimaryName(DataFlow::ClassNode cls, string package, string name) {
|
|
|
|
|
classObjectHasPrimaryName(cls, package, name, _)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if `(package, name)` is an alias for the class object of `cls`. */
|
|
|
|
|
predicate classObjectHasAlias(DataFlow::ClassNode cls, string package, string name) {
|
|
|
|
|
not classObjectHasPrimaryName(cls, package, name) and
|
|
|
|
|
exists(int badness |
|
|
|
|
|
classObjectHasNameCandidate(cls, package, name, badness) and
|
|
|
|
|
badness < 100
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if an instance of `cls` can be exposed to client code. */
|
|
|
|
|
private predicate hasEscapingInstance(DataFlow::ClassNode cls) {
|
|
|
|
|
cls.getAnInstanceReference().flowsTo(any(API::Node n).asSink())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `(package, name)` is a potential name to use for instances of `cls`, with the given `badness`.
|
|
|
|
|
*/
|
|
|
|
|
private predicate classInstanceHasNameCandidate(
|
|
|
|
|
DataFlow::ClassNode cls, string package, string name, int badness
|
|
|
|
|
) {
|
|
|
|
|
exists(string baseName |
|
|
|
|
|
classObjectHasPrimaryName(cls, package, baseName, badness) and
|
|
|
|
|
name = join(baseName, "prototype")
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
// In case the class itself is unaccessible, but an instance is exposed via an access path,
|
|
|
|
|
// consider using that access path. For example:
|
|
|
|
|
//
|
|
|
|
|
// class InternalClass {}
|
|
|
|
|
// module.exports.foo = new InternalClass();
|
|
|
|
|
//
|
|
|
|
|
exists(int baseBadness |
|
|
|
|
|
sinkHasPrimaryName(getASinkNode(cls.getAnInstanceReference()), package, name, baseBadness) and
|
|
|
|
|
badness = baseBadness + 30 // add penalty, as we prefer to base this on the class name
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
// If neither the class nor its instances are accessible via an access path, but instances of the
|
|
|
|
|
// class can still escape via more complex access patterns, resort to a synthesized name.
|
|
|
|
|
// For example:
|
|
|
|
|
//
|
|
|
|
|
// class InternalClass {}
|
|
|
|
|
// function foo() {
|
|
|
|
|
// return new InternalClass();
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
hasEscapingInstance(cls) and
|
|
|
|
|
exists(string baseName |
|
|
|
|
|
InternalModuleNaming::fallbackModuleName(cls.getTopLevel(), package, baseName, badness - 100) and
|
|
|
|
|
name = join(baseName, cls.getName()) + ".prototype"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private predicate classInstanceHasPrimaryName(
|
|
|
|
|
DataFlow::ClassNode cls, string package, string name, int badness
|
|
|
|
|
) {
|
|
|
|
|
badness = min(int b | classInstanceHasNameCandidate(cls, _, _, b) | b) and
|
|
|
|
|
package = min(string p | classInstanceHasNameCandidate(cls, p, _, badness) | p) and
|
|
|
|
|
name =
|
|
|
|
|
min(string n |
|
|
|
|
|
classInstanceHasNameCandidate(cls, package, n, badness)
|
|
|
|
|
|
|
|
|
|
|
n order by n.length(), n
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if `(package, name)` is the primary name to use for instances of `cls`. */
|
|
|
|
|
predicate classInstanceHasPrimaryName(DataFlow::ClassNode cls, string package, string name) {
|
|
|
|
|
classInstanceHasPrimaryName(cls, package, name, _)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if `(package, name)` is an alias referring to some instance of `cls`. */
|
|
|
|
|
predicate classInstanceHasAlias(DataFlow::ClassNode cls, string package, string name) {
|
|
|
|
|
not classInstanceHasPrimaryName(cls, package, name) and
|
|
|
|
|
exists(int badness |
|
|
|
|
|
classInstanceHasNameCandidate(cls, package, name, badness) and
|
|
|
|
|
badness < 100 // Badness 100 is when we start to synthesize names. Do not suggest these as aliases.
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private predicate functionHasNameCandidate(
|
|
|
|
|
DataFlow::FunctionNode function, string package, string name, int badness
|
|
|
|
|
) {
|
|
|
|
|
sinkHasPrimaryName(getASinkNode(function), package, name, badness)
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::ClassNode cls |
|
|
|
|
|
function = cls.getConstructor() and
|
|
|
|
|
classObjectHasPrimaryName(cls, package, name, badness)
|
|
|
|
|
or
|
|
|
|
|
exists(string baseName, string memberName |
|
|
|
|
|
function = cls.getInstanceMethod(memberName) and
|
|
|
|
|
classInstanceHasPrimaryName(cls, package, baseName, badness) and
|
|
|
|
|
name = join(baseName, memberName)
|
|
|
|
|
or
|
|
|
|
|
function = cls.getStaticMethod(memberName) and
|
|
|
|
|
classObjectHasPrimaryName(cls, package, baseName, badness) and
|
|
|
|
|
name = join(baseName, memberName)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private predicate functionHasPrimaryName(
|
|
|
|
|
DataFlow::FunctionNode function, string package, string name, int badness
|
|
|
|
|
) {
|
|
|
|
|
badness = min(int b | functionHasNameCandidate(function, _, _, b) | b) and
|
|
|
|
|
package = min(string p | functionHasNameCandidate(function, p, _, badness) | p) and
|
|
|
|
|
name =
|
|
|
|
|
min(string n |
|
|
|
|
|
functionHasNameCandidate(function, package, n, badness)
|
|
|
|
|
|
|
|
|
|
|
n order by n.length(), n
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `(package, name)` is the primary name for the given `function`.
|
|
|
|
|
*/
|
|
|
|
|
predicate functionHasPrimaryName(DataFlow::FunctionNode function, string package, string name) {
|
|
|
|
|
functionHasPrimaryName(function, package, name, _)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Holds if `(package, name)` is an alias for the given `function`.
|
|
|
|
|
*/
|
|
|
|
|
predicate functionHasAlias(DataFlow::FunctionNode function, string package, string name) {
|
|
|
|
|
not functionHasPrimaryName(function, package, name) and
|
|
|
|
|
exists(int badness |
|
|
|
|
|
functionHasNameCandidate(function, package, name, badness) and
|
|
|
|
|
badness < 100
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a `(package, name)` pair to a string of form `(package).name`.
|
|
|
|
|
*/
|
|
|
|
|
bindingset[package, name]
|
|
|
|
|
string renderName(string package, string name) { result = join("(" + package + ")", name) }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Contains predicates for naming individual modules (i.e. files) inside of a package.
|
|
|
|
|
*
|
|
|
|
|
* These names are not necessarily part of a package's public API, and so we only used them
|
|
|
|
|
* as a fallback when a publicly-accessible access path cannot be found.
|
|
|
|
|
*/
|
|
|
|
|
private module InternalModuleNaming {
|
|
|
|
|
/** Gets the path to `folder` relative to its enclosing non-private `package.json` file. */
|
|
|
|
|
private string getPackageRelativePathFromFolder(Folder folder) {
|
|
|
|
|
exists(PackageJson json |
|
|
|
|
|
json.getFile() = folder.getFile("package.json") and
|
|
|
|
|
not json.isPrivate() and
|
|
|
|
|
result = json.getPackageName()
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
not exists(folder.getFile("package.json")) and
|
|
|
|
|
result =
|
|
|
|
|
getPackageRelativePathFromFolder(folder.getParentContainer()) + "/" + folder.getBaseName()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string getPackageRelativePath(Module mod) {
|
|
|
|
|
exists(PackageJson json, string relativePath |
|
|
|
|
|
not json.isPrivate() and
|
|
|
|
|
json.getExportedModule(relativePath) = mod and
|
|
|
|
|
if relativePath = "."
|
|
|
|
|
then result = json.getPackageName()
|
|
|
|
|
else result = json.getPackageName() + "/" + relativePath.regexpReplaceAll("^\\./", "")
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
not mod = any(PackageJson json | not json.isPrivate()).getExportedModule(_) and
|
|
|
|
|
not mod.isAmbient() and
|
|
|
|
|
exists(string folderPath |
|
|
|
|
|
folderPath = getPackageRelativePathFromFolder(mod.getFile().getParentContainer()) and
|
|
|
|
|
if mod.getName() = "index"
|
|
|
|
|
then result = folderPath
|
|
|
|
|
else result = folderPath + "/" + mod.getName()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if `(package, name)` should be used to refer to code inside `mod`. */
|
|
|
|
|
predicate fallbackModuleName(Module mod, string package, string name, int badness) {
|
|
|
|
|
sinkHasPrimaryName(getASinkNode(mod.getDefaultOrBulkExport()), package, name, badness)
|
|
|
|
|
or
|
|
|
|
|
badness = 50 and
|
|
|
|
|
package = getPackageRelativePath(mod) and
|
|
|
|
|
name = ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Contains query predicates for emitting debugging information about endpoint naming.
|
|
|
|
|
*/
|
|
|
|
|
module Debug {
|
|
|
|
|
/** Holds if `node` has multiple preferred predecessors. */
|
|
|
|
|
query predicate ambiguousPreferredPredecessor(API::Node node) {
|
|
|
|
|
strictcount(API::Node pred, string name, int badness |
|
|
|
|
|
pred = getPreferredPredecessor(node, name, badness)
|
|
|
|
|
) > 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if the given `node` has multiple primary names. */
|
|
|
|
|
query string ambiguousSinkName(API::Node node) {
|
|
|
|
|
strictcount(string package, string name | sinkHasPrimaryName(node, package, name)) > 1 and
|
|
|
|
|
result =
|
|
|
|
|
concat(string package, string name |
|
|
|
|
|
sinkHasPrimaryName(node, package, name)
|
|
|
|
|
|
|
|
|
|
|
renderName(package, name), ", "
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if the given `node` has multiple primary names. */
|
|
|
|
|
query string ambiguousClassObjectName(DataFlow::ClassNode node) {
|
|
|
|
|
strictcount(string package, string name | classObjectHasPrimaryName(node, package, name)) > 1 and
|
|
|
|
|
result =
|
|
|
|
|
concat(string package, string name |
|
|
|
|
|
classObjectHasPrimaryName(node, package, name)
|
|
|
|
|
|
|
|
|
|
|
renderName(package, name), ", "
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if the given `node` has multiple primary names. */
|
|
|
|
|
query string ambiguousClassInstanceName(DataFlow::ClassNode node) {
|
|
|
|
|
strictcount(string package, string name | classInstanceHasPrimaryName(node, package, name)) > 1 and
|
|
|
|
|
result =
|
|
|
|
|
concat(string package, string name |
|
|
|
|
|
classInstanceHasPrimaryName(node, package, name)
|
|
|
|
|
|
|
|
|
|
|
renderName(package, name), ", "
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Holds if the given `node` has multiple primary names. */
|
|
|
|
|
query string ambiguousFunctionName(DataFlow::FunctionNode node) {
|
|
|
|
|
strictcount(string package, string name | functionHasPrimaryName(node, package, name)) > 1 and
|
|
|
|
|
result =
|
|
|
|
|
concat(string package, string name |
|
|
|
|
|
functionHasPrimaryName(node, package, name)
|
|
|
|
|
|
|
|
|
|
|
renderName(package, name), ", "
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|