Modelling APIs with Coko
To evaluate rules for an API, Coko needs an understanding of the components of the API, such as classes or functions. In Coko these components are modelled through interfaces, classes and a class called Op
.
Ops¶
Ops
are the basic building blocks for writing policies in Coko. With Ops
you can model and group functions of the API that serve a similar functionality. They are also a way to define queries to the Codyze backend for finding calls to these functions. Each Op
object is one query to the backend.
There are currently two types of Ops
, FunctionOps
for modelling functions and ConstructorOps
for modelling constructors in object-oriented languages. They are both built with type-safe builders. The following sections will explain the builders for each Op
type.
FunctionOps¶
The function op()
is the start for building FunctionOps
. Within the block of op()
the fully qualified name of the functions you want to model can be specified as a string. In the block of the fully qualified name the arguments to function can be defined. They serve as a filter for the query. Only calls to the function with the same number of arguments with the same value at each position will be found by the query.
In signature
it is also possible to specify unordered arguments. These are arguments, that should somehow be passed to the function calls we want to find, but it is not important, in which position they are passed.
op {
"my.fully.qualified.name" { // (1)!
signature(5..10) // (2)!
signature(".*one.*") // (3)!
signature(0..5, listOf("one", "two")) // (4)!
signature() // (5)!
}
"my.other.fully.qualified.name" { // (6)!
signature { // (7)!
- ".*" // (8)!
-7 // (9)!
}
signature(arrayOf(4)) { // (10)!
- 123
}
}
}
- The fully qualified name of the function we want to find.
- Filters for calls to
my.fully.qualified.name
that have as only argument a number between 5 and 10. - Filters for calls to
my.fully.qualified.name
that have a string as only argument that contains "one", for examplemy.fully.qualified.name("tone")
. - Filters for calls to
my.fully.qualified.name
that have a number between 0 and 5 as first argument and as second argument either the string "one" or "two". - Filters for calls to
my.fully.qualified.name
where no arguments were passed. - The fully qualified name of the other function we want to find.
- The
signature
function can also invoke a type-safe builder. - In the type-safe builder of
signature
the arguments are listed using-
. - The space after the
-
is optional. - The unordered arguments are given as an array to
signature
. In this example, the unordered argument is 4.
ConstructorOps¶
The function of the builder for ConstructorOps
is constructor()
. The fully qualified name of the class is the first argument. In the block of constructor()
you can specify the arguments to the constructor like for the FunctionOp
. They serve the same purpose as for FunctionOps
.
constructor("my.fully.qualified.MyClass") { // (1)!
signature() // (2)!
}
- This models the constructor of the class
my.fully.qualified.MyClass
. - The signature is directly specified in
ConstructorOps
, because the name of the constructor is clear.
Special argument types¶
Arguments in Ops
are used to filter calls of a function based on the arguments that are given to the calls. In Coko there are a few special argument types that help with the filtering.
The first type is the Wildcard
object. If the Wildcard
object is given as an argument to Op
, the filter will allow any kind of value in the same argument position of the function calls.
The second type is null
. It can be used to signify that the given arguments should be filtered out of the query. This type will be helpful when constructing Op
templates with Coko functions.
Another special type are ParameterGroups
. They are a filter to express that the argument at the position should be composed of multiple values. An example would be if the argument is a string that should contain multiple strings, for example foo("Start string" + 12 + "End string")
. This can be modeled with ParameterGroups
. Coko offers the DSL function group
, in which these values can be specified.
The last types are the Type
class and the ParamWithType
class. The Type
class is used to filter the type of the arguments. The fully qualified name of the type must be given ParamWithType
combines the filter for the value with the filter for the type.
op {
"my.fully.qualified.name" {
signature(Wildcard) // (1)!
signature(Wildcard, 2) // (2)!
signature(null, 1) // (3)!
signature(
group {
- "Start string .*"
- 12
} // (4)!
)
signature(Type("java.util.List")) // (5)!
signature(1.0 withType "java.lang.Float") // (6)!
}
}
- Queries for all calls to
my.fully.qualified.name
that have one argument. - Queries for all calls to
my.fully.qualified.name
with two arguments and where the second argument is 2. - Filters out all calls to
my.fully.qualified.name
with two arguments and where the second argument is 1. - Filters for all calls to
my.fully.qualified.name
with one argument. The argument must contain both"Start string"
and the number 12. An example would bemy.fully.qualified.name("Start string with the number " + 12)
. - Queries for all calls to
my.fully.qualified.name
with one argument which must be of typejava.util.List
1. - Queries for all calls to
my.fully.qualified.name
with one argument which has the value 1.0 and has the typejava.lang.Float
1.
Functions¶
Since each Op
is interpreted by Codyze as one query for function calls to the backend, it would be helpful to have templates for Ops
that find calls to the same function but with different filters. This can be achieved with functions in Coko. The parameters of these functions in Coko can be used to pass the specific values to the filter.
fun add(
element: Any, // (1)!
index: Any? // (2)!
) = op {
"java.util.List.add" {
signature(element)
signature(index withType "int", element) // (3)!
}
}
List.add
in Java is a generic method, so the type ofelement
is not static. However, it is recommended to useAny
even if the type of the value is static since you might want to pass one of the special argument types likeWildcard
.index
is nullable, so it is also possible to filter for calls where no index is given. The type isAny?
to be able to passWildcard
as well.- The arguments can be further specified like the additional filter for the type of
index
.
If the reference to a Coko function is used for order
rules, all parameters must have a nullable type. Coko invokes them with dummy arguments and uses internal functions to query for all calls to the modelled function regardless of the specified signatures.
Interfaces¶
A goal of Coko is to make rules more reusable. Aside from specific rules of an API, there might be some general policies that should be followed for all API that provide similar functionality. An example would be that all actions executed on a database should be logged. Instead of writing the corresponding rule for all combinations of database and logging APIs, one might want to combine this into one reusable rule.
This reusability is achieved through interfaces. In Coko interfaces and their functions describe the functionalities that a group of APIs has in common. Rules can thus be written on a more conceptual level.
When Coko encounters rules using a Coko interface as a parameter, it will use one of the available classes implementing the interface as argument to evaluate the rule. Currently, it uses the first class it finds, however, we will implement an algorithm in the future which will try to find the implementation that fits the best for the analyzed code.
interface Logging {
fun log(message: Any?, vararg args: Any?): Op
}
class JavaLogging: Logging {
override fun log(message: Any?, vararg args: Any?): Op =
op {
"java.util.logging.Logger.info" {
signature {
group {
- message
args.forEach { - it }
}
}
}
}
}
class PythonLogging: Logging {
override fun log(message: Any?, vararg args: Any?): Op =
op {
"logging.info" {
signature(args) { // (1)!
- message
}
}
}
}
- We don't care about the order of the arguments for
args
, just that they appear somewhere as arguments for the call.
Classes¶
Classes in Coko model the actual components of an API. They can implement interfaces or just be normal classes. With classes, API functions can be grouped
For APIs written in object-oriented languages it might be sensible to create a Coko class for each class in the API.