Files
codeql/javascript/ql/src/AngularJS/IncompatibleService.ql
2018-08-02 17:53:23 +01:00

135 lines
4.9 KiB
Plaintext

/**
* @name Incompatible dependency injection
* @description Dependency-injecting a service of the wrong kind causes an error at runtime.
* @kind problem
* @problem.severity error
* @precision high
* @id js/angular/incompatible-service
* @tags correctness
* frameworks/angularjs
*/
import javascript
import AngularJS
/**
* Holds if `request` originates from a "service", "directive" or "filter" method call.
*/
predicate isServiceDirectiveOrFilterFunction(InjectableFunctionServiceRequest request) {
exists(InjectableFunction f | f = request.getAnInjectedFunction() |
exists(ServiceRecipeDefinition def | def.getAFactoryFunction() = f) or
exists(FactoryRecipeDefinition def | def.getAFactoryFunction() = f) or
exists(DirectiveDefinition def | def.getAFactoryFunction() = f) or
exists(FilterDefinition def | def.getAFactoryFunction() = f)
)
}
/**
* Holds if `request` originates from a "controller" method call.
*/
predicate isControllerFunction(InjectableFunctionServiceRequest request) {
exists(InjectableFunction f | f = request.getAnInjectedFunction() |
exists(ControllerDefinition def | def.getAFactoryFunction() = f)
)
}
/**
* Holds if `request` originates from a "run" method call.
*/
predicate isRunMethod(InjectableFunctionServiceRequest request) {
exists(InjectableFunction f | f = request.getAnInjectedFunction() |
exists(RunMethodDefinition def | def.getRunMethod() = f)
)
}
/**
* Holds if `request` originates from a "config" method call.
*/
predicate isConfigMethod(InjectableFunctionServiceRequest request) {
exists(InjectableFunction f | f = request.getAnInjectedFunction() |
exists(ConfigMethodDefinition def | def.getConfigMethod() = f)
)
}
/**
* Holds if `kind` is a service kind that is compatible with all requests.
*/
predicate isWildcardKind(string kind) {
kind = "type" or // builtins of kind "type" are usable everywhere
kind = "decorator" // a decorator is always allowed, its decoratee might not be
}
/**
* Holds if `request` is compatible with a service of kind `kind`.
* (see https://docs.angularjs.org/guide/di)
*/
predicate isCompatibleRequestedService(InjectableFunctionServiceRequest request, string kind) {
isWildcardKind(kind) or
((isServiceDirectiveOrFilterFunction(request) or
isRunMethod(request) or
isControllerFunction(request)
) 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 (
kind = "constant" or
kind = "provider"
)
)
}
/**
* Gets the kind of a service named `serviceName`.
*/
string getServiceKind(InjectableFunctionServiceRequest request, string serviceName) {
exists(ServiceReference id | id = request.getAServiceDefinition(serviceName) |
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"
else result = "provider-value"
))
)
)
}
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
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
(
(isServiceDirectiveOrFilterFunction(request) and
componentDescriptionString = "Components such as services, directives, filters, and animations"
) or
(isControllerFunction(request) and
componentDescriptionString = "Controllers"
) or
(isRunMethod(request) and
componentDescriptionString = "Run methods"
) 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 + "."