Java: Initial implementation of type based model generation.

This commit is contained in:
Michael Nebel
2022-09-29 13:45:28 +02:00
parent eea062d357
commit bdc569feea
3 changed files with 244 additions and 1 deletions

View File

@@ -67,6 +67,8 @@ private predicate isRelevantForModels(J::Callable api) {
*/
predicate isRelevantForDataFlowModels = isRelevantForModels/1;
predicate isRelevantForTypeBasedFlowModels = isRelevantForModels/1;
/**
* A class of Callables that are relevant for generating summary, source and sinks models for.
*
@@ -175,7 +177,7 @@ predicate isRelevantType(J::Type t) {
*/
string qualifierString() { result = "Argument[-1]" }
private string parameterAccess(J::Parameter p) {
string parameterAccess(J::Parameter p) {
if
p.getType() instanceof J::Array and
not isPrimitiveTypeUsedForBulkData(p.getType().(J::Array).getElementType())

View File

@@ -0,0 +1,238 @@
private import java
private import semmle.code.java.Collections
private import CaptureModelsSpecific as Specific
private import CaptureModels
/**
* TODO: We might just inline this instead.
* Holds if `t` is a collection of type `tv` (eg. `List<T>`)
*/
private predicate genericCollectionType(CollectionType t, TypeVariable tv) {
t.getElementType() = tv
}
/**
* Holds if `tv` is a type variable of the immediate type declaring `callable`.
*/
private predicate classTypeParameter(Callable callable, TypeVariable tv) {
callable.getDeclaringType().(GenericType).getATypeParameter() = tv
}
/**
* Holds if `tv` is type variable of `callable` or the type declaring `callable`.
*/
private predicate localTypeParameter(Callable callable, TypeVariable tv) {
classTypeParameter(callable, tv) or
callable.(GenericCallable).getATypeParameter() = tv
}
/**
* Holds if `callable` has a type parameter `tv`
* or collection parameterized over type `tv`.
*/
private predicate parameter(Callable callable, string input, TypeVariable tv) {
exists(Parameter p |
input = Specific::parameterAccess(p) and
p = callable.getAParameter() and
(
// Parameter of type tv
p.getType() = tv
or
// Parameter is a collection of type tv
genericCollectionType(p.getType(), tv)
)
)
}
/**
* Gets the string representation of a synthetic field corresponding to `tv`.
*/
private string getSyntheticField(TypeVariable tv) {
result = ".SyntheticField[ArgType" + tv.getIndex() + "]"
}
/**
* Gets a models as data string representation of, how a value of type `tv`
* can be read or stored implicitly in relation to `callable`.
*/
private string implicit(Callable callable, TypeVariable tv) {
classTypeParameter(callable, tv) and
exists(string access |
if genericCollectionType(callable.getDeclaringType(), tv)
then access = ".Element"
else access = getSyntheticField(tv)
|
result = Specific::qualifierString() + access
)
}
/**
* A class of types that represents functions.
*/
private class Function extends ParameterizedType {
Function() {
exists(FunctionalInterface fi |
fi = this.getGenericType() and
fi.hasName("Function")
)
}
/**
* Gets the parameter type of `this` function.
*/
Type getParameterType() { result = this.getTypeArgument(0) }
/**
* Get the return type of `this` function.
*/
Type getReturnType() { result = this.getTypeArgument(1) }
}
/**
* Holds if `callable` has a functional interface parameter `fi` at parameter position `position`.
*/
private predicate functional(Callable callable, Function f, int position) {
exists(Parameter p |
p = callable.getAParameter() and
f = p.getType() and
position = p.getPosition()
)
}
/**
* Gets models as data input/output access relative to the type parameter `tv` in the
* type `t` in the scope of `callable`.
*
* Note: This predicate has to be inlined as `callable` is not related to `return` or `tv`
* in every disjunction.
*/
bindingset[callable]
private string getAccess(Callable callable, Type return, TypeVariable tv) {
return = tv and result = ""
or
genericCollectionType(return, tv) and result = ".Element"
or
not genericCollectionType(return, tv) and
(
return.(ParameterizedType).getATypeArgument() = tv
or
callable.getDeclaringType() = return and return.(GenericType).getATypeParameter() = tv
) and
result = getSyntheticField(tv)
}
/**
* Holds if `input` is a models as data string representation of, how a value of type `tv`
* (or a generic parameterized over `tv`) can be generated by a functionalinterface parameter of `callable`.
*/
private predicate functionalSource(Callable callable, string input, TypeVariable tv) {
exists(Function f, int position, Type return, string access |
functional(callable, f, position) and
return = f.getReturnType() and
access = getAccess(callable, return, tv) and
input = "Argument[" + position + "].ReturnValue" + access
)
}
/**
* Holds if `input` is a models as data string representation of, how a
* value of type `tv` (or a generic parameterized over `tv`)
* can be provided as input to `callable`.
* This includes
* (1) The implicit synthetic field(s) of the declaring type of `callable`.
* (2) The parameters of `callable`.
* (3) Any delegate parameters of `callable`.
*/
private predicate input(Callable callable, string input, TypeVariable tv) {
input = implicit(callable, tv)
or
parameter(callable, input, tv)
or
functionalSource(callable, input, tv)
}
/**
* Holds if `callable` returns a value of type `tv` (or a generic parameterized over `tv`) and `output`
* is a models as data string representation of, how data is routed to the return.
*/
private predicate returns(Callable callable, TypeVariable tv, string output) {
exists(Type return, string access | return = callable.getReturnType() |
access = getAccess(callable, return, tv) and
output = "ReturnValue" + access
)
}
/**
* Holds if `callable` has a functional interface parameter that accepts a value of type `tv`
* and `output` is the models as data string representation of, how data is routed to
* the delegate parameter.
*/
private predicate functionalSink(Callable callable, TypeVariable tv, string output) {
exists(Function f, int position |
functional(callable, f, position) and
tv = f.getParameterType() and
output = "Argument[" + position + "]" + ".Parameter[0]"
)
}
/**
* Holds if `output` is a models as data string representation of, how values of type `tv`
* (or generics parameterized over `tv`) can be routed.
* This includes
* (1) The implicit synthetic field(s) of the declaring type of `callable`.
* (2) The return of `callable`.
* (3) Any delegate parameters of `callable`.
*/
private predicate output(Callable callable, TypeVariable tv, string output) {
output = implicit(callable, tv)
or
returns(callable, tv, output)
or
functionalSink(callable, tv, output)
}
/**
* A class of callables that are relevant generating summaries for based
* on the Theorems for Free approach.
*/
class TypeBasedFlowTargetApi extends Specific::TargetApiSpecific {
TypeBasedFlowTargetApi() { Specific::isRelevantForTypeBasedFlowModels(this) }
/**
* Gets the string representation of all type based summaries for `this`
* inspired by the Theorems for Free approach.
*
* Examples could be (see C# pseudo code below)
* (1) `Get` returns a value of type `T`. We assume that the returned
* value was fetched from a (synthetic) field.
* (2) `Set` consumes a value of type `T`. We assume that the value is stored in
* a (synthetic) field.
* (3) `Apply<S>` is assumed to apply the provided function to a value stored in
* a (synthetic) field and return the result.
* (4) `Apply<S1,S2>` is assumed to apply the provided function to provided value
* and return the result.
* ```csharp
* public class MyGeneric<T> {
* public void Set(T x) { ... }
* public T Get() { ... }
* public S Apply<S>(Func<T, S> f) { ... }
* public S2 Apply<S1, S2>(S1 x, Func<S1, S2> f) { ... }
* }
* ```
*/
string getSummaries() {
exists(TypeVariable tv, string input, string output |
localTypeParameter(this, tv) and
input(this, input, tv) and
output(this, tv, output) and
input != output
|
result = asValueModel(this, input, output)
)
}
}
/**
* Returns the Theorems for Free inspired typed based summaries for `api`.
*/
string captureFlow(TypeBasedFlowTargetApi api) { result = api.getSummaries() }