Ratpack: Better support for Promise API

This commit is contained in:
Jonathan Leitschuh
2021-05-11 17:39:29 -04:00
parent cdfdcc66bd
commit b9dc3d0cfe
5 changed files with 290 additions and 26 deletions

View File

@@ -29,8 +29,8 @@ private class RatpackHttpSource extends SourceModelCsv {
row =
["ratpack.handling;", "ratpack.core.handling;"] + "Context;true;parse;" +
[
"(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,O);",
"(com.google.common.reflect.TypeToken,O);", "(ratpack.core.parse.Parse);",
"(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,java.lang.Object);",
"(com.google.common.reflect.TypeToken,java.lang.Object);", "(ratpack.core.parse.Parse);",
"(ratpack.parse.Parse);"
] + ";ReturnValue;remote"
}

View File

@@ -15,31 +15,82 @@ private class RatpackPromiseValueMethod extends Method, TaintPreservingCallable
override predicate returnsTaintFrom(int arg) { arg = 0 }
}
abstract private class SimpleFluentLambdaMethod extends Method {
SimpleFluentLambdaMethod() { getNumberOfParameters() = 1 }
abstract private class FluentLambdaMethod extends Method {
/**
* Holds if this lambda consumes taint from the quaifier when `lambdaArg`
* for `methodArg` is tainted.
* Eg. `tainted.map(stillTainted -> ..)`
*/
abstract predicate consumesTaint(int methodArg, int lambdaArg);
/**
* Holds if the lambda passed at the given `arg` position produces taint
* that taints the result of this method.
* Eg. `var tainted = CompletableFuture.supplyAsync(() -> taint());`
*/
predicate doesReturnTaint(int arg) { none() }
}
private class RatpackPromiseProviderethod extends Method, FluentLambdaMethod {
RatpackPromiseProviderethod() { isStatic() and hasName(["flatten", "sync"]) }
override predicate consumesTaint(int methodArg, int lambdaArg) { none() }
override predicate doesReturnTaint(int arg) { arg = 0 }
}
abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod {
override predicate consumesTaint(int methodArg, int lambdaArg) {
methodArg = 0 and consumesTaint(lambdaArg)
}
/**
* Holds if this lambda consumes taint from the quaifier when `arg` is tainted.
* Eg. `tainted.map(stillTainted -> ..)`
*/
abstract predicate consumesTaint(int arg);
/**
* Holds if the lambda passed produces taint that taints the result of this method.
* Eg. `var tainted = CompletableFuture.supplyAsync(() -> taint());`
*/
predicate doesReturnTaint() { none() }
abstract predicate consumesTaint(int lambdaArg);
}
private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod {
RatpackPromiseMapMethod() {
getDeclaringType() instanceof RatpackPromise and
hasName(["map", "flatMap"])
hasName(["map", "flatMap", "blockingMap"])
}
override predicate consumesTaint(int arg) { arg = 0 }
override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 }
override predicate doesReturnTaint() { any() }
override predicate doesReturnTaint(int arg) { arg = 0 }
}
/**
* Represents the `mapIf` and `flatMapIf` method.
*
* `<O> Promise<O> mapIf(Predicate<T> predicate, Function<T, O> onTrue, Function<T, O> onFalse)`
* `<O> Promise<O> flatMapIf(Predicate<T> predicate, Function<T, Promise<O>> onTrue, Function<T, Promise<O>> onFalse)`
*/
private class RatpackPromiseMapIfMethod extends FluentLambdaMethod {
RatpackPromiseMapIfMethod() {
getDeclaringType() instanceof RatpackPromise and
hasName(["mapIf", "flatMapIf"]) and
getNumberOfParameters() = 3
}
override predicate consumesTaint(int methodArg, int lambdaArg) {
methodArg = [1, 2, 3] and lambdaArg = 0
}
override predicate doesReturnTaint(int arg) { arg = [1, 2] }
}
private class RatpackPromiseMapErrorMethod extends FluentLambdaMethod {
RatpackPromiseMapErrorMethod() {
getDeclaringType() instanceof RatpackPromise and
hasName(["mapError", "flatMapError"])
}
override predicate consumesTaint(int methodArg, int lambdaArg) { none() }
override predicate doesReturnTaint(int arg) { arg = getNumberOfParameters() - 1 }
}
private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod {
@@ -48,16 +99,29 @@ private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod {
hasName("then")
}
override predicate consumesTaint(int arg) { arg = 0 }
override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 }
}
private class RatpackPromiseNextMethod extends FluentMethod, SimpleFluentLambdaMethod {
RatpackPromiseNextMethod() {
private class RatpackPromiseFluentMethod extends FluentMethod, FluentLambdaMethod {
RatpackPromiseFluentMethod() {
getDeclaringType() instanceof RatpackPromise and
hasName("next")
not isStatic() and
exists(ParameterizedType t |
t instanceof RatpackPromise and
t = getDeclaringType() and
t = getReturnType()
)
}
override predicate consumesTaint(int arg) { arg = 0 }
override predicate consumesTaint(int methodArg, int lambdaArg) {
hasName(["next"]) and methodArg = 0 and lambdaArg = 0
or
hasName(["cacheIf"]) and methodArg = 0 and lambdaArg = 0
or
hasName(["route"]) and methodArg = [0, 1] and lambdaArg = 0
}
override predicate doesReturnTaint(int arg) { hasName(["flatMapIf"]) and arg = 1 }
}
private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep {
@@ -70,10 +134,13 @@ private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep {
* Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`.
*/
predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma, SimpleFluentLambdaMethod sflm, int arg | sflm.consumesTaint(arg) |
ma.getMethod() = sflm and
exists(MethodAccess ma, FluentLambdaMethod flm, int methodArg, int lambdaArg |
flm.consumesTaint(methodArg, lambdaArg)
|
ma.getMethod() = flm and
node1.asExpr() = ma.getQualifier() and
ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(arg) = node2.asParameter()
ma.getArgument(methodArg).(FunctionalExpr).asMethod().getParameter(lambdaArg) =
node2.asParameter()
)
}
@@ -82,13 +149,13 @@ private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep {
* method access result `node2`.
*/
predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) {
exists(SimpleFluentLambdaMethod sflm, MethodAccess ma, FunctionalExpr fe |
sflm.doesReturnTaint()
exists(FluentLambdaMethod flm, MethodAccess ma, FunctionalExpr fe, int arg |
flm.doesReturnTaint(arg)
|
fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and
ma.getMethod() = sflm and
ma.getMethod() = flm and
node2.asExpr() = ma and
ma.getArgument(0) = fe
ma.getArgument(arg) = fe
)
}
}

View File

@@ -4,6 +4,7 @@ import ratpack.core.form.Form;
import ratpack.core.form.UploadedFile;
import ratpack.core.parse.Parse;
import ratpack.exec.Promise;
import ratpack.func.Action;
import java.io.OutputStream;
class Resource {
@@ -100,5 +101,92 @@ class Resource {
.then(form -> {
sink(form); //$hasTaintFlow
});
ctx
.parse(Form.class, "Some Object")
.then(form -> {
sink(form); //$hasTaintFlow
});
}
void test7() {
String tainted = taint();
Promise
.flatten(() -> Promise.value(tainted))
.next(value -> {
sink(value); //$hasTaintFlow
})
.onError(Action.noop())
.next(value -> {
sink(value); //$hasTaintFlow
})
.cache()
.next(value -> {
sink(value); //$hasTaintFlow
})
.fork()
.next(value -> {
sink(value); //$hasTaintFlow
})
.route(value -> {
sink(value); //$hasTaintFlow
return false;
}, value -> {
sink(value); //$hasTaintFlow
})
.next(value -> {
sink(value); //$hasTaintFlow
})
.cacheIf(value -> {
sink(value); //$hasTaintFlow
return true;
})
.next(value -> {
sink(value); //$hasTaintFlow
})
.onError(RuntimeException.class, Action.noop())
.next(value -> {
sink(value); //$hasTaintFlow
})
.then(value -> {
sink(value); //$hasTaintFlow
});
}
void test8() {
String tainted = taint();
Promise
.sync(() -> tainted)
.mapError(RuntimeException.class, exception -> {
sink(exception); // no taint
return "potato";
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value("potato")
.mapError(RuntimeException.class, exception -> {
return taint();
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value(tainted)
.flatMapError(RuntimeException.class, exception -> {
sink(exception); // no taint
return Promise.value("potato");
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value("potato")
.flatMapError(RuntimeException.class, exception -> {
return Promise.value(taint());
})
.then(value -> {
sink(value); //$hasTaintFlow
});
}
}

View File

@@ -17,7 +17,9 @@
package ratpack.exec;
import ratpack.func.Action;
import ratpack.func.Factory;
import ratpack.func.Function;
import ratpack.func.Predicate;
/**
* A promise for a single value.
@@ -43,6 +45,15 @@ import ratpack.func.Function;
*/
@SuppressWarnings("JavadocReference")
public interface Promise<T> {
static <T> Promise<T> sync(Factory<T> factory) {
return null;
}
static <T> Promise<T> flatten(Factory<? extends Promise<T>> factory) {
return null;
}
static <T> Promise<T> value(T t) {
return null;
}
@@ -54,4 +65,52 @@ public interface Promise<T> {
void then(Action<? super T> then);
Promise<T> next(Action<? super T> action);
default <E extends Throwable> Promise<T> onError(Class<E> errorType, Action<? super E> errorHandler) {
return null;
}
default Promise<T> onError(Action<? super Throwable> errorHandler) {
return null;
}
default Promise<T> mapError(Function<? super Throwable, ? extends T> transformer) {
return null;
}
default <E extends Throwable> Promise<T> mapError(Class<E> type, Function<? super E, ? extends T> function) {
return null;
}
default Promise<T> mapError(Predicate<? super Throwable> predicate, Function<? super Throwable, ? extends T> function) {
return null;
}
default Promise<T> flatMapError(Function<? super Throwable, ? extends Promise<T>> function) {
return null;
}
default <E extends Throwable> Promise<T> flatMapError(Class<E> type, Function<? super E, ? extends Promise<T>> function) {
return null;
}
default Promise<T> flatMapError(Predicate<? super Throwable> predicate, Function<? super Throwable, ? extends Promise<T>> function) {
return null;
}
default Promise<T> route(Predicate<? super T> predicate, Action<? super T> action) {
return null;
}
default Promise<T> cache() {
return null;
}
default Promise<T> cacheIf(Predicate<? super T> shouldCache) {
return null;
}
default Promise<T> fork() {
return null;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ratpack.func;
/**
* An object that creates another.
*
* Factories are expected to create a new object each time.
* Implementors should explain there behaviour if they do not do this.
*
* @param <T> the type of object that this factory creates
*/
@FunctionalInterface
public interface Factory<T> {
/**
* Creates a new object.
*
* @return a newly created object
* @throws Exception any
*/
T create() throws Exception;
/**
* Creates a factory that always returns the given item.
*
* @param item the item to always provide
* @param <T> the type of the item
* @return a factory that always returns {@code item}
* @since 1.1
*/
static <T> Factory<T> constant(T item) {
return () -> item;
}
}