diff --git a/javascript/ql/src/AlertSuppression.ql b/javascript/ql/src/AlertSuppression.ql index ca75fc52bd7b..9bd6bbbf8c1e 100644 --- a/javascript/ql/src/AlertSuppression.ql +++ b/javascript/ql/src/AlertSuppression.ql @@ -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 diff --git a/javascript/ql/src/AngularJS/DeadAngularJSEventListener.ql b/javascript/ql/src/AngularJS/DeadAngularJSEventListener.ql index 4ae4ba7e8534..54ec854bae4a 100644 --- a/javascript/ql/src/AngularJS/DeadAngularJSEventListener.ql +++ b/javascript/ql/src/AngularJS/DeadAngularJSEventListener.ql @@ -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." diff --git a/javascript/ql/src/AngularJS/DependencyMismatch.ql b/javascript/ql/src/AngularJS/DependencyMismatch.ql index bb620167af86..314c0a12320f 100644 --- a/javascript/ql/src/AngularJS/DependencyMismatch.ql +++ b/javascript/ql/src/AngularJS/DependencyMismatch.ql @@ -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 \ No newline at end of file +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 diff --git a/javascript/ql/src/AngularJS/DisablingSce.ql b/javascript/ql/src/AngularJS/DisablingSce.ql index 52287d4adb23..c4aa3822d041 100644 --- a/javascript/ql/src/AngularJS/DisablingSce.ql +++ b/javascript/ql/src/AngularJS/DisablingSce.ql @@ -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." diff --git a/javascript/ql/src/AngularJS/DoubleCompilation.ql b/javascript/ql/src/AngularJS/DoubleCompilation.ql index bc41b12d866b..74624e51c6ec 100644 --- a/javascript/ql/src/AngularJS/DoubleCompilation.ql +++ b/javascript/ql/src/AngularJS/DoubleCompilation.ql @@ -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 + "'." diff --git a/javascript/ql/src/AngularJS/DuplicateDependency.ql b/javascript/ql/src/AngularJS/DuplicateDependency.ql index 35b39391ad93..c7d7e0c55d2a 100644 --- a/javascript/ql/src/AngularJS/DuplicateDependency.ql +++ b/javascript/ql/src/AngularJS/DuplicateDependency.ql @@ -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 diff --git a/javascript/ql/src/AngularJS/IncompatibleService.ql b/javascript/ql/src/AngularJS/IncompatibleService.ql index 90c4c524aa17..b92ee93407f9 100644 --- a/javascript/ql/src/AngularJS/IncompatibleService.ql +++ b/javascript/ql/src/AngularJS/IncompatibleService.ql @@ -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 + "." diff --git a/javascript/ql/src/AngularJS/InsecureUrlWhitelist.ql b/javascript/ql/src/AngularJS/InsecureUrlWhitelist.ql index 06371bdc3a7b..d363de41552c 100644 --- a/javascript/ql/src/AngularJS/InsecureUrlWhitelist.ql +++ b/javascript/ql/src/AngularJS/InsecureUrlWhitelist.ql @@ -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 + ) 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() diff --git a/javascript/ql/src/AngularJS/MissingExplicitInjection.ql b/javascript/ql/src/AngularJS/MissingExplicitInjection.ql index 32c7d1a2f124..f7ff51fb5475 100644 --- a/javascript/ql/src/AngularJS/MissingExplicitInjection.ql +++ b/javascript/ql/src/AngularJS/MissingExplicitInjection.ql @@ -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" diff --git a/javascript/ql/src/AngularJS/RepeatedInjection.ql b/javascript/ql/src/AngularJS/RepeatedInjection.ql index d092a5b2a9fb..246fbbdbd1cf 100644 --- a/javascript/ql/src/AngularJS/RepeatedInjection.ql +++ b/javascript/ql/src/AngularJS/RepeatedInjection.ql @@ -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" diff --git a/javascript/ql/src/AngularJS/UnusedAngularDependency.ql b/javascript/ql/src/AngularJS/UnusedAngularDependency.ql index 580beff03239..8358b2382c0c 100644 --- a/javascript/ql/src/AngularJS/UnusedAngularDependency.ql +++ b/javascript/ql/src/AngularJS/UnusedAngularDependency.ql @@ -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 diff --git a/javascript/ql/src/AngularJS/UseNgSrc.ql b/javascript/ql/src/AngularJS/UseNgSrc.ql index 63c9a105f424..333b36722f53 100644 --- a/javascript/ql/src/AngularJS/UseNgSrc.ql +++ b/javascript/ql/src/AngularJS/UseNgSrc.ql @@ -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 + "'." diff --git a/javascript/ql/src/DOM/AmbiguousIdAttribute.ql b/javascript/ql/src/DOM/AmbiguousIdAttribute.ql index 502f2ffc0417..583ebddb672f 100644 --- a/javascript/ql/src/DOM/AmbiguousIdAttribute.ql +++ b/javascript/ql/src/DOM/AmbiguousIdAttribute.ql @@ -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 ) } diff --git a/javascript/ql/src/DOM/ConflictingAttributes.ql b/javascript/ql/src/DOM/ConflictingAttributes.ql index 9e4cf1cdccd0..44d29000fc4c 100644 --- a/javascript/ql/src/DOM/ConflictingAttributes.ql +++ b/javascript/ql/src/DOM/ConflictingAttributes.ql @@ -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" \ No newline at end of file +select earlier, + "This attribute has the same name as $@ of the same element, " + "but a different value.", later, + "another attribute" diff --git a/javascript/ql/src/DOM/DuplicateAttributes.ql b/javascript/ql/src/DOM/DuplicateAttributes.ql index 02272bdd6092..b88582c0c5a5 100644 --- a/javascript/ql/src/DOM/DuplicateAttributes.ql +++ b/javascript/ql/src/DOM/DuplicateAttributes.ql @@ -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() diff --git a/javascript/ql/src/DOM/MalformedIdAttribute.ql b/javascript/ql/src/DOM/MalformedIdAttribute.ql index fd202a3f343d..d9a99aa1937e 100644 --- a/javascript/ql/src/DOM/MalformedIdAttribute.ql +++ b/javascript/ql/src/DOM/MalformedIdAttribute.ql @@ -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 + "." \ No newline at end of file +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 + "." diff --git a/javascript/ql/src/DOM/PseudoEval.ql b/javascript/ql/src/DOM/PseudoEval.ql index f0b9bbcff7aa..d173c66f96a1 100644 --- a/javascript/ql/src/DOM/PseudoEval.ql +++ b/javascript/ql/src/DOM/PseudoEval.ql @@ -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. */ diff --git a/javascript/ql/src/DOM/TargetBlank.ql b/javascript/ql/src/DOM/TargetBlank.ql index cc48d548074e..abfb44f23b5a 100644 --- a/javascript/ql/src/DOM/TargetBlank.ql +++ b/javascript/ql/src/DOM/TargetBlank.ql @@ -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." diff --git a/javascript/ql/src/Declarations/ArgumentsRedefined.ql b/javascript/ql/src/Declarations/ArgumentsRedefined.ql index 8775f525d097..fdd70ac1479f 100644 --- a/javascript/ql/src/Declarations/ArgumentsRedefined.ql +++ b/javascript/ql/src/Declarations/ArgumentsRedefined.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/Declarations/AssignmentToConst.ql b/javascript/ql/src/Declarations/AssignmentToConst.ql index 22699315df47..e9efb03fd838 100644 --- a/javascript/ql/src/Declarations/AssignmentToConst.ql +++ b/javascript/ql/src/Declarations/AssignmentToConst.ql @@ -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" diff --git a/javascript/ql/src/Declarations/BuiltinRedefined.ql b/javascript/ql/src/Declarations/BuiltinRedefined.ql index 1d500787b238..327dfd9f06e8 100644 --- a/javascript/ql/src/Declarations/BuiltinRedefined.ql +++ b/javascript/ql/src/Declarations/BuiltinRedefined.ql @@ -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 + "." \ No newline at end of file +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 + "." diff --git a/javascript/ql/src/Declarations/ClobberingVarInit.ql b/javascript/ql/src/Declarations/ClobberingVarInit.ql index 38e6904553aa..3c99517b4772 100644 --- a/javascript/ql/src/Declarations/ClobberingVarInit.ql +++ b/javascript/ql/src/Declarations/ClobberingVarInit.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Declarations/ConflictingFunctions.ql b/javascript/ql/src/Declarations/ConflictingFunctions.ql index bfbbdd1cdc28..d808627c37f6 100644 --- a/javascript/ql/src/Declarations/ConflictingFunctions.ql +++ b/javascript/ql/src/Declarations/ConflictingFunctions.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Declarations/DeadStoreOfGlobal.ql b/javascript/ql/src/Declarations/DeadStoreOfGlobal.ql index b50975de6d69..2e6b54fc3019 100644 --- a/javascript/ql/src/Declarations/DeadStoreOfGlobal.ql +++ b/javascript/ql/src/Declarations/DeadStoreOfGlobal.ql @@ -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." diff --git a/javascript/ql/src/Declarations/DeadStoreOfLocal.ql b/javascript/ql/src/Declarations/DeadStoreOfLocal.ql index 3299f38e93ec..c996c74a723e 100644 --- a/javascript/ql/src/Declarations/DeadStoreOfLocal.ql +++ b/javascript/ql/src/Declarations/DeadStoreOfLocal.ql @@ -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." diff --git a/javascript/ql/src/Declarations/DeadStoreOfProperty.ql b/javascript/ql/src/Declarations/DeadStoreOfProperty.ql index 393dfd7068e0..ab5eaa4f8777 100644 --- a/javascript/ql/src/Declarations/DeadStoreOfProperty.ql +++ b/javascript/ql/src/Declarations/DeadStoreOfProperty.ql @@ -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" diff --git a/javascript/ql/src/Declarations/DeclBeforeUse.ql b/javascript/ql/src/Declarations/DeclBeforeUse.ql index 985a1a6be7ba..49a7a3f01c83 100644 --- a/javascript/ql/src/Declarations/DeclBeforeUse.ql +++ b/javascript/ql/src/Declarations/DeclBeforeUse.ql @@ -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" diff --git a/javascript/ql/src/Declarations/Declarations.qll b/javascript/ql/src/Declarations/Declarations.qll index 15a1f8549198..1ebf94f711a3 100644 --- a/javascript/ql/src/Declarations/Declarations.qll +++ b/javascript/ql/src/Declarations/Declarations.qll @@ -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() + ) } diff --git a/javascript/ql/src/Declarations/DefaultArgumentReferencesNestedFunction.ql b/javascript/ql/src/Declarations/DefaultArgumentReferencesNestedFunction.ql index f64c1850a595..cd13314095c1 100644 --- a/javascript/ql/src/Declarations/DefaultArgumentReferencesNestedFunction.ql +++ b/javascript/ql/src/Declarations/DefaultArgumentReferencesNestedFunction.ql @@ -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() \ No newline at end of file +where + accessToNestedFunction(va, g, f) and + va.getParentExpr*() = f.getAParameter().getDefault() +select va, "This expression refers to $@ before it is defined.", g, g.getName() diff --git a/javascript/ql/src/Declarations/DuplicateVarDecl.ql b/javascript/ql/src/Declarations/DuplicateVarDecl.ql index 0077361d60f8..048eb15e9f49 100644 --- a/javascript/ql/src/Declarations/DuplicateVarDecl.ql +++ b/javascript/ql/src/Declarations/DuplicateVarDecl.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Declarations/IneffectiveParameterType.ql b/javascript/ql/src/Declarations/IneffectiveParameterType.ql index 68e3f5ea5aac..8e4eb82bcf89 100644 --- a/javascript/ql/src/Declarations/IneffectiveParameterType.ql +++ b/javascript/ql/src/Declarations/IneffectiveParameterType.ql @@ -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 diff --git a/javascript/ql/src/Declarations/InefficientMethodDefinition.ql b/javascript/ql/src/Declarations/InefficientMethodDefinition.ql index a3c700c61611..fac9046e631a 100644 --- a/javascript/ql/src/Declarations/InefficientMethodDefinition.ql +++ b/javascript/ql/src/Declarations/InefficientMethodDefinition.ql @@ -18,7 +18,7 @@ import semmle.javascript.RestrictedLocations * Holds if `stmt` is of the form `this. = ;`. */ 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." \ No newline at end of file +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." diff --git a/javascript/ql/src/Declarations/MissingThisQualifier.ql b/javascript/ql/src/Declarations/MissingThisQualifier.ql index 9a57ec987f89..c18388cb80c9 100644 --- a/javascript/ql/src/Declarations/MissingThisQualifier.ql +++ b/javascript/ql/src/Declarations/MissingThisQualifier.ql @@ -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() diff --git a/javascript/ql/src/Declarations/MissingVarDecl.ql b/javascript/ql/src/Declarations/MissingVarDecl.ql index 4a180408cb5b..8b72adcd6188 100644 --- a/javascript/ql/src/Declarations/MissingVarDecl.ql +++ b/javascript/ql/src/Declarations/MissingVarDecl.ql @@ -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." \ No newline at end of file +select getFirstAccessIn(gv, f), + "Variable " + gv.getName() + " is used like a local variable, but is missing a declaration." diff --git a/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql b/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql index d322b13bbe3b..c41b514737a4 100644 --- a/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql +++ b/javascript/ql/src/Declarations/MixedStaticInstanceThisAccess.ql @@ -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() diff --git a/javascript/ql/src/Declarations/RedeclaredVariable.ql b/javascript/ql/src/Declarations/RedeclaredVariable.ql index 08ef0760a52c..eb3b7261efaa 100644 --- a/javascript/ql/src/Declarations/RedeclaredVariable.ql +++ b/javascript/ql/src/Declarations/RedeclaredVariable.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Declarations/TemporalDeadZone.ql b/javascript/ql/src/Declarations/TemporalDeadZone.ql index 7747b6110812..dbb5f7275d35 100644 --- a/javascript/ql/src/Declarations/TemporalDeadZone.ql +++ b/javascript/ql/src/Declarations/TemporalDeadZone.ql @@ -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() diff --git a/javascript/ql/src/Declarations/TooManyParameters.ql b/javascript/ql/src/Declarations/TooManyParameters.ql index 062675a047a0..7cdae429322e 100644 --- a/javascript/ql/src/Declarations/TooManyParameters.ql +++ b/javascript/ql/src/Declarations/TooManyParameters.ql @@ -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() + ")." \ No newline at end of file +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() + ")." diff --git a/javascript/ql/src/Declarations/UniqueParameterNames.ql b/javascript/ql/src/Declarations/UniqueParameterNames.ql index c30fe5872af6..baaed809c4df 100644 --- a/javascript/ql/src/Declarations/UniqueParameterNames.ql +++ b/javascript/ql/src/Declarations/UniqueParameterNames.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Declarations/UniquePropertyNames.ql b/javascript/ql/src/Declarations/UniquePropertyNames.ql index 2485006cbb41..e98857945ad8 100644 --- a/javascript/ql/src/Declarations/UniquePropertyNames.ql +++ b/javascript/ql/src/Declarations/UniquePropertyNames.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Declarations/UnstableCyclicImport.ql b/javascript/ql/src/Declarations/UnstableCyclicImport.ql index d0c8bb7e0f9c..2fb94eb054ac 100644 --- a/javascript/ql/src/Declarations/UnstableCyclicImport.ql +++ b/javascript/ql/src/Declarations/UnstableCyclicImport.ql @@ -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() diff --git a/javascript/ql/src/Declarations/UnusedParameter.qll b/javascript/ql/src/Declarations/UnusedParameter.qll index eeceba9222fa..b4fb5cb578a7 100644 --- a/javascript/ql/src/Declarations/UnusedParameter.qll +++ b/javascript/ql/src/Declarations/UnusedParameter.qll @@ -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() ) ) diff --git a/javascript/ql/src/Declarations/UnusedVariable.ql b/javascript/ql/src/Declarations/UnusedVariable.ql index 5247e5b09bd8..aa361582c10c 100644 --- a/javascript/ql/src/Declarations/UnusedVariable.ql +++ b/javascript/ql/src/Declarations/UnusedVariable.ql @@ -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 diff --git a/javascript/ql/src/Electron/AllowRunningInsecureContent.ql b/javascript/ql/src/Electron/AllowRunningInsecureContent.ql index d9680985fbec..b6d1141cfcb4 100644 --- a/javascript/ql/src/Electron/AllowRunningInsecureContent.ql +++ b/javascript/ql/src/Electron/AllowRunningInsecureContent.ql @@ -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." diff --git a/javascript/ql/src/Electron/DisablingWebSecurity.ql b/javascript/ql/src/Electron/DisablingWebSecurity.ql index 0653ab0ddbc3..792759d9cf68 100644 --- a/javascript/ql/src/Electron/DisablingWebSecurity.ql +++ b/javascript/ql/src/Electron/DisablingWebSecurity.ql @@ -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." \ No newline at end of file +where + webSecurity = preferences.getAPropertyWrite("webSecurity") and + webSecurity.getRhs().mayHaveBooleanValue(false) +select webSecurity, "Disabling webSecurity is strongly discouraged." diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.ql b/javascript/ql/src/Electron/EnablingNodeIntegration.ql index d8e764012a20..e0ef84a20153 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.ql +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.ql @@ -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) \ No newline at end of file +select preferences, getNodeIntegrationWarning(preferences) diff --git a/javascript/ql/src/Expressions/BadParityCheck.ql b/javascript/ql/src/Expressions/BadParityCheck.ql index c109c411ae4a..b725d20f0905 100644 --- a/javascript/ql/src/Expressions/BadParityCheck.ql +++ b/javascript/ql/src/Expressions/BadParityCheck.ql @@ -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." diff --git a/javascript/ql/src/Expressions/BitwiseSignCheck.ql b/javascript/ql/src/Expressions/BitwiseSignCheck.ql index f3b615a1e89f..e28c6db2d914 100644 --- a/javascript/ql/src/Expressions/BitwiseSignCheck.ql +++ b/javascript/ql/src/Expressions/BitwiseSignCheck.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Expressions/Clones.qll b/javascript/ql/src/Expressions/Clones.qll index 54a996717717..cc68defb23cc 100644 --- a/javascript/ql/src/Expressions/Clones.qll +++ b/javascript/ql/src/Expressions/Clones.qll @@ -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 diff --git a/javascript/ql/src/Expressions/CompareIdenticalValues.ql b/javascript/ql/src/Expressions/CompareIdenticalValues.ql index 3bf1999f0052..9ad25e5ab107 100644 --- a/javascript/ql/src/Expressions/CompareIdenticalValues.ql +++ b/javascript/ql/src/Expressions/CompareIdenticalValues.ql @@ -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() diff --git a/javascript/ql/src/Expressions/ComparisonWithNaN.ql b/javascript/ql/src/Expressions/ComparisonWithNaN.ql index 4ffd8b539b68..e77b16af65ed 100644 --- a/javascript/ql/src/Expressions/ComparisonWithNaN.ql +++ b/javascript/ql/src/Expressions/ComparisonWithNaN.ql @@ -15,5 +15,5 @@ import javascript from Comparison cmp -where ((GlobalVarAccess)cmp.getAnOperand()).getName() = "NaN" -select cmp, "Useless comparison with NaN." \ No newline at end of file +where (cmp.getAnOperand().(GlobalVarAccess)).getName() = "NaN" +select cmp, "Useless comparison with NaN." diff --git a/javascript/ql/src/Expressions/DOMProperties.qll b/javascript/ql/src/Expressions/DOMProperties.qll index 9ed6db0e4c15..7ff1884fb1b4 100644 --- a/javascript/ql/src/Expressions/DOMProperties.qll +++ b/javascript/ql/src/Expressions/DOMProperties.qll @@ -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*()) ) } diff --git a/javascript/ql/src/Expressions/DuplicateCondition.ql b/javascript/ql/src/Expressions/DuplicateCondition.ql index 02872ae16dcc..a13a0f158104 100644 --- a/javascript/ql/src/Expressions/DuplicateCondition.ql +++ b/javascript/ql/src/Expressions/DuplicateCondition.ql @@ -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() \ No newline at end of file +select f, "This condition is a duplicate of $@.", e, e.toString() diff --git a/javascript/ql/src/Expressions/DuplicateProperty.ql b/javascript/ql/src/Expressions/DuplicateProperty.ql index f012839f8fe9..686241068813 100644 --- a/javascript/ql/src/Expressions/DuplicateProperty.ql +++ b/javascript/ql/src/Expressions/DuplicateProperty.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/Expressions/DuplicateSwitchCase.ql b/javascript/ql/src/Expressions/DuplicateSwitchCase.ql index 57a04fcaf3e8..56cb848dba14 100644 --- a/javascript/ql/src/Expressions/DuplicateSwitchCase.ql +++ b/javascript/ql/src/Expressions/DuplicateSwitchCase.ql @@ -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() \ No newline at end of file +select f, "This case label is a duplicate of $@.", e, e.toString() diff --git a/javascript/ql/src/Expressions/ExprHasNoEffect.ql b/javascript/ql/src/Expressions/ExprHasNoEffect.ql index 49dbfc53fef2..ae784950e237 100644 --- a/javascript/ql/src/Expressions/ExprHasNoEffect.ql +++ b/javascript/ql/src/Expressions/ExprHasNoEffect.ql @@ -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, )` - 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." diff --git a/javascript/ql/src/Expressions/HapaxLegomenon.ql b/javascript/ql/src/Expressions/HapaxLegomenon.ql index ecf5df42f62d..b60312706eb0 100644 --- a/javascript/ql/src/Expressions/HapaxLegomenon.ql +++ b/javascript/ql/src/Expressions/HapaxLegomenon.ql @@ -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 + "'." diff --git a/javascript/ql/src/Expressions/HeterogeneousComparison.ql b/javascript/ql/src/Expressions/HeterogeneousComparison.ql index ae1be5a7a989..3f1eb083a4a3 100644 --- a/javascript/ql/src/Expressions/HeterogeneousComparison.ql +++ b/javascript/ql/src/Expressions/HeterogeneousComparison.ql @@ -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) -} - -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 +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 diff --git a/javascript/ql/src/Expressions/ImplicitOperandConversion.ql b/javascript/ql/src/Expressions/ImplicitOperandConversion.ql index fce88459eaeb..e777355eade6 100644 --- a/javascript/ql/src/Expressions/ImplicitOperandConversion.ql +++ b/javascript/ql/src/Expressions/ImplicitOperandConversion.ql @@ -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 + "." \ No newline at end of file +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 + "." diff --git a/javascript/ql/src/Expressions/MissingDotLengthInComparison.ql b/javascript/ql/src/Expressions/MissingDotLengthInComparison.ql index b35e062e316f..705b3eedfc07 100644 --- a/javascript/ql/src/Expressions/MissingDotLengthInComparison.ql +++ b/javascript/ql/src/Expressions/MissingDotLengthInComparison.ql @@ -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" diff --git a/javascript/ql/src/Expressions/MissingSpaceInAppend.ql b/javascript/ql/src/Expressions/MissingSpaceInAppend.ql index c74411b5a749..4870927fd519 100644 --- a/javascript/ql/src/Expressions/MissingSpaceInAppend.ql +++ b/javascript/ql/src/Expressions/MissingSpaceInAppend.ql @@ -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 + "'." diff --git a/javascript/ql/src/Expressions/MisspelledIdentifier.ql b/javascript/ql/src/Expressions/MisspelledIdentifier.ql index 0e2cef3a2236..4d482724a500 100644 --- a/javascript/ql/src/Expressions/MisspelledIdentifier.ql +++ b/javascript/ql/src/Expressions/MisspelledIdentifier.ql @@ -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() + "." diff --git a/javascript/ql/src/Expressions/Misspelling.qll b/javascript/ql/src/Expressions/Misspelling.qll index 1d471a8bcfcc..d143c0394beb 100644 --- a/javascript/ql/src/Expressions/Misspelling.qll +++ b/javascript/ql/src/Expressions/Misspelling.qll @@ -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 diff --git a/javascript/ql/src/Expressions/RedundantExpression.ql b/javascript/ql/src/Expressions/RedundantExpression.ql index ffc7c484e24e..b35c0980706e 100644 --- a/javascript/ql/src/Expressions/RedundantExpression.ql +++ b/javascript/ql/src/Expressions/RedundantExpression.ql @@ -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() \ No newline at end of file +select e.toReport(), "Operands $@ and $@ are identical.", e, e.toString(), f, f.toString() diff --git a/javascript/ql/src/Expressions/SelfAssignment.ql b/javascript/ql/src/Expressions/SelfAssignment.ql index d07f568a6a60..50d55a50bfe4 100644 --- a/javascript/ql/src/Expressions/SelfAssignment.ql +++ b/javascript/ql/src/Expressions/SelfAssignment.ql @@ -24,26 +24,24 @@ import DOMProperties * in which case `element ` 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." diff --git a/javascript/ql/src/Expressions/ShiftOutOfRange.ql b/javascript/ql/src/Expressions/ShiftOutOfRange.ql index bc5e0e9a6f6b..1354c58501c3 100644 --- a/javascript/ql/src/Expressions/ShiftOutOfRange.ql +++ b/javascript/ql/src/Expressions/ShiftOutOfRange.ql @@ -15,4 +15,4 @@ import javascript from ShiftExpr shift where shift.getRightOperand().getIntValue() > 31 -select shift, "Shift out of range." \ No newline at end of file +select shift, "Shift out of range." diff --git a/javascript/ql/src/Expressions/StringInsteadOfRegex.ql b/javascript/ql/src/Expressions/StringInsteadOfRegex.ql index 228a6ff6290e..7a7843638324 100644 --- a/javascript/ql/src/Expressions/StringInsteadOfRegex.ql +++ b/javascript/ql/src/Expressions/StringInsteadOfRegex.ql @@ -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 diff --git a/javascript/ql/src/Expressions/SuspiciousInvocation.ql b/javascript/ql/src/Expressions/SuspiciousInvocation.ql index 902fbd5abdae..ca7281eba5cd 100644 --- a/javascript/ql/src/Expressions/SuspiciousInvocation.ql +++ b/javascript/ql/src/Expressions/SuspiciousInvocation.ql @@ -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() + "." \ No newline at end of file +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() + "." diff --git a/javascript/ql/src/Expressions/SuspiciousPropAccess.ql b/javascript/ql/src/Expressions/SuspiciousPropAccess.ql index 946c6d78418c..0522de6958e4 100644 --- a/javascript/ql/src/Expressions/SuspiciousPropAccess.ql +++ b/javascript/ql/src/Expressions/SuspiciousPropAccess.ql @@ -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() + "." diff --git a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql index a0940d777279..9095c735a702 100644 --- a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql +++ b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql @@ -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. = .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, ) bindAll.getArgument(1).mayHaveStringValue(name) or // _.bindAll(this, [, ]) - 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() diff --git a/javascript/ql/src/Expressions/UnclearOperatorPrecedence.ql b/javascript/ql/src/Expressions/UnclearOperatorPrecedence.ql index 7438835d794e..869971f5f1fb 100644 --- a/javascript/ql/src/Expressions/UnclearOperatorPrecedence.ql +++ b/javascript/ql/src/Expressions/UnclearOperatorPrecedence.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/Expressions/UnknownDirective.ql b/javascript/ql/src/Expressions/UnknownDirective.ql index 6102a69bf2e2..500711436ae8 100644 --- a/javascript/ql/src/Expressions/UnknownDirective.ql +++ b/javascript/ql/src/Expressions/UnknownDirective.ql @@ -11,7 +11,8 @@ import javascript from Directive d -where not d instanceof KnownDirective and - // but exclude attribute top-levels: `` - 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: `` + not (d.getParent() instanceof CodeInAttribute) +select d, "Unknown directive: '" + truncate(d.getDirectiveText(), 20, " ... (truncated)") + "'." diff --git a/javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql b/javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql index bb36d94aef33..099d033b9928 100644 --- a/javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql +++ b/javascript/ql/src/Expressions/UnneededDefensiveProgramming.ql @@ -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 + "." diff --git a/javascript/ql/src/Expressions/WhitespaceContradictsPrecedence.ql b/javascript/ql/src/Expressions/WhitespaceContradictsPrecedence.ql index 9f3ad1d3117a..58e16189f3f7 100644 --- a/javascript/ql/src/Expressions/WhitespaceContradictsPrecedence.ql +++ b/javascript/ql/src/Expressions/WhitespaceContradictsPrecedence.ql @@ -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." \ No newline at end of file +where + interestingNesting(inner, outer) and + inner.getWhitespaceAroundOperator() > outer.getWhitespaceAroundOperator() and + not outer.getTopLevel().isMinified() +select outer, "Whitespace around nested operators contradicts precedence." diff --git a/javascript/ql/src/JSDoc/BadParamTag.ql b/javascript/ql/src/JSDoc/BadParamTag.ql index 284368d7bd23..b4a9012a0ddb 100644 --- a/javascript/ql/src/JSDoc/BadParamTag.ql +++ b/javascript/ql/src/JSDoc/BadParamTag.ql @@ -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 + "." \ No newline at end of file +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 + "." diff --git a/javascript/ql/src/JSDoc/JSDocForNonExistentParameter.ql b/javascript/ql/src/JSDoc/JSDocForNonExistentParameter.ql index 721a15749bfd..07fa594a28b9 100644 --- a/javascript/ql/src/JSDoc/JSDocForNonExistentParameter.ql +++ b/javascript/ql/src/JSDoc/JSDocForNonExistentParameter.ql @@ -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 + "." \ No newline at end of file +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 + "." diff --git a/javascript/ql/src/JSDoc/UndocumentedParameter.ql b/javascript/ql/src/JSDoc/UndocumentedParameter.ql index 8fdce73b3a9d..ec0a895fd260 100644 --- a/javascript/ql/src/JSDoc/UndocumentedParameter.ql +++ b/javascript/ql/src/JSDoc/UndocumentedParameter.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/JSDoc/UnknownTagType.ql b/javascript/ql/src/JSDoc/UnknownTagType.ql index e6e4e2f75ce9..c30e9dd688af 100644 --- a/javascript/ql/src/JSDoc/UnknownTagType.ql +++ b/javascript/ql/src/JSDoc/UnknownTagType.ql @@ -136,4 +136,4 @@ predicate knownTagType(string tp) { from JSDocTag tag where not knownTagType(tag.getTitle()) -select tag, "Unknown tag type '" + tag.getTitle() + "'." \ No newline at end of file +select tag, "Unknown tag type '" + tag.getTitle() + "'." diff --git a/javascript/ql/src/JSLint/InvalidJSLintDirective.ql b/javascript/ql/src/JSLint/InvalidJSLintDirective.ql index f1d64428d9ec..f5a7a73d6fbd 100644 --- a/javascript/ql/src/JSLint/InvalidJSLintDirective.ql +++ b/javascript/ql/src/JSLint/InvalidJSLintDirective.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/JSLint/MalformedJSLintDirective.ql b/javascript/ql/src/JSLint/MalformedJSLintDirective.ql index e20543e39052..bf93cf756068 100644 --- a/javascript/ql/src/JSLint/MalformedJSLintDirective.ql +++ b/javascript/ql/src/JSLint/MalformedJSLintDirective.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/LanguageFeatures/ArgumentsCallerCallee.ql b/javascript/ql/src/LanguageFeatures/ArgumentsCallerCallee.ql index 1e0ddb0325a3..fb7055d8f991 100644 --- a/javascript/ql/src/LanguageFeatures/ArgumentsCallerCallee.ql +++ b/javascript/ql/src/LanguageFeatures/ArgumentsCallerCallee.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/LanguageFeatures/BadTypeof.ql b/javascript/ql/src/LanguageFeatures/BadTypeof.ql index 16deb3f5e7be..74813100f58c 100644 --- a/javascript/ql/src/LanguageFeatures/BadTypeof.ql +++ b/javascript/ql/src/LanguageFeatures/BadTypeof.ql @@ -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() diff --git a/javascript/ql/src/LanguageFeatures/ConditionalComments.ql b/javascript/ql/src/LanguageFeatures/ConditionalComments.ql index 0b39f371e941..255415c05640 100644 --- a/javascript/ql/src/LanguageFeatures/ConditionalComments.ql +++ b/javascript/ql/src/LanguageFeatures/ConditionalComments.ql @@ -15,4 +15,4 @@ import javascript from Comment c where c.getText().trim().matches("@cc\\_on%") -select c, "Do not use conditional comments." \ No newline at end of file +select c, "Do not use conditional comments." diff --git a/javascript/ql/src/LanguageFeatures/DebuggerStatement.ql b/javascript/ql/src/LanguageFeatures/DebuggerStatement.ql index 69ce841c0fe9..6ffca36df7cd 100644 --- a/javascript/ql/src/LanguageFeatures/DebuggerStatement.ql +++ b/javascript/ql/src/LanguageFeatures/DebuggerStatement.ql @@ -14,4 +14,4 @@ import javascript from DebuggerStmt ds -select ds, "Do not use 'debugger'." \ No newline at end of file +select ds, "Do not use 'debugger'." diff --git a/javascript/ql/src/LanguageFeatures/DeleteVar.ql b/javascript/ql/src/LanguageFeatures/DeleteVar.ql index ff45b84cd5d9..ed3940a1c70c 100644 --- a/javascript/ql/src/LanguageFeatures/DeleteVar.ql +++ b/javascript/ql/src/LanguageFeatures/DeleteVar.ql @@ -15,4 +15,4 @@ import javascript from DeleteExpr del where not del.getOperand().stripParens() instanceof PropAccess -select del, "Only properties should be deleted." \ No newline at end of file +select del, "Only properties should be deleted." diff --git a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql index 48ae48112446..5b0d4bb9b856 100644 --- a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql +++ b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql @@ -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." \ No newline at end of file +select ae, "Avoid omitted array elements." diff --git a/javascript/ql/src/LanguageFeatures/Eval.ql b/javascript/ql/src/LanguageFeatures/Eval.ql index 93310c803db3..f1f753774c04 100644 --- a/javascript/ql/src/LanguageFeatures/Eval.ql +++ b/javascript/ql/src/LanguageFeatures/Eval.ql @@ -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." \ No newline at end of file +select eval, "Do not use eval or the Function constructor." diff --git a/javascript/ql/src/LanguageFeatures/ExpressionClosures.ql b/javascript/ql/src/LanguageFeatures/ExpressionClosures.ql index cbe9e2dd0dff..6c81cf90fc56 100644 --- a/javascript/ql/src/LanguageFeatures/ExpressionClosures.ql +++ b/javascript/ql/src/LanguageFeatures/ExpressionClosures.ql @@ -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 + "." \ No newline at end of file +select depr, "Use " + replacement + " instead of " + type + "." diff --git a/javascript/ql/src/LanguageFeatures/HTMLComments.ql b/javascript/ql/src/LanguageFeatures/HTMLComments.ql index 2f0849762924..67195c7bead7 100644 --- a/javascript/ql/src/LanguageFeatures/HTMLComments.ql +++ b/javascript/ql/src/LanguageFeatures/HTMLComments.ql @@ -15,4 +15,4 @@ import javascript from HtmlLineComment c -select c, "Do not use HTML comments." \ No newline at end of file +select c, "Do not use HTML comments." diff --git a/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql b/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql index 58290a6fd19f..3593666e3ea3 100644 --- a/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql +++ b/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql @@ -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 diff --git a/javascript/ql/src/LanguageFeatures/InconsistentNew.ql b/javascript/ql/src/LanguageFeatures/InconsistentNew.ql index a9fa57cfce67..847d00ea8275 100644 --- a/javascript/ql/src/LanguageFeatures/InconsistentNew.ql +++ b/javascript/ql/src/LanguageFeatures/InconsistentNew.ql @@ -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" diff --git a/javascript/ql/src/LanguageFeatures/InvalidPrototype.ql b/javascript/ql/src/LanguageFeatures/InvalidPrototype.ql index 3397e77677b2..89dc7535c60b 100644 --- a/javascript/ql/src/LanguageFeatures/InvalidPrototype.ql +++ b/javascript/ql/src/LanguageFeatures/InvalidPrototype.ql @@ -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." diff --git a/javascript/ql/src/LanguageFeatures/JumpFromFinally.ql b/javascript/ql/src/LanguageFeatures/JumpFromFinally.ql index e1bf946e7e61..06620741a77a 100644 --- a/javascript/ql/src/LanguageFeatures/JumpFromFinally.ql +++ b/javascript/ql/src/LanguageFeatures/JumpFromFinally.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/LanguageFeatures/LengthComparisonOffByOne.ql b/javascript/ql/src/LanguageFeatures/LengthComparisonOffByOne.ql index a9326e5fc4a3..e3935386289a 100644 --- a/javascript/ql/src/LanguageFeatures/LengthComparisonOffByOne.ql +++ b/javascript/ql/src/LanguageFeatures/LengthComparisonOffByOne.ql @@ -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" \ No newline at end of file +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" diff --git a/javascript/ql/src/LanguageFeatures/MultilineStringLiteral.ql b/javascript/ql/src/LanguageFeatures/MultilineStringLiteral.ql index 08b959b2a1ab..3da685f0ef1d 100644 --- a/javascript/ql/src/LanguageFeatures/MultilineStringLiteral.ql +++ b/javascript/ql/src/LanguageFeatures/MultilineStringLiteral.ql @@ -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." \ No newline at end of file +where + l = sl.getLocation() and + l.getStartLine() != l.getEndLine() +select sl, "Avoid multi-line string literals." diff --git a/javascript/ql/src/LanguageFeatures/NonLinearPattern.ql b/javascript/ql/src/LanguageFeatures/NonLinearPattern.ql index 55b211cf649b..faa5392806e1 100644 --- a/javascript/ql/src/LanguageFeatures/NonLinearPattern.ql +++ b/javascript/ql/src/LanguageFeatures/NonLinearPattern.ql @@ -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" diff --git a/javascript/ql/src/LanguageFeatures/OctalLiteral.ql b/javascript/ql/src/LanguageFeatures/OctalLiteral.ql index 3a5428f55729..a09406579d50 100644 --- a/javascript/ql/src/LanguageFeatures/OctalLiteral.ql +++ b/javascript/ql/src/LanguageFeatures/OctalLiteral.ql @@ -14,4 +14,4 @@ import javascript from NumberLiteral nl where nl.getRawValue().regexpMatch("0\\d+") -select nl, "Do not use octal literals." \ No newline at end of file +select nl, "Do not use octal literals." diff --git a/javascript/ql/src/LanguageFeatures/PropertyWriteOnPrimitive.ql b/javascript/ql/src/LanguageFeatures/PropertyWriteOnPrimitive.ql index 07f1e2c7082e..19d372f65290 100644 --- a/javascript/ql/src/LanguageFeatures/PropertyWriteOnPrimitive.ql +++ b/javascript/ql/src/LanguageFeatures/PropertyWriteOnPrimitive.ql @@ -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() + "." diff --git a/javascript/ql/src/LanguageFeatures/ReservedWords.ql b/javascript/ql/src/LanguageFeatures/ReservedWords.ql index 5f98790864a9..3cd45a2d7ad7 100644 --- a/javascript/ql/src/LanguageFeatures/ReservedWords.ql +++ b/javascript/ql/src/LanguageFeatures/ReservedWords.ql @@ -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." \ No newline at end of file +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." diff --git a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql index fc52539be8db..3f99062112da 100644 --- a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql +++ b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql @@ -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 \ No newline at end of file +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 diff --git a/javascript/ql/src/LanguageFeatures/SetterIgnoresParameter.ql b/javascript/ql/src/LanguageFeatures/SetterIgnoresParameter.ql index e184cd07a836..4f1758d2cd8a 100644 --- a/javascript/ql/src/LanguageFeatures/SetterIgnoresParameter.ql +++ b/javascript/ql/src/LanguageFeatures/SetterIgnoresParameter.ql @@ -15,11 +15,15 @@ import javascript import semmle.javascript.RestrictedLocations from PropertySetter s, FunctionExpr f, SimpleParameter p -where f = s.getInit() and - p = f.getAParameter() and - not exists (p.getVariable().getAnAccess()) and - not f.usesArgumentsObject() and - // the setter body is either empty, or it is not just a single 'throw' statement - (not exists(f.getABodyStmt()) or - exists (Stmt stmt | stmt = f.getABodyStmt() | not stmt instanceof ThrowStmt)) -select (FirstLineOf)s, "This setter function does not use its parameter $@.", p, p.getName() \ No newline at end of file +where + f = s.getInit() and + p = f.getAParameter() and + not exists(p.getVariable().getAnAccess()) and + not f.usesArgumentsObject() and + // the setter body is either empty, or it is not just a single 'throw' statement + ( + not exists(f.getABodyStmt()) + or + exists(Stmt stmt | stmt = f.getABodyStmt() | not stmt instanceof ThrowStmt) + ) +select s.(FirstLineOf), "This setter function does not use its parameter $@.", p, p.getName() diff --git a/javascript/ql/src/LanguageFeatures/SetterReturn.ql b/javascript/ql/src/LanguageFeatures/SetterReturn.ql index 1bcb79359b23..0333246a4391 100644 --- a/javascript/ql/src/LanguageFeatures/SetterReturn.ql +++ b/javascript/ql/src/LanguageFeatures/SetterReturn.ql @@ -13,7 +13,8 @@ import javascript from FunctionExpr f, ReturnStmt ret -where ret.getContainer() = f and - f.isSetter() and - exists (ret.getExpr()) -select ret, "Useless return statement in setter function." \ No newline at end of file +where + ret.getContainer() = f and + f.isSetter() and + exists(ret.getExpr()) +select ret, "Useless return statement in setter function." diff --git a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql index 1b882622b419..fb4c63290e5d 100644 --- a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql +++ b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql @@ -30,7 +30,7 @@ predicate isFixedArity(Function fn) { * This is only defined if all potential callees have a fixed arity. */ int maxArity(DataFlow::InvokeNode invk) { - forall (Function callee | callee = invk.getACallee() | isFixedArity(callee)) and + forall(Function callee | callee = invk.getACallee() | isFixedArity(callee)) and result = max(invk.getACallee().getNumParameter()) } @@ -52,9 +52,7 @@ class SpuriousArguments extends Expr { /** * Gets the call site at which the spurious arguments are passed. */ - DataFlow::InvokeNode getCall() { - result = invk - } + DataFlow::InvokeNode getCall() { result = invk } /** * Gets the number of spurious arguments, that is, the number of @@ -72,25 +70,29 @@ class SpuriousArguments extends Expr { * For more information, see * [LGTM locations](https://lgtm.com/help/ql/locations). */ - predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and - exists (DataFlow::Node lastArg | - lastArg = max(DataFlow::Node arg, int i | arg = invk.getArgument(i) | arg order by i) | + exists(DataFlow::Node lastArg | + lastArg = max(DataFlow::Node arg, int i | arg = invk.getArgument(i) | arg order by i) + | lastArg.hasLocationInfo(_, _, _, endline, endcolumn) ) } } from SpuriousArguments args, Function f, string arguments -where f = args.getCall().getACallee() and - if args.getCount() = 1 then arguments = "argument" else arguments = "arguments" and - ( - // exclude empty functions, they are probably commented out debug utilities ... - exists(f.getABodyStmt()) or - // ... but include: constructors, arrows and externals/ambients - f instanceof Constructor or // unlikely to be a debug utility - f instanceof ArrowFunctionExpr or // cannot be empty - f instanceof ExternalFunction or // always empty - f.isAmbient() // always empty - ) +where + f = args.getCall().getACallee() and + if args.getCount() = 1 then arguments = "argument" else arguments = "arguments" and + ( + // exclude empty functions, they are probably commented out debug utilities ... + exists(f.getABodyStmt()) or + // ... but include: constructors, arrows and externals/ambients + f instanceof Constructor or // unlikely to be a debug utility + f instanceof ArrowFunctionExpr or // cannot be empty + f instanceof ExternalFunction or // always empty + f.isAmbient() // always empty + ) select args, "Superfluous " + arguments + " passed to $@.", f, f.describe() diff --git a/javascript/ql/src/LanguageFeatures/StrictModeCallStackIntrospection.ql b/javascript/ql/src/LanguageFeatures/StrictModeCallStackIntrospection.ql index f7d510a00b2f..69bc5ac70c45 100644 --- a/javascript/ql/src/LanguageFeatures/StrictModeCallStackIntrospection.ql +++ b/javascript/ql/src/LanguageFeatures/StrictModeCallStackIntrospection.ql @@ -18,16 +18,19 @@ import javascript * code; `baseDesc` is a description of `baseVal` used in the alert message. */ predicate illegalPropAccess(AbstractValue baseVal, string baseDesc, string prop) { - baseVal instanceof AbstractArguments and baseDesc = "arguments" and + baseVal instanceof AbstractArguments and + baseDesc = "arguments" and (prop = "caller" or prop = "callee") or - baseVal instanceof AbstractFunction and baseDesc = "Function.prototype" and + baseVal instanceof AbstractFunction and + baseDesc = "Function.prototype" and (prop = "caller" or prop = "arguments") } from PropAccess acc, DataFlow::AnalyzedNode baseNode, string base, string prop -where acc.accesses(baseNode.asExpr(), prop) and - acc.getContainer().isStrict() and - illegalPropAccess(baseNode.getAValue(), base, prop) and - forex (AbstractValue av | av = baseNode.getAValue() | illegalPropAccess(av, _, prop)) -select acc, "Strict mode code cannot use " + base + "." + prop + "." \ No newline at end of file +where + acc.accesses(baseNode.asExpr(), prop) and + acc.getContainer().isStrict() and + illegalPropAccess(baseNode.getAValue(), base, prop) and + forex(AbstractValue av | av = baseNode.getAValue() | illegalPropAccess(av, _, prop)) +select acc, "Strict mode code cannot use " + base + "." + prop + "." diff --git a/javascript/ql/src/LanguageFeatures/SyntaxError.ql b/javascript/ql/src/LanguageFeatures/SyntaxError.ql index 75c4bbd703e7..4d0a21a96619 100644 --- a/javascript/ql/src/LanguageFeatures/SyntaxError.ql +++ b/javascript/ql/src/LanguageFeatures/SyntaxError.ql @@ -13,4 +13,4 @@ import javascript from JSParseError pe -select pe, pe.getMessage() \ No newline at end of file +select pe, pe.getMessage() diff --git a/javascript/ql/src/LanguageFeatures/TemplateSyntaxInStringLiteral.ql b/javascript/ql/src/LanguageFeatures/TemplateSyntaxInStringLiteral.ql index f20b85a99474..efc300e1974f 100644 --- a/javascript/ql/src/LanguageFeatures/TemplateSyntaxInStringLiteral.ql +++ b/javascript/ql/src/LanguageFeatures/TemplateSyntaxInStringLiteral.ql @@ -7,19 +7,18 @@ * @precision high * @tags correctness */ - + import javascript /** A toplevel that contains at least one template literal. */ class CandidateTopLevel extends TopLevel { - CandidateTopLevel() { - exists (TemplateLiteral template | template.getTopLevel() = this) - } + CandidateTopLevel() { exists(TemplateLiteral template | template.getTopLevel() = this) } } /** A string literal in a toplevel that contains at least one template literal. */ class CandidateStringLiteral extends StringLiteral { CandidateTopLevel tl; + string v; CandidateStringLiteral() { @@ -46,8 +45,9 @@ class CandidateStringLiteral extends StringLiteral { * stepping over scope elements. */ ASTNode getIntermediate() { - result = this or - exists (ASTNode mid | mid = getIntermediate() | + result = this + or + exists(ASTNode mid | mid = getIntermediate() | not mid instanceof ScopeElement and result = mid.getParent() ) @@ -62,7 +62,6 @@ class CandidateStringLiteral extends StringLiteral { } } - /** * Holds if `obj` has a property for each template variable in `lit` and they occur as arguments * to the same call. @@ -76,14 +75,12 @@ class CandidateStringLiteral extends StringLiteral { * ``` */ predicate providesTemplateVariablesFor(ObjectExpr obj, CandidateStringLiteral lit) { - exists (CallExpr call | call.getAnArgument() = obj and call.getAnArgument() = lit) and - forex (string name | lit.getAReferencedVariable() = name | hasProperty(obj, name)) + exists(CallExpr call | call.getAnArgument() = obj and call.getAnArgument() = lit) and + forex(string name | lit.getAReferencedVariable() = name | hasProperty(obj, name)) } /** Holds if `object` has a property with the given `name`. */ -predicate hasProperty(ObjectExpr object, string name) { - name = object.getAProperty().getName() -} +predicate hasProperty(ObjectExpr object, string name) { name = object.getAProperty().getName() } /** * Gets a declaration of variable `v` in `tl`, where `v` has the given `name` and @@ -97,10 +94,11 @@ VarDecl getDeclIn(Variable v, Scope s, string name, CandidateTopLevel tl) { } from CandidateStringLiteral lit, Variable v, Scope s, string name, VarDecl decl -where decl = getDeclIn(v, s, name, lit.getTopLevel()) - and lit.getAReferencedVariable() = name - and lit.isInScope(s) - and not exists (ObjectExpr obj | providesTemplateVariablesFor(obj, lit)) - and not lit.getStringValue() = "${" + name + "}" -select lit, "This string is not a template literal, but appears to reference the variable $@.", - decl, v.getName() +where + decl = getDeclIn(v, s, name, lit.getTopLevel()) and + lit.getAReferencedVariable() = name and + lit.isInScope(s) and + not exists(ObjectExpr obj | providesTemplateVariablesFor(obj, lit)) and + not lit.getStringValue() = "${" + name + "}" +select lit, "This string is not a template literal, but appears to reference the variable $@.", + decl, v.getName() diff --git a/javascript/ql/src/LanguageFeatures/ThisBeforeSuper.ql b/javascript/ql/src/LanguageFeatures/ThisBeforeSuper.ql index 66026fc83fc2..75317faf7f09 100644 --- a/javascript/ql/src/LanguageFeatures/ThisBeforeSuper.ql +++ b/javascript/ql/src/LanguageFeatures/ThisBeforeSuper.ql @@ -30,12 +30,12 @@ predicate needsGuard(Expr e, string kind) { * a `super` call. */ predicate unguardedBB(BasicBlock bb, Function ctor) { - exists (ClassDefinition c | exists(c.getSuperClass()) | + exists(ClassDefinition c | exists(c.getSuperClass()) | ctor = c.getConstructor().getBody() and bb = ctor.getStartBB() ) or - exists (BasicBlock pred | pred = bb.getAPredecessor() | + exists(BasicBlock pred | pred = bb.getAPredecessor() | unguardedBB(pred, ctor) and not pred.getANode() instanceof SuperCall ) @@ -47,17 +47,19 @@ predicate unguardedBB(BasicBlock bb, Function ctor) { * a `super` call. */ predicate unguarded(ControlFlowNode nd, Function ctor) { - exists (BasicBlock bb, int i | nd = bb.getNode(i) | + exists(BasicBlock bb, int i | nd = bb.getNode(i) | unguardedBB(bb, ctor) and - not bb.getNode([0..i-1]) instanceof SuperCall + not bb.getNode([0 .. i - 1]) instanceof SuperCall ) } from Expr e, string kind, Function ctor -where needsGuard(e, kind) and unguarded(e, ctor) and - // don't flag if there is a super call in a nested arrow function - not exists (SuperCall sc | - sc.getBinder() = ctor and - sc.getEnclosingFunction() != ctor - ) -select (FirstLineOf)ctor, "The super constructor must be called before using '$@'.", e, kind \ No newline at end of file +where + needsGuard(e, kind) and + unguarded(e, ctor) and + // don't flag if there is a super call in a nested arrow function + not exists(SuperCall sc | + sc.getBinder() = ctor and + sc.getEnclosingFunction() != ctor + ) +select ctor.(FirstLineOf), "The super constructor must be called before using '$@'.", e, kind diff --git a/javascript/ql/src/LanguageFeatures/TrailingComma.ql b/javascript/ql/src/LanguageFeatures/TrailingComma.ql index 2e31f7c36a24..1f1784381c82 100644 --- a/javascript/ql/src/LanguageFeatures/TrailingComma.ql +++ b/javascript/ql/src/LanguageFeatures/TrailingComma.ql @@ -22,19 +22,16 @@ class ArrayOrObjectExpr extends Expr { /** Holds if this array or object expression has a trailing comma. */ predicate hasTrailingComma() { - ((ArrayExpr)this).hasTrailingComma() or - ((ObjectExpr)this).hasTrailingComma() + (this.(ArrayExpr)).hasTrailingComma() or + (this.(ObjectExpr)).hasTrailingComma() } /** Gets a short description of this expression. */ string getShortName() { - if this instanceof ArrayExpr then - result = "array expression" - else - result = "object expression" + if this instanceof ArrayExpr then result = "array expression" else result = "object expression" } } from ArrayOrObjectExpr e where e.hasTrailingComma() -select e.getLastToken().getPreviousToken(), "Trailing comma in " + e.getShortName() + "." \ No newline at end of file +select e.getLastToken().getPreviousToken(), "Trailing comma in " + e.getShortName() + "." diff --git a/javascript/ql/src/LanguageFeatures/WithStatement.ql b/javascript/ql/src/LanguageFeatures/WithStatement.ql index 881454e402ce..cb2cfc728d8b 100644 --- a/javascript/ql/src/LanguageFeatures/WithStatement.ql +++ b/javascript/ql/src/LanguageFeatures/WithStatement.ql @@ -13,4 +13,4 @@ import javascript import semmle.javascript.RestrictedLocations from WithStmt ws -select (FirstLineOf)ws, "Do not use 'with'." +select ws.(FirstLineOf), "Do not use 'with'." diff --git a/javascript/ql/src/LanguageFeatures/WrongExtensionJSON.ql b/javascript/ql/src/LanguageFeatures/WrongExtensionJSON.ql index 972c29a02c1d..a3e73e921677 100644 --- a/javascript/ql/src/LanguageFeatures/WrongExtensionJSON.ql +++ b/javascript/ql/src/LanguageFeatures/WrongExtensionJSON.ql @@ -12,7 +12,8 @@ import javascript from JSONValue v, File f -where f = v.getFile() and - f.getExtension().regexpMatch("(?i)jsx?") and - not exists(v.getParent()) -select v, "JSON data in file with extension '" + f.getExtension() + "'." \ No newline at end of file +where + f = v.getFile() and + f.getExtension().regexpMatch("(?i)jsx?") and + not exists(v.getParent()) +select v, "JSON data in file with extension '" + f.getExtension() + "'." diff --git a/javascript/ql/src/LanguageFeatures/YieldInNonGenerator.ql b/javascript/ql/src/LanguageFeatures/YieldInNonGenerator.ql index 7c400046f318..f13c19ab6548 100644 --- a/javascript/ql/src/LanguageFeatures/YieldInNonGenerator.ql +++ b/javascript/ql/src/LanguageFeatures/YieldInNonGenerator.ql @@ -13,7 +13,8 @@ import javascript from YieldExpr yield, Function f -where f = yield.getEnclosingFunction() and - not f.isGenerator() +where + f = yield.getEnclosingFunction() and + not f.isGenerator() select yield, "This yield expression is contained in $@ which is not marked as a generator.", - f.getFirstToken(), f.describe() \ No newline at end of file + f.getFirstToken(), f.describe() diff --git a/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.ql b/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.ql index bd18a8a092aa..a63830647f13 100644 --- a/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.ql +++ b/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.ql @@ -13,5 +13,4 @@ import ExternalDependencies from File f, Dependency dep, string entity, int n where externalDependencies(f, dep, entity, n) -select entity, n -order by n desc +select entity, n order by n desc diff --git a/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.qll b/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.qll index 53f62bf69c6c..d153d20b9b89 100644 --- a/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.qll +++ b/javascript/ql/src/Metrics/Dependencies/ExternalDependencies.qll @@ -11,9 +11,8 @@ import semmle.javascript.dependencies.Dependencies * respectively, of `dep`. */ predicate externalDependencies(File f, Dependency dep, string entity, int n) { - exists (string id, string v | dep.info(id, v) | + exists(string id, string v | dep.info(id, v) | entity = "/" + f.getRelativePath() + "<|>" + id + "<|>" + v - ) - and - n = strictcount (Locatable use | use.getFile() = f and use = dep.getAUse(_)) -} \ No newline at end of file + ) and + n = strictcount(Locatable use | use.getFile() = f and use = dep.getAUse(_)) +} diff --git a/javascript/ql/src/Metrics/ES20xxFeatures.qll b/javascript/ql/src/Metrics/ES20xxFeatures.qll index d3e76ff7a10c..ddc40d7f8df6 100644 --- a/javascript/ql/src/Metrics/ES20xxFeatures.qll +++ b/javascript/ql/src/Metrics/ES20xxFeatures.qll @@ -14,102 +14,124 @@ import javascript * (https://kangax.github.io/compat-table/esnext/). */ predicate isES20xxFeature(ASTNode nd, int version, string category) { - version = 2015 and ( - exists(nd.(Parameter).getDefault()) and category = "default function parameters" or - exists (Expr e | e = nd and - // synthetic constructors use rest/spread/super, but we don't want to count those - not e.getEnclosingFunction() instanceof SyntheticConstructor | - nd.(Parameter).isRestParameter() and - category = "rest parameters" - or - nd instanceof SpreadElement and - // spread properties are an ES2018 feature, see below - not nd = any(SpreadProperty sp).getInit() and - category = "spread (...) operator" - or - nd instanceof SuperExpr and category = "super" - ) or - exists (Property prop | prop = nd | - prop.getName() = "__proto__" or - prop.isShorthand() or - prop.isMethod() or - prop.isComputed() - ) and category = "object literal extensions" or - nd instanceof ForOfStmt and category = "for..of loops" or - nd.(Literal).getRawValue().regexpMatch("^0[bo].*") and category = "octal and binary literals" or - nd instanceof TemplateLiteral and category = "template literals" or - exists (RegExpLiteral rel | rel = nd | - rel.getFlags().regexpMatch(".*[yu].*") and - category = "RegExp \"y\" and \"u\" flags" - ) - or - exists (VariableDeclarator vd | vd = nd | - vd.getBindingPattern() instanceof DestructuringPattern and - category = "destructuring declarations" - ) - or - exists (AssignExpr assgn | assgn = nd | - assgn.getLhs().stripParens() instanceof DestructuringPattern and - category = "destructuring assignment" - ) - or - (Parameter)nd instanceof DestructuringPattern and category = "destructuring parameters" or - exists (StringLiteral sl | sl = nd | - sl.getRawValue().regexpMatch(".*(? 1 - ) -select assgn, "Assigning to 'exports' does not export anything." \ No newline at end of file +where + exportsAssign(assgn, exportsVar, exportsVal) and + not exists(exportsVal.getAPredecessor()) and + not ( + // this is OK if `exportsVal` flows into `module.exports` + moduleExportsAssign(_, exportsVal) and + // however, if there are no further uses of `exports` the assignment is useless anyway + strictcount(exportsVar.getAnAccess()) > 1 + ) +select assgn, "Assigning to 'exports' does not export anything." diff --git a/javascript/ql/src/NodeJS/MissingExports.ql b/javascript/ql/src/NodeJS/MissingExports.ql index 788e907e3f0b..49938d198897 100644 --- a/javascript/ql/src/NodeJS/MissingExports.ql +++ b/javascript/ql/src/NodeJS/MissingExports.ql @@ -14,30 +14,26 @@ import javascript /** Holds if variable `v` is assigned somewhere in module `m`. */ predicate definedInModule(GlobalVariable v, NodeModule m) { - exists (LValue def | + exists(LValue def | def.getTopLevel() = m and def.(Expr).accessesGlobal(v.getName()) ) } from NodeModule m, GlobalVariable f, InvokeExpr invk, ASTNode export, GlobalVarAccess acc -where m.exports(f.getName(), export) and - acc = f.getAnAccess() and - invk.getCallee() = acc and - invk.getTopLevel() = m and - - // don't flag if the variable is defined in the same module - not definedInModule(f, m) and - - // don't flag if there is a linter directive declaring the variable - not exists (Linting::GlobalDeclaration glob | - glob.declaresGlobalForAccess(acc) - ) and - - // don't flag if there is an externs declaration for the variable - not exists (ExternalGlobalDecl egd | egd.getName() = f.getName()) and - - // don't flag if the invocation could refer to a property introduced by `with` - not exists (WithStmt with | with.mayAffect(invk.getCallee())) -select invk, "'" + f.getName() + "' references an undeclared global variable, " - + "not the variable exported $@.", export, "here" \ No newline at end of file +where + m.exports(f.getName(), export) and + acc = f.getAnAccess() and + invk.getCallee() = acc and + invk.getTopLevel() = m and + // don't flag if the variable is defined in the same module + not definedInModule(f, m) and + // don't flag if there is a linter directive declaring the variable + not exists(Linting::GlobalDeclaration glob | glob.declaresGlobalForAccess(acc)) and + // don't flag if there is an externs declaration for the variable + not exists(ExternalGlobalDecl egd | egd.getName() = f.getName()) and + // don't flag if the invocation could refer to a property introduced by `with` + not exists(WithStmt with | with.mayAffect(invk.getCallee())) +select invk, + "'" + f.getName() + "' references an undeclared global variable, " + + "not the variable exported $@.", export, "here" diff --git a/javascript/ql/src/NodeJS/UnresolvableImport.ql b/javascript/ql/src/NodeJS/UnresolvableImport.ql index 00ed20d59314..c478a5ebe9de 100644 --- a/javascript/ql/src/NodeJS/UnresolvableImport.ql +++ b/javascript/ql/src/NodeJS/UnresolvableImport.ql @@ -17,33 +17,32 @@ import javascript * file `f` belongs. */ PackageJSON getClosestPackageJSON(Folder f) { - result = f.(NPMPackage).getPackageJSON() or + result = f.(NPMPackage).getPackageJSON() + or not f instanceof NPMPackage and result = getClosestPackageJSON(f.getParentContainer()) } from Require r, string path, string mod -where path = r.getImportedPath().getValue() and - - // the imported module is the initial segment of the path, up to - // `/` or the end of the string, whichever comes first; we exclude - // local paths starting with `.` or `/`, since they might refer to files - // downloaded or generated during the build - mod = path.regexpCapture("([^./][^/]*)(/.*|$)", 1) and - - // exclude WebPack/Require.js loaders - not mod.matches("%!%") and - - // import cannot be resolved statically - not exists (r.getImportedModule()) and - - // no enclosing NPM package declares a dependency on `mod` - forex (NPMPackage pkg, PackageJSON pkgJSON | - pkg.getAModule() = r.getTopLevel() and pkgJSON = pkg.getPackageJSON() | - not pkgJSON.declaresDependency(mod, _) and - not pkgJSON.getPeerDependencies().getADependency(mod, _) and - // exclude packages depending on `fbjs`, which automatically pulls in many otherwise - // undeclared dependencies - not pkgJSON.declaresDependency("fbjs", _) - ) +where + path = r.getImportedPath().getValue() and + // the imported module is the initial segment of the path, up to + // `/` or the end of the string, whichever comes first; we exclude + // local paths starting with `.` or `/`, since they might refer to files + // downloaded or generated during the build + mod = path.regexpCapture("([^./][^/]*)(/.*|$)", 1) and + // exclude WebPack/Require.js loaders + not mod.matches("%!%") and + // import cannot be resolved statically + not exists(r.getImportedModule()) and + // no enclosing NPM package declares a dependency on `mod` + forex(NPMPackage pkg, PackageJSON pkgJSON | + pkg.getAModule() = r.getTopLevel() and pkgJSON = pkg.getPackageJSON() + | + not pkgJSON.declaresDependency(mod, _) and + not pkgJSON.getPeerDependencies().getADependency(mod, _) and + // exclude packages depending on `fbjs`, which automatically pulls in many otherwise + // undeclared dependencies + not pkgJSON.declaresDependency("fbjs", _) + ) select r, "Module " + mod + " cannot be resolved, and is not declared as a dependency in $@.", - getClosestPackageJSON(r.getFile().getParentContainer()), "package.json" + getClosestPackageJSON(r.getFile().getParentContainer()), "package.json" diff --git a/javascript/ql/src/NodeJS/UnusedDependency.ql b/javascript/ql/src/NodeJS/UnusedDependency.ql index 1349e6072737..e0b9daca7321 100644 --- a/javascript/ql/src/NodeJS/UnusedDependency.ql +++ b/javascript/ql/src/NodeJS/UnusedDependency.ql @@ -23,9 +23,7 @@ predicate declaresDependency(NPMPackage pkg, string name, JSONValue dep) { /** * Gets a path expression in a module belonging to `pkg`. */ -PathExpr getAPathExpr(NPMPackage pkg) { - result.getEnclosingModule() = pkg.getAModule() -} +PathExpr getAPathExpr(NPMPackage pkg) { result.getEnclosingModule() = pkg.getAModule() } /** * Gets a URL-valued attribute in a module or HTML file belonging to `pkg`. @@ -39,14 +37,14 @@ DOM::AttributeDefinition getAURLAttribute(NPMPackage pkg) { * Gets the name of a script in the 'scripts' object of `pkg`. * The script makes use of a declared `dependency` of `pkg`. */ -string getPackageScriptNameWithDependency(NPMPackage pkg, string dependency){ - exists (JSONObject scriptsObject, string scriptName, string script | - declaresDependency(pkg, dependency, _) and - scriptsObject = pkg.getPackageJSON().getPropValue("scripts") and - script = scriptsObject.getPropStringValue(scriptName) and - script.regexpMatch(".*\\b\\Q" + dependency + "\\E\\b.*") and - result = scriptName - ) +string getPackageScriptNameWithDependency(NPMPackage pkg, string dependency) { + exists(JSONObject scriptsObject, string scriptName, string script | + declaresDependency(pkg, dependency, _) and + scriptsObject = pkg.getPackageJSON().getPropValue("scripts") and + script = scriptsObject.getPropStringValue(scriptName) and + script.regexpMatch(".*\\b\\Q" + dependency + "\\E\\b.*") and + result = scriptName + ) } /** @@ -58,20 +56,21 @@ predicate usesDependency(NPMPackage pkg, string name) { ( // there is a path expression (e.g., in a `require` or `import`) that // references `pkg` - exists (PathExpr path | path = getAPathExpr(pkg) | + exists(PathExpr path | path = getAPathExpr(pkg) | // check whether the path is `name` or starts with `name/`, ignoring a prefix that ends with '!' (example: "scriptloader!moment") path.getValue().regexpMatch("(.*!)?\\Q" + name + "\\E(/.*)?") ) or // there is an HTML URL attribute that may reference `pkg` - exists (DOM::AttributeDefinition attr | attr = getAURLAttribute(pkg) | + exists(DOM::AttributeDefinition attr | attr = getAURLAttribute(pkg) | // check whether the URL contains `node_modules/name` attr.getStringValue().regexpMatch(".*\\bnode_modules/\\Q" + name + "\\E(/.*)?") ) or // there is a reference in a package.json white-listed script - exists (string packageScriptName | - packageScriptName = getPackageScriptNameWithDependency(pkg, name) | + exists(string packageScriptName | + packageScriptName = getPackageScriptNameWithDependency(pkg, name) + | packageScriptName = "preinstall" or packageScriptName = "install" or packageScriptName = "postinstall" @@ -88,23 +87,22 @@ predicate usesDependency(NPMPackage pkg, string name) { */ predicate implicitRequire(NPMPackage pkg, string name) { // look for Express `set('view engine', ...)` calls - exists (MethodCallExpr setViewEngine, string engine | + exists(MethodCallExpr setViewEngine, string engine | Express::appCreation().flowsToExpr(setViewEngine.getReceiver()) and setViewEngine.getMethodName() = "set" and setViewEngine.getArgument(0).getStringValue() = "view engine" and setViewEngine.getArgument(1).getStringValue() = engine and - setViewEngine.getTopLevel() = pkg.getAModule() | + setViewEngine.getTopLevel() = pkg.getAModule() + | // chop off leading dot, if any - if engine.matches(".%") then - name = engine.suffix(1) - else - name = engine + if engine.matches(".%") then name = engine.suffix(1) else name = engine ) } from NPMPackage pkg, string name, JSONValue dep -where exists (pkg.getAModule()) and - declaresDependency(pkg, name, dep) and - not usesDependency(pkg, name) and - not implicitRequire(pkg, name) +where + exists(pkg.getAModule()) and + declaresDependency(pkg, name, dep) and + not usesDependency(pkg, name) and + not implicitRequire(pkg, name) select dep, "Unused dependency '" + name + "'." diff --git a/javascript/ql/src/Performance/NonLocalForIn.ql b/javascript/ql/src/Performance/NonLocalForIn.ql index 88a9884d4b9d..ffbf5814f3c1 100644 --- a/javascript/ql/src/Performance/NonLocalForIn.ql +++ b/javascript/ql/src/Performance/NonLocalForIn.ql @@ -21,16 +21,19 @@ predicate nonLocalIterator(EnhancedForLoop f, string descr) { f.getIterator() instanceof PropAccess and descr = "a property" or // iterator is not a purely local variable: - exists (Variable v | v = f.getAnIterationVariable() | + exists(Variable v | v = f.getAnIterationVariable() | // either it is global... - v.isGlobal() and descr = "a global variable" or + v.isGlobal() and descr = "a global variable" + or // ...or it is captured by an inner function v.isLocal() and v.isCaptured() and descr = "captured" ) } from EnhancedForLoop f, string reason -where nonLocalIterator(f, reason) and - // exclude toplevel statements, since the toplevel is unlikely to be optimized anyway - not f.getContainer() instanceof TopLevel -select f.getIterator(), "This loop may prevent optimization because its iteration variable is " + reason + "." \ No newline at end of file +where + nonLocalIterator(f, reason) and + // exclude toplevel statements, since the toplevel is unlikely to be optimized anyway + not f.getContainer() instanceof TopLevel +select f.getIterator(), + "This loop may prevent optimization because its iteration variable is " + reason + "." diff --git a/javascript/ql/src/Performance/ReDoS.ql b/javascript/ql/src/Performance/ReDoS.ql index f92874138f87..4bdf265ab70c 100644 --- a/javascript/ql/src/Performance/ReDoS.ql +++ b/javascript/ql/src/Performance/ReDoS.ql @@ -83,14 +83,17 @@ import javascript * A branch in a disjunction that is the root node in a literal, or a literal * whose root node is not a disjunction. */ -class RegExpRoot extends @regexpterm { // RegExpTerm is abstract, so do not extend it. +class RegExpRoot extends @regexpterm { + // RegExpTerm is abstract, so do not extend it. RegExpRoot() { - exists (RegExpLiteral literal, RegExpAlt alt | alt.getParent() = literal | - this = alt.getAChild()) + exists(RegExpLiteral literal, RegExpAlt alt | alt.getParent() = literal | + this = alt.getAChild() + ) or - exists (RegExpLiteral literal | - not exists (RegExpAlt alt | alt.getParent() = literal) and - this.(RegExpTerm).getParent() = literal) + exists(RegExpLiteral literal | + not exists(RegExpAlt alt | alt.getParent() = literal) and + this.(RegExpTerm).getParent() = literal + ) } /** @@ -98,10 +101,9 @@ class RegExpRoot extends @regexpterm { // RegExpTerm is abstract, so do not exte */ predicate isRelevant() { // there is at least one repetition - exists (RegExpRepetition rep | getRoot(rep) = this) - and + exists(RegExpRepetition rep | getRoot(rep) = this) and // there are no lookbehinds - not exists (RegExpLookbehind lbh | getRoot(lbh) = this) + not exists(RegExpLookbehind lbh | getRoot(lbh) = this) } string toString() { result = this.(RegExpTerm).toString() } @@ -132,8 +134,7 @@ RegExpRoot getRoot(RegExpTerm term) { */ newtype TInputSymbol = /** An input symbol corresponding to character `c`. */ - Char(string c) { c = any(RegExpConstant cc).getValue() } - or + Char(string c) { c = any(RegExpConstant cc).getValue() } or /** * An input symbol representing all characters matched by * (positive, non-universal) character class `recc`. @@ -142,18 +143,14 @@ newtype TInputSymbol = getRoot(recc).isRelevant() and not recc.isInverted() and not isUniversalClass(recc) - } - or + } or /** An input symbol representing all characters matched by `.`. */ - Dot() - or + Dot() or /** An input symbol representing all characters. */ - Any() - or + Any() or /** An epsilon transition in the automaton. */ Epsilon() - /** * Holds if character class `cc` matches all characters. */ @@ -163,9 +160,10 @@ predicate isUniversalClass(RegExpCharacterClass cc) { or // [\w\W] and similar not cc.isInverted() and - exists (string cce1, string cce2 | + exists(string cce1, string cce2 | cce1 = cc.getAChild().(RegExpCharacterClassEscape).getValue() and - cce2 = cc.getAChild().(RegExpCharacterClassEscape).getValue() | + cce2 = cc.getAChild().(RegExpCharacterClassEscape).getValue() + | cce1 != cce2 and cce1.toLowerCase() = cce2.toLowerCase() ) } @@ -174,14 +172,15 @@ predicate isUniversalClass(RegExpCharacterClass cc) { * An abstract input symbol, representing a set of concrete characters. */ class InputSymbol extends TInputSymbol { - InputSymbol() { - not this instanceof Epsilon - } + InputSymbol() { not this instanceof Epsilon } string toString() { - this = Char(result) or - result = any(RegExpCharacterClass recc | this = CharClass(recc)).toString() or - this = Dot() and result = "." or + this = Char(result) + or + result = any(RegExpCharacterClass recc | this = CharClass(recc)).toString() + or + this = Dot() and result = "." + or this = Any() and result = "[^]" } } @@ -192,20 +191,26 @@ class InputSymbol extends TInputSymbol { string getCCLowerBound(RegExpTerm t) { t.getParent() instanceof RegExpCharacterClass and ( - result = t.(RegExpConstant).getValue() or - t.(RegExpCharacterRange).isRange(result, _) or - exists (string name | name = t.(RegExpCharacterClassEscape).getValue() | - name = "w" and result = "0" or - name = "W" and result = "" or - name = "s" and result = "" or - name = "S" and result = "") + result = t.(RegExpConstant).getValue() + or + t.(RegExpCharacterRange).isRange(result, _) + or + exists(string name | name = t.(RegExpCharacterClassEscape).getValue() | + name = "w" and result = "0" + or + name = "W" and result = "" + or + name = "s" and result = "" + or + name = "S" and result = "" + ) ) } /** * The highest character used in a regular expression. Used to represent intervals without an upper bound. */ -string highestCharacter() { result = max(RegExpConstant c || c.getValue()) } +string highestCharacter() { result = max(RegExpConstant c | | c.getValue()) } /** * Gets an upper bound on the characters matched by the given character class term. @@ -213,13 +218,19 @@ string highestCharacter() { result = max(RegExpConstant c || c.getValue()) } string getCCUpperBound(RegExpTerm t) { t.getParent() instanceof RegExpCharacterClass and ( - result = t.(RegExpConstant).getValue() or - t.(RegExpCharacterRange).isRange(_, result) or - exists (string name | name = t.(RegExpCharacterClassEscape).getValue() | - name = "w" and result = "z" or - name = "W" and result = highestCharacter() or - name = "s" and result = highestCharacter() or - name = "S" and result = highestCharacter()) + result = t.(RegExpConstant).getValue() + or + t.(RegExpCharacterRange).isRange(_, result) + or + exists(string name | name = t.(RegExpCharacterClassEscape).getValue() | + name = "w" and result = "z" + or + name = "W" and result = highestCharacter() + or + name = "s" and result = highestCharacter() + or + name = "S" and result = highestCharacter() + ) ) } @@ -228,10 +239,11 @@ string getCCUpperBound(RegExpTerm t) { * in the interval `lo-hi`. */ predicate hasBounds(RegExpRoot l, InputSymbol s, string lo, string hi) { - exists (RegExpCharacterClass cc | s = CharClass(cc) | + exists(RegExpCharacterClass cc | s = CharClass(cc) | l = getRoot(cc) and lo = min(getCCLowerBound(cc.getAChild())) and - hi = max(getCCUpperBound(cc.getAChild()))) + hi = max(getCCUpperBound(cc.getAChild())) + ) } /** @@ -240,21 +252,18 @@ predicate hasBounds(RegExpRoot l, InputSymbol s, string lo, string hi) { * This predicate is over-approximate; it is only used for pruning the search space. */ predicate compatible(InputSymbol s1, InputSymbol s2) { - exists (RegExpRoot l, string lo1, string lo2, string hi1, string hi2 | - hasBounds(l, s1, lo1, hi1) and hasBounds(l, s2, lo2, hi2) and - max(string s | s = lo1 or s = lo2) <= min(string s | s = hi1 or s = hi2)) + exists(RegExpRoot l, string lo1, string lo2, string hi1, string hi2 | + hasBounds(l, s1, lo1, hi1) and + hasBounds(l, s2, lo2, hi2) and + max(string s | s = lo1 or s = lo2) <= min(string s | s = hi1 or s = hi2) + ) or - exists (intersect(s1, s2)) + exists(intersect(s1, s2)) } newtype TState = - Match(RegExpTerm t) { - getRoot(t).isRelevant() - } - or - Accept(RegExpRoot l) { - l.isRelevant() - } + Match(RegExpTerm t) { getRoot(t).isRelevant() } or + Accept(RegExpRoot l) { l.isRelevant() } /** * A state in the NFA corresponding to a regular expression. @@ -267,24 +276,21 @@ newtype TState = class State extends TState { RegExpParent repr; - State() { - this = Match(repr) or this = Accept(repr) - } + State() { this = Match(repr) or this = Accept(repr) } string toString() { - result = "Match(" + (RegExpTerm)repr + ")" or - result = "Accept(" + (RegExpRoot)repr + ")" + result = "Match(" + repr.(RegExpTerm) + ")" or + result = "Accept(" + repr.(RegExpRoot) + ")" } - Location getLocation() { - result = repr.getLocation() - } + Location getLocation() { result = repr.getLocation() } } class EdgeLabel extends TInputSymbol { string toString() { - this = Epsilon() and result = "" or - exists (InputSymbol s | this = s and result = s.toString()) + this = Epsilon() and result = "" + or + exists(InputSymbol s | this = s and result = s.toString()) } } @@ -292,78 +298,63 @@ class EdgeLabel extends TInputSymbol { * Gets a state the NFA may be in after matching `t`. */ State after(RegExpTerm t) { - exists (RegExpAlt alt | t = alt.getAChild() | - result = after(alt) - ) + exists(RegExpAlt alt | t = alt.getAChild() | result = after(alt)) or - exists (RegExpSequence seq, int i | t = seq.getChild(i) | - result = Match(seq.getChild(i+1)) or - i+1 = seq.getNumChild() and result = after(seq) + exists(RegExpSequence seq, int i | t = seq.getChild(i) | + result = Match(seq.getChild(i + 1)) + or + i + 1 = seq.getNumChild() and result = after(seq) ) or - exists (RegExpGroup grp | t = grp.getAChild() | - result = after(grp) - ) + exists(RegExpGroup grp | t = grp.getAChild() | result = after(grp)) or - exists (RegExpStar star | t = star.getAChild() | - result = Match(star) - ) + exists(RegExpStar star | t = star.getAChild() | result = Match(star)) or - exists (RegExpPlus plus | t = plus.getAChild() | + exists(RegExpPlus plus | t = plus.getAChild() | result = Match(plus) or result = after(plus) ) or - exists (RegExpOpt opt | t = opt.getAChild() | - result = after(opt) - ) + exists(RegExpOpt opt | t = opt.getAChild() | result = after(opt)) or - exists (RegExpRoot root | t = root | - result = Accept(root) - ) + exists(RegExpRoot root | t = root | result = Accept(root)) } /** * Holds if the NFA has a transition from `q1` to `q2` labelled with `lbl`. */ predicate delta(State q1, EdgeLabel lbl, State q2) { - exists (RegExpConstant s | - q1 = Match(s) and lbl = Char(s.getValue()) and q2 = after(s) - ) + exists(RegExpConstant s | q1 = Match(s) and lbl = Char(s.getValue()) and q2 = after(s)) or - exists (RegExpDot dot, RegExpLiteral rel | - q1 = Match(dot) and q2 = after(dot) and rel = dot.getLiteral() | + exists(RegExpDot dot, RegExpLiteral rel | + q1 = Match(dot) and q2 = after(dot) and rel = dot.getLiteral() + | if rel.isDotAll() then lbl = Any() else lbl = Dot() ) or - exists (RegExpCharacterClass cc | - isUniversalClass(cc) and q1 = Match(cc) and lbl = Any() and q2 = after(cc) or + exists(RegExpCharacterClass cc | + isUniversalClass(cc) and q1 = Match(cc) and lbl = Any() and q2 = after(cc) + or q1 = Match(cc) and lbl = CharClass(cc) and q2 = after(cc) ) or - exists (RegExpAlt alt | lbl = Epsilon() | - q1 = Match(alt) and q2 = Match(alt.getAChild()) - ) + exists(RegExpAlt alt | lbl = Epsilon() | q1 = Match(alt) and q2 = Match(alt.getAChild())) or - exists (RegExpSequence seq | lbl = Epsilon() | - q1 = Match(seq) and q2 = Match(seq.getChild(0)) - ) + exists(RegExpSequence seq | lbl = Epsilon() | q1 = Match(seq) and q2 = Match(seq.getChild(0))) or - exists (RegExpGroup grp | lbl = Epsilon() | - q1 = Match(grp) and q2 = Match(grp.getChild(0)) - ) + exists(RegExpGroup grp | lbl = Epsilon() | q1 = Match(grp) and q2 = Match(grp.getChild(0))) or - exists (RegExpStar star | lbl = Epsilon() | - q1 = Match(star) and q2 = Match(star.getChild(0)) or + exists(RegExpStar star | lbl = Epsilon() | + q1 = Match(star) and q2 = Match(star.getChild(0)) + or q1 = Match(star) and q2 = after(star) ) or - exists (RegExpPlus plus | lbl = Epsilon() | - q1 = Match(plus) and q2 = Match(plus.getChild(0)) - ) + exists(RegExpPlus plus | lbl = Epsilon() | q1 = Match(plus) and q2 = Match(plus.getChild(0))) or - exists (RegExpOpt opt | lbl = Epsilon() | - q1 = Match(opt) and q2 = Match(opt.getChild(0)) or + exists(RegExpOpt opt | lbl = Epsilon() | + q1 = Match(opt) and q2 = Match(opt.getChild(0)) + or q1 = Match(opt) and q2 = after(opt) ) } @@ -371,26 +362,19 @@ predicate delta(State q1, EdgeLabel lbl, State q2) { /** * Gets a state that `q` has an epsilon transition to. */ -State epsilonSucc(State q) { - delta(q, Epsilon(), result) -} +State epsilonSucc(State q) { delta(q, Epsilon(), result) } /** * Gets a state that has an epsilon transition to `q`. */ -State epsilonPred(State q) { - q = epsilonSucc(result) -} +State epsilonPred(State q) { q = epsilonSucc(result) } /** * Holds if there is a state `q` that can be reached from `q1` * along epsilon edges, such that there is a transition from * `q` to `q2` that consumes symbol `s`. */ -predicate deltaClosed(State q1, InputSymbol s, State q2) { - delta(epsilonSucc*(q1), s, q2) -} - +predicate deltaClosed(State q1, InputSymbol s, State q2) { delta(epsilonSucc*(q1), s, q2) } /** * A state in the product automaton. @@ -411,6 +395,7 @@ newtype TStatePair = class StatePair extends TStatePair { State q1; + State q2; StatePair() { this = MkStatePair(q1, q2) } @@ -448,15 +433,20 @@ int statePairDist(StatePair q, StatePair r) = */ pragma[noopt] predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, State r2) { - exists (State q1, State q2 | - q1 = epsilonSucc*(q) and delta(q1, s1, r1) and - q2 = epsilonSucc*(q) and delta(q2, s2, r2) and + exists(State q1, State q2 | + q1 = epsilonSucc*(q) and + delta(q1, s1, r1) and + q2 = epsilonSucc*(q) and + delta(q2, s2, r2) and // Use pragma[noopt] to prevent compatible(s1,s2) from being the starting point of the join. // From (s1,s2) it would find a huge number of intermediate state pairs (q1,q2) originating from different literals, // and discover at the end that no `q` can reach both `q1` and `q2` by epsilon transitions. - compatible(s1, s2) | - s1 != s2 or - r1 != r2 or + compatible(s1, s2) + | + s1 != s2 + or + r1 != r2 + or r1 = r2 and q1 != q2 ) } @@ -466,9 +456,7 @@ predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, State r2) { * components of `r` labelled with `s1` and `s2`, respectively. */ predicate step(StatePair q, InputSymbol s1, InputSymbol s2, StatePair r) { - exists (State r1, State r2 | - step(q, s1, s2, r1, r2) and r = mkStatePair(r1, r2) - ) + exists(State r1, State r2 | step(q, s1, s2, r1, r2) and r = mkStatePair(r1, r2)) } /** @@ -476,8 +464,9 @@ predicate step(StatePair q, InputSymbol s1, InputSymbol s2, StatePair r) { * labelled with `s1` and `s2`, respectively. */ predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, State r2) { - exists (State q1, State q2 | q = MkStatePair(q1, q2) | - deltaClosed(q1, s1, r1) and deltaClosed(q2, s2, r2) and + exists(State q1, State q2 | q = MkStatePair(q1, q2) | + deltaClosed(q1, s1, r1) and + deltaClosed(q2, s2, r2) and compatible(s1, s2) ) } @@ -489,10 +478,11 @@ predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, State r2) newtype Trace = Nil() or Step(InputSymbol s1, InputSymbol s2, Trace t) { - exists (StatePair p | + exists(StatePair p | isReachableFromFork(_, p, t, _) and step(p, s1, s2, _) - ) or + ) + or t = Nil() and isFork(_, s1, s2, _, _) } @@ -500,28 +490,42 @@ newtype Trace = * Gets a character that is represented by both `c` and `d`. */ string intersect(InputSymbol c, InputSymbol d) { - c = Char(result) and ( - d = Char(result) or - exists (RegExpCharacterClass cc | d = CharClass(cc) | - exists (RegExpTerm child | child = cc.getAChild() | - result = child.(RegExpConstant).getValue() or - exists (string lo, string hi | child.(RegExpCharacterRange).isRange(lo, hi) | + c = Char(result) and + ( + d = Char(result) + or + exists(RegExpCharacterClass cc | d = CharClass(cc) | + exists(RegExpTerm child | child = cc.getAChild() | + result = child.(RegExpConstant).getValue() + or + exists(string lo, string hi | child.(RegExpCharacterRange).isRange(lo, hi) | lo <= result and result <= hi ) ) - ) or - d = Dot() and not (result = "\n" or result = "\r") or + ) + or + d = Dot() and + not (result = "\n" or result = "\r") + or d = Any() - ) or - exists (RegExpCharacterClass cc | c = CharClass(cc) and result = choose(cc) | - d = CharClass(cc) or - d = Dot() and not (result = "\n" or result = "\r") or + ) + or + exists(RegExpCharacterClass cc | c = CharClass(cc) and result = choose(cc) | + d = CharClass(cc) + or + d = Dot() and + not (result = "\n" or result = "\r") + or d = Any() - ) or - c = Dot() and ( - d = Dot() and result = "a" or + ) + or + c = Dot() and + ( + d = Dot() and result = "a" + or d = Any() and result = "a" - ) or + ) + or c = Any() and d = Any() and result = "a" or result = intersect(d, c) @@ -532,11 +536,11 @@ string intersect(InputSymbol c, InputSymbol d) { */ string choose(RegExpCharacterClass cc) { result = min(string c | - exists (RegExpTerm child | child = cc.getAChild() | - c = child.(RegExpConstant).getValue() or - child.(RegExpCharacterRange).isRange(c, _) + exists(RegExpTerm child | child = cc.getAChild() | + c = child.(RegExpConstant).getValue() or + child.(RegExpCharacterRange).isRange(c, _) + ) ) - ) } /** @@ -545,7 +549,7 @@ string choose(RegExpCharacterClass cc) { string concretise(Trace t) { t = Nil() and result = "" or - exists (InputSymbol s1, InputSymbol s2, Trace rest | t = Step(s1, s2, rest) | + exists(InputSymbol s1, InputSymbol s2, Trace rest | t = Step(s1, s2, rest) | result = concretise(rest) + intersect(s1, s2) ) } @@ -555,15 +559,15 @@ string concretise(Trace t) { * a path from `r` back to `(fork, fork)` with `rem` steps. */ predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) { - exists (InputSymbol s1, InputSymbol s2, State q1, State q2 | + exists(InputSymbol s1, InputSymbol s2, State q1, State q2 | isFork(fork, s1, s2, q1, q2) and r = MkStatePair(q1, q2) and w = Step(s1, s2, Nil()) and rem = statePairDist(r, MkStatePair(fork, fork)) ) or - exists (StatePair p, Trace v, InputSymbol s1, InputSymbol s2 | - isReachableFromFork(fork, p, v, rem+1) and + exists(StatePair p, Trace v, InputSymbol s1, InputSymbol s2 | + isReachableFromFork(fork, p, v, rem + 1) and step(p, s1, s2, r) and w = Step(s1, s2, v) and rem > 0 @@ -583,12 +587,12 @@ StatePair getAForkPair(State fork) { * Holds if `fork` is a pumpable fork with word `w`. */ predicate isPumpable(State fork, string w) { - exists (StatePair q, Trace t | + exists(StatePair q, Trace t | isReachableFromFork(fork, q, t, _) and ( q = getAForkPair(fork) and w = concretise(t) or - exists (InputSymbol s1, InputSymbol s2 | + exists(InputSymbol s1, InputSymbol s2 | step(q, s1, s2, getAForkPair(fork)) and w = concretise(Step(s1, s2, t)) ) @@ -608,8 +612,12 @@ predicate isPumpable(State fork, string w) { */ State process(State fork, string w, int i) { isPumpable(fork, w) and - exists (State prev | i = 0 and prev = fork or prev = process(fork, w, i-1) | - exists (InputSymbol s | + exists(State prev | + i = 0 and prev = fork + or + prev = process(fork, w, i - 1) + | + exists(InputSymbol s | deltaClosed(prev, s, result) and exists(intersect(Char(w.charAt(i)), s)) ) @@ -626,7 +634,10 @@ string escape(string s) { } from RegExpTerm t, string c -where c = min(string w | isPumpable(Match(t), w)) and not isPumpable(epsilonSucc+(Match(t)), _) and - not epsilonSucc*(process(Match(t), c, c.length()-1)) = Accept(_) -select t, "This part of the regular expression may cause exponential backtracking on strings " + - "containing many repetitions of '" + escape(c) + "'." +where + c = min(string w | isPumpable(Match(t), w)) and + not isPumpable(epsilonSucc+(Match(t)), _) and + not epsilonSucc*(process(Match(t), c, c.length() - 1)) = Accept(_) +select t, + "This part of the regular expression may cause exponential backtracking on strings " + + "containing many repetitions of '" + escape(c) + "'." diff --git a/javascript/ql/src/Performance/ReassignParameterAndUseArguments.ql b/javascript/ql/src/Performance/ReassignParameterAndUseArguments.ql index 0890336e52ec..f6dafcdd95a9 100644 --- a/javascript/ql/src/Performance/ReassignParameterAndUseArguments.ql +++ b/javascript/ql/src/Performance/ReassignParameterAndUseArguments.ql @@ -13,10 +13,12 @@ import javascript from Function f, SimpleParameter p, VarAccess assgn -where p = f.getAParameter() and - f.usesArgumentsObject() and - assgn = p.getVariable().getAnAccess() and - assgn.isLValue() -select p, "This parameter is reassigned $@, " + - "which may prevent optimization because the surrounding function " + - "uses the arguments object.", assgn, "here" \ No newline at end of file +where + p = f.getAParameter() and + f.usesArgumentsObject() and + assgn = p.getVariable().getAnAccess() and + assgn.isLValue() +select p, + "This parameter is reassigned $@, " + + "which may prevent optimization because the surrounding function " + + "uses the arguments object.", assgn, "here" diff --git a/javascript/ql/src/React/DirectStateMutation.ql b/javascript/ql/src/React/DirectStateMutation.ql index ee424635c7d9..6e3d63a52148 100644 --- a/javascript/ql/src/React/DirectStateMutation.ql +++ b/javascript/ql/src/React/DirectStateMutation.ql @@ -13,7 +13,8 @@ import semmle.javascript.frameworks.React from DataFlow::PropWrite pwn, ReactComponent c -where pwn.getBase() = c.getAStateAccess() and - // writes in constructors are ok - not pwn.getContainer() instanceof Constructor +where + pwn.getBase() = c.getAStateAccess() and + // writes in constructors are ok + not pwn.getContainer() instanceof Constructor select pwn, "Use `setState` instead of directly modifying component state." diff --git a/javascript/ql/src/React/InconsistentStateUpdate.ql b/javascript/ql/src/React/InconsistentStateUpdate.ql index 7ad78c87d9e5..31625b2187af 100644 --- a/javascript/ql/src/React/InconsistentStateUpdate.ql +++ b/javascript/ql/src/React/InconsistentStateUpdate.ql @@ -27,18 +27,15 @@ DataFlow::PropRead getAnUnsafeAccess(ReactComponent c) { * access. */ DataFlow::PropRead getAnOutermostUnsafeAccess(ReactComponent c) { - result = getAnUnsafeAccess(c) - and - not exists (DataFlow::PropRead outer | outer = getAnUnsafeAccess(c) | - result = outer.getBase() - ) + result = getAnUnsafeAccess(c) and + not exists(DataFlow::PropRead outer | outer = getAnUnsafeAccess(c) | result = outer.getBase()) } /** * Gets a property write through `setState` for state property `name` of `c`. */ DataFlow::PropWrite getAStateUpdate(ReactComponent c, string name) { - exists (DataFlow::ObjectLiteralNode newState | + exists(DataFlow::ObjectLiteralNode newState | newState.flowsTo(c.getAMethodCall("setState").getArgument(0)) and result = newState.getAPropertyWrite(name) ) @@ -48,7 +45,7 @@ DataFlow::PropWrite getAStateUpdate(ReactComponent c, string name) { * Gets a property write through `setState` for a state property of `c` that is only written at this property write. */ DataFlow::PropWrite getAUniqueStateUpdate(ReactComponent c) { - exists (string name | + exists(string name | count(getAStateUpdate(c, name)) = 1 and result = getAStateUpdate(c, name) ) @@ -58,7 +55,7 @@ DataFlow::PropWrite getAUniqueStateUpdate(ReactComponent c) { * Holds for "self dependent" component state updates. E.g. `this.setState({toggled: !this.state.toggled})`. */ predicate isAStateUpdateFromSelf(ReactComponent c, DataFlow::PropWrite pwn, DataFlow::PropRead prn) { - exists (string name | + exists(string name | pwn = getAStateUpdate(c, name) and c.getADirectStateAccess().flowsTo(prn.getBase()) and prn.getPropertyName() = name and @@ -68,13 +65,14 @@ predicate isAStateUpdateFromSelf(ReactComponent c, DataFlow::PropWrite pwn, Data } from ReactComponent c, MethodCallExpr setState, Expr getState -where setState = c.getAMethodCall("setState").asExpr() and - getState = getAnOutermostUnsafeAccess(c).asExpr() and - getState.getParentExpr*() = setState.getArgument(0) and - getState.getEnclosingFunction() = setState.getEnclosingFunction() and - // ignore self-updates that only occur in one location: `setState({toggled: !this.state.toggled})`, they are most likely safe in practice - not exists (DataFlow::PropWrite pwn | - pwn = getAUniqueStateUpdate(c) and - isAStateUpdateFromSelf(c, pwn, DataFlow::valueNode(getState)) - ) -select setState, "Component state update uses $@.", getState, "potentially inconsistent value" \ No newline at end of file +where + setState = c.getAMethodCall("setState").asExpr() and + getState = getAnOutermostUnsafeAccess(c).asExpr() and + getState.getParentExpr*() = setState.getArgument(0) and + getState.getEnclosingFunction() = setState.getEnclosingFunction() and + // ignore self-updates that only occur in one location: `setState({toggled: !this.state.toggled})`, they are most likely safe in practice + not exists(DataFlow::PropWrite pwn | + pwn = getAUniqueStateUpdate(c) and + isAStateUpdateFromSelf(c, pwn, DataFlow::valueNode(getState)) + ) +select setState, "Component state update uses $@.", getState, "potentially inconsistent value" diff --git a/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql b/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql index bffe9e0b2e7b..d1798ee51e8e 100644 --- a/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql +++ b/javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql @@ -15,11 +15,9 @@ import javascript * A call that invokes a method on its own receiver. */ class CallOnSelf extends DataFlow::CallNode { - CallOnSelf() { - exists (Function binder | - binder = getEnclosingFunction().getThisBinder() | - exists (DataFlow::ThisNode thiz | + exists(Function binder | binder = getEnclosingFunction().getThisBinder() | + exists(DataFlow::ThisNode thiz | this = thiz.getAMethodCall(_) and thiz.getBinder().getAstNode() = binder ) @@ -31,32 +29,25 @@ class CallOnSelf extends DataFlow::CallNode { /** * Gets a `CallOnSelf` in the callee of this call. */ - CallOnSelf getACalleCallOnSelf() { - result.getEnclosingFunction() = this.getACallee() - } - + CallOnSelf getACalleCallOnSelf() { result.getEnclosingFunction() = this.getACallee() } } /** * A call that is definitely invoked by the caller, unless an exception occurs. */ class UnconditionalCallOnSelf extends CallOnSelf { - - UnconditionalCallOnSelf() { - isUnconditionalCall(this) - } + UnconditionalCallOnSelf() { isUnconditionalCall(this) } override UnconditionalCallOnSelf getACalleCallOnSelf() { result = CallOnSelf.super.getACalleCallOnSelf() } - } /** * Holds if `call` is guaranteed to occur in its enclosing function, unless an exception occurs. */ predicate isUnconditionalCall(DataFlow::CallNode call) { - exists (ReachableBasicBlock callBlock, ReachableBasicBlock entryBlock | + exists(ReachableBasicBlock callBlock, ReachableBasicBlock entryBlock | callBlock.postDominates(entryBlock) and callBlock = call.getBasicBlock() and entryBlock = call.getEnclosingFunction().getEntryBB() @@ -64,10 +55,11 @@ predicate isUnconditionalCall(DataFlow::CallNode call) { } predicate isStateUpdateMethodCall(DataFlow::MethodCallNode mce) { - exists (string updateMethodName | + exists(string updateMethodName | updateMethodName = "setState" or updateMethodName = "replaceState" or - updateMethodName = "forceUpdate" | + updateMethodName = "forceUpdate" + | mce.getMethodName() = updateMethodName ) } @@ -76,7 +68,6 @@ predicate isStateUpdateMethodCall(DataFlow::MethodCallNode mce) { * A React component method in which state updates may have surprising effects. */ class StateUpdateVolatileMethod extends Function { - string methodName; StateUpdateVolatileMethod() { @@ -84,15 +75,15 @@ class StateUpdateVolatileMethod extends Function { // - componentWillUnmount // - componentsWillMount // - componentsDidMount - - exists (ReactComponent c | + exists(ReactComponent c | methodName = "componentDidUnmount" or methodName = "componentDidUpdate" or methodName = "componentWillUpdate" or methodName = "getDefaultProps" or methodName = "getInitialState" or methodName = "render" or - methodName = "shouldComponentUpdate" | + methodName = "shouldComponentUpdate" + | this = c.getInstanceMethod(methodName) ) or @@ -104,21 +95,24 @@ class StateUpdateVolatileMethod extends Function { * Holds if conditional state updates are benign in this method. */ predicate conditionalStateUpatesAreBenign() { - methodName = "componentDidUpdate" or - methodName = "componentWillUpdate" or - methodName = "shouldComponentUpdate" + methodName = "componentDidUpdate" or + methodName = "componentWillUpdate" or + methodName = "shouldComponentUpdate" } - } -from StateUpdateVolatileMethod root, CallOnSelf initCall, DataFlow::MethodCallNode stateUpdate, - string callDescription -where initCall.getEnclosingFunction() = root and - stateUpdate = initCall.getACalleCallOnSelf*() and - isStateUpdateMethodCall(stateUpdate) and - if root.conditionalStateUpatesAreBenign() then - initCall instanceof UnconditionalCallOnSelf and - callDescription = "Unconditional call" - else - callDescription = "Call" -select initCall, callDescription + " to state update method $@ is not allowed from within this method.", stateUpdate, " ." + stateUpdate.getMethodName() +from + StateUpdateVolatileMethod root, CallOnSelf initCall, DataFlow::MethodCallNode stateUpdate, + string callDescription +where + initCall.getEnclosingFunction() = root and + stateUpdate = initCall.getACalleCallOnSelf*() and + isStateUpdateMethodCall(stateUpdate) and + if root.conditionalStateUpatesAreBenign() + then + initCall instanceof UnconditionalCallOnSelf and + callDescription = "Unconditional call" + else callDescription = "Call" +select initCall, + callDescription + " to state update method $@ is not allowed from within this method.", + stateUpdate, " ." + stateUpdate.getMethodName() diff --git a/javascript/ql/src/React/UnusedOrUndefinedStateProperty.ql b/javascript/ql/src/React/UnusedOrUndefinedStateProperty.ql index 629740fa35d5..721d6639ac7e 100644 --- a/javascript/ql/src/React/UnusedOrUndefinedStateProperty.ql +++ b/javascript/ql/src/React/UnusedOrUndefinedStateProperty.ql @@ -1,6 +1,6 @@ /** * @name Unused or undefined state property - * @description Unused or undefined component state properties may be a symptom of a bug and should be examined carefully. + * @description Unused or undefined component state properties may be a symptom of a bug and should be examined carefully. * @kind problem * @problem.severity warning * @id js/react/unused-or-undefined-state-property @@ -13,7 +13,6 @@ import semmle.javascript.frameworks.React import semmle.javascript.RestrictedLocations - /** * Gets the source of a future, present or past state object of `c`. */ @@ -34,7 +33,7 @@ DataFlow::PropRef getAPotentialStateAccess(ReactComponent c) { * Holds if the state object of `c` escapes from the scope of this file's query. */ predicate hasAStateEscape(ReactComponent c) { - exists (DataFlow::InvokeNode invk | + exists(DataFlow::InvokeNode invk | not invk = c.getAMethodCall("setState") and potentialStateSource(c).flowsTo(invk.getAnArgument()) ) @@ -44,11 +43,12 @@ predicate hasAStateEscape(ReactComponent c) { * Holds if there exists a write for a state property of `c` that uses an unknown property name. */ predicate hasUnknownStatePropertyWrite(ReactComponent c) { - exists (DataFlow::PropWrite pwn | + exists(DataFlow::PropWrite pwn | pwn = getAPotentialStateAccess(c) and - not exists (pwn.getPropertyName()) - ) or - exists (DataFlow::SourceNode source | + not exists(pwn.getPropertyName()) + ) + or + exists(DataFlow::SourceNode source | source = c.getACandidateStateSource() and not source instanceof DataFlow::ObjectLiteralNode ) @@ -58,13 +58,12 @@ predicate hasUnknownStatePropertyWrite(ReactComponent c) { * Holds if there exists a read for a state property of `c` that uses an unknown property name. */ predicate hasUnknownStatePropertyRead(ReactComponent c) { - exists (DataFlow::PropRead prn | + exists(DataFlow::PropRead prn | prn = getAPotentialStateAccess(c) and - not exists (prn.getPropertyName()) - ) or - exists (SpreadElement spread | - potentialStateSource(c).flowsToExpr(spread.getOperand()) + not exists(prn.getPropertyName()) ) + or + exists(SpreadElement spread | potentialStateSource(c).flowsToExpr(spread.getOperand())) } /** @@ -79,8 +78,7 @@ predicate usesMixins(ES5Component c) { */ DataFlow::PropWrite getAnUnusedStateProperty(ReactComponent c) { result = getAPotentialStateAccess(c) and - exists (string name | - name = result.getPropertyName() | + exists(string name | name = result.getPropertyName() | not exists(DataFlow::PropRead prn | prn = getAPotentialStateAccess(c) and prn.getPropertyName() = name @@ -93,8 +91,7 @@ DataFlow::PropWrite getAnUnusedStateProperty(ReactComponent c) { */ DataFlow::PropRead getAnUndefinedStateProperty(ReactComponent c) { result = getAPotentialStateAccess(c) and - exists (string name | - name = result.getPropertyName() | + exists(string name | name = result.getPropertyName() | not exists(DataFlow::PropWrite pwn | pwn = getAPotentialStateAccess(c) and pwn.getPropertyName() = name @@ -103,15 +100,20 @@ DataFlow::PropRead getAnUndefinedStateProperty(ReactComponent c) { } from ReactComponent c, DataFlow::PropRef n, string action, string nonAction -where ( - action = "written" and nonAction = "read" and - n = getAnUnusedStateProperty(c) and - not hasUnknownStatePropertyRead(c) - or - action = "read" and nonAction = "written" and - n = getAnUndefinedStateProperty(c) and - not hasUnknownStatePropertyWrite(c) - ) and - not hasAStateEscape(c) and - not usesMixins(c) -select (FirstLineOf)c, "Component state property '" + n.getPropertyName() + "' is $@, but it is never " + nonAction + ".", n, action +where + ( + action = "written" and + nonAction = "read" and + n = getAnUnusedStateProperty(c) and + not hasUnknownStatePropertyRead(c) + or + action = "read" and + nonAction = "written" and + n = getAnUndefinedStateProperty(c) and + not hasUnknownStatePropertyWrite(c) + ) and + not hasAStateEscape(c) and + not usesMixins(c) +select c.(FirstLineOf), + "Component state property '" + n.getPropertyName() + "' is $@, but it is never " + nonAction + ".", + n, action diff --git a/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql b/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql index 01335fbbe0a5..4cf49cbdc7b1 100644 --- a/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql +++ b/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql @@ -18,47 +18,40 @@ import javascript class Configuration extends TaintTracking::Configuration { Configuration() { this = "IncompleteHostnameRegExpTracking" } - override - predicate isSource(DataFlow::Node source) { + override predicate isSource(DataFlow::Node source) { isIncompleteHostNameRegExpPattern(source.asExpr().getStringValue(), _) } - override - predicate isSink(DataFlow::Node sink) { - isInterpretedAsRegExp(sink) - } - + override predicate isSink(DataFlow::Node sink) { isInterpretedAsRegExp(sink) } } - /** * Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`, * and `pattern` contains a subtle mistake that allows it to match unexpected hosts. */ bindingset[pattern] predicate isIncompleteHostNameRegExpPattern(string pattern, string hostPart) { - hostPart = pattern.regexpCapture( - "(?i).*" + - // an unescaped single `.` - "(? B.length, or (A.length - B.length) > 0 - not exists (RelationalComparison compare | + not exists(RelationalComparison compare | isDerivedFromLength(compare.getAnOperand().flow(), indexOf.getReceiver()) and isDerivedFromLength(compare.getAnOperand().flow(), indexOf.getArgument(0)) ) and - // Check for indexOf being -1 - not exists (EqualityTest test, Expr minusOne | + not exists(EqualityTest test, Expr minusOne | test.hasOperands(indexOf.getAnEquivalentIndexOfCall().getAUse(), minusOne) and minusOne.getIntValue() = -1 ) and - // Check for indexOf being >1, or >=0, etc - not exists (RelationalComparison test | + not exists(RelationalComparison test | test.getGreaterOperand() = indexOf.getAnEquivalentIndexOfCall().getAUse() and - exists (int value | value = test.getLesserOperand().getIntValue() | + exists(int value | value = test.getLesserOperand().getIntValue() | value >= 0 or not test.isInclusive() and - value = -1) + value = -1 + ) ) } - IndexOfCall getIndexOf() { - result = indexOf - } + IndexOfCall getIndexOf() { result = indexOf } } from UnsafeIndexOfComparison comparison -select comparison, "This suffix check is missing a length comparison to correctly handle " + comparison.getIndexOf().getMethodName() + " returning -1." +select comparison, + "This suffix check is missing a length comparison to correctly handle " + + comparison.getIndexOf().getMethodName() + " returning -1." diff --git a/javascript/ql/src/Security/CWE-022/TaintedPath.ql b/javascript/ql/src/Security/CWE-022/TaintedPath.ql index b545fabafedd..f7f79c630425 100644 --- a/javascript/ql/src/Security/CWE-022/TaintedPath.ql +++ b/javascript/ql/src/Security/CWE-022/TaintedPath.ql @@ -20,5 +20,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "This path depends on $@.", - source.getNode(), "a user-provided value" +select sink.getNode(), source, sink, "This path depends on $@.", source.getNode(), + "a user-provided value" diff --git a/javascript/ql/src/Security/CWE-078/CommandInjection.ql b/javascript/ql/src/Security/CWE-078/CommandInjection.ql index 2e356e9fdc3a..b1ae2740defd 100644 --- a/javascript/ql/src/Security/CWE-078/CommandInjection.ql +++ b/javascript/ql/src/Security/CWE-078/CommandInjection.ql @@ -17,10 +17,10 @@ import semmle.javascript.security.dataflow.CommandInjection::CommandInjection import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight -where cfg.hasFlowPath(source, sink) and - if cfg.isSinkWithHighlight(sink.getNode(), _) then - cfg.isSinkWithHighlight(sink.getNode(), highlight) - else - highlight = sink.getNode() -select highlight, source, sink, "This command depends on $@.", - source.getNode(), "a user-provided value" +where + cfg.hasFlowPath(source, sink) and + if cfg.isSinkWithHighlight(sink.getNode(), _) + then cfg.isSinkWithHighlight(sink.getNode(), highlight) + else highlight = sink.getNode() +select highlight, source, sink, "This command depends on $@.", source.getNode(), + "a user-provided value" diff --git a/javascript/ql/src/Security/CWE-079/ReflectedXss.ql b/javascript/ql/src/Security/CWE-079/ReflectedXss.ql index c9ed51c35cd0..79ff3ce4a922 100644 --- a/javascript/ql/src/Security/CWE-079/ReflectedXss.ql +++ b/javascript/ql/src/Security/CWE-079/ReflectedXss.ql @@ -18,4 +18,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.", - source.getNode(), "user-provided value" + source.getNode(), "user-provided value" diff --git a/javascript/ql/src/Security/CWE-079/StoredXss.ql b/javascript/ql/src/Security/CWE-079/StoredXss.ql index e0deee47b71b..fd7331fd0cc3 100644 --- a/javascript/ql/src/Security/CWE-079/StoredXss.ql +++ b/javascript/ql/src/Security/CWE-079/StoredXss.ql @@ -18,4 +18,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.", - source.getNode(), "stored value" + source.getNode(), "stored value" diff --git a/javascript/ql/src/Security/CWE-079/Xss.ql b/javascript/ql/src/Security/CWE-079/Xss.ql index 7364fcad010e..29f66aac49e5 100644 --- a/javascript/ql/src/Security/CWE-079/Xss.ql +++ b/javascript/ql/src/Security/CWE-079/Xss.ql @@ -17,5 +17,6 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", - source.getNode(), "user-provided value" +select sink.getNode(), source, sink, + sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(), + "user-provided value" diff --git a/javascript/ql/src/Security/CWE-089/SqlInjection.ql b/javascript/ql/src/Security/CWE-089/SqlInjection.ql index 4df8b41a06f2..f33376fcf58c 100644 --- a/javascript/ql/src/Security/CWE-089/SqlInjection.ql +++ b/javascript/ql/src/Security/CWE-089/SqlInjection.ql @@ -1,14 +1,14 @@ /** -* @name Database query built from user-controlled sources -* @description Building a database query from user-controlled sources is vulnerable to insertion of -* malicious code by the user. -* @kind path-problem -* @problem.severity error -* @precision high -* @id js/sql-injection -* @tags security -* external/cwe/cwe-089 -*/ + * @name Database query built from user-controlled sources + * @description Building a database query from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind path-problem + * @problem.severity error + * @precision high + * @id js/sql-injection + * @tags security + * external/cwe/cwe-089 + */ import javascript import semmle.javascript.security.dataflow.SqlInjection @@ -16,8 +16,11 @@ import semmle.javascript.security.dataflow.NosqlInjection import DataFlow::PathGraph from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where (cfg instanceof SqlInjection::Configuration or - cfg instanceof NosqlInjection::Configuration) and - cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "This query depends on $@.", - source.getNode(), "a user-provided value" +where + ( + cfg instanceof SqlInjection::Configuration or + cfg instanceof NosqlInjection::Configuration + ) and + cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This query depends on $@.", source.getNode(), + "a user-provided value" diff --git a/javascript/ql/src/Security/CWE-094/CodeInjection.ql b/javascript/ql/src/Security/CWE-094/CodeInjection.ql index 745dd2cd62ea..2e79e2093a44 100644 --- a/javascript/ql/src/Security/CWE-094/CodeInjection.ql +++ b/javascript/ql/src/Security/CWE-094/CodeInjection.ql @@ -19,4 +19,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ flows to here and is interpreted as code.", - source.getNode(), "User-provided value" + source.getNode(), "User-provided value" diff --git a/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql b/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql index b0715177412c..f28ce03c554c 100644 --- a/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql +++ b/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql @@ -8,10 +8,13 @@ * @tags security * external/cwe/cwe-094 */ + import javascript import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccess::UnsafeDynamicMethodAccess import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink, source, sink, "Invocation of method derived from $@ may lead to remote code execution.", source.getNode(), "user-controlled value" +select sink, source, sink, + "Invocation of method derived from $@ may lead to remote code execution.", source.getNode(), + "user-controlled value" diff --git a/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql b/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql index ecf0029732fb..7bc69c75b6c2 100644 --- a/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql +++ b/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql @@ -30,13 +30,16 @@ import javascript */ language[monotonicAggregates] string getStringValue(RegExpLiteral rl) { - exists (RegExpTerm root | root = rl.getRoot() | + exists(RegExpTerm root | root = rl.getRoot() | result = root.(RegExpConstant).getValue() or result = strictconcat(RegExpTerm ch, int i | - ch = root.(RegExpSequence).getChild(i) | - ch.(RegExpConstant).getValue() order by i - ) + ch = root.(RegExpSequence).getChild(i) + | + ch.(RegExpConstant).getValue() + order by + i + ) ) } @@ -67,7 +70,7 @@ class Replacement extends DataFlow::Node { RegExpLiteral pattern; Replacement() { - exists (DataFlow::MethodCallNode mcn | this = mcn | + exists(DataFlow::MethodCallNode mcn | this = mcn | mcn.getMethodName() = "replace" and mcn.getArgument(0).asExpr() = pattern and mcn.getNumArgument() = 2 and @@ -79,7 +82,7 @@ class Replacement extends DataFlow::Node { * Holds if this replacement replaces the string `input` with `output`. */ predicate replaces(string input, string output) { - exists (DataFlow::MethodCallNode mcn | + exists(DataFlow::MethodCallNode mcn | mcn = this and input = getStringValue(pattern) and output = mcn.getArgument(1).asExpr().getStringValue() @@ -93,7 +96,7 @@ class Replacement extends DataFlow::Node { * using `&`. */ predicate escapes(string char, string metachar) { - exists (string regexp, string repl | + exists(string regexp, string repl | escapingScheme(metachar, regexp) and replaces(char, repl) and repl.regexpMatch(regexp) @@ -107,7 +110,7 @@ class Replacement extends DataFlow::Node { * `<`) using `<`. */ predicate unescapes(string metachar, string char) { - exists (string regexp, string orig | + exists(string regexp, string orig | escapingScheme(metachar, regexp) and replaces(orig, char) and orig.regexpMatch(regexp) @@ -126,11 +129,10 @@ class Replacement extends DataFlow::Node { * performs an escaping. */ Replacement getAnEarlierEscaping(string metachar) { - exists (Replacement pred | pred = this.getPreviousReplacement() | - if pred.escapes(_, metachar) then - result = pred - else - result = pred.getAnEarlierEscaping(metachar) + exists(Replacement pred | pred = this.getPreviousReplacement() | + if pred.escapes(_, metachar) + then result = pred + else result = pred.getAnEarlierEscaping(metachar) ) } @@ -139,21 +141,21 @@ class Replacement extends DataFlow::Node { * performs a unescaping. */ Replacement getALaterUnescaping(string metachar) { - exists (Replacement succ | this = succ.getPreviousReplacement() | - if succ.unescapes(metachar, _) then - result = succ - else - result = succ.getALaterUnescaping(metachar) + exists(Replacement succ | this = succ.getPreviousReplacement() | + if succ.unescapes(metachar, _) + then result = succ + else result = succ.getALaterUnescaping(metachar) ) } } from Replacement primary, Replacement supplementary, string message, string metachar -where primary.escapes(metachar, _) and - supplementary = primary.getAnEarlierEscaping(metachar) and - message = "may double-escape '" + metachar + "' characters from $@" - or - primary.unescapes(_, metachar) and - supplementary = primary.getALaterUnescaping(metachar) and - message = "may produce '" + metachar + "' characters that are double-unescaped $@" +where + primary.escapes(metachar, _) and + supplementary = primary.getAnEarlierEscaping(metachar) and + message = "may double-escape '" + metachar + "' characters from $@" + or + primary.unescapes(_, metachar) and + supplementary = primary.getALaterUnescaping(metachar) and + message = "may produce '" + metachar + "' characters that are double-unescaped $@" select primary, "This replacement " + message + ".", supplementary, "here" diff --git a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql index 16eb53adb45c..02587f7a0e05 100644 --- a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql +++ b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql @@ -17,9 +17,7 @@ import javascript /** * Gets a character that is commonly used as a meta-character. */ -string metachar() { - result = "'\"\\&<>\n\r\t*|{}[]%$".charAt(_) -} +string metachar() { result = "'\"\\&<>\n\r\t*|{}[]%$".charAt(_) } /** Gets a string matched by `e` in a `replace` call. */ string getAMatchedString(Expr e) { @@ -36,7 +34,7 @@ RegExpConstant getAMatchedConstant(RegExpTerm t) { or result = getAMatchedConstant(t.(RegExpGroup).getAChild()) or - exists (RegExpCharacterClass recc | recc = t and not recc.isInverted() | + exists(RegExpCharacterClass recc | recc = t and not recc.isInverted() | result = getAMatchedConstant(recc.getAChild()) ) } @@ -48,10 +46,11 @@ predicate isSimple(RegExpTerm t) { isSimple(t.(RegExpGroup).getAChild()) or ( - t instanceof RegExpAlt or - t instanceof RegExpCharacterClass and not t.(RegExpCharacterClass).isInverted() + t instanceof RegExpAlt + or + t instanceof RegExpCharacterClass and not t.(RegExpCharacterClass).isInverted() ) and - forall (RegExpTerm ch | ch = t.getAChild() | isSimple(ch)) + forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch)) } /** @@ -62,7 +61,7 @@ predicate isBackslashEscape(MethodCallExpr mce, RegExpLiteral re) { mce.getMethodName() = "replace" and re = mce.getArgument(0) and re.isGlobal() and - exists (string new | new = mce.getArgument(1).getStringValue() | + exists(string new | new = mce.getArgument(1).getStringValue() | // `new` is `\$&`, `\$1` or similar new.regexpMatch("\\\\\\$(&|\\d)") or @@ -79,17 +78,22 @@ predicate allBackslashesEscaped(DataFlow::Node nd) { nd = DataFlow::globalVarRef("JSON").getAMemberCall("stringify") or // check whether `nd` itself escapes backslashes - exists (RegExpLiteral rel | isBackslashEscape(nd.asExpr(), rel) | + exists(RegExpLiteral rel | isBackslashEscape(nd.asExpr(), rel) | // if it's a complex regexp, we conservatively assume that it probably escapes backslashes not isSimple(rel.getRoot()) or getAMatchedString(rel) = "\\" ) or // flow through string methods - exists (DataFlow::MethodCallNode mc, string m | + exists(DataFlow::MethodCallNode mc, string m | m = "replace" or - m = "slice" or m = "substr" or m = "substring" or - m = "toLowerCase" or m = "toUpperCase" or m = "trim" | + m = "slice" or + m = "substr" or + m = "substring" or + m = "toLowerCase" or + m = "toUpperCase" or + m = "trim" + | mc = nd and m = mc.getMethodName() and allBackslashesEscaped(mc.getReceiver()) ) or @@ -98,31 +102,32 @@ predicate allBackslashesEscaped(DataFlow::Node nd) { } from MethodCallExpr repl, Expr old, string msg -where repl.getMethodName() = "replace" and - old = repl.getArgument(0) and - ( - not old.(RegExpLiteral).isGlobal() and - msg = "This replaces only the first occurrence of " + old + "." and - // only flag if this is likely to be a sanitizer or URL encoder or decoder - exists (string m | m = getAMatchedString(old) | - // sanitizer - m = metachar() - or - exists (string urlEscapePattern | urlEscapePattern = "(%[0-9A-Fa-f]{2})+" | - // URL decoder - m.regexpMatch(urlEscapePattern) - or - // URL encoder - repl.getArgument(1).getStringValue().regexpMatch(urlEscapePattern) - ) - ) and - // don't flag replace operations in a loop - not DataFlow::valueNode(repl.getReceiver()) = DataFlow::valueNode(repl).getASuccessor+() - or - exists (RegExpLiteral rel | - isBackslashEscape(repl, rel) and - not allBackslashesEscaped(DataFlow::valueNode(repl)) and - msg = "This does not backslash-escape the backslash character." - ) +where + repl.getMethodName() = "replace" and + old = repl.getArgument(0) and + ( + not old.(RegExpLiteral).isGlobal() and + msg = "This replaces only the first occurrence of " + old + "." and + // only flag if this is likely to be a sanitizer or URL encoder or decoder + exists(string m | m = getAMatchedString(old) | + // sanitizer + m = metachar() + or + exists(string urlEscapePattern | urlEscapePattern = "(%[0-9A-Fa-f]{2})+" | + // URL decoder + m.regexpMatch(urlEscapePattern) + or + // URL encoder + repl.getArgument(1).getStringValue().regexpMatch(urlEscapePattern) ) + ) and + // don't flag replace operations in a loop + not DataFlow::valueNode(repl.getReceiver()) = DataFlow::valueNode(repl).getASuccessor+() + or + exists(RegExpLiteral rel | + isBackslashEscape(repl, rel) and + not allBackslashesEscaped(DataFlow::valueNode(repl)) and + msg = "This does not backslash-escape the backslash character." + ) + ) select old, msg diff --git a/javascript/ql/src/Security/CWE-134/TaintedFormatString.ql b/javascript/ql/src/Security/CWE-134/TaintedFormatString.ql index bd7e9c869c79..3c12cc1d3f92 100644 --- a/javascript/ql/src/Security/CWE-134/TaintedFormatString.ql +++ b/javascript/ql/src/Security/CWE-134/TaintedFormatString.ql @@ -16,4 +16,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ flows here and is used in a format string.", - source.getNode(), "User-provided value" + source.getNode(), "User-provided value" diff --git a/javascript/ql/src/Security/CWE-200/FileAccessToHttp.ql b/javascript/ql/src/Security/CWE-200/FileAccessToHttp.ql index 05a28eba3c65..b116af31c4c1 100644 --- a/javascript/ql/src/Security/CWE-200/FileAccessToHttp.ql +++ b/javascript/ql/src/Security/CWE-200/FileAccessToHttp.ql @@ -15,4 +15,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ flows directly to outbound network request", - source.getNode(), "File data" + source.getNode(), "File data" diff --git a/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql b/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql index 24bb60be541b..1a4a26136759 100644 --- a/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql +++ b/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql @@ -17,5 +17,6 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Stack trace information from $@ may be exposed to an external user here.", - source.getNode(), "here" +select sink.getNode(), source, sink, + "Stack trace information from $@ may be exposed to an external user here.", source.getNode(), + "here" diff --git a/javascript/ql/src/Security/CWE-312/CleartextLogging.ql b/javascript/ql/src/Security/CWE-312/CleartextLogging.ql index 03c86d327556..2d65ae9c471e 100644 --- a/javascript/ql/src/Security/CWE-312/CleartextLogging.ql +++ b/javascript/ql/src/Security/CWE-312/CleartextLogging.ql @@ -20,21 +20,22 @@ import DataFlow::PathGraph * Holds if `tl` is used in a browser environment. */ predicate inBrowserEnvironment(TopLevel tl) { - tl instanceof InlineScript or - tl instanceof CodeInAttribute or - exists (GlobalVarAccess e | - e.getTopLevel() = tl | - e.getName() = "window" - ) or - exists (Module m | inBrowserEnvironment(m) | + tl instanceof InlineScript + or + tl instanceof CodeInAttribute + or + exists(GlobalVarAccess e | e.getTopLevel() = tl | e.getName() = "window") + or + exists(Module m | inBrowserEnvironment(m) | tl = m.getAnImportedModule() or m = tl.(Module).getAnImportedModule() ) } from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) and - // ignore logging to the browser console (even though it is not a good practice) - not inBrowserEnvironment(sink.getNode().asExpr().getTopLevel()) +where + cfg.hasFlowPath(source, sink) and + // ignore logging to the browser console (even though it is not a good practice) + not inBrowserEnvironment(sink.getNode().asExpr().getTopLevel()) select sink.getNode(), source, sink, "Sensitive data returned by $@ is logged here.", - source.getNode(), source.getNode().(Source).describe() + source.getNode(), source.getNode().(Source).describe() diff --git a/javascript/ql/src/Security/CWE-312/CleartextStorage.ql b/javascript/ql/src/Security/CWE-312/CleartextStorage.ql index d54d51b024ce..b59f8e74844b 100644 --- a/javascript/ql/src/Security/CWE-312/CleartextStorage.ql +++ b/javascript/ql/src/Security/CWE-312/CleartextStorage.ql @@ -19,4 +19,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "Sensitive data returned by $@ is stored here.", - source.getNode(), source.getNode().(Source).describe() + source.getNode(), source.getNode().(Source).describe() diff --git a/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql b/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql index 99c8c46c4766..999b4609d957 100644 --- a/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql +++ b/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql @@ -21,13 +21,12 @@ import javascript * Dependencies in `package.json` files are excluded by this predicate. */ predicate config(string key, string val, Locatable valElement) { - exists (JSONObject obj | - not exists(PackageJSON p | obj = p.getADependenciesObject(_)) | + exists(JSONObject obj | not exists(PackageJSON p | obj = p.getADependenciesObject(_)) | obj.getPropValue(key) = valElement and val = valElement.(JSONString).getValue() ) or - exists (YAMLMapping m, YAMLString keyElement | + exists(YAMLMapping m, YAMLString keyElement | m.maps(keyElement, valElement) and key = keyElement.getValue() and val = valElement.(YAMLString).getValue() @@ -43,10 +42,14 @@ predicate exclude(File f) { } from string key, string val, Locatable valElement -where config(key, val, valElement) and val != "" and - (key.toLowerCase() = "password" - or - key.toLowerCase() != "readme" and - val.regexpMatch("(?is).*password\\s*=(?!\\s*;).*")) and - not exclude(valElement.getFile()) +where + config(key, val, valElement) and + val != "" and + ( + key.toLowerCase() = "password" + or + key.toLowerCase() != "readme" and + val.regexpMatch("(?is).*password\\s*=(?!\\s*;).*") + ) and + not exclude(valElement.getFile()) select valElement, "Avoid plaintext passwords in configuration files." diff --git a/javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql b/javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql index 501041a4b2fb..efc5714c8f6e 100644 --- a/javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql +++ b/javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql @@ -15,7 +15,9 @@ import semmle.javascript.security.SensitiveActions import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where cfg.hasFlowPath(source, sink) and - not source.getNode().asExpr() instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash -select sink.getNode(), source, sink, "Sensitive data from $@ is used in a broken or weak cryptographic algorithm.", - source.getNode(), source.getNode().(Source).describe() +where + cfg.hasFlowPath(source, sink) and + not source.getNode().asExpr() instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash +select sink.getNode(), source, sink, + "Sensitive data from $@ is used in a broken or weak cryptographic algorithm.", source.getNode(), + source.getNode().(Source).describe() diff --git a/javascript/ql/src/Security/CWE-338/InsecureRandomness.ql b/javascript/ql/src/Security/CWE-338/InsecureRandomness.ql index 5b48a98740ea..6db8ea0054e0 100644 --- a/javascript/ql/src/Security/CWE-338/InsecureRandomness.ql +++ b/javascript/ql/src/Security/CWE-338/InsecureRandomness.ql @@ -10,6 +10,7 @@ * @tags security * external/cwe/cwe-338 */ + import javascript import semmle.javascript.security.dataflow.InsecureRandomness::InsecureRandomness import DataFlow::PathGraph @@ -17,4 +18,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "Cryptographically insecure $@ in a security context.", - source.getNode(), "random value" + source.getNode(), "random value" diff --git a/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql b/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql index 2c1aef77795a..2df657b59f7e 100644 --- a/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql +++ b/javascript/ql/src/Security/CWE-346/CorsMisconfigurationForCredentials.ql @@ -10,7 +10,6 @@ * external/cwe/cwe-639 */ - import javascript import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentials::CorsMisconfigurationForCredentials import DataFlow::PathGraph @@ -18,5 +17,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ leak vulnerability due to $@.", - sink.getNode().(Sink).getCredentialsHeader(), "Credential", - source.getNode(), "a misconfigured CORS header value" + sink.getNode().(Sink).getCredentialsHeader(), "Credential", source.getNode(), + "a misconfigured CORS header value" diff --git a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql index 9a1a3e1aa883..192f32dc79b1 100644 --- a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql +++ b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql @@ -40,7 +40,7 @@ predicate hasCookieMiddleware(Express::RouteHandlerExpr expr, Express::RouteHand * ``` */ DataFlow::CallNode csrfMiddlewareCreation() { - exists (DataFlow::SourceNode callee | result = callee.getACall() | + exists(DataFlow::SourceNode callee | result = callee.getACall() | callee = DataFlow::moduleImport("csurf") or callee = DataFlow::moduleImport("lusca") and @@ -57,18 +57,17 @@ predicate hasCsrfMiddleware(Express::RouteHandlerExpr handler) { csrfMiddlewareCreation().flowsToExpr(handler.getAMatchingAncestor()) } -from Express::RouterDefinition router, Express::RouteSetup setup, Express::RouteHandlerExpr handler, - Express::RouteHandlerExpr cookie -where router = setup.getRouter() - and handler = setup.getARouteHandlerExpr() - - and hasCookieMiddleware(handler, cookie) - and not hasCsrfMiddleware(handler) - +from + Express::RouterDefinition router, Express::RouteSetup setup, Express::RouteHandlerExpr handler, + Express::RouteHandlerExpr cookie +where + router = setup.getRouter() and + handler = setup.getARouteHandlerExpr() and + hasCookieMiddleware(handler, cookie) and + not hasCsrfMiddleware(handler) and // Only warn for the last handler in a chain. - and handler.isLastHandler() - + handler.isLastHandler() and // Only warn for dangerous for handlers, such as for POST and PUT. - and not setup.getRequestMethod().isSafe() - -select cookie, "This cookie middleware is serving a request handler $@ without CSRF protection.", handler, "here" + not setup.getRequestMethod().isSafe() +select cookie, "This cookie middleware is serving a request handler $@ without CSRF protection.", + handler, "here" diff --git a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql index 1dafa5b2dfcf..818d3ab6af1b 100644 --- a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql +++ b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql @@ -9,7 +9,7 @@ * @tags security * external/cwe/cwe-250 * external/cwe/cwe-400 - */ + */ import javascript import semmle.javascript.security.dataflow.RemotePropertyInjection::RemotePropertyInjection @@ -18,4 +18,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "A $@ is used as" + sink.getNode().(Sink).getMessage(), - source.getNode(), "user-provided value" + source.getNode(), "user-provided value" diff --git a/javascript/ql/src/Security/CWE-502/UnsafeDeserialization.ql b/javascript/ql/src/Security/CWE-502/UnsafeDeserialization.ql index 8795546883e5..29251e1c5e65 100644 --- a/javascript/ql/src/Security/CWE-502/UnsafeDeserialization.ql +++ b/javascript/ql/src/Security/CWE-502/UnsafeDeserialization.ql @@ -16,5 +16,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Unsafe deserialization of $@.", - source.getNode(), "user input" +select sink.getNode(), source, sink, "Unsafe deserialization of $@.", source.getNode(), "user input" diff --git a/javascript/ql/src/Security/CWE-506/HardcodedDataInterpretedAsCode.ql b/javascript/ql/src/Security/CWE-506/HardcodedDataInterpretedAsCode.ql index e274ac45d37a..9c5bca8dc0e3 100644 --- a/javascript/ql/src/Security/CWE-506/HardcodedDataInterpretedAsCode.ql +++ b/javascript/ql/src/Security/CWE-506/HardcodedDataInterpretedAsCode.ql @@ -18,5 +18,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, - "Hard-coded data from $@ is interpreted as " + sink.getNode().(Sink).getKind() + ".", - source.getNode(), "here" + "Hard-coded data from $@ is interpreted as " + sink.getNode().(Sink).getKind() + ".", + source.getNode(), "here" diff --git a/javascript/ql/src/Security/CWE-601/ClientSideUrlRedirect.ql b/javascript/ql/src/Security/CWE-601/ClientSideUrlRedirect.ql index 9edc3e739d8a..c6748b820748 100644 --- a/javascript/ql/src/Security/CWE-601/ClientSideUrlRedirect.ql +++ b/javascript/ql/src/Security/CWE-601/ClientSideUrlRedirect.ql @@ -18,5 +18,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", - source.getNode(), "user-provided value" +select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(), + "user-provided value" diff --git a/javascript/ql/src/Security/CWE-601/ServerSideUrlRedirect.ql b/javascript/ql/src/Security/CWE-601/ServerSideUrlRedirect.ql index a591f9d1d52b..a39144d2478f 100644 --- a/javascript/ql/src/Security/CWE-601/ServerSideUrlRedirect.ql +++ b/javascript/ql/src/Security/CWE-601/ServerSideUrlRedirect.ql @@ -16,5 +16,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", - source.getNode(), "user-provided value" +select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(), + "user-provided value" diff --git a/javascript/ql/src/Security/CWE-611/Xxe.ql b/javascript/ql/src/Security/CWE-611/Xxe.ql index 3f2cb797e1c6..3334a0db498f 100644 --- a/javascript/ql/src/Security/CWE-611/Xxe.ql +++ b/javascript/ql/src/Security/CWE-611/Xxe.ql @@ -17,5 +17,6 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "A $@ is parsed as XML without guarding against external entity expansion.", - source.getNode(), "user-provided value" +select sink.getNode(), source, sink, + "A $@ is parsed as XML without guarding against external entity expansion.", source.getNode(), + "user-provided value" diff --git a/javascript/ql/src/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql b/javascript/ql/src/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql index c5749967e2c2..c8e581bacf9a 100644 --- a/javascript/ql/src/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql +++ b/javascript/ql/src/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql @@ -16,5 +16,6 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Links in this email can be hijacked by poisoning the HTTP host header $@.", - source.getNode(), "here" +select sink.getNode(), source, sink, + "Links in this email can be hijacked by poisoning the HTTP host header $@.", source.getNode(), + "here" diff --git a/javascript/ql/src/Security/CWE-643/XpathInjection.ql b/javascript/ql/src/Security/CWE-643/XpathInjection.ql index 8fe847b8f16c..5f30bb3ee217 100644 --- a/javascript/ql/src/Security/CWE-643/XpathInjection.ql +++ b/javascript/ql/src/Security/CWE-643/XpathInjection.ql @@ -17,4 +17,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ flows here and is used in an XPath expression.", - source.getNode(), "User-provided value" + source.getNode(), "User-provided value" diff --git a/javascript/ql/src/Security/CWE-730/RegExpInjection.ql b/javascript/ql/src/Security/CWE-730/RegExpInjection.ql index 578733974470..ccff952e0958 100644 --- a/javascript/ql/src/Security/CWE-730/RegExpInjection.ql +++ b/javascript/ql/src/Security/CWE-730/RegExpInjection.ql @@ -19,4 +19,4 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "This regular expression is constructed from a $@.", - source.getNode(), "user-provided value" + source.getNode(), "user-provided value" diff --git a/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql b/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql index df3b2e8aa162..a47265664517 100644 --- a/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql +++ b/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql @@ -17,5 +17,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, - "Invocation of method with $@ name may dispatch to unexpected target and cause an exception.", - source.getNode(), "user-controlled" + "Invocation of method with $@ name may dispatch to unexpected target and cause an exception.", + source.getNode(), "user-controlled" diff --git a/javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql b/javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql index d891ec706246..5e011a0ca7e5 100644 --- a/javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql +++ b/javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql @@ -17,9 +17,12 @@ import javascript import semmle.javascript.security.dataflow.MissingRateLimiting import semmle.javascript.RestrictedLocations -from ExpensiveRouteHandler r, Express::RouteHandlerExpr rhe, - string explanation, DataFlow::Node reference, string referenceLabel -where r = rhe.getBody() and - r.explain(explanation, reference, referenceLabel) and - not rhe instanceof RateLimitedRouteHandlerExpr -select (FirstLineOf)rhe, "This route handler " + explanation + ", but is not rate-limited.", reference, referenceLabel +from + ExpensiveRouteHandler r, Express::RouteHandlerExpr rhe, string explanation, + DataFlow::Node reference, string referenceLabel +where + r = rhe.getBody() and + r.explain(explanation, reference, referenceLabel) and + not rhe instanceof RateLimitedRouteHandlerExpr +select rhe.(FirstLineOf), "This route handler " + explanation + ", but is not rate-limited.", + reference, referenceLabel diff --git a/javascript/ql/src/Security/CWE-776/XmlBomb.ql b/javascript/ql/src/Security/CWE-776/XmlBomb.ql index 878d01d6f4df..a9f9647ca35d 100644 --- a/javascript/ql/src/Security/CWE-776/XmlBomb.ql +++ b/javascript/ql/src/Security/CWE-776/XmlBomb.ql @@ -17,5 +17,6 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "A $@ is parsed as XML without guarding against uncontrolled entity expansion.", - source.getNode(), "user-provided value" +select sink.getNode(), source, sink, + "A $@ is parsed as XML without guarding against uncontrolled entity expansion.", source.getNode(), + "user-provided value" diff --git a/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql b/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql index 9c6959989e76..d6d48cb5ff74 100644 --- a/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql +++ b/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql @@ -17,11 +17,13 @@ private import semmle.javascript.security.dataflow.HardcodedCredentials::Hardcod import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string value -where cfg.hasFlowPath(source, sink) and - // use source value in message if it's available - if source.getNode().asExpr() instanceof ConstantString then - value = "The hard-coded value \"" + source.getNode().asExpr().(ConstantString).getStringValue() + "\"" - else - value = "This hard-coded value" -select source.getNode(), source, sink, value + " is used as $@.", - sink.getNode(), sink.getNode().(Sink).getKind() +where + cfg.hasFlowPath(source, sink) and + // use source value in message if it's available + if source.getNode().asExpr() instanceof ConstantString + then + value = "The hard-coded value \"" + source.getNode().asExpr().(ConstantString).getStringValue() + + "\"" + else value = "This hard-coded value" +select source.getNode(), source, sink, value + " is used as $@.", sink.getNode(), + sink.getNode().(Sink).getKind() diff --git a/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql b/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql index 8470dc7a4c38..e6359281614a 100644 --- a/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql +++ b/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql @@ -27,20 +27,14 @@ predicate flowsToGuardExpr(DataFlow::Node nd, SensitiveActionGuardConditional gu * `var ok = x == y; if (ok) login()`. */ class SensitiveActionGuardComparison extends Comparison { - SensitiveActionGuardConditional guard; - SensitiveActionGuardComparison() { - flowsToGuardExpr(DataFlow::valueNode(this), guard) - } + SensitiveActionGuardComparison() { flowsToGuardExpr(DataFlow::valueNode(this), guard) } /** * Gets the guard that uses this comparison. */ - SensitiveActionGuardConditional getGuard() { - result = guard - } - + SensitiveActionGuardConditional getGuard() { result = guard } } /** @@ -48,17 +42,11 @@ class SensitiveActionGuardComparison extends Comparison { * This sink should not be presented to the client of this query. */ class SensitiveActionGuardComparisonOperand extends Sink { - SensitiveActionGuardComparison comparison; - SensitiveActionGuardComparisonOperand() { - asExpr() = comparison.getAnOperand() - } - - override SensitiveAction getAction() { - result = comparison.getGuard().getAction() - } + SensitiveActionGuardComparisonOperand() { asExpr() = comparison.getAnOperand() } + override SensitiveAction getAction() { result = comparison.getGuard().getAction() } } /** @@ -67,19 +55,27 @@ class SensitiveActionGuardComparisonOperand extends Sink { * If flow from `source` taints `sink`, then an attacker can * control if `action` should be executed or not. */ -predicate isTaintedGuardForSensitiveAction(DataFlow::PathNode sink, DataFlow::PathNode source, SensitiveAction action) { +predicate isTaintedGuardForSensitiveAction( + DataFlow::PathNode sink, DataFlow::PathNode source, SensitiveAction action +) { action = sink.getNode().(Sink).getAction() and // exclude the intermediary sink not sink.getNode() instanceof SensitiveActionGuardComparisonOperand and - exists (Configuration cfg | + exists(Configuration cfg | // ordinary taint tracking to a guard - cfg.hasFlowPath(source, sink) or + cfg.hasFlowPath(source, sink) + or // taint tracking to both operands of a guard comparison - exists (SensitiveActionGuardComparison cmp, DataFlow::PathNode lSource, DataFlow::PathNode rSource, - DataFlow::PathNode lSink, DataFlow::PathNode rSink | + exists( + SensitiveActionGuardComparison cmp, DataFlow::PathNode lSource, DataFlow::PathNode rSource, + DataFlow::PathNode lSink, DataFlow::PathNode rSink + | sink.getNode() = cmp.getGuard() and - cfg.hasFlowPath(lSource, lSink) and lSink.getNode() = DataFlow::valueNode(cmp.getLeftOperand()) and - cfg.hasFlowPath(rSource, rSink) and rSink.getNode() = DataFlow::valueNode(cmp.getRightOperand()) | + cfg.hasFlowPath(lSource, lSink) and + lSink.getNode() = DataFlow::valueNode(cmp.getLeftOperand()) and + cfg.hasFlowPath(rSource, rSink) and + rSink.getNode() = DataFlow::valueNode(cmp.getRightOperand()) + | source = lSource or source = rSource ) @@ -92,26 +88,28 @@ predicate isTaintedGuardForSensitiveAction(DataFlow::PathNode sink, DataFlow::Pa * Example: `if (e) return; action(x)`. */ predicate isEarlyAbortGuard(DataFlow::PathNode e, SensitiveAction action) { - exists (IfStmt guard | + exists(IfStmt guard | // `e` is in the condition of an if-statement ... e.getNode().(Sink).asExpr().getParentExpr*() = guard.getCondition() and // ... where the then-branch always throws or returns - exists (Stmt abort | + exists(Stmt abort | abort instanceof ThrowStmt or - abort instanceof ReturnStmt | + abort instanceof ReturnStmt + | abort.nestedIn(guard) and - abort.getBasicBlock().(ReachableBasicBlock).postDominates(guard.getThen().getBasicBlock() ) + abort.getBasicBlock().(ReachableBasicBlock).postDominates(guard.getThen().getBasicBlock()) ) and // ... and the else-branch does not exist - not exists (guard.getElse()) | + not exists(guard.getElse()) + | // ... and `action` is outside the if-statement not action.asExpr().getEnclosingStmt().nestedIn(guard) ) } from DataFlow::PathNode source, DataFlow::PathNode sink, SensitiveAction action -where isTaintedGuardForSensitiveAction(sink, source, action) and - not isEarlyAbortGuard(sink, action) +where + isTaintedGuardForSensitiveAction(sink, source, action) and + not isEarlyAbortGuard(sink, action) select sink.getNode(), source, sink, "This condition guards a sensitive $@, but $@ controls it.", - action, "action", - source.getNode(), "a user-provided value" + action, "action", source.getNode(), "a user-provided value" diff --git a/javascript/ql/src/Security/CWE-807/DifferentKindsComparisonBypass.ql b/javascript/ql/src/Security/CWE-807/DifferentKindsComparisonBypass.ql index cfc42889962c..476112e0781f 100644 --- a/javascript/ql/src/Security/CWE-807/DifferentKindsComparisonBypass.ql +++ b/javascript/ql/src/Security/CWE-807/DifferentKindsComparisonBypass.ql @@ -9,15 +9,20 @@ * external/cwe/cwe-807 * external/cwe/cwe-290 */ + import javascript import semmle.javascript.security.dataflow.DifferentKindsComparisonBypass::DifferentKindsComparisonBypass from DifferentKindsComparison cmp, DataFlow::Node lSource, DataFlow::Node rSource -where lSource = cmp.getLSource() and rSource = cmp.getRSource() and - not ( - // Standard names for the double submit cookie pattern (CSRF protection) - exists (DataFlow::PropRead s | s = lSource or s = rSource | - s.getPropertyName().regexpMatch("(?i).*(csrf|state|token).*") - ) - ) -select cmp, "This comparison of $@ and $@ is a potential security risk since it is controlled by the user.", lSource, lSource.toString(), rSource, rSource.toString() \ No newline at end of file +where + lSource = cmp.getLSource() and + rSource = cmp.getRSource() and + not ( + // Standard names for the double submit cookie pattern (CSRF protection) + exists(DataFlow::PropRead s | s = lSource or s = rSource | + s.getPropertyName().regexpMatch("(?i).*(csrf|state|token).*") + ) + ) +select cmp, + "This comparison of $@ and $@ is a potential security risk since it is controlled by the user.", + lSource, lSource.toString(), rSource, rSource.toString() diff --git a/javascript/ql/src/Security/CWE-843/TypeConfusionThroughParameterTampering.ql b/javascript/ql/src/Security/CWE-843/TypeConfusionThroughParameterTampering.ql index c2bad5090151..17788e66d68f 100644 --- a/javascript/ql/src/Security/CWE-843/TypeConfusionThroughParameterTampering.ql +++ b/javascript/ql/src/Security/CWE-843/TypeConfusionThroughParameterTampering.ql @@ -15,5 +15,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Potential type confusion for $@.", - source.getNode(), "HTTP request parameter" +select sink.getNode(), source, sink, "Potential type confusion for $@.", source.getNode(), + "HTTP request parameter" diff --git a/javascript/ql/src/Security/CWE-916/InsufficientPasswordHash.ql b/javascript/ql/src/Security/CWE-916/InsufficientPasswordHash.ql index e2a94d06ae07..2f1df96f3ec9 100644 --- a/javascript/ql/src/Security/CWE-916/InsufficientPasswordHash.ql +++ b/javascript/ql/src/Security/CWE-916/InsufficientPasswordHash.ql @@ -15,5 +15,5 @@ import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Password from $@ is hashed insecurely.", - source.getNode(), source.getNode().(Source).describe() +select sink.getNode(), source, sink, "Password from $@ is hashed insecurely.", source.getNode(), + source.getNode().(Source).describe() diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.ql b/javascript/ql/src/Security/CWE-918/RequestForgery.ql index c1d80a78a3e9..43096ea62409 100644 --- a/javascript/ql/src/Security/CWE-918/RequestForgery.ql +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.ql @@ -14,7 +14,8 @@ import semmle.javascript.security.dataflow.RequestForgery::RequestForgery import DataFlow::PathGraph from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request -where cfg.hasFlowPath(source, sink) and - request = sink.getNode().(Sink).getARequest() -select request, source, sink, "The $@ of this request depends on $@.", - sink.getNode(), sink.getNode().(Sink).getKind(), source, "a user-provided value" +where + cfg.hasFlowPath(source, sink) and + request = sink.getNode().(Sink).getARequest() +select request, source, sink, "The $@ of this request depends on $@.", sink.getNode(), + sink.getNode().(Sink).getKind(), source, "a user-provided value" diff --git a/javascript/ql/src/StandardLibrary/ParseIntRadix.ql b/javascript/ql/src/StandardLibrary/ParseIntRadix.ql index 7ae4c27c1f9d..409909d45a24 100644 --- a/javascript/ql/src/StandardLibrary/ParseIntRadix.ql +++ b/javascript/ql/src/StandardLibrary/ParseIntRadix.ql @@ -15,6 +15,7 @@ import javascript from DataFlow::CallNode parseInt -where parseInt = DataFlow::globalVarRef("parseInt").getACall() and - parseInt.getNumArgument() = 1 -select parseInt, "Missing radix parameter." \ No newline at end of file +where + parseInt = DataFlow::globalVarRef("parseInt").getACall() and + parseInt.getNumArgument() = 1 +select parseInt, "Missing radix parameter." diff --git a/javascript/ql/src/Statements/DanglingElse.ql b/javascript/ql/src/Statements/DanglingElse.ql index 28c95cfaea1c..215399a0066c 100644 --- a/javascript/ql/src/Statements/DanglingElse.ql +++ b/javascript/ql/src/Statements/DanglingElse.ql @@ -17,11 +17,7 @@ import javascript * A token that is relevant for this query, that is, an `if`, `else` or `}` token. */ class RelevantToken extends Token { - RelevantToken() { - exists (string v | v = getValue() | - v = "if" or v = "else" or v = "}" - ) - } + RelevantToken() { exists(string v | v = getValue() | v = "if" or v = "else" or v = "}") } } /** @@ -54,9 +50,10 @@ int semanticIndent(RelevantToken tk) { not prevTokenOnSameLine(tk, _, _, _) and result = tk.getLocation().getStartColumn() or - exists (RelevantToken prev | + exists(RelevantToken prev | prevTokenOnSameLine(tk, "if", prev, "else") or - prevTokenOnSameLine(tk, "else", prev, "}") | + prevTokenOnSameLine(tk, "else", prev, "}") + | result = semanticIndent(prev) ) } @@ -64,24 +61,23 @@ int semanticIndent(RelevantToken tk) { /** * Gets the semantic indentation of the `if` token of statement `i`. */ -int ifIndent(IfStmt i) { - result = semanticIndent(i.getIfToken()) -} +int ifIndent(IfStmt i) { result = semanticIndent(i.getIfToken()) } /** * Gets the semantic indentation of the `else` token of statement `i`, * if any. */ -int elseIndent(IfStmt i) { - result = semanticIndent(i.getElseToken()) -} +int elseIndent(IfStmt i) { result = semanticIndent(i.getElseToken()) } from IfStmt outer, IfStmt inner, Token outerIf, Token innerIf, Token innerElse, int outerIndent -where inner = outer.getThen().getAChildStmt*() and - outerIf = outer.getIfToken() and outerIndent = ifIndent(outer) and - innerIf = inner.getIfToken() and innerElse = inner.getElseToken() and - outerIndent < ifIndent(inner) and - outerIndent = elseIndent(inner) and - not outer.getTopLevel().isMinified() +where + inner = outer.getThen().getAChildStmt*() and + outerIf = outer.getIfToken() and + outerIndent = ifIndent(outer) and + innerIf = inner.getIfToken() and + innerElse = inner.getElseToken() and + outerIndent < ifIndent(inner) and + outerIndent = elseIndent(inner) and + not outer.getTopLevel().isMinified() select innerElse, "This else branch belongs to $@, but its indentation suggests it belongs to $@.", - innerIf, "this if statement", outerIf, "this other if statement" \ No newline at end of file + innerIf, "this if statement", outerIf, "this other if statement" diff --git a/javascript/ql/src/Statements/EphemeralLoop.ql b/javascript/ql/src/Statements/EphemeralLoop.ql index 5d626a65ffb9..e99f0a64ed23 100644 --- a/javascript/ql/src/Statements/EphemeralLoop.ql +++ b/javascript/ql/src/Statements/EphemeralLoop.ql @@ -14,9 +14,10 @@ import semmle.javascript.RestrictedLocations import semmle.javascript.frameworks.Emscripten from LoopStmt l, BasicBlock body -where body = l.getBody().getBasicBlock() and - not body.getASuccessor+() = body and - not l instanceof EnhancedForLoop and - // Emscripten generates lots of `do { ... } while(0);` loops, so exclude - not l.getTopLevel() instanceof EmscriptenGeneratedToplevel -select (FirstLineOf)l, "This loop executes at most once." \ No newline at end of file +where + body = l.getBody().getBasicBlock() and + not body.getASuccessor+() = body and + not l instanceof EnhancedForLoop and + // Emscripten generates lots of `do { ... } while(0);` loops, so exclude + not l.getTopLevel() instanceof EmscriptenGeneratedToplevel +select l.(FirstLineOf), "This loop executes at most once." diff --git a/javascript/ql/src/Statements/ImplicitReturn.ql b/javascript/ql/src/Statements/ImplicitReturn.ql index e6f964344694..bf187c205c5f 100644 --- a/javascript/ql/src/Statements/ImplicitReturn.ql +++ b/javascript/ql/src/Statements/ImplicitReturn.ql @@ -26,22 +26,21 @@ predicate isThrowOrReturn(Stmt s) { /** * A `return` statement with an operand (that is, not just `return;`). */ -class ValueReturn extends ReturnStmt { - ValueReturn() { exists(getExpr()) } -} +class ValueReturn extends ReturnStmt { ValueReturn() { exists(getExpr()) } } /** Gets the lexically first explicit return statement in function `f`. */ ValueReturn getFirstExplicitReturn(Function f) { result = min(ValueReturn ret | - ret.getContainer() = f | - ret order by ret.getLocation().getStartLine(), ret.getLocation().getStartColumn() - ) + ret.getContainer() = f + | + ret + order by + ret.getLocation().getStartLine(), ret.getLocation().getStartColumn() + ) } /** Gets the number of return statements in function `f`, assuming there is at least one. */ -int numRet(Function f) { - result = strictcount(ReturnStmt ret | ret.getContainer() = f) -} +int numRet(Function f) { result = strictcount(ReturnStmt ret | ret.getContainer() = f) } /** * Holds if `f` is a dual-use constructor, that is, is a function that is meant to be invoked @@ -55,7 +54,7 @@ int numRet(Function f) { */ predicate isDualUseConstructor(Function f) { numRet(f) = 1 and - exists (ReturnStmt ret, DataFlow::NewNode new | ret.getContainer() = f | + exists(ReturnStmt ret, DataFlow::NewNode new | ret.getContainer() = f | new.asExpr() = ret.getExpr().getUnderlyingValue() and new.getACallee() = f ) @@ -67,7 +66,7 @@ predicate isDualUseConstructor(Function f) { * and isn't contained in a `finally` block. */ Stmt getAFallThroughStmt(Function f) { - exists (ReachableBasicBlock bb, ControlFlowNode nd | + exists(ReachableBasicBlock bb, ControlFlowNode nd | bb.getANode() = nd and nd.isAFinalNode() and f = bb.getContainer() and @@ -78,10 +77,12 @@ Stmt getAFallThroughStmt(Function f) { } from Function f, Stmt fallthrough -where fallthrough = getAFallThroughStmt(f) and - // no control path ends with an implicit return statement of the form `return;` - not exists(ReturnStmt ret | ret.getContainer() = f and not exists(ret.getExpr())) and - // f doesn't look like a dual-use constructor (which otherwise would trigger a violation) - not isDualUseConstructor(f) -select (FirstLineOf)fallthrough, "$@ may implicitly return 'undefined' here, while $@ an explicit value is returned.", - f, capitalize(f.describe()), getFirstExplicitReturn(f), "elsewhere" +where + fallthrough = getAFallThroughStmt(f) and + // no control path ends with an implicit return statement of the form `return;` + not exists(ReturnStmt ret | ret.getContainer() = f and not exists(ret.getExpr())) and + // f doesn't look like a dual-use constructor (which otherwise would trigger a violation) + not isDualUseConstructor(f) +select fallthrough.(FirstLineOf), + "$@ may implicitly return 'undefined' here, while $@ an explicit value is returned.", f, + capitalize(f.describe()), getFirstExplicitReturn(f), "elsewhere" diff --git a/javascript/ql/src/Statements/InconsistentLoopOrientation.ql b/javascript/ql/src/Statements/InconsistentLoopOrientation.ql index 0a6dced5e8e5..7692a9d3d201 100644 --- a/javascript/ql/src/Statements/InconsistentLoopOrientation.ql +++ b/javascript/ql/src/Statements/InconsistentLoopOrientation.ql @@ -23,7 +23,8 @@ import javascript * downward. */ predicate bounds(RelationalComparison test, Variable v, string direction) { - test.getLesserOperand() = v.getAnAccess() and direction = "upward" or + test.getLesserOperand() = v.getAnAccess() and direction = "upward" + or test.getGreaterOperand() = v.getAnAccess() and direction = "downward" } @@ -36,12 +37,16 @@ predicate bounds(RelationalComparison test, Variable v, string direction) { */ predicate updates(UpdateExpr upd, Variable v, string direction) { upd.getOperand() = v.getAnAccess() and - (upd instanceof IncExpr and direction = "upward" or - upd instanceof DecExpr and direction = "downward") + ( + upd instanceof IncExpr and direction = "upward" + or + upd instanceof DecExpr and direction = "downward" + ) } from ForStmt l, Variable v, string d1, string d2 -where bounds(l.getTest(), v, d1) and - updates(l.getUpdate(), v, d2) and - d1 != d2 +where + bounds(l.getTest(), v, d1) and + updates(l.getUpdate(), v, d2) and + d1 != d2 select l, "This loop counts " + d2 + ", but its variable is bounded " + d1 + "." diff --git a/javascript/ql/src/Statements/InconsistentReturn.ql b/javascript/ql/src/Statements/InconsistentReturn.ql index 02d304bc8b17..b6978301e023 100644 --- a/javascript/ql/src/Statements/InconsistentReturn.ql +++ b/javascript/ql/src/Statements/InconsistentReturn.ql @@ -12,9 +12,11 @@ import javascript from Function f, ReturnStmt explicit, ReturnStmt implicit -where explicit.getContainer() = f and - implicit.getContainer() = f and - exists(explicit.getExpr()) and - not exists(implicit.getExpr()) -select implicit, "This return statement implicitly returns 'undefined', whereas $@ returns an explicit value.", - explicit, "another return statement in the same function" \ No newline at end of file +where + explicit.getContainer() = f and + implicit.getContainer() = f and + exists(explicit.getExpr()) and + not exists(implicit.getExpr()) +select implicit, + "This return statement implicitly returns 'undefined', whereas $@ returns an explicit value.", + explicit, "another return statement in the same function" diff --git a/javascript/ql/src/Statements/LabelInCase.ql b/javascript/ql/src/Statements/LabelInCase.ql index 3a933f4c871a..e88d366136f6 100644 --- a/javascript/ql/src/Statements/LabelInCase.ql +++ b/javascript/ql/src/Statements/LabelInCase.ql @@ -13,6 +13,7 @@ import javascript from LabeledStmt l, Case c -where l = c.getAChildStmt+() and - l.getLocation().getStartColumn() = c.getLocation().getStartColumn() -select l.getChildExpr(0), "Non-case labels in switch statements are confusing." \ No newline at end of file +where + l = c.getAChildStmt+() and + l.getLocation().getStartColumn() = c.getLocation().getStartColumn() +select l.getChildExpr(0), "Non-case labels in switch statements are confusing." diff --git a/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql index bbaca72234e2..dc79821e4e68 100644 --- a/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql +++ b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql @@ -1,13 +1,14 @@ /** * @name Loop iteration skipped due to shifting * @description Removing elements from an array while iterating over it can cause the loop to skip over some elements, - * unless the loop index is decremented accordingly. + * unless the loop index is decremented accordingly. * @kind problem * @problem.severity warning * @id js/loop-iteration-skipped-due-to-shifting * @tags correctness * @precision high */ + import javascript /** @@ -24,25 +25,19 @@ class ArrayShiftingCall extends DataFlow::MethodCallNode { (name = "splice" or name = "shift" or name = "unshift") } - DataFlow::SourceNode getArray() { - result = getReceiver().getALocalSource() - } + DataFlow::SourceNode getArray() { result = getReceiver().getALocalSource() } } /** * A call to `splice` on an array. */ class SpliceCall extends ArrayShiftingCall { - SpliceCall() { - name = "splice" - } + SpliceCall() { name = "splice" } /** * Gets the index from which elements are removed and possibly new elemenst are inserted. */ - DataFlow::Node getIndex() { - result = getArgument(0) - } + DataFlow::Node getIndex() { result = getArgument(0) } /** * Gets the number of removed elements. @@ -66,21 +61,21 @@ class SpliceCall extends ArrayShiftingCall { */ class ArrayIterationLoop extends ForStmt { DataFlow::SourceNode array; + LocalVariable indexVariable; - + ArrayIterationLoop() { - exists (RelationalComparison compare | compare = getTest() | + exists(RelationalComparison compare | compare = getTest() | compare.getLesserOperand() = indexVariable.getAnAccess() and - compare.getGreaterOperand() = array.getAPropertyRead("length").asExpr()) and + compare.getGreaterOperand() = array.getAPropertyRead("length").asExpr() + ) and getUpdate().(IncExpr).getOperand() = indexVariable.getAnAccess() } /** * Gets the variable holding the loop variable and current array index. */ - LocalVariable getIndexVariable() { - result = indexVariable - } + LocalVariable getIndexVariable() { result = indexVariable } /** * Gets the loop entry point. @@ -92,12 +87,10 @@ class ArrayIterationLoop extends ForStmt { /** * Gets a call that potentially shifts the elements of the given array. */ - ArrayShiftingCall getAnArrayShiftingCall() { - result.getArray() = array - } + ArrayShiftingCall getAnArrayShiftingCall() { result.getArray() = array } /** - * Gets a call to `splice` that removes elements from the looped-over array at the current index + * Gets a call to `splice` that removes elements from the looped-over array at the current index * * The `splice` call is not guaranteed to be inside the loop body. */ @@ -126,21 +119,21 @@ class ArrayIterationLoop extends ForStmt { hasPathTo(cfg.getAPredecessor()) and getLoopEntry().dominates(cfg.getBasicBlock()) and not hasIndexingManipulation(cfg) and - // Ignore splice calls guarded by an index equality check. // This indicates that the index of an element is the basis for removal, not its value, // which means it may be okay to skip over elements. - not exists (ConditionGuardNode guard, EqualityTest test | cfg = guard | + not exists(ConditionGuardNode guard, EqualityTest test | cfg = guard | test = guard.getTest() and test.getAnOperand() = getIndexVariable().getAnAccess() and - guard.getOutcome() = test.getPolarity()) and - + guard.getOutcome() = test.getPolarity() + ) and // Block flow after inspecting an array element other than that at the current index. // For example, if the splice happens after inspecting `array[i + 1]`, then the next // element has already been "looked at" and so it doesn't matter if we skip it. - not exists (IndexExpr index | cfg = index | + not exists(IndexExpr index | cfg = index | array.flowsToExpr(index.getBase()) and - not index.getIndex() = getIndexVariable().getAnAccess()) + not index.getIndex() = getIndexVariable().getAnAccess() + ) } /** @@ -160,4 +153,6 @@ class ArrayIterationLoop extends ForStmt { from ArrayIterationLoop loop, SpliceCall splice where loop.hasPathThrough(splice, loop.getUpdate().getFirstControlFlowNode()) -select splice, "Removing an array item without adjusting the loop index '" + loop.getIndexVariable().getName() + "' causes the subsequent array item to be skipped." +select splice, + "Removing an array item without adjusting the loop index '" + loop.getIndexVariable().getName() + + "' causes the subsequent array item to be skipped." diff --git a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql index 0629224ed503..91ee1bd4c005 100644 --- a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql +++ b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql @@ -26,19 +26,22 @@ predicate misleadingIndentationCandidate(ControlStmt ctrl, Stmt s1, Stmt s2) { not s2 = ctrl.getAControlledStmt() } -from ControlStmt ctrl, Stmt s1, Stmt s2, string indent, int ctrlStartColumn, int startColumn, - File f, int ctrlStartLine, int startLine1, int startLine2 -where misleadingIndentationCandidate(ctrl, s1, s2) and - ctrl.getLocation().hasLocationInfo(f.getAbsolutePath(), ctrlStartLine, ctrlStartColumn, _, _) and - // `s1` and `s2` are indented the same - s1.getLocation().hasLocationInfo(f.getAbsolutePath(), startLine1, startColumn, _, _) and - s2.getLocation().hasLocationInfo(f.getAbsolutePath(), startLine2, startColumn, _, _) and - // `s1` is indented relative to `ctrl` - startColumn > ctrlStartColumn and - // `ctrl`, `s1` and `s2` all have the same indentation character - f.hasIndentation(ctrlStartLine, indent, _) and - f.hasIndentation(startLine1, indent, _) and - f.hasIndentation(startLine2, indent, _) and - not s2 instanceof EmptyStmt -select (FirstLineOf)s2, "The indentation of this statement suggests that it is controlled by $@, while in fact it is not.", - (FirstLineOf)ctrl, "this statement" \ No newline at end of file +from + ControlStmt ctrl, Stmt s1, Stmt s2, string indent, int ctrlStartColumn, int startColumn, File f, + int ctrlStartLine, int startLine1, int startLine2 +where + misleadingIndentationCandidate(ctrl, s1, s2) and + ctrl.getLocation().hasLocationInfo(f.getAbsolutePath(), ctrlStartLine, ctrlStartColumn, _, _) and + // `s1` and `s2` are indented the same + s1.getLocation().hasLocationInfo(f.getAbsolutePath(), startLine1, startColumn, _, _) and + s2.getLocation().hasLocationInfo(f.getAbsolutePath(), startLine2, startColumn, _, _) and + // `s1` is indented relative to `ctrl` + startColumn > ctrlStartColumn and + // `ctrl`, `s1` and `s2` all have the same indentation character + f.hasIndentation(ctrlStartLine, indent, _) and + f.hasIndentation(startLine1, indent, _) and + f.hasIndentation(startLine2, indent, _) and + not s2 instanceof EmptyStmt +select s2.(FirstLineOf), + "The indentation of this statement suggests that it is controlled by $@, while in fact it is not.", + ctrl.(FirstLineOf), "this statement" diff --git a/javascript/ql/src/Statements/NestedLoopsSameVariable.ql b/javascript/ql/src/Statements/NestedLoopsSameVariable.ql index 408aed0225fa..7288415b903b 100644 --- a/javascript/ql/src/Statements/NestedLoopsSameVariable.ql +++ b/javascript/ql/src/Statements/NestedLoopsSameVariable.ql @@ -16,12 +16,15 @@ import javascript * Gets an iteration variable that loop `for` tests and updates. */ Variable getAnIterationVariable(ForStmt for) { - result.getAnAccess().getParentExpr*() = for.getTest() and - exists (UpdateExpr upd | upd.getParentExpr*() = for.getUpdate() | upd.getOperand() = result.getAnAccess()) + result.getAnAccess().getParentExpr*() = for.getTest() and + exists(UpdateExpr upd | upd.getParentExpr*() = for.getUpdate() | + upd.getOperand() = result.getAnAccess() + ) } from ForStmt outer, ForStmt inner -where inner.nestedIn(outer) and - getAnIterationVariable(outer) = getAnIterationVariable(inner) -select inner.getTest(), "This for statement uses the same loop variable as an enclosing $@.", - outer, "for statement" +where + inner.nestedIn(outer) and + getAnIterationVariable(outer) = getAnIterationVariable(inner) +select inner.getTest(), "This for statement uses the same loop variable as an enclosing $@.", outer, + "for statement" diff --git a/javascript/ql/src/Statements/ReturnAssignsLocal.ql b/javascript/ql/src/Statements/ReturnAssignsLocal.ql index 0a84062c1eff..6d0c79913c27 100644 --- a/javascript/ql/src/Statements/ReturnAssignsLocal.ql +++ b/javascript/ql/src/Statements/ReturnAssignsLocal.ql @@ -15,8 +15,11 @@ import javascript import semmle.javascript.RestrictedLocations from ReturnStmt r, AssignExpr assgn, Variable v -where assgn = r.getExpr().stripParens() and - v = ((Function)r.getContainer()).getScope().getAVariable() and - not v.isCaptured() and - assgn.getLhs() = v.getAnAccess() -select (FirstLineOf)r, "The assignment to " + v.getName() + " is useless, since it is a local variable and will go out of scope." \ No newline at end of file +where + assgn = r.getExpr().stripParens() and + v = (r.getContainer().(Function)).getScope().getAVariable() and + not v.isCaptured() and + assgn.getLhs() = v.getAnAccess() +select r.(FirstLineOf), + "The assignment to " + v.getName() + + " is useless, since it is a local variable and will go out of scope." diff --git a/javascript/ql/src/Statements/ReturnOutsideFunction.ql b/javascript/ql/src/Statements/ReturnOutsideFunction.ql index 05c2ba559142..0cbdbe5ac350 100644 --- a/javascript/ql/src/Statements/ReturnOutsideFunction.ql +++ b/javascript/ql/src/Statements/ReturnOutsideFunction.ql @@ -14,7 +14,8 @@ import javascript import semmle.javascript.RestrictedLocations from ReturnStmt ret, TopLevel tl -where tl = ret.getContainer() and - not tl instanceof EventHandlerCode and - not tl instanceof NodeModule -select (FirstLineOf)ret, "Return statement outside function." \ No newline at end of file +where + tl = ret.getContainer() and + not tl instanceof EventHandlerCode and + not tl instanceof NodeModule +select ret.(FirstLineOf), "Return statement outside function." diff --git a/javascript/ql/src/Statements/SuspiciousUnusedLoopIterationVariable.ql b/javascript/ql/src/Statements/SuspiciousUnusedLoopIterationVariable.ql index 0936cbc30f66..1fd2faba582c 100644 --- a/javascript/ql/src/Statements/SuspiciousUnusedLoopIterationVariable.ql +++ b/javascript/ql/src/Statements/SuspiciousUnusedLoopIterationVariable.ql @@ -20,11 +20,15 @@ import javascript class IncrementExpr extends Expr { IncrementExpr() { // x += e - this instanceof AssignAddExpr or + this instanceof AssignAddExpr + or // ++x or x++ - this instanceof PreIncExpr or this instanceof PostIncExpr or + this instanceof PreIncExpr + or + this instanceof PostIncExpr + or // x = x + e - exists (AssignExpr assgn, Variable v | assgn = this | + exists(AssignExpr assgn, Variable v | assgn = this | assgn.getTarget() = v.getAnAccess() and assgn.getRhs().(AddExpr).getAnOperand().getUnderlyingReference() = v.getAnAccess() ) @@ -35,18 +39,17 @@ class IncrementExpr extends Expr { * Holds if `efl` is a loop whose body increments a variable and does nothing else. */ predicate countingLoop(EnhancedForLoop efl) { - exists (ExprStmt inc | inc.getExpr().stripParens() instanceof IncrementExpr | + exists(ExprStmt inc | inc.getExpr().stripParens() instanceof IncrementExpr | inc = efl.getBody() or inc = efl.getBody().(BlockStmt).getAStmt() ) } from EnhancedForLoop efl, PurelyLocalVariable iter -where iter = efl.getAnIterationVariable() and - not exists (SsaExplicitDefinition ssa | ssa.defines(efl.getIteratorExpr(), iter)) and - exists (ReachableBasicBlock body | body.getANode() = efl.getBody() | - body.getASuccessor+() = body - ) and - not countingLoop(efl) and - not iter.getName().toLowerCase().regexpMatch("(_|dummy|unused).*") -select efl.getIterator(), "For loop variable " + iter.getName() + " is not used in the loop body." \ No newline at end of file +where + iter = efl.getAnIterationVariable() and + not exists(SsaExplicitDefinition ssa | ssa.defines(efl.getIteratorExpr(), iter)) and + exists(ReachableBasicBlock body | body.getANode() = efl.getBody() | body.getASuccessor+() = body) and + not countingLoop(efl) and + not iter.getName().toLowerCase().regexpMatch("(_|dummy|unused).*") +select efl.getIterator(), "For loop variable " + iter.getName() + " is not used in the loop body." diff --git a/javascript/ql/src/Statements/UnreachableStatement.ql b/javascript/ql/src/Statements/UnreachableStatement.ql index 04a7a4d3496a..9793f22aea85 100644 --- a/javascript/ql/src/Statements/UnreachableStatement.ql +++ b/javascript/ql/src/Statements/UnreachableStatement.ql @@ -14,16 +14,17 @@ import javascript import semmle.javascript.RestrictedLocations from Stmt s -where // `s` is unreachable in the CFG - s.getFirstControlFlowNode().isUnreachable() and - // the CFG does not model all possible exceptional control flow, so be conservative about catch clauses - not s instanceof CatchClause and - // function declarations are special and always reachable - not s instanceof FunctionDeclStmt and - // allow a spurious 'break' statement at the end of a switch-case - not exists(Case c, int i | i = c.getNumBodyStmt() | (BreakStmt)s = c.getBodyStmt(i-1)) and - // ignore ambient statements - not s.isAmbient() and - // ignore empty statements - not s instanceof EmptyStmt -select (FirstLineOf)s, "This statement is unreachable." \ No newline at end of file +where + // `s` is unreachable in the CFG + s.getFirstControlFlowNode().isUnreachable() and + // the CFG does not model all possible exceptional control flow, so be conservative about catch clauses + not s instanceof CatchClause and + // function declarations are special and always reachable + not s instanceof FunctionDeclStmt and + // allow a spurious 'break' statement at the end of a switch-case + not exists(Case c, int i | i = c.getNumBodyStmt() | s.(BreakStmt) = c.getBodyStmt(i - 1)) and + // ignore ambient statements + not s.isAmbient() and + // ignore empty statements + not s instanceof EmptyStmt +select s.(FirstLineOf), "This statement is unreachable." diff --git a/javascript/ql/src/Statements/UselessComparisonTest.ql b/javascript/ql/src/Statements/UselessComparisonTest.ql index 419f02a000c3..6066d7c4329b 100644 --- a/javascript/ql/src/Statements/UselessComparisonTest.ql +++ b/javascript/ql/src/Statements/UselessComparisonTest.ql @@ -17,18 +17,20 @@ import javascript * We use this to restrict reachability analysis to a small set of containers. */ predicate hasContradictoryGuardNodes(StmtContainer container) { - exists (ConditionGuardNode guard | + exists(ConditionGuardNode guard | RangeAnalysis::isContradictoryGuardNode(guard) and - container = guard.getContainer()) + container = guard.getContainer() + ) } /** * Holds if `block` is reachable and is in a container with contradictory guard nodes. */ predicate isReachable(BasicBlock block) { - exists (StmtContainer container | + exists(StmtContainer container | hasContradictoryGuardNodes(container) and - block = container.getEntryBB()) + block = container.getEntryBB() + ) or isReachable(block.getAPredecessor()) and not RangeAnalysis::isContradictoryGuardNode(block.getANode()) @@ -50,11 +52,13 @@ predicate isBlockedByContradictoryGuardNodes(BasicBlock block, ConditionGuardNod * Holds if the given guard node is contradictory and causes an expression or statement to be unreachable. */ predicate isGuardNodeWithDeadCode(ConditionGuardNode guard) { - exists (BasicBlock block | + exists(BasicBlock block | isBlockedByContradictoryGuardNodes(block, guard) and - block.getANode() instanceof ExprOrStmt) + block.getANode() instanceof ExprOrStmt + ) } from ConditionGuardNode guard where isGuardNodeWithDeadCode(guard) -select guard.getTest(), "The condition '" + guard.getTest() + "' is always " + guard.getOutcome().booleanNot() + "." +select guard.getTest(), + "The condition '" + guard.getTest() + "' is always " + guard.getOutcome().booleanNot() + "." diff --git a/javascript/ql/src/Statements/UselessConditional.ql b/javascript/ql/src/Statements/UselessConditional.ql index f5f92454c69c..e2994f0cb207 100644 --- a/javascript/ql/src/Statements/UselessConditional.ql +++ b/javascript/ql/src/Statements/UselessConditional.ql @@ -25,9 +25,9 @@ import semmle.javascript.DefensiveProgramming */ predicate isSymbolicConstant(Variable v) { // defined exactly once - count (VarDef vd | vd.getAVariable() = v) = 1 and + count(VarDef vd | vd.getAVariable() = v) = 1 and // the definition is either a `const` declaration or it assigns a constant to it - exists (VarDef vd | vd.getAVariable() = v and count(vd.getAVariable()) = 1 | + exists(VarDef vd | vd.getAVariable() = v and count(vd.getAVariable()) = 1 | vd.(VariableDeclarator).getDeclStmt() instanceof ConstDeclStmt or isConstant(vd.getSource()) ) @@ -46,7 +46,7 @@ predicate isConstant(Expr e) { */ 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() @@ -60,11 +60,11 @@ predicate isInitialParameterUse(Expr e) { */ predicate isConstantBooleanReturnValue(Expr e) { // unlike `SourceNode.flowsTo` this will not include uses we have refinement information for - exists (DataFlow::CallNode call | - exists (call.analyze().getTheBooleanValue()) | - e = call.asExpr() or + exists(DataFlow::CallNode call | exists(call.analyze().getTheBooleanValue()) | + e = call.asExpr() + or // also support return values that are assigned to variables - exists (SsaExplicitDefinition ssa | + exists(SsaExplicitDefinition ssa | ssa.getDef().getSource() = call.asExpr() and ssa.getVariable().getAUse() = e ) @@ -113,10 +113,14 @@ predicate whitelist(Expr e) { * is not used for anything other than this truthiness check. */ predicate isExplicitConditional(ASTNode cond, Expr e) { - e = cond.(IfStmt).getCondition() or - e = cond.(LoopStmt).getTest() or - e = cond.(ConditionalExpr).getCondition() or - isExplicitConditional(_, cond) and e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand() + e = cond.(IfStmt).getCondition() + or + e = cond.(LoopStmt).getTest() + or + e = cond.(ConditionalExpr).getCondition() + or + isExplicitConditional(_, cond) and + e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand() } /** @@ -132,19 +136,18 @@ predicate isConditional(ASTNode cond, Expr e) { } from ASTNode cond, DataFlow::AnalyzedNode op, boolean cv, ASTNode sel, string msg -where isConditional(cond, op.asExpr()) and - cv = op.getTheBooleanValue()and - not whitelist(op.asExpr()) and - - // if `cond` is of the form ` && `, - // we suggest replacing it with `, ` - if cond.(LogAndExpr).getLeftOperand() = op.asExpr() and cv = true and not op.asExpr().isPure() then - (sel = cond and msg = "This logical 'and' expression can be replaced with a comma expression.") - - // otherwise we just report that `op` always evaluates to `cv` - else ( - sel = op.asExpr().stripParens() and - msg = "This " + describeExpression(sel) + " always evaluates to " + cv + "." - ) - +where + isConditional(cond, op.asExpr()) and + cv = op.getTheBooleanValue() and + not whitelist(op.asExpr()) and + // if `cond` is of the form ` && `, + // we suggest replacing it with `, ` + if cond.(LogAndExpr).getLeftOperand() = op.asExpr() and cv = true and not op.asExpr().isPure() + then ( + sel = cond and msg = "This logical 'and' expression can be replaced with a comma expression." + ) else ( + // otherwise we just report that `op` always evaluates to `cv` + sel = op.asExpr().stripParens() and + msg = "This " + describeExpression(sel) + " always evaluates to " + cv + "." + ) select sel, msg diff --git a/javascript/ql/src/definitions.ql b/javascript/ql/src/definitions.ql index a31bf79d4bad..ff97699d0aeb 100644 --- a/javascript/ql/src/definitions.ql +++ b/javascript/ql/src/definitions.ql @@ -19,17 +19,16 @@ private import Declarations.Declarations * `x` has kind `"V"`. */ string refKind(RefExpr r) { - if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference()) then - result = "M" - else - result = "V" + if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference()) + then result = "M" + else result = "V" } /** * Gets a class, function or object literal `va` may refer to. */ ASTNode lookupDef(VarAccess va) { - exists (AbstractValue av | av = va.analyze().getAValue() | + exists(AbstractValue av | av = va.analyze().getAValue() | result = av.(AbstractClass).getClass() or result = av.(AbstractFunction).getFunction() or result = av.(AbstractObjectLiteral).getObjectExpr() @@ -72,15 +71,15 @@ predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) { predicate importLookup(PathExpr path, Module target, string kind) { kind = "I" and ( - exists (Import i | - path = i.getImportedPath() and - target = i.getImportedModule() - ) - or - exists (ReExportDeclaration red | - path = red.getImportedPath() and - target = red.getImportedModule() - ) + exists(Import i | + path = i.getImportedPath() and + target = i.getImportedModule() + ) + or + exists(ReExportDeclaration red | + path = red.getImportedPath() and + target = red.getImportedModule() + ) ) } @@ -88,18 +87,20 @@ predicate importLookup(PathExpr path, Module target, string kind) { * Gets a node that may write the property read by `prn`. */ ASTNode getAWrite(DataFlow::PropRead prn) { - exists (DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName | - base = prn.getBase() and propName = prn.getPropertyName() and - baseVal = base.getAValue().getAPrototype*() | + exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName | + base = prn.getBase() and + propName = prn.getPropertyName() and + baseVal = base.getAValue().getAPrototype*() + | // write to a property on baseVal - exists (AnalyzedPropertyWrite apw | + exists(AnalyzedPropertyWrite apw | result = apw.getAstNode() and apw.writes(baseVal, propName, _) ) or // non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled // separately - exists (ClassDefinition c, MemberDefinition m | + exists(ClassDefinition c, MemberDefinition m | m = c.getMember(propName) and baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and result = m.getNameExpr() @@ -114,7 +115,7 @@ ASTNode getAWrite(DataFlow::PropRead prn) { * at the moment. */ predicate propertyLookup(Expr prop, ASTNode write, string kind) { - exists (DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() | + exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() | count(getAWrite(prn)) = 1 and write = getAWrite(prn) and kind = "M" @@ -125,15 +126,17 @@ predicate propertyLookup(Expr prop, ASTNode write, string kind) { * Holds if `ref` is an identifier that refers to a type or namespace declared at `decl`. */ predicate typeLookup(ASTNode ref, ASTNode decl, string kind) { - exists (TypeAccess typeAccess | + exists(TypeAccess typeAccess | ref = typeAccess.getIdentifier() and decl = typeAccess.getTypeName().getADefinition() and - kind = "T") + kind = "T" + ) or - exists (NamespaceAccess namespaceAccess | + exists(NamespaceAccess namespaceAccess | ref = namespaceAccess.getIdentifier() and decl = namespaceAccess.getNamespace().getADefinition() and - kind = "T") + kind = "T" + ) } /** @@ -142,19 +145,26 @@ predicate typeLookup(ASTNode ref, ASTNode decl, string kind) { predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) { not variableDefLookup(ref, decl, _) and not propertyLookup(ref, decl, _) and - exists (InvokeExpr invoke, Expr callee | + exists(InvokeExpr invoke, Expr callee | callee = invoke.getCallee().getUnderlyingReference() and (ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and decl = invoke.getResolvedCallee() and - kind = "M") + kind = "M" + ) } from ASTNode ref, ASTNode decl, string kind -where variableDefLookup(ref, decl, kind) or - // prefer definitions over declarations - not variableDefLookup(ref, _, _) and variableDeclLookup(ref, decl, kind) or - importLookup(ref, decl, kind) or - propertyLookup(ref, decl, kind) or - typeLookup(ref, decl, kind) or - typedInvokeLookup(ref, decl, kind) +where + variableDefLookup(ref, decl, kind) + or + // prefer definitions over declarations + not variableDefLookup(ref, _, _) and variableDeclLookup(ref, decl, kind) + or + importLookup(ref, decl, kind) + or + propertyLookup(ref, decl, kind) + or + typeLookup(ref, decl, kind) + or + typedInvokeLookup(ref, decl, kind) select ref, decl, kind diff --git a/javascript/ql/src/filters/ClassifyFiles.ql b/javascript/ql/src/filters/ClassifyFiles.ql index 70b8f635e4a6..b60b5b8f2ef8 100644 --- a/javascript/ql/src/filters/ClassifyFiles.ql +++ b/javascript/ql/src/filters/ClassifyFiles.ql @@ -20,14 +20,13 @@ import semmle.javascript.dependencies.FrameworkLibraries * `templates`. */ predicate maybeCausedByTemplate(JSParseError e) { - exists (File f | f = e.getFile() | + exists(File f | f = e.getFile() | e.getLine().indexOf(Templating::getADelimiter()) <= e.getLocation().getStartColumn() or f.getAbsolutePath().regexpMatch("(?i).*\\btemplates?\\b.*") ) } - /** * Holds if `e` is an expression in the form `o.p1.p2.p3....pn`. */ @@ -40,9 +39,9 @@ private predicate isNestedDotExpr(DotExpr e) { * Holds if `tl` only contains variable declarations and field reads. */ private predicate looksLikeExterns(TopLevel tl) { - forex (Stmt s | s.getParent() = tl | - exists (VarDeclStmt vds | vds = s | - forall (VariableDeclarator vd | vd = vds.getADecl() | not exists(vd.getInit())) + forex(Stmt s | s.getParent() = tl | + exists(VarDeclStmt vds | vds = s | + forall(VariableDeclarator vd | vd = vds.getADecl() | not exists(vd.getInit())) ) or isNestedDotExpr(s.(ExprStmt).getExpr()) @@ -62,18 +61,19 @@ private predicate looksLikeExterns(TopLevel tl) { predicate classify(File f, string category) { isGenerated(f.getATopLevel()) and category = "generated" or - exists (Test t | t.getFile() = f | category = "test") + exists(Test t | t.getFile() = f | category = "test") or - (f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel())) and category = "externs" + (f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel())) and + category = "externs" or f.getATopLevel() instanceof FrameworkLibraryInstance and category = "library" or - exists (JSParseError err | maybeCausedByTemplate(err) | + exists(JSParseError err | maybeCausedByTemplate(err) | f = err.getFile() and category = "template" ) or // Polymer templates - exists (HTML::Element elt | elt.getName() = "template" | + exists(HTML::Element elt | elt.getName() = "template" | f = elt.getFile() and category = "template" ) } diff --git a/javascript/ql/src/filters/FilterFrameworks.ql b/javascript/ql/src/filters/FilterFrameworks.ql index 3e972c39d079..ba21a3f596bd 100644 --- a/javascript/ql/src/filters/FilterFrameworks.ql +++ b/javascript/ql/src/filters/FilterFrameworks.ql @@ -11,4 +11,4 @@ import external.DefectFilter from DefectResult defres where nonFrameworkFile(defres.getFile()) -select defres, defres.getMessage() \ No newline at end of file +select defres, defres.getMessage() diff --git a/javascript/ql/src/filters/FilterFrameworks.qll b/javascript/ql/src/filters/FilterFrameworks.qll index bdae00768431..d2e94305a949 100644 --- a/javascript/ql/src/filters/FilterFrameworks.qll +++ b/javascript/ql/src/filters/FilterFrameworks.qll @@ -3,6 +3,5 @@ import semmle.javascript.dependencies.FrameworkLibraries /** * Holds if file `f` does not contain a framework library. */ -pragma[nomagic] predicate nonFrameworkFile(File f) { - not exists (FrameworkLibraryInstance fl | fl.getFile() = f) -} +pragma[nomagic] +predicate nonFrameworkFile(File f) { not exists(FrameworkLibraryInstance fl | fl.getFile() = f) } diff --git a/javascript/ql/src/filters/FilterFrameworksForMetric.ql b/javascript/ql/src/filters/FilterFrameworksForMetric.ql index e100255f516a..72916a263512 100644 --- a/javascript/ql/src/filters/FilterFrameworksForMetric.ql +++ b/javascript/ql/src/filters/FilterFrameworksForMetric.ql @@ -10,4 +10,4 @@ import external.MetricFilter from MetricResult res where nonFrameworkFile(res.getFile()) -select res, res.getValue() \ No newline at end of file +select res, res.getValue() diff --git a/javascript/ql/src/filters/FilterGenerated.ql b/javascript/ql/src/filters/FilterGenerated.ql index f1edfd6fc003..5fe34572f31a 100644 --- a/javascript/ql/src/filters/FilterGenerated.ql +++ b/javascript/ql/src/filters/FilterGenerated.ql @@ -12,4 +12,4 @@ import external.DefectFilter from DefectResult defres where not isGeneratedCode(defres.getFile()) -select defres, defres.getMessage() \ No newline at end of file +select defres, defres.getMessage() diff --git a/javascript/ql/src/filters/FilterGeneratedForMetric.ql b/javascript/ql/src/filters/FilterGeneratedForMetric.ql index 2a2dea78f4e2..89ba33dcda81 100644 --- a/javascript/ql/src/filters/FilterGeneratedForMetric.ql +++ b/javascript/ql/src/filters/FilterGeneratedForMetric.ql @@ -12,4 +12,4 @@ import external.MetricFilter from MetricResult res where not isGeneratedCode(res.getFile()) -select res, res.getValue() \ No newline at end of file +select res, res.getValue() diff --git a/javascript/ql/src/filters/FilterMinified.ql b/javascript/ql/src/filters/FilterMinified.ql index 768bbb942669..8eff17ba7dc6 100644 --- a/javascript/ql/src/filters/FilterMinified.ql +++ b/javascript/ql/src/filters/FilterMinified.ql @@ -10,4 +10,4 @@ import external.DefectFilter from DefectResult defres where not defres.getFile().getATopLevel().isMinified() -select defres, defres.getMessage() \ No newline at end of file +select defres, defres.getMessage() diff --git a/javascript/ql/src/filters/FilterMinifiedForMetric.ql b/javascript/ql/src/filters/FilterMinifiedForMetric.ql index 3f87daea6dd1..e209fb0ee3ed 100644 --- a/javascript/ql/src/filters/FilterMinifiedForMetric.ql +++ b/javascript/ql/src/filters/FilterMinifiedForMetric.ql @@ -10,4 +10,4 @@ import external.MetricFilter from MetricResult res where not res.getFile().getATopLevel().isMinified() -select res, res.getValue() \ No newline at end of file +select res, res.getValue() diff --git a/javascript/ql/src/semmle/javascript/AMD.qll b/javascript/ql/src/semmle/javascript/AMD.qll index ff2c27d51e80..c66d460c7ebd 100644 --- a/javascript/ql/src/semmle/javascript/AMD.qll +++ b/javascript/ql/src/semmle/javascript/AMD.qll @@ -27,9 +27,11 @@ class AMDModuleDefinition extends CallExpr { AMDModuleDefinition() { getParent() instanceof ExprStmt and getCallee().(GlobalVarAccess).getName() = "define" and - exists (int n | n = getNumArgument() | - n = 1 or - n = 2 and getArgument(0) instanceof ArrayExpr or + exists(int n | n = getNumArgument() | + n = 1 + or + n = 2 and getArgument(0) instanceof ArrayExpr + or n = 3 and getArgument(0) instanceof ConstantString and getArgument(1) instanceof ArrayExpr ) } @@ -41,9 +43,7 @@ class AMDModuleDefinition extends CallExpr { } /** Gets the `i`th dependency of this module definition. */ - PathExpr getDependency(int i) { - result = getDependencies().getElement(i) - } + PathExpr getDependency(int i) { result = getDependencies().getElement(i) } /** Gets a dependency of this module definition. */ PathExpr getADependency() { @@ -57,10 +57,7 @@ class AMDModuleDefinition extends CallExpr { * Gets the factory expression of this module definition, * which may be a function or a literal. */ - deprecated - Expr getFactoryExpr() { - result = getFactoryNode().asExpr() - } + deprecated Expr getFactoryExpr() { result = getFactoryNode().asExpr() } /** * Gets a data flow node containing the factory value of this module definition. @@ -73,26 +70,24 @@ class AMDModuleDefinition extends CallExpr { /** Gets the expression defining this module. */ Expr getModuleExpr() { - exists (DataFlow::Node f | f = getFactoryNode() | - if f instanceof DataFlow::FunctionNode then - exists (ReturnStmt ret | ret.getContainer() = f.(DataFlow::FunctionNode).getAstNode() | + exists(DataFlow::Node f | f = getFactoryNode() | + if f instanceof DataFlow::FunctionNode + then + exists(ReturnStmt ret | ret.getContainer() = f.(DataFlow::FunctionNode).getAstNode() | result = ret.getExpr() ) - else - result = f.asExpr() + else result = f.asExpr() ) } /** Gets a source node whose value becomes the definition of this module. */ - DataFlow::SourceNode getAModuleSource() { - result.flowsToExpr(getModuleExpr()) - } + DataFlow::SourceNode getAModuleSource() { result.flowsToExpr(getModuleExpr()) } /** * Holds if `p` is the parameter corresponding to dependency `dep`. */ predicate dependencyParameter(PathExpr dep, Parameter p) { - exists (int i | + exists(int i | dep = getDependency(i) and p = getFactoryParameter(i) ) @@ -111,7 +106,7 @@ class AMDModuleDefinition extends CallExpr { * `dep1` and `dep2`. */ Parameter getDependencyParameter(string name) { - exists (PathExpr dep | + exists(PathExpr dep | dependencyParameter(dep, result) and dep.getValue() = name ) @@ -128,21 +123,21 @@ class AMDModuleDefinition extends CallExpr { * Gets the parameter corresponding to the pseudo-dependency `require`. */ SimpleParameter getRequireParameter() { - result = getDependencyParameter("require") or + result = getDependencyParameter("require") + or // if no dependencies are listed, the first parameter is assumed to be `require` not exists(getDependencies()) and result = getFactoryParameter(0) } pragma[noinline] - private Variable getRequireVariable() { - result = getRequireParameter().getVariable() - } + private Variable getRequireVariable() { result = getRequireParameter().getVariable() } /** * Gets the parameter corresponding to the pseudo-dependency `exports`. */ SimpleParameter getExportsParameter() { - result = getDependencyParameter("exports") or + result = getDependencyParameter("exports") + or // if no dependencies are listed, the second parameter is assumed to be `exports` not exists(getDependencies()) and result = getFactoryParameter(1) } @@ -151,7 +146,8 @@ class AMDModuleDefinition extends CallExpr { * Gets the parameter corresponding to the pseudo-dependency `module`. */ SimpleParameter getModuleParameter() { - result = getDependencyParameter("module") or + result = getDependencyParameter("module") + or // if no dependencies are listed, the third parameter is assumed to be `module` not exists(getDependencies()) and result = getFactoryParameter(2) } @@ -165,10 +161,11 @@ class AMDModuleDefinition extends CallExpr { result = getModuleExpr().analyze().getAValue() or // explicit exports: anything assigned to `module.exports` - exists (AbstractProperty moduleExports, AMDModule m | + exists(AbstractProperty moduleExports, AMDModule m | this = m.getDefine() and moduleExports.getBase().(AbstractModuleObject).getModule() = m and - moduleExports.getPropertyName() = "exports" | + moduleExports.getPropertyName() = "exports" + | result = moduleExports.getAValue() ) } @@ -184,7 +181,7 @@ class AMDModuleDefinition extends CallExpr { /** A path expression appearing in the list of dependencies of an AMD module. */ private class AMDDependencyPath extends PathExprInModule, ConstantString { AMDDependencyPath() { - exists (AMDModuleDefinition amd | this.getParentExpr*() = amd.getDependencies().getAnElement()) + exists(AMDModuleDefinition amd | this.getParentExpr*() = amd.getDependencies().getAnElement()) } override string getValue() { result = this.(ConstantString).getStringValue() } @@ -193,7 +190,7 @@ private class AMDDependencyPath extends PathExprInModule, ConstantString { /** A path expression appearing in a `require` call in an AMD module. */ private class AMDRequirePath extends PathExprInModule, ConstantString { AMDRequirePath() { - exists (AMDModuleDefinition amd | this.getParentExpr*() = amd.getARequireCall().getAnArgument()) + exists(AMDModuleDefinition amd | this.getParentExpr*() = amd.getARequireCall().getAnArgument()) } override string getValue() { result = this.(ConstantString).getStringValue() } @@ -212,21 +209,15 @@ private predicate amdModuleTopLevel(AMDModuleDefinition def, TopLevel tl) { * An AMD-style module. */ class AMDModule extends Module { - AMDModule() { - strictcount (AMDModuleDefinition def | amdModuleTopLevel(def, this)) = 1 - } + AMDModule() { strictcount(AMDModuleDefinition def | amdModuleTopLevel(def, this)) = 1 } /** Gets the definition of this module. */ - AMDModuleDefinition getDefine() { - amdModuleTopLevel(result, this) - } + AMDModuleDefinition getDefine() { amdModuleTopLevel(result, this) } - override Module getAnImportedModule() { - result.getFile() = resolve(getDefine().getADependency()) - } + override Module getAnImportedModule() { result.getFile() = resolve(getDefine().getADependency()) } override predicate exports(string name, ASTNode export) { - exists (DataFlow::PropWrite pwn | export = pwn.getAstNode() | + exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() | pwn.getBase().analyze().getAValue() = getDefine().getAModuleExportsValue() and name = pwn.getPropertyName() ) diff --git a/javascript/ql/src/semmle/javascript/AST.qll b/javascript/ql/src/semmle/javascript/AST.qll index 696d0b1af91c..666bd0951d1b 100644 --- a/javascript/ql/src/semmle/javascript/AST.qll +++ b/javascript/ql/src/semmle/javascript/AST.qll @@ -12,13 +12,11 @@ import javascript * such as obtaining the children of an AST node. */ class ASTNode extends @ast_node, Locatable { - override Location getLocation() { - hasLocation(this, result) - } + override Location getLocation() { hasLocation(this, result) } /** Gets the first token belonging to this element. */ Token getFirstToken() { - exists (Location l1, Location l2 | + exists(Location l1, Location l2 | l1 = this.getLocation() and l2 = result.getLocation() and l1.getFile() = l2.getFile() and @@ -29,7 +27,7 @@ class ASTNode extends @ast_node, Locatable { /** Gets the last token belonging to this element. */ Token getLastToken() { - exists (Location l1, Location l2 | + exists(Location l1, Location l2 | l1 = this.getLocation() and l2 = result.getLocation() and l1.getFile() = l2.getFile() and @@ -42,13 +40,20 @@ class ASTNode extends @ast_node, Locatable { /** Gets a token belonging to this element. */ Token getAToken() { - exists (string path, int sl, int sc, int el, int ec, - int tksl, int tksc, int tkel, int tkec | + exists(string path, int sl, int sc, int el, int ec, int tksl, int tksc, int tkel, int tkec | this.getLocation().hasLocationInfo(path, sl, sc, el, ec) and - result.getLocation().hasLocationInfo(path, tksl, tksc, tkel, tkec) | - (sl < tksl or (sl = tksl and sc <= tksc)) - and - (tkel < el or (tkel = el and tkec <= ec)) + result.getLocation().hasLocationInfo(path, tksl, tksc, tkel, tkec) + | + ( + sl < tksl + or + (sl = tksl and sc <= tksc) + ) and + ( + tkel < el + or + (tkel = el and tkec <= ec) + ) ) and // exclude empty EOF token not result instanceof EOFToken @@ -56,9 +61,7 @@ class ASTNode extends @ast_node, Locatable { /** Gets the toplevel syntactic unit to which this element belongs. */ cached - TopLevel getTopLevel() { - result = getParent().getTopLevel() - } + TopLevel getTopLevel() { result = getParent().getTopLevel() } /** * Gets the `i`th child node of this node. @@ -74,64 +77,40 @@ class ASTNode extends @ast_node, Locatable { } /** Gets the `i`th child statement of this node. */ - Stmt getChildStmt(int i) { - stmts(result, _, this, i, _) - } + Stmt getChildStmt(int i) { stmts(result, _, this, i, _) } /** Gets the `i`th child expression of this node. */ - Expr getChildExpr(int i) { - exprs(result, _, this, i, _) - } + Expr getChildExpr(int i) { exprs(result, _, this, i, _) } /** Gets the `i`th child type expression of this node. */ - TypeExpr getChildTypeExpr(int i) { - typeexprs(result, _, this, i, _) - } + TypeExpr getChildTypeExpr(int i) { typeexprs(result, _, this, i, _) } /** Gets a child node of this node. */ - ASTNode getAChild() { - result = getChild(_) - } + ASTNode getAChild() { result = getChild(_) } /** Gets a child expression of this node. */ - Expr getAChildExpr() { - result = getChildExpr(_) - } + Expr getAChildExpr() { result = getChildExpr(_) } /** Gets a child statement of this node. */ - Stmt getAChildStmt() { - result = getChildStmt(_) - } + Stmt getAChildStmt() { result = getChildStmt(_) } /** Gets the number of child nodes of this node. */ - int getNumChild() { - result = count(getAChild()) - } + int getNumChild() { result = count(getAChild()) } /** Gets the number of child expressions of this node. */ - int getNumChildExpr() { - result = count(getAChildExpr()) - } + int getNumChildExpr() { result = count(getAChildExpr()) } /** Gets the number of child statements of this node. */ - int getNumChildStmt() { - result = count(getAChildStmt()) - } + int getNumChildStmt() { result = count(getAChildStmt()) } /** Gets the parent node of this node, if any. */ - ASTNode getParent() { - this = result.getAChild() - } + ASTNode getParent() { this = result.getAChild() } /** Gets the first control flow node belonging to this syntactic entity. */ - ControlFlowNode getFirstControlFlowNode() { - result = this - } + ControlFlowNode getFirstControlFlowNode() { result = this } /** Holds if this syntactic entity belongs to an externs file. */ - predicate inExternsFile() { - getTopLevel().isExterns() - } + predicate inExternsFile() { getTopLevel().isExterns() } /** * Holds if this is part of an ambient declaration or type annotation in a TypeScript file. @@ -142,9 +121,7 @@ class ASTNode extends @ast_node, Locatable { * The TypeScript compiler emits no code for ambient declarations, but they * can affect name resolution and type checking at compile-time. */ - predicate isAmbient() { - getParent().isAmbient() - } + predicate isAmbient() { getParent().isAmbient() } } /** @@ -158,9 +135,9 @@ class TopLevel extends @toplevel, StmtContainer { // file name contains 'min' (not as part of a longer word) getFile().getBaseName().regexpMatch(".*[^-.]*[-.]min([-.].*)?\\.\\w+") or - exists (int numstmt | numstmt = strictcount(Stmt s | s.getTopLevel() = this) | + exists(int numstmt | numstmt = strictcount(Stmt s | s.getTopLevel() = this) | // there are more than two statements per line on average - (float)numstmt / getNumberOfLines() > 2 and + numstmt.(float) / getNumberOfLines() > 2 and // and there are at least ten statements overall numstmt >= 10 ) @@ -169,45 +146,32 @@ class TopLevel extends @toplevel, StmtContainer { /** Holds if this toplevel is an externs definitions file. */ predicate isExterns() { // either it was explicitly extracted as an externs file... - isExterns(this) or + isExterns(this) + or // ...or it has a comment with an `@externs` tag in it - exists (JSDocTag externs | + exists(JSDocTag externs | externs.getTitle() = "externs" and externs.getTopLevel() = this ) } /** Gets the toplevel to which this element belongs, that is, itself. */ - override TopLevel getTopLevel() { - result = this - } + override TopLevel getTopLevel() { result = this } /** Gets the number of lines in this toplevel. */ - int getNumberOfLines() { - numlines(this, result, _, _) - } + int getNumberOfLines() { numlines(this, result, _, _) } /** Gets the number of lines containing code in this toplevel. */ - int getNumberOfLinesOfCode() { - numlines(this, _, result, _) - } + int getNumberOfLinesOfCode() { numlines(this, _, result, _) } /** Gets the number of lines containing comments in this toplevel. */ - int getNumberOfLinesOfComments() { - numlines(this, _, _, result) - } + int getNumberOfLinesOfComments() { numlines(this, _, _, result) } - override predicate isStrict() { - getAStmt() instanceof StrictModeDecl - } + override predicate isStrict() { getAStmt() instanceof StrictModeDecl } - override ControlFlowNode getFirstControlFlowNode() { - result = getEntry() - } + override ControlFlowNode getFirstControlFlowNode() { result = getEntry() } - override string toString() { - result = "" - } + override string toString() { result = "" } override predicate isAmbient() { getFile().getFileType().isTypeScript() and @@ -218,50 +182,40 @@ class TopLevel extends @toplevel, StmtContainer { /** * A stand-alone file or script originating from an HTML `