зеркало из https://github.com/github/codeql.git
JavaScript: Autoformat all QL files.
This commit is contained in:
Родитель
aa6b89dc34
Коммит
31bb39a810
|
@ -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
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче