JavaScript: Autoformat all QL files.

This commit is contained in:
Max Schaefer 2019-01-07 10:15:45 +00:00
Родитель aa6b89dc34
Коммит 31bb39a810
380 изменённых файлов: 9957 добавлений и 13923 удалений

Просмотреть файл

@ -12,80 +12,70 @@ import javascript
*/
class SuppressionComment extends Locatable {
string text;
string annotation;
SuppressionComment() {
(
text = this.(LineComment).getText() or
text = this.(HTML::CommentNode).getText()
)
and
text = this.(LineComment).getText() or
text = this.(HTML::CommentNode).getText()
) and
(
// match `lgtm[...]` anywhere in the comment
annotation = text.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _)
or
// match `lgtm` at the start of the comment and after semicolon
annotation = text.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim()
// match `lgtm[...]` anywhere in the comment
annotation = text.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _)
or
// match `lgtm` at the start of the comment and after semicolon
annotation = text.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim()
)
}
/** Gets the text of this suppression comment, not including delimiters. */
string getText() {
result = text
}
string getText() { result = text }
/** Gets the suppression annotation in this comment. */
string getAnnotation() {
result = annotation
}
string getAnnotation() { result = annotation }
/**
* Holds if this comment applies to the range from column `startcolumn` of line `startline`
* to column `endcolumn` of line `endline` in file `filepath`.
*/
* Holds if this comment applies to the range from column `startcolumn` of line `startline`
* to column `endcolumn` of line `endline` in file `filepath`.
*/
predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and
startcolumn = 1
}
/** Gets the scope of this suppression. */
SuppressionScope getScope() {
this = result.getSuppressionComment()
}
SuppressionScope getScope() { this = result.getSuppressionComment() }
}
/**
* The scope of an alert suppression comment.
*/
class SuppressionScope extends @locatable {
SuppressionScope() {
this instanceof SuppressionComment
}
SuppressionScope() { this instanceof SuppressionComment }
/** Gets a suppression comment with this scope. */
SuppressionComment getSuppressionComment() {
result = this
}
SuppressionComment getSuppressionComment() { result = this }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets a textual representation of this element. */
string toString() {
result = "suppression range"
}
string toString() { result = "suppression range" }
}
from SuppressionComment c
select c, // suppression comment
c.getText(), // text of suppression comment (excluding delimiters)
c.getAnnotation(), // text of suppression annotation
c.getScope() // scope of suppression
select c, // suppression comment
c.getText(), // text of suppression comment (excluding delimiters)
c.getAnnotation(), // text of suppression annotation
c.getScope() // scope of suppression

Просмотреть файл

@ -17,14 +17,11 @@ import javascript
predicate isABuiltinEventName(string name) {
// $rootScope.Scope
name = "$destroy" or
// $location
name = "$locationChangeStart" or
name = "$locationChangeSuccess" or
// ngView
name = "$viewContentLoaded" or
// angular-ui/ui-router
name = "$stateChangeStart" or
name = "$stateNotFound" or
@ -32,13 +29,11 @@ predicate isABuiltinEventName(string name) {
name = "$stateChangeError" or
name = "$viewContentLoading " or
name = "$viewContentLoaded " or
// $route
name = "$routeChangeStart" or
name = "$routeChangeSuccess" or
name = "$routeChangeError" or
name = "$routeUpdate" or
// ngInclude
name = "$includeContentRequested" or
name = "$includeContentLoaded" or
@ -49,20 +44,21 @@ predicate isABuiltinEventName(string name) {
* Holds if user code emits or broadcasts an event named `name`.
*/
predicate isAUserDefinedEventName(string name) {
exists (string methodName, MethodCallExpr mce |
methodName = "$emit" or methodName = "$broadcast" |
exists(string methodName, MethodCallExpr mce | methodName = "$emit" or methodName = "$broadcast" |
mce.getArgument(0).mayHaveStringValue(name) and
(
// dataflow based scope resolution
mce = any(AngularJS::ScopeServiceReference scope).getAMethodCall(methodName) or
mce = any(AngularJS::ScopeServiceReference scope).getAMethodCall(methodName)
or
// heuristic scope resolution: assume parameters like `$scope` or `$rootScope` are AngularJS scope objects
exists(SimpleParameter param |
param.getName() = any(AngularJS::ScopeServiceReference scope).getName() and
mce.getReceiver().mayReferToParameter(param) and
mce.getMethodName() = methodName
) or
)
or
// a call in an AngularJS expression
exists (AngularJS::NgCallExpr call |
exists(AngularJS::NgCallExpr call |
call.getCallee().(AngularJS::NgVarExpr).getName() = methodName and
call.getArgument(0).(AngularJS::NgString).getStringValue() = name
)
@ -71,14 +67,16 @@ predicate isAUserDefinedEventName(string name) {
}
from AngularJS::ScopeServiceReference scope, MethodCallExpr mce, string eventName
where mce = scope.getAMethodCall("$on") and
mce.getArgument(0).mayHaveStringValue(eventName) and
not (
isAUserDefinedEventName(eventName) or
isABuiltinEventName(eventName) or
// external, namespaced
eventName.regexpMatch(".*[.:].*") or
// from other event system (DOM: onClick et al)
eventName.regexpMatch("on[A-Z][a-zA-Z]+") // camelCased with 'on'-prefix
)
select mce.getArgument(1), "This event listener is dead, the event '" + eventName + "' is not emitted anywhere."
where
mce = scope.getAMethodCall("$on") and
mce.getArgument(0).mayHaveStringValue(eventName) and
not (
isAUserDefinedEventName(eventName) or
isABuiltinEventName(eventName) or
// external, namespaced
eventName.regexpMatch(".*[.:].*") or
// from other event system (DOM: onClick et al)
eventName.regexpMatch("on[A-Z][a-zA-Z]+") // camelCased with 'on'-prefix
)
select mce.getArgument(1),
"This event listener is dead, the event '" + eventName + "' is not emitted anywhere."

Просмотреть файл

@ -15,16 +15,17 @@
import javascript
from AngularJS::InjectableFunction f, SimpleParameter p, string msg
where p = f.asFunction().getAParameter() and
(
not p = f.getDependencyParameter(_) and
msg = "This parameter has no injected dependency."
or
exists (string n | p = f.getDependencyParameter(n) |
p.getName() != n and
exists(f.getDependencyParameter(p.getName())) and
msg = "This parameter is named '" + p.getName() + "', " +
"but actually refers to dependency '" + n + "'."
)
)
select p, msg
where
p = f.asFunction().getAParameter() and
(
not p = f.getDependencyParameter(_) and
msg = "This parameter has no injected dependency."
or
exists(string n | p = f.getDependencyParameter(n) |
p.getName() != n and
exists(f.getDependencyParameter(p.getName())) and
msg = "This parameter is named '" + p.getName() + "', " +
"but actually refers to dependency '" + n + "'."
)
)
select p, msg

Просмотреть файл

@ -13,7 +13,8 @@
import javascript
from MethodCallExpr mce, AngularJS::BuiltinServiceReference service
where service.getName() ="$sceProvider" and
mce = service.getAMethodCall( "enabled") and
mce.getArgument(0).mayHaveBooleanValue(false)
where
service.getName() = "$sceProvider" and
mce = service.getAMethodCall("enabled") and
mce.getArgument(0).mayHaveBooleanValue(false)
select mce, "Disabling SCE is strongly discouraged."

Просмотреть файл

@ -13,10 +13,14 @@
import javascript
from AngularJS::ServiceReference compile, SimpleParameter elem, CallExpr c
where compile.getName() = "$compile" and
elem = any(AngularJS::CustomDirective d).getALinkFunction().(AngularJS::LinkFunction).getElementParameter() and
c = compile.getACall() and
c.getArgument(0).mayReferToParameter(elem) and
// don't flag $compile calls that specify a `maxPriority`
c.getNumArgument() < 3
where
compile.getName() = "$compile" and
elem = any(AngularJS::CustomDirective d)
.getALinkFunction()
.(AngularJS::LinkFunction)
.getElementParameter() and
c = compile.getACall() and
c.getArgument(0).mayReferToParameter(elem) and
// don't flag $compile calls that specify a `maxPriority`
c.getNumArgument() < 3
select c, "This call to $compile may cause double compilation of '" + elem + "'."

Просмотреть файл

@ -13,12 +13,15 @@ import javascript
import semmle.javascript.RestrictedLocations
predicate isRepeatedDependency(AngularJS::InjectableFunction f, string name, ASTNode location) {
exists(int i, int j | i < j and
exists(int i, int j |
i < j and
exists(f.getDependencyDeclaration(i, name)) and
location = f.getDependencyDeclaration(j, name)
)
}
from AngularJS::InjectableFunction f, ASTNode node, string name
where isRepeatedDependency(f, name, node) and
not count(f.asFunction().getParameterByName(name)) > 1 // avoid duplicating reports from js/duplicate-parameter-name
select (FirstLineOf)f.asFunction(), "This function has a duplicate dependency '$@'.", node, name
where
isRepeatedDependency(f, name, node) and
not count(f.asFunction().getParameterByName(name)) > 1 // avoid duplicating reports from js/duplicate-parameter-name
select f.asFunction().(FirstLineOf), "This function has a duplicate dependency '$@'.", node, name

Просмотреть файл

@ -8,6 +8,7 @@
* @tags correctness
* frameworks/angularjs
*/
import javascript
import AngularJS
@ -63,21 +64,31 @@ predicate isWildcardKind(string kind) {
* (see https://docs.angularjs.org/guide/di)
*/
predicate isCompatibleRequestedService(InjectableFunctionServiceRequest request, string kind) {
isWildcardKind(kind) or
((isServiceDirectiveOrFilterFunction(request) or
isWildcardKind(kind)
or
(
(
isServiceDirectiveOrFilterFunction(request) or
isRunMethod(request) or
isControllerFunction(request)
) and (
) and
(
kind = "value" or
kind = "service" or
kind = "factory" or
kind = "constant" or
kind = "provider-value"
)
) or
(isControllerFunction(request) and
kind = "controller-only") or
(isConfigMethod(request) and (
)
or
(
isControllerFunction(request) and
kind = "controller-only"
)
or
(
isConfigMethod(request) and
(
kind = "constant" or
kind = "provider"
)
@ -89,46 +100,72 @@ predicate isCompatibleRequestedService(InjectableFunctionServiceRequest request,
*/
string getServiceKind(InjectableFunctionServiceRequest request, string serviceName) {
exists(ServiceReference id | id = request.getAServiceDefinition(serviceName) |
id = getBuiltinServiceOfKind(result) or
id = getBuiltinServiceOfKind(result)
or
exists(CustomServiceDefinition custom |
id = custom.getServiceReference() and
((custom instanceof ValueRecipeDefinition and result = "value") or
(custom instanceof ServiceRecipeDefinition and result = "service") or
(custom instanceof FactoryRecipeDefinition and result = "factory") or
(custom instanceof DecoratorRecipeDefinition and result = "decorator") or
(custom instanceof ConstantRecipeDefinition and result = "constant") or
(custom instanceof ProviderRecipeDefinition and
if (serviceName.matches("%Provider")) then result = "provider"
(
(custom instanceof ValueRecipeDefinition and result = "value")
or
(custom instanceof ServiceRecipeDefinition and result = "service")
or
(custom instanceof FactoryRecipeDefinition and result = "factory")
or
(custom instanceof DecoratorRecipeDefinition and result = "decorator")
or
(custom instanceof ConstantRecipeDefinition and result = "constant")
or
(
custom instanceof ProviderRecipeDefinition and
if (serviceName.matches("%Provider"))
then result = "provider"
else result = "provider-value"
))
)
)
)
)
}
from InjectableFunctionServiceRequest request, string name, string componentDescriptionString, string compatibleWithString, string kind
from
InjectableFunctionServiceRequest request, string name, string componentDescriptionString,
string compatibleWithString, string kind
where
name = request.getAServiceName() and
name != "$provide" and name != "$injector" and // special case: these services are always allowed
name != "$provide" and
name != "$injector" and // special case: these services are always allowed
kind = getServiceKind(request, name) and
exists(request.getAServiceDefinition(name)) and // ignore unknown/undefined services
not isCompatibleRequestedService(request, kind) and
compatibleWithString = concat(string compatibleKind |
isCompatibleRequestedService(request, compatibleKind) and
not isWildcardKind(compatibleKind) |
"'" + compatibleKind + "'", ", " order by compatibleKind).regexpReplaceAll(",(?=[^,]+$)", " or") and
isCompatibleRequestedService(request, compatibleKind) and
not isWildcardKind(compatibleKind)
|
"'" + compatibleKind + "'", ", "
order by
compatibleKind
).regexpReplaceAll(",(?=[^,]+$)", " or") and
(
(isServiceDirectiveOrFilterFunction(request) and
(
isServiceDirectiveOrFilterFunction(request) and
componentDescriptionString = "Components such as services, directives, filters, and animations"
) or
(isControllerFunction(request) and
)
or
(
isControllerFunction(request) and
componentDescriptionString = "Controllers"
) or
(isRunMethod(request) and
)
or
(
isRunMethod(request) and
componentDescriptionString = "Run methods"
) or
(isConfigMethod(request) and
)
or
(
isConfigMethod(request) and
componentDescriptionString = "Config methods"
)
)
select request, "'" + name + "' is a dependency of kind '" + kind + "', and cannot be injected here. " + componentDescriptionString + " can only be injected with dependencies of kind " + compatibleWithString + "."
select request,
"'" + name + "' is a dependency of kind '" + kind + "', and cannot be injected here. " +
componentDescriptionString + " can only be injected with dependencies of kind " +
compatibleWithString + "."

Просмотреть файл

@ -17,8 +17,10 @@ import javascript
* Holds if `setupCall` is a call to `$sceDelegateProvider.resourceUrlWhitelist` with
* argument `list`.
*/
predicate isResourceUrlWhitelist(DataFlow::MethodCallNode setupCall, DataFlow::ArrayCreationNode list) {
exists (AngularJS::ServiceReference service |
predicate isResourceUrlWhitelist(
DataFlow::MethodCallNode setupCall, DataFlow::ArrayCreationNode list
) {
exists(AngularJS::ServiceReference service |
service.getName() = "$sceDelegateProvider" and
setupCall.asExpr() = service.getAMethodCall("resourceUrlWhitelist") and
list.flowsTo(setupCall.getArgument(0))
@ -30,10 +32,11 @@ predicate isResourceUrlWhitelist(DataFlow::MethodCallNode setupCall, DataFlow::A
*/
class ResourceUrlWhitelistEntry extends Expr {
DataFlow::MethodCallNode setupCall;
string pattern;
ResourceUrlWhitelistEntry() {
exists (DataFlow::ArrayCreationNode whitelist |
exists(DataFlow::ArrayCreationNode whitelist |
isResourceUrlWhitelist(setupCall, whitelist) and
this = whitelist.getAnElement().asExpr() and
this.mayHaveStringValue(pattern)
@ -43,28 +46,25 @@ class ResourceUrlWhitelistEntry extends Expr {
/**
* Gets the method call that sets up this whitelist.
*/
DataFlow::MethodCallNode getSetupCall() {
result = setupCall
}
DataFlow::MethodCallNode getSetupCall() { result = setupCall }
/**
* Holds if this expression is insecure to use in an URL pattern whitelist due
* to the reason given by `explanation`.
*/
predicate isInsecure(string explanation) {
exists (string componentName, string component |
exists (int componentNumber |
exists(string componentName, string component |
exists(int componentNumber |
componentName = "scheme" and componentNumber = 1
or
componentName = "domain" and componentNumber = 2
or
componentName = "TLD" and componentNumber = 4
|
component = pattern.regexpCapture("(.*?)://(.*?(\\.(.*?))?)(:\\d+)?(/.*)?", componentNumber)
)
and
explanation = "the " + componentName + " '" + component + "' is insecurely specified"
|
component = pattern.regexpCapture("(.*?)://(.*?(\\.(.*?))?)(:\\d+)?(/.*)?", componentNumber)
) and
explanation = "the " + componentName + " '" + component + "' is insecurely specified"
|
componentName = "scheme" and component.matches("%*%")
or
componentName = "domain" and component.matches("%**%")
@ -75,7 +75,8 @@ class ResourceUrlWhitelistEntry extends Expr {
}
from ResourceUrlWhitelistEntry entry, DataFlow::MethodCallNode setupCall, string explanation
where entry.isInsecure(explanation) and
setupCall = entry.getSetupCall()
select setupCall, "'$@' is not a secure whitelist entry, because " + explanation + ".",
entry, entry.toString()
where
entry.isInsecure(explanation) and
setupCall = entry.getSetupCall()
select setupCall, "'$@' is not a secure whitelist entry, because " + explanation + ".", entry,
entry.toString()

Просмотреть файл

@ -1,23 +1,26 @@
/**
* @name Missing explicit dependency injection
* @description Functions without explicit dependency injections
will not work when their parameter names are minified.
* will not work when their parameter names are minified.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/angular/missing-explicit-injection
* @tags correctness
maintainability
* maintainability
* frameworks/angularjs
*/
import javascript
from AngularJS::InjectableFunction f1, AngularJS::InjectableFunction f2
where f1.asFunction().getNumParameter() > 0 and
not exists(f1.getAnExplicitDependencyInjection()) and
// ... but only if explicit dependencies are used somewhere else in the same file
f1 != f2 and
exists(f2.getAnExplicitDependencyInjection()) and
f1.getFile() = f2.getFile()
select f1, "This function has no explicit dependency injections, but $@ has an explicit dependency injection.", f2, "this function"
where
f1.asFunction().getNumParameter() > 0 and
not exists(f1.getAnExplicitDependencyInjection()) and
// ... but only if explicit dependencies are used somewhere else in the same file
f1 != f2 and
exists(f2.getAnExplicitDependencyInjection()) and
f1.getFile() = f2.getFile()
select f1,
"This function has no explicit dependency injections, but $@ has an explicit dependency injection.",
f2, "this function"

Просмотреть файл

@ -13,6 +13,8 @@ import javascript
import semmle.javascript.RestrictedLocations
from AngularJS::InjectableFunction f, ASTNode explicitInjection
where count(f.getAnExplicitDependencyInjection()) > 1 and
explicitInjection = f.getAnExplicitDependencyInjection()
select (FirstLineOf)f.asFunction(), "This function has $@ defined in multiple places.", explicitInjection, "dependency injections"
where
count(f.getAnExplicitDependencyInjection()) > 1 and
explicitInjection = f.getAnExplicitDependencyInjection()
select f.asFunction().(FirstLineOf), "This function has $@ defined in multiple places.",
explicitInjection, "dependency injections"

Просмотреть файл

@ -22,19 +22,25 @@ predicate isUnusedParameter(Function f, string msg, Parameter parameter) {
}
predicate isMissingParameter(AngularJS::InjectableFunction f, string msg, ASTNode location) {
exists(int paramCount, int injectionCount |
exists(int paramCount, int injectionCount |
DataFlow::valueNode(location) = f and
paramCount = f.asFunction().getNumParameter() and
injectionCount = count(f.getADependencyDeclaration(_)) and
paramCount < injectionCount and
exists(string parametersString, string dependenciesAreString |
(if paramCount = 1 then parametersString = "parameter" else parametersString = "parameters") and
(if injectionCount = 1 then dependenciesAreString = "dependency is" else dependenciesAreString = "dependencies are") and
msg = "This function has " + paramCount + " " + parametersString + ", but " + injectionCount + " " + dependenciesAreString + " injected into it."
(
if injectionCount = 1
then dependenciesAreString = "dependency is"
else dependenciesAreString = "dependencies are"
) and
msg = "This function has " + paramCount + " " + parametersString + ", but " + injectionCount +
" " + dependenciesAreString + " injected into it."
)
)
}
from AngularJS::InjectableFunction f, string message, ASTNode location
where isUnusedParameter(f.asFunction(), message, location) or isMissingParameter(f, message, location)
select (FirstLineOf)location, message
where
isUnusedParameter(f.asFunction(), message, location) or isMissingParameter(f, message, location)
select location.(FirstLineOf), message

Просмотреть файл

@ -14,12 +14,13 @@
import javascript
from HTML::Attribute attr, string name
where name = attr.getName() and
// only flag URL-valued attributes...
(name = "href" or name = "src" or name = "srcset") and
// ...where the value contains some interpolated expressions
attr.getValue().matches("%{{%}}") and
// check that there is at least one use of an AngularJS attribute directive nearby
// (`{{...}}` is used by other templating frameworks as well)
any(AngularJS::DirectiveInstance d).getATarget().getElement().getRoot() = attr.getRoot()
where
name = attr.getName() and
// only flag URL-valued attributes...
(name = "href" or name = "src" or name = "srcset") and
// ...where the value contains some interpolated expressions
attr.getValue().matches("%{{%}}") and
// check that there is at least one use of an AngularJS attribute directive nearby
// (`{{...}}` is used by other templating frameworks as well)
any(AngularJS::DirectiveInstance d).getATarget().getElement().getRoot() = attr.getRoot()
select attr, "Use 'ng-" + name + "' instead of '" + name + "'."

Просмотреть файл

@ -19,15 +19,17 @@ import javascript
*
* Furthermore, the id is required to be valid, and not look like a template.
*/
predicate idAt(DOM::AttributeDefinition attr, string id, DOM::ElementDefinition root, int line, int column) {
exists (DOM::ElementDefinition elt |
attr = elt.getAttributeByName("id") |
predicate idAt(
DOM::AttributeDefinition attr, string id, DOM::ElementDefinition root, int line, int column
) {
exists(DOM::ElementDefinition elt | attr = elt.getAttributeByName("id") |
id = attr.getStringValue() and
root = elt.getRoot() and
elt.getLocation().hasLocationInfo(_, line, column, _, _) and
not (
// exclude invalid ids (reported by another query)
DOM::isInvalidHtmlIdAttributeValue(attr, _) or
DOM::isInvalidHtmlIdAttributeValue(attr, _)
or
// exclude attribute values that look like they might be templated
attr.mayHaveTemplateValue()
)
@ -39,9 +41,11 @@ predicate idAt(DOM::AttributeDefinition attr, string id, DOM::ElementDefinition
* the same document, and `earlier` appears textually before `later`.
*/
predicate sameId(DOM::AttributeDefinition earlier, DOM::AttributeDefinition later) {
exists (string id, DOM::ElementDefinition root, int l1, int c1, int l2, int c2 |
idAt(earlier, id, root, l1, c1) and idAt(later, id, root, l2, c2) |
l1 < l2 or
exists(string id, DOM::ElementDefinition root, int l1, int c1, int l2, int c2 |
idAt(earlier, id, root, l1, c1) and idAt(later, id, root, l2, c2)
|
l1 < l2
or
l1 = l2 and c1 < c2
)
}

Просмотреть файл

@ -18,8 +18,9 @@ import javascript
* and different values, where `earlier` appears textually before `later`.
*/
predicate conflict(DOM::AttributeDefinition earlier, DOM::AttributeDefinition later) {
exists (DOM::ElementDefinition elt, int i, int j |
earlier = elt.getAttribute(i) and later = elt.getAttribute(j) |
exists(DOM::ElementDefinition elt, int i, int j |
earlier = elt.getAttribute(i) and later = elt.getAttribute(j)
|
i < j and
earlier.getName() = later.getName() and
not earlier.getStringValue() = later.getStringValue()
@ -28,5 +29,6 @@ predicate conflict(DOM::AttributeDefinition earlier, DOM::AttributeDefinition la
from DOM::AttributeDefinition earlier, DOM::AttributeDefinition later
where conflict(earlier, later) and not conflict(_, earlier)
select earlier, "This attribute has the same name as $@ of the same element, " +
"but a different value.", later, "another attribute"
select earlier,
"This attribute has the same name as $@ of the same element, " + "but a different value.", later,
"another attribute"

Просмотреть файл

@ -17,8 +17,9 @@ import javascript
* and the same value, where `earlier` appears textually before `later`.
*/
predicate duplicate(DOM::AttributeDefinition earlier, DOM::AttributeDefinition later) {
exists (DOM::ElementDefinition elt, int i, int j |
earlier = elt.getAttribute(i) and later = elt.getAttribute(j) |
exists(DOM::ElementDefinition elt, int i, int j |
earlier = elt.getAttribute(i) and later = elt.getAttribute(j)
|
i < j and
earlier.getName() = later.getName() and
earlier.getStringValue() = later.getStringValue()

Просмотреть файл

@ -14,7 +14,8 @@
import javascript
from DOM::AttributeDefinition id, string reason
where DOM::isInvalidHtmlIdAttributeValue(id, reason) and
// exclude attribute values that look like they might be templated
not id.mayHaveTemplateValue()
select id, "The value of the id attribute " + reason + "."
where
DOM::isInvalidHtmlIdAttributeValue(id, reason) and
// exclude attribute values that look like they might be templated
not id.mayHaveTemplateValue()
select id, "The value of the id attribute " + reason + "."

Просмотреть файл

@ -18,7 +18,7 @@ import javascript
*/
class EvilTwin extends DataFlow::CallNode {
EvilTwin() {
exists (string fn | fn = "setTimeout" or fn = "setInterval" |
exists(string fn | fn = "setTimeout" or fn = "setInterval" |
this = DataFlow::globalVarRef(fn).getACall() and
getArgument(0).asExpr() instanceof ConstantString
)
@ -28,7 +28,7 @@ class EvilTwin extends DataFlow::CallNode {
/** A call to `document.write` or `document.writeln`. */
class DocumentWrite extends DataFlow::CallNode {
DocumentWrite() {
exists (string writeln |
exists(string writeln |
this = DataFlow::globalVarRef("document").getAMemberCall(writeln) and
writeln.regexpMatch("write(ln)?")
)
@ -37,9 +37,7 @@ class DocumentWrite extends DataFlow::CallNode {
/** A call to `window.execScript`. */
class ExecScript extends DataFlow::CallNode {
ExecScript() {
this = DataFlow::globalVarRef("execScript").getACall()
}
ExecScript() { this = DataFlow::globalVarRef("execScript").getACall() }
}
/** A call to a DOM function that may evaluate a string as code. */

Просмотреть файл

@ -19,15 +19,16 @@ import semmle.javascript.RestrictedLocations
* Holds if the href attribute contains a host that we cannot determine statically.
*/
predicate hasDynamicHrefHostAttributeValue(DOM::ElementDefinition elem) {
exists (DOM::AttributeDefinition attr |
exists(DOM::AttributeDefinition attr |
attr = elem.getAnAttribute() and
attr.getName().matches("%href%") |
attr.getName().matches("%href%")
|
// unknown string
not exists(attr.getStringValue()) or
exists (string url | url = attr.getStringValue() |
not exists(attr.getStringValue())
or
exists(string url | url = attr.getStringValue() |
// fixed string with templating
url.regexpMatch(Templating::getDelimiterMatchingRegexp())
and
url.regexpMatch(Templating::getDelimiterMatchingRegexp()) and
// ... that does not start with a fixed host or a relative path (common formats)
not url.regexpMatch("(?i)((https?:)?//)?[-a-z0-9.]*/.*")
)
@ -35,23 +36,22 @@ predicate hasDynamicHrefHostAttributeValue(DOM::ElementDefinition elem) {
}
from DOM::ElementDefinition e
where // `e` is a link that opens in a new browsing context (that is, it has `target="_blank"`)
e.getName() = "a" and
// and the host in the href is not hard-coded
hasDynamicHrefHostAttributeValue(e) and
e.getAttributeByName("target").getStringValue() = "_blank" and
// there is no `rel` attribute specifying link type `noopener`/`noreferrer`;
// `rel` attributes with non-constant value are handled conservatively
forall (DOM::AttributeDefinition relAttr | relAttr = e.getAttributeByName("rel") |
exists (string rel | rel = relAttr.getStringValue() |
not exists (string linkType | linkType = rel.splitAt(" ") |
linkType = "noopener" or
linkType = "noreferrer"
)
)
) and
// exclude elements with spread attributes or dynamically computed attribute names
not exists (DOM::AttributeDefinition attr | attr = e.getAnAttribute() |
not exists(attr.getName())
where
// `e` is a link that opens in a new browsing context (that is, it has `target="_blank"`)
e.getName() = "a" and
// and the host in the href is not hard-coded
hasDynamicHrefHostAttributeValue(e) and
e.getAttributeByName("target").getStringValue() = "_blank" and
// there is no `rel` attribute specifying link type `noopener`/`noreferrer`;
// `rel` attributes with non-constant value are handled conservatively
forall(DOM::AttributeDefinition relAttr | relAttr = e.getAttributeByName("rel") |
exists(string rel | rel = relAttr.getStringValue() |
not exists(string linkType | linkType = rel.splitAt(" ") |
linkType = "noopener" or
linkType = "noreferrer"
)
select (FirstLineOf)e, "External links without noopener/noreferrer are a potential security risk."
)
) and
// exclude elements with spread attributes or dynamically computed attribute names
not exists(DOM::AttributeDefinition attr | attr = e.getAnAttribute() | not exists(attr.getName()))
select e.(FirstLineOf), "External links without noopener/noreferrer are a potential security risk."

Просмотреть файл

@ -15,8 +15,9 @@ import javascript
import Definitions
from VarRef d
where d.getVariable().(LocalVariable).getName() = "arguments" and
(d instanceof LValue or d instanceof VarDecl) and
not d.isAmbient() and
not d.inExternsFile()
select d, "Redefinition of arguments."
where
d.getVariable().(LocalVariable).getName() = "arguments" and
(d instanceof LValue or d instanceof VarDecl) and
not d.isAmbient() and
not d.inExternsFile()
select d, "Redefinition of arguments."

Просмотреть файл

@ -14,9 +14,11 @@ import javascript
import semmle.javascript.RestrictedLocations
from ConstDeclStmt cds, VariableDeclarator decl, VarDef def, Variable v
where decl = cds.getADecl() and
def.getAVariable() = v and
decl.getBindingPattern().getAVariable() = v and
def != decl and
def.(ExprOrStmt).getTopLevel() = decl.getTopLevel()
select def.(FirstLineOf), "Assignment to variable " + v.getName() + ", which is $@ constant.", cds, "declared"
where
decl = cds.getADecl() and
def.getAVariable() = v and
decl.getBindingPattern().getAVariable() = v and
def != decl and
def.(ExprOrStmt).getTopLevel() = decl.getTopLevel()
select def.(FirstLineOf), "Assignment to variable " + v.getName() + ", which is $@ constant.", cds,
"declared"

Просмотреть файл

@ -21,26 +21,30 @@ import Definitions
predicate acceptableRedefinition(Identifier id) {
// function(x, y, undefined) { ... }(23, 42)
id.getName() = "undefined" and
exists (ImmediatelyInvokedFunctionExpr iife |
exists(ImmediatelyInvokedFunctionExpr iife |
id = iife.getParameter(iife.getInvocation().getNumArgument())
) or
)
or
// Date = global.Date
exists (AssignExpr assgn |
exists(AssignExpr assgn |
id = assgn.getTarget() and
id.getName() = assgn.getRhs().getUnderlyingValue().(PropAccess).getPropertyName()
) or
)
or
// var Date = global.Date
exists (VariableDeclarator decl |
exists(VariableDeclarator decl |
id = decl.getBindingPattern() and
id.getName() = decl.getInit().getUnderlyingValue().(PropAccess).getPropertyName()
)
}
from DefiningIdentifier id, string name
where not id.inExternsFile() and
name = id.getName() and
name.regexpMatch("Object|Function|Array|String|Boolean|Number|Math|Date|RegExp|Error|" +
"NaN|Infinity|undefined|eval|parseInt|parseFloat|isNaN|isFinite|" +
"decodeURI|decodeURIComponent|encodeURI|encodeURIComponent") and
not acceptableRedefinition(id)
select id, "Redefinition of " + name + "."
where
not id.inExternsFile() and
name = id.getName() and
name
.regexpMatch("Object|Function|Array|String|Boolean|Number|Math|Date|RegExp|Error|" +
"NaN|Infinity|undefined|eval|parseInt|parseFloat|isNaN|isFinite|" +
"decodeURI|decodeURIComponent|encodeURI|encodeURIComponent") and
not acceptableRedefinition(id)
select id, "Redefinition of " + name + "."

Просмотреть файл

@ -15,13 +15,15 @@ import javascript
import semmle.javascript.RestrictedLocations
from DeclStmt vds, VariableDeclarator vd1, VariableDeclarator vd2, int i, int j, Variable v
where vd1 = vds.getDecl(i) and
vd2 = vds.getDecl(j) and
v = vd1.getBindingPattern().getAVariable() and
v = vd2.getBindingPattern().getAVariable() and
i < j and
// exclude a somewhat common pattern where the first declaration is used as a temporary
exists (Expr e1, Expr e2 | e1 = vd1.getInit() and e2 = vd2.getInit() |
not v.getAnAccess().getParentExpr*() = e2
)
select (FirstLineOf)vd2, "This initialization of " + v.getName() + " overwrites $@.", vd1, "an earlier initialization"
where
vd1 = vds.getDecl(i) and
vd2 = vds.getDecl(j) and
v = vd1.getBindingPattern().getAVariable() and
v = vd2.getBindingPattern().getAVariable() and
i < j and
// exclude a somewhat common pattern where the first declaration is used as a temporary
exists(Expr e1, Expr e2 | e1 = vd1.getInit() and e2 = vd2.getInit() |
not v.getAnAccess().getParentExpr*() = e2
)
select vd2.(FirstLineOf), "This initialization of " + v.getName() + " overwrites $@.", vd1,
"an earlier initialization"

Просмотреть файл

@ -15,11 +15,14 @@
import javascript
from FunctionDeclStmt f, FunctionDeclStmt g
where f.getVariable() = g.getVariable() and
// ignore global functions; conflicts across scripts are usually false positives
not f.getVariable().isGlobal() and
// only report each pair once
f.getLocation().startsBefore(g.getLocation()) and
// ignore ambient, abstract, and overloaded declarations in TypeScript
f.hasBody() and g.hasBody()
select f.getId(), "Declaration of " + f.describe() + " conflicts with $@ in the same scope.", g.getId(), "another declaration"
where
f.getVariable() = g.getVariable() and
// ignore global functions; conflicts across scripts are usually false positives
not f.getVariable().isGlobal() and
// only report each pair once
f.getLocation().startsBefore(g.getLocation()) and
// ignore ambient, abstract, and overloaded declarations in TypeScript
f.hasBody() and
g.hasBody()
select f.getId(), "Declaration of " + f.describe() + " conflicts with $@ in the same scope.",
g.getId(), "another declaration"

Просмотреть файл

@ -14,27 +14,26 @@ import javascript
/** Holds if every access to `v` is a write. */
predicate onlyWritten(Variable v) {
forall (VarAccess va | va = v.getAnAccess() |
exists (Assignment assgn | assgn.getTarget() = va)
)
forall(VarAccess va | va = v.getAnAccess() | exists(Assignment assgn | assgn.getTarget() = va))
}
from Variable v, GlobalVarAccess gva
where v = gva.getVariable() and
onlyWritten(v) and
// 'v' is not externally declared...
not exists (ExternalVarDecl d | d.getName() = v.getName() |
// ...as a member of {Window,Worker,WebWorker}.prototype
d.(ExternalInstanceMemberDecl).getBaseName().regexpMatch("Window|Worker|WebWorker") or
// ...or as a member of window
d.(ExternalStaticMemberDecl).getBaseName() = "window" or
// ...or as a global
d instanceof ExternalGlobalDecl
) and
// it isn't declared using a linter directive
not exists (Linting::GlobalDeclaration decl |
decl.declaresGlobalForAccess(gva)
) and
// ignore accesses under 'with', since they may well refer to properties of the with'ed object
not exists (WithStmt with | with.mayAffect(gva))
where
v = gva.getVariable() and
onlyWritten(v) and
// 'v' is not externally declared...
not exists(ExternalVarDecl d | d.getName() = v.getName() |
// ...as a member of {Window,Worker,WebWorker}.prototype
d.(ExternalInstanceMemberDecl).getBaseName().regexpMatch("Window|Worker|WebWorker")
or
// ...or as a member of window
d.(ExternalStaticMemberDecl).getBaseName() = "window"
or
// ...or as a global
d instanceof ExternalGlobalDecl
) and
// it isn't declared using a linter directive
not exists(Linting::GlobalDeclaration decl | decl.declaresGlobalForAccess(gva)) and
// ignore accesses under 'with', since they may well refer to properties of the with'ed object
not exists(WithStmt with | with.mayAffect(gva))
select gva, "This definition of " + v.getName() + " is useless, since its value is never read."

Просмотреть файл

@ -19,33 +19,35 @@ import DeadStore
*/
predicate deadStoreOfLocal(VarDef vd, PurelyLocalVariable v) {
v = vd.getAVariable() and
exists (vd.getSource()) and
exists(vd.getSource()) and
// the definition is not in dead code
exists (ReachableBasicBlock rbb | vd = rbb.getANode()) and
exists(ReachableBasicBlock rbb | vd = rbb.getANode()) and
// but it has no associated SSA definition, that is, it is dead
not exists (SsaExplicitDefinition ssa | ssa.defines(vd, v))
not exists(SsaExplicitDefinition ssa | ssa.defines(vd, v))
}
from VarDef dead, PurelyLocalVariable v // captured variables may be read by closures, so don't flag them
where deadStoreOfLocal(dead, v) and
// the variable should be accessed somewhere; otherwise it will be flagged by UnusedVariable
exists(v.getAnAccess()) and
// don't flag ambient variable definitions
not dead.(ASTNode).isAmbient() and
// don't flag function expressions
not exists (FunctionExpr fe | dead = fe.getId()) and
// don't flag function declarations nested inside blocks or other compound statements;
// their meaning is only partially specified by the standard
not exists (FunctionDeclStmt fd, StmtContainer outer |
dead = fd.getId() and outer = fd.getEnclosingContainer() |
not fd = outer.getBody().(BlockStmt).getAStmt()
) and
// don't flag overwrites with `null` or `undefined`
not SyntacticConstants::isNullOrUndefined(dead.getSource()) and
// don't flag default inits that are later overwritten
not (isDefaultInit(dead.getSource().(Expr).getUnderlyingValue()) and dead.isOverwritten(v)) and
// don't flag assignments in externs
not dead.(ASTNode).inExternsFile() and
// don't flag exported variables
not any(ES2015Module m).exportsAs(v, _)
from VarDef dead, PurelyLocalVariable v // captured variables may be read by closures, so don't flag them
where
deadStoreOfLocal(dead, v) and
// the variable should be accessed somewhere; otherwise it will be flagged by UnusedVariable
exists(v.getAnAccess()) and
// don't flag ambient variable definitions
not dead.(ASTNode).isAmbient() and
// don't flag function expressions
not exists(FunctionExpr fe | dead = fe.getId()) and
// don't flag function declarations nested inside blocks or other compound statements;
// their meaning is only partially specified by the standard
not exists(FunctionDeclStmt fd, StmtContainer outer |
dead = fd.getId() and outer = fd.getEnclosingContainer()
|
not fd = outer.getBody().(BlockStmt).getAStmt()
) and
// don't flag overwrites with `null` or `undefined`
not SyntacticConstants::isNullOrUndefined(dead.getSource()) and
// don't flag default inits that are later overwritten
not (isDefaultInit(dead.getSource().(Expr).getUnderlyingValue()) and dead.isOverwritten(v)) and
// don't flag assignments in externs
not dead.(ASTNode).inExternsFile() and
// don't flag exported variables
not any(ES2015Module m).exportsAs(v, _)
select dead, "This definition of " + v.getName() + " is useless, since its value is never read."

Просмотреть файл

@ -17,7 +17,7 @@ import DeadStore
*/
predicate unambiguousPropWrite(DataFlow::SourceNode base, string name, DataFlow::PropWrite write) {
write = base.getAPropertyWrite(name) and
not exists (DataFlow::SourceNode otherBase |
not exists(DataFlow::SourceNode otherBase |
otherBase != base and
write = otherBase.getAPropertyWrite(name)
)
@ -26,8 +26,13 @@ predicate unambiguousPropWrite(DataFlow::SourceNode base, string name, DataFlow:
/**
* Holds if `assign1` and `assign2` both assign property `name` of the same object, and `assign2` post-dominates `assign1`.
*/
predicate postDominatedPropWrite(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
exists (ControlFlowNode write1, ControlFlowNode write2, DataFlow::SourceNode base, ReachableBasicBlock block1, ReachableBasicBlock block2 |
predicate postDominatedPropWrite(
string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2
) {
exists(
ControlFlowNode write1, ControlFlowNode write2, DataFlow::SourceNode base,
ReachableBasicBlock block1, ReachableBasicBlock block2
|
write1 = assign1.getWriteNode() and
write2 = assign2.getWriteNode() and
block1 = write1.getBasicBlock() and
@ -35,8 +40,10 @@ predicate postDominatedPropWrite(string name, DataFlow::PropWrite assign1, DataF
unambiguousPropWrite(base, name, assign1) and
unambiguousPropWrite(base, name, assign2) and
block2.postDominates(block1) and
(block1 = block2 implies
exists (int i1, int i2 |
(
block1 = block2
implies
exists(int i1, int i2 |
write1 = block1.getNode(i1) and
write2 = block2.getNode(i2) and
i1 < i2
@ -50,7 +57,8 @@ predicate postDominatedPropWrite(string name, DataFlow::PropWrite assign1, DataF
*/
bindingset[name]
predicate maybeAccessesProperty(Expr e, string name) {
(e.(PropAccess).getPropertyName() = name and e instanceof RValue) or
(e.(PropAccess).getPropertyName() = name and e instanceof RValue)
or
// conservatively reject all side-effects
e.isImpure()
}
@ -70,12 +78,13 @@ predicate isDeadAssignment(string name, DataFlow::PropWrite assign1, DataFlow::P
*/
bindingset[name]
predicate maybeAccessesAssignedPropInBlock(string name, DataFlow::PropWrite assign, boolean after) {
exists (ControlFlowNode write, ReachableBasicBlock block, int i, int j, Expr e |
exists(ControlFlowNode write, ReachableBasicBlock block, int i, int j, Expr e |
write = assign.getWriteNode() and
block = assign.getBasicBlock() and
write = block.getNode(i) and
e = block.getNode(j) and
maybeAccessesProperty(e, name) |
maybeAccessesProperty(e, name)
|
after = true and i < j
or
after = false and j < i
@ -87,30 +96,37 @@ predicate maybeAccessesAssignedPropInBlock(string name, DataFlow::PropWrite assi
*/
bindingset[name]
predicate noPropAccessBetween(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
exists (ControlFlowNode write1, ControlFlowNode write2, ReachableBasicBlock block1, ReachableBasicBlock block2 |
exists(
ControlFlowNode write1, ControlFlowNode write2, ReachableBasicBlock block1,
ReachableBasicBlock block2
|
write1 = assign1.getWriteNode() and
write2 = assign2.getWriteNode() and
write1.getBasicBlock() = block1 and
write2.getBasicBlock() = block2 and
if block1 = block2 then
if block1 = block2
then
// same block: check for access between
not exists (int i1, Expr mid, int i2 |
not exists(int i1, Expr mid, int i2 |
assign1.getWriteNode() = block1.getNode(i1) and
assign2.getWriteNode() = block2.getNode(i2) and
mid = block1.getNode([i1+1..i2-1]) and
mid = block1.getNode([i1 + 1 .. i2 - 1]) and
maybeAccessesProperty(mid, name)
)
else
// other block:
not (
// check for an access after the first write node
maybeAccessesAssignedPropInBlock(name, assign1, true) or
maybeAccessesAssignedPropInBlock(name, assign1, true)
or
// check for an access between the two write blocks
exists (ReachableBasicBlock mid |
exists(ReachableBasicBlock mid |
block1.getASuccessor+() = mid and
mid.getASuccessor+() = block2 |
mid.getASuccessor+() = block2
|
maybeAccessesProperty(mid.getANode(), name)
) or
)
or
// check for an access before the second write node
maybeAccessesAssignedPropInBlock(name, assign2, false)
)
@ -118,25 +134,29 @@ predicate noPropAccessBetween(string name, DataFlow::PropWrite assign1, DataFlow
}
from string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2
where isDeadAssignment(name, assign1, assign2) and
// whitelist
not (
// Google Closure Compiler pattern: `o.p = o['p'] = v`
exists (PropAccess p1, PropAccess p2 |
p1 = assign1.getAstNode() and
p2 = assign2.getAstNode() |
p1 instanceof DotExpr and p2 instanceof IndexExpr
or
p2 instanceof DotExpr and p1 instanceof IndexExpr
)
or
// don't flag overwrites for default values
isDefaultInit(assign1.getRhs().asExpr().getUnderlyingValue())
or
// don't flag assignments in externs
assign1.getAstNode().inExternsFile()
or
// exclude result from js/overwritten-property
assign2.getBase() instanceof DataFlow::ObjectLiteralNode
)
select assign1.getWriteNode(), "This write to property '" + name + "' is useless, since $@ always overrides it.", assign2.getWriteNode(), "another property write"
where
isDeadAssignment(name, assign1, assign2) and
// whitelist
not (
// Google Closure Compiler pattern: `o.p = o['p'] = v`
exists(PropAccess p1, PropAccess p2 |
p1 = assign1.getAstNode() and
p2 = assign2.getAstNode()
|
p1 instanceof DotExpr and p2 instanceof IndexExpr
or
p2 instanceof DotExpr and p1 instanceof IndexExpr
)
or
// don't flag overwrites for default values
isDefaultInit(assign1.getRhs().asExpr().getUnderlyingValue())
or
// don't flag assignments in externs
assign1.getAstNode().inExternsFile()
or
// exclude result from js/overwritten-property
assign2.getBase() instanceof DataFlow::ObjectLiteralNode
)
select assign1.getWriteNode(),
"This write to property '" + name + "' is useless, since $@ always overrides it.",
assign2.getWriteNode(), "another property write"

Просмотреть файл

@ -13,20 +13,20 @@ import javascript
private import Declarations
from VarAccess acc, VarDecl decl, Variable var, StmtContainer sc
where // the first reference to `var` in `sc` is `acc` (that is, an access, not a declaration)
acc = firstRefInContainer(var, Ref(), sc) and
// `decl` is a declaration of `var` in `sc` (which must come after `acc`)
decl = refInContainer(var, Decl(), sc) and
// exclude globals declared by a linter directive
not exists(Linting::GlobalDeclaration glob |
glob.declaresGlobalForAccess(acc)
) and
// exclude declarations in synthetic constructors
not acc.getEnclosingFunction() instanceof SyntheticConstructor and
// exclude results in ambient contexts
not acc.isAmbient() and
// a class may be referenced in its own decorators
not exists (ClassDefinition cls |
decl = cls.getIdentifier() and
acc.getParent*() = cls.getADecorator())
where
// the first reference to `var` in `sc` is `acc` (that is, an access, not a declaration)
acc = firstRefInContainer(var, Ref(), sc) and
// `decl` is a declaration of `var` in `sc` (which must come after `acc`)
decl = refInContainer(var, Decl(), sc) and
// exclude globals declared by a linter directive
not exists(Linting::GlobalDeclaration glob | glob.declaresGlobalForAccess(acc)) and
// exclude declarations in synthetic constructors
not acc.getEnclosingFunction() instanceof SyntheticConstructor and
// exclude results in ambient contexts
not acc.isAmbient() and
// a class may be referenced in its own decorators
not exists(ClassDefinition cls |
decl = cls.getIdentifier() and
acc.getParent*() = cls.getADecorator()
)
select acc, "Variable '" + acc.getName() + "' is used before its $@.", decl, "declaration"

Просмотреть файл

@ -12,7 +12,9 @@ import javascript
* Note that references that are not declarations are called accesses elsewhere,
* but they are not treated specially in this context.
*/
newtype RefKind = Ref() or Decl()
newtype RefKind =
Ref() or
Decl()
/**
* Gets a reference to `var` (if `kind` is `Ref()`) or declaration of
@ -30,8 +32,9 @@ VarRef refInContainer(Variable var, RefKind kind, StmtContainer sc) {
*/
VarRef firstRefInContainer(Variable var, RefKind kind, StmtContainer sc) {
result = min(refInContainer(var, kind, sc) as ref
order by ref.getLocation().getStartLine(),
ref.getLocation().getStartColumn())
order by
ref.getLocation().getStartLine(), ref.getLocation().getStartColumn()
)
}
/**
@ -50,6 +53,7 @@ VarRef refInTopLevel(Variable var, RefKind kind, TopLevel tl) {
*/
VarRef firstRefInTopLevel(Variable var, RefKind kind, TopLevel tl) {
result = min(refInTopLevel(var, kind, tl) as ref
order by ref.getLocation().getStartLine(),
ref.getLocation().getStartColumn())
order by
ref.getLocation().getStartLine(), ref.getLocation().getStartColumn()
)
}

Просмотреть файл

@ -22,6 +22,7 @@ predicate accessToNestedFunction(VarAccess va, FunctionDeclStmt inner, Function
}
from Function f, VarAccess va, FunctionDeclStmt g
where accessToNestedFunction(va, g, f) and
va.getParentExpr*() = f.getAParameter().getDefault()
select va, "This expression refers to $@ before it is defined.", g, g.getName()
where
accessToNestedFunction(va, g, f) and
va.getParentExpr*() = f.getAParameter().getDefault()
select va, "This expression refers to $@ before it is defined.", g, g.getName()

Просмотреть файл

@ -12,9 +12,10 @@
import javascript
from DeclStmt vds, VariableDeclarator vd1, int i, VariableDeclarator vd2, int j, Variable v
where vd1 = vds.getDecl(i) and
vd2 = vds.getDecl(j) and
vd1.getBindingPattern().getAVariable() = v and
vd2.getBindingPattern().getAVariable() = v and
i < j
select vd2, "Variable " + v.getName() + " has already been declared $@.", vd1, "here"
where
vd1 = vds.getDecl(i) and
vd2 = vds.getDecl(j) and
vd1.getBindingPattern().getAVariable() = v and
vd2.getBindingPattern().getAVariable() = v and
i < j
select vd2, "Variable " + v.getName() + " has already been declared $@.", vd1, "here"

Просмотреть файл

@ -26,25 +26,29 @@ predicate isCommonPredefinedTypeName(string name) {
*/
class DefiniteTypeDecl extends TypeDecl {
DefiniteTypeDecl() {
this = any(ImportSpecifier im).getLocal()
implies
exists(getLocalTypeName().getAnAccess())
this = any(ImportSpecifier im).getLocal() implies exists(getLocalTypeName().getAnAccess())
}
}
from SimpleParameter parameter, Function function, Locatable link, string linkText
where function.getFile().getFileType().isTypeScript()
and function.getAParameter() = parameter
and not function.hasBody()
and not exists(parameter.getTypeAnnotation())
and (
isCommonPredefinedTypeName(parameter.getName()) and link = parameter and linkText = "predefined type '" + parameter.getName() + "'"
where
function.getFile().getFileType().isTypeScript() and
function.getAParameter() = parameter and
not function.hasBody() and
not exists(parameter.getTypeAnnotation()) and
(
isCommonPredefinedTypeName(parameter.getName()) and
link = parameter and
linkText = "predefined type '" + parameter.getName() + "'"
or
exists (DefiniteTypeDecl decl, LocalTypeName typename |
exists(DefiniteTypeDecl decl, LocalTypeName typename |
decl = typename.getFirstDeclaration() and
parameter.getVariable().getScope().getOuterScope*() = typename.getScope() and
decl.getName() = parameter.getName() and
link = decl and
linkText = decl.describe())
linkText = decl.describe()
)
)
select parameter, "The parameter '" + parameter.getName() + "' has type 'any', but its name coincides with the $@.", link, linkText
select parameter,
"The parameter '" + parameter.getName() + "' has type 'any', but its name coincides with the $@.",
link, linkText

Просмотреть файл

@ -18,7 +18,7 @@ import semmle.javascript.RestrictedLocations
* Holds if `stmt` is of the form `this.<name> = <method>;`.
*/
predicate methodDefinition(ExprStmt stmt, string name, Function method) {
exists (AssignExpr assgn, PropAccess pacc |
exists(AssignExpr assgn, PropAccess pacc |
assgn = stmt.getExpr() and
pacc = assgn.getLhs() and
pacc.getBase() instanceof ThisExpr and
@ -28,12 +28,14 @@ predicate methodDefinition(ExprStmt stmt, string name, Function method) {
}
from Function ctor, ExprStmt defn, string name, Function method
where not ctor instanceof ImmediatelyInvokedFunctionExpr and
defn = ctor.getABodyStmt() and
methodDefinition(defn, name, method) and
// if the method captures a local variable of the constructor, it cannot
// easily be moved to the constructor object
not exists (Variable v | v.getScope() = ctor.getScope() |
v.getAnAccess().getContainer().getEnclosingContainer*() = method
)
select (FirstLineOf)defn, name + " should be added to the prototype object rather than to each instance."
where
not ctor instanceof ImmediatelyInvokedFunctionExpr and
defn = ctor.getABodyStmt() and
methodDefinition(defn, name, method) and
// if the method captures a local variable of the constructor, it cannot
// easily be moved to the constructor object
not exists(Variable v | v.getScope() = ctor.getScope() |
v.getAnAccess().getContainer().getEnclosingContainer*() = method
)
select defn.(FirstLineOf),
name + " should be added to the prototype object rather than to each instance."

Просмотреть файл

@ -1,14 +1,14 @@
/**
* @name Missing 'this' qualifier
* @description Referencing an undeclared global variable in a class that has a member of the same name is confusing and may indicate a bug.
* @kind problem
* @problem.severity error
* @id js/missing-this-qualifier
* @tags maintainability
* correctness
* methods
* @precision high
*/
* @name Missing 'this' qualifier
* @description Referencing an undeclared global variable in a class that has a member of the same name is confusing and may indicate a bug.
* @kind problem
* @problem.severity error
* @id js/missing-this-qualifier
* @tags maintainability
* correctness
* methods
* @precision high
*/
import javascript
@ -26,38 +26,37 @@ predicate maybeMissingThis(CallExpr call, MethodDeclaration intendedTarget, Glob
}
from CallExpr call, MethodDeclaration intendedTarget, GlobalVariable gv
where maybeMissingThis(call, intendedTarget, gv)
and
// exceptions:
not (
// affected by `with`
exists (WithStmt with | with.mayAffect(call.getCallee()))
or
// locally declared, so probably intentional
gv.getADeclaration().getTopLevel() = call.getTopLevel()
or
// linter declaration for the variable
exists (Linting::GlobalDeclaration glob |
glob.declaresGlobalForAccess(call.getCallee())
)
or
// externs declaration for the variable
exists (ExternalGlobalDecl egd | egd.getName() = call.getCalleeName())
or
// variable available through a namespace
exists (Variable decl |
decl.getName() = gv.getName() and
decl.isNamespaceExport() and
call.getContainer().getEnclosingContainer*() instanceof NamespaceDeclaration
)
or
// call to global function with additional arguments
exists (Function self |
intendedTarget.getBody() = self and
call.getEnclosingFunction() = self and
call.flow().(DataFlow::CallNode).getNumArgument() > self.getNumParameter() and
not self.hasRestParameter() and
not self.usesArgumentsObject()
)
)
select call, "This call refers to a global function, and not the local method $@.", intendedTarget, intendedTarget.getName()
where
maybeMissingThis(call, intendedTarget, gv) and
// exceptions:
not (
// affected by `with`
exists(WithStmt with | with.mayAffect(call.getCallee()))
or
// locally declared, so probably intentional
gv.getADeclaration().getTopLevel() = call.getTopLevel()
or
// linter declaration for the variable
exists(Linting::GlobalDeclaration glob | glob.declaresGlobalForAccess(call.getCallee()))
or
// externs declaration for the variable
exists(ExternalGlobalDecl egd | egd.getName() = call.getCalleeName())
or
// variable available through a namespace
exists(Variable decl |
decl.getName() = gv.getName() and
decl.isNamespaceExport() and
call.getContainer().getEnclosingContainer*() instanceof NamespaceDeclaration
)
or
// call to global function with additional arguments
exists(Function self |
intendedTarget.getBody() = self and
call.getEnclosingFunction() = self and
call.flow().(DataFlow::CallNode).getNumArgument() > self.getNumParameter() and
not self.hasRestParameter() and
not self.usesArgumentsObject()
)
)
select call, "This call refers to a global function, and not the local method $@.", intendedTarget,
intendedTarget.getName()

Просмотреть файл

@ -17,12 +17,10 @@ import javascript
* but not declared in the same toplevel as `f`.
*/
GlobalVariable undeclaredGlobalIn(Function f) {
exists (GlobalVarAccess gva | gva = result.getAnAccess() |
exists(GlobalVarAccess gva | gva = result.getAnAccess() |
gva.getEnclosingFunction() = f and
not result.declaredIn(f.getTopLevel()) and
not exists (Linting::GlobalDeclaration gd |
gd.declaresGlobalForAccess(gva)
)
not exists(Linting::GlobalDeclaration gd | gd.declaresGlobalForAccess(gva))
)
}
@ -33,9 +31,7 @@ GlobalVariable undeclaredGlobalIn(Function f) {
*/
GlobalVariable accidentalGlobalIn(Function f) {
result = undeclaredGlobalIn(f) and
exists (BasicBlock startBB | startBB = f.getStartBB() |
not startBB.isLiveAtEntry(result)
)
exists(BasicBlock startBB | startBB = f.getStartBB() | not startBB.isLiveAtEntry(result))
}
/**
@ -62,9 +58,12 @@ GlobalVarAccess getAccessIn(GlobalVariable v, Function f) {
*/
GlobalVarAccess getFirstAccessIn(GlobalVariable v, Function f) {
result = min(getAccessIn(v, f) as gva
order by gva.getLocation().getStartLine(), gva.getLocation().getStartColumn())
order by
gva.getLocation().getStartLine(), gva.getLocation().getStartColumn()
)
}
from Function f, GlobalVariable gv
where gv = candidateVariable(f)
select getFirstAccessIn(gv, f), "Variable " + gv.getName() + " is used like a local variable, but is missing a declaration."
select getFirstAccessIn(gv, f),
"Variable " + gv.getName() + " is used like a local variable, but is missing a declaration."

Просмотреть файл

@ -1,13 +1,14 @@
/**
* @name Wrong use of 'this' for static method
* @description A reference to a static method from within an instance method needs to be qualified with the class name, not `this`.
* @kind problem
* @problem.severity error
* @id js/mixed-static-instance-this-access
* @tags correctness
* methods
* @precision high
*/
* @name Wrong use of 'this' for static method
* @description A reference to a static method from within an instance method needs to be qualified with the class name, not `this`.
* @kind problem
* @problem.severity error
* @id js/mixed-static-instance-this-access
* @tags correctness
* methods
* @precision high
*/
import javascript
/** Holds if `base` declares or inherits method `m` with the given `name`. */
@ -17,11 +18,13 @@ predicate hasMethod(ClassDefinition base, string name, MethodDefinition m) {
}
/**
* Holds if `access` is in`fromMethod`, and it references `toMethod` through `this`,
* where `fromMethod` and `toMethod` are of kind `fromKind` and `toKind`, respectively.
*/
predicate isLocalMethodAccess(PropAccess access, MethodDefinition fromMethod, string fromKind,
MethodDefinition toMethod, string toKind) {
* Holds if `access` is in`fromMethod`, and it references `toMethod` through `this`,
* where `fromMethod` and `toMethod` are of kind `fromKind` and `toKind`, respectively.
*/
predicate isLocalMethodAccess(
PropAccess access, MethodDefinition fromMethod, string fromKind, MethodDefinition toMethod,
string toKind
) {
hasMethod(fromMethod.getDeclaringClass(), access.getPropertyName(), toMethod) and
access.getEnclosingFunction() = fromMethod.getBody() and
access.getBase() instanceof ThisExpr and
@ -33,25 +36,31 @@ string getKind(MethodDefinition m) {
if m.isStatic() then result = "static" else result = "instance"
}
from PropAccess access, MethodDefinition fromMethod, MethodDefinition toMethod, string fromKind, string toKind
from
PropAccess access, MethodDefinition fromMethod, MethodDefinition toMethod, string fromKind,
string toKind
where
isLocalMethodAccess(access, fromMethod, fromKind, toMethod, toKind) and
toKind != fromKind and
// exceptions
not (
// the class has a second member with the same name and the right kind
isLocalMethodAccess(access, fromMethod, _, _, fromKind)
or
// there is a dynamically assigned second member with the same name and the right kind
exists (AnalyzedPropertyWrite apw, AbstractClass declaringClass, AbstractValue base |
"static" = fromKind and base = declaringClass or
"instance" = fromKind and base = TAbstractInstance(declaringClass) |
declaringClass = TAbstractClass(fromMethod.getDeclaringClass()) and
apw.writes(base, access.getPropertyName(), _)
)
or
// the access is an assignment, probably deliberate
access instanceof LValue
isLocalMethodAccess(access, fromMethod, fromKind, toMethod, toKind) and
toKind != fromKind and
// exceptions
not (
// the class has a second member with the same name and the right kind
isLocalMethodAccess(access, fromMethod, _, _, fromKind)
or
// there is a dynamically assigned second member with the same name and the right kind
exists(AnalyzedPropertyWrite apw, AbstractClass declaringClass, AbstractValue base |
"static" = fromKind and base = declaringClass
or
"instance" = fromKind and base = TAbstractInstance(declaringClass)
|
declaringClass = TAbstractClass(fromMethod.getDeclaringClass()) and
apw.writes(base, access.getPropertyName(), _)
)
select access, "Access to " + toKind + " method $@ from " + fromKind + " method $@ is not possible through `this`.", toMethod, toMethod.getName(), fromMethod, fromMethod.getName()
or
// the access is an assignment, probably deliberate
access instanceof LValue
)
select access,
"Access to " + toKind + " method $@ from " + fromKind +
" method $@ is not possible through `this`.", toMethod, toMethod.getName(), fromMethod,
fromMethod.getName()

Просмотреть файл

@ -13,17 +13,15 @@ import javascript
private import Declarations
from Variable v, TopLevel tl, VarDecl decl, VarDecl redecl
where decl = firstRefInTopLevel(v, Decl(), tl) and
redecl = refInTopLevel(v, Decl(), tl) and
redecl != decl and
not tl.isExterns() and
// Ignore redeclared ambient declarations, such as overloaded functions.
not decl.isAmbient() and
not redecl.isAmbient() and
// Redeclaring a namespace extends the previous definition.
not decl = any(NamespaceDeclaration ns).getId() and
not redecl = any(NamespaceDeclaration ns).getId()
select redecl, "This variable has already been declared $@.", decl, "here"
where
decl = firstRefInTopLevel(v, Decl(), tl) and
redecl = refInTopLevel(v, Decl(), tl) and
redecl != decl and
not tl.isExterns() and
// Ignore redeclared ambient declarations, such as overloaded functions.
not decl.isAmbient() and
not redecl.isAmbient() and
// Redeclaring a namespace extends the previous definition.
not decl = any(NamespaceDeclaration ns).getId() and
not redecl = any(NamespaceDeclaration ns).getId()
select redecl, "This variable has already been declared $@.", decl, "here"

Просмотреть файл

@ -22,11 +22,12 @@ int letDeclAt(BlockStmt blk, Variable v, LetStmt let) {
}
from VarAccess va, LetStmt let, BlockStmt blk, int i, int j, Variable v
where v = va.getVariable() and
j = letDeclAt(blk, v, let) and
blk.getStmt(i) = va.getEnclosingStmt().getParentStmt*() and
i < j and
// don't flag uses in different functions
blk.getContainer() = va.getContainer() and
not letDeclAt(blk, v, _) < i
select va, "This expression refers to $@ inside its temporal dead zone.", let, va.getName()
where
v = va.getVariable() and
j = letDeclAt(blk, v, let) and
blk.getStmt(i) = va.getEnclosingStmt().getParentStmt*() and
i < j and
// don't flag uses in different functions
blk.getContainer() = va.getContainer() and
not letDeclAt(blk, v, _) < i
select va, "This expression refers to $@ inside its temporal dead zone.", let, va.getName()

Просмотреть файл

@ -13,8 +13,10 @@ import javascript
import semmle.javascript.RestrictedLocations
from Function f
where not f.inExternsFile() and
f.getNumParameter() > 7 and
// exclude AMD modules
not exists (AMDModuleDefinition m | f = m.getFactoryNode().(DataFlow::FunctionNode).getAstNode())
select (FirstLineOf)f, capitalize(f.describe()) + " has too many parameters (" + f.getNumParameter() + ")."
where
not f.inExternsFile() and
f.getNumParameter() > 7 and
// exclude AMD modules
not exists(AMDModuleDefinition m | f = m.getFactoryNode().(DataFlow::FunctionNode).getAstNode())
select f.(FirstLineOf),
capitalize(f.describe()) + " has too many parameters (" + f.getNumParameter() + ")."

Просмотреть файл

@ -30,11 +30,12 @@ predicate isDummy(SimpleParameter p) {
}
from Function f, Parameter p, Parameter q, int i, int j, string name
where parmBinds(f, i, p, name) and
parmBinds(f, j, q, name) and
i < j and
j = max(int k | parmBinds(f, k, _, name) | k) and
not isDummy(p) and
// duplicate parameters in strict mode functions are flagged by the 'Syntax error' rule
not f.isStrict()
select p, "This parameter has the same name as $@ of the same function.", q, "another parameter"
where
parmBinds(f, i, p, name) and
parmBinds(f, j, q, name) and
i < j and
j = max(int k | parmBinds(f, k, _, name) | k) and
not isDummy(p) and
// duplicate parameters in strict mode functions are flagged by the 'Syntax error' rule
not f.isStrict()
select p, "This parameter has the same name as $@ of the same function.", q, "another parameter"

Просмотреть файл

@ -32,7 +32,7 @@ predicate hasProperty(ObjectExpr o, Property p, string name, int kind, int i) {
* both have the same name and kind, but are not structurally identical.
*/
predicate overwrittenBy(Property p, Property q) {
exists (ObjectExpr o, string name, int i, int j, int kind |
exists(ObjectExpr o, string name, int i, int j, int kind |
hasProperty(o, p, name, kind, i) and
hasProperty(o, q, name, kind, j) and
i < j
@ -43,7 +43,8 @@ predicate overwrittenBy(Property p, Property q) {
}
from Property p, Property q
where overwrittenBy(p, q) and
// ensure that `q` is the last property with the same name as `p`
not overwrittenBy(q, _)
select p, "This property is overwritten by $@ in the same object literal.", q, "another property"
where
overwrittenBy(p, q) and
// ensure that `q` is the last property with the same name as `p`
not overwrittenBy(q, _)
select p, "This property is overwritten by $@ in the same object literal.", q, "another property"

Просмотреть файл

@ -19,27 +19,26 @@ import javascript
* as part of the top-level means cyclic imports can't be known to be resolved at this stage.
*/
predicate isImmediatelyExecutedContainer(StmtContainer container) {
container instanceof TopLevel or
container instanceof TopLevel
or
// Namespaces are immediately executed (they cannot be declared inside a function).
container instanceof NamespaceDeclaration or
container instanceof NamespaceDeclaration
or
// IIFEs at the top-level are immediately executed
exists (ImmediatelyInvokedFunctionExpr function | container = function |
exists(ImmediatelyInvokedFunctionExpr function | container = function |
not function.isAsync() and
not function.isGenerator() and
isImmediatelyExecutedContainer(container.getEnclosingContainer()))
isImmediatelyExecutedContainer(container.getEnclosingContainer())
)
}
/**
* Holds if the given import is only used to import type names, hence has no runtime effect.
*/
predicate isAmbientImport(ImportDeclaration decl) {
decl.getFile().getFileType().isTypeScript()
and
exists (decl.getASpecifier())
and
not exists (decl.getASpecifier().getLocal().getVariable().getAnAccess())
decl.getFile().getFileType().isTypeScript() and
exists(decl.getASpecifier()) and
not exists(decl.getASpecifier().getLocal().getVariable().getAnAccess())
}
/**
@ -52,7 +51,7 @@ Import getARuntimeImport(Module source, Module destination) {
}
predicate isImportedAtRuntime(Module source, Module destination) {
exists (getARuntimeImport(source, destination))
exists(getARuntimeImport(source, destination))
}
/**
@ -65,7 +64,7 @@ predicate isImportedAtRuntime(Module source, Module destination) {
class CandidateVarAccess extends VarAccess {
CandidateVarAccess() {
isImmediatelyExecutedContainer(getContainer()) and
not exists (ExportSpecifier spec | spec.getLocal() = this)
not exists(ExportSpecifier spec | spec.getLocal() = this)
}
}
@ -75,7 +74,10 @@ class CandidateVarAccess extends VarAccess {
* We use this to avoid duplicate alerts about the same underlying cyclic import.
*/
VarAccess getFirstCandidateAccess(ImportDeclaration decl) {
result = min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p order by p.getFirstToken().getIndex())
result = min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p
order by
p.getFirstToken().getIndex()
)
}
/**
@ -89,10 +91,9 @@ predicate cycleAlert(Module mod, ImportDeclaration import_, Module importedModul
access = getFirstCandidateAccess(import_) and
importedModule = import_.getImportedModule() and
importedModule != mod and // don't report self-imports
// Suppress warning if this is the unique importer of that module.
// That's a sufficient and somewhat maintainable safety guarantee.
exists (Module otherEntry | isImportedAtRuntime(otherEntry, importedModule) and otherEntry != mod)
exists(Module otherEntry | isImportedAtRuntime(otherEntry, importedModule) and otherEntry != mod)
}
/** Holds if the length of the shortest sequence of runtime imports from `source` to `destination` is `steps`. */
@ -104,9 +105,7 @@ predicate anyModule(Module m) { any() }
/**
* Gets the name of the module containing the given import.
*/
string repr(Import import_) {
result = import_.getEnclosingModule().getName()
}
string repr(Import import_) { result = import_.getEnclosingModule().getName() }
/**
* Builds a string visualizing the shortest import path from `source` to `destination`, excluding
@ -125,25 +124,32 @@ string repr(Import import_) {
*/
string pathToModule(Module source, Module destination, int steps) {
// Restrict paths to those that are relevant for building a path from the imported module of an alert back to the importer.
exists (Module m | cycleAlert(destination, _, m, _) and numberOfStepsToModule(m, source, _)) and
exists(Module m | cycleAlert(destination, _, m, _) and numberOfStepsToModule(m, source, _)) and
numberOfStepsToModule(source, destination, steps) and
(
steps = 1 and
result = repr(getARuntimeImport(source, destination))
or
steps > 1 and
exists (Module next |
exists(Module next |
// Only extend the path to one of the potential successors, as we only need one example.
next = min(Module mod |
isImportedAtRuntime(source, mod) and
numberOfStepsToModule(mod, destination, steps - 1) |
mod order by mod.getName()) and
result = repr(getARuntimeImport(source, next)) + " => " + pathToModule(next, destination, steps - 1))
isImportedAtRuntime(source, mod) and
numberOfStepsToModule(mod, destination, steps - 1)
|
mod
order by
mod.getName()
) and
result = repr(getARuntimeImport(source, next)) + " => " +
pathToModule(next, destination, steps - 1)
)
)
}
from Module mod, ImportDeclaration import_, Module importedModule, VarAccess access
where cycleAlert(mod, import_, importedModule, access)
select access, access.getName() + " is uninitialized if $@ is loaded first in the cyclic import:"
+ " " + repr(import_) + " => " + min(pathToModule(importedModule, mod, _)) + " => " + repr(import_) + ".",
import_.getImportedPath(), importedModule.getName()
select access,
access.getName() + " is uninitialized if $@ is loaded first in the cyclic import:" + " " +
repr(import_) + " => " + min(pathToModule(importedModule, mod, _)) + " => " + repr(import_) +
".", import_.getImportedPath(), importedModule.getName()

Просмотреть файл

@ -4,13 +4,14 @@
* In order to suppress alerts that are similar to the 'js/unused-parameter' alerts,
* `isAnAccidentallyUnusedParameter` should be used since it holds iff that alert is active.
*/
import javascript
/**
* Holds if `e` is an expression whose value is invoked as a function.
*/
private predicate isCallee(Expr e) {
exists (InvokeExpr invk | e = invk.getCallee().getUnderlyingValue())
exists(InvokeExpr invk | e = invk.getCallee().getUnderlyingValue())
}
/**
@ -23,12 +24,9 @@ private predicate isCallee(Expr e) {
*/
private predicate isFirstOrder(Function f) {
// if `f` is itself an expression, it is invoked
(f instanceof FunctionDeclStmt or isCallee(f))
and
(f instanceof FunctionDeclStmt or isCallee(f)) and
// all references to `f` are also invocations
forall (VarAccess use | use = f.getVariable().getAnAccess() |
isCallee(use)
)
forall(VarAccess use | use = f.getVariable().getAnAccess() | isCallee(use))
}
/**
@ -42,7 +40,7 @@ predicate isUnused(Function f, Parameter p, Variable pv, int i) {
// nor could it be accessed through arguments
not f.usesArgumentsObject() and
// nor is it mentioned in a type
not exists (LocalVarTypeAccess acc | acc.getVariable() = pv) and
not exists(LocalVarTypeAccess acc | acc.getVariable() = pv) and
// functions without a body cannot use their parameters
f.hasBody() and
// field parameters are used to initialize a field
@ -59,16 +57,22 @@ predicate isUnused(Function f, Parameter p, Variable pv, int i) {
predicate isAnAccidentallyUnusedParameter(Parameter p) {
exists(Function f, Variable pv, int i |
isUnused(f, p, pv, i) and
(// either f is first-order (so its parameter list is easy to adjust), or
isFirstOrder(f) or
(
// either f is first-order (so its parameter list is easy to adjust), or
isFirstOrder(f)
or
// p is a destructuring parameter, or
not p instanceof SimpleParameter or
not p instanceof SimpleParameter
or
// every later parameter is non-destructuring and also unused
forall (Parameter q, int j | q = f.getParameter(j) and j > i | isUnused(f, q.(SimpleParameter), _, _))) and
forall(Parameter q, int j | q = f.getParameter(j) and j > i |
isUnused(f, q.(SimpleParameter), _, _)
)
) and
// f is not an extern
not f.inExternsFile() and
// and p is not documented as being unused
not exists (JSDocParamTag parmdoc | parmdoc.getDocumentedParameter() = pv |
not exists(JSDocParamTag parmdoc | parmdoc.getDocumentedParameter() = pv |
parmdoc.getDescription().toLowerCase().matches("%unused%")
) and
// and f is not marked as abstract
@ -76,7 +80,7 @@ predicate isAnAccidentallyUnusedParameter(Parameter p) {
// this case is checked by a different query
not f.(FunctionExpr).isSetter() and
// `p` isn't used in combination with a rest property pattern to filter out unwanted properties
not exists (ObjectPattern op | exists(op.getRest()) |
not exists(ObjectPattern op | exists(op.getRest()) |
op.getAPropertyPattern().getValuePattern() = pv.getADeclaration()
)
)

Просмотреть файл

@ -33,8 +33,9 @@ class UnusedLocal extends LocalVariable {
* contains externs declarations.
*/
predicate mentionedInJSDocComment(UnusedLocal v) {
exists (Externs ext, JSDoc jsdoc |
ext = v.getADeclaration().getTopLevel() and jsdoc.getComment().getTopLevel() = ext |
exists(Externs ext, JSDoc jsdoc |
ext = v.getADeclaration().getTopLevel() and jsdoc.getComment().getTopLevel() = ext
|
jsdoc.getComment().getText().regexpMatch("(?s).*\\b" + v.getName() + "\\b.*")
)
}
@ -46,7 +47,7 @@ predicate mentionedInJSDocComment(UnusedLocal v) {
* copies all properties of `o` into `props`, except for `x` which is copied into `v`.
*/
predicate isPropertyFilter(UnusedLocal v) {
exists (ObjectPattern op | exists(op.getRest()) |
exists(ObjectPattern op | exists(op.getRest()) |
op.getAPropertyPattern().getValuePattern() = v.getADeclaration()
)
}
@ -57,14 +58,13 @@ predicate hasJsxInScope(UnusedLocal v) {
/**
* Holds if `v` is a "React" variable that is implicitly used by a JSX element.
*/
*/
predicate isReactForJSX(UnusedLocal v) {
hasJsxInScope(v) and
(
v.getName() = "React"
or
exists(TopLevel tl |
tl = v.getADeclaration().getTopLevel() |
exists(TopLevel tl | tl = v.getADeclaration().getTopLevel() |
// legacy `@jsx` pragmas
exists(JSXPragma p | p.getTopLevel() = tl | p.getDOMName() = v.getName())
or
@ -81,50 +81,46 @@ predicate isReactForJSX(UnusedLocal v) {
* Holds if `decl` is both a variable declaration and a type declaration,
* and the declared type has a use.
*/
predicate isUsedAsType(VarDecl decl) {
exists (decl.(TypeDecl).getLocalTypeName().getAnAccess())
}
predicate isUsedAsType(VarDecl decl) { exists(decl.(TypeDecl).getLocalTypeName().getAnAccess()) }
/**
* Holds if `decl` declares a local alias for a namespace that is used from inside a type.
*/
predicate isUsedAsNamespace(VarDecl decl) {
exists (decl.(LocalNamespaceDecl).getLocalNamespaceName().getAnAccess())
exists(decl.(LocalNamespaceDecl).getLocalNamespaceName().getAnAccess())
}
/**
* Holds if the given identifier belongs to a decorated class or enum.
*/
predicate isDecorated(VarDecl decl) {
exists (ClassDefinition cd | cd.getIdentifier() = decl | exists(cd.getDecorator(_))) or
exists (EnumDeclaration cd | cd.getIdentifier() = decl | exists(cd.getDecorator(_)))
exists(ClassDefinition cd | cd.getIdentifier() = decl | exists(cd.getDecorator(_)))
or
exists(EnumDeclaration cd | cd.getIdentifier() = decl | exists(cd.getDecorator(_)))
}
/**
* Holds if this is the name of an enum member.
*/
predicate isEnumMember(VarDecl decl) {
decl = any(EnumMember member).getIdentifier()
}
predicate isEnumMember(VarDecl decl) { decl = any(EnumMember member).getIdentifier() }
/**
* Gets a description of the declaration `vd`, which is either of the form
* "function f", "variable v" or "class c".
*/
string describeVarDecl(VarDecl vd) {
if vd = any(Function f).getId() then
result = "function " + vd.getName()
else if vd = any(ClassDefinition c).getIdentifier() then
result = "class " + vd.getName()
if vd = any(Function f).getId()
then result = "function " + vd.getName()
else
result = "variable " + vd.getName()
if vd = any(ClassDefinition c).getIdentifier()
then result = "class " + vd.getName()
else result = "variable " + vd.getName()
}
/**
* An import statement that provides variable declarations.
*/
class ImportVarDeclProvider extends Stmt {
ImportVarDeclProvider() {
this instanceof ImportDeclaration or
this instanceof ImportEqualsDeclaration
@ -145,7 +141,6 @@ class ImportVarDeclProvider extends Stmt {
result = getAVarDecl().getVariable() and
not whitelisted(result)
}
}
/**
@ -153,25 +148,32 @@ class ImportVarDeclProvider extends Stmt {
*/
predicate whitelisted(UnusedLocal v) {
// exclude variables mentioned in JSDoc comments in externs
mentionedInJSDocComment(v) or
mentionedInJSDocComment(v)
or
// exclude variables used to filter out unwanted properties
isPropertyFilter(v) or
isPropertyFilter(v)
or
// exclude imports of React that are implicitly referenced by JSX
isReactForJSX(v) or
isReactForJSX(v)
or
// exclude names that are used as types
exists (VarDecl vd |
v = vd.getVariable() |
isUsedAsType(vd) or
exists(VarDecl vd | v = vd.getVariable() |
isUsedAsType(vd)
or
// exclude names that are used as namespaces from inside a type
isUsedAsNamespace(vd)or
isUsedAsNamespace(vd)
or
// exclude decorated functions and classes
isDecorated(vd) or
isDecorated(vd)
or
// exclude names of enum members; they also define property names
isEnumMember(vd) or
isEnumMember(vd)
or
// ignore ambient declarations - too noisy
vd.isAmbient()
) or
exists (DirectEval eval |
)
or
exists(DirectEval eval |
// eval nearby
eval.getEnclosingFunction() = v.getADeclaration().getEnclosingFunction() and
// ... but not on the RHS
@ -183,7 +185,7 @@ predicate whitelisted(UnusedLocal v) {
* Holds if `vd` declares an unused variable that does not come from an import statement, as explained by `msg`.
*/
predicate unusedNonImports(VarDecl vd, string msg) {
exists (UnusedLocal v |
exists(UnusedLocal v |
v = vd.getVariable() and
msg = "Unused " + describeVarDecl(vd) + "." and
not vd = any(ImportVarDeclProvider p).getAVarDecl() and
@ -195,7 +197,7 @@ predicate unusedNonImports(VarDecl vd, string msg) {
* Holds if `provider` declares one or more unused variables, as explained by `msg`.
*/
predicate unusedImports(ImportVarDeclProvider provider, string msg) {
exists (string imports, string names |
exists(string imports, string names |
imports = pluralize("import", count(provider.getAnUnacceptableUnusedLocal())) and
names = strictconcat(provider.getAnUnacceptableUnusedLocal().getName(), ", ") and
msg = "Unused " + imports + " " + names + "."
@ -203,6 +205,7 @@ predicate unusedImports(ImportVarDeclProvider provider, string msg) {
}
from ASTNode sel, string msg
where unusedNonImports(sel, msg) or
unusedImports(sel, msg)
where
unusedNonImports(sel, msg) or
unusedImports(sel, msg)
select sel, msg

Просмотреть файл

@ -9,10 +9,10 @@
* @id js/enabling-electron-insecure-content
*/
import javascript
from DataFlow::PropWrite allowRunningInsecureContent, Electron::WebPreferences preferences
where allowRunningInsecureContent = preferences.getAPropertyWrite("allowRunningInsecureContent")
and allowRunningInsecureContent.getRhs().mayHaveBooleanValue(true)
where
allowRunningInsecureContent = preferences.getAPropertyWrite("allowRunningInsecureContent") and
allowRunningInsecureContent.getRhs().mayHaveBooleanValue(true)
select allowRunningInsecureContent, "Enabling allowRunningInsecureContent is strongly discouraged."

Просмотреть файл

@ -12,6 +12,7 @@
import javascript
from DataFlow::PropWrite webSecurity, Electron::WebPreferences preferences
where webSecurity = preferences.getAPropertyWrite("webSecurity")
and webSecurity.getRhs().mayHaveBooleanValue(false)
select webSecurity, "Disabling webSecurity is strongly discouraged."
where
webSecurity = preferences.getAPropertyWrite("webSecurity") and
webSecurity.getRhs().mayHaveBooleanValue(false)
select webSecurity, "Disabling webSecurity is strongly discouraged."

Просмотреть файл

@ -16,19 +16,19 @@ import javascript
* Gets a warning message for `pref` if one of the `nodeIntegration` features is enabled.
*/
string getNodeIntegrationWarning(Electron::WebPreferences pref) {
exists (string feature |
feature = "nodeIntegration" or
feature = "nodeIntegrationInWorker" |
pref.getAPropertyWrite(feature).getRhs().mayHaveBooleanValue(true) and
result = "The `" + feature + "` feature has been enabled."
)
or
exists (string feature |
feature = "nodeIntegration" |
not exists(pref.getAPropertyWrite(feature)) and
result = "The `" + feature + "` feature is enabled by default."
)
exists(string feature |
feature = "nodeIntegration" or
feature = "nodeIntegrationInWorker"
|
pref.getAPropertyWrite(feature).getRhs().mayHaveBooleanValue(true) and
result = "The `" + feature + "` feature has been enabled."
)
or
exists(string feature | feature = "nodeIntegration" |
not exists(pref.getAPropertyWrite(feature)) and
result = "The `" + feature + "` feature is enabled by default."
)
}
from Electron::WebPreferences preferences
select preferences, getNodeIntegrationWarning(preferences)
select preferences, getNodeIntegrationWarning(preferences)

Просмотреть файл

@ -34,52 +34,76 @@ import javascript
*/
predicate relevant(Expr e) {
// base case: left operands of `%`
exists (ModExpr me | e = me.getLeftOperand()) or
exists(ModExpr me | e = me.getLeftOperand())
or
// first inductive case: downward AST traversal
relevant(e.getParentExpr()) or
relevant(e.getParentExpr())
or
// second inductive case: following variable assignments
exists (Variable v | relevant(v.getAnAccess()) | e = v.getAnAssignedExpr())
exists(Variable v | relevant(v.getAnAccess()) | e = v.getAnAssignedExpr())
}
/** Holds if `e` could evaluate to a negative number. */
predicate maybeNegative(Expr e) {
relevant(e) and
if exists(e.getIntValue()) then
e.getIntValue() < 0
else if e instanceof ParExpr then
maybeNegative(e.(ParExpr).getExpression())
else if e instanceof IncExpr then
maybeNegative(e.(IncExpr).getOperand())
else if e instanceof VarAccess then
maybeNegativeVar(e.(VarAccess).getVariable())
else if e instanceof AddExpr then
maybeNegative(e.(AddExpr).getAnOperand())
if exists(e.getIntValue())
then e.getIntValue() < 0
else
// anything else is considered to possibly be negative
any()
if e instanceof ParExpr
then maybeNegative(e.(ParExpr).getExpression())
else
if e instanceof IncExpr
then maybeNegative(e.(IncExpr).getOperand())
else
if e instanceof VarAccess
then maybeNegativeVar(e.(VarAccess).getVariable())
else
if e instanceof AddExpr
then maybeNegative(e.(AddExpr).getAnOperand())
else
// anything else is considered to possibly be negative
any()
}
/** Holds if `v` could be assigned a negative number. */
predicate maybeNegativeVar(Variable v) {
v.isGlobal() or v.isParameter() or
v.isGlobal()
or
v.isParameter()
or
// is v ever assigned a potentially negative value?
maybeNegative(v.getAnAssignedExpr()) or
maybeNegative(v.getAnAssignedExpr())
or
// is v ever decremented?
exists (DecExpr dec | dec.getOperand().getUnderlyingReference() = v.getAnAccess()) or
exists(DecExpr dec | dec.getOperand().getUnderlyingReference() = v.getAnAccess())
or
// is v ever subject to a compound assignment other than +=, or to
// += with potentially negative rhs?
exists (CompoundAssignExpr assgn | assgn.getTarget() = v.getAnAccess() |
exists(CompoundAssignExpr assgn | assgn.getTarget() = v.getAnAccess() |
not assgn instanceof AssignAddExpr or
maybeNegative(assgn.getRhs())
)
}
from Comparison cmp, ModExpr me, int num, string parity
where cmp.getAnOperand().stripParens() = me and
cmp.getAnOperand().getIntValue() = num and
me.getRightOperand().getIntValue() = 2 and
maybeNegative(me.getLeftOperand()) and
(((cmp instanceof EqExpr or cmp instanceof StrictEqExpr) and num = 1 and parity = "oddness") or
((cmp instanceof NEqExpr or cmp instanceof StrictNEqExpr) and num = 1 and parity = "evenness") or
(cmp instanceof GTExpr and num = 0 and parity = "oddness"))
where
cmp.getAnOperand().stripParens() = me and
cmp.getAnOperand().getIntValue() = num and
me.getRightOperand().getIntValue() = 2 and
maybeNegative(me.getLeftOperand()) and
(
(
(cmp instanceof EqExpr or cmp instanceof StrictEqExpr) and
num = 1 and
parity = "oddness"
)
or
(
(cmp instanceof NEqExpr or cmp instanceof StrictNEqExpr) and
num = 1 and
parity = "evenness"
)
or
(cmp instanceof GTExpr and num = 0 and parity = "oddness")
)
select cmp, "Test for " + parity + " does not take negative numbers into account."

Просмотреть файл

@ -20,7 +20,8 @@ import javascript
*/
predicate acceptableSignCheck(BitwiseExpr b) {
// projecting out constant bit patterns not containing the sign bit is fine
b.(BitAndExpr).getRightOperand().getIntValue() <= 2147483647 or
b.(BitAndExpr).getRightOperand().getIntValue() <= 2147483647
or
/*
* `| 0` and `0 |` are popular ways of converting a value to an integer;
* `>> 0`, `<< 0`, `^ 0` and `0 ^` achieve the same effect;
@ -28,23 +29,28 @@ predicate acceptableSignCheck(BitwiseExpr b) {
* so any binary bitwise operation involving zero is acceptable, _except_ for `x >>> 0`,
* which amounts to a cast to unsigned int
*/
exists (int i |
exists(int i |
b.(BinaryExpr).getChildExpr(i).getIntValue() = 0 and
not (b instanceof URShiftExpr and i = 1)
) or
)
or
/*
* `<< 16 >> 16` is how Emscripten converts values to short integers; since this
* is sign-preserving, we shouldn't flag it (and we allow arbitrary shifts, not just 16-bit ones)
*/
exists (RShiftExpr rsh, LShiftExpr lsh |
rsh = b and lsh = rsh.getLeftOperand().getUnderlyingValue() and
exists(RShiftExpr rsh, LShiftExpr lsh |
rsh = b and
lsh = rsh.getLeftOperand().getUnderlyingValue() and
lsh.getRightOperand().getIntValue() = rsh.getRightOperand().getIntValue()
)
}
from Comparison e, BitwiseExpr b
where b = e.getLeftOperand().getUnderlyingValue() and
not e instanceof EqualityTest and
e.getRightOperand().getIntValue() = 0 and
not acceptableSignCheck(b)
select e, "Sign check of a bitwise operation"
where
b = e.getLeftOperand().getUnderlyingValue() and
not e instanceof EqualityTest and
e.getRightOperand().getIntValue() = 0 and
not acceptableSignCheck(b)
select e, "Sign check of a bitwise operation"

Просмотреть файл

@ -9,12 +9,16 @@ import javascript
*/
private int kindOf(ASTNode nd) {
// map expression kinds to even non-negative numbers
result = nd.(Expr).getKind() * 2 or
result = nd.(Expr).getKind() * 2
or
// map statement kinds to odd non-negative numbers
result = nd.(Stmt).getKind() * 2 + 1 or
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 TopLevel and result = -1
or
nd instanceof Property and result = -2
or
nd instanceof ClassDefinition and result = -3
}
@ -57,8 +61,8 @@ abstract class StructurallyCompared extends ASTNode {
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 = candidateKind(kind, numChildren)
or
result = candidateKind(kind, numChildren)
or
result = uncleKind(kind, numChildren)
)
}
@ -68,7 +72,7 @@ abstract class StructurallyCompared extends ASTNode {
* where this node is the `i`th child of its parent.
*/
private ASTNode getAStructuralUncle(int i) {
exists (StructurallyCompared parent | this = parent.getChild(i) |
exists(StructurallyCompared parent | this = parent.getChild(i) |
result = parent.candidateInternal()
)
}
@ -79,7 +83,8 @@ abstract class StructurallyCompared extends ASTNode {
pragma[noinline]
private ASTNode uncleKind(int kind, int numChildren) {
exists(int i | result = getAStructuralUncle(i).getChild(i)) and kindAndArity(result, kind, numChildren)
exists(int i | result = getAStructuralUncle(i).getChild(i)) and
kindAndArity(result, kind, numChildren)
}
/**
@ -89,20 +94,24 @@ abstract class StructurallyCompared extends ASTNode {
*/
private predicate sameInternal(ASTNode that) {
that = this.candidateInternal() and
/* Check that `this` and `that` bind to the same variable, if any.
/*
* 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. */
* 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.
*/
/* 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 = getChild(i) and that = child.getAStructuralUncle(i) |
child.sameInternal(that.getChild(i))
forall(StructurallyCompared child, int i |
child = getChild(i) and that = child.getAStructuralUncle(i)
|
child.sameInternal(that.getChild(i))
)
}
@ -121,9 +130,7 @@ abstract class StructurallyCompared extends ASTNode {
* A child of a node that is subject to structural comparison.
*/
private class InternalCandidate extends StructurallyCompared {
InternalCandidate() {
exists (getParent().(StructurallyCompared).candidateInternal())
}
InternalCandidate() { exists(getParent().(StructurallyCompared).candidateInternal()) }
override ASTNode candidate() { none() }
}
@ -133,13 +140,9 @@ private class InternalCandidate extends StructurallyCompared {
* structurally identical.
*/
class OperandComparedToSelf extends StructurallyCompared {
OperandComparedToSelf() {
exists (Comparison comp | this = comp.getLeftOperand())
}
OperandComparedToSelf() { exists(Comparison comp | this = comp.getLeftOperand()) }
override Expr candidate() {
result = getParent().(Comparison).getRightOperand()
}
override Expr candidate() { result = getParent().(Comparison).getRightOperand() }
}
/**
@ -147,20 +150,14 @@ class OperandComparedToSelf extends StructurallyCompared {
* structurally identical.
*/
class SelfAssignment extends StructurallyCompared {
SelfAssignment() {
exists (AssignExpr assgn | this = assgn.getLhs())
}
SelfAssignment() { exists(AssignExpr assgn | this = assgn.getLhs()) }
override Expr candidate() {
result = getAssignment().getRhs()
}
override Expr candidate() { result = getAssignment().getRhs() }
/**
* Gets the enclosing assignment.
*/
AssignExpr getAssignment() {
result.getLhs() = this
}
AssignExpr getAssignment() { result.getLhs() = this }
}
/**
@ -169,14 +166,14 @@ class SelfAssignment extends StructurallyCompared {
*/
class DuplicatePropertyInitDetector extends StructurallyCompared {
DuplicatePropertyInitDetector() {
exists (ObjectExpr oe, string p |
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 |
exists(ObjectExpr oe, string p |
this = oe.getPropertyByName(p).getInit() and
result = oe.getPropertyByName(p).getInit() and
result != this

Просмотреть файл

@ -26,10 +26,11 @@ predicate accessWithConversions(Expr e, Variable v) {
or
accessWithConversions(e.(ParExpr).getExpression(), v)
or
exists (UnaryExpr ue | ue instanceof NegExpr or ue instanceof PlusExpr |
exists(UnaryExpr ue | ue instanceof NegExpr or ue instanceof PlusExpr |
ue = e and accessWithConversions(ue.getOperand(), v)
) or
exists (CallExpr ce | ce = e |
)
or
exists(CallExpr ce | ce = e |
ce = DataFlow::globalVarRef("Number").getACall().asExpr() and
ce.getNumArgument() = 1 and
accessWithConversions(ce.getArgument(0), v)
@ -57,26 +58,28 @@ predicate isNaNComment(Comment c, string filePath, int startLine) {
* one line away in either direction) that contains the word `NaN`.
*/
predicate isNaNCheck(EqualityTest eq) {
exists (Variable v |
exists(Variable v |
accessWithConversions(eq.getLeftOperand(), v) and
accessWithConversions(eq.getRightOperand(), v) |
accessWithConversions(eq.getRightOperand(), v)
|
// `v` is a parameter of the enclosing function, which is called `isNaN`
exists (Function isNaN |
exists(Function isNaN |
isNaN = eq.getEnclosingFunction() and
isNaN.getName().toLowerCase() = "isnan" and
v = isNaN.getAParameter().getAVariable()
) or
)
or
// there is a comment containing the word "NaN" next to the comparison
exists (string f, int l |
exists(string f, int l |
eq.getLocation().hasLocationInfo(f, l, _, _, _) and
isNaNComment(_, f, [l-1..l+1])
isNaNComment(_, f, [l - 1 .. l + 1])
)
)
}
from Comparison selfComparison, OperandComparedToSelf e
where e = selfComparison.getAnOperand() and e.same(_) and
not isNaNCheck(selfComparison)
where
e = selfComparison.getAnOperand() and
e.same(_) and
not isNaNCheck(selfComparison)
select selfComparison, "This expression compares $@ to itself.", e, e.toString()

Просмотреть файл

@ -15,5 +15,5 @@
import javascript
from Comparison cmp
where ((GlobalVarAccess)cmp.getAnOperand()).getName() = "NaN"
select cmp, "Useless comparison with NaN."
where (cmp.getAnOperand().(GlobalVarAccess)).getName() = "NaN"
select cmp, "Useless comparison with NaN."

Просмотреть файл

@ -6,15 +6,13 @@ import semmle.javascript.Externs
/** Holds if `et` is a root interface of the DOM type hierarchy. */
predicate isDOMRootType(ExternalType et) {
exists (string n | n = et.getName() |
n = "EventTarget" or n = "StyleSheet"
)
exists(string n | n = et.getName() | n = "EventTarget" or n = "StyleSheet")
}
/** Holds if `p` is declared as a property of a DOM class or interface. */
pragma[nomagic]
predicate isDOMProperty(string p) {
exists (ExternalMemberDecl emd | emd.getName() = p |
exists(ExternalMemberDecl emd | emd.getName() = p |
isDOMRootType(emd.getDeclaringType().getASupertype*())
)
}

Просмотреть файл

@ -15,20 +15,19 @@ import Clones
/** Gets the `i`th condition in the `if`-`else if` chain starting at `stmt`. */
Expr getCondition(IfStmt stmt, int i) {
i = 0 and result = stmt.getCondition() or
result = getCondition(stmt.getElse(), i-1)
i = 0 and result = stmt.getCondition()
or
result = getCondition(stmt.getElse(), i - 1)
}
/**
* A detector for duplicated `if` conditions in the same `if`-`else if` chain.
*/
class DuplicateIfCondition extends StructurallyCompared {
DuplicateIfCondition() {
this = getCondition(_, 0)
}
DuplicateIfCondition() { this = getCondition(_, 0) }
override Expr candidate() {
exists (IfStmt stmt, int j | this = getCondition(stmt, 0) |
exists(IfStmt stmt, int j | this = getCondition(stmt, 0) |
j > 0 and result = getCondition(stmt, j)
)
}
@ -36,4 +35,4 @@ class DuplicateIfCondition extends StructurallyCompared {
from DuplicateIfCondition e, Expr f
where e.same(f)
select f, "This condition is a duplicate of $@.", e, e.toString()
select f, "This condition is a duplicate of $@.", e, e.toString()

Просмотреть файл

@ -12,11 +12,13 @@
import Clones
from ObjectExpr oe, int i, int j, Property p, Property q,
DuplicatePropertyInitDetector dpid
where p = oe.getProperty(i) and q = oe.getProperty(j) and dpid = p.getInit() and
dpid.same(q.getInit()) and
i < j and
// only report the next duplicate
not exists (int mid | mid in [i+1..j-1] | dpid.same(oe.getProperty(mid).getInit()))
select p, "This property is duplicated $@.", q, "here"
from ObjectExpr oe, int i, int j, Property p, Property q, DuplicatePropertyInitDetector dpid
where
p = oe.getProperty(i) and
q = oe.getProperty(j) and
dpid = p.getInit() and
dpid.same(q.getInit()) and
i < j and
// only report the next duplicate
not exists(int mid | mid in [i + 1 .. j - 1] | dpid.same(oe.getProperty(mid).getInit()))
select p, "This property is duplicated $@.", q, "here"

Просмотреть файл

@ -17,12 +17,10 @@ import Clones
* A clone detector for finding structurally identical case labels.
*/
class DuplicateSwitchCase extends StructurallyCompared {
DuplicateSwitchCase() {
exists (Case c | this = c.getExpr())
}
DuplicateSwitchCase() { exists(Case c | this = c.getExpr()) }
override Expr candidate() {
exists (SwitchStmt s, int i, int j |
exists(SwitchStmt s, int i, int j |
this = s.getCase(i).getExpr() and
result = s.getCase(j).getExpr() and
i < j
@ -32,4 +30,4 @@ class DuplicateSwitchCase extends StructurallyCompared {
from DuplicateSwitchCase e, Expr f
where e.same(f)
select f, "This case label is a duplicate of $@.", e, e.toString()
select f, "This case label is a duplicate of $@.", e, e.toString()

Просмотреть файл

@ -21,27 +21,34 @@ import semmle.javascript.RestrictedLocations
* Holds if `e` appears in a syntactic context where its value is discarded.
*/
predicate inVoidContext(Expr e) {
exists (ExprStmt parent |
exists(ExprStmt parent |
// e is a toplevel expression in an expression statement
parent = e.getParent() and
// but it isn't an HTML attribute or a configuration object
not exists (TopLevel tl | tl = parent.getParent() |
tl instanceof CodeInAttribute or
not exists(TopLevel tl | tl = parent.getParent() |
tl instanceof CodeInAttribute
or
// if the toplevel in its entirety is of the form `({ ... })`,
// it is probably a configuration object (e.g., a require.js build configuration)
(tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr)
)
) or
exists (SeqExpr seq, int i, int n |
)
or
exists(SeqExpr seq, int i, int n |
e = seq.getOperand(i) and
n = seq.getNumOperands() |
i < n-1 or inVoidContext(seq)
) or
exists (ForStmt stmt | e = stmt.getUpdate()) or
exists (ForStmt stmt | e = stmt.getInit() |
n = seq.getNumOperands()
|
i < n - 1 or inVoidContext(seq)
)
or
exists(ForStmt stmt | e = stmt.getUpdate())
or
exists(ForStmt stmt | e = stmt.getInit() |
// Allow the pattern `for(i; i < 10; i++)`
not e instanceof VarAccess) or
exists (LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
not e instanceof VarAccess
)
or
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
}
/**
@ -54,7 +61,7 @@ predicate inVoidContext(Expr e) {
*/
predicate isDeclaration(Expr e) {
(e instanceof VarAccess or e instanceof PropAccess) and
exists (e.getParent().(ExprStmt).getDocumentation().getATag())
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
}
/**
@ -63,24 +70,23 @@ predicate isDeclaration(Expr e) {
predicate isGetterProperty(string name) {
// there is a call of the form `Object.defineProperty(..., name, { get: ..., ... })`
// or `Object.defineProperty(..., name, <something that's not an object literal>)`
exists (CallToObjectDefineProperty defProp |
exists(CallToObjectDefineProperty defProp |
name = defProp.getPropertyName() and
exists (Expr descriptor | descriptor = defProp.getPropertyDescriptor().asExpr() |
exists(Expr descriptor | descriptor = defProp.getPropertyDescriptor().asExpr() |
exists(descriptor.(ObjectExpr).getPropertyByName("get")) or
not descriptor instanceof ObjectExpr
)
) or
)
or
// there is an object expression with a getter property `name`
exists (ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
}
/**
* A property access that may invoke a getter.
*/
class GetterPropertyAccess extends PropAccess {
override predicate isImpure() {
isGetterProperty(getPropertyName())
}
override predicate isImpure() { isGetterProperty(getPropertyName()) }
}
/**
@ -89,7 +95,7 @@ class GetterPropertyAccess extends PropAccess {
* exists to prevent the call from being interpreted as a direct eval.
*/
predicate isIndirectEval(CallExpr c, Expr dummy) {
exists (SeqExpr seq | seq = c.getCallee().stripParens() |
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
seq.getNumOperands() = 2
@ -102,7 +108,7 @@ predicate isIndirectEval(CallExpr c, Expr dummy) {
* to prevent the call from being interpreted as a method call.
*/
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
exists (SeqExpr seq | seq = c.getCallee().stripParens() |
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1) = callee and
seq.getNumOperands() = 2
@ -121,32 +127,34 @@ predicate noSideEffects(Expr e) {
e.isPure()
or
// `new Error(...)`, `new SyntaxError(...)`, etc.
forex (Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
f.(ExternalType).getASupertype*().getName() = "Error"
)
}
from Expr e
where noSideEffects(e) and inVoidContext(e) and
// disregard pure expressions wrapped in a void(...)
not e instanceof VoidExpr and
// filter out directives (unknown directives are handled by UnknownDirective.ql)
not exists (Directive d | e = d.getExpr()) and
// or about externs
not e.inExternsFile() and
// don't complain about declarations
not isDeclaration(e) and
// exclude DOM properties, which sometimes have magical auto-update properties
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude xUnit.js annotations
not e instanceof XUnitAnnotation and
// exclude common patterns that are most likely intentional
not isIndirectEval(_, e) and
not isReceiverSuppressingCall(_, e, _) and
// exclude anonymous function expressions as statements; these can only arise
// from a syntax error we already flag
not exists (FunctionExpr fe, ExprStmt es | fe = e |
fe = es.getExpr() and
not exists(fe.getName())
)
select (FirstLineOf)e, "This expression has no effect."
where
noSideEffects(e) and
inVoidContext(e) and
// disregard pure expressions wrapped in a void(...)
not e instanceof VoidExpr and
// filter out directives (unknown directives are handled by UnknownDirective.ql)
not exists(Directive d | e = d.getExpr()) and
// or about externs
not e.inExternsFile() and
// don't complain about declarations
not isDeclaration(e) and
// exclude DOM properties, which sometimes have magical auto-update properties
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude xUnit.js annotations
not e instanceof XUnitAnnotation and
// exclude common patterns that are most likely intentional
not isIndirectEval(_, e) and
not isReceiverSuppressingCall(_, e, _) and
// exclude anonymous function expressions as statements; these can only arise
// from a syntax error we already flag
not exists(FunctionExpr fe, ExprStmt es | fe = e |
fe = es.getExpr() and
not exists(fe.getName())
)
select e.(FirstLineOf), "This expression has no effect."

Просмотреть файл

@ -15,13 +15,16 @@ import javascript
/** Gets the number of identifiers and string literals that refer to `name`. */
int countOccurrences(string name) {
(exists (PropAccess pacc | name = pacc.getPropertyName()) or
exists (VarAccess acc | name = acc.getName())) and
result = strictcount(Expr id |
id.(Identifier).getName() = name or
// count string literals as well to capture meta-programming
id.(ConstantString).getStringValue() = name
)
(
exists(PropAccess pacc | name = pacc.getPropertyName()) or
exists(VarAccess acc | name = acc.getName())
) and
result = strictcount(Expr id |
id.(Identifier).getName() = name
or
// count string literals as well to capture meta-programming
id.(ConstantString).getStringValue() = name
)
}
/**
@ -33,7 +36,7 @@ abstract class Hapax extends @expr {
abstract string getName();
/** Gets a textual representation of this element. */
string toString() { result = ((Expr)this).toString() }
string toString() { result = (this.(Expr)).toString() }
}
/**
@ -42,14 +45,10 @@ abstract class Hapax extends @expr {
*/
class UndeclaredPropertyAccess extends Hapax, @dotexpr {
UndeclaredPropertyAccess() {
exists (string name | name = this.(DotExpr).getPropertyName() |
countOccurrences(name) = 1
and
not exists (JSLintProperties jslpd |
jslpd.appliesTo(this) and jslpd.getAProperty() = name
)
and
not exists (ExternalMemberDecl emd | emd.getProperty() = this)
exists(string name | name = this.(DotExpr).getPropertyName() |
countOccurrences(name) = 1 and
not exists(JSLintProperties jslpd | jslpd.appliesTo(this) and jslpd.getAProperty() = name) and
not exists(ExternalMemberDecl emd | emd.getProperty() = this)
)
}
@ -62,12 +61,10 @@ class UndeclaredPropertyAccess extends Hapax, @dotexpr {
*/
class UndeclaredGlobal extends Hapax, @varaccess {
UndeclaredGlobal() {
exists (GlobalVariable gv, string name | this = gv.getAnAccess() and name = gv.getName() |
exists(GlobalVariable gv, string name | this = gv.getAnAccess() and name = gv.getName() |
countOccurrences(name) = 1 and
not exists (Linting::GlobalDeclaration glob |
glob.declaresGlobalForAccess(this)
) and
not exists (gv.getADeclaration())
not exists(Linting::GlobalDeclaration glob | glob.declaresGlobalForAccess(this)) and
not exists(gv.getADeclaration())
)
}
@ -79,7 +76,7 @@ class UndeclaredGlobal extends Hapax, @varaccess {
* except for capitalization, ensuring that it occurs at least twice.
*/
int candidateSpellingCount(Hapax hapax, string m) {
exists (string n | n = hapax.getName() |
exists(string n | n = hapax.getName() |
m.toLowerCase() = n.toLowerCase() and
m != n and
result = countOccurrences(m) and
@ -88,6 +85,7 @@ int candidateSpellingCount(Hapax hapax, string m) {
}
from Hapax hapax, string n, string m
where n = hapax.getName() and
candidateSpellingCount(hapax, m) = max(candidateSpellingCount(hapax, _))
select (Expr)hapax, "'" + n + "' is mentioned only once; it may be a typo for '" + m + "'."
where
n = hapax.getName() and
candidateSpellingCount(hapax, m) = max(candidateSpellingCount(hapax, _))
select hapax.(Expr), "'" + n + "' is mentioned only once; it may be a typo for '" + m + "'."

Просмотреть файл

@ -25,8 +25,11 @@ private import semmle.javascript.DefensiveProgramming
* with the switched-on expression being the right operand and all case labels the left operands.
*/
predicate comparisonOperands(ASTNode nd, Expr left, Expr right) {
exists (Comparison cmp | cmp = nd | left = cmp.getLeftOperand() and right = cmp.getRightOperand()) or
exists (SwitchStmt switch | switch = nd | right = switch.getExpr() and left = switch.getACase().getExpr())
exists(Comparison cmp | cmp = nd | left = cmp.getLeftOperand() and right = cmp.getRightOperand())
or
exists(SwitchStmt switch | switch = nd |
right = switch.getExpr() and left = switch.getACase().getExpr()
)
}
/**
@ -34,7 +37,7 @@ predicate comparisonOperands(ASTNode nd, Expr left, Expr right) {
*/
predicate hasImplicitConversionMethod(DefiniteAbstractValue av) {
// look for assignments to `toString` or `valueOf` on `av` or its prototypes
exists (AnalyzedPropertyWrite apw, string p | p = "toString" or p = "valueOf" |
exists(AnalyzedPropertyWrite apw, string p | p = "toString" or p = "valueOf" |
apw.writes(av.getAPrototype*(), p, _)
)
}
@ -44,10 +47,10 @@ predicate hasImplicitConversionMethod(DefiniteAbstractValue av) {
*/
InferredType strictEqualityOperandType(ASTNode eq, DataFlow::AnalyzedNode operand) {
// strict equality tests do no conversion at all
operand.asExpr() = eq.(StrictEqualityTest).getAChildExpr() and result = operand.getAType() or
operand.asExpr() = eq.(StrictEqualityTest).getAChildExpr() and result = operand.getAType()
or
// switch behaves like a strict equality test
exists (SwitchStmt switch | switch = eq |
exists(SwitchStmt switch | switch = eq |
(operand.asExpr() = switch.getExpr() or operand.asExpr() = switch.getACase().getExpr()) and
result = operand.getAType()
)
@ -70,7 +73,7 @@ predicate implicitlyConvertedOperand(ASTNode parent, DataFlow::AnalyzedNode oper
InferredType nonStrictOperandType(ASTNode parent, DataFlow::AnalyzedNode operand) {
// non-strict equality tests perform conversions
operand.asExpr() = parent.(NonStrictEqualityTest).getAChildExpr() and
exists (InferredType tp | tp = operand.getAValue().getType() |
exists(InferredType tp | tp = operand.getAValue().getType() |
result = tp
or
// Booleans are converted to numbers
@ -95,7 +98,7 @@ InferredType nonStrictOperandType(ASTNode parent, DataFlow::AnalyzedNode operand
or
// relational operators convert their operands to numbers or strings
operand.asExpr() = parent.(RelationalComparison).getAChildExpr() and
exists (AbstractValue v | v = operand.getAValue() |
exists(AbstractValue v | v = operand.getAValue() |
result = v.getType()
or
v.isCoercibleToNumber() and result = TTNumber()
@ -121,11 +124,14 @@ InferredType convertedOperandType(ASTNode parent, DataFlow::AnalyzedNode operand
* `leftTypes` and `rightTypes`, respectively, but there is no
* common type they coerce to.
*/
predicate isHeterogeneousComparison(ASTNode cmp, DataFlow::AnalyzedNode left, DataFlow::AnalyzedNode right,
string leftTypes, string rightTypes) {
predicate isHeterogeneousComparison(
ASTNode cmp, DataFlow::AnalyzedNode left, DataFlow::AnalyzedNode right, string leftTypes,
string rightTypes
) {
comparisonOperands(cmp, left.asExpr(), right.asExpr()) and
not convertedOperandType(cmp, left) = convertedOperandType(cmp, right) and
leftTypes = left.ppTypes() and rightTypes = right.ppTypes()
leftTypes = left.ppTypes() and
rightTypes = right.ppTypes()
}
/**
@ -141,11 +147,8 @@ predicate isPseudoKeyword(string name) {
* Gets a user friendly description of `e`, if such a description exists.
*/
string getDescription(VarAccess e) {
exists (string name | name = e.getName() |
if isPseudoKeyword(name) then
result = "'" + name + "'"
else
result = "variable '" + name + "'"
exists(string name | name = e.getName() |
if isPseudoKeyword(name) then result = "'" + name + "'" else result = "variable '" + name + "'"
)
}
@ -154,10 +157,7 @@ string getDescription(VarAccess e) {
*/
bindingset[default]
string getDescription(Expr e, string default) {
if exists (getDescription(e)) then
result = getDescription(e)
else
result = default
if exists(getDescription(e)) then result = getDescription(e) else result = default
}
/**
@ -165,10 +165,7 @@ string getDescription(Expr e, string default) {
*/
bindingset[message1, message2, complexity1, complexity2]
string getTypeDescription(string message1, string message2, int complexity1, int complexity2) {
if complexity1 > 4 and complexity2 <= 2 then
result = message2
else
result = message1
if complexity1 > 4 and complexity2 <= 2 then result = message2 else result = message1
}
/**
@ -176,7 +173,7 @@ string getTypeDescription(string message1, string message2, int complexity1, int
*/
predicate isInitialParameterUse(Expr e) {
// unlike `SimpleParameter.getAnInitialUse` this will not include uses we have refinement information for
exists (SimpleParameter p, SsaExplicitDefinition ssa |
exists(SimpleParameter p, SsaExplicitDefinition ssa |
ssa.getDef() = p and
ssa.getVariable().getAUse() = e and
not p.isRestParameter()
@ -188,25 +185,25 @@ predicate isInitialParameterUse(Expr e) {
*
* We currently whitelist expressions that rely on inter-procedural parameter information.
*/
predicate whitelist(Expr e) {
isInitialParameterUse(e)
}
predicate whitelist(Expr e) { isInitialParameterUse(e) }
from ASTNode cmp,
DataFlow::AnalyzedNode left, DataFlow::AnalyzedNode right,
string leftTypes, string rightTypes,
string leftExprDescription, string rightExprDescription,
int leftTypeCount, int rightTypeCount ,
string leftTypeDescription, string rightTypeDescription
where isHeterogeneousComparison(cmp, left, right, leftTypes, rightTypes) and
not exists (cmp.(Expr).flow().(DefensiveExpressionTest).getTheTestResult()) and
not whitelist(left.asExpr()) and
not whitelist(right.asExpr()) and
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and
rightExprDescription = getDescription(right.asExpr(), "an expression") and
leftTypeCount = strictcount(left.getAType()) and
rightTypeCount = strictcount(right.getAType()) and
leftTypeDescription = getTypeDescription("is of type " + leftTypes, "cannot be of type " + rightTypes, leftTypeCount, rightTypeCount) and
rightTypeDescription = getTypeDescription("of type " + rightTypes, ", which cannot be of type " + leftTypes, rightTypeCount, leftTypeCount)
select left, leftExprDescription + " " + leftTypeDescription + ", but it is compared to $@ " + rightTypeDescription + ".",
right, rightExprDescription
from
ASTNode cmp, DataFlow::AnalyzedNode left, DataFlow::AnalyzedNode right, string leftTypes,
string rightTypes, string leftExprDescription, string rightExprDescription, int leftTypeCount,
int rightTypeCount, string leftTypeDescription, string rightTypeDescription
where
isHeterogeneousComparison(cmp, left, right, leftTypes, rightTypes) and
not exists(cmp.(Expr).flow().(DefensiveExpressionTest).getTheTestResult()) and
not whitelist(left.asExpr()) and
not whitelist(right.asExpr()) and
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and
rightExprDescription = getDescription(right.asExpr(), "an expression") and
leftTypeCount = strictcount(left.getAType()) and
rightTypeCount = strictcount(right.getAType()) and
leftTypeDescription = getTypeDescription("is of type " + leftTypes,
"cannot be of type " + rightTypes, leftTypeCount, rightTypeCount) and
rightTypeDescription = getTypeDescription("of type " + rightTypes,
", which cannot be of type " + leftTypes, rightTypeCount, leftTypeCount)
select left,
leftExprDescription + " " + leftTypeDescription + ", but it is compared to $@ " +
rightTypeDescription + ".", right, rightExprDescription

Просмотреть файл

@ -21,9 +21,7 @@ private import semmle.javascript.dataflow.InferredTypes
abstract class ImplicitConversion extends DataFlow::AnalyzedNode {
Expr parent;
ImplicitConversion() {
this.asExpr() = parent.getAChildExpr()
}
ImplicitConversion() { this.asExpr() = parent.getAChildExpr() }
/**
* Gets a description of the type(s) to which the value `v`, which is
@ -62,17 +60,11 @@ abstract class ImplicitConversionWithWhitelist extends ImplicitConversion {
* so they should be strings or numbers.
*/
class PropertyNameConversion extends ImplicitConversionWithWhitelist {
PropertyNameConversion() {
this.asExpr() = parent.(InExpr).getLeftOperand()
}
PropertyNameConversion() { this.asExpr() = parent.(InExpr).getLeftOperand() }
override InferredType getAWhitelistedType() {
result = TTString() or result = TTNumber()
}
override InferredType getAWhitelistedType() { result = TTString() or result = TTNumber() }
override string getConversionTarget() {
result = "string"
}
override string getConversionTarget() { result = "string" }
}
/**
@ -80,17 +72,13 @@ class PropertyNameConversion extends ImplicitConversionWithWhitelist {
* so they should be Booleans, strings or numbers.
*/
class IndexExprConversion extends ImplicitConversionWithWhitelist {
IndexExprConversion() {
this.asExpr() = parent.(IndexExpr).getIndex()
}
IndexExprConversion() { this.asExpr() = parent.(IndexExpr).getIndex() }
override InferredType getAWhitelistedType() {
result = TTBoolean() or result = TTString() or result = TTNumber()
}
override string getConversionTarget() {
result = "string"
}
override string getConversionTarget() { result = "string" }
}
/**
@ -102,30 +90,20 @@ class ObjectConversion extends ImplicitConversionWithWhitelist {
this.asExpr() = parent.(InstanceofExpr).getLeftOperand()
}
override InferredType getAWhitelistedType() {
result instanceof NonPrimitiveType
}
override InferredType getAWhitelistedType() { result instanceof NonPrimitiveType }
override string getConversionTarget() {
result = "object"
}
override string getConversionTarget() { result = "object" }
}
/**
* The right-hand operand of `instanceof` should be a function or class.
*/
class ConstructorConversion extends ImplicitConversionWithWhitelist {
ConstructorConversion() {
this.asExpr() = parent.(InstanceofExpr).getRightOperand()
}
ConstructorConversion() { this.asExpr() = parent.(InstanceofExpr).getRightOperand() }
override InferredType getAWhitelistedType() {
result = TTFunction() or result = TTClass()
}
override InferredType getAWhitelistedType() { result = TTFunction() or result = TTClass() }
override string getConversionTarget() {
result = "function"
}
override string getConversionTarget() { result = "function" }
}
/**
@ -133,17 +111,13 @@ class ConstructorConversion extends ImplicitConversionWithWhitelist {
* and hence should be strings, numbers or Dates.
*/
class RelationalOperandConversion extends ImplicitConversionWithWhitelist {
RelationalOperandConversion() {
parent instanceof RelationalComparison
}
RelationalOperandConversion() { parent instanceof RelationalComparison }
override InferredType getAWhitelistedType() {
result = TTString() or result = TTNumber() or result = TTDate()
}
override string getConversionTarget() {
result = "number or string"
}
override string getConversionTarget() { result = "number or string" }
}
/**
@ -152,9 +126,12 @@ class RelationalOperandConversion extends ImplicitConversionWithWhitelist {
*/
class NumericConversion extends ImplicitConversion {
NumericConversion() {
parent instanceof BitwiseExpr or
parent instanceof ArithmeticExpr and not parent instanceof AddExpr or
parent instanceof CompoundAssignExpr and not parent instanceof AssignAddExpr or
parent instanceof BitwiseExpr
or
parent instanceof ArithmeticExpr and not parent instanceof AddExpr
or
parent instanceof CompoundAssignExpr and not parent instanceof AssignAddExpr
or
parent instanceof UpdateExpr
}
@ -183,13 +160,9 @@ abstract class NullOrUndefinedConversion extends ImplicitConversion {
* should not be `null` or `undefined`.
*/
class PlusConversion extends NullOrUndefinedConversion {
PlusConversion() {
parent instanceof AddExpr or parent instanceof AssignAddExpr
}
PlusConversion() { parent instanceof AddExpr or parent instanceof AssignAddExpr }
override string getConversionTarget() {
result = "number or string"
}
override string getConversionTarget() { result = "number or string" }
}
/**
@ -197,17 +170,14 @@ class PlusConversion extends NullOrUndefinedConversion {
* be `null` or `undefined`.
*/
class TemplateElementConversion extends NullOrUndefinedConversion {
TemplateElementConversion() {
parent instanceof TemplateLiteral
}
TemplateElementConversion() { parent instanceof TemplateLiteral }
override string getConversionTarget() {
result = "string"
}
override string getConversionTarget() { result = "string" }
}
from ImplicitConversion e, string convType
where convType = e.getAnImplicitConversionTarget(_) and
forall (AbstractValue v | v = e.getAValue() | exists(e.getAnImplicitConversionTarget(v)))
select e, "This expression will be implicitly converted from " +
e.ppTypes() + " to " + convType + "."
where
convType = e.getAnImplicitConversionTarget(_) and
forall(AbstractValue v | v = e.getAValue() | exists(e.getAnImplicitConversionTarget(v)))
select e,
"This expression will be implicitly converted from " + e.ppTypes() + " to " + convType + "."

Просмотреть файл

@ -18,25 +18,28 @@ import javascript
* Such expressions make contradictory assumptions about the types of `base` and `index`.
*/
predicate contradictoryAccess(RelationalComparison compare, IndexExpr lookup) {
exists (SsaVariable base, SsaVariable index |
exists(SsaVariable base, SsaVariable index |
base != index and
compare.hasOperands(base.getAUse(), index.getAUse()) and
lookup.getBase() = base.getAUse() and
lookup.getIndex() = index.getAUse())
lookup.getIndex() = index.getAUse()
)
or
// We allow `base` to be a global, since globals rarely undergo radical type changes
// that depend on local control flow.
// We could do the same for `index`, but it rarely matters for the pattern we are looking for.
sameIndex(compare, lookup) and
exists (GlobalVariable base |
exists(GlobalVariable base |
compare.getAnOperand() = base.getAnAccess() and
lookup.getBase() = base.getAnAccess())
lookup.getBase() = base.getAnAccess()
)
}
predicate sameIndex(RelationalComparison compare, IndexExpr lookup) {
exists (SsaVariable index |
exists(SsaVariable index |
compare.getAnOperand() = index.getAUse() and
lookup.getIndex() = index.getAUse())
lookup.getIndex() = index.getAUse()
)
}
predicate relevantBasicBlocks(ReachableBasicBlock b1, ReachableBasicBlock b2) {
@ -49,6 +52,7 @@ predicate sameBranch(ReachableBasicBlock b1, ReachableBasicBlock b2) {
}
from RelationalComparison compare, IndexExpr lookup
where contradictoryAccess(compare, lookup)
and sameBranch(compare.getBasicBlock(), lookup.getBasicBlock())
where
contradictoryAccess(compare, lookup) and
sameBranch(compare.getBasicBlock(), lookup.getBasicBlock())
select compare, "Missing .length in comparison, or erroneous $@.", lookup, "index expression"

Просмотреть файл

@ -30,16 +30,16 @@ class LiteralOrTemplate extends Expr {
}
from AddExpr e, LiteralOrTemplate l, LiteralOrTemplate r, string word
where // l and r are appended together
l = rightChild*(e.getLeftOperand()) and
r = leftChild*(e.getRightOperand()) and
// `l + r` is of the form `... word" + "word2...`, possibly including some
// punctuation after `word`.
// Only the first character of `word2` is matched, whereas `word` is matched
// completely to distinguish grammatical punctuation after which a space is
// needed, and intra-identifier punctuation in, for example, a qualified name.
word = l.getStringValue().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,!?']*)", 1) and
r.getStringValue().regexpMatch("[a-zA-Z].*") and
not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]")
where
// l and r are appended together
l = rightChild*(e.getLeftOperand()) and
r = leftChild*(e.getRightOperand()) and
// `l + r` is of the form `... word" + "word2...`, possibly including some
// punctuation after `word`.
// Only the first character of `word2` is matched, whereas `word` is matched
// completely to distinguish grammatical punctuation after which a space is
// needed, and intra-identifier punctuation in, for example, a qualified name.
word = l.getStringValue().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,!?']*)", 1) and
r.getStringValue().regexpMatch("[a-zA-Z].*") and
not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]")
select l, "This string appears to be missing a space after '" + word + "'."

Просмотреть файл

@ -15,9 +15,7 @@ import Misspelling
* An identifier part.
*/
class IdentifierPart extends string {
IdentifierPart() {
idPart(_, this, _)
}
IdentifierPart() { idPart(_, this, _) }
/**
* Holds if this element is at the specified location.
@ -26,13 +24,18 @@ class IdentifierPart extends string {
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(string filepath, int startline, int startcolumn,
int endline, int endcolumn) {
exists (Identifier id, int start, Location l, int len | occursIn(id, start, len) and l = id.getLocation() |
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(Identifier id, int start, Location l, int len |
occursIn(id, start, len) and l = id.getLocation()
|
filepath = l.getFile().getAbsolutePath() and
startline = l.getStartLine() and startcolumn = l.getStartColumn() + start and
startline = l.getStartLine() and
startcolumn = l.getStartColumn() + start and
// identifiers cannot span more than one line
endline = startline and endcolumn = startcolumn + len - 1
endline = startline and
endcolumn = startcolumn + len - 1
)
}
@ -49,15 +52,13 @@ class IdentifierPart extends string {
* An identifier part that corresponds to a typo in `normalized_typos`.
*/
class WrongIdentifierPart extends IdentifierPart {
WrongIdentifierPart() {
normalized_typos(this, _, _, _, _, _)
}
WrongIdentifierPart() { normalized_typos(this, _, _, _, _, _) }
/**
* Gets an identifier part that corresponds to a correction of this typo.
*/
string getASuggestion() {
exists (IdentifierPart right | normalized_typos(this, right, _, _, _, _) |
exists(IdentifierPart right | normalized_typos(this, right, _, _, _, _) |
result = "'" + right + "'"
)
}
@ -67,7 +68,7 @@ class WrongIdentifierPart extends IdentifierPart {
* this typo that appear as identifier parts in the code.
*/
string ppSuggestions() {
exists (string cat |
exists(string cat |
// first, concatenate with commas
cat = concat(getASuggestion(), ", ") and
// then, replace last comma with "or"
@ -79,10 +80,12 @@ class WrongIdentifierPart extends IdentifierPart {
super.occursIn(id, start, len) and
// throw out cases where the wrong word appears as a prefix or suffix of a right word,
// and thus the result is most likely a false positive caused by our word segmentation algorithm
exists (string lowerid | lowerid = id.getName().toLowerCase() |
not exists (string right, int rightlen |
this.prefixOf(right, rightlen) and lowerid.substring(start, start+rightlen) = right or
this.suffixOf(right, rightlen) and lowerid.substring(start+len-rightlen, start+len) = right
exists(string lowerid | lowerid = id.getName().toLowerCase() |
not exists(string right, int rightlen |
this.prefixOf(right, rightlen) and lowerid.substring(start, start + rightlen) = right
or
this.suffixOf(right, rightlen) and
lowerid.substring(start + len - rightlen, start + len) = right
)
) and
// also throw out cases flagged by another query
@ -94,10 +97,13 @@ class WrongIdentifierPart extends IdentifierPart {
* a correct spelling with length `rightlen`.
*/
predicate prefixOf(string right, int rightlen) {
exists (string c, int wronglen |
normalized_typos(this, _, c, _, _, _) and normalized_typos(_, right, _, _, c, _) and
wronglen = this.length() and rightlen = right.length() and
wronglen < rightlen and right.prefix(wronglen) = this
exists(string c, int wronglen |
normalized_typos(this, _, c, _, _, _) and
normalized_typos(_, right, _, _, c, _) and
wronglen = this.length() and
rightlen = right.length() and
wronglen < rightlen and
right.prefix(wronglen) = this
)
}
@ -106,17 +112,21 @@ class WrongIdentifierPart extends IdentifierPart {
* a correct spelling with length `rightlen`.
*/
predicate suffixOf(string right, int rightlen) {
exists (string c, int wronglen |
normalized_typos(this, _, _, c, _, _) and normalized_typos(_, right, _, _, _, c) and
wronglen = this.length() and rightlen = right.length() and
wronglen < rightlen and right.suffix(rightlen-wronglen) = this
exists(string c, int wronglen |
normalized_typos(this, _, _, c, _, _) and
normalized_typos(_, right, _, _, _, c) and
wronglen = this.length() and
rightlen = right.length() and
wronglen < rightlen and
right.suffix(rightlen - wronglen) = this
)
}
}
from WrongIdentifierPart wrong
where // make sure we have at least one occurrence of a correction
exists(wrong.getASuggestion()) and
// make sure we have at least one unambiguous occurrence of the wrong word
wrong.occursIn(_, _, _)
where
// make sure we have at least one occurrence of a correction
exists(wrong.getASuggestion()) and
// make sure we have at least one unambiguous occurrence of the wrong word
wrong.occursIn(_, _, _)
select wrong, "'" + wrong + "' may be a typo for " + wrong.ppSuggestions() + "."

Просмотреть файл

@ -3,7 +3,6 @@
*/
import javascript
// import typo database (generated from Wikipedia, licensed under CC BY-SA 3.0)
import TypoDatabase
@ -12,9 +11,12 @@ import TypoDatabase
* is not interesting enough to flag.
*/
predicate whitelisted(string wrong, string right) {
wrong = "thru" and right = "through" or
wrong = "cant" and right = "cannot" or
wrong = "inbetween" and right = "between" or
wrong = "thru" and right = "through"
or
wrong = "cant" and right = "cannot"
or
wrong = "inbetween" and right = "between"
or
wrong = "strat" and right = "start" // often used as abbreviation for "strategy"
}
@ -24,15 +26,18 @@ predicate whitelisted(string wrong, string right) {
* of `wrong`, and similarly for `rightstart` and `rightend`.
*/
cached
predicate normalized_typos(string wrong, string right,
string wrongstart, string wrongend, string rightstart, string rightend) {
predicate normalized_typos(
string wrong, string right, string wrongstart, string wrongend, string rightstart, string rightend
) {
typos(wrong, right) and
not whitelisted(wrong, right) and
// omit very short identifiers, which are often idiosyncratic abbreviations
wrong.length() > 3 and
// record first and last characters
wrongstart = wrong.charAt(0) and wrongend = wrong.charAt(wrong.length()-1) and
rightstart = right.charAt(0) and rightend = right.charAt(right.length()-1)
wrongstart = wrong.charAt(0) and
wrongend = wrong.charAt(wrong.length() - 1) and
rightstart = right.charAt(0) and
rightend = right.charAt(right.length() - 1)
}
/**
@ -63,7 +68,7 @@ predicate idPart(Identifier id, string part, int offset) {
/** An identifier that contains at least one misspelling. */
private class WrongIdentifier extends Identifier {
WrongIdentifier() {
exists (string wrongPart |
exists(string wrongPart |
idPart(this, wrongPart, _) and
normalized_typos(wrongPart, _, _, _, _, _)
)
@ -73,7 +78,7 @@ private class WrongIdentifier extends Identifier {
/** A variable whose name contains at least one misspelling. */
private class WrongVariable extends LocalVariable {
WrongVariable() {
exists (string wrongPart |
exists(string wrongPart |
idPart(this.getADeclaration(), wrongPart, _) and
normalized_typos(wrongPart, _, _, _, _, _)
)
@ -82,12 +87,12 @@ private class WrongVariable extends LocalVariable {
/** Gets the name of identifier `wrong`, with one misspelling corrected. */
private string replaceATypoAndLowerCase(Identifier wrong) {
exists (string wrongPart, string rightName, string rightPart, int offset |
idPart(wrong, wrongPart, offset) |
exists(string wrongPart, string rightName, string rightPart, int offset |
idPart(wrong, wrongPart, offset)
|
normalized_typos(wrongPart, rightPart, _, _, _, _) and
rightName = wrong.getName().substring(0, offset)
+ rightPart
+ wrong.getName().suffix(offset + wrongPart.length()) and
rightName = wrong.getName().substring(0, offset) + rightPart +
wrong.getName().suffix(offset + wrongPart.length()) and
result = rightName.toLowerCase()
)
}
@ -108,7 +113,7 @@ private Identifier idInScopeOfWrongVariable(WrongVariable wrong) {
* of `lvd` or vice versa.
*/
predicate misspelledVariableName(GlobalVarAccess gva, VarDecl lvd) {
exists (LocalVariable lv | lvd = lv.getADeclaration() |
exists(LocalVariable lv | lvd = lv.getADeclaration() |
lv.getScope() = scopeAroundWrongIdentifier(gva) and
lv.getName().toLowerCase() = replaceATypoAndLowerCase(gva)
or

Просмотреть файл

@ -19,18 +19,12 @@ import Clones
* A clone detector that finds redundant expressions.
*/
abstract class RedundantOperand extends StructurallyCompared {
RedundantOperand() {
exists (BinaryExpr parent | this = parent.getLeftOperand())
}
RedundantOperand() { exists(BinaryExpr parent | this = parent.getLeftOperand()) }
override Expr candidate() {
result = getParent().(BinaryExpr).getRightOperand()
}
override Expr candidate() { result = getParent().(BinaryExpr).getRightOperand() }
/** Gets the expression to report when a pair of clones is found. */
Expr toReport() {
result = getParent()
}
Expr toReport() { result = getParent() }
}
/**
@ -55,7 +49,7 @@ class IdemnecantExpr extends BinaryExpr {
*/
class RedundantIdemnecantOperand extends RedundantOperand {
RedundantIdemnecantOperand() {
exists (IdemnecantExpr parent |
exists(IdemnecantExpr parent |
parent = getParent() and
// exclude trivial cases like `1-1`
not parent.getRightOperand().getUnderlyingValue() instanceof Literal
@ -70,9 +64,7 @@ class RedundantIdemnecantOperand extends RedundantOperand {
* arguments to integers. For example, `x&x` is a common idiom for converting `x` to an integer.
*/
class RedundantIdempotentOperand extends RedundantOperand {
RedundantIdempotentOperand() {
getParent() instanceof LogicalBinaryExpr
}
RedundantIdempotentOperand() { getParent() instanceof LogicalBinaryExpr }
}
/**
@ -90,16 +82,12 @@ class AverageExpr extends DivExpr {
*/
class RedundantAverageOperand extends RedundantOperand {
RedundantAverageOperand() {
exists (AverageExpr aver |
(AddExpr)getParent() = aver.getLeftOperand().getUnderlyingValue()
)
exists(AverageExpr aver | getParent().(AddExpr) = aver.getLeftOperand().getUnderlyingValue())
}
override AverageExpr toReport() {
getParent() = result.getLeftOperand().getUnderlyingValue()
}
override AverageExpr toReport() { getParent() = result.getLeftOperand().getUnderlyingValue() }
}
from RedundantOperand e, Expr f
where e.same(f)
select e.toReport(), "Operands $@ and $@ are identical.", e, e.toString(), f, f.toString()
select e.toReport(), "Operands $@ and $@ are identical.", e, e.toString(), f, f.toString()

Просмотреть файл

@ -24,26 +24,24 @@ import DOMProperties
* in which case `element <name>` is used instead.
*/
string describe(Expr e) {
exists (VarAccess va | va = e | result = "variable " + va.getName())
exists(VarAccess va | va = e | result = "variable " + va.getName())
or
exists (string name | name = e.(PropAccess).getPropertyName() |
if exists(name.toInt()) then
result = "element " + name
else
result = "property " + name
exists(string name | name = e.(PropAccess).getPropertyName() |
if exists(name.toInt()) then result = "element " + name else result = "property " + name
)
}
from SelfAssignment e, string dsc
where e.same(_) and
dsc = describe(e) and
// exclude properties for which there is an accessor somewhere
not exists(string propName | propName = e.(PropAccess).getPropertyName() |
propName = any(PropertyAccessor acc).getName() or
propName = any(AccessorMethodDeclaration amd).getName()
) and
// exclude DOM properties
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude self-assignments that have been inserted to satisfy the TypeScript JS-checker
not e.getAssignment().getParent().(ExprStmt).getDocumentation().getATag().getTitle() = "type"
where
e.same(_) and
dsc = describe(e) and
// exclude properties for which there is an accessor somewhere
not exists(string propName | propName = e.(PropAccess).getPropertyName() |
propName = any(PropertyAccessor acc).getName() or
propName = any(AccessorMethodDeclaration amd).getName()
) and
// exclude DOM properties
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude self-assignments that have been inserted to satisfy the TypeScript JS-checker
not e.getAssignment().getParent().(ExprStmt).getDocumentation().getATag().getTitle() = "type"
select e.getParent(), "This expression assigns " + dsc + " to itself."

Просмотреть файл

@ -15,4 +15,4 @@ import javascript
from ShiftExpr shift
where shift.getRightOperand().getIntValue() > 31
select shift, "Shift out of range."
select shift, "Shift out of range."

Просмотреть файл

@ -7,6 +7,7 @@
* @tags correctness
* @precision high
*/
import javascript
/**
@ -26,11 +27,16 @@ private string getALikelyRegExpPattern() {
* Holds if `mce` is a call to String.prototype.replace or String.prototype.split
*/
predicate isStringSplitOrReplace(MethodCallExpr mce) {
exists (string name, int arity |
exists(string name, int arity |
mce.getMethodName() = name and
mce.getNumArgument() = arity |
(name = "replace" and arity = 2) or
(name = "split" and (arity = 1 or arity = 2))
mce.getNumArgument() = arity
|
(name = "replace" and arity = 2)
or
(
name = "split" and
(arity = 1 or arity = 2)
)
)
}
@ -42,9 +48,12 @@ predicate mayReferToString(DataFlow::Node nd, StringLiteral s) {
}
from MethodCallExpr mce, StringLiteral arg, string raw, string s
where isStringSplitOrReplace(mce) and
mayReferToString(mce.getArgument(0).flow(), arg) and
raw = arg.getRawValue() and
s = raw.substring(1, raw.length() - 1) and
s.regexpMatch(getALikelyRegExpPattern())
select mce, "String argument '$@' looks like a regular expression, but it will be interpreted as a string.", arg, s
where
isStringSplitOrReplace(mce) and
mayReferToString(mce.getArgument(0).flow(), arg) and
raw = arg.getRawValue() and
s = raw.substring(1, raw.length() - 1) and
s.regexpMatch(getALikelyRegExpPattern())
select mce,
"String argument '$@' looks like a regular expression, but it will be interpreted as a string.",
arg, s

Просмотреть файл

@ -14,8 +14,9 @@ import javascript
private import semmle.javascript.dataflow.InferredTypes
from InvokeExpr invk, DataFlow::AnalyzedNode callee
where callee.asExpr() = invk.getCallee() and
forex (InferredType tp | tp = callee.getAType() | tp != TTFunction() and tp != TTClass()) and
not invk.isAmbient() and
not invk instanceof OptionalUse
select invk, "Callee is not a function: it has type " + callee.ppTypes() + "."
where
callee.asExpr() = invk.getCallee() and
forex(InferredType tp | tp = callee.getAType() | tp != TTFunction() and tp != TTClass()) and
not invk.isAmbient() and
not invk instanceof OptionalUse
select invk, "Callee is not a function: it has type " + callee.ppTypes() + "."

Просмотреть файл

@ -23,15 +23,18 @@ private import semmle.javascript.dataflow.InferredTypes
* if it is part of a const enum access, so we conservatively silence the alert in that case.
*/
predicate namespaceOrConstEnumAccess(VarAccess e) {
exists (NamespaceDeclaration decl | e.getVariable().getADeclaration() = decl.getId())
exists(NamespaceDeclaration decl | e.getVariable().getADeclaration() = decl.getId())
or
exists (EnumDeclaration decl | e.getVariable().getADeclaration() = decl.getIdentifier() | decl.isConst())
exists(EnumDeclaration decl | e.getVariable().getADeclaration() = decl.getIdentifier() |
decl.isConst()
)
}
from PropAccess pacc, DataFlow::AnalyzedNode base
where base.asExpr() = pacc.getBase() and
forex (InferredType tp | tp = base.getAType() | tp = TTNull() or tp = TTUndefined()) and
not namespaceOrConstEnumAccess(pacc.getBase()) and
not pacc.isAmbient() and
not pacc instanceof OptionalUse
where
base.asExpr() = pacc.getBase() and
forex(InferredType tp | tp = base.getAType() | tp = TTNull() or tp = TTUndefined()) and
not namespaceOrConstEnumAccess(pacc.getBase()) and
not pacc.isAmbient() and
not pacc instanceof OptionalUse
select pacc, "The base expression of this property access is always " + base.ppTypes() + "."

Просмотреть файл

@ -7,35 +7,37 @@
* @tags correctness
* @precision high
*/
import javascript
/**
* Holds if the receiver of `method` is bound.
*/
private predicate isBoundInMethod(MethodDeclaration method) {
exists (DataFlow::ThisNode thiz, MethodDeclaration bindingMethod |
exists(DataFlow::ThisNode thiz, MethodDeclaration bindingMethod |
bindingMethod.getDeclaringClass() = method.getDeclaringClass() and
not bindingMethod.isStatic() and
thiz.getBinder().getAstNode() = bindingMethod.getBody() |
thiz.getBinder().getAstNode() = bindingMethod.getBody()
|
// require("auto-bind")(this)
thiz.flowsTo(DataFlow::moduleImport("auto-bind").getACall().getArgument(0))
or
exists (string name |
name = method.getName() |
exists (DataFlow::MethodCallNode bind |
exists(string name | name = method.getName() |
exists(DataFlow::MethodCallNode bind |
// this.<methodName> = <expr>.bind(...)
bind = thiz.getAPropertySource(name) and
bind.getMethodName() = "bind"
)
or
exists (DataFlow::MethodCallNode bindAll |
exists(DataFlow::MethodCallNode bindAll |
bindAll.getMethodName() = "bindAll" and
thiz.flowsTo(bindAll.getArgument(0)) |
thiz.flowsTo(bindAll.getArgument(0))
|
// _.bindAll(this, <name1>)
bindAll.getArgument(1).mayHaveStringValue(name)
or
// _.bindAll(this, [<name1>, <name2>])
exists (DataFlow::ArrayCreationNode names |
exists(DataFlow::ArrayCreationNode names |
names.flowsTo(bindAll.getArgument(1)) and
names.getAnElement().mayHaveStringValue(name)
)
@ -43,15 +45,17 @@ private predicate isBoundInMethod(MethodDeclaration method) {
)
)
or
exists (Expr decoration, string name |
exists(Expr decoration, string name |
(
decoration = method.getADecorator().getExpression()
or
decoration = method.getDeclaringType().(ClassDefinition).getADecorator().getExpression()
) and
name.regexpMatch("(?i).*(bind|bound).*") |
name.regexpMatch("(?i).*(bind|bound).*")
|
// @autobind
decoration.(Identifier).getName() = name or
decoration.(Identifier).getName() = name
or
// @action.bound
decoration.(PropAccess).getPropertyName() = name
)
@ -61,7 +65,7 @@ private predicate isBoundInMethod(MethodDeclaration method) {
* Gets an event handler attribute (onClick, onTouch, ...).
*/
private DOM::AttributeDefinition getAnEventHandlerAttribute() {
exists (ReactComponent c, JSXNode rendered, string attributeName |
exists(ReactComponent c, JSXNode rendered, string attributeName |
c.getRenderMethod().getAReturnedExpr().flow().getALocalSource().asExpr() = rendered and
result = rendered.getABodyElement*().(JSXElement).getAttributeByName(attributeName) and
attributeName.regexpMatch("on[A-Z][a-zA-Z]+") // camelCased with 'on'-prefix
@ -70,8 +74,11 @@ private DOM::AttributeDefinition getAnEventHandlerAttribute() {
from MethodDeclaration callback, DOM::AttributeDefinition attribute, ThisExpr unbound
where
attribute = getAnEventHandlerAttribute() and
attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = callback.getBody() and
unbound.getBinder() = callback.getBody() and
not isBoundInMethod(callback)
select attribute, "The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@", unbound, "this", callback, callback.getName()
attribute = getAnEventHandlerAttribute() and
attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = callback
.getBody() and
unbound.getBinder() = callback.getBody() and
not isBoundInMethod(callback)
select attribute,
"The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@",
unbound, "this", callback, callback.getName()

Просмотреть файл

@ -16,14 +16,16 @@
import javascript
from BitwiseBinaryExpr bit, Comparison rel, Expr other
where bit.hasOperands(rel, other) and
// only flag if whitespace doesn't clarify the nesting (note that if `bit` has less
// whitespace than `rel`, it will be reported by `js/whitespace-contradicts-precedence`)
bit.getWhitespaceAroundOperator() = rel.getWhitespaceAroundOperator() and
// don't flag if the other operand is itself a comparison,
// since the nesting tends to be visually more obvious in such cases
not other instanceof Comparison and
// don't flag occurrences in minified code
not rel.getTopLevel().isMinified()
select rel, "The '" + rel.getOperator() + "' operator binds more tightly than " +
"'" + bit.getOperator() + "', which may not be obvious in this case."
where
bit.hasOperands(rel, other) and
// only flag if whitespace doesn't clarify the nesting (note that if `bit` has less
// whitespace than `rel`, it will be reported by `js/whitespace-contradicts-precedence`)
bit.getWhitespaceAroundOperator() = rel.getWhitespaceAroundOperator() and
// don't flag if the other operand is itself a comparison,
// since the nesting tends to be visually more obvious in such cases
not other instanceof Comparison and
// don't flag occurrences in minified code
not rel.getTopLevel().isMinified()
select rel,
"The '" + rel.getOperator() + "' operator binds more tightly than " + "'" + bit.getOperator() +
"', which may not be obvious in this case."

Просмотреть файл

@ -11,7 +11,8 @@
import javascript
from Directive d
where not d instanceof KnownDirective and
// but exclude attribute top-levels: `<a href="javascript:'some-attribute-string'">`
not (d.getParent() instanceof CodeInAttribute)
select d, "Unknown directive: '" + truncate(d.getDirectiveText(), 20, " ... (truncated)") + "'."
where
not d instanceof KnownDirective and
// but exclude attribute top-levels: `<a href="javascript:'some-attribute-string'">`
not (d.getParent() instanceof CodeInAttribute)
select d, "Unknown directive: '" + truncate(d.getDirectiveText(), 20, " ... (truncated)") + "'."

Просмотреть файл

@ -14,18 +14,18 @@ import javascript
import semmle.javascript.DefensiveProgramming
from DefensiveExpressionTest e, boolean cv
where e.getTheTestResult() = cv and
// whitelist
not (
// module environment detection
exists (VarAccess access, string name |
name = "exports" or name = "module" |
e.asExpr().(Internal::TypeofUndefinedTest).getOperand() = access and
access.getName() = name and
not exists (access.getVariable().getADeclaration())
)
or
// too benign in practice
e instanceof Internal::DefensiveInit
)
where
e.getTheTestResult() = cv and
// whitelist
not (
// module environment detection
exists(VarAccess access, string name | name = "exports" or name = "module" |
e.asExpr().(Internal::TypeofUndefinedTest).getOperand() = access and
access.getName() = name and
not exists(access.getVariable().getADeclaration())
)
or
// too benign in practice
e instanceof Internal::DefensiveInit
)
select e, "This guard always evaluates to " + cv + "."

Просмотреть файл

@ -26,9 +26,15 @@ class AssocNestedExpr extends BinaryExpr {
AssocNestedExpr() {
exists(BinaryExpr parent, int idx | this = parent.getChildExpr(idx) |
// +, *, &&, || and the bitwise operations are associative
((this instanceof AddExpr or this instanceof MulExpr or
this instanceof BitwiseExpr or this instanceof LogicalBinaryExpr) and
parent.getOperator() = this.getOperator())
(
(
this instanceof AddExpr or
this instanceof MulExpr or
this instanceof BitwiseExpr or
this instanceof LogicalBinaryExpr
) and
parent.getOperator() = this.getOperator()
)
or
// (x*y)/z = x*(y/z)
(this instanceof MulExpr and parent instanceof DivExpr and idx = 0)
@ -37,7 +43,8 @@ class AssocNestedExpr extends BinaryExpr {
(this instanceof DivExpr and parent instanceof ModExpr and idx = 0)
or
// (x+y)-z = x+(y-z)
(this instanceof AddExpr and parent instanceof SubExpr and idx = 0))
(this instanceof AddExpr and parent instanceof SubExpr and idx = 0)
)
}
}
@ -48,9 +55,13 @@ class AssocNestedExpr extends BinaryExpr {
class HarmlessNestedExpr extends BinaryExpr {
HarmlessNestedExpr() {
exists(BinaryExpr parent | this = parent.getAChildExpr() |
(parent instanceof Comparison and (this instanceof ArithmeticExpr or this instanceof ShiftExpr))
(
parent instanceof Comparison and
(this instanceof ArithmeticExpr or this instanceof ShiftExpr)
)
or
(parent instanceof LogicalExpr and this instanceof Comparison))
(parent instanceof LogicalExpr and this instanceof Comparison)
)
}
}
@ -66,7 +77,8 @@ predicate interestingNesting(BinaryExpr inner, BinaryExpr outer) {
}
from BinaryExpr inner, BinaryExpr outer
where interestingNesting(inner, outer) and
inner.getWhitespaceAroundOperator() > outer.getWhitespaceAroundOperator() and
not outer.getTopLevel().isMinified()
select outer, "Whitespace around nested operators contradicts precedence."
where
interestingNesting(inner, outer) and
inner.getWhitespaceAroundOperator() > outer.getWhitespaceAroundOperator() and
not outer.getTopLevel().isMinified()
select outer, "Whitespace around nested operators contradicts precedence."

Просмотреть файл

@ -15,8 +15,13 @@
import javascript
from JSDocParamTag parm, string missing
where // JSDoc comments in externs files are not necessarily meant for human readers, so don't complain
not parm.getFile().getATopLevel().isExterns() and
(not exists(parm.getName()) and missing = "name" or
(not exists(parm.getDescription()) or parm.getDescription().regexpMatch("\\s*")) and missing = "description")
select parm, "@param tag is missing " + missing + "."
where
// JSDoc comments in externs files are not necessarily meant for human readers, so don't complain
not parm.getFile().getATopLevel().isExterns() and
(
not exists(parm.getName()) and missing = "name"
or
(not exists(parm.getDescription()) or parm.getDescription().regexpMatch("\\s*")) and
missing = "description"
)
select parm, "@param tag is missing " + missing + "."

Просмотреть файл

@ -14,13 +14,14 @@
import javascript
from Function f, JSDoc doc, JSDocParamTag tag, string parmName
where doc = f.getDocumentation() and
tag = doc.getATag() and
parmName = tag.getName() and
tag.documentsSimpleName() and
not exists (f.getParameterByName(parmName)) and
// don't report functions without declared parameters that use `arguments`
not (f.getNumParameter() = 0 and f.usesArgumentsObject()) and
// don't report a violation in ambiguous cases
strictcount(JSDoc d | d = f.getDocumentation() and d.getATag() instanceof JSDocParamTag) = 1
select tag, "@param tag refers to non-existent parameter " + parmName + "."
where
doc = f.getDocumentation() and
tag = doc.getATag() and
parmName = tag.getName() and
tag.documentsSimpleName() and
not exists(f.getParameterByName(parmName)) and
// don't report functions without declared parameters that use `arguments`
not (f.getNumParameter() = 0 and f.usesArgumentsObject()) and
// don't report a violation in ambiguous cases
strictcount(JSDoc d | d = f.getDocumentation() and d.getATag() instanceof JSDocParamTag) = 1
select tag, "@param tag refers to non-existent parameter " + parmName + "."

Просмотреть файл

@ -14,13 +14,14 @@
import javascript
from Function f, Parameter parm, Variable v, JSDoc doc
where parm = f.getAParameter() and
doc = f.getDocumentation() and
v = parm.getAVariable() and
// at least one parameter is documented
exists(doc.getATag().(JSDocParamTag).getDocumentedParameter()) and
// but v is not
not doc.getATag().(JSDocParamTag).getDocumentedParameter() = v and
// don't report a violation in ambiguous cases
strictcount(JSDoc d | d = f.getDocumentation() and d.getATag() instanceof JSDocParamTag) = 1
select parm, "Parameter " + v.getName() + " is not documented."
where
parm = f.getAParameter() and
doc = f.getDocumentation() and
v = parm.getAVariable() and
// at least one parameter is documented
exists(doc.getATag().(JSDocParamTag).getDocumentedParameter()) and
// but v is not
not doc.getATag().(JSDocParamTag).getDocumentedParameter() = v and
// don't report a violation in ambiguous cases
strictcount(JSDoc d | d = f.getDocumentation() and d.getATag() instanceof JSDocParamTag) = 1
select parm, "Parameter " + v.getName() + " is not documented."

Просмотреть файл

@ -136,4 +136,4 @@ predicate knownTagType(string tp) {
from JSDocTag tag
where not knownTagType(tag.getTitle())
select tag, "Unknown tag type '" + tag.getTitle() + "'."
select tag, "Unknown tag type '" + tag.getTitle() + "'."

Просмотреть файл

@ -13,6 +13,9 @@
import javascript
from SlashStarComment c
where // use possessive quantifiers '*+' and '++' to avoid backtracking
c.getText().regexpMatch("\\s+(global|properties|property|jslint)\\s(\\s*+[a-zA-Z$_][a-zA-Z0-9$_]*+(\\s*+:\\s*+\\w++)?\\s*+,?)++\\s*")
select c, "JSLint directives must not have whitespace characters before the directive name."
where
// use possessive quantifiers '*+' and '++' to avoid backtracking
c
.getText()
.regexpMatch("\\s+(global|properties|property|jslint)\\s(\\s*+[a-zA-Z$_][a-zA-Z0-9$_]*+(\\s*+:\\s*+\\w++)?\\s*+,?)++\\s*")
select c, "JSLint directives must not have whitespace characters before the directive name."

Просмотреть файл

@ -13,13 +13,14 @@
import javascript
from JSLintDirective dir, string flag, string flags, string directive
where // a flag, optionally followed by a colon and a value, where the value may be
// a Boolean or a number
flag = "[a-zA-Z$_][a-zA-Z0-9$_]*(\\s*:\\s*(true|false|\\d+))?" and
// a non-empty, comma-separated list of flags
flags = "(" + flag + "\\s*,\\s*)*" + flag and
// a word (which is the directive's name), followed by a possibly empty list of flags
// note that there may be trailing whitespace, but no leading whitespace
directive = "\\s*\\w+\\s+(" + flags + ")?\\s*" and
not dir.getText().regexpMatch(directive)
select dir, "Malformed JSLint directive."
where
// a flag, optionally followed by a colon and a value, where the value may be
// a Boolean or a number
flag = "[a-zA-Z$_][a-zA-Z0-9$_]*(\\s*:\\s*(true|false|\\d+))?" and
// a non-empty, comma-separated list of flags
flags = "(" + flag + "\\s*,\\s*)*" + flag and
// a word (which is the directive's name), followed by a possibly empty list of flags
// note that there may be trailing whitespace, but no leading whitespace
directive = "\\s*\\w+\\s+(" + flags + ")?\\s*" and
not dir.getText().regexpMatch(directive)
select dir, "Malformed JSLint directive."

Просмотреть файл

@ -13,12 +13,13 @@
import javascript
from PropAccess acc, ArgumentsVariable args
where acc.getBase() = args.getAnAccess() and
acc.getPropertyName().regexpMatch("caller|callee") and
// don't flag cases where the variable can never contain an arguments object
not exists (Function fn | args = fn.getVariable()) and
not exists (Parameter p | args = p.getAVariable()) and
// arguments.caller/callee in strict mode causes runtime errors,
// this is covered by the query 'Use of call stack introspection in strict mode'
not acc.getContainer().isStrict()
select acc, "Avoid using arguments.caller and arguments.callee."
where
acc.getBase() = args.getAnAccess() and
acc.getPropertyName().regexpMatch("caller|callee") and
// don't flag cases where the variable can never contain an arguments object
not exists(Function fn | args = fn.getVariable()) and
not exists(Parameter p | args = p.getAVariable()) and
// arguments.caller/callee in strict mode causes runtime errors,
// this is covered by the query 'Use of call stack introspection in strict mode'
not acc.getContainer().isStrict()
select acc, "Avoid using arguments.caller and arguments.callee."

Просмотреть файл

@ -37,9 +37,9 @@ class EqOrSwitch extends ASTNode {
* of `case 1:` in `switch (y) { case 1: ... }` are `y` and `1`.
*/
Expr getAnOperand() {
result = ((EqualityTest)this).getAnOperand()
result = (this.(EqualityTest)).getAnOperand()
or
exists (Case c | c = this |
exists(Case c | c = this |
result = c.getSwitch().getExpr() or
result = c.getExpr()
)
@ -47,7 +47,12 @@ class EqOrSwitch extends ASTNode {
}
from EqOrSwitch et, TypeofExpr typeof, ConstantString str
where typeof = et.getAnOperand().getUnderlyingValue() and
str = et.getAnOperand().getUnderlyingValue() and
not str.getStringValue().regexpMatch("undefined|boolean|number|string|object|function|symbol|unknown|date|bigint")
select typeof, "The result of this 'typeof' expression is compared to '$@', but the two can never be equal.", str, str.getStringValue()
where
typeof = et.getAnOperand().getUnderlyingValue() and
str = et.getAnOperand().getUnderlyingValue() and
not str
.getStringValue()
.regexpMatch("undefined|boolean|number|string|object|function|symbol|unknown|date|bigint")
select typeof,
"The result of this 'typeof' expression is compared to '$@', but the two can never be equal.",
str, str.getStringValue()

Просмотреть файл

@ -15,4 +15,4 @@ import javascript
from Comment c
where c.getText().trim().matches("@cc\\_on%")
select c, "Do not use conditional comments."
select c, "Do not use conditional comments."

Просмотреть файл

@ -14,4 +14,4 @@
import javascript
from DebuggerStmt ds
select ds, "Do not use 'debugger'."
select ds, "Do not use 'debugger'."

Просмотреть файл

@ -15,4 +15,4 @@ import javascript
from DeleteExpr del
where not del.getOperand().stripParens() instanceof PropAccess
select del, "Only properties should be deleted."
select del, "Only properties should be deleted."

Просмотреть файл

@ -22,9 +22,7 @@ import javascript
class OmittedArrayElement extends ArrayExpr {
int idx;
OmittedArrayElement() {
idx = min(int i | elementIsOmitted(i))
}
OmittedArrayElement() { idx = min(int i | elementIsOmitted(i)) }
/**
* Holds if this element is at the specified location.
@ -33,11 +31,16 @@ class OmittedArrayElement extends ArrayExpr {
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
exists (Token pre, Location before, Location after |
idx = 0 and pre = getFirstToken() or
pre = getElement(idx-1).getLastToken().getNextToken() |
before = pre.getLocation() and after = pre.getNextToken().getLocation() and
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(Token pre, Location before, Location after |
idx = 0 and pre = getFirstToken()
or
pre = getElement(idx - 1).getLastToken().getNextToken()
|
before = pre.getLocation() and
after = pre.getNextToken().getLocation() and
before.hasLocationInfo(filepath, startline, startcolumn, _, _) and
after.hasLocationInfo(_, _, _, endline, endcolumn)
)
@ -45,4 +48,4 @@ class OmittedArrayElement extends ArrayExpr {
}
from OmittedArrayElement ae
select ae, "Avoid omitted array elements."
select ae, "Avoid omitted array elements."

Просмотреть файл

@ -17,28 +17,22 @@ import javascript
* A call to `new Function(...)`.
*/
class NewFunction extends DataFlow::NewNode {
NewFunction() {
this = DataFlow::globalVarRef("Function").getAnInvocation()
}
NewFunction() { this = DataFlow::globalVarRef("Function").getAnInvocation() }
}
/**
* A call to `eval`.
*/
class EvalCall extends DataFlow::CallNode {
EvalCall() {
this = DataFlow::globalVarRef("eval").getACall()
}
EvalCall() { this = DataFlow::globalVarRef("eval").getACall() }
}
/**
* A call to `new Function(...)` or `eval`.
*/
class EvalUse extends DataFlow::Node {
EvalUse() {
this instanceof NewFunction or this instanceof EvalCall
}
EvalUse() { this instanceof NewFunction or this instanceof EvalCall }
}
from EvalUse eval
select eval, "Do not use eval or the Function constructor."
select eval, "Do not use eval or the Function constructor."

Просмотреть файл

@ -19,16 +19,25 @@ import javascript
* is the recommended replacement.
*/
predicate deprecated_feature(ASTNode nd, string type, string replacement) {
exists (FunctionExpr fe | fe = nd and fe.getBody() instanceof Expr |
exists(FunctionExpr fe | fe = nd and fe.getBody() instanceof Expr |
type = "expression closures" and replacement = "arrow expressions"
) or
nd instanceof LegacyLetExpr and type = "let expressions" and replacement = "let declarations" or
nd instanceof LegacyLetStmt and type = "let statements" and replacement = "let declarations" or
nd instanceof ForEachStmt and type = "for each statements" and replacement = "for of statements" or
nd.(ComprehensionExpr).isPostfix() and type = "postfix comprehensions" and replacement = "prefix comprehensions" or
nd.(ExprStmt).isDoubleColonMethod(_, _, _) and type = "double colon method declarations" and replacement = "standard method definitions"
)
or
nd instanceof LegacyLetExpr and type = "let expressions" and replacement = "let declarations"
or
nd instanceof LegacyLetStmt and type = "let statements" and replacement = "let declarations"
or
nd instanceof ForEachStmt and type = "for each statements" and replacement = "for of statements"
or
nd.(ComprehensionExpr).isPostfix() and
type = "postfix comprehensions" and
replacement = "prefix comprehensions"
or
nd.(ExprStmt).isDoubleColonMethod(_, _, _) and
type = "double colon method declarations" and
replacement = "standard method definitions"
}
from ASTNode depr, string type, string replacement
where deprecated_feature(depr, type, replacement)
select depr, "Use " + replacement + " instead of " + type + "."
select depr, "Use " + replacement + " instead of " + type + "."

Просмотреть файл

@ -15,4 +15,4 @@
import javascript
from HtmlLineComment c
select c, "Do not use HTML comments."
select c, "Do not use HTML comments."

Просмотреть файл

@ -19,7 +19,8 @@ import javascript
predicate calls(DataFlow::InvokeNode cs, Function callee, string how) {
callee = cs.getACallee() and
(
cs instanceof DataFlow::CallNode and not cs.asExpr() instanceof SuperCall and
cs instanceof DataFlow::CallNode and
not cs.asExpr() instanceof SuperCall and
how = "as a function"
or
cs instanceof DataFlow::NewNode and
@ -48,17 +49,16 @@ predicate illegalInvocation(DataFlow::InvokeNode cs, Function callee, string cal
*/
predicate isCallToFunction(DataFlow::InvokeNode ce) {
ce instanceof DataFlow::CallNode and
exists (Function f | f = ce.getACallee() |
not f instanceof Constructor
)
exists(Function f | f = ce.getACallee() | not f instanceof Constructor)
}
from DataFlow::InvokeNode cs, Function callee, string calleeDesc, string how
where illegalInvocation(cs, callee, calleeDesc, how) and
// filter out some easy cases
not isCallToFunction(cs) and
// conservatively only flag call sites where _all_ callees are illegal
forex (Function otherCallee | otherCallee = cs.getACallee() |
illegalInvocation(cs, otherCallee, _, _)
)
where
illegalInvocation(cs, callee, calleeDesc, how) and
// filter out some easy cases
not isCallToFunction(cs) and
// conservatively only flag call sites where _all_ callees are illegal
forex(Function otherCallee | otherCallee = cs.getACallee() |
illegalInvocation(cs, otherCallee, _, _)
)
select cs, "Illegal invocation of $@ " + how + ".", callee, calleeDesc

Просмотреть файл

@ -22,7 +22,7 @@ import semmle.javascript.RestrictedLocations
* have to call itself using `new`, so that is what we look for.
*/
predicate guardsAgainstMissingNew(Function f) {
exists (DataFlow::NewNode new |
exists(DataFlow::NewNode new |
new.asExpr().getEnclosingFunction() = f and
f = new.getACallee()
)
@ -38,7 +38,8 @@ predicate calls(DataFlow::InvokeNode cs, Function callee, int imprecision) {
callee = cs.getACallee() and
(
// if global flow was used to derive the callee, we may be imprecise
if cs.isIndefinite("global") then
if cs.isIndefinite("global")
then
// callees within the same file are probably genuine
callee.getFile() = cs.getFile() and imprecision = 0
or
@ -65,9 +66,11 @@ Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) {
not cs.isUncertain() and
not whitelistedCall(cs) and
not whitelistedCallee(result) and
(cs instanceof DataFlow::NewNode and isNew = true
or
cs instanceof DataFlow::CallNode and isNew = false)
(
cs instanceof DataFlow::NewNode and isNew = true
or
cs instanceof DataFlow::CallNode and isNew = false
)
}
/**
@ -76,10 +79,12 @@ Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) {
*/
predicate whitelistedCallee(Function f) {
// externs are special, so don't flag them
f.inExternsFile() or
f.inExternsFile()
or
// illegal constructor calls are flagged by query 'Illegal invocation',
// so don't flag them
f instanceof Constructor or
f instanceof Constructor
or
// if `f` itself guards against missing `new`, don't flag it
guardsAgainstMissingNew(f)
}
@ -90,7 +95,8 @@ predicate whitelistedCallee(Function f) {
*/
predicate whitelistedCall(DataFlow::CallNode call) {
// super constructor calls behave more like `new`, so don't flag them
call.asExpr() instanceof SuperCall or
call.asExpr() instanceof SuperCall
or
// don't flag if there is a receiver object
exists(call.getReceiver())
}
@ -102,14 +108,19 @@ predicate whitelistedCall(DataFlow::CallNode call) {
*/
DataFlow::InvokeNode getFirstInvocation(Function f, boolean isNew) {
result = min(DataFlow::InvokeNode invk, string path, int line, int col |
f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _) |
invk order by path, line, col
)
f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _)
|
invk
order by
path, line, col
)
}
from Function f, DataFlow::NewNode new, DataFlow::CallNode call
where new = getFirstInvocation(f, true) and
call = getFirstInvocation(f, false)
select (FirstLineOf)f, capitalize(f.describe()) + " is sometimes invoked as a constructor " +
"(for example $@), and sometimes as a normal function (for example $@).",
new, "here", call, "here"
where
new = getFirstInvocation(f, true) and
call = getFirstInvocation(f, false)
select f.(FirstLineOf),
capitalize(f.describe()) + " is sometimes invoked as a constructor " +
"(for example $@), and sometimes as a normal function (for example $@).", new, "here", call,
"here"

Просмотреть файл

@ -32,8 +32,7 @@ predicate isProto(DataFlow::AnalyzedNode e) {
}
from DataFlow::AnalyzedNode proto
where isProto(proto) and
forex (InferredType tp | tp = proto.getAType() |
tp instanceof PrimitiveType and tp != TTNull()
)
where
isProto(proto) and
forex(InferredType tp | tp = proto.getAType() | tp instanceof PrimitiveType and tp != TTNull())
select proto, "Values of type " + proto.ppTypes() + " cannot be used as prototypes."

Просмотреть файл

@ -25,14 +25,15 @@ class Jump extends Stmt {
/** Gets the target to which this jump refers. */
Stmt getTarget() {
result = ((BreakOrContinueStmt)this).getTarget() or
result = ((Function)((ReturnStmt)this).getContainer()).getBody()
result = (this.(BreakOrContinueStmt)).getTarget() or
result = ((this.(ReturnStmt)).getContainer().(Function)).getBody()
}
}
from TryStmt try, BlockStmt finally, Jump jump
where finally = try.getFinally() and
jump.getContainer() = try.getContainer() and
jump.getParentStmt+() = finally and
finally.getParentStmt+() = jump.getTarget()
select jump, "This statement jumps out of the finally block, potentially hiding an exception."
where
finally = try.getFinally() and
jump.getContainer() = try.getContainer() and
jump.getParentStmt+() = finally and
finally.getParentStmt+() = jump.getTarget()
select jump, "This statement jumps out of the finally block, potentially hiding an exception."

Просмотреть файл

@ -17,17 +17,15 @@ import javascript
/**
* Gets an access to `array.length`.
*/
PropAccess arrayLen(Variable array) {
result.accesses(array.getAnAccess(), "length")
}
PropAccess arrayLen(Variable array) { result.accesses(array.getAnAccess(), "length") }
/**
* Gets a condition that checks that `index` is less than or equal to `array.length`.
*/
ConditionGuardNode getLengthLEGuard(Variable index, Variable array) {
exists (RelationalComparison cmp |
cmp instanceof GEExpr or cmp instanceof LEExpr |
cmp = result.getTest() and result.getOutcome() = true and
exists(RelationalComparison cmp | cmp instanceof GEExpr or cmp instanceof LEExpr |
cmp = result.getTest() and
result.getOutcome() = true and
cmp.getGreaterOperand() = arrayLen(array) and
cmp.getLesserOperand() = index.getAnAccess()
)
@ -37,8 +35,9 @@ ConditionGuardNode getLengthLEGuard(Variable index, Variable array) {
* Gets a condition that checks that `index` is not equal to `array.length`.
*/
ConditionGuardNode getLengthNEGuard(Variable index, Variable array) {
exists (EqualityTest eq |
eq = result.getTest() and result.getOutcome() = eq.getPolarity().booleanNot() and
exists(EqualityTest eq |
eq = result.getTest() and
result.getOutcome() = eq.getPolarity().booleanNot() and
eq.hasOperands(index.getAnAccess(), arrayLen(array))
)
}
@ -54,12 +53,14 @@ predicate elementRead(IndexExpr ea, Variable array, Variable index, BasicBlock b
}
from ConditionGuardNode cond, Variable array, Variable index, IndexExpr ea, BasicBlock bb
where // there is a comparison `index <= array.length`
cond = getLengthLEGuard(index, array) and
// there is a read from `array[index]`
elementRead(ea, array, index, bb) and
// and the read is guarded by the comparison
cond.dominates(bb) and
// but the read is not guarded by another check that `index != array.length`
not getLengthNEGuard(index, array).dominates(bb)
select cond.getTest(), "Off-by-one index comparison against length may lead to out-of-bounds $@.", ea, "read"
where
// there is a comparison `index <= array.length`
cond = getLengthLEGuard(index, array) and
// there is a read from `array[index]`
elementRead(ea, array, index, bb) and
// and the read is guarded by the comparison
cond.dominates(bb) and
// but the read is not guarded by another check that `index != array.length`
not getLengthNEGuard(index, array).dominates(bb)
select cond.getTest(), "Off-by-one index comparison against length may lead to out-of-bounds $@.",
ea, "read"

Просмотреть файл

@ -13,6 +13,7 @@
import javascript
from StringLiteral sl, Location l
where l = sl.getLocation() and
l.getStartLine() != l.getEndLine()
select sl, "Avoid multi-line string literals."
where
l = sl.getLocation() and
l.getStartLine() != l.getEndLine()
select sl, "Avoid multi-line string literals."

Просмотреть файл

@ -15,8 +15,11 @@
import javascript
from BindingPattern p, string n, VarDecl v, VarDecl w
where v = p.getABindingVarRef() and w = p.getABindingVarRef() and
v.getName() = n and w.getName() = n and
v != w and
v.getLocation().startsBefore(w.getLocation())
where
v = p.getABindingVarRef() and
w = p.getABindingVarRef() and
v.getName() = n and
w.getName() = n and
v != w and
v.getLocation().startsBefore(w.getLocation())
select w, "Repeated binding of pattern variable '" + n + "' previously bound $@.", v, "here"

Просмотреть файл

@ -14,4 +14,4 @@ import javascript
from NumberLiteral nl
where nl.getRawValue().regexpMatch("0\\d+")
select nl, "Do not use octal literals."
select nl, "Do not use octal literals."

Просмотреть файл

@ -22,12 +22,14 @@ string describeProp(DataFlow::PropWrite pwn) {
}
from DataFlow::PropWrite pwn, DataFlow::AnalyzedNode base
where base = pwn.getBase() and
forex (InferredType tp | tp = base.getAType() |
tp instanceof PrimitiveType and
// assignments on `null` and `undefined` are covered by
// the query 'Property access on null or undefined'
tp != TTNull() and tp != TTUndefined()
)
select base, "Assignment to " + describeProp(pwn) +
" of a primitive value with type " + base.ppTypes() + "."
where
base = pwn.getBase() and
forex(InferredType tp | tp = base.getAType() |
tp instanceof PrimitiveType and
// assignments on `null` and `undefined` are covered by
// the query 'Property access on null or undefined'
tp != TTNull() and
tp != TTUndefined()
)
select base,
"Assignment to " + describeProp(pwn) + " of a primitive value with type " + base.ppTypes() + "."

Просмотреть файл

@ -13,9 +13,12 @@
import javascript
from Identifier id
where id.getName().regexpMatch("class|const|enum|export|extends|import|super|implements|interface|let|package|private|protected|public|static|yield") and
not exists(DotExpr de | id = de.getProperty()) and
not exists(Property prop | id = prop.getNameExpr()) and
// exclude JSX attribute names
not exists(JSXElement e | id = e.getAnAttribute().getNameExpr())
select id, "Identifier name is a reserved word."
where
id
.getName()
.regexpMatch("class|const|enum|export|extends|import|super|implements|interface|let|package|private|protected|public|static|yield") and
not exists(DotExpr de | id = de.getProperty()) and
not exists(Property prop | id = prop.getNameExpr()) and
// exclude JSX attribute names
not exists(JSXElement e | id = e.getAnAttribute().getNameExpr())
select id, "Identifier name is a reserved word."

Просмотреть файл

@ -20,23 +20,24 @@ import semmle.javascript.RestrictedLocations
* is `false`.
*/
predicate asi(StmtContainer sc, Stmt s, boolean asi) {
exists (TopLevel tl | tl = sc.getTopLevel() |
not tl instanceof EventHandlerCode and
not tl.isExterns()
) and
sc = s.getContainer() and
s.isSubjectToSemicolonInsertion() and
(if s.hasSemicolonInserted() then asi = true else asi = false)
exists(TopLevel tl | tl = sc.getTopLevel() |
not tl instanceof EventHandlerCode and
not tl.isExterns()
) and
sc = s.getContainer() and
s.isSubjectToSemicolonInsertion() and
(if s.hasSemicolonInserted() then asi = true else asi = false)
}
from Stmt s, StmtContainer sc, string sctype, float asi, int nstmt, int perc
where s.hasSemicolonInserted() and
sc = s.getContainer() and
(if sc instanceof Function then sctype = "function" else sctype = "script") and
asi = strictcount(Stmt ss | asi(sc, ss, true)) and
nstmt = strictcount(Stmt ss | asi(sc, ss, _)) and
perc = ((1-asi/nstmt)*100).floor() and
perc >= 90
select (LastLineOf)s, "Avoid automated semicolon insertion " +
"(" + perc + "% of all statements in $@ have an explicit semicolon).",
sc, "the enclosing " + sctype
where
s.hasSemicolonInserted() and
sc = s.getContainer() and
(if sc instanceof Function then sctype = "function" else sctype = "script") and
asi = strictcount(Stmt ss | asi(sc, ss, true)) and
nstmt = strictcount(Stmt ss | asi(sc, ss, _)) and
perc = ((1 - asi / nstmt) * 100).floor() and
perc >= 90
select s.(LastLineOf),
"Avoid automated semicolon insertion " + "(" + perc +
"% of all statements in $@ have an explicit semicolon).", sc, "the enclosing " + sctype

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше