mirror of
https://github.com/github/codeql.git
synced 2026-01-11 13:40:21 +01:00
151 lines
4.9 KiB
Plaintext
151 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) and exists(request)
|
|
or
|
|
(
|
|
isServiceDirectiveOrFilterFunction(request) or
|
|
isRunMethod(request) or
|
|
isControllerFunction(request)
|
|
) and
|
|
kind = ["value", "service", "factory", "constant", "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 + "."
|